--- 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