doc/book/en/devweb/js.rst
changeset 8128 0a927fe4541b
parent 5742 74c19dac29cf
child 8987 d9195dce3a5b
equal deleted inserted replaced
8125:7070250bf50d 8128:0a927fe4541b
    70 
    70 
    71 
    71 
    72 A simple example with asyncRemoteExec
    72 A simple example with asyncRemoteExec
    73 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    73 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    74 
    74 
    75 In the python side, we have to extend the ``BaseController``
    75 On the python side, we have to define an
    76 class. The ``@jsonize`` decorator ensures that the return value of the
    76 :class:`cubicweb.web.views.ajaxcontroller.AjaxFunction` object. The
    77 method is encoded as JSON data. By construction, the JSonController
    77 simplest way to do that is to use the
    78 inputs everything in JSON format.
    78 :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator (for more
       
    79 details on this, refer to :ref:`ajax`).
    79 
    80 
    80 .. sourcecode: python
    81 .. sourcecode: python
    81 
    82 
    82     from cubicweb.web.views.basecontrollers import JSonController, jsonize
    83     from cubicweb.web.views.ajaxcontroller import ajaxfunc
    83 
    84 
    84     @monkeypatch(JSonController)
    85     # serialize output to json to get it back easily on the javascript side
    85     @jsonize
    86     @ajaxfunc(output_type='json')
    86     def js_say_hello(self, name):
    87     def js_say_hello(self, name):
    87         return u'hello %s' % name
    88         return u'hello %s' % name
    88 
    89 
    89 In the javascript side, we do the asynchronous call. Notice how it
    90 On the javascript side, we do the asynchronous call. Notice how it
    90 creates a `deferred` object. Proper treatment of the return value or
    91 creates a `deferred` object. Proper treatment of the return value or
    91 error handling has to be done through the addCallback and addErrback
    92 error handling has to be done through the addCallback and addErrback
    92 methods.
    93 methods.
    93 
    94 
    94 .. sourcecode: javascript
    95 .. sourcecode: javascript
   129 
   130 
   130 A simple reloadComponent example
   131 A simple reloadComponent example
   131 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   132 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   132 
   133 
   133 The server side implementation of `reloadComponent` is the
   134 The server side implementation of `reloadComponent` is the
   134 js_component method of the JSonController.
   135 :func:`cubicweb.web.views.ajaxcontroller.component` *AjaxFunction* appobject.
   135 
   136 
   136 The following function implements a two-steps method to delete a
   137 The following function implements a two-steps method to delete a
   137 standard bookmark and refresh the UI, while keeping the UI responsive.
   138 standard bookmark and refresh the UI, while keeping the UI responsive.
   138 
   139 
   139 .. sourcecode:: javascript
   140 .. sourcecode:: javascript
   164 
   165 
   165 .. _`jQuery.load`: http://api.jquery.com/load/
   166 .. _`jQuery.load`: http://api.jquery.com/load/
   166 
   167 
   167 
   168 
   168 * `url` (mandatory) should be a complete url (typically referencing
   169 * `url` (mandatory) should be a complete url (typically referencing
   169   the JSonController, but this is not strictly mandatory)
   170   the :class:`cubicweb.web.views.ajaxcontroller.AjaxController`,
       
   171   but this is not strictly mandatory)
   170 
   172 
   171 * `data` (optional) is a dictionary of values given to the
   173 * `data` (optional) is a dictionary of values given to the
   172   controller specified through an `url` argument; some keys may have a
   174   controller specified through an `url` argument; some keys may have a
   173   special meaning depending on the choosen controller (such as `fname`
   175   special meaning depending on the choosen controller (such as `fname`
   174   for the JSonController); the `callback` key, if present, must refer
   176   for the JSonController); the `callback` key, if present, must refer
   202 injected in the live DOM. The view will be of course selected
   204 injected in the live DOM. The view will be of course selected
   203 server-side using an entity eid provided by the client side.
   205 server-side using an entity eid provided by the client side.
   204 
   206 
   205 .. sourcecode:: python
   207 .. sourcecode:: python
   206 
   208 
   207     from cubicweb import typed_eid
   209     from cubicweb.web.views.ajaxcontroller import ajaxfunc
   208     from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
   210 
   209 
   211     @ajaxfunc(output_type='xhtml')
   210     @monkeypatch(JSonController)
       
   211     @xhtmlize
       
   212     def js_frob_status(self, eid, frobname):
   212     def js_frob_status(self, eid, frobname):
   213         entity = self._cw.entity_from_eid(typed_eid(eid))
   213         entity = self._cw.entity_from_eid(eid)
   214         return entity.view('frob', name=frobname)
   214         return entity.view('frob', name=frobname)
   215 
   215 
   216 .. sourcecode:: javascript
   216 .. sourcecode:: javascript
   217 
   217 
   218     function update_some_div(divid, eid, frobname) {
   218     function updateSomeDiv(divid, eid, frobname) {
   219         var params = {fname:'frob_status', eid: eid, frobname:frobname};
   219         var params = {fname:'frob_status', eid: eid, frobname:frobname};
   220         jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
   220         jQuery('#'+divid).loadxhtml(JSON_BASE_URL, params, 'post');
   221      }
   221      }
   222 
   222 
   223 In this example, the url argument is the base json url of a cube
   223 In this example, the url argument is the base json url of a cube
   224 instance (it should contain something like
   224 instance (it should contain something like
   225 `http://myinstance/json?`). The actual JSonController method name is
   225 `http://myinstance/ajax?`). The actual AjaxController method name is
   226 encoded in the `params` dictionary using the `fname` key.
   226 encoded in the `params` dictionary using the `fname` key.
   227 
   227 
   228 A more real-life example
   228 A more real-life example
   229 ~~~~~~~~~~~~~~~~~~~~~~~~
   229 ~~~~~~~~~~~~~~~~~~~~~~~~
   230 
   230 
   248         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
   248         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
   249             vid, xml_escape(self._cw.build_url('json', **urlparams))))
   249             vid, xml_escape(self._cw.build_url('json', **urlparams))))
   250         w(u'</div>')
   250         w(u'</div>')
   251         self._cw.add_onload(u"""
   251         self._cw.add_onload(u"""
   252             jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
   252             jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
   253                    load_now('#lazy-%(vid)s');});"""
   253                    loadNow('#lazy-%(vid)s');});"""
   254             % {'event': 'load_%s' % vid, 'vid': vid})
   254             % {'event': 'load_%s' % vid, 'vid': vid})
   255 
   255 
   256 This creates a `div` with a specific event associated to it.
   256 This creates a `div` with a specific event associated to it.
   257 
   257 
   258 The full version deals with:
   258 The full version deals with:
   269 
   269 
   270 The javascript side is quite simple, due to loadxhtml awesomeness.
   270 The javascript side is quite simple, due to loadxhtml awesomeness.
   271 
   271 
   272 .. sourcecode:: javascript
   272 .. sourcecode:: javascript
   273 
   273 
   274     function load_now(eltsel) {
   274     function loadNow(eltsel) {
   275         var lazydiv = jQuery(eltsel);
   275         var lazydiv = jQuery(eltsel);
   276         lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
   276         lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
   277     }
   277     }
   278 
   278 
   279 This is all significantly different of the previous `simple example`
   279 This is all significantly different of the previous `simple example`
   304 
   304 
   305     def forceview(self, vid):
   305     def forceview(self, vid):
   306         """trigger an event that will force immediate loading of the view
   306         """trigger an event that will force immediate loading of the view
   307         on dom readyness
   307         on dom readyness
   308         """
   308         """
   309         self._cw.add_onload("trigger_load('%s');" % vid)
   309         self._cw.add_onload("triggerLoad('%s');" % vid)
   310 
   310 
   311 The browser-side definition follows.
   311 The browser-side definition follows.
   312 
   312 
   313 .. sourcecode:: javascript
   313 .. sourcecode:: javascript
   314 
   314 
   315     function trigger_load(divid) {
   315     function triggerLoad(divid) {
   316         jQuery('#lazy-' + divd).trigger('load_' + divid);
   316         jQuery('#lazy-' + divd).trigger('load_' + divid);
   317     }
   317     }
   318 
   318 
   319 
   319 
   320 .. XXX userCallback / user_callback
   320 python/ajax dynamic callbacks
       
   321 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   322 
       
   323 CubicWeb provides a way to dynamically register a function and make it
       
   324 callable from the javascript side. The typical use case for this is a
       
   325 situation where you have everything at hand to implement an action
       
   326 (whether it be performing a RQL query or executing a few python
       
   327 statements) that you'd like to defer to a user click in the web
       
   328 interface.  In other words, generate an HTML ``<a href=...`` link that
       
   329 would execute your few lines of code.
       
   330 
       
   331 The trick is to create a python function and store this function in
       
   332 the user's session data. You will then be able to access it later.
       
   333 While this might sound hard to implement, it's actually quite easy
       
   334 thanks to the ``_cw.user_callback()``. This method takes a function,
       
   335 registers it and returns a javascript instruction suitable for
       
   336 ``href`` or ``onclick`` usage. The call is then performed
       
   337 asynchronously.
       
   338 
       
   339 Here's a simplified example taken from the vcreview_ cube that will
       
   340 generate a link to change an entity state directly without the
       
   341 standard intermediate *comment / validate* step:
       
   342 
       
   343 .. sourcecode:: python
       
   344 
       
   345     def entity_call(self, entity):
       
   346         # [...]
       
   347         def change_state(req, eid):
       
   348             entity = req.entity_from_eid(eid)
       
   349             entity.cw_adapt_to('IWorkflowable').fire_transition('done')
       
   350         url = self._cw.user_callback(change_state, (entity.eid,))
       
   351         self.w(tags.input(type='button', onclick=url, value=self._cw._('mark as done')))
       
   352 
       
   353 
       
   354 The ``change_state`` callback function is registered with
       
   355 ``self._cw.user_callback()`` which returns the ``url`` value directly
       
   356 used for the ``onclick`` attribute of the button. On the javascript
       
   357 side, the ``userCallback()`` function is used but you most probably
       
   358 won't have to bother with it.
       
   359 
       
   360 Of course, when dealing with session data, the question of session
       
   361 cleaning pops up immediately. If you use ``user_callback()``, the
       
   362 registered function will be deleted automatically at some point
       
   363 as any other session data. If you want your function to be deleted once
       
   364 the web page is unloaded or when the user has clicked once on your link, then
       
   365 ``_cw.register_onetime_callback()`` is what you need. It behaves as
       
   366 ``_cw.user_callback()`` but stores the function in page data instead
       
   367 of global session data.
       
   368 
       
   369 
       
   370 .. Warning::
       
   371 
       
   372   Be careful when registering functions with closures, keep in mind that
       
   373   enclosed data will be kept in memory until the session gets cleared. Also,
       
   374   if you keep entities or any object referecing the current ``req`` object, you
       
   375   might have problems reusing them later because the underlying session
       
   376   might have been closed at the time the callback gets executed.
       
   377 
       
   378 
       
   379 .. _vcreview: http://www.cubicweb.org/project/cubicweb-vcreview
   321 
   380 
   322 Javascript library: overview
   381 Javascript library: overview
   323 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   382 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   324 
   383 
   325 * jquery.* : jquery and jquery UI library
   384 * jquery.* : jquery and jquery UI library
   354 API
   413 API
   355 ~~~
   414 ~~~
   356 
   415 
   357 .. toctree::
   416 .. toctree::
   358     :maxdepth: 1
   417     :maxdepth: 1
   359     
   418 
   360     js_api/index
   419     js_api/index
   361 
   420 
   362 
   421 
   363 Testing javascript
   422 Testing javascript
   364 ~~~~~~~~~~~~~~~~~~~~~~
   423 ~~~~~~~~~~~~~~~~~~
   365 
   424 
   366 You with the ``cubicweb.qunit.QUnitTestCase`` can include standard Qunit tests
   425 You with the ``cubicweb.qunit.QUnitTestCase`` can include standard Qunit tests
   367 inside the python unittest run . You simply have to define a new class that
   426 inside the python unittest run . You simply have to define a new class that
   368 inherit from ``QUnitTestCase`` and register your javascript test file in the
   427 inherit from ``QUnitTestCase`` and register your javascript test file in the
   369 ``all_js_tests`` lclass attribut. This  ``all_js_tests`` is a sequence a
   428 ``all_js_tests`` lclass attribut. This  ``all_js_tests`` is a sequence a