≡ Menu
Deep in the Code

Free Spell Checker for TextBoxes

One of the hardest things for me to find coherent information on has been how to set up spell checking for multi-line TextBoxes on ASP.NET forms.  In my search, I came across Hunspell, an open source spell checker that is widely used in applications such as OpenOffice.

To set up Hunspell for use in your ASP.NET Web site, you will need to complete these steps.  Download the most recent version (currently 1.3.2) from the Hunspell download page and extract the archive file.

  1. Place the hunspellx64.dll and/or hunspellx86.dll (depending on 32- or 64-bit IIS process), NHunspell.dll, and language-appropriate dictionary files (for English – en_US.aff, en_US.dic, and hyph_en_US.dic) into the “bin” folder of your Web site.
  2. Create the SpellCheckerService Web Service.

Create a folder under the root named WebServices and add SpellCheckerService.asmx:

<%@ WebService Language="VB" CodeBehind="~/App_Code/SpellCheckerService.vb" %>

In the App_Code folder, add SpellCheckerService.vb:

Imports NHunspell
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Web
Imports System.Web.Script.Services
Imports System.Web.Services
Imports System.Web.Services.Protocols

' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class SpellCheckerService
Inherits System.Web.Services.WebService

<WebMethod()> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
Public Function CheckSpelling(words As List(Of String)) As Dictionary(Of String, List(Of String))

Dim results As Dictionary(Of String, List(Of String)) = New Dictionary(Of String, List(Of String))
Dim spellEngine As SpellEngine = CType(Application("SpellEngine"), SpellEngine)
'Check spelling of each word that has been passed to the method
For Each word As String In words
Dim correct As Boolean = spellEngine("en").Spell(word)
'If the word is spelled incorrectly, add it to the list of words to be
'returned along with suggestions
If Not correct Then
Dim suggestions As List(Of String) = spellEngine("en").Suggest(word)
suggestions.Sort()
results.Add(word, suggestions)
End If
Next
Return results
End Function

End Class
  1. Put the spellchecker.css file into a folder called css directly under the root and put this line into the head section of the markup file (for the page where the TextBox to be spell-checked is):

<link href="~/css/SpellChecker.css" type="text/css" rel="stylesheet"/>

  1. Place the jQuery files from the Hunspell download into a folder called js under the root and insert this JavaScript into the head section of your markup. For my folder names to work, minor changes were necessary for the jquery.spellcheker.js and jquery.spelldialog.js files, so the text of those files (as modified by me) will be included at the bottom of this post.
<script type="text/javascript" src="../js/jquery-1.4.2.js"></script>
<script type="text/javascript" src="../js/jquery.spellchecker.js"></script>
<script type="text/javascript" src="../js/jquery.spelldialog.js"></script>
<script type="text/javascript">
var SpellCheckOptions = { learn: false, serverScriptURL: '../WebServices/SpellCheckerService.asmx' };

function spellCheckTextArea(targetId) {
var $textarea = $(targetId);

//this callback will be called when the spellcheck is
//complete. In this case we set the value of the target
//textarea to the corrected text.
var callback = function (textInfos) {
$textarea.val(textInfos[0].text);
}

var spellcheck = JQSpellCheckDialog.create({ text: $textarea.val() }, callback, SpellCheckOptions);
spellcheck.open();
}
</script>
  1. Next to the TextBox (I’ll call it TextBox1 here) that you want to have associated with the spell checker, place a Button (called Button1 here). In your code-behind, enter this into the Page.Load event:
Button1.Attributes.Add("OnClick", "spellCheckTextArea('#" & TextBox1.ClientID & "');return false;")

And that’s it.

Code for the jQuery files is below!

jquery.spellchecker.js:

////////////////////////////////////////////////////
// spellcheck.js
//
// SpellCheck object
//
// This file is sourced on web pages that have a textarea object to evaluate
// for spelling. It includes the implementation for the SpellCheck object.
// 
// Author: Lenny Marks
// Date: 10.08.2008
//
// Modified by David Young on 05.31.2012
//
////////////////////////////////////////////////////


/**
 * A SpellCheck object is created with one or more <i>textObjects</i>
 * and a callback which is invoked once the checking has been completed.
 * 
 * Each textObject must minimally include a text property which will reflect
 * the corrected text when the callback is invoked.
 * 
 * eg.
 * textObjects = [{text: $textarea.val()}, ...]
 * 
 * Each textObject can additionally include other properties such as contentType 
 * for use by the spellchecking program and identifying info to be picked up
 * when the spellcheck is complete.
 * 
 * See demo for examples.
 *  
 * SpellCheck must be <i>configure</i>d with a view and a remote
 * spellcheck handler. 
 * 
 * ExampleView = function() {
 *   //called when the SpellCheck is opened
 *   this.open = function() {}; 
 *   
 *   //called each time a remote spellcheck is about to start
 *   //can be used to display loading message
 *   this.checkingStarted = function(textObject) {}  
 *   
 *   //invoked each time the position context has changed with
 *   //the current position <i>SpellCheck.Context</i> object.                                       
 *   this.update = function(context, enableUndo, skipEnabled) {} 
 *                                                         
 * }
 * 
 * The remote check handler is expected to be a function that accepts a textObject
 * and a callback. The callback should be invoked with a hash of misspelled words
 * once the remote check has returned. The misspelled words hash should be made
 * up of 'misspelledword' => ['list', 'of', 'suggestions'] pairs.
 * 
 * ex. from the JQuery implementation 
 * 
 * var remoteCheckHandler = function(textObject, callback) {
 *      jQuery.post(options.serverScriptURL, { 'text' : textObject.text }, function(result) {            
 *            callback(result);
 *      }, 'json');
 * };
 */
function SpellCheck(textObjects, spellCheckCompletedCallback) {
    SpellCheck.prototype.configure = function(view, server) {
        var self = this;
        
        self.view = view;
        self.server = server;
        
        var checkStartingCallback = function(textObjectIndex) {
            self.view.checkingStarted(self._textObjects[textObjectIndex]);
        };
        
        self._misspelledWords = new SpellCheck.internal.MisspelledWords(server, checkStartingCallback);
        
        self._contextManager = new SpellCheck.internal.ContextManager(self._textObjects, self._misspelledWords, function() { 
            var context = self._contextManager.getContext();
            
            if(context.word && self.isIgnored(context.word)) {
               self._contextManager.step();
            } else {
                self._updateView();
            }
        });
        
    }
    
    SpellCheck.prototype.open = function() {
        this.view.open(); 
            
        this._contextManager.step();
       
    }
    
    SpellCheck.prototype.close = function() {
        this.view.close(); 
            
        spellCheckCompletedCallback(this._textObjects);
       
    }
     
    SpellCheck.prototype._updateView = function() {
        var context = this._contextManager.getContext();
        
        var textObjects = this._textObjects;
        
        var canUndo = this._lastAction != null;
        var canSkip = context.textObjectIndex < textObjects.length - 1;
        
        this.view.update(textObjects[context.textObjectIndex], context, canUndo, canSkip);
    }
    
    SpellCheck.prototype.ignore = function() {
        this._lastAction = new SpellCheck.internal.IgnoreAction(this._contextManager.getContext());
        this._contextManager.step();
    }
    
    SpellCheck.prototype.learn = function() {
       
        var self = this;
        
        var context = self._contextManager.getContext();
        
        self.server.learn(context.word, function() {
            self._contextManager.step();
        });
        
      
    }
    
    SpellCheck.prototype.ignoreAll = function() {
        this._lastAction = new SpellCheck.internal.IgnoreAllAction(this._ignoredWords, this._contextManager.getContext());
        this._contextManager.step();
    }
    
    SpellCheck.prototype.undo = function() {
        if(this._lastAction) {
            this._contextManager.setContext(this._lastAction.undo());
            this._updateView();
        }
        
    }
    
    SpellCheck.prototype.replace = function(replacement) {
      this._lastAction = new SpellCheck.internal.ReplaceAction(this._textObjects, this._contextManager.getContext(), replacement);
      this._contextManager.step();
    }
    
    SpellCheck.prototype.replaceAll = function(replacement) {
      this._lastAction = new SpellCheck.internal.ReplaceAllAction(this._textObjects, this._contextManager.getContext(), replacement);
      this._contextManager.step();
    }
    
    SpellCheck.prototype.skip = function() {
       this._contextManager.skip();
    }
    
    SpellCheck.prototype.isIgnored = function(word) {
        return this._ignoredWords[word];
    }
    
    SpellCheck.prototype.textObject = function() {
        var context = this._contextManager.getContext();
        return this._textObjects[context.textObjectIndex];
    }
    
    SpellCheck.prototype.getSuggestions = function(word) {
        var context = this._contextManager.getContext();
        return this._misspelledWords.getSuggestions(this.textObject(), word);
    }
    
    SpellCheck.prototype.isMisspelled = function(word) {
        var context = this._contextManager.getContext();
        return this._misspelledWords.isMisspelled(this.textObject(), word);
    }
    
    SpellCheck.prototype.replaceMisspelled = function(startIndex, replaceWithBlock) {
        
        var replacedText = '';
        
        var textObject = this.textObject();
        
        var text = textObject.text;
        
        var self = this;
        
        SpellCheck.eachWord(textObject, startIndex, function(word, wordIndex) {
            replacedText += text.substring(startIndex, wordIndex);
            
            if(self.isMisspelled(word)) {
                replacedText += replaceWithBlock(word);
            } else {
                replacedText += word;
            }
            
            startIndex = wordIndex + word.length;
            
            return true;
        });
        
        if(startIndex < text.length) replacedText += text.substring(startIndex);
        
        return replacedText;
        
        
    }
   
    var self = this;
    
    self._ignoredWords = {}
    self._textObjects = textObjects instanceof Array ? textObjects : [textObjects];
    self._lastAction = null;
      
    //collaborators setup by configure   
    self._misspelledWords = null;   
    self._contextManager = null;    
    self.view = null;
    self.server = null;
   
}   

SpellCheck.internal = {}

SpellCheck.internal.IgnoreAction = function(context) {
  SpellCheck.internal.IgnoreAction.prototype.undo = function() {
      return context;
  }
  
}

SpellCheck.internal.IgnoreAllAction = function(ignoredWords, context) {
  SpellCheck.internal.IgnoreAllAction.prototype.undo = function() {
      
      ignoredWords[context.word] = false;
  
      return context;
      
  }
 
  SpellCheck.internal.IgnoreAllAction.prototype._init = function() {
      if(!context) return;
      
      ignoredWords[context.word] = true;
  };
  
  this._init();
}

SpellCheck.internal.ReplaceAction = function(textObjects, context, replacement) {
  SpellCheck.internal.ReplaceAction.prototype.undo = function() {
      
      var textObject = textObjects[context.textObjectIndex];
       
      var text = textObject.text;
      
      text = text.substring(0, context.wordIndex) + 
          context.word +
          text.substring(context.wordIndex + replacement.length);
      
      
      textObject.text = text;
      
      return context;
  }
  
  SpellCheck.internal.ReplaceAction.prototype._init = function() {
    
      if(!context) return;
      
      var textObject = textObjects[context.textObjectIndex];
      
      var text = textObject.text;
      
      text = text.substring(0, context.wordIndex) + 
          replacement +
          text.substring(context.wordIndex + context.word.length);
      
      textObject.text = text;
  }
  
  this._init();
}

SpellCheck.internal.ReplaceAllAction = function(textObjects, context, replacement) {
    SpellCheck.internal.ReplaceAllAction.prototype.undo = function() {
        for(var i = context.textObjectIndex; i < textObjects.length; i++) {
           textObjects[i].text = this._originalTexts.shift();  
        }
        
        return context;
    }
    
    SpellCheck.internal.ReplaceAllAction.prototype._init = function() {
        
        if(!context) return;
        
        var regexp = new RegExp('b' + context.word + 'b', 'g');
        
        var textObject = textObjects[context.textObjectIndex];
        
        var text = textObject.text;
        
        this._originalTexts.push(text);
        
        textObject.text = text.substring(0, context.wordIndex) + 
            textObject.text.substring(context.wordIndex).replace(regexp, replacement);
        
        for(var i = context.textObjectIndex + 1; i < textObjects.length; i++) {
            textObject = textObjects[i];
            this._originalTexts.push(textObject.text);
            textObject.text = textObject.text.replace(regexp, replacement);
        }
    }
    
   
    this._originalTexts = []
    
    this._init();
}

/**
 * This class manages the list of misspellings for each textObject. It delegates
 * to <i>server.check</i> to connect to spell checking program(on server).
 * 
 */
SpellCheck.internal.MisspelledWords = function(server, beforeCheckCallback) {
    var idSequence = 1;
    
    /*
     * Get misspelled words for textObject at textObjectIndex and invoke afterCheckCallback
     * when we have results. 
     * 
     */
    SpellCheck.internal.MisspelledWords.prototype.check = function(textObject, afterCheckCallback) {
        var self = this;
        
        textObject._identifier = idSequence++;
        
        if(!textObject.text.match(/w+/)) {
            self._misspelledWords[textObject._identifier] = {};
            afterCheckCallback();
            return;
        }
        
        beforeCheckCallback();
        
        var seen = {};
        var uniqueWords = [];
        
        SpellCheck.eachWord(textObject, 0, function(word, wordIndex) {
            if(seen[word] != true) { //specifically check for true to avoid false positives object props like 'constructor'
                uniqueWords.push(word);
                seen[word] = true;
            }
            return true;
        });
        
        server.check(uniqueWords, function(result) {
            self._misspelledWords[textObject._identifier] = result;            
            afterCheckCallback();            
        });
        
    }
    
    SpellCheck.internal.MisspelledWords.prototype.isMisspelled = function(textObject, word) {
        return typeof(this._misspelledWords[textObject._identifier]['d'][word]) != 'undefined';
    }
    
    SpellCheck.internal.MisspelledWords.prototype.getSuggestions = function(textObject, word) {
        return this._misspelledWords[textObject._identifier]['d'][word];
    }
    
    SpellCheck.internal.MisspelledWords.prototype.haveWords = function(textObject) {
        return typeof(this._misspelledWords[textObject._identifier]) != 'undefined';
    }
    
    this._misspelledWords = {}
}

/**
 * This class manages the position of the current misspelled word(Context) as
 * we step/skip through the textObjects. It uses a MisspelledWords instance
 * to handle actual spell checking.  
 * 
 */
SpellCheck.internal.ContextManager = function(textObjects, misspelledWords, contextChangedCallback) {
    SpellCheck.internal.ContextManager.prototype.getContext = function() {
        return this._context;
    }
    
    SpellCheck.internal.ContextManager.prototype.setContext = function(context) {
      this._context = context;
    }
    
    SpellCheck.internal.ContextManager.prototype.step = function() {
        var self = this;
        
        var textObject = textObjects[this._context.textObjectIndex];
        
        if(!misspelledWords.haveWords(textObject)) { 
          return misspelledWords.check(textObject, function() { self.step() });
        }
        
        this._nextLocalContext();
        
        var noMoreTextObjects = this._context.textObjectIndex == textObjects.length - 1;
        
        if(this._context.wordIndex >= 0 || noMoreTextObjects) {
            if(contextChangedCallback) contextChangedCallback(this._context);
        } else {
            
          this._context = new SpellCheck.Context(this._context.textObjectIndex + 1, 0);
          this.step();
        }
       
    }
    
    SpellCheck.internal.ContextManager.prototype.skip = function() {
        if(this._context.textObjectIndex < textObjects.length - 1) {
          this._context = new SpellCheck.Context(this._context.textObjectIndex + 1, 0);
          this.step();
        }
    }
    
    SpellCheck.internal.ContextManager.prototype._nextLocalContext = function() {
        if(this._context.word) {
          this._context = 
              new SpellCheck.Context(this._context.textObjectIndex, this._context.wordIndex + this._context.word.length);
        } 
         
        var context = this._context;
        
        var textObject = textObjects[context.textObjectIndex];
        
        SpellCheck.eachWord(textObject, context.startIndex, function(word, wordIndex) {
            if(misspelledWords.isMisspelled(textObject, word)) {
                
                context.word = word;
                context.wordIndex = wordIndex;
                
                return false;
            }
            
            return true;
        });
        
    }
    
   
   this._context = new SpellCheck.Context(0, 0);
   
}

/**
 *  The position context represents the context
 *  around the current misspelled word and basically breaks it up 
 *  into the text before misspelling, misspelled word, and text after
 *  
 *  Each time the position changes due to a user action(e.g. replace, ignore..),
 *  the view will be notified. When there are no more misspellings, the context.word
 *  will be null and the wordIndex will be -1.
 *  
 */
SpellCheck.Context = function(textObjectIndex, startIndex) {
  
    this.textObjectIndex = textObjectIndex;
    this.startIndex = startIndex;
    this.word = null;
    this.wordIndex = -1;
    
}

SpellCheck.lastNLines = function(text, maxLines) {
     
    var re = /[rn]/g;
    
    var result = null;
    
    var startLineIndexes = [];
    
    while((result = re.exec(text))) {    
      startLineIndexes.push(result.index);
    }
      
    if(startLineIndexes.length < maxLines) return text;
    
    var lineCnt = 0;
    var cutIndex = 0;
    
    for(var i = startLineIndexes.length - 1; i >= 0; i--) {
      
      cutIndex = startLineIndexes[i];
      
      if(++lineCnt == maxLines) break;
    }
    
    var lastNLines = text.substring(cutIndex).replace(/^[rn]/, '');
    
    return lastNLines;
}


    
/**
 * Call block(word, wordIndex) for each word. Stop if block returns false. 
 *
*/
SpellCheck.eachWord = function(textObject, startIndex, block) {
    var nonSpaceSeqPattern =  /S+/g;
    var wordPattern = /[a-zA-Z]+('[a-zA-Z]+)?/g;
    
    var nonSpacesMatchData, wordMatchData;
    
    var text = textObject.text.substring(startIndex);
    
    while((nonSpacesMatchData = nonSpaceSeqPattern.exec(text))) {
        var word = nonSpacesMatchData[0];       
        var wordIndex = startIndex + nonSpacesMatchData.index;
        
        if(word.match(/^/)) continue;        //TeX
        if(word.match(/w+@w+.w+(.w+)?/)) continue; //email   
        if(word.match(/^https?/)) continue; //url
       
        while((wordMatchData = wordPattern.exec(word))) {
           
            var subWord = wordMatchData[0];
            var subWordIndex = wordIndex + wordMatchData.index;
           
            if(subWord.length < 2) continue;
            if(subWord.match(/'$/)) {
              subWord = subWord.substring(0, subWord.length - 1);
            }
            
            if(!block(subWord, subWordIndex)) {
                nonSpaceSeqPattern.lastIndex = 0;
                wordPattern.lastIndex = 0;
                return;
            }
        }                    
    }            
}

jquery.spelldialog.js:

/**
 * JQuery Dialog wiring for SpellCheck. 
 * 
 * ex.
 * 
 * //See default options below. SpellCheck options are:
 * loadingImgSrc - 
 * serverScriptURL - 
 * 
 * All other options are passed directly to JQuery Dialog. 
 * 
 * var spellcheck = JQSpellCheckDialog.create({text : $textarea.val()}, callback, SpellCheckOptions);
 *
 * spellcheck.open();
 * 
 *
 * Modified by David Young on 05.31.2012
 *
 */

var JQSpellCheckDialog = {};

JQSpellCheckDialog.defaults = {
    loadingImgSrc : '../images/loading.gif',
    serverScriptURL : '../WebServices/SpellCheckerService.asmx',
    modal:true, 
    autoOpen:true,
    width : 700,
    height : 550,
    title : 'Spell Check',
    learn : false
}

JQSpellCheckDialog.create = function(textObjects, completedCallback, options) {
    options = jQuery.extend(JQSpellCheckDialog.defaults, options);
    
    var spellcheck = new SpellCheck(textObjects, completedCallback);
    
    var view = new JQSpellCheckDialog.View(spellcheck, options);
   
    spellcheck.configure(view, new JQSpellCheckDialog.Server(options));
    
    return spellcheck;
    
}

JQSpellCheckDialog.Server = function(options) {
    JQSpellCheckDialog.Server.prototype.serverError = function() {
       alert('A server error occurred during the spellcheck.');
    }
    
    JQSpellCheckDialog.Server.prototype.check = function(words, callback) {
        var self = this;
        
        var p = { words: words };

        jQuery.jsonService({
             url:  options.serverScriptURL,
             method: '/CheckSpelling',
             params: p,
             error : self.serverError,
             success : function(result) { callback(result); }
         });                      
    };
    
    JQSpellCheckDialog.Server.prototype.learn = function(word, callback) {
        var self = this;
        
        jQuery.ajax({
            type : 'POST',
            dataType : 'json',
            url : options.serverScriptURL,
            data : { learn : word },
            error : self.serverError,
            success : function() { callback(); }
        });
    }
}


JQSpellCheckDialog.View = function(spellcheck, options) {
    JQSpellCheckDialog.View.Template = 
	'<div class="controlWindowBody">' +
	'' +
	'<table>' +
	'<tr>' +
	'   <td colspan="3" class="normalLabel">Not in dictionary:</td>' +
	'</tr>' +
	'<tr>' +
	'	<td colspan="3"></td>' +
	'</tr>' +
	'<tr>' +
	'	<td class="normalLabel">Change to:</td>' +
	'</tr>' +
	'<tr valign="top">' +
	'	<td>' +
	'		<table>' +
	'		<tr>' +
	'			<td class="normalLabel">' +
	'        		' +
	'			</td>' +
	'       </tr>' +
	'       <tr>' +
	'			<td> ' +
	'			' +
	'				' +
	'			' +
	'			</td>' +
	'		</tr>' +
	'		</table>' +
	'	</td>' +
	'	<td>  </td>' +
	'	<td>' +
	'		<table>' +
	'		<tr>' +
	'			<td style="padding-right:10px;">' +
	'			Ignore' +
	'			</td>' +
	'			<td>' +
	'			Ignore All' +
	'			</td>' +
	'		</tr>' +
	'		<tr>' +
	'			<td>' +
	'			Replace' +
	'			</td>' +
	'			<td>' +
	'			Replace All' +
	'			</td>' +
	'		</tr>' +
        '               <tr>' +
	'			<td>' +
	'			Undo' +
	'			</td>' +
	'			<td>' +
	'			Learn' +
	'			</td>' +
	'		</tr>' +
	'		<tr>' +
	'			<td style="padding-top:13px;">' +
	'			Next Block' +
	'			</td>' +
	'			<td style="padding-top:13px;">' +
	'			Done' +
	'			</td>' +
	'		</tr>' +
	'		</table>' +
	'	</td>' +
	'</tr>' +
	'</table></div>';
    
    JQSpellCheckDialog.View.prototype._createUI = function() {
        var $loading = $('<div class="loadingMessage"><img src="' + options.loadingImgSrc + 
            '" /> Checking....</div>').hide();

        var $wordText = $('<div class="wordWindowText"></div>');
        
        var $wordWindow = $('<div class="wordWindow"></div>').append($loading).append($wordText);
        
        var $window = $('<div id="spellCheckDialog"></div>').
            append($wordWindow).
            append(JQSpellCheckDialog.View.Template);
        
        var thisView = this;
        
        $('button[name=close]', $window).click(function() {
            spellcheck.close();
            return false;
        });
        
        $('button[name=replace]', $window).click(function() {
            spellcheck.replace(thisView.getChangeTo());
            return false;
        });
        
        $('button[name=replaceAll]', $window).click(function() {
            spellcheck.replaceAll(thisView.getChangeTo());
            return false;
        });
        
        $('button[name=undo]', $window).click(function() {
            spellcheck.undo();
            return false;
        });
        
        $('button[name=ignore]', $window).click(function() {
            spellcheck.ignore();
            return false;
        });
        
        $('button[name=ignoreAll]', $window).click(function() {
            spellcheck.ignoreAll();
            return false;
        });
        
        $('button[name=skip]', $window).click(function() {
            spellcheck.skip();
            return false;
        });
        
        if(options.learn) {
            $('button[name=learn]', $window).click(function() {
                spellcheck.learn();
                return false;
            });
        } else {
            $('button[name=learn]', $window).attr('disabled', 'disabled');
        }
        
        $('select', $window).change(function() {
            $('input[name=txtsugg]', $window).val($(this).val());
        });
        
        return $window;
    }
    
    JQSpellCheckDialog.View.prototype.open = function() {        
        this._$dialog = this._createUI().dialog(options);
    }
    
    JQSpellCheckDialog.View.prototype.close = function() {
        this._$dialog.dialog('close');
    }
    
    JQSpellCheckDialog.View.prototype.getChangeTo = function() {
      return $('input[name=txtsugg]', this._$dialog).val();
    }
    
    JQSpellCheckDialog.View.prototype.checkingStarted = function(textObject) {
      $('.loadingMessage', this._$dialog).show();
      $('.wordWindowText', this._$dialog).hide();
      
    }
    
    JQSpellCheckDialog.View.prototype.update = function(textObject, context, canUndo, canSkip) {
        var self = this;
        
        $('.loadingMessage', this._$dialog).hide();
        $('.wordWindowText', this._$dialog).show();
        
        if(context.word == null) {
            var $closeLink = $('<a href="void(0);">Close</a>').click(function() {
                spellcheck.close();
                return false;
            });
            
            var $container = $('<div class="spellCheckCompleted"></div>');
            
            $container.append('Spell Check Completed. ').
                append($closeLink);
            
            
            $('.controlWindowBody', this._$dialog).empty().append($container);
        } 
        this._updateWordWin(textObject, context);
        this._updateControlWindow(context);
        
        if(!canUndo) {
            $('button[name=undo]', this._$dialog).attr('disabled', 'disabled');
        } else {
            $('button[name=undo]', this._$dialog).removeAttr('disabled');
        }
        
        if(!canSkip) {
            $('button[name=skip]', this._$dialog).attr('disabled', 'disabled');
        } else {
            $('button[name=skip]', this._$dialog).removeAttr('disabled');
        }
        
    }
    

    JQSpellCheckDialog.View.prototype._updateControlWindow = function(context) {
        var word = context.word;
        
        $('input[name=misword]', this._$dialog).val(word);
        
        var $select = $('select', this._$dialog).empty();
        
        var suggestions = spellcheck.getSuggestions(word);
        
        if(suggestions) {
            for(var i in suggestions) {
                $select.append('' + suggestions[i] + '');
            }
            
            if(suggestions.length > 0) {
                $select.val(suggestions[0]);
                $select.change();
            }    
        } else {
            $('input[name=txtsugg]', this._$dialog).val('');
        }
    }
    
    JQSpellCheckDialog.View.prototype._updateWordWin = function(textObject, context) {
        var text = textObject.text;
        
        var markup = '';
        
        if(context.word != null) {
            markup = text.substring(0, context.wordIndex);
            
            markup = SpellCheck.lastNLines(markup, 10);
            
            markup += '<span class="currentWord">' + context.word + '</span>';
             
            var startIndex = context.wordIndex + context.word.length;
            
            var theRest = spellcheck.replaceMisspelled(startIndex, function(word) {
                
                if(! spellcheck.isIgnored(word)) {
                    return '<span class="misspelled">' + word + '</span>';
                }
                
                return word;
            });
            
            markup += theRest;
        } else {
            markup = text;
        }       
                
        $('.wordWindowText', this._$dialog).html(markup);
    }
    
    this.spellcheck = spellcheck;
    this._$dialog = null;
    
}