doc/book/en/devweb/js.rst
changeset 10491 c67bcee93248
parent 10490 76ab3c71aff2
child 10492 68c13e0c0fc5
equal deleted inserted replaced
10490:76ab3c71aff2 10491:c67bcee93248
     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 Server-side 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 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 javascript 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 On the python side, we have to define an
       
    76 :class:`cubicweb.web.views.ajaxcontroller.AjaxFunction` object. The
       
    77 simplest way to do that is to use the
       
    78 :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator (for more
       
    79 details on this, refer to :ref:`ajax`).
       
    80 
       
    81 .. sourcecode: python
       
    82 
       
    83     from cubicweb.web.views.ajaxcontroller import ajaxfunc
       
    84 
       
    85     # serialize output to json to get it back easily on the javascript side
       
    86     @ajaxfunc(output_type='json')
       
    87     def js_say_hello(self, name):
       
    88         return u'hello %s' % name
       
    89 
       
    90 On the javascript side, we do the asynchronous call. Notice how it
       
    91 creates a `deferred` object. Proper treatment of the return value or
       
    92 error handling has to be done through the addCallback and addErrback
       
    93 methods.
       
    94 
       
    95 .. sourcecode: javascript
       
    96 
       
    97     function asyncHello(name) {
       
    98         var deferred = asyncRemoteExec('say_hello', name);
       
    99         deferred.addCallback(function (response) {
       
   100             alert(response);
       
   101         });
       
   102         deferred.addErrback(function (error) {
       
   103             alert('something fishy happened');
       
   104         });
       
   105      }
       
   106 
       
   107      function syncHello(name) {
       
   108          alert( remoteExec('say_hello', name) );
       
   109      }
       
   110 
       
   111 Anatomy of a reloadComponent call
       
   112 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   113 
       
   114 `reloadComponent` allows to dynamically replace some DOM node with new
       
   115 elements. It has the following signature:
       
   116 
       
   117 * `compid` (mandatory) is the name of the component to be reloaded
       
   118 
       
   119 * `rql` (optional) will be used to generate a result set given as
       
   120   argument to the selected component
       
   121 
       
   122 * `registry` (optional) defaults to 'components' but can be any other
       
   123   valid registry name
       
   124 
       
   125 * `nodeid` (optional) defaults to compid + 'Component' but can be any
       
   126   explicitly specified DOM node id
       
   127 
       
   128 * `extraargs` (optional) should be a dictionary of values that will be
       
   129   given to the cell_call method of the component
       
   130 
       
   131 A simple reloadComponent example
       
   132 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   133 
       
   134 The server side implementation of `reloadComponent` is the
       
   135 :func:`cubicweb.web.views.ajaxcontroller.component` *AjaxFunction* appobject.
       
   136 
       
   137 The following function implements a two-steps method to delete a
       
   138 standard bookmark and refresh the UI, while keeping the UI responsive.
       
   139 
       
   140 .. sourcecode:: javascript
       
   141 
       
   142     function removeBookmark(beid) {
       
   143         d = asyncRemoteExec('delete_bookmark', beid);
       
   144         d.addCallback(function(boxcontent) {
       
   145 	    reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
       
   146             document.location.hash = '#header';
       
   147             updateMessage(_("bookmark has been removed"));
       
   148          });
       
   149     }
       
   150 
       
   151 `reloadComponent` is called with the id of the bookmark box as
       
   152 argument, no rql expression (because the bookmarks display is actually
       
   153 independant of any dataset context), a reference to the 'boxes'
       
   154 registry (which hosts all left, right and contextual boxes) and
       
   155 finally an explicit 'bookmarks_box' nodeid argument that stipulates
       
   156 the target DOM node.
       
   157 
       
   158 Anatomy of a loadxhtml call
       
   159 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   160 
       
   161 `jQuery.fn.loadxhtml` is an important extension to jQuery which allows
       
   162 proper loading and in-place DOM update of xhtml views. The existing
       
   163 `jQuery.load`_ function does not handle xhtml, hence the addition. The
       
   164 API of loadxhtml is roughly similar to that of `jQuery.load`_.
       
   165 
       
   166 .. _`jQuery.load`: http://api.jquery.com/load/
       
   167 
       
   168 
       
   169 * `url` (mandatory) should be a complete url (typically referencing
       
   170   the :class:`cubicweb.web.views.ajaxcontroller.AjaxController`,
       
   171   but this is not strictly mandatory)
       
   172 
       
   173 * `data` (optional) is a dictionary of values given to the
       
   174   controller specified through an `url` argument; some keys may have a
       
   175   special meaning depending on the choosen controller (such as `fname`
       
   176   for the JSonController); the `callback` key, if present, must refer
       
   177   to a function to be called at the end of loadxhtml (more on this
       
   178   below)
       
   179 
       
   180 * `reqtype` (optional) specifies the request method to be used (get or
       
   181   post); if the argument is 'post', then the post method is used,
       
   182   otherwise the get method is used
       
   183 
       
   184 * `mode` (optional) is one of `replace` (the default) which means the
       
   185   loaded node will replace the current node content, `swap` to replace
       
   186   the current node with the loaded node, and `append` which will
       
   187   append the loaded node to the current node content
       
   188 
       
   189 About the `callback` option:
       
   190 
       
   191 * it is called with two parameters: the current node, and a list
       
   192   containing the loaded (and post-processed node)
       
   193 
       
   194 * whenever it returns another function, this function is called in
       
   195   turn with the same parameters as above
       
   196 
       
   197 This mechanism allows callback chaining.
       
   198 
       
   199 
       
   200 A simple example with loadxhtml
       
   201 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   202 
       
   203 Here we are concerned with the retrieval of a specific view to be
       
   204 injected in the live DOM. The view will be of course selected
       
   205 server-side using an entity eid provided by the client side.
       
   206 
       
   207 .. sourcecode:: python
       
   208 
       
   209     from cubicweb.web.views.ajaxcontroller import ajaxfunc
       
   210 
       
   211     @ajaxfunc(output_type='xhtml')
       
   212     def frob_status(self, eid, frobname):
       
   213         entity = self._cw.entity_from_eid(eid)
       
   214         return entity.view('frob', name=frobname)
       
   215 
       
   216 .. sourcecode:: javascript
       
   217 
       
   218     function updateSomeDiv(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/ajax?`). The actual AjaxController method name is
       
   226 encoded in the `params` dictionary using the `fname` key.
       
   227 
       
   228 A more real-life example
       
   229 ~~~~~~~~~~~~~~~~~~~~~~~~
       
   230 
       
   231 A frequent need of Web 2 applications is the delayed (or demand
       
   232 driven) loading of pieces of the DOM. This is typically achieved using
       
   233 some preparation of the initial DOM nodes, jQuery event handling and
       
   234 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                    loadNow('#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 loadNow(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("triggerLoad('%s');" % vid)
       
   310 
       
   311 The browser-side definition follows.
       
   312 
       
   313 .. sourcecode:: javascript
       
   314 
       
   315     function triggerLoad(divid) {
       
   316         jQuery('#lazy-' + divd).trigger('load_' + divid);
       
   317     }
       
   318 
       
   319 
       
   320 Javascript library: overview
       
   321 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   322 
       
   323 * jquery.* : jquery and jquery UI library
       
   324 
       
   325 * cubicweb.ajax.js : concentrates all ajax related facilities (it
       
   326   extends jQuery with the loahxhtml function, provides a handfull of
       
   327   high-level ajaxy operations like asyncRemoteExec, reloadComponent,
       
   328   replacePageChunk, getDomFromResponse)
       
   329 
       
   330 * cubicweb.python.js : adds a number of practical extension to stdanrd
       
   331   javascript objects (on Date, Array, String, some list and dictionary
       
   332   operations), and a pythonesque way to build classes. Defines a
       
   333   CubicWeb namespace.
       
   334 
       
   335 * cubicweb.htmlhelpers.js : a small bag of convenience functions used
       
   336   in various other cubicweb javascript resources (baseuri, progress
       
   337   cursor handling, popup login box, html2dom function, etc.)
       
   338 
       
   339 * cubicweb.widgets.js : provides a widget namespace and constructors
       
   340   and helpers for various widgets (mainly facets and timeline)
       
   341 
       
   342 * cubicweb.edition.js : used by edition forms
       
   343 
       
   344 * cubicweb.preferences.js : used by the preference form
       
   345 
       
   346 * cubicweb.facets.js : used by the facets mechanism
       
   347 
       
   348 There is also javascript support for massmailing, gmap (google maps),
       
   349 fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over
       
   350 AppEngine), flot (charts drawing), tabs and bookmarks.
       
   351 
       
   352 API
       
   353 ~~~
       
   354 
       
   355 .. toctree::
       
   356     :maxdepth: 1
       
   357 
       
   358     js_api/index
       
   359 
       
   360 
       
   361 Testing javascript
       
   362 ~~~~~~~~~~~~~~~~~~
       
   363 
       
   364 You with the ``cubicweb.qunit.QUnitTestCase`` can include standard Qunit tests
       
   365 inside the python unittest run . You simply have to define a new class that
       
   366 inherit from ``QUnitTestCase`` and register your javascript test file in the
       
   367 ``all_js_tests`` lclass attribut. This  ``all_js_tests`` is a sequence a
       
   368 3-tuple (<test_file, [<dependencies> ,] [<data_files>]):
       
   369 
       
   370 The <test_file> should contains the qunit test. <dependencies> defines the list
       
   371 of javascript file that must be imported before the test script.  Dependencies
       
   372 are included their definition order. <data_files> are additional files copied in the
       
   373 test directory. both <dependencies> and <data_files> are optionnal.
       
   374 ``jquery.js`` is preincluded in for all test.
       
   375 
       
   376 .. sourcecode:: python
       
   377 
       
   378     from cubicweb.qunit import QUnitTestCase
       
   379 
       
   380     class MyQUnitTest(QUnitTestCase):
       
   381 
       
   382         all_js_tests = (
       
   383             ("relative/path/to/my_simple_testcase.js",)
       
   384             ("relative/path/to/my_qunit_testcase.js",(
       
   385                 "rel/path/to/dependency_1.js",
       
   386                 "rel/path/to/dependency_2.js",)),
       
   387             ("relative/path/to/my_complexe_qunit_testcase.js",(
       
   388                  "rel/path/to/dependency_1.js",
       
   389                  "rel/path/to/dependency_2.js",
       
   390                ),(
       
   391                  "rel/path/file_dependency.html",
       
   392                  "path/file_dependency.json")
       
   393                 ),
       
   394             )