Add autocomplete functionality to make Flash complete the data entry for your fill-in text forms.
Most of us don't want to type any more than we have to. For certain users?such as children, disabled users, or those using mobile devices with cumbersome keypads?saving keystrokes is a priority. One way to save time is to fill in commonly used words before the user finishes typing them (i.e., an autocomplete feature).
The 300 Most Common Words (http://www.zingman.com/commonWords.html) and alt-usage-english.org's Commonest Words (http://alt-usage-english.org/excerpts/fxcommon.html) purport to list the 300 most common words in written English. Other word lists include The American Heritage Word Frequency Book by John B. Carroll, Peter Davies, and Barry Richman (Houghton Mifflin).
When I first stumbled on those resources, I thought, "Hmm...that's a good start for creating an autocomplete text engine." After removing all words that are of two or fewer letters (and therefore don't save much time by being autocompleted) and sorting them alphabetically, Figure 6-2 shows the results.
For those interested in stripping words from another word list they might encounter, here is the code I used:
function textLoader(data) { // Split the text file out based on any spaces dictionary = data.split(" "); // Sort the word list dictionary.sort( ); // Display the sorted word list, separated by spaces trace(dictionary.join(" ")); } var myText:LoadVars = new LoadVars( ); var dictionary:Array = new Array( ); myText.load("commonWords.txt"); myText.onData = textLoader;
This assumes the words are in a file called commonWords.txt and that they are separated by spaces. You would change the filename and delimiter (such as a Return character) in accordance with the name and format of your word list.
My stripped-down word list takes up only 1.5 KB, and you can reduce that to 990 bytes?a bargain!?by copying the output from the preceding script into the ActionScript listing itself, like this (although the full version looks a bit messier, as shown shortly):
var myText:String = "about above across ... years you young your"; var dictionary:Array = new Array( ); dictionary = myText.split(" "); dictionary.sort( ); delete (myText);
When you publish the SWF, Flash compresses the dictionary definition text.
To use the dictionary, I found a rather ingenious hack for adding the autocomplete text at the Stickman site (http://www.the-stickman.com).
It uses two text fields, one on top of the other. The top one is an input field, and the lower one is a dynamic field that displays our autocomplete suggestions. To create this arrangement, assuming you already have a layer named actions (to the first frame of which is attached the previous code listing), add two new layers named entered text and completed text, as shown in Figure 6-3.
In the entered text layer, create a multiline, borderless input text field, named myText_txt, as shown in Figure 6-4.
Next, copy the text field, lock the entered text
layer, and paste the text field in place into layer
completed text (Ctrl-Shift-V on Windows or
-Shift-V on Mac). Using the Properties panel, change
the text field to a bordered dynamic text field, and change the text
color to something other than the original text
field's color (this will be the color of your
autocomplete text). Change the instance name to
complete_txt in the Properties panel, as shown in
Figure 6-5.
Finally, change the script attached to the actions layer to the following, which contains both the code and the preinitialized dictionary of words:
function autoComplete( ) { // Function to accept the currently suggested autocomplete // text when the CONTROL key is pressed. if (Key.isDown(Key.CONTROL)) { myText_txt.text = complete_txt.text + " "; Selection.setSelection(myText_txt.text.length, myText_txt.text.length); } } function fieldChange( ) { // Function to search for and display the // autocomplete text string. match = ""; // Extract the last word in progress in the text input field startOfWord = this.text.lastIndexOf(" ") + 1; lastWord = this.text.substring(startOfWord, this.text.length); // Search for any matches in the dictionary to the last // word in progress. if (lastWord.length > 1) { for (var i = 0; i < dictionary.length; i++) { if (lastWord == (dictionary[i].substr(0, lastWord.length))) { // If match found, store the match string in variable match match = dictionary[i]; search = i; break; } } } else { search = 0; } // Display the current match in the autocomplete text field complete_txt.text = this.text.substr(0, startOfWord) + match; } // Initialize var myText:String = "about above across after again against air all almost along also always and animals another answer any are around asked away back because been before began being below best better between big both boy boys but bye called came can change children city come cool could country day days did different does don't down during each earth email end enough even ever every example eyes far father feet few find first five following food for form found four from get give going good goodbye got great had hand hard has have head hear heard hello help her here high him himself his home house how however html important into it's its just keep kind knew know land large last learn left let life light like line little live long look looked made make man many may means men might miles more most mother much must name near need never new next night no not now number off often old once one only other others our out over own page paper part parts people picture place play point put read right room said same saw say school sea second see sentence set several she should show side since small some something sometimes soon sound still story study such sun sure take tell than that the their them then there these they thing things think this those thought three through time times today together told too took top toward try turned two under until use used using usually very want was water way ways web website well went were what when where which while white who whole why will with without word words work world would write year years you young your"; var dictionary:Array = new Array( ); var search:Number = 0; var lastWord:String = ""; var startOfWord:String = ""; var control:Object = new Object( ); // Set up dictionary dictionary = myText.split(" "); dictionary.sort( ); // Set up events and listeners myText_txt.onChanged = fieldChange; control.onKeyDown = autoComplete; Key.addListener(control);
The main part of this script is the fieldChange( ) function, which runs whenever the user enters something in the text field.
First, it clears a variable, match, which holds our autocomplete guess. Next, it extracts the last complete word typed by the user. We do this by looking for the last incidence of a space and counting from the next character to the end of the text.
For example, in the sentence, "The cat sat," the last space is the one before "sat" (and its zero-relative index is 7). Adding 1 to it gives us the position of the first character of the word "sat." Using this information (startOfWord), we can extract the substring "sat" from the sentence, remembering that the last letter of "sat" corresponds to the end of our sentence so far. Here we repeat the relevant code:
function fieldChange( ) { match = ""; startOfWord = this.text.lastIndexOf(" ") + 1; lastWord = this.text.substring(startOfWord, this.text.length);
It's not worth autocompleting until the current word is at least two letters long, so we use an if statement in the next section of code to avoid performing the check when only one letter has been entered. If the user has entered at least two characters, the for loop compares our word with each entry in the dictionary until it finds a match.
The variable search is used to optimize future searches. Because we know that the dictionary is sorted alphabetically, we know that any future matches for the current word will be after the currently found word in the dictionary. Therefore, remembering the position of the current word allows us to continue searching from the same position later:
if (lastWord.length > 1) { for (var i = 0; i < dictionary.length; i++) { if (lastWord == (dictionary[i].substr(0, lastWord.length))) { // Match found match = dictionary[i]; search = i; break; } }
Finally, if we haven't yet attempted a search, it is because the current word is too short, so we reset the search position, because when the word is long enough, we want to start searching the dictionary from the beginning.
} else { search = 0; }
The code is set up to allow the user to accept the current autocomplete suggestion by pressing the Ctrl key (we discuss alternatives later). When the user presses Ctrl, the autoComplete( ) function is invoked via a listener event. The autoComplete( ) function transfers the current contents of the autocomplete text into the input field, effectively completing the word for the user.
function autoComplete( ) { if (Key.isDown(Key.CONTROL)) { myText_txt.text = complete_txt.text + " "; Selection.setSelection(myText_txt.text.length, myText_txt.text.length); } }
The last line of the preceding code moves the text cursor to the end of the current line. The trick is to set a zero-length selection to force the cursor to move to the end of the text field.
Figure 6-6 shows autocomplete in action. Entering "He sa" gives you an autocomplete suggestion of "He said." Pressing the Ctrl key adds the suggested text to the current input text. If desired, you can add static help text to inform the user that the Ctrl key initiates the autocomplete feature.
This hack shows the basic code needed to create an autocomplete feature. Almost everyone will want to change it, the most likely candidate being to accept the autocomplete text by hitting another key such as the spacebar. You need a bit more code to do that, because although you can check for a spacebar press simply by looking for a " " at the end of the current input text, you need to be sure that the last pressed key was not a backspace. Another potential drawback is if you press the spacebar to go to the next word, already having a complete word, then the autocomplete adds more unwanted text. For example, if you enter "table" and then press the spacebar, the autocomplete feature might change it to "tablecloth." But this is unlikely because the common words in the word list tend to be shorter words.
The other thing that will probably irk some folks is the way I have added a blank space on the end of every autocompleted word. These are both minor details though; the basic principle remains the same even if you change the implementation details.
Using the most common words as the core autocomplete dictionary should also start you well on your way to adding a more useful list. Although the addition of a language dictionary sounds as if it should add a massive download to your SWF, the most common 300 words add less than 1 KB. The top 3,000 words (which approaches the number of words used in normal conversation, ignoring proper nouns and specializations) should consume no more than 10-15 KB! Not as much as you might think, which makes it feasible to teach a speech synthesizer [Hack #52] a vocabulary so that it can attempt reading plain text. You could have it kick out words it doesn't understand and add them to the dictionary [Hack #44] as you see fit.
Here is a list of other enhancements you might make:
Trigger autocomplete on the Return or Enter key.
Prevent the user from tabbing to the hidden field by setting the tabIndex property for myText_txt but not for complete_txt.
Add logic so that autocomplete works even if you click in the middle of an existing text field.
Deal with wrapping and scrolling as required by longer text input.
Speed up the word search to accommodate longer dictionaries without degrading performance. For example, you can split the dictionary into multiple arrays stored in a hash table in which the key is the first two characters.
?Inspired by Stickman