Implementing a Factory Pattern in ASP.NET

factory pattern diagram

A question posed by @Ramsharma1234 on Twitter this morning asked how to implement Factory Patterns in ASP.NET.

(definition of Factory Pattern)

Below is what would be considered a very basic form of Factory Pattern.  This method will essentially instantiate a generic Object, which is the parent of all types of Objects, and later be treated as if it were the type of child object that is used as a parameter in calling the method.  On a Web site where I needed to build Web controls dynamically, based on values from a database query, I created a method called “AddControl” that would add a generic Object to a Placeholder on my Web form:

Private Sub AddControl(ByVal oControl As Object)
   Placeholder1.Controls.Add(oControl)
End Sub

To call this method, I would instantiate a Web control such as a Button and add it to the Placeholder with the method:

Dim btnPrint As New Button
btnPrint.Attributes.Add("onclick", "javascript:window.print();")
btnPrint.Visible = False
AddControl(btnPrint)

Any type of Web control (TextBox, Label, Literal, etc.) could be instantiated and then added using this method.  For instance, if the table value for a particular field indicated the creation of a TextBox, this would be how that could be accomplished:

Dim oControl As New Object
'...
oControl = New TextBox
With oControl
.ID = strFieldName & "_mltxt"
.TextMode = TextBoxMode.MultiLine
.MaxLength = 2000
.Style("overflow") = "hidden"
.Height = 300
.Width = 500
.BorderStyle = BorderStyle.None
.Enabled = True
.ReadOnly = True
End With
AddControl(oControl)

Setting the Connection String Programmatically on a Crystal Reports ReportDocument

logos for Visual Studio and Crystal Reports

Programmatically setting the connection string for a Crystal Reports ReportDocument will allow you to have the report automatically use the active connection string for the rest of the application (as in previous posts). In this case, the report is created using a method that is triggered by clicking a button on a form. The log on information must be applied to each table in the ReportDocument.

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim crReportDocument As ReportDocument
            crReportDocument = New ReportDocument
            crReportDocument.Load(<<name of reportdocument>>)

            Dim sConn As SqlConnectionStringBuilder = New SqlConnectionStringBuilder(<<connection string>>)

            Dim tables As CrystalDecisions.CrystalReports.Engine.Tables = crReportDocument.Database.Tables

            For Each table As CrystalDecisions.CrystalReports.Engine.Table In tables
                Dim tableLogOnInfo As CrystalDecisions.Shared.TableLogOnInfo = table.LogOnInfo
                tableLogOnInfo.ConnectionInfo.ServerName = sConn.DataSource
                tableLogOnInfo.ConnectionInfo.DatabaseName = sConn.InitialCatalog
                tableLogOnInfo.ConnectionInfo.IntegratedSecurity = True
                table.ApplyLogOnInfo(tableLogOnInfo)
            Next

            ' ...

       End Sub

Free Spell Checker for TextBoxes

spell check

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.
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22..%2Fjs%2Fjquery-1.4.2.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22..%2Fjs%2Fjquery.spellchecker.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22..%2Fjs%2Fjquery.spelldialog.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%3E%0Avar%20SpellCheckOptions%20%3D%20%7B%20learn%3A%20false%2C%20serverScriptURL%3A%20'..%2FWebServices%2FSpellCheckerService.asmx'%20%7D%3B%0A%0Afunction%20spellCheckTextArea(targetId)%20%7B%0Avar%20%24textarea%20%3D%20%24(targetId)%3B%0A%0A%2F%2Fthis%20callback%20will%20be%20called%20when%20the%20spellcheck%20is%0A%2F%2Fcomplete.%20In%20this%20case%20we%20set%20the%20value%20of%20the%20target%0A%2F%2Ftextarea%20to%20the%20corrected%20text.%0Avar%20callback%20%3D%20function%20(textInfos)%20%7B%0A%24textarea.val(textInfos%5B0%5D.text)%3B%0A%7D%0A%0Avar%20spellcheck%20%3D%20JQSpellCheckDialog.create(%7B%20text%3A%20%24textarea.val()%20%7D%2C%20callback%2C%20SpellCheckOptions)%3B%0Aspellcheck.open()%3B%0A%7D%0A%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<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>&"""nbsp;&"""nbsp;</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 +
            '" />&"""nbsp;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;

}