IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML | Web development  >

用 E4X 和 Prototype 创建 Ajax mindreader 应用程序,第 2 部分: 使 mindreader 应用程序更智能化

使用 Prototype 连接后端数据库

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论

样例代码

英文原文

英文原文


级别: 中级

Nicholas Chase (ibmquestions@nicholaschase.com), 自由撰稿人, 自由职业

2008 年 3 月 17 日

在这个分两部分的文章系列中,学习如何用 ECMAScript for XML(E4X)和 Prototype JavaScript 库创建一个简单的 Ajax mindreader 应用程序,这个程序实现 Twenty Questions 游戏,并可以在游戏过程中学习新东西。在第 1 部分中,我们创建了一个系统,它接受并分析一个现有的知识库,从而判断用户可能在想什么。在第 2 部分中,将学习在知识库中添加新信息,并使用 Prototype JavaScript 库把 Twenty Questions 应用程序与一个外部数据库集成起来,让一个用户在知识库中添加的新信息能够对其他用户有所帮助。

完成后的应用程序见 http://www.backstopmedia.com/examples/e4x.html。本系列假设您熟悉 XML 和 JavaScript 概念。如果需要了解背景知识,请参见 参考资料。还需要一个支持 E4X 的浏览器,比如 Firefox 1.5 或更高版本。

目前的状态

如果您还没有阅读第 1 部分,现在就应该这么做(参见 参考资料)。在第 1 部分中,我们创建了一个应用程序,它用问题(问题的答案主要是 "yes" 和 "no")分析一个知识库,判断用户正在想什么。知识库与清单 1 类似。


清单 1. 知识库示例
                
<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>

常用缩写词
  • Ajax:Asynchronous JavaScript™ and XML
  • DOM:Document Object Model
  • HTML:Hypertext Markup Language
  • JSON:JavaScript Object Notation
  • XML:Extensible Markup Language

为了进行这种分析,我们使用一个算法提出问题、排除与答案不相符的所有内容、在余下的问题中选择最相关的问题并再次提问,直到只剩下一项。

这时候,应用程序猜测用户心里想的就是最后一项。如果猜测正确,应用程序就重新开始。但是,如果猜测错了,就要做更多的工作了。







回页首


猜测错误

如果应用程序找到了最后的可能项,而这一项是错误的,就需要让系统知道 用户想的究竟是什么。例如,如果用户想的是 “芹菜” 而系统猜的是 “胡萝卜”,系统就需要知道有芹菜这一项。它还需要知道如何区分芹菜和胡萝卜。

第一个任务是查明正确答案是什么。表单很简单,见清单 2。

注意:如果您还没有按照第 1 部分去做,那么通过 参考资料 下载目前的应用程序。


清单 2. 新目标表单
                
<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>

当用户告诉应用程序猜测错误时,要显示这个表单,它会调用 get_new_target() 函数,见清单 3。


清单 3. 获得新目标
                
function get_new_target(){
     document.getElementById("guessDiv").style.visibility = "hidden" ;
     document.getElementById("targetFormDiv").style.visibility = "visible" ;
}

结果是图 1 所示的新目标表单。


图 1. 新目标表单
新目标表单

在框中输入一个新项(比如 “a lion”)并单击 Teach me!,它就会运行 submit_new_target() 函数,见清单 4。


清单 4. 提交新目标
                
...
     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){
...

注意,这里声明了 newTarget,所以在从表单获得新目标之后可以使用它。然后在 answerFormDiv 中设置这一信息(见清单 5)并显示它。


清单 5. 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>

结果见图 2。


图 2. 获得新问题
获得新问题

现在需要把这些数据添加到知识库中。





回页首


补充知识库的内容和使用 JavaScript 变量

把新项添加到数据库中的第一步是,创建并添加一个新的问题元素,见清单 6。


清单 6. 添加新问题
                
...
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);

}

在获得新问题和答案之后,就该创建新元素了。可以从 nextQuestionId 变量获得新问题的 ID,然后需要更新这个变量。

接下来,创建新元素。仍然使用第 1 部分中使用过的 XML 语法(参见 参考资料),但是在这里可以使用 JavaScript 变量语法插入新的 ID 和 display 值。注意,ID 属性不带引号;它是一个值;如果加上引号,就会得到 "{thisQuestionId}" 而不是 "3"。

创建 XML 之后,可以从 XML 生成一个 XML() 对象,然后使用 appendChild() 方法把它添加到问题元素中。

然后把新的答案添加到原来的目标中。换句话说,如果应用程序猜测 “a house cat”,而用户说它是 “a lion”,就需要告诉知识库:如果对 “Is it wild?” 针对 “a house cat” 的答案是 “No”(见清单 7)。


清单 7. 添加新目标
                
...       
    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);
...

首先,根据用户提交的信息获得原来的答案和新答案,并创建一个新的答案元素。在这里使用两种不同的技术。

首先使用变量插入,这在前面已经见过了。

第二种方法是使用散列表示法。注意,这里隐式地创建 oldTargetNewAnswer.answerValue3 元素;如果它不存在,E4X 就创建它。

最后,选择知识库中的所有目标元素,过滤出与 currentGuessId 对应的目标,并在其中添加新的答案元素。这样就更新了现有目标。

现在需要创建新目标。这个新目标元素应该包含应用于它的所有以前的答案,所以最简便的方法是复制原来的目标,见清单 8。


清单 8. 创建新目标
                
...
    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();

}

首先,获得原来的目标元素的引用。然后,使用 copy() 方法创建这个元素的拷贝。在此之后,需要把 ID 和 display 重新设置为新的值。最后,需要把最近的答案元素的值改为新的值。

现在,可以把新元素添加到知识库中。

现在,可以用 start_over() 从头开始游戏。除非删除了 alert() 语句,否则在选择 “Animal” 之后就可以看到新的问题和目标,见图 3。


图 3. 新值
新值
所以,直到重新装载页面之前,添加的所有新项和问题都会起作用;您只要心里想着添加的新项,并相应地回答问题,就会看到它们。

但糟糕的是,重新装载页面之后,知识库就会回到原来的状态。另外,用户只能看到自己添加的新项。如果每个人都能够看到别人添加的新项,那不是很棒吗?





回页首


改进后的新算法

为了让玩家能够利用所有人添加的新信息,需要设置一个外部数据库。对于本文,我们使用一个 MySQL 数据库和 PHP,但是因为实际的实现与本文的主题无关,所以这里不讨论实现方法。(可以从 参考资料 下载 SQL 文件和 PHP 脚本来维护和生成知识库。)以原来的算法为基础,添加数据库集成之后,就形成下面的算法:

  1. 从数据库中获取最新的知识库。
  2. 问用户他想的项是 “animal, vegetable, or mineral?”。
  3. 排除所有与这个问题的回答不相符的项。
  4. 如果只剩下最后一项,就询问用户这个猜测是否正确。
    如果正确,就从数据库重新装载知识库并重新开始游戏。
    如果不正确,就询问用户究竟是什么项,并要求用户提供一个可以区分正确答案和错误答案的问题。
    • 把这个问题发送到数据库并返回新问题的 ID
    • 使用新的问题 ID 在原来的目标和新目标中添加答案,并把新目标添加到知识库中
  5. 如果仍然有多个项,就要确定一个问题,这个问题应该应用于当前范围中尽可能多的项。(这是关键;这样可以排除尽可能多的可能选择。)
  6. 提出这个问题。
  7. 返回到第 3 步。

应用程序和数据库之间交互的关键是 Prototype JavaScript 库。





回页首


Prototype 简介

我们将主要使用 Prototype JavaScript 库中的 Ajax 功能,但是实际上这个库的功能非常全面。Prototype 提供四个方面的特性:

  • 类管理:Prototype 支持更轻松地创建和扩展类和对象。
  • DOM 管理:Prototype 支持更轻松地连接页面元素,尤其对于表单,并提供了执行显示或隐藏元素等任务的简便方法。
  • JSON:Prototype 可以快速可靠地转换 JavaScript Object Notation,包括从字符串直接生成对象。
  • Ajax:Prototype 的 Ajax 功能简化了从外部 URL 请求数据并在页面上显示信息的过程。Prototype 还包含一个定期更新器,但是本文并不使用它。

为了使用 Prototype 类和方法,从 Prototypejs.org Web 站点下载最新的文件并把它添加到 HTML 页面中,见清单 9。


清单 9. 在 HTML 页面中添加 Prototype
                
<html>
<head>
<title>E4X mindreader</title>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript; e4x=1" src="e4x.js"></script>
...

我们先从一些比较简单的任务开始。





回页首


表单管理

尽管肯定可以使用 DOM 操作 Web 页面的内容,但是 Prototype 提供了许多简便的函数。例如,可以使用 $() 函数访问一个元素,所以表达式 $('answerFormDiv') 引用 ID 为 answerFormDiv 的元素。可以使用这个功能简化第 1 部分中的许多 DOM 操作。例如,可以把 document.getElementById("displayQuestion").innerHTML = questionDisplay ; 替换为 $("displayQuestion").innerHTML = questionDisplay ;

还可以使用 $F() 函数简化对表单元素的访问。例如,可以把 newTarget = document.getElementById("targetForm").elements[0].value; 替换为 newTarget = $F("newTargetDisplay");

这些函数使用 HTML 元素的 ID 属性,所以一定要在 HTML 文件中包含 ID 属性。

现在看看如何集成这个应用程序和后端数据库。





回页首


请求知识库

使用 Prototype 的 Ajax.Request 对象请求知识库。可以使用这个对象发送 HTTP 请求并根据结果调用一个新函数。在这个示例中,希望请求一个代表知识库的 XML 字符串,然后使用这个字符串创建一个 XML 对象,见清单 10。


清单 10. 在 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();
       }
      })    
}

在创建新的 Ajax.Request 对象时,要传递许多数据。首先,要提供 HTTP 请求的 URL。可以指定主机名,但是一般来说 Ajax 请求必须来自与发出请求的页面相同的服务器和端口,所以我们只使用相对 URL。

还需要指定希望使用 GET 方法,并指定一个值为 YES 的 getkb 参数。可以使用 parameters 参数设置多个值,稍后将会见到。

这个函数最重要的部分可能是 onSuccess 处理函数,它告诉对象在请求成功时应该做什么。Prototype 定义了 7 个不同的处理函数,所以可以处理几乎任何情况。当请求成功时,脚本提取出请求的文本(请记住,这仅仅是代表文档的 XML),并使用它创建一个新的 XML() 对象 knowledgeBase。完成这些之后,只需调用 start_over() 函数开始显示页面。

如果保存这个文件并重新装载页面,就会看到所有功能都与以前一样,只有两点不同。首先,如果仍然显示警告框,您会看到现在是由数据库在控制知识库中的数据。第二,如果别人更新了集成的数据库,您也会看到比以前更多的目标。

我们来看看如何输入新数据。





回页首


发送新问题

在使用 Prototype 中的 Ajax 功能时,一定要认识到一点:它们是异步的。您能做的只是发送请求并牢记下一步操作;您必须告诉它在完成时应该做什么。因此,需要把 add_new_question() 函数分成两部分。首先,发送问题,但是在返回 questionid 之前无法添加目标,见清单 11。


清单 11. 发送问题
                
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);
       }
      })

}

这非常简单。首先获取新问题和答案,然后把它发送给 PHP 文件。注意,在 parameters 中使用了多个以逗号分隔的名称-值对。

现在,等待脚本返回并结束这个函数。当脚本返回时,它会提供新问题的 ID,所以可以转到这个例程的第二部分,见清单 12。


清单 12. 完成新问题的添加过程
                
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();
       }
      })
}

与前面一样,创建问题元素并更新原来的答案和新答案。完成之后,可以发出一个新请求。注意,这个请求包含多个参数。它把所有信息发送给 PHP 脚本,脚本获取所有信息并在数据库中添加新目标。

这个例程返回之后,就可以请求知识库并获得最新的信息。





回页首


结束语和以后的改进

我们在这里构建的应用程序肯定还算不上人工智能,但是它能够给用户提供娱乐。每当用户心里想着知识库中没有的项玩这个游戏时,应用程序会在数据库中添加该项以及适当的问题和答案。通过使用 Prototype JavaScript 库,在每次玩游戏时应用程序都请求知识库的新拷贝,所以总会获得最新的信息。

分享这篇文章……

digg 提交到 Digg
del.icio.us 发布到 del.icio.us
Slashdot Slashdot 一下!

当然,这个解决方案也有问题;到编写本文时,知识库的内容还不丰富。如果许多人玩这个游戏并添加许多项,那么下载完整的数据库内容可能不现实,必须设法减少下载时间。

目前,有几种方法。最简单的方法是只发送相关的集合。例如,可以对第一个问题 “animal, vegetable, mineral” 进行硬编码,然后只下载相关的三分之一知识库。最终,如果数据库变得非常大了,可以考虑在服务器而不是浏览器中进行处理;但是本文的意图只是使用 E4X 和 Prototype 开发小型 mindreader 应用程序,在服务器上进行处理超出了本文的范围。






回页首


下载

描述名字大小下载方法
第 2 部分示例代码x-e4xpart2code.zip5KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • Prototype 库:下载 Ajax 库并使用这个 JavaScript 框架和易用的工具集进行类驱动的动态 Web 应用程序开发。

  • IBM 试用版软件:使用这些可以从 developerWorks 直接下载的试用版软件构建您的下一个开发项目。


讨论


关于作者

Nicholas Chase 曾经参与多家公司的网站开发,包括 Lucent Technologies、Sun Microsystems、Oracle 和 Tampa Bay Buccaneers。Nick 曾经做过高中物理教师、低放射性废弃设备管理员、在线科幻杂志的编辑、多媒体工程师、Oracle 教员以及一家交互通信公司的首席技术官。他出版了多部著作,包括 XML Primer Plus(Sams)。他还是 InterSection Unlimited 的合伙人,这家公司从事 Second Life 内容和应用程序的创建。在 Second Life 中,他的名字是 Chase Marellan。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

将您的建议发给我们或者通过参加讨论与其他人分享您的想法.




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款