doc/book/en/development/devweb/js.rst
branchstable
changeset 5394 105011657405
parent 5393 875bdc0fe8ce
child 5395 e0ab7433e640
equal deleted inserted replaced
5393:875bdc0fe8ce 5394:105011657405
     1 .. -*- coding: utf-8 -*-
       
     2 
       
     3 Javascript
       
     4 ----------
       
     5 
       
     6 *CubicWeb* uses quite a bit of javascript in its user interface and
       
     7 ships with jquery (1.3.x) and parts of the jquery UI library, plus a
       
     8 number of homegrown files and also other third party libraries.
       
     9 
       
    10 All javascript files are stored in cubicweb/web/data/. There are
       
    11 around thirty js files there. In a cube it goes to data/.
       
    12 
       
    13 Obviously one does not want javascript pieces to be loaded all at
       
    14 once, hence the framework provides a number of mechanisms and
       
    15 conventions to deal with javascript resources.
       
    16 
       
    17 Conventions
       
    18 ~~~~~~~~~~~
       
    19 
       
    20 It is good practice to name cube specific js files after the name of
       
    21 the cube, like this : 'cube.mycube.js', so as to avoid name clashes.
       
    22 
       
    23 XXX external_resources variable (which needs love)
       
    24 
       
    25 CubicWeb javascript API
       
    26 ~~~~~~~~~~~~~~~~~~~~~~~
       
    27 
       
    28 Javascript resources are typically loaded on demand, from views. The
       
    29 request object (available as self._cw from most application objects,
       
    30 for instance views and entities objects) has a few methods to do that:
       
    31 
       
    32 * `add_js(self, jsfiles, localfile=True)` which takes a sequence of
       
    33   javascript files and writes proper entries into the HTML header
       
    34   section. The localfile parameter allows to declare resources which
       
    35   are not from web/data (for instance, residing on a content delivery
       
    36   network).
       
    37 
       
    38 * `add_onload(self, jscode)` which adds one raw javascript code
       
    39   snippet inline in the html headers. This is quite useful for setting
       
    40   up early jQuery(document).ready(...) initialisations.
       
    41 
       
    42 CubicWeb javascript events
       
    43 ~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    44 
       
    45 * ``server-response``: this event is triggered on HTTP responses (both
       
    46   standard and ajax). The two following extra parameters are passed
       
    47   to callbacks :
       
    48 
       
    49   - ``ajax``: a boolean that says if the reponse was issued by an
       
    50     ajax request
       
    51 
       
    52   - ``node``: the DOM node returned by the server in case of an
       
    53     ajax request, otherwise the document itself for standard HTTP
       
    54     requests.
       
    55 
       
    56 Important AJAX APIS
       
    57 ~~~~~~~~~~~~~~~~~~~
       
    58 
       
    59 * `asyncRemoteExec` and `remoteExec` are the base building blocks for
       
    60   doing arbitrary async (resp. sync) communications with the server
       
    61 
       
    62 * `reloadComponent` is a convenience function to replace a DOM node
       
    63   with server supplied content coming from a specific registry (this
       
    64   is quite handy to refresh the content of some boxes for instances)
       
    65 
       
    66 * `jQuery.fn.loadxhtml` is an important extension to jQuery which
       
    67   allows proper loading and in-place DOM update of xhtml views. It is
       
    68   suitably augmented to trigger necessary events, and process CubicWeb
       
    69   specific elements such as the facet system, fckeditor, etc.
       
    70 
       
    71 
       
    72 A simple example with asyncRemoteExec
       
    73 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    74 
       
    75 In the python side, we have to extend the BaseController class. The
       
    76 @jsonize decorator ensures that the `return value` of the method is
       
    77 encoded as JSON data. By construction, the JSonController inputs
       
    78 everything in JSON format.
       
    79 
       
    80 .. sourcecode: python
       
    81 
       
    82     from cubicweb.web.views.basecontrollers import JSonController, jsonize
       
    83 
       
    84     @monkeypatch(JSonController)
       
    85     @jsonize
       
    86     def js_say_hello(self, name):
       
    87         return u'hello %s' % name
       
    88 
       
    89 In the javascript side, we do the asynchronous call. Notice how it
       
    90 creates a `deferred` object. Proper treatment of the return value or
       
    91 error handling has to be done through the addCallback and addErrback
       
    92 methods.
       
    93 
       
    94 .. sourcecode: javascript
       
    95 
       
    96     function asyncHello(name) {
       
    97         var deferred = asyncRemoteExec('say_hello', name);
       
    98         deferred.addCallback(function (response) {
       
    99             alert(response);
       
   100         });
       
   101         deferred.addErrback(function (error) {
       
   102             alert('something fishy happened');
       
   103         });
       
   104      }
       
   105 
       
   106      function syncHello(name) {
       
   107          alert( remoteExec('say_hello', name) );
       
   108      }
       
   109 
       
   110 Anatomy of a reloadComponent call
       
   111 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   112 
       
   113 `reloadComponent` allows to dynamically replace some DOM node with new
       
   114 elements. It has the following signature:
       
   115 
       
   116 * `compid` (mandatory) is the name of the component to be reloaded
       
   117 
       
   118 * `rql` (optional) will be used to generate a result set given as
       
   119   argument to the selected component
       
   120 
       
   121 * `registry` (optional) defaults to 'components' but can be any other
       
   122   valid registry name
       
   123 
       
   124 * `nodeid` (optional) defaults to compid + 'Component' but can be any
       
   125   explicitly specified DOM node id
       
   126 
       
   127 * `extraargs` (optional) should be a dictionary of values that will be
       
   128   given to the cell_call method of the component
       
   129 
       
   130 A simple reloadComponent example
       
   131 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   132 
       
   133 The server side implementation of `reloadComponent` is the
       
   134 js_component method of the JSonController.
       
   135 
       
   136 The following function implements a two-steps method to delete a
       
   137 standard bookmark and refresh the UI, while keeping the UI responsive.
       
   138 
       
   139 .. sourcecode:: javascript
       
   140 
       
   141     function removeBookmark(beid) {
       
   142         d = asyncRemoteExec('delete_bookmark', beid);
       
   143         d.addCallback(function(boxcontent) {
       
   144 	    reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
       
   145             document.location.hash = '#header';
       
   146             updateMessage(_("bookmark has been removed"));
       
   147          });
       
   148     }
       
   149 
       
   150 `reloadComponent` is called with the id of the bookmark box as
       
   151 argument, no rql expression (because the bookmarks display is actually
       
   152 independant of any dataset context), a reference to the 'boxes'
       
   153 registry (which hosts all left, right and contextual boxes) and
       
   154 finally an explicit 'bookmarks_box' nodeid argument that stipulates
       
   155 the target DOM node.
       
   156 
       
   157 Anatomy of a loadxhtml call
       
   158 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   159 
       
   160 `jQuery.fn.loadxhtml` is an important extension to jQuery which allows
       
   161 proper loading and in-place DOM update of xhtml views. The existing
       
   162 `jQuery.load`_ function does not handle xhtml, hence the addition. The
       
   163 API of loadxhtml is roughly similar to that of `jQuery.load`_.
       
   164 
       
   165 .. _`jQuery.load`: http://api.jquery.com/load/
       
   166 
       
   167 
       
   168 * `url` (mandatory) should be a complete url (typically referencing
       
   169   the JSonController, but this is not strictly mandatory)
       
   170 
       
   171 * `data` (optional) is a dictionary of values given to the
       
   172   controller specified through an `url` argument; some keys may have a
       
   173   special meaning depending on the choosen controller (such as `fname`
       
   174   for the JSonController); the `callback` key, if present, must refer
       
   175   to a function to be called at the end of loadxhtml (more on this
       
   176   below)
       
   177 
       
   178 * `reqtype` (optional) specifies the request method to be used (get or
       
   179   post); if the argument is 'post', then the post method is used,
       
   180   otherwise the get method is used
       
   181 
       
   182 * `mode` (optional) is one of `replace` (the default) which means the
       
   183   loaded node will replace the current node content, `swap` to replace
       
   184   the current node with the loaded node, and `append` which will
       
   185   append the loaded node to the current node content
       
   186 
       
   187 About the `callback` option:
       
   188 
       
   189 * it is called with two parameters: the current node, and a list
       
   190   containing the loaded (and post-processed node)
       
   191 
       
   192 * whenever is returns another function, this function is called in
       
   193   turn with the same parameters as above
       
   194 
       
   195 This mechanism allows callback chaining.
       
   196 
       
   197 
       
   198 A simple example with loadxhtml
       
   199 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   200 
       
   201 Here we are concerned with the retrieval of a specific view to be
       
   202 injected in the live DOM. The view will be of course selected
       
   203 server-side using an entity eid provided by the client side.
       
   204 
       
   205 .. sourcecode:: python
       
   206 
       
   207     from cubicweb import typed_eid
       
   208     from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
       
   209 
       
   210     @monkeypatch(JSonController)
       
   211     @xhtmlize
       
   212     def js_frob_status(self, eid, frobname):
       
   213         entity = self._cw.entity_from_eid(typed_eid(eid))
       
   214         return entity.view('frob', name=frobname)
       
   215 
       
   216 .. sourcecode:: javascript
       
   217 
       
   218     function update_some_div(divid, eid, frobname) {
       
   219         var params = {fname:'frob_status', eid: eid, frobname:frobname};
       
   220         jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
       
   221      }
       
   222 
       
   223 In this example, the url argument is the base json url of a cube
       
   224 instance (it should contain something like
       
   225 `http://myinstance/json?`). The actual JSonController method name is
       
   226 encoded in the `params` dictionary using the `fname` key.
       
   227 
       
   228 A more real-life example from CubicWeb
       
   229 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   230 
       
   231 A frequent use case of Web 2 applications is the delayed (or
       
   232 on-demand) loading of pieces of the DOM. This is typically achieved
       
   233 using some preparation of the initial DOM nodes, jQuery event handling
       
   234 and proper use of loadxhtml.
       
   235 
       
   236 We present here a skeletal version of the mecanism used in CubicWeb
       
   237 and available in web/views/tabs.py, in the `LazyViewMixin` class.
       
   238 
       
   239 .. sourcecode:: python
       
   240 
       
   241     def lazyview(self, vid, rql=None):
       
   242         """ a lazy version of wview """
       
   243         w = self.w
       
   244         self._cw.add_js('cubicweb.lazy.js')
       
   245         urlparams = {'vid' : vid, 'fname' : 'view'}
       
   246         if rql is not None:
       
   247             urlparams['rql'] = rql
       
   248         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
       
   249             vid, xml_escape(self._cw.build_url('json', **urlparams))))
       
   250         w(u'</div>')
       
   251         self._cw.add_onload(u"""
       
   252             jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
       
   253                    load_now('#lazy-%(vid)s');});"""
       
   254             % {'event': 'load_%s' % vid, 'vid': vid})
       
   255 
       
   256 This creates a `div` with a specific event associated to it.
       
   257 
       
   258 The full version deals with:
       
   259 
       
   260 * optional parameters such as an entity eid, an rset
       
   261 
       
   262 * the ability to further reload the fragment
       
   263 
       
   264 * the ability to display a spinning wheel while the fragment is still
       
   265   not loaded
       
   266 
       
   267 * handling of browsers that do not support ajax (search engines,
       
   268   text-based browsers such as lynx, etc.)
       
   269 
       
   270 The javascript side is quite simple, due to loadxhtml awesomeness.
       
   271 
       
   272 .. sourcecode:: javascript
       
   273 
       
   274     function load_now(eltsel) {
       
   275         var lazydiv = jQuery(eltsel);
       
   276         lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
       
   277     }
       
   278 
       
   279 This is all significantly different of the previous `simple example`
       
   280 (albeit this example actually comes from real-life code).
       
   281 
       
   282 Notice how the `cubicweb:loadurl` is used to convey the url
       
   283 information. The base of this url is similar to the global javascript
       
   284 JSON_BASE_URL. According to the pattern described earlier,
       
   285 the `fname` parameter refers to the standard `js_view` method of the
       
   286 JSonController. This method renders an arbitrary view provided a view
       
   287 id (or `vid`) is provided, and most likely an rql expression yielding
       
   288 a result set against which a proper view instance will be selected.
       
   289 
       
   290 The `cubicweb:loadurl` is one of the 29 attributes extensions to XHTML
       
   291 in a specific cubicweb namespace. It is a means to pass information
       
   292 without breaking HTML nor XHTML compliance and without resorting to
       
   293 ungodly hacks.
       
   294 
       
   295 Given all this, it is easy to add a small nevertheless useful feature
       
   296 to force the loading of a lazy view (for instance, a very
       
   297 computation-intensive web page could be scinded into one fast-loading
       
   298 part and a delayed part).
       
   299 
       
   300 On the server side, a simple call to a javascript function is
       
   301 sufficient.
       
   302 
       
   303 .. sourcecode:: python
       
   304 
       
   305     def forceview(self, vid):
       
   306         """trigger an event that will force immediate loading of the view
       
   307         on dom readyness
       
   308         """
       
   309         self._cw.add_onload("trigger_load('%s');" % vid)
       
   310 
       
   311 The browser-side definition follows.
       
   312 
       
   313 .. sourcecode:: javascript
       
   314 
       
   315     function trigger_load(divid) {
       
   316         jQuery('#lazy-' + divd).trigger('load_' + divid);
       
   317     }
       
   318 
       
   319 
       
   320 
       
   321 
       
   322 XXX reloadComponent
       
   323 XXX userCallback / user_callback
       
   324 
       
   325 Javascript library: overview
       
   326 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   327 
       
   328 * jquery.* : jquery and jquery UI library
       
   329 
       
   330 * cubicweb.ajax.js : concentrates all ajax related facilities (it
       
   331   extends jQuery with the loahxhtml function, provides a handfull of
       
   332   high-level ajaxy operations like asyncRemoteExec, reloadComponent,
       
   333   replacePageChunk, getDomFromResponse)
       
   334 
       
   335 * cubicweb.python.js : adds a number of practical extension to stdanrd
       
   336   javascript objects (on Date, Array, String, some list and dictionary
       
   337   operations), and a pythonesque way to build classes. Defines a
       
   338   CubicWeb namespace.
       
   339 
       
   340 * cubicweb.htmlhelpers.js : a small bag of convenience functions used
       
   341   in various other cubicweb javascript resources (baseuri, progress
       
   342   cursor handling, popup login box, html2dom function, etc.)
       
   343 
       
   344 * cubicweb.widgets.js : provides a widget namespace and constructors
       
   345   and helpers for various widgets (mainly facets and timeline)
       
   346 
       
   347 * cubicweb.edition.js : used by edition forms
       
   348 
       
   349 * cubicweb.preferences.js : used by the preference form
       
   350 
       
   351 * cubicweb.facets.js : used by the facets mechanism
       
   352 
       
   353 There is also javascript support for massmailing, gmap (google maps),
       
   354 fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over
       
   355 AppEngine), flot (charts drawing), tabs and bookmarks.