.. -*- coding: utf-8 -*- .. include:: warning.rst Javascript ---------- *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. Conventions ~~~~~~~~~~~ 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. .. XXX external_resources variable (which needs love) Server-side Javascript API ~~~~~~~~~~~~~~~~~~~~~~~~~~ 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: * `add_js(self, jsfiles, localfile=True)` which takes a sequence of javascript files and writes proper entries into the HTML header section. The localfile parameter allows to declare resources which are not from web/data (for instance, residing on a content delivery network). * `add_onload(self, jscode)` which adds one raw javascript code snippet inline in the html headers. This is quite useful for setting up early jQuery(document).ready(...) initialisations. Javascript events ~~~~~~~~~~~~~~~~~ * ``server-response``: this event is triggered on HTTP responses (both standard and ajax). The two following extra parameters are passed to callbacks : - ``ajax``: a boolean that says if the reponse was issued by an ajax request - ``node``: the DOM node returned by the server in case of an ajax request, otherwise the document itself for standard HTTP requests. Important javascript AJAX APIS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * `asyncRemoteExec` and `remoteExec` are the base building blocks for doing arbitrary async (resp. sync) communications with the server * `reloadComponent` is a convenience function to replace a DOM node with server supplied content coming from a specific registry (this is quite handy to refresh the content of some boxes for instances) * `jQuery.fn.loadxhtml` is an important extension to jQuery which allows proper loading and in-place DOM update of xhtml views. It is suitably augmented to trigger necessary events, and process CubicWeb specific elements such as the facet system, fckeditor, etc. A simple example with asyncRemoteExec ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On the python side, we have to define an :class:`cubicweb_web.views.ajaxcontroller.AjaxFunction` object. The simplest way to do that is to use the :func:`cubicweb_web.views.ajaxcontroller.ajaxfunc` decorator (for more details on this, refer to :ref:`ajax`). .. sourcecode: python from cubicweb_web.views.ajaxcontroller import ajaxfunc # serialize output to json to get it back easily on the javascript side @ajaxfunc(output_type='json') def js_say_hello(self, name): return u'hello %s' % name On 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. .. sourcecode: javascript function asyncHello(name) { var deferred = asyncRemoteExec('say_hello', name); deferred.addCallback(function (response) { alert(response); }); deferred.addErrback(function (error) { alert('something fishy happened'); }); } function syncHello(name) { alert( remoteExec('say_hello', name) ); } Anatomy of a reloadComponent call ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `reloadComponent` allows to dynamically replace some DOM node with new elements. It has the following signature: * `compid` (mandatory) is the name of the component to be reloaded * `rql` (optional) will be used to generate a result set given as argument to the selected component * `registry` (optional) defaults to 'components' but can be any other valid registry name * `nodeid` (optional) defaults to compid + 'Component' but can be any explicitly specified DOM node id * `extraargs` (optional) should be a dictionary of values that will be given to the cell_call method of the component A simple reloadComponent example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The server side implementation of `reloadComponent` is the :func:`cubicweb_web.views.ajaxcontroller.component` *AjaxFunction* appobject. The following function implements a two-steps method to delete a standard bookmark and refresh the UI, while keeping the UI responsive. .. sourcecode:: javascript 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. Anatomy of a loadxhtml call ~~~~~~~~~~~~~~~~~~~~~~~~~~~ `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`_. .. _`jQuery.load`: http://api.jquery.com/load/ * `url` (mandatory) should be a complete url (typically referencing the :class:`cubicweb_web.views.ajaxcontroller.AjaxController`, but this is not strictly mandatory) * `data` (optional) is a dictionary of values given to the controller specified through an `url` argument; some keys may have a special meaning depending on the choosen controller (such as `fname` for the JSonController); the `callback` key, if present, must refer to a function to be called at the end of loadxhtml (more on this below) * `reqtype` (optional) specifies the request method to be used (get or post); if the argument is 'post', then the post method is used, otherwise the get method is used * `mode` (optional) is one of `replace` (the default) which means the loaded node will replace the current node content, `swap` to replace the current node with the loaded node, and `append` which will append the loaded node to the current node content About the `callback` option: * it is called with two parameters: the current node, and a list containing the loaded (and post-processed node) * whenever it returns another function, this function is called in turn with the same parameters as above This mechanism allows callback chaining. A simple example with loadxhtml ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. .. sourcecode:: python from cubicweb_web.views.ajaxcontroller import ajaxfunc @ajaxfunc(output_type='xhtml') def frob_status(self, eid, frobname): entity = self._cw.entity_from_eid(eid) return entity.view('frob', name=frobname) .. sourcecode:: javascript function updateSomeDiv(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/ajax?`). The actual AjaxController method name is encoded in the `params` dictionary using the `fname` key. A more real-life example ~~~~~~~~~~~~~~~~~~~~~~~~ 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. .. sourcecode:: python def lazyview(self, vid, rql=None): """ a lazy version of wview """ self._cw.add_js('cubicweb.lazy.js') urlparams = {'vid' : vid, 'fname' : 'view'} if rql is not None: urlparams['rql'] = rql self.w(u'