Friday 27 February 2009

Spell as you Type 1.0 plug-in released

Today I released the Spell as you Type jQuery plug-in. It provides inline (red wavy) spell checking and correction options to Internet Explorer users - functionality that is built into every other browser.
Jquery: http://plugins.jquery.com/project/spellayt
To use, simply call the spellayt function in jQuery
$(document).ready(function() {
$('#txtTextarea').spellayt();
})
The plug-in works fully on the client side. For more information, view my series on building this plug-in:

Monday 23 February 2009

Referencing a template file from a Visual Studio Unit Test

As part of the test suite for the Simple OOXML project, I need to reference a .docx file in a template folder in code - the problem being that every unit test is run in it's own output folder. The solution is to copy the template files to the output folder and retrieve the path from the test context:
1. Make sure the TestContext is set when the test is run by providing a TestContext property (this is now really simple in 3.5)
    [TestClass()]
    public class SpreadsheetTests
    {
        public TestContext TestContext { get; set; }
        ...
2. Make sure that the requested file is copied to the output folder by using the DeploymentItem attribute
    [TestMethod(), DeploymentItem("Templates\\template.xlsx")]
    public void WorksheetCopyTest()
    {   ...
3. Reference from code using the TestContext.TestDeploymentDir
MemoryStream stream = SpreadsheetReader.Copy(string.Format("{0}\\template.xlsx", TestContext.TestDeploymentDir));
Note that even though we copied the template.xlsx file from the Templates folder, it ended up in the root output folder.

Thursday 19 February 2009

Writing an xlsx document to the response stream using ASP.NET and OOXML

Have spent a most frustrating day trying to output an Excel document created using OOXML sdk 2.0 in a web service to the web browser.
There are two elements to solving this problem>
  1. Using a web service to transfer binary data
  2. Opening Excel on the browser using the new xlsx format
This is actually pretty straight forward, as long as you use a byte array, the data is base64 encoded in the background automatically. On the client, use the right content type and use BinaryWrite instead of Write which is where I was having the problem:
Public Function PerformanceReportByUser(ByVal startDate As Date, ByVal endDate As Date) As Byte()
'Get the path to the template
Dim path As String = Server.MapPath("Templates/PerformanceByUserTemplate.xlsx")

'Get the stream containing the report package
Dim stream As MemoryStream = PerformanceReport.ExecuteByUser(path, startDate, endDate)
Return stream.ToArray()
End Function
On the client, process the byte array. Note the content type, and use of BinaryWrite.
Dim bytes() As Byte = portalservice.PerformanceReportByUser(CDate(ctlStartDate.Text), CDate(ctlEndDate.Text))

'Send response with content type to display as MS Excel
Response.Clear()
Response.Buffer = True
Response.AddHeader("content-disposition", String.Format("attachment;filename={0}", "performance.xlsx"))

'The following directive causes a open/save/cancel dialog for Excel to be displayed
Response.Cache.SetCacheability(HttpCacheability.Private)
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

'Write to response
Response.BinaryWrite(bytes)

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

Tuesday 10 February 2009

Creating a Spell as you type jQuery plugin - Part 2

In my previous post, I outlined solutions to the technical challenges I faced trying to implement a complete client side browser based spell checking solution.
With the technical challenges sorted, I looked into writing a JQuery plug-in. The tutorials on the JQuery website are a good place to start. I also found the plugin development pattern from Mike Alsup extremely useful. It helps you set up a closure to keep your methods and variables private as well as setting up defaults and options (passed in as a JSON). I won’t go into these details here as they are covered so well in the articles I’ve referenced here.
Detecting non-IE browsers in your plug-in. The first task once the structure of the plug-in was in place and the options set up was to block non IE clients from using the plug-in, or more simply to exit the main function without executing any additional code. This was made very simple by using the following built-in JQuery code
//This plugin is only for IE 6.0 + users
if (!.browser.msie) return;
if (.browser.version <> 6) return;
Loading the dictionary using JQuery AJAX
The next item of code I wanted to tackle was getting the text-based dictionary into a structure the plug-in could understand and use. The $.ajax function provided an asynchronous way of retrieving data from the server. This is useful, because you don’t want the interface to be blocked whilst you potentially download a large file.
The success function passes the data to the loadDictionary function. When successful, the .fn.spellayt.loaded() function is called if it has been set on the plug-in. This is similar the event model used in c# programming. Note that this is how function pointers are exposed outside of the plug-in.
The loadDictionary function loads the words in the dictionary into a three dimensional array, consisting of an array for the first letter of the word, the length and the matching words. In this way, the words starting with the same letter and of the same length can be checked very quickly, as they will most likely have the closest Levenshtein distance.
//Load the dictionary from a url
$.ajax({
  type: "GET",
  url: $.fn.spellayt.global.options.url,
  dataType: "text",
  success: function(data) {

    //Load the dictionary into the dictionaryArray.
    loadDictionary(data);
    if ($.fn.spellayt.loaded != null) $.fn.spellayt.loaded();

  },

  error: function(XMLHttpRequest, textStatus, errorThrown) {
    if ($.fn.spellayt.loadError != null) fn.spellayt.loadError(textStatus);
  }
Highlighting words that have been misspellt
When the input gains focus, a few things need to happen on a regular basis as the user types
  • find new words to check
  • check if a word exists in the dictionary
  • Highlight misspellt words

This is done by using the setTimer and setInterval functions – the difference between them is minimal - the one fires a function every x milliseconds, the other calls a function once after the next x seconds.

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

var g = $.fn.spellayt.global;
if (g.ready) {
 g.current = this;

 //Set a timer to break words every 1 second
 g.breakTimer = setInterval(function() {
   if (g.wordQueue.length == 0) breakText(g.current); }, 1000);

 //Set a timer to highlight words every 1/2 second
 g.highlight = setInterval(function() {
   $.fn.spellayt.highlight(g.current); }, 500);

 //Start the the word breaking queue
 doWordBreak()
}
});
The sentence and word breaking is done by the use of regular expression parameters – this is incredibly useful – a word breaking regex can be set as a parameter for e.g. a different language.

In the next part of this series, Ill look into the problems I faced when the processor intensive code for word breaking and checking blocked the UI and how to emulate a multi-threaded environment in the current version of javascript.