CubicWeb uses quite a bit of javascript in its user interface and ships with jquery (1.3.x) and parts of the jquery UI library, plus a number of homegrown files and also other third party libraries.
All javascript files are stored in cubicweb/web/data/. There are around thirty js files there. In a cube it goes to data/.
Obviously one does not want javascript pieces to be loaded all at once, hence the framework provides a number of mechanisms and conventions to deal with javascript resources.
It is good practice to name cube specific js files after the name of the cube, like this : ‘cube.mycube.js’, so as to avoid name clashes.
Javascript resources are typically loaded on demand, from views. The request object (available as self._cw from most application objects, for instance views and entities objects) has a few methods to do that:
In the python side, we have to extend the BaseController class. The @jsonize decorator ensures that the return value of the method is encoded as JSON data. By construction, the JSonController inputs everything in JSON format.
In the javascript side, we do the asynchronous call. Notice how it creates a deferred object. Proper treatment of the return value or error handling has to be done through the addCallback and addErrback methods.
reloadComponent allows to dynamically replace some DOM node with new elements. It has the following signature:
The server side implementation of reloadComponent is the js_component method of the JSonController.
The following function implements a two-steps method to delete a standard bookmark and refresh the UI, while keeping the UI responsive.
function removeBookmark(beid) {
d = asyncRemoteExec('delete_bookmark', beid);
d.addCallback(function(boxcontent) {
reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
});
}
reloadComponent is called with the id of the bookmark box as argument, no rql expression (because the bookmarks display is actually independant of any dataset context), a reference to the ‘boxes’ registry (which hosts all left, right and contextual boxes) and finally an explicit ‘bookmarks_box’ nodeid argument that stipulates the target DOM node.
jQuery.fn.loadxhtml is an important extension to jQuery which allows proper loading and in-place DOM update of xhtml views. The existing jQuery.load function does not handle xhtml, hence the addition. The API of loadxhtml is roughly similar to that of jQuery.load.
About the callback option:
This mechanism allows callback chaining.
Here we are concerned with the retrieval of a specific view to be injected in the live DOM. The view will be of course selected server-side using an entity eid provided by the client side.
from cubicweb import typed_eid
from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
@monkeypatch(JSonController)
@xhtmlize
def js_frob_status(self, eid, frobname):
entity = self._cw.entity_from_eid(typed_eid(eid))
return entity.view('frob', name=frobname)
function update_some_div(divid, eid, frobname) {
var params = {fname:'frob_status', eid: eid, frobname:frobname};
jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
}
In this example, the url argument is the base json url of a cube instance (it should contain something like http://myinstance/json?). The actual JSonController method name is encoded in the params dictionary using the fname key.
A frequent need of Web 2 applications is the delayed (or demand driven) loading of pieces of the DOM. This is typically achieved using some preparation of the initial DOM nodes, jQuery event handling and proper use of loadxhtml.
We present here a skeletal version of the mecanism used in CubicWeb and available in web/views/tabs.py, in the LazyViewMixin class.
def lazyview(self, vid, rql=None):
""" a lazy version of wview """
w = self.w
self._cw.add_js('cubicweb.lazy.js')
urlparams = {'vid' : vid, 'fname' : 'view'}
if rql is not None:
urlparams['rql'] = rql
w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
vid, xml_escape(self._cw.build_url('json', **urlparams))))
w(u'</div>')
self._cw.add_onload(u"""
jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
load_now('#lazy-%(vid)s');});"""
% {'event': 'load_%s' % vid, 'vid': vid})
This creates a div with a specific event associated to it.
The full version deals with:
The javascript side is quite simple, due to loadxhtml awesomeness.
function load_now(eltsel) {
var lazydiv = jQuery(eltsel);
lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
}
This is all significantly different of the previous simple example (albeit this example actually comes from real-life code).
Notice how the cubicweb:loadurl is used to convey the url information. The base of this url is similar to the global javascript JSON_BASE_URL. According to the pattern described earlier, the fname parameter refers to the standard js_view method of the JSonController. This method renders an arbitrary view provided a view id (or vid) is provided, and most likely an rql expression yielding a result set against which a proper view instance will be selected.
The cubicweb:loadurl is one of the 29 attributes extensions to XHTML in a specific cubicweb namespace. It is a means to pass information without breaking HTML nor XHTML compliance and without resorting to ungodly hacks.
Given all this, it is easy to add a small nevertheless useful feature to force the loading of a lazy view (for instance, a very computation-intensive web page could be scinded into one fast-loading part and a delayed part).
On the server side, a simple call to a javascript function is sufficient.
def forceview(self, vid):
"""trigger an event that will force immediate loading of the view
on dom readyness
"""
self._cw.add_onload("trigger_load('%s');" % vid)
The browser-side definition follows.
function trigger_load(divid) {
jQuery('#lazy-' + divd).trigger('load_' + divid);
}
There is also javascript support for massmailing, gmap (google maps), fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over AppEngine), flot (charts drawing), tabs and bookmarks.
You with the cubicweb.qunit.QUnitTestCase can include standard Qunit tests inside the python unittest run . You simply have to define a new class that inherit from QUnitTestCase and register your javascript test file in the all_js_tests lclass attribut. This all_js_tests is a sequence a 3-tuple (<test_file, [<dependencies> ,] [<data_files>]):
The <test_file> should contains the qunit test. <dependencies> defines the list of javascript file that must be imported before the test script. Dependencies are included their definition order. <data_files> are additional files copied in the test directory. both <dependencies> and <data_files> are optionnal. jquery.js is preincluded in for all test.
from cubicweb.qunit import QUnitTestCase
class MyQUnitTest(QUnitTestCase):
all_js_tests = (
("relative/path/to/my_simple_testcase.js",)
("relative/path/to/my_qunit_testcase.js",(
"rel/path/to/dependency_1.js",
"rel/path/to/dependency_2.js",)),
("relative/path/to/my_complexe_qunit_testcase.js",(
"rel/path/to/dependency_1.js",
"rel/path/to/dependency_2.js",
),(
"rel/path/file_dependency.html",
"path/file_dependency.json")
),
)