Personal tools
You are here: Home Documentation Tutorials Using YUI on Grok Introducing AJAX

Introducing AJAX

Up until now we haven't done anything AJAXy, however. We've used some JavaScript to make the page look a bit more dynamic, but that's about it.
In this tutorial you'll be learning how to use the Yahoo! User Interface Library, or YUI in short in your Grok project. YUI is a JavaScript and CSS library providing all sorts of handy components that you can use to make you site work an look better without too much effort, and in a cross-browser compatible way. In the process, you'll also be learning some basic uses of megrok.layout, megrok.navigation and JSON.
Page 3 of 6.

What is AJAX?

AJAX stands for Asynchronous JavaScript And XML. A non-AJAX page works synchronously, in the sense that you request a page, read it, click a link or submit a form, and the entire page is refreshed. Repeat ad infinitum. However, in most cases a large part of the new page is exactly the same as the previous page: headers, footers, menus and perhaps more. So the server is actually doing a lot of work for something that only changed partly. And a lot of network bandwidth is wasted too. If only you could just update the needed parts...

This is exactly what AJAX was invented for, and why it is said to be asynchronous: you do something on the page which triggers a request to the server that only returns some data or a part of the page, which is then dynamically inserted in the page. Multiple requests would be pending, and the responses need not arrive in the order of the requests. The request and the update are usually done using JavaScript, although technically other scripting languages could be used, too (including Python!).

That explains the A and the J, but what about XML? In the initial definition it was specified that XML was to be used for data interchange between client and server, and for that purpose the XMLHttpRequest object was introduced. Since then however, it has become clear that XML is not a requirement: you could as well send plain text, pre-formatted HTML or format your data as JSON (JavaScript Object Notation, see below); it all depends on how you handle the data. So the X stands for whatever rather than XML, nowadays.

A quick note on JSON: JSON is a very lightweight way of defining an object in plain text. It is much easier to decode or produce JSON data than XML data: e.g. calling eval('<json-text>') in JavaScript will result in a JavaScript object. JSON also very closely matches a text-representation of a Python dictionary (if there really IS a difference?).

Getting the AJAX feeling

The first thing we're going to do with AJAX is using the YUI TabView, which can use AJAX under the hood to load the contents of the tabs, so you can have a feeling of what AJAX can do without having to do too much 'plumbing' yourself. We'll change the way a blog entry is presented with tabs: one for viewing the entry, and one for viewing meta data (Dublin Core).

A TabView can be used to display contents that are in the HTML to start with, or as we want, it can load the contents from an 'external source': a separate view on our server. However, in that case it also needs the YUI Connection Manager to be loaded, so we shouldn't forget that!

We'll go for the pre-formatted HTML approach, so the current 'blogindex.pt' has to be displayed inside the View tab, but using the BlogIndex will cause the layout to be loaded too, so we'll have to make a separate view that doesn't show layout and change the original 'blogindex.pt' to display the TabView.

Copy 'blogindex.pt' to 'blogview.pt'. Create a grok View called BlogView in 'blog.py', and specify that BlogIndex now needs yui.tabview and yui.connection like this:

from megrok import resource
from hurry import yui

class BlogIndex(layout.Page):
    grok.name('index')
    resource.include(yui.tabview)
    resource.include(yui.connection)

class BlogView(grok.View):
    grok.name('view')

Note that we derive from grok.View, and not layout.Page!

We also used another way of specifying needed resources. Instead of using the need() function, you can also use megrok.resource's include directive. There's a catch, however: this directive only works with objects you actually traverse to: in this case we traverse over our app object to a BlogEntry object and finally to the BlogIndex view. In case of our Layout object, this won't work, because you never traverse to your layout, it is merely looked up in code when needed by a Page to which you did traverse. So be careful with this!

Now edit 'blogindex.pt'

<div id="blog-view" class="yui-navset">
    <ul class="yui-nav">
    </ul>
    <div class="yui-content">
    </div>
</div>

We need some JavaScript to hook it up and load the appropriate CSS. Add a script and stylesheet viewlet to the file 'blog.py':

from layout import Scripts, StyleSheets

class BlogIndexScript(grok.Viewlet):
    grok.view(BlogIndex)
    grok.viewletmanager(Scripts)
    grok.context(Interface)
    grok.template('script')

class BlogIndexStylesheet(grok.Viewlet):
    grok.viewletmanager(StyleSheets)
    grok.context(Interface)
    template = grok.PageTemplate('<link rel="stylesheet" type="text/css" '
         'tal:attributes="href '
                'context/++resource++yui/tabview/assets/skins/sam/tabview.css" />')

and the associated 'script.pt' template in the 'blog-templates' directory:

<tal:tag tal:replace='structure string:<script type="text/javascript">' />
        var myTabs = new YAHOO.widget.TabView("blog-view");
        myTabs.addTab(new YAHOO.widget.Tab(
                {label:'View',
                 dataSrc: '<tal:tag tal:replace="python:view.url(context, 'view')"/>',
                 active: true})
                    );
        myTabs.addTab(new YAHOO.widget.Tab(
                {label:'Meta Data',
                 dataSrc: '<tal:tag tal:replace="python:view.url(context, 'meta')"/>'})
                    );

<tal:tag tal:replace="structure string:</script>" />

You might be wondering why we didn't just use normal <script> elements. This is because we need some dynamic parts in the script: the urls to the views. ZPT will not parse anything that is placed in <script> (and <style>) elements because typically that is application code that should not be parsed. Maybe that wasn't such a good idea after all, but this is how ZPT works, so we have to deal with it. Another workaround would be to add a funtion to the viewlet that returns the JavaScript as a string and use the tal:content expression on the script tag. Or overwrite the render method and do it all by yourself.

The only thing left now is the meta-data view in 'blog.py'.

class MetaDataView(grok.View):
    grok.name('meta')

and 'metadataview.pt' in 'blog-templates'

<span tal:define="created context/zope:created;
                  modified context/zope:modified;
                  formatter python:request.locale.dates.getFormatter('dateTime')">
        <dl>
        <span tal:condition="created" > <dt>Created</dt>
            <dd tal:content="python:formatter.format(created)"></dd></span>
        <span tal:condition="modified" >        <dt>Modified</dt>
            <dd tal:content="python:formatter.format(modified)"></dd></span>
        </dl>
</span>

We're using the Zope implementation of Dublin Core, which is a de facto industry standard for meta-data in document-oriented systems. I won't go any further into that, as it has nothing to do with JavaScript or AJAX.

The Result

Now restart the server and check it out.

site_p3.jpg

Each time you switch tabs, the content is loaded from the server. You can also change the behaviour, such that the contents are only loaded once, by setting the 'cacheData' configuration option of the tab to true. But we specifically don't want that behaviour, or the rest of the tutorial won't work!