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