At the University of Georgia we are in the last few weeks of the semester. The project of one of the students working with LiveCode - Russ Palmer, an instructional designer at UGA's College of Pharmacy - uses a text entry for an interesting game involving the memorization of pharmaceutical information. The player must get an exact match in order to be successful, which can be rather daunting given the complexity of drug names. Russ is really not interested in requiring the player to spell everything exactly right. In previous programming languages I've used, there have always been ways to allow for simple spelling errors. LiveCode has two functions that could be used here, namely matchChunk and matchText.
We also thought about either creating a pull-down menu or a list of all the possible terms. That way the player could just choose or click on one. But choosing among a list of items is a very different learning outcome as compared to recalling a certain drug name. But as we continued to talk, we decided what we would really like to have is an autofill function. That way, the player is forced to at least begin typing the appropriate response, but could then choose among the possible entries as the player typed successive letters. Alas, I told Russ, there is no such function available in LiveCode.
Over the last few days, I've been thinking about this autofill option. Given LiveCode's excellent list processing functions I thought it was worth trying my hand at building it. I think I've created an example that does a pretty good job. In fact, I think Russ and I have invented a new kind of question type. So, you've heard of the Likert-type question? Well, now you have the Rieber-Palmer-type question!
The field on the far right shows the "dictionary" for the autofill using a field titled "words" (yes, in hindsight I should have titled it "dictionary"). These are the only words that will appear in the autofill, but the list can be lengthened or shortened as needed. Plus, it would be very easy to make the list dynamic, such as by adding words to it as the program executes.
As the player begins to type a word, LiveCode begins a matching process after each letter is typed. All words in the dictionary that begin with that letter, then two letters, then three letters, etc. appear in a field just below the answer field. The player can then choose to just click on one of the words in the autofill list and that word is then pasted into the answer field.
To demonstrate a working example, I created a little quiz where LiveCode chooses one of the words in the dictionary at random and makes it the answer to a (bogus) question. (This code is in the card script, should you want to take a look.)
There are two main scripts at work here: one that generates the autofill list and the other that tracks what line the player clicks on in the autofill list.
The first script is in the field "answer":
The "on keyDown" script is the heart of the autofill function. Everytime the player types a key, it is triggered and the identity of the key pressed is stored in the local variable "theKey". When the first key is pressed, it is so noted and the autofill field is made visible (and the autofill field is emptied before going any further, just in case some residual text is there from a previous question).
Every time a key is pressed, a repeat loop is triggered that repeats for as many words as there are in the dictionary (again, the field titled "words" located on the right-hand side of the screen). The loop uses the variable i as a counter starting at 1 and ending with the total number of lines in the dictionary. For example, when i is 4, line i would refer to the word "basket."
So, let's pretend that the letter "b" was pressed. The next line says to "put the number of characters in line 1 of me into varChar." Of course, "me" refers to the answer field itself. So, at this point, varChar = 1 because only one letter has been typed.
Consider the next line:
if line 1 of me = char 1 to varChar of line i of field "words"
This line looks at each word in the dictionary (line i) and basically asks: Does the word begin with the letters entered so far in the answer text field? Remember, so far, only the letter "b" has been pressed. So, "char 1 to varChar" asks the computer to look at each word in each line starting with the first letter and going to letter varChar. Since varChar = 1, the match simply starts and ends with the first character. So, all words starting with "b" are considered matches. It then puts a copy of each "b" word (along with a return) into the field "autofill," which is located just below the field "answer."
Next, let's pretend that the player typed the letter "e". The same repeat loop is again triggered. This time, varChar = 2 because the letters "be" now appear in the answer field. So, "char 1 to varChar" now asks the computer to look at each word in each line starting with the first letter and going to the second letter. So, all the words starting with "be" are matches.
This pattern continue as the player types in successive letters.
The second script is in the field "autofill". This script allows the player to click on any word in the autofill list and have it entered into the answer field. The script for this is pretty simple and takes advantage of a nifty LiveCode function named "clickline":
on mousedown
select the clickline
put the value of the clickLine into line 1 of field "answer"
hide me
put empty into me
focus on card field "answer"
end mousedown
First, it's important to note that the field needs to be set to "lock text" for this script to work. (This is something I did not know at first. It took me about 15 frustrating minutes to discover this fact.) The easiest way to lock the text is to open the property inspector for the field (just double-clicking on the field opens the property inspector), then choose "lock text" in Basic Properties.
The function "clickline" monitors if a line in the field has been clicked and records which line it is. I then put the value of that line into the field "answer."
The autofill field then is emptied and hidden, and the focus (as denoted by the blinking I-beam) is put back on the field "answer."
I am happy with how this autofill function works and I think it does the job. However, one bug remains that I admit I've not been able to solve. When the player deletes a letter, the autofill field does not update as I thought it would. That is, all words currently in the autofill list remain. I'm sure there is a simple explanation to this, but so far it continues to elude me. Fortunately, once you start typing letters again, the function works properly. I think I may need to trap for the delete key with some special code. Perhaps someone reading this blog posting will suggest some other solution.
I posted a note about this autofill file to the "Talking LiveCode" forum - here's a link to the topic thread:
Craig Newman and Mark Schonewille replied to it and provided some very valuable assistance and insights. The first, and the simplest, suggestion by Craig is to add the following code to the field "answer" completely resolved the bug:
on backspacekey
delete the last char of me
send "keydown" to me
end backspacekey
(I still get confused about how the "send" command works.)
Craig also provided his own version of how to accomplish the autofill feature. I'll be reviewing that approach carefully.
Mark suggested using the "filter" command, a command I did not know existed. It's a very elegant and powerful way to extract elements from a list.
I also noticed another little bug in my program that was easily fixed. I noticed that the entire list of words from the dictionary appear if you backspace all the way to the beginning. So, I added this line of code to the very end of the keydown procedure:
if me is empty then hide field "autofill"
I've revised my autofill program. Here's the link to it:
Thanks again to Craig and Mark.
We also thought about either creating a pull-down menu or a list of all the possible terms. That way the player could just choose or click on one. But choosing among a list of items is a very different learning outcome as compared to recalling a certain drug name. But as we continued to talk, we decided what we would really like to have is an autofill function. That way, the player is forced to at least begin typing the appropriate response, but could then choose among the possible entries as the player typed successive letters. Alas, I told Russ, there is no such function available in LiveCode.
Over the last few days, I've been thinking about this autofill option. Given LiveCode's excellent list processing functions I thought it was worth trying my hand at building it. I think I've created an example that does a pretty good job. In fact, I think Russ and I have invented a new kind of question type. So, you've heard of the Likert-type question? Well, now you have the Rieber-Palmer-type question!
Here is a screen snapshot of the program:
[ Get the free LiveCode Community version. ]
As the player begins to type a word, LiveCode begins a matching process after each letter is typed. All words in the dictionary that begin with that letter, then two letters, then three letters, etc. appear in a field just below the answer field. The player can then choose to just click on one of the words in the autofill list and that word is then pasted into the answer field.
To demonstrate a working example, I created a little quiz where LiveCode chooses one of the words in the dictionary at random and makes it the answer to a (bogus) question. (This code is in the card script, should you want to take a look.)
Building the Autofill function
There are two main scripts at work here: one that generates the autofill list and the other that tracks what line the player clicks on in the autofill list.
The first script is in the field "answer":
global varWord
on returninfield
if line 1 of me = varWord then
show field "correct"
else
show field "wrong"
end if
wait 1 second
put empty into me
hide field "autofill"
opencard
end returninfield
on keyDown theKey
show field "autofill"
put theKey after me
put empty into field "autofill"
repeat with i = 1 to number of lines in field "words"
put the number of characters in line 1 of me into varChar
if line 1 of me = char 1 to varChar of line i of field "words" then
put line i of field "words"&return after field "autofill"
end if
end repeat
if the number of lines in field "autofill" > 5 then
set the vScrollbar of field "autofill" to true
else
set the vScrollbar of field "autofill" to false
end if
end keyDown
The first code block - "on returninfield" - is not related to the autofill function. It's only used to take whatever text is currently in the answer field when the user presses the Return key and evaluate it to see if it is a match. Appropriate feedback is given if it is or isn't a match. Then a new word is chosen for the next "question" by executing the openCard command.
on returninfield
if line 1 of me = varWord then
show field "correct"
else
show field "wrong"
end if
wait 1 second
put empty into me
hide field "autofill"
opencard
end returninfield
on keyDown theKey
show field "autofill"
put theKey after me
put empty into field "autofill"
repeat with i = 1 to number of lines in field "words"
put the number of characters in line 1 of me into varChar
if line 1 of me = char 1 to varChar of line i of field "words" then
put line i of field "words"&return after field "autofill"
end if
end repeat
if the number of lines in field "autofill" > 5 then
set the vScrollbar of field "autofill" to true
else
set the vScrollbar of field "autofill" to false
end if
end keyDown
The first code block - "on returninfield" - is not related to the autofill function. It's only used to take whatever text is currently in the answer field when the user presses the Return key and evaluate it to see if it is a match. Appropriate feedback is given if it is or isn't a match. Then a new word is chosen for the next "question" by executing the openCard command.
on keyDown
The "on keyDown" script is the heart of the autofill function. Everytime the player types a key, it is triggered and the identity of the key pressed is stored in the local variable "theKey". When the first key is pressed, it is so noted and the autofill field is made visible (and the autofill field is emptied before going any further, just in case some residual text is there from a previous question).
Every time a key is pressed, a repeat loop is triggered that repeats for as many words as there are in the dictionary (again, the field titled "words" located on the right-hand side of the screen). The loop uses the variable i as a counter starting at 1 and ending with the total number of lines in the dictionary. For example, when i is 4, line i would refer to the word "basket."
So, let's pretend that the letter "b" was pressed. The next line says to "put the number of characters in line 1 of me into varChar." Of course, "me" refers to the answer field itself. So, at this point, varChar = 1 because only one letter has been typed.
Consider the next line:
if line 1 of me = char 1 to varChar of line i of field "words"
This line looks at each word in the dictionary (line i) and basically asks: Does the word begin with the letters entered so far in the answer text field? Remember, so far, only the letter "b" has been pressed. So, "char 1 to varChar" asks the computer to look at each word in each line starting with the first letter and going to letter varChar. Since varChar = 1, the match simply starts and ends with the first character. So, all words starting with "b" are considered matches. It then puts a copy of each "b" word (along with a return) into the field "autofill," which is located just below the field "answer."
Next, let's pretend that the player typed the letter "e". The same repeat loop is again triggered. This time, varChar = 2 because the letters "be" now appear in the answer field. So, "char 1 to varChar" now asks the computer to look at each word in each line starting with the first letter and going to the second letter. So, all the words starting with "be" are matches.
This pattern continue as the player types in successive letters.
The second script: Clickline
The second script is in the field "autofill". This script allows the player to click on any word in the autofill list and have it entered into the answer field. The script for this is pretty simple and takes advantage of a nifty LiveCode function named "clickline":
on mousedown
select the clickline
put the value of the clickLine into line 1 of field "answer"
hide me
put empty into me
focus on card field "answer"
end mousedown
First, it's important to note that the field needs to be set to "lock text" for this script to work. (This is something I did not know at first. It took me about 15 frustrating minutes to discover this fact.) The easiest way to lock the text is to open the property inspector for the field (just double-clicking on the field opens the property inspector), then choose "lock text" in Basic Properties.
The function "clickline" monitors if a line in the field has been clicked and records which line it is. I then put the value of that line into the field "answer."
The autofill field then is emptied and hidden, and the focus (as denoted by the blinking I-beam) is put back on the field "answer."
One Bug Remains
I am happy with how this autofill function works and I think it does the job. However, one bug remains that I admit I've not been able to solve. When the player deletes a letter, the autofill field does not update as I thought it would. That is, all words currently in the autofill list remain. I'm sure there is a simple explanation to this, but so far it continues to elude me. Fortunately, once you start typing letters again, the function works properly. I think I may need to trap for the delete key with some special code. Perhaps someone reading this blog posting will suggest some other solution.
Addendum
November 27, 2013I posted a note about this autofill file to the "Talking LiveCode" forum - here's a link to the topic thread:
Craig Newman and Mark Schonewille replied to it and provided some very valuable assistance and insights. The first, and the simplest, suggestion by Craig is to add the following code to the field "answer" completely resolved the bug:
on backspacekey
delete the last char of me
send "keydown" to me
end backspacekey
(I still get confused about how the "send" command works.)
Craig also provided his own version of how to accomplish the autofill feature. I'll be reviewing that approach carefully.
Mark suggested using the "filter" command, a command I did not know existed. It's a very elegant and powerful way to extract elements from a list.
I also noticed another little bug in my program that was easily fixed. I noticed that the entire list of words from the dictionary appear if you backspace all the way to the beginning. So, I added this line of code to the very end of the keydown procedure:
if me is empty then hide field "autofill"
I've revised my autofill program. Here's the link to it:
Thanks again to Craig and Mark.
Hi Lloyd. Thank you so much for sharing your code. It was simply amazing to see it in use with my application. Because of my unwieldy data, your search was taking a few seconds with each keyDown, and so I figured out how to use Filter. Wow! It removed about 20 lines of code AND was nearly instantaneous. If you haven't incorporated it already, you should give it a try. -Kory
ReplyDelete