MochiKit 示例
设计师们长期梦想的交互式网页终于实现了——Web 界面可以即时响应用户命令,只需最少的页面重绘。所有这些以及更多功能都通过 Ajax(Asynchronous JavaScript and XML,异步 JavaScript 和 XML)实现,Ajax 最近在 Web 领域备受瞩目。
在 JavaScript 以及一般的编程中,最好的代码就是你无需编写的代码。对于严肃的项目,这通常意味着使用框架——一个经过测试、优化和(理想情况下)同行评审的有用且可重用代码的集合。更好的框架具有自动化的单元测试,以确保它们持续工作。例如,拥有一个良好的、高级的 JavaScript 框架意味着有更多时间来突破界限,更少时间编写枯燥的基础代码(然后在不可避免的跨浏览器不兼容性出现时重新修改它)。
MochiKit ( www.mochikit.com ) 是一个 JavaScript 框架,它提供了处理异步请求 (Ajax)、DOM (文档对象模型) 功能、函数式编程工具、日期和时间、字符串格式化、颜色、视觉效果、事件、拖放能力、排序和许多其他功能的工具。MochiKit 1.3 的目标是在 Safari 2.0.2、Firefox (1.0.7、1.5 和 2.0)、Opera 8.5 和 Internet Explorer (6 和 7) 上工作。
本文快速介绍了 MochiKit,解释了如何开始使用 MochiKit(沿途会有有趣的停顿),并描述了三个不同复杂程度的演练示例,这些示例也足够通用,可以立即在您的 Web 应用程序中使用。有没有想过在 HTML 中使元素角变圆?创建一个只能点击一次的链接?创建动态登录机制?继续阅读!
MochiKit 中包含用于数据结构(包括序列化)、函数式编程、迭代、DOM 和 CSS 操作、异步服务器通信、JavaScript 事件的信号/槽机制和日志记录算法。在这一点上,MochiKit 听起来像是 JavaScript 的 C++ STL。除了 STL 为 C++ 提供的功能之外,MochiKit 还提供事件处理、拖放、颜色和视觉效果。
关于数据结构算法,MochiKit 提供了强大的迭代工具 filter(),它只返回符合条件的列表元素;find();map(),它返回通过操作运行的列表元素的结果;等等。MochiKit 还提供了在 JSON (JavaScript 对象表示法) 语法之间转换的工具:serializeJSON() 和 evalJSON()。最重要的是,MochiKit 使您能够将自己的对象挂钩到 MochiKit 的魔力中。
MochiKit 的函数式编程工具允许动态创建函数,或者它们可以简单地为 JavaScript 中已提供的功能提供更广泛(或更少错误)的行为。MochiKit 的 partial() 和 bind() 函数分别创建一个需要较少参数的函数版本或重新绑定 JavaScript 的 this 参数。简而言之,这些工具使您可以动态创建函数。这两个不同的函数现在可能不是非常有用,但当与 MochiKit 的迭代工具结合使用时,它们会变得非常强大。
除了这些数据结构工具外,MochiKit 还允许您动态创建 DOM 元素、将 DOM 对象转换为字符串、检索匹配类或类型属性的元素,以及将 DOM 对象与其他 DOM 对象交换。
想看看 MochiKit 的魔力吗?mochikit.com 上的演示页面有几个有趣的示例。MochiKit 的一个示例是一个交互式 JavaScript 解释器,可以执行您输入的任何 JavaScript 代码。此外,此解释器还通过 help() 提供 MochiKit 函数的文档,返回指向传递函数的clickable链接。
MochiKit 的文档页面也使用一些(启用 MochiKit 的)JavaScript 来显示 MochiKit 子命名空间的列表。当主页加载时,它会动态创建子命名空间的列表 (MochiKit.Async、MochiKit.Base、MochiKit.DOM 等)。单击列表中的任何项目会展开或折叠命名空间的文档。实际上没有函数列表存在——所有内容都是通过(异步)从服务器请求每个文档页面,然后解析每个文档页面的 DOM 来动态计算的。值得注意的是,在线文档与当前正在开发的 MochiKit 版本相对应,而不是当前的“稳定”版本;每个函数还列出了它出现的 MochiKit 版本。
好的框架都有测试来验证其功能,MochiKit 也不例外。MochiKit 测试页面(请参阅资源)有近 800 个测试来验证 MochiKit。这种自动化框架允许在所有支持的浏览器上轻松验证 MochiKit。
MochiKit 包易于安装。如果您下载了 zip 版本,请将 lib 文件夹中的 Mochikit 文件夹移动到您的 Web 空间。要使用 Subversion 版本,请从您的 checkout 中复制 Mochikit 文件夹。
创建以下 HTML 页面
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>MochiKit Example #1</title> <script type="text/javascript" charset="utf-8" src="Mochikit/MochiKit.js" /> <script type="text/javascript" charset="utf-8" src="example1.js" /> </head> <body> <p style="background: red; padding-top: 1em;"> Hello world this is Mochikit!</p> </body> </html>
请注意您的 <head> 部分中的以下行,它加载了 MochiKit
<script type="text/javascript" charset="utf-8" src="Mochikit/MochiKit.js" />
另请注意,body 中有一个带有红色背景的段落。这个框看起来很普通。如果它有圆角不是更好吗?MochiKit 使其变得容易。
首先,我们希望在加载页面时执行 JavaScript 函数。为此,MochiKit 函数是 MochiKit.DOM.addLoadEvent()。在 JavaScript 中,命名空间说明符是可选的,但为了清晰起见,我们在此处包含它们。
我们为我们的 JavaScript 创建一个单独的文件(单独的文件是最好的)。在该文件中,我们有一个函数和一个对 addLoadEvent() 的调用
function myLoadFunction() { MochiKit.Visual.roundClass('p', null); }; MochiKit.DOM.addLoadEvent(myLoadFunction);
MochiKit 的 roundClass 允许您指定整个类类型,并使其类中的所有元素都变为圆角。您还可以使用 MochiKit.Visual.roundElement() 选择性地使元素变为圆角,它接受指定 id 的字符串或元素对象。
我们的第二个示例使用 MochiKit.Base.map()、MochiKit.Base.partial()、MochiKit.Signal 和 MochiKit.DOM 创建一个只能单击一次然后消失的链接
function myLoadFunction(eventObj) { /* Find all A elements whose class is "onepush" and make them all to call handleJSHREFClick() in response to click */ elementsToApplyOn = MochiKit.DOM.getElementsByTagAndClassName( "a", "onepush"); /*now that we have all of the elements that match our transformation query run our function that connects everything, calling it once for every item.*/ MochiKit.Base.map( connectOneClickOnly, elementsToApplyOn ); } MochiKit.Signal.connect(window, "onload", myLoadFunction); //end main and load
MochiKit 的 Signal 模块允许我们在事件发生时调用函数(它基于 Qt 的信号/槽机制)。在最后一行代码的情况下,我们让 window 对象的 onload 事件调用我们的 load 函数。细心的读者会记得第一个示例中使用的 MochiKit.DOM.addLoadEvent(),是的,我们正在使用与 MochiKit.Signal 类似的功能。请注意,一旦您选择了一种处理 load 事件的方法,您就不能在同一脚本中使用另一种方法——它们在某种程度上是不兼容的。
当文档在浏览器中加载时,将调用 myLoadFunction()。此函数收集所有类为 oneclick 的 A 元素,并将它们逐个传递给 connectOneClickOnly 函数。MochiKit.Base.map 函数可以看作是编写以下模式的便捷方式
for (i = 0; i < elementsToApplyOn.length; i++) connectOneClickOnly(elementsToApplyOn[i]);
接下来,我们检查 partial() 和 bind() 的真正亮点——为回调函数提供参数
function connectOneClickOnly(linkElement) { /* This function gets called for each A of type "oneclick" we have. Hook it up so that our handleJSHREFClick gets called (properly) when a user clicks the linkElement object */ /*Each of our calls to handleJSHREFClick, in addition to getting the event object passed to it via MochiKit.Signal, also gets called with the object to call to create our replacement. */ newH = partial(handleJSHREFClick, makeNewObj); MochiKit.Signal.connect( linkElement, 'onclick', newH ); } //end connectOneClickOnly
请记住,MochiKit.Base.partial() 和 MochiKit.Base.bind() 允许运行时创建基于其他函数的函数。这些包装函数可以提供参数,甚至可以将 JavaScript 的 this 变量重新映射到它们包装的函数。
在这种情况下,我们使用 MochiKit.Base.partial(),因为无法为通过 MochiKit.Signal(或任何其他回调用户函数的 MochiKit 方法)调用的函数提供任意参数。使用 MochiKit.Base.partial(),我们可以传递任意数量的参数,而 MochiKit 毫不知情。在这种情况下,我们提供了一个函数,该函数创建替换的 SPAN 元素,以作为我们的事件处理程序回调。我们让 MochiKit.Signal 在用户单击我们的元素时调用我们的函数
function makeNewObj(target) { /* Create a new item to replace our target with. Return the created element */ makeNew = SPAN({}); inHTMLStr = "One Click Only!"; makeNew.innerHTML = inHTMLStr; return makeNew; } function handleJSHREFClick(makeNewF, eventObj) { /* When one of our "oneclick" elements have been clicked, this function runs. */ ourTarget = eventObj.target(); /*stop the event right here (don't let it go to the href listed in the A) Here also so the event is stopped if we have errors further on*/ eventObj.stop(); //call our function that creates new elements makeNew = makeNewF(ourTarget); swapDOM(ourTarget, makeNew); } //end click functionality code
如前所述,当用户单击我们的 oneClick A 元素时,将调用 handleJSHREFClick() 函数。通常,此函数仅接受一个参数:MochiKit.Signal 传递的 eventObj 参数。由于我们使用了 MochiKit.Base.partial(),因此该函数会传递另一个参数(在本例中,是用于创建替换对象的函数)。
MochiKit.Signal 负责处理事件的繁重工作。无论用户使用什么浏览器(或该浏览器使用什么事件模型),JavaScript 代码都不必更改——来自 MochiKit.Signal 的自定义事件对象会为您处理所有这些。通过传递的事件对象,您可以获取键状态、鼠标状态、触发事件的对象、connect() 连接到事件的对象、发生的事件类型,甚至通过阻止 DOM 对象的默认操作来阻止事件进一步传播。
handleJSHREFClick() 将用户单击的项目与我们创建的元素交换。首先,它停止事件,因为(在本例中)我们不想再进一步(那将跟随 A 的 HREF 元素,这是我们在此特定示例中不希望发生的事情)。
创建替换 span 的代码位于显而易见的位置:makeNew = SPAN({}),但这怪物需要一些解释。DOM 元素通过 MochiKit 中的 MochiKit.DOM 中的函数创建。像其他 MochiKit 模块一样,这里有很多内容。在此模块中可以找到创建、查询、交换甚至转换 DOM 元素的函数——getElement()、getElementsByTagAndClassName()、currentDocument()、currentWindow() 和 createDOM() 列举几个。MochiKit.DOM.createDOM() 是此处(间接)使用的函数。MochiKit.DOM 包括创建常见 DOM 元素的快捷方式(在撰写本文时为 A、BUTTON、BR、CANVAS、DIV、FIELDSET、FORM、H1、H2、H3、HR、IMG、INPUT、LABEL、LEGEND、LI、OL、OPTGROUP、OPTION、P、PRE、SELECT、SPAN、STRONG、TABLE、TBODY、TD、TEXTAREA、TFOOT、TH、THEAD、TR、TT 和 UL)。这些通过关联数组参数中指定的属性调用。在传递的数组中,每个键对应于 HTML 元素的属性。例如,要创建指向 example.com 的链接,代码将是
makeNew = A({'href':'http://www.example.com'});
此示例涵盖了很多内容——MochiKit.Signal.connect()、MochiKit.Base.partial() 和 MochiKit.DOM.createElement()。但是,我们仅触及了这些的表面,还有很多 MochiKit 内容要介绍。下一个示例将 Web 上随处可见的普通登录框“Web 2.0”化。
我们的最后一个示例创建一个动态登录屏幕。这个想法是在不刷新整个网页的情况下为不正确的密码提供反馈。成功后,主菜单屏幕将加载,而无需完全页面重新加载。
MochiKit 的日志记录功能不仅用于调试。相反,它可以被选择作为一种简单的错误报告机制。我们使用此机制来报告不正确的用户名/密码错误以及服务器上登录脚本的错误(服务器宕机等)
function fatalLog(sendLogTo, logInst) /*handles our logError calls, displays in element param #1, displaying the error in yellow then fading it out after 5 seconds*/ { var errStr = logInst.info.join(" "); if (errStr.length == 0) errStr = "Unknown error"; sendLogTo.innerHTML = "<pre> We're sorry an error occurred: " + errStr + ". Please try again. </pre>"; Highlight( sendLogTo, {delay: 1, duration: 5} ); //Yellow Fade Technique //see Visual.DefaultOptions documentation for //associative array options that affect //MochiKit.Visual } //end fatalLog
选择 MochiKit 的日志功能意味着可以使用 MochiKit.Logging 调用报告所有错误。在调试此站点期间,可以设置其他内容来处理错误——可能记录更多信息或进入源代码级调试器。
我们在这里有一个问题:当页面的一部分重绘时,有时不明显页面的那部分包含新信息。用户可能在等待某些事情发生,而实际上它已经发生了。Ajax 社区通过(传统上)以黄色突出显示更改的元素并在一段时间后淡回正常背景来解决此问题。这种技术(黄色淡入淡出技术)尤其被 37 Signals(Ruby on Rails 背后的思想家)使用。此处使用该技术来提示用户对错误做出反应(例如,再次尝试密码)。MochiKit 通过 MochiKit.Visual.Highlight() 使此技术变得非常容易。我们可以指定在开始效果之前延迟多长时间、它应该持续多长时间以及其他选项,所有这些都通过关联数组指定(请参阅 MochiKit.Visuals.DefaultOptions 中概述的键)。MochiKit 的许多视觉效果都是从 Scriptaculous 为 MochiKit 1.4 移植过来的。
如下面的代码示例所示,我们的 load 函数将我们的 fatalLog 函数插入到 MochiKit.Logging 框架中,然后设置环境以在按下提交按钮时调用我们的 subClicked 处理程序
function subClicked(eventObj) /*checks the username/pw*/ { d = MochiKit.Async.doSimpleXMLHttpRequest( cgiLoginLocation, { 'username': getElement('uname').value, 'passw': getElement('pword').value } ); d.addCallback(handleServerResult_Login); d.addErrback(handleServerError); getElement('waitMsg').innerHTML = "Please wait..."; clearError(); //clear the old error message, it doesn't apply } //end subClicked function myLoadFunction() { /*first create our Logging listener, and direct our generic function the errMsg span we have*/ fatalLogTo = partial(fatalLog, MochiKit.DOM.getElement('errorMsg')); MochiKit.Logging.logger.addListener('ERRORONLY', null, fatalLogTo); MochiKit.Signal.connect( 'submit', 'onclick', subClicked ); //now hide the place where our main menu will be MochiKit.Style.hideElement("Result"); } /*connect our event handlers right off*/ MochiKit.Signal.connect(window, "onload", myLoadFunction); //end script
MochiKit.Async.doSimpleXMLHttpRequest 是 MochiKit 中执行 Ajax 请求的最简单方法。它接受要向其发送请求的 URI、该 URI 的 GET 参数,并返回一个 MochiKit.Async.Deferred 对象。对于高级需求,MochiKit.Async 还提供发送 POST 而不是 GET、获取 JSON 文档等功能。所有 MochiKit.Async Ajax 函数都返回一个 Deferred 对象,因此(除了构造之外),这些函数的行为与我们的示例 doSimpleXMLHttpRequest() 中的行为完全相同。
当我们拥有 Deferred 对象时,Ajax 事件已经发送出去。由于调用是异步的,因此可能需要几秒钟才能收到答案——有足够的时间来设置将处理错误或成功的函数(回调)。MochiKit.Async.Deferred 对象仅仅是对某些事情将发生的保证——发生什么取决于我们。当 Deferred 对象从 MochiKit Ajax 请求返回时,脚本的执行会在我们等待时继续。这允许我们设置回调并执行所需的任何其他内务处理。当服务器返回响应时,无论好坏,都会调用在 Deferred 对象中设置的适当回调。
成功和失败回调函数从 MochiKit.Async 获取一个状态参数。可以通过将其他参数传递给 MochiKit.Async.addCallback()/MochiKit.Async.addErrback() 来将它们发送到回调函数。状态参数始终是提供给回调函数的最后一个参数。如果 XMLHttpRequest 中的 HTTP 状态代码为 200、201、204 或 304,则调用成功函数。如果状态是任何其他数字,则调用错误函数。成功函数将标准 XMLHttpRequest 对象作为状态对象获取。如果您从未见过其中一个,则重要项是 responseText 和 status。失败函数获取 XMLHttpRequestError 参数。此对象的重要项是 message 和 number。
有了所有这些背景知识,我们就可以处理我们的 XMLHttpRequest。请记住,此示例背后的想法是在登录屏幕中完成所有必需的工作,而无需刷新页面。为了实现这一点,首先我们发送一个 XMLHttpRequest 来检查用户名和密码并返回会话 ID。在我们的例子中,来自 CGI 的 responseText 是一个字符串,其内容格式化为 JavaScript 数组。使用此格式化,我们可以运行 JavaScript 的 eval() 来获取 JavaScript 数组对象的结果,或者我们可以简单地调用 MochiKit.Async.evalJSONRequest() 来执行相同的操作。在我们的例子中,数组包含(按顺序):一个成功布尔值、一个失败消息(如果调用成功,则为空字符串)和一个会话 ID(如果失败,则为 0)。
成功回调 handleServerResult_Login() 应检查从 CGI 返回的成功。如果我们成功,它会发送第二个 Ajax 请求来加载主菜单。在此第二个请求期间,服务器检查会话 ID,确保其有效,然后返回主菜单的 HTML 代码(或错误消息)。当此(第二个)请求成功时,handleServerResult_Manage() 会清除登录控件(我们通过 MochiKit.Visual 提供的酷炫过渡效果来完成此操作)并插入主菜单代码。成功后,可以设置 cookie 以保存会话 ID(有关 cookie 操作函数,请参阅 www.quirksmode.org/js/cookies.html ;未来版本的 MochiKit 可能会包含 cookie 操作函数)
function handleServerError(err) { getElement("waitMsg").innerHTML = ""; logError( err.message + " (error #" + err.number + ")" ); //err.message will be like "Request Failed" } //end handleServerError function handleServerResult_Manage(sessionID, res) { //get rid of our login controls - we're very //much validated by this point slideUp( getElement('loginDlg') ); //our responseText will be the HTML for the //"main menu" whereTo = getElement("Result"); whereTo.innerHTML = res.responseText; MochiKit.Visual.appear( whereTo, {delay: 1} ); createCookie("sessionID", sessionID, 1); getElement("waitMsg").innerHTML = "Cookie value = " + readCookie('sessionID'); } //end handleServerResult_Manage function handleServerResult_Login (res) { getElement("waitMsg").innerHTML = ""; //no more waiting required! //res.responseText contains our result. Our CGI //returns it as a JS array inside a string //but just let MochiKit handle it for us resList = MochiKit.Async.evalJSONRequest(res); success = resList[0]; failMsg = resList[1]; sessionID = resList[2]; if (success) { //send off _another_ AJAX request //(passing session id), this time to get //the main menu screen d2 = MochiKit.Async.doSimpleXMLHttpRequest( cgiMainMenuLocation, {'sessionID': sessionID } ); d2.addCallback( handleServerResult_Manage, sessionID ); d2.addErrback(handleServerError); } else { logError(failMsg); } } //end handleServerResult_Login
资源
MochiKit 截屏视频: mochikit.com/screencasts/MochiKit_Intro-1.html 。虽然它仅涵盖 MochiKit 1.1,但它应该让您了解 MochiKit 的可能性。作为一项附加功能,整个截屏视频都使用了 MochiKit 示例页面中的 JavaScript 交互式解释器。
MochiKit 测试页面: www.mochikit.org/tests/index.html 。通过访问此页面运行 MochiKit 框架的测试。
MochiKit Subversion 仓库: svn.mochikit.com/mochikit 。获取当前或正在开发中的 MochiKit 版本,或随时了解最新的更新版本。
JavaScript 世界杯: www.sitepoint.com/article/javascript-library/2 。JavaScript 库领域当前重量级人物(截至撰写本文时)的概述:Dojo、Prototype/script.aculo.us、MochiKit 和 Yahoo UI 库。
斑马表对决: jquery.com/blog/2006/10/18/zebra-table-showdown 。使用主要的 JavaScript 库参与者制作具有交替背景行的表格(如 iTunes 播放列表)。
Turbogears: www.turbogears.org 。MochiKit 是 TurboGears Python Web 应用程序框架的一部分,但是,正如我们在此处看到的,它可以完全独立于 TurboGears 使用。
Scriptaculous: script.aculo.us 。MochiKit 的一些视觉效果函数是从 Scriptaculous 移植过来的。
Ryan Wilcox 是 Wilcox Development Solutions ( www.wilcoxd.com ) 的创始人,该公司专门从事跨平台应用程序开发和 Web 解决方案。他还认为自己是编程语言的“全科医生”。他唯一的希望是永远不必从宇宙飞船的疯狂计算机中拔出内存核心。