Monday 23 March 2009

Exclusive Checkbox jQuery Plug-in

Web interfaces like Hotmail often have a checkbox or radio button next to each item so that you can select that row. I've always disliked the radio button approach as you are limited to one row at a time eg when deleting. However sometimes you'd like to keep the checkboxes but have them behave like radio buttons. I couldn't find a really simple jQuery plug-in so I decided to write one. Original and minified versions at the end of the article. 

(Update: I changed the code slightly to use the official jQuery extend function for the plug-in definition, and to include the usage in the comments.)

/*
* Exclusive Check Plugin v1.0.2
* Copyright (c) James Westgate
*
* @requires jQuery v1.3.2
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.ph
*   http://www.gnu.org/licenses/gpl.html
*
* @usage $('input:checked').exclusiveCheck();
* @usage $('form input:checked').exclusiveCheck();
* @usage $('table tbody input:checked').exclusiveCheck();
*
*/

//Create closure
(function($) {

    //Plugin definition
    $.fn.extend({

        exclusiveCheck: function() {

            var selector = $(this);

            //Loop through each item in the matched set and apply event handlers
            return this.each(function(i) {

                //When the checkbox gets clicked, uncheck other checkboxes
                $(this).click(function(event) {

                    var clicked = this;

                    //Uncheck all except current
                    if (this.checked) {
                        selector.each(function() {
                            if (this != clicked) this.checked = false;
                        });
                    }
                });
            });
        }
    });

// end of closure
})(jQuery);

Download original and minified version.

Thursday 5 March 2009

Introducing the Simple OOXML library

The new xml based document formats (.xlsx, .docx, .pptx etc) introduced with Office 2007 finally provided developers with the ability to create documents on a server without having to have either MS Word or Excel installed or to use a 3rd party component.

However producing this Xml is very complicated and it's certainly not a pretty format. The OOXML SDK goes as far as wrapping the Xml elements of the specification into a set of .net classes, but still falls short of the higher-level functionality required to actually create documents. Some open source libraries, such as ExcelPackage, are incomplete or discontinued and I wanted to create a simple, robust fully supported library that fits right into the object model of version 2.0 of the sdk.

The http://simpleooxml.codeplex.com project addresses this issue by providing a layer of abstraction over version 2.0 as a set of simple classes and methods to create new spreadsheet (Excel) and word processing (Word) files with the following benefits:

  1. No Excel or Word is required on the server.
  2. No in-depth knowledge of the OOXML standard or SDK is required.
  3. New documents can be created or existing templates can be modified.
  4. Ability to stream directly to the browser or between servers
  5. High performance
The following table outlines the classes used to create and read office open xml documents:

DocumentReader

Static functions used to create new word processing documents.

DocumentWriter

Static functions used to paste text items into a word processing document using bookmarks and save the document to a stream or file.

SpreadsheetReader

Static functions to create new spreadsheet documents and to retrieve worksheet, column and row references. Get style and defined name range parts.

SpreadsheetStyle

Create an object that can set style, color and font information in a spreadsheet and retrieve and compare style parts.

SpreadsheetWriter

Static functions to create style parts in a spreadsheet, control shared strings and helper functions when working with spreadsheet documents

WorksheetReader

High level and static fucntions to retrieve cell and style information from a worksheet.

WorksheetWriter

High level and static functions to write text, numeric and datatable based values to a worksheet. Draw borders, insert rows, merge cells and set print areas.

Simple OOXML requires version 2.0 of the OOXML SDK which can be found here:
In my next series of posts, I'll show how easy and powerful this library is.

(The DocumentFormat.OpenXml.Extensions.Testing unit test project included in the source on Codeplex contains samples of almost every type of operation supported by the library.)

Tuesday 3 March 2009

Creating a Spell as you type jQuery plug-in - Part 3

In my previous posts, I described how to create a jQuery plug-in called spellayt (Spell as you Type) that provided spelling correction to Internet Explorer users.

The timed work queue pattern

The performance of initial implementations of the spellayt were disappointing, you would often wait a few seconds whilst the latest words typed in were checked and highlighted. Even worse, you could get the dreaded "A script on this page is causing Internet Explorer to run slowly." message. I soon found I needed a solution usng a multi-threaded approach so that the spell checking could happen in the background to the user typing. A quick check confirmed that JavaScript does not support more than one thread although the search did reveal some interesting options.

Because the word breaking and dictionary checks could potentially take a few seconds each, I decided that only one word could be checked against the dictionary at a time. The trick is to use lots of short functions at regular intervals, so that the UI can continue to process events from the user.

This gave me the idea for implementing the following pattern which I call the Timed Work Queue pattern. In the plug-in, a call to doWordBreak() starts the ball rolling when the input receives focus:

//Global values
$.fn.spellayt.global = {
  options: null,         //plug-in options
  wordQueue: new Array() //word breaking queue
};

$(this).focus(function(event) {

  //Start the the word breaking queue
  doWordBreak();
});

The doWordBreak() function is actually really straight forward. It shifts the oldest work item off the queue and executes that item. The function then calls itself again in a predetermined time (50 ms seems to be a good figure)

//Pops the next word off the word breaking queue
function doWordBreak() {
  var g = $.fn.spellayt.global;

  //Get an item off the queue
  if (g.wordQueue.length > 0) {
     var work = g.wordQueue.shift();
     work.call(work.data);
  }

  //Process the next item
  g.wordTimer = setTimeout(function() { doWordBreak(); }, g.options.milliseconds);
}

So what does the object look like that we are pushing onto the work queue ? The breaktext function breaks text into sentences and words, and loads words onto the queue one at a time

//Check the spelling for all words in the input provided
function breakText(input) {
  if (input == null) return;

  //Split text into a 2d array of sentences and words
  var sentences = splitWords(input.value);

  if (sentences == null) return;

  //Add the call to checkWord() to the work queue for each word in each sentence
  for (var i = 0; i < sentences.length; i++) {

    $.fn.spellayt.global.wordQueue.push({ call: function(parm) { checkSentence(parm); }, data: sentences[i] });

  }
};

The object consists of a function pointer call and data to be passed to the function data. As long as your function has a single parameter (e.g. a JSON object), you could queue up any combination of functions to execute.

This is really handy as you can push completely different function pointers onto the queue, as well as putting some items ahead of others with a higher priority.

Compress ASP.NET response streams

When sending large amounts of data from IIS to the browser, it is sometimes worth compressing certain types of data such as text documents. Although compression is not supported by IIS 6.0, most browsers support basic gzip compression and they notify the server of this ability by sending a header in each request. The following piece of code shows how to use the System.IO.Compression namespace to add a filter to the output stream that compresses the output whilst checking and setting the correct headers. In this example, a .xls document containing in a string is being sent to the client:

    'Send response with content type to display as MS Excel
    context.Response.Clear()
    context.Response.Buffer = True

    context.Response.AddHeader("content-disposition", String.Format( "attachment;filename={0}", fileName))
    context.Response.ContentEncoding = Encoding.UTF8

    context.Response.Cache.SetCacheability(HttpCacheability.Private)

    'Compress the output as it may be very large
    'When flushing or closing+ending the stream, the compression filter does not have a chance to write the compression footer
    'Therefore, make sure the compression filter stream is closed before flushing
    AddCompression(context)

    context.Response.ContentType = "application/vnd.ms-excel"

    'Write to response
    context.Response.Write(_reportXmlss)

    'context.Response.Flush() 'Do not flush if using compression
    'context.Response.Close()
    context.Response.End()

The AddCompression method checks the appropriate headers and adds a compression filter stream to the output:
'Add compression to the response stream
    Public Sub AddCompression(ByVal context As HttpContext)

        Dim acceptEncoding As String = context.Request.Headers("Accept-Encoding")
        If acceptEncoding Is Nothing OrElse acceptEncoding.Length = 0 Then Return

        'Convert to lower to check
        acceptEncoding = acceptEncoding.ToLower

        'Gzip or Compress compression
        'Compress compression is quicker and performs better compression so try that first
        If (acceptEncoding.Contains("deflate")) Then

            context.Response.Filter = New DeflateStream(context.Response.Filter, CompressionMode.Compress)
            context.Response.AppendHeader("Content-Encoding", "deflate")

        ElseIf acceptEncoding.Contains("gzip") Then

            context.Response.Filter = New GZipStream(context.Response.Filter, CompressionMode.Compress)
            context.Response.AppendHeader("Content-Encoding", "gzip")

        End If

    End Sub
To check if compression is being used, I use the awesome HttpWatch, which shows useful information such as headers, amount of compression and bytes sent/received.

Monday 2 March 2009

Combining jQuery and UpdatePanels

Some of you may have noticed using Microsoft Ajax and jQuery together works fine until you do a post back in e.g. an UpdatePanel and the jQuery plugins referencing elements contained in the panel stop working. I believe these are the event handlers which are bound to the old DOM elements by jQuery.
A simple work around is to combine a bit of Microsoft AJAX with jQuery - in this example I rerun all my jQuery that would normally reside in $(document).ready after each ajax callback instead.
//Jquery document ready
$(document).ready(function(){
  RunScript();
})

//This is called after every page load by ajax
//It is used instead of the normal document ready function
function pageLoad(sender, arg) {
  if (arg.get_isPartialLoad()) {
      RunScript();
  };
}

//Main Jquery function
function RunScript() {
 //... normal jQuery code here etc