doc/book/en/devweb/js.rst
changeset 8128 0a927fe4541b
parent 5742 74c19dac29cf
child 8987 d9195dce3a5b
--- a/doc/book/en/devweb/js.rst	Fri Dec 09 12:14:11 2011 +0100
+++ b/doc/book/en/devweb/js.rst	Fri Dec 16 12:30:12 2011 +0100
@@ -72,21 +72,22 @@
 A simple example with asyncRemoteExec
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-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.
+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.basecontrollers import JSonController, jsonize
+    from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
-    @monkeypatch(JSonController)
-    @jsonize
+    # 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
 
-In the javascript side, we do the asynchronous call. Notice how it
+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.
@@ -131,7 +132,7 @@
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The server side implementation of `reloadComponent` is the
-js_component method of the JSonController.
+: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.
@@ -166,7 +167,8 @@
 
 
 * `url` (mandatory) should be a complete url (typically referencing
-  the JSonController, but this is not strictly mandatory)
+  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
@@ -204,25 +206,23 @@
 
 .. sourcecode:: python
 
-    from cubicweb import typed_eid
-    from cubicweb.web.views.basecontrollers import JSonController, xhtmlize
+    from cubicweb.web.views.ajaxcontroller import ajaxfunc
 
-    @monkeypatch(JSonController)
-    @xhtmlize
+    @ajaxfunc(output_type='xhtml')
     def js_frob_status(self, eid, frobname):
-        entity = self._cw.entity_from_eid(typed_eid(eid))
+        entity = self._cw.entity_from_eid(eid)
         return entity.view('frob', name=frobname)
 
 .. sourcecode:: javascript
 
-    function update_some_div(divid, eid, frobname) {
+    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/json?`). The actual JSonController method name is
+`http://myinstance/ajax?`). The actual AjaxController method name is
 encoded in the `params` dictionary using the `fname` key.
 
 A more real-life example
@@ -250,7 +250,7 @@
         w(u'</div>')
         self._cw.add_onload(u"""
             jQuery('#lazy-%(vid)s').bind('%(event)s', function() {
-                   load_now('#lazy-%(vid)s');});"""
+                   loadNow('#lazy-%(vid)s');});"""
             % {'event': 'load_%s' % vid, 'vid': vid})
 
 This creates a `div` with a specific event associated to it.
@@ -271,7 +271,7 @@
 
 .. sourcecode:: javascript
 
-    function load_now(eltsel) {
+    function loadNow(eltsel) {
         var lazydiv = jQuery(eltsel);
         lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'));
     }
@@ -306,18 +306,77 @@
         """trigger an event that will force immediate loading of the view
         on dom readyness
         """
-        self._cw.add_onload("trigger_load('%s');" % vid)
+        self._cw.add_onload("triggerLoad('%s');" % vid)
 
 The browser-side definition follows.
 
 .. sourcecode:: javascript
 
-    function trigger_load(divid) {
+    function triggerLoad(divid) {
         jQuery('#lazy-' + divd).trigger('load_' + divid);
     }
 
 
-.. XXX userCallback / user_callback
+python/ajax dynamic callbacks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+CubicWeb provides a way to dynamically register a function and make it
+callable from the javascript side. The typical use case for this is a
+situation where you have everything at hand to implement an action
+(whether it be performing a RQL query or executing a few python
+statements) that you'd like to defer to a user click in the web
+interface.  In other words, generate an HTML ``<a href=...`` link that
+would execute your few lines of code.
+
+The trick is to create a python function and store this function in
+the user's session data. You will then be able to access it later.
+While this might sound hard to implement, it's actually quite easy
+thanks to the ``_cw.user_callback()``. This method takes a function,
+registers it and returns a javascript instruction suitable for
+``href`` or ``onclick`` usage. The call is then performed
+asynchronously.
+
+Here's a simplified example taken from the vcreview_ cube that will
+generate a link to change an entity state directly without the
+standard intermediate *comment / validate* step:
+
+.. sourcecode:: python
+
+    def entity_call(self, entity):
+        # [...]
+        def change_state(req, eid):
+            entity = req.entity_from_eid(eid)
+            entity.cw_adapt_to('IWorkflowable').fire_transition('done')
+        url = self._cw.user_callback(change_state, (entity.eid,))
+        self.w(tags.input(type='button', onclick=url, value=self._cw._('mark as done')))
+
+
+The ``change_state`` callback function is registered with
+``self._cw.user_callback()`` which returns the ``url`` value directly
+used for the ``onclick`` attribute of the button. On the javascript
+side, the ``userCallback()`` function is used but you most probably
+won't have to bother with it.
+
+Of course, when dealing with session data, the question of session
+cleaning pops up immediately. If you use ``user_callback()``, the
+registered function will be deleted automatically at some point
+as any other session data. If you want your function to be deleted once
+the web page is unloaded or when the user has clicked once on your link, then
+``_cw.register_onetime_callback()`` is what you need. It behaves as
+``_cw.user_callback()`` but stores the function in page data instead
+of global session data.
+
+
+.. Warning::
+
+  Be careful when registering functions with closures, keep in mind that
+  enclosed data will be kept in memory until the session gets cleared. Also,
+  if you keep entities or any object referecing the current ``req`` object, you
+  might have problems reusing them later because the underlying session
+  might have been closed at the time the callback gets executed.
+
+
+.. _vcreview: http://www.cubicweb.org/project/cubicweb-vcreview
 
 Javascript library: overview
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -356,12 +415,12 @@
 
 .. toctree::
     :maxdepth: 1
-    
+
     js_api/index
 
 
 Testing javascript
-~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~
 
 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