For a look at the finished application, see http://www.backstopmedia.com/examples/e4x.html. This article assumes that you are familiar with XML and JavaScript concepts. See the Resources if you need to polish up your skills. You will also need access to an E4X-capable browser such as Firefox 1.5 or above.
If you haven't read Part 1, you should (see Resources). In that article we created an application that analyses a knowledge base using mostly "yes" and "no" questions to determine what item a user is thinking about. The knowledge base looks something like Listing 1.
Listing 1. A sample of the knowledge base
<knowledgebase>
<questions>
<question id='1'>
<display>Is it animal, vegetable, or mineral?</display>
<answerOption>Animal</answerOption><
answerOption>Vegetable</answerOption>
<answerOption>Mineral</answerOption>
</question>
...
</questions>
<targets>
<target id='1'>
<display>a house cat</display>
<answer questionid = '1'><answerValue1>Animal</
answerValue1></answer>
<answer questionid = '41'><answerValue41>No</
answerValue41></answer>
</target>
<target id='2'>
<display>a carrot</display>
<answer questionid = '1'><answerValue1>Vegetable</
answerValue1></answer>
<answer questionid = '44'><answerValue44>No</
answerValue44></answer>
</target>
...
</targets>
</knowledgebase>
|
To do this analysis, you use an algorithm that asks a question, eliminates all items that don't correspond with the answer, finds the most popular remaining question and asks that until only one item remains.
At that point, the application guesses that the user was thinking of the final item. If the guess is right, the application returns to the beginning. If wrong, however, you have more work to do.
If the application gets to the last possible item and that item is wrong, you'll want to teach the system about whatever item the user really did think about. For example, if the user thinks "celery" and the system guesses "a carrot", the system needs to know that celery exists. It also needs to know how to tell the difference between celery and a carrot.
The first task is to find out what was the correct answer. The form is simple, as shown in Listing 2.
Note: If you didn't follow along with Part 1, see the Resources to download the files you should start with here.
Listing 2. The new target form
<div id="targetFormDiv" style="position: absolute; top: 50px;
visibility: hidden; width: 100%;">
<form id="targetForm" name="targetForm">
OK, what is it? It's <input type="text" name="newTargetDisplay"
id="newTargetDisplay" />/>
<input type="button" onclick="submit_new_target()" value="Teach me!" />
</form>
</div>
|
You'll display that form when the user tells the application that it guessed wrong, which calls the get_new_target() function, as in Listing 3.
Listing 3. Get the new target
function get_new_target(){
document.getElementById("guessDiv").style.visibility = "hidden" ;
document.getElementById("targetFormDiv").style.visibility = "visible" ;
}
|
The result is the new target form, as shown in Figure 1.
Figure 1. The new target form

When you enter a new item, such as "a lion" in that box and click Teach me!, it runs the submit_new_target() function, as in Listing 4.
Listing 4. Submitting a new target
...
show_form("targetFormDiv");
}
var newTarget;
function submit_new_target(){
newTarget = document.getElementById("targetForm").elements[0].value;
document.getElementById("newTarget").innerHTML = newTarget;
document.getElementById("oldTarget").innerHTML = currentGuessText;
hide_form("targetFormDiv");
show_form("answerFormDiv");
}
function hide_form(divName){
...
|
Notice that you declare newTarget so you can use it later, after you retrieve it from the form. From there, set the information on the answerFormDiv (see Listing 5) and show it.
Listing 5. The answerFormDiv
<div id="answerFormDiv" style="position: absolute; top: 50px;
visibility: hidden; width: 100%;">
<form id="answerForm" name="answerForm" >
What question distinguishes <span id="newTarget"></span>
from <span id="oldTarget"></span>?<br />
<input type="text" name="newQuestion" value="" id="newQuestion" /><br />
What is the correct answer for this item?
<select id="newAnswer" name="newAnswer">
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<input type="button" value="Add Question" onclick="add_new_question()" />
</form>
</div>
|
You can see the result in Figure 2.
Figure 2. Getting the new question

Now you need to add that data to the knowledge base.
Supplementing the knowledge base and using JavaScript variables
The first step in adding the new item to the database is to create a new question element and add that, as in Listing 6.
Listing 6. Adding a new question
...
var nextQuestionId = 3;
var nextTargetId = 5;
function add_new_question(){
var newQuestion = document.getElementById("answerForm").elements[0].value;
var newAnswer = document.getElementById("answerForm").elements[1].value;
thisQuestionId = nextQuestionId;
nextQuestionId++;
var newQuestionXML = <question id={thisQuestionId}>
<display>{newQuestion}</display>
<answerOption>Yes</answerOption>
<answerOption>No</answerOption>
</question>;
var newQuestionElement = new XML(newQuestionXML);
knowledgeBase.questions.appendChild(newQuestionElement);
}
|
After you retrieve the new question and answer, it's time to create the new element. You can retrieve the new question ID from the nextQuestionId variable, which you then need to keep updated.
Next, you create the new element. You'll use the same XML syntax you used in Part 1 (see Resources), but here you can interpolate the new ID and display values using JavaScript variable syntax. Notice that the ID attribute has no quotes around it; it's the value that is important; if you put it in quotes, you'll wind up with a value of "{thisQuestionId}" rather than "3".
Once you create the XML, you can make an XML() object out of it and add it to the questions element using the appendChild() method.
Then you add the new answer to the original target. In other words, if the application guessed "a house cat" and you said it was "a lion", you need to tell the knowledge base that the answer to "Is it wild?" is "No" for "a house cat" (see Listing 7).
Listing 7. Add the new target
...
var newQuestionElement = new XML(newQuestionXML);
knowledgeBase.questions.appendChild(newQuestionElement);
//Add new answer to old target
var oldAnswer = "Yes";
if (newAnswer == "Yes"){
oldAnswer = "No";
}
var oldTargetNewAnswer = <answer questionid={thisQuestionId}></answer>;
oldTargetNewAnswer["answerValue"+thisQuestionId] = oldAnswer;
knowledgeBase..target.(@id==currentGuessId).appendChild(oldTargetNewAnswer);
...
|
Start by getting the old and new answers, based on what the user submitted, and create a new answer element. Here you see a couple of different techniques in play.
First you use variable interpolation, which you saw a moment ago.
The second method is to use the hash notation. Note that you create the oldTargetNewAnswer.answerValue3 element implicitly; if it doesn't exist, E4X creates it.
Finally, you select all of the target elements in the knowledge base, filter for just the target that corresponds to the currentGuessId, and add the new answer element to it. That takes care of the existing target.
Now you need to create the new target. This new target element should include all of the previous answers that apply to it, so the easiest way is to make a copy of the old target, as in Listing 8.
Listing 8. Create the new target
...
knowledgeBase..target.(@id==currentGuessId).appendChild(oldTargetNewAnswer);
var oldTargetElement = new XML();
oldTargetElement = knowledgeBase..target.(@id==currentGuessId);
//Clone old target
var newTargetElement = oldTargetElement.copy();
newTargetElement.@id = nextTargetId;
nextTargetId++;
newTargetElement.display = newTarget;
var newAnswerElement = newTargetElement.answer.(@questionid == thisQuestionId);
newAnswerElement["answerValue"+thisQuestionId] = newAnswer;
knowledgeBase.targets.appendChild(newTargetElement);
hide_form("answerFormDiv");
start_over();
}
|
To start, get a reference to the old target element, as before. You can then use the
copy() method to create a clone of the element. Once you
have that, you need to reset the ID and the display to the new values. Finally, you need to change the value of the most recent answer element to the new value.
Now you can add the new element to the knowledge base.
At this point, you can start_over() with the first question, and unless you've removed the alert() statement, you can see the new question and target after choosing "Animal", as in Figure 3.
Figure 3. The new values

So until you reload the page, any new items and questions you add are in play; you can see them by simply thinking of the new item and answering questions accordingly.
Unfortunately, as soon as you reload the page, the knowledge base reverts to its original state. Also, you're the only one who can see your new items. Wouldn't it be nice if everybody's new items were seen by everyone else?
The new and improved algorithm
To make everybody's new information available to other players, you need to set up an external database. For the purpose of this example, you'll use a MySQL database and PHP, but because the actual implementation is irrelevant, I won't cover it here. (You can download the SQL files and the PHP script to maintain and generate the knowledge base in Resources.). When you take the original algorithm and add in the integration with this database, you get something like this:
- Get the most recent knowledge base from the database.
- Ask the user whether the item is "animal, vegetable, or mineral?"
- Eliminate all of the items that don't satisfy the answer to the question.
- If you're down to just one item, ask the user if it's correct.
If it's correct, reload the knowledge base from the database to get fresh data and start again.
If it's not correct, find out what the correct response is and ask the user for a question that would have distinguished the right answer from the wrong one.- Send the question to the database and get back the ID for the new question
- Use the new questionid to add answers to the old and new targets, and to add the new target to the knowledge base
- If you still have more than one item, determine the question that applies to the greatest number of the remaining items. (This is key; it enables you to eliminate the greatest number of potential items.)
- Ask that question.
- Return to step 3.
The key to the interaction between the application and the database is the Prototype JavaScript library.
You'll make significant use of the well-known Ajax capabilities in the Prototype JavaScript library, but the library is actually a pretty full-featured suite of functionality. Prototype features four basic areas:
- Class management: Prototype includes easier creation and extension of classes and objects.
- DOM management: Prototype includes easier hooks for page elements, particularly when it comes to forms, and convenience methods for tasks such as showing or hiding elements.
- JSON: Prototype includes fast, safe methods for converting to and from JavaScript Object Notation, including generating an object directly from a string.
- Ajax: The star of the show, Prototype's Ajax capabilities make it easy to request data from an external URL and display that information on the page. Prototype also includes a periodical updater, although you won't use it in this article.
To make Prototype's classes and methods available, download the latest file from the Prototypejs.org Web site and add it to your HTML page, as in Listing 9.
Listing 9. Adding Prototype to your HTML page
<html> <head> <title>E4X mindreader</title> <script type="text/javascript" src="prototype.js"></script> <script type="text/javascript; e4x=1" src="e4x.js"></script> ... |
Let's start with some of the simpler tasks.
Although it's certainly possible to use the raw DOM to manipulate the contents of a
Web page, Prototype has a number of convenience functions. For example, you can
access an element using the $() function, so the expression $('answerFormDiv') refers to the element with an ID of answerFormDiv.
You can use this capability to clean up a lot of your DOM actions from Part 1. For example, you can replace:
document.getElementById("displayQuestion").innerHTML = questionDisplay ; with
$("displayQuestion").innerHTML = questionDisplay ;.
You can also get easier access to form elements using the $F() function. For example, you can replace
newTarget = document.getElementById("targetForm").elements[0].value; with newTarget = $F("newTargetDisplay");.
These functions use the ID attributes of HTML elements, so you need to make sure that you include them in the HTML file.
Now look at what it takes to integrate this application with a backend database.
To request the knowledge base, use Prototype's Ajax.Request object. You can use this object to send an HTTP request and call a new function based on the results. In this case, you want to request the XML string that represents the knowledge base, and then use that string to create an XML object, as in Listing 10.
Listing 10. Requesting the knowledge base in e4x.js
function get_knowledge_base(){
new Ajax.Request("knowledgebase.php",
{method: "get",
parameters: {getkb: 'YES'},
onSuccess: function(transport){
kstring = transport.responseText;
knowledgeBase = new XML(kstring);
start_over();
}
})
}
|
When you create the new Ajax.Request object, you pass a
significant amount of data to it. First, you give it the URL of the HTTP request you want to make. You can specify the hostname, but in general, Ajax requests must come from the same server and port as the page making the request, so I'll just use a relative URL.
You also specify that you want to use the GET method, and that you have a single parameter, getkb, with a value of YES. You can use the parameters parameter to set multiple values, as you'll see in a moment.
Perhaps the most crucial part of this function is the onSuccess handler, which tells the object what to do when and if the request succeeds. (Prototype defines as many as 7 different handlers, so you can be prepared for just about anything.) When the request succeeds, the script extracts the text of the request—remember, it's just the XML that represents the document—and uses it to create knowledgeBase as a new XML() object. Once that's done, it simply starts the page rendering by sending the browser to the start_over() function.
If you save this file and reload the page, you'll see everything function as before, with two exceptions. First, if alerts still show the data to you, you'll see that it's now the database controlling the data in the knowledge base. Second, if other people have updated the integrated database, you'll have more already programmed targets than before.
Speaking of new data, look at ho to enter your new data.
One important thing to realize when you work with the Ajax functions in Prototype: They are, of necessity, asynchronous. All you can do is send off the request and make a note to do something when it comes back. That's right, you can't just send it off and wait; instead, you have to tell it what it should do when it's ready.
For this reason, you need to break up the add_new_question()
function. First, you send off the question, but you can't add the target until you get the questionid back, as in Listing 11.
Listing 11. Sending off the question
function add_new_question(){
var newQuestion = $F('newQuestion');
var newAnswer = $F('newAnswer');
var thisQuestionId;
new Ajax.Request("knowledgebase.php",
{method: "get",
parameters: {getkb: 'NO', question: newQuestion},
onSuccess: function(transport){
thisQuestionId = transport.responseText;
finish_adding_new_question(newQuestion, newAnswer, thisQuestionId);
}
})
}
|
This is pretty straightforward. First you retrieve the new question and answer, and then you send it to the PHP file. Notice the use of multiple name-value pairs separated by the comma in the parameters.
At this point you wait until the script returns, and end the function there. When it does return, however, it brings with it the ID for the new question, so you can move to the second half of the routine, as in Listing 12.
Listing 12. Completing the addition of a new question
function finish_adding_new_question(newQuestion, newAnswer, thisQuestionId){
var newQuestionXML = <question id={thisQuestionId}>
<display>{newQuestion}</display>
<answerOption>Yes</answerOption>
<answerOption>No</answerOption>
</question>;
var oldAnswer = "Yes";
if (newAnswer == "Yes"){
oldAnswer = "No";
}
new Ajax.Request("knowledgebase.php",
{method: "get",
parameters: {addAnswers: 'YES',
old_target_id: currentGuessId,
old_answer: oldAnswer,
target_display: newTarget,
question_id: thisQuestionId,
new_answer: newAnswer},
onSuccess: function(transport){
get_knowledge_base();
}
})
}
|
As before, you create the interior of the question element, and determine the old and new answers. Once you have that, you can initiate a new request. Notice that this one includes multiple parameters. It sends all of this information to the PHP script, which takes all of that information and creates the new target in the database.
Once that routine returns, you know you can request the knowledge base and get the most current information.
What you built here certainly can't be called artificial intelligence, but it does provide a good approximation for user entertainment. Each time a user plays and thinks of an item not in the knowledge base, the application adds that item and the appropriate question and answers to the database. By using the Prototype JavaScript library, you can enable your application to request a fresh copy of the knowledge base each time you play, so that it always has the most up-to-date information.
Of course, this solution presents its own issues; at the time of this writing the knowledge base is minuscule. If many people play it and add a lot of items, to download the full database even once might not be practical, much less each time the user plays a new game.
At that point, you have several options. The simplest is to send only the relevant set. For example, you might hard-code the first "animal, vegetable, mineral" question, then download only the third of the knowledge base that applies. Eventually, if the database got big enough, you probably want to do your processing on the server rather than in the browser, but that's taking things well beyond the scope of what is intended here, which is a small, mindreader application using E4X and Prototype.
| Description | Name | Size | Download method |
|---|---|---|---|
| Part 2 sample code | x-e4xpart2code.zip | 5KB | HTTP |
Information about download methods
Learn
- Live sample application: See and try the finished Twenty Questions application for this article.
- E4X specification (PDF file, 1.81 MB): Read the official syntax and semantics for ECMAScript for XML, a set of programming language extensions that add native XML support to ECMAScript.
- Ajax and scripting Web Services with E4X (Paul Fremantle and Anthony Elder, developerWorks, April 2005): In this article, find a more complex example of E4X and JavaScript combined to initiate Web services requests.
- Sample knowledge base: Download the latest version of the knowledge base the Twenty Questions application.
- 20q.net: Try a real implementation of Twenty Questions using a neural net.
- The Game of Twenty Questions: Learn strategies for playing Twenty Questions with humans.
- Binary search algorithm: On Wikipedia, learn more about this technique to find a particular value in a sorted list.
- Tutorial: Build apps using Asynchronous JavaScript with XML (Naveen Balani and Rajeev Hathi, developerWorks, November 2005): Learn to develop and design Web applications based on Asynchronous JavaScript with XML, or Ajax.
- Ajax and XML: Five Ajax anti-patterns (Jack Herrington, developerWorks, March 2007): Learn which common practices of Ajax coding to avoid in this article.
- Ajax and XML: Five common Ajax patterns (Jack Herrington, developerWorks, March 2007): Look at five common Ajax design patterns that you can use as a basis for your own work.
- Ajax for Java developers: Build dynamic Java applications (Philip McArthy, developerWorks, September 2005): Dig into this five-part series that introduces a groundbreaking approach to creating dynamic Web application experiences using Ajax.
- IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
- XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
- developerWorks technical events and webcasts: Stay current with technology in these sessions.
- The technology bookstore: Browse for books on these and other technical topics.
Get products and technologies
- Prototype library: Download the Ajax library and try out this JavaScript Framework and easy-to-use toolkit for class-driven development in dynamic Web apps.
- IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
Discuss
- Participate in the discussion forum.
- XML zone discussion forums: Participate in any of several XML-related discussions.
- developerWorks XML zone: Share your thoughts: After you read this article, post your comments and thoughts in this forum. The XML zone editors moderate the forum and welcome your input.
- developerWorks blogs: Check out these blogs and get involved in the developerWorks community.
Nicholas Chase has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, an Oracle instructor, and the Chief Technology Officer of an interactive communications company. He is the author of several books, including XML Primer Plus (Sams). He is also a partner in InterSection Unlimited, which specializes in creating Second Life content and applications. You can find him in-world as Chase Marellan.




