Interactive Web pages, long dreamed of by designers, are finally here—Web interfaces that respond instantly to user commands, with minimal page redraw. All this and more is possible via Ajax (Asynchronous JavaScript and XML), which recently has come into vogue with the Web illuminati.
In JavaScript, and programming in general, the best code is code you don't have to write. For serious projects, this often means using a framework—a collection of useful and reusable code that is tested, optimized and (ideally) peer-reviewed. The better frameworks have automated unit tests to make certain they keep working. Having a good, high-level JavaScript framework, for example, means more time to push the boundaries and less time writing boring building-block code (and then reworking it when the inevitable cross-browser incompatibilities poke their heads up).
MochiKit (www.mochikit.com) is a JavaScript framework that provides tools for dealing with asynchronous requests (Ajax), DOM (Document Object Model) functionality, functional programming tools, dates and times, string formatting, colors, visual effects, events, drag-and-drop ability, sorting and many other features. MochiKit 1.3 is targeted to work on Safari 2.0.2, Firefox (1.0.7, 1.5 and 2.0), Opera 8.5 and Internet Explorer (6 and 7).
This article provides a quick introduction to MochiKit, explains how to get started with MochiKit (with interesting stops along the way) and describes three walk-through examples of varying complexity that also are generic enough to use in your Web applications right now. Ever wanted to round element corners in HTML? Make a link that is clickable only once? Create a dynamic login mechanism? Keep reading!
Included in MochiKit are algorithms for data structures (including serialization), functional programming, iteration, DOM and CSS manipulation, asynchronous server communication, a signal/slot mechanism for JavaScript events and logging tools. At this point, MochiKit sounds like the C++ STL of JavaScript. Above and beyond what the STL provides C++, MochiKit also provides event handling, drag and drop, colors and visual effects.
On the topic of data structure algorithms, MochiKit provides the powerful iteration tools of filter(), which returns only list elements that match a criteria; find(); map(), which returns the result of list elements run through an operation; and more. MochiKit also provides tools to translate to and from JSON (JavaScript Object Notation) syntax: serializeJSON() and evalJSON(). Above all, MochiKit gives you the power to hook your own objects into MochiKit's magic.
MochiKit's tools for functional programming allow functions to be created dynamically, or they may simply provide more extensive (or less broken) behaviors for functionality provided already in JavaScript. MochiKit's partial() and bind() functions create a version of a function that requires less parameters or rebind JavaScript's this parameter, respectively. In a nutshell, these tools let you create functions dynamically. These two different functions aren't obviously useful right now, but they are powerful when combined with MochiKit's iteration tools.
In addition to these data structure tools, MochiKit allows you to create DOM elements dynamically, convert DOM objects to strings, retrieve elements matching class or type attributes, and swap DOM objects for other DOM objects.
Want to see some MochiKit magic? The Demos page on mochikit.com has several interesting samples. One of MochiKit's examples is an interactive JavaScript Interpreter, executing whatever JavaScript code you enter. In addition, this interpreter provides documentation for MochiKit functions—via help()—returning a clickable link to the passed function.
MochiKit's documentation page also uses some (MochiKit-enabled) JavaScript to display a list of the sub-namespaces of MochiKit. When the main page loads, it dynamically creates a list of sub-namespaces (MochiKit.Async, MochiKit.Base, MochiKit.DOM and so on). Clicking any item in the list expands or collapses the documentation for the namespace. No actual list of functions exists—it's all computed dynamically by (asynchronously) requesting each documentation page from the server, then parsing the DOM of each one. It is worth noting that the documentation on-line corresponds with the release of MochiKit currently in development and not the current “stable” version; each function also lists the version of MochiKit in which it appeared.
Good frameworks have tests to validate their functionality, and MochiKit is no different. The MochiKit test page (see Resources) has almost 800 tests validating MochiKit. This automated framework allows MochiKit to be validated easily on all the supported browsers.
The MochiKit package is easy to install. If you downloaded the zip version, move the Mochikit folder in the lib folder to your Web space. To use the Subversion version, copy the Mochikit folder from your checkout.
Create the following HTML page:
<!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>
Notice the following line in your <head> section, which loads MochiKit:
<script type="text/javascript" charset="utf-8" src="Mochikit/MochiKit.js" />
Also notice that in the body is a paragraph with a red background. This box is plain-looking. Wouldn't it be great if it had rounded corners? MochiKit makes it easy.
First, we want to execute a JavaScript function when we load the page. The MochiKit function for that is MochiKit.DOM.addLoadEvent(). In JavaScript, the namespace specifiers are optional, but for clarity, we include them here.
We create a separate file for our JavaScript (a separate file is best). In the file, we have one function and a call to addLoadEvent():
function myLoadFunction() { MochiKit.Visual.roundClass('p', null); }; MochiKit.DOM.addLoadEvent(myLoadFunction);
MochiKit's roundClass allows you to specify an entire class type, and it rounds all of the elements of that class. You also can round elements selectively with MochiKit.Visual.roundElement(), which accepts either a string specifying the id or an element object.
Our second example uses MochiKit.Base.map(), MochiKit.Base.partial(), MochiKit.Signal and MochiKit.DOM to create a link that can be clicked only once and then goes away:
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's Signal module allows us to have functions called when events happen (it's based off Qt's signal/slot mechanism). In the case of the last line of code here, we are having the window object's onload event call our load function. Careful readers will remember MochiKit.DOM.addLoadEvent() used in the first example, and yes, we are using similar functionality with MochiKit.Signal. Be aware that once you choose one method of handling the load event, you cannot, in the same script, use the other method—they are incompatible in that way.
When the document loads in the browser, myLoadFunction() is called. This function gathers all the A elements whose class is oneclick and passes them, one by one, to the connectOneClickOnly function. The MochiKit.Base.map function could be seen as a convenience in writing the following pattern:
for (i = 0; i < elementsToApplyOn.length; i++) connectOneClickOnly(elementsToApplyOn[i]);
Next, we examine the real sweet spot for partial() and bind()—providing parameters to callback functions:
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
Remember, MochiKit.Base.partial() and MochiKit.Base.bind() allow for runtime creation of functions that are based on other functions. These wrapper functions can provide parameters or even remap the JavaScript for this variable to the functions they are wrapping.
In this case, we use MochiKit.Base.partial(), because there is no way to provide arbitrary arguments to functions called via MochiKit.Signal (or any other MochiKit methods that call back to user functions). Using MochiKit.Base.partial(), we can pass as many parameters as we want, and MochiKit is none the wiser. In this case, we supply a function, which creates the replacement SPAN element, to our event handler callback. We have MochiKit.Signal call our function when the user clicks on our element:
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
The handleJSHREFClick() function is called, as previously mentioned, when a user clicks on our oneClick A elements. Normally, this function would accept only one parameter: the eventObj parameter passed by MochiKit.Signal. Because we used MochiKit.Base.partial(), the function is passed another parameter (in this case, the function to call to create our replacement object).
MochiKit.Signal takes care of the hard work of handling events. No matter what browser the user is using (or what event modal that browser uses), the JavaScript code doesn't have to change—the custom event object from MochiKit.Signal takes care of all that for you. Through the passed event object, you can get the key state, the mouse state, the object that triggered the event, the object connect()ed to the event, what type of event happened, and even stop the event from propagating further by preventing the default action of the DOM object.
handleJSHREFClick() swaps the item the user clicked on with the element we created. First it stops the event, because (in this instance) we don't want to go any further (that would follow the HREF element of the A, something we don't want to happen in this particular example).
The code that creates our replacement span is in the obvious place: makeNew = SPAN({}), yet this monster requires some explanation. DOM elements are created through MochiKit via functions in MochiKit.DOM. Like other MochiKit modules, there's a lot here. Functions to create, functions to query, swap and even convert DOM elements can be found in this module—getElement(), getElementsByTagAndClassName(), currentDocument(), currentWindow() and createDOM() to list a few. MochiKit.DOM.createDOM() is what is (indirectly) used here. MochiKit.DOM includes shortcuts to create common DOM elements (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 and UL, at the time of this writing). These are called with the attributes specified in the associative array parameter. In the passed array, each key corresponds to an attribute of the HTML element. For example, to create a link to example.com, the code would be:
makeNew = A({'href':'http://www.example.com'});
This example covered a lot of ground—MochiKit.Signal.connect(), MochiKit.Base.partial() and MochiKit.DOM.createElement(). However, we've scratched only the surface of these, and there's a whole lot more of MochiKit to cover. The next example takes the normal login box found all over the Web and “Web 2.0's” it up.
Our final example creates a dynamic login screen. The idea is to provide feedback for an incorrect password without refreshing the entire Web page. On success, the main menu screen loads without requiring a full page reload.
MochiKit's logging functionality isn't only for debugging. Instead, it can be co-opted to be an easy error-reporting mechanism. We use this mechanism to report both incorrect user name/password errors and errors with the login script on the server (server down and so on):
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
Co-opting MochiKit's log functionality means that all errors can be reported using MochiKit.Logging calls. During debugging of this site, something else could be set up to handle the errors—perhaps logging more information or breaking into a source-level debugger.
We have an issue here: when parts of the page redraw, it's sometimes not obvious that part of the page contains new information. The user could be waiting for something to happen when, in fact, it already has. The Ajax community has solved this by highlighting changed elements in yellow (traditionally) and fading back to the normal background after an amount of time. This technique (the yellow fade technique) is used, in particular, by 37 Signals (the minds behind Ruby on Rails). The technique is used here to prompt users to react to the error (for example, try the password again). MochiKit makes this technique very easy with MochiKit.Visual.Highlight(). We can specify how long to delay before starting the effect, how long it should last and other options, all specified via an associative array (see the keys outlined in MochiKit.Visuals.DefaultOptions). Many of MochiKit's visual effects were ported from Scriptaculous for MochiKit 1.4.
As shown in the next code sample, our load function plugs our fatalLog function into the MochiKit.Logging framework, then sets up the environment to call our subClicked handler when the submit button is pressed:
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 is the simplest way in MochiKit to do an Ajax request. It accepts the URI to which to send the request, the GET parameters for that URI, and it returns a MochiKit.Async.Deferred object. For advanced requirements, MochiKit.Async also provides functionality for sending POSTs instead of GETs, obtaining JSON documents and more. All MochiKit.Async Ajax functions return a Deferred object, so (beyond the construction) these functions behave exactly as in our example doSimpleXMLHttpRequest().
By the time we have our Deferred object, the Ajax event already is sent off. Because the call is asynchronous, it may be several seconds until an answer is received—plenty of time to set up functions (callbacks) that will handle error or success. The MochiKit.Async.Deferred object is merely a guarantee that something will happen—what happens is up to us. When a Deferred object comes back from a MochiKit Ajax request, execution of the script continues while we wait. This allows us to set up our callbacks and do whatever other housekeeping is required. When a response comes back from the server, good or bad, the appropriate callback set up in the Deferred object is called.
The success and failure callback functions get one status parameter from MochiKit.Async. Additional parameters can be sent to the callback functions by passing them to MochiKit.Async.addCallback()/MochiKit.Async.addErrback(). The status parameter always is the last parameter provided to the callback function. Success functions are called if the HTTP status code from the XMLHttpRequest is 200, 201, 204 or 304. The error function is called if the status is any other number. Success functions get a standard XMLHttpRequest object as the status object. If you've never seen one of these, the important items are responseText and status. Failure functions get an XMLHttpRequestError parameter. The important items of this object are message and number.
With all this background, we can handle our XMLHttpRequest. Remember, the idea behind this example is to do all the work required in a login screen without refreshing the page. To accomplish this, first we send an XMLHttpRequest to check the user name and password and return a session ID. In our case, the responseText from the CGI is a string whose contents are formatted as a JavaScript array. With this formatting, we could run JavaScript's eval() to get the result as a JavaScript array object, or we could simply call MochiKit.Async.evalJSONRequest() to do the same thing. In our case, the array contains (in order): a success boolean, a failure message (or an empty string, if the call succeeded) and a session ID (or 0, in the case of failure).
The success callback handleServerResult_Login() should check the success passed back from the CGI. If we have a success, it sends a second Ajax request to load the main menu. During this second request, the server checks the session ID, making sure it is valid, then returns HTML code for the main menu (or an error message). When this (second) request succeeds, the handleServerResult_Manage() clears the login controls away (which we do with a cool transition effect, courtesy of MochiKit.Visual) and inserts the main menu code. On success, cookies can be set to save the session ID (see www.quirksmode.org/js/cookies.html for cookie manipulation functions; future versions of MochiKit may include cookie manipulation functions):
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 is a powerful toolkit, making advanced features easy in JavaScript. From visual effects, event handling and functional tools to Ajax functionality, MochiKit puts amazing features at your fingertips.