Spell checking in Flex with Squiggly - Part 2
Posted on May 05, 2010
In part 1 of my Squiggly for Flex tutorial, we discussed how easy it is to use the check-as-you-type spell checking for any TextInput or TextArea. This works great for most scenarios. However, sometimes you're requirements may ask for more customized spell-checking behavior. For example, perhaps you need to persist user custom dictionary items to a database or you need to build a custom spelling suggestion UI - these were just a couple of the requirements I faced. Thankfully, Squiggly provides most of the tools you will need to get these tasks done...with some caveats.
Customizing the Spell Checker
Our last
tutorial focused almost completely on the SpellUI class provided with
Squiggly. This time, we will work through using the other three classes
that are included: SpellChecker, HunSpellingDictionary and
UserDictionary. You would need to utilize these classes primarily when
your application requires spellchecking functionality that is outside
the SpellUI class' built in functionality discussed in Part 1.
My application required several customized spell-checking features in order to build a spelling wizard similar to MS Word's. These features include the ability to persist user-defined dictionary items in the database (SpellUI simply puts these in a shared object) and the ability to get a list of misspelled words from any UI element (SpellUI simply underlines them in red). I can't share the entire code of my proof-of-concept for the spelling wizard but I will show how I used Squiggly to get some of the basis of the wizard built into my code.
The
ITextWithSpell Interface
In order to get access to any of the
spell checking functionality within any given text element (TextInput or
TextArea), I created an interface to enforce the methods I would
require. EnableSpell and DisableSpell were discussed in Part 1 and
enable and disable SpellUI functionality. The getMispelledWords() method
will utilize SpellChecker class to return an array containing every
misspelled word in the given text input. The replaceWord() method will
simply replace the first instance of a word (its assumed this would be a
misspelling) with another word (its assumed this would be the correct
spelling). Finally, the addToDictionary() method will use the
UserDictionary class to add the provided word to a user-defined
dictionary. Below is the full code for the ITextWithSpell interface:
package com.views
{
import flash.events.Event;
public interface ITextWithSpell
{
function enableSpell(e:Event):void
function disableSpell(e:Event):void
function getMispelledWords():Array
function replaceWord(word:String,wordToReplace:String):void
function addToDictionary(word:String):void
}
}
One of the things that this interface allows me to do, and which is completely necessary for a spelling wizard that spans multiple text inputs within an application, is to get an array of all text elements within an application that implement this interface. In doing so, you can then work through each item using the getMispelledWords() method to display any misspellings to the user. As I mentioned earlier, this is still a work in progress, but here is the method I use to get all items implementing the above interface (you will notice that it is recursive):
private function getAllSpellcheck(d:Container):Array {
var resultsArr:Array = new Array();
var children:Array = d.getChildren();
for each (var child:UIComponent in children) {
if (child is Container) {
resultsArr = resultsArr.concat(getAllSpellcheck(child as Container));
}
else if (child is com.views.ITextWithSpell) {
resultsArr.push(child);
}
}
return resultsArr;
}
Adding
Custom Spell Checking Functionality to a TextInput
Now that we
have defined our methods using the interface, the next step is to
actually implement those in a TextInput and TextArea. With the recent
update to Squiggly some of the way you implement the SpellChecker class
changed. Firstly, the dictionary class was changed to
HunSpellDictionary. The HunSpellDictionary loads the dictionary file you
want Squiggly to use to perform its spell check (in the code below, I
use the US English dictionary provided with the Squiggly download files -
just copy the dictionary folder into your source). Another change in
the newer release was that you needed to add an event listener to listen
for when Squiggly has finished loading the dictionary element before
you can add a custom user dictionary.
Below is the beginning code for a text input with the custom spelling functions added that adds the constructor to load the dictionary file and a function to add our custom dictionary object when the dictionary file has complete loading. I have also left in the enableSpell and disableSpell functions I discussed in part 1. Note that using this code will give you an error at the moment because we haven't finished implementing all the methods required by the interface yet (we'll define those later):
package com.views
{
import com.adobe.linguistics.spelling.*;
import com.vo.MispellingVO;
import flash.events.Event;
import flash.net.URLRequest;
import spark.components.TextInput;
public class TextInputWithSpell extends TextInput implements ITextWithSpell
{
private var spellingDictionary:HunSpellDictionary = new HunSpellDictionary();
private var spellchecker:SpellChecker;
private var userDictionary:UserDictionary = new UserDictionary();
public function TextInputWithSpell()
{
super();
spellingDictionary.addEventListener(Event.COMPLETE, handleLoadComplete);
spellingDictionary.load("dictionaries/en_US/en_US.aff","dictionaries/en_US/en_US.dic");
addEventListener("focusIn",enableSpell);
addEventListener("focusOut",disableSpell);
}
private function handleLoadComplete(e:Event):void
{
spellchecker = new SpellChecker(spellingDictionary);
spellchecker.addUserDictionary(userDictionary);
}
public function enableSpell(e:Event):void {
SpellUI.enableSpelling(this,"en_US");
}
public function disableSpell(e:Event):void {
try {
SpellUI.disableSpelling(this);
}
catch (error:Error) {
// do nothing. sometimes the disable seemed to occur before the enable if you clicked too quickly
}
}
}
}
Getting All Misspelled
Words
I will admit that I thought this type of functionality
would be something built into Squiggly's SpellChecker class along the
lines of having the ability to pass it a string and it could return all
the misspelled words within that string. While that function does not
exist by default, it's pretty simple to create using Squiggly even if it
seems slightly inefficient. As you can see in the method below from my
TextInputWithSpell class, I simply loop through an array of words within
the TextInput and use the SpellChecker class' checkWord() function to
see if it is misspelled. If the word is misspelled, I create an instance
of a MispellingVO value object I created into which I pass the word as
well as the suggestions Squiggly has for the correct spelling (these are
easily available via the getSuggestions() method in the SpellChecker
class).
public function getMispelledWords():Array {
var contentArr:Array = this.text.split(" ");
var results:Array = new Array();
var checkSpelling:Boolean;
for (var i:String in contentArr) {
checkSpelling = spellchecker.checkWord(contentArr[i]);
if (checkSpelling == false) {
results.push(new MispellingVO(contentArr[i],i as Number,spellchecker.getSuggestions(contentArr[i])));
}
}
return results;
}
The replaceWord() function is designed to allow you to replace an instance of a misspelling with the corrected spelling. This method really has nothing to do with Squiggly necessarily but would be necessary if you were making a custom spell checker. It simply uses the built in replace() method in the String object. It is not fully fleshed out however since that method replaces the first instance of the match which would create an issue in the unlikely but plausible scenario that you have two instances of a misspelled word but only wanted to replace the second instance.
public function replaceWord(word:String,wordToReplace:String):void {
this.text = this.text.replace(word,wordToReplace);
}
Custom
User-Defined Dictionaries
Finally, the addToDictionary() method
which is also not completely implemented. Adding a word to the
userDictionary is as easy as calling the addWord() method as seen below.
Where this is lacking however is that once you take responsibility for
the user-defined dictionary items away from SpellUI, they are not
persisted in any way for you (remember, SpellUI creates a shared
object). In this case, we'd probably want to ensure that user defined
items are persisted to the database perhaps by responding to some event
and, in addition, we'd need to load these words into our UserDictionary
object when its instantiated.
public function addToDictionary(word:String):void {
userDictionary.addWord(word);
}
Going Forward
& Some Issues
As I stated earlier, this was all part of a
proof-of-concept and not a fully complete application so as noted some
methods are not completely implemented while others could use some
refinement. Nonetheless, Squiggly does offer a lot in terms of useful
functionality if you need to go beyond the built-in spell-checking
behavior is SpellUI. Nonetheless, there were some issues I ran into that
I feel should be addressed in a future release.
Firstly, the user dictionary between spellUI and UserDictionary are not shareable. By this I mean that items added to SpellingDictionary still get underlined in SpellUI and items added via SpellUI can't be easily accessed to synchronize them with your SpellingDictionary. What this means is that if you want to use the highlighting of misspelled words built-in to SpellUI, you cannot do so with a custom UserDictionary (i.e. you'd have to build your own highlighter).
Secondly, as noted earlier, I think it would be useful if SpellChecker could return you an array of misspelled words for a given string as this seems to me a key requirement for any custom spell checking functionality.
All in all, however, I am quite pleased with the progress of the Squiggly project and hope that Adobe continues to devote effort into moving it forward.
Comments
Nice write up Brian. Any plans on putting up your sample swf to play with on the site?
Posted By Kyle Dodge / Posted on 05/05/2010 at 11:03 AM
@Kyle - thanks! I wish I could share but all the custom functionality is wrapped up in the spell check wizard PoC that my boss has asked me not to share. My goal here was to share as much as I could without crossing his request not to share the wizard at the moment.
Posted By Brian Rinaldi / Posted on 05/05/2010 at 12:49 PM
Posted By Tahir Alvi / Posted on 10/06/2010 at 5:40 AM