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 CubicWeb 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 CubicWeb 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 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 In the python side, we have to extend the BaseController class. The |
|
76 @jsonize decorator ensures that the `return value` of the method is |
|
77 encoded as JSON data. By construction, the JSonController inputs |
|
78 everything in JSON format. |
|
79 |
|
80 .. sourcecode: python |
|
81 |
|
82 from cubicweb.web.views.basecontrollers import JSonController, jsonize |
|
83 |
|
84 @monkeypatch(JSonController) |
|
85 @jsonize |
|
86 def js_say_hello(self, name): |
|
87 return u'hello %s' % name |
|
88 |
|
89 In the javascript side, we do the asynchronous call. Notice how it |
|
90 creates a `deferred` object. Proper treatment of the return value or |
|
91 error handling has to be done through the addCallback and addErrback |
|
92 methods. |
|
93 |
|
94 .. sourcecode: javascript |
|
95 |
|
96 function asyncHello(name) { |
|
97 var deferred = asyncRemoteExec('say_hello', name); |
|
98 deferred.addCallback(function (response) { |
|
99 alert(response); |
|
100 }); |
|
101 deferred.addErrback(function (error) { |
|
102 alert('something fishy happened'); |
|
103 }); |
|
104 } |
|
105 |
|
106 function syncHello(name) { |
|
107 alert( remoteExec('say_hello', name) ); |
|
108 } |
|
109 |
|
110 Anatomy of a reloadComponent call |
|
111 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
112 |
|
113 `reloadComponent` allows to dynamically replace some DOM node with new |
|
114 elements. It has the following signature: |
|
115 |
|
116 * `compid` (mandatory) is the name of the component to be reloaded |
|
117 |
|
118 * `rql` (optional) will be used to generate a result set given as |
|
119 argument to the selected component |
|
120 |
|
121 * `registry` (optional) defaults to 'components' but can be any other |
|
122 valid registry name |
|
123 |
|
124 * `nodeid` (optional) defaults to compid + 'Component' but can be any |
|
125 explicitly specified DOM node id |
|
126 |
|
127 * `extraargs` (optional) should be a dictionary of values that will be |
|
128 given to the cell_call method of the component |
|
129 |
|
130 A simple reloadComponent example |
|
131 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
132 |
|
133 The server side implementation of `reloadComponent` is the |
|
134 js_component method of the JSonController. |
|
135 |
|
136 The following function implements a two-steps method to delete a |
|
137 standard bookmark and refresh the UI, while keeping the UI responsive. |
|
138 |
|
139 .. sourcecode:: javascript |
|
140 |
|
141 function removeBookmark(beid) { |
|
142 d = asyncRemoteExec('delete_bookmark', beid); |
|
143 d.addCallback(function(boxcontent) { |
|
144 reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box'); |
|
145 document.location.hash = '#header'; |
|
146 updateMessage(_("bookmark has been removed")); |
|
147 }); |
|
148 } |
|
149 |
|
150 `reloadComponent` is called with the id of the bookmark box as |
|
151 argument, no rql expression (because the bookmarks display is actually |
|
152 independant of any dataset context), a reference to the 'boxes' |
|
153 registry (which hosts all left, right and contextual boxes) and |
|
154 finally an explicit 'bookmarks_box' nodeid argument that stipulates |
|
155 the target DOM node. |
|
156 |
|
157 Anatomy of a loadxhtml call |
|
158 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
159 |
|
160 `jQuery.fn.loadxhtml` is an important extension to jQuery which allows |
|
161 proper loading and in-place DOM update of xhtml views. The existing |
|
162 `jQuery.load`_ function does not handle xhtml, hence the addition. The |
|
163 API of loadxhtml is roughly similar to that of `jQuery.load`_. |
|
164 |
|
165 .. _`jQuery.load`: http://api.jquery.com/load/ |
|
166 |
|
167 |
|
168 * `url` (mandatory) should be a complete url (typically referencing |
|
169 the JSonController, but this is not strictly mandatory) |
|
170 |
|
171 * `data` (optional) is a dictionary of values given to the |
|
172 controller specified through an `url` argument; some keys may have a |
|
173 special meaning depending on the choosen controller (such as `fname` |
|
174 for the JSonController); the `callback` key, if present, must refer |
|
175 to a function to be called at the end of loadxhtml (more on this |
|
176 below) |
|
177 |
|
178 * `reqtype` (optional) specifies the request method to be used (get or |
|
179 post); if the argument is 'post', then the post method is used, |
|
180 otherwise the get method is used |
|
181 |
|
182 * `mode` (optional) is one of `replace` (the default) which means the |
|
183 loaded node will replace the current node content, `swap` to replace |
|
184 the current node with the loaded node, and `append` which will |
|
185 append the loaded node to the current node content |
|
186 |
|
187 About the `callback` option: |
|
188 |
|
189 * it is called with two parameters: the current node, and a list |
|
190 containing the loaded (and post-processed node) |
|
191 |
|
192 * whenever is returns another function, this function is called in |
|
193 turn with the same parameters as above |
|
194 |
|
195 This mechanism allows callback chaining. |
|
196 |
|
197 |
|
198 A simple example with loadxhtml |
|
199 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
200 |
|
201 Here we are concerned with the retrieval of a specific view to be |
|
202 injected in the live DOM. The view will be of course selected |
|
203 server-side using an entity eid provided by the client side. |
|
204 |
|
205 .. sourcecode:: python |
|
206 |
|
207 from cubicweb import typed_eid |
|
208 from cubicweb.web.views.basecontrollers import JSonController, xhtmlize |
|
209 |
|
210 @monkeypatch(JSonController) |
|
211 @xhtmlize |
|
212 def js_frob_status(self, eid, frobname): |
|
213 entity = self._cw.entity_from_eid(typed_eid(eid)) |
|
214 return entity.view('frob', name=frobname) |
|
215 |
|
216 .. sourcecode:: javascript |
|
217 |
|
218 function update_some_div(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/json?`). The actual JSonController method name is |
|
226 encoded in the `params` dictionary using the `fname` key. |
|
227 |
|
228 A more real-life example from CubicWeb |
|
229 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
230 |
|
231 A frequent use case of Web 2 applications is the delayed (or |
|
232 on-demand) loading of pieces of the DOM. This is typically achieved |
|
233 using some preparation of the initial DOM nodes, jQuery event handling |
|
234 and 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 load_now('#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 load_now(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("trigger_load('%s');" % vid) |
|
310 |
|
311 The browser-side definition follows. |
|
312 |
|
313 .. sourcecode:: javascript |
|
314 |
|
315 function trigger_load(divid) { |
|
316 jQuery('#lazy-' + divd).trigger('load_' + divid); |
|
317 } |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 XXX reloadComponent |
|
323 XXX userCallback / user_callback |
|
324 |
|
325 Javascript library: overview |
|
326 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
327 |
|
328 * jquery.* : jquery and jquery UI library |
|
329 |
|
330 * cubicweb.ajax.js : concentrates all ajax related facilities (it |
|
331 extends jQuery with the loahxhtml function, provides a handfull of |
|
332 high-level ajaxy operations like asyncRemoteExec, reloadComponent, |
|
333 replacePageChunk, getDomFromResponse) |
|
334 |
|
335 * cubicweb.python.js : adds a number of practical extension to stdanrd |
|
336 javascript objects (on Date, Array, String, some list and dictionary |
|
337 operations), and a pythonesque way to build classes. Defines a |
|
338 CubicWeb namespace. |
|
339 |
|
340 * cubicweb.htmlhelpers.js : a small bag of convenience functions used |
|
341 in various other cubicweb javascript resources (baseuri, progress |
|
342 cursor handling, popup login box, html2dom function, etc.) |
|
343 |
|
344 * cubicweb.widgets.js : provides a widget namespace and constructors |
|
345 and helpers for various widgets (mainly facets and timeline) |
|
346 |
|
347 * cubicweb.edition.js : used by edition forms |
|
348 |
|
349 * cubicweb.preferences.js : used by the preference form |
|
350 |
|
351 * cubicweb.facets.js : used by the facets mechanism |
|
352 |
|
353 There is also javascript support for massmailing, gmap (google maps), |
|
354 fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over |
|
355 AppEngine), flot (charts drawing), tabs and bookmarks. |
|