|
1 /* |
|
2 * :organization: Logilab |
|
3 * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
4 * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
5 */ |
|
6 |
|
7 CubicWeb.require('python.js'); |
|
8 CubicWeb.require('htmlhelpers.js'); |
|
9 |
|
10 var JSON_BASE_URL = baseuri() + 'json?'; |
|
11 |
|
12 // cubicweb loadxhtml plugin to make jquery handle xhtml response |
|
13 jQuery.fn.loadxhtml = function(url, data, reqtype, mode) { |
|
14 var ajax = null; |
|
15 if (reqtype == 'post') { |
|
16 ajax = jQuery.post; |
|
17 } else { |
|
18 ajax = jQuery.get; |
|
19 } |
|
20 if (this.size() > 1) { |
|
21 log('loadxhtml was called with more than one element'); |
|
22 } |
|
23 mode = mode || 'replace'; |
|
24 var callback = null; |
|
25 if (data && data.callback) { |
|
26 callback = data.callback; |
|
27 delete data.callback; |
|
28 } |
|
29 var node = this.get(0); // only consider the first element |
|
30 ajax(url, data, function(response) { |
|
31 var domnode = getDomFromResponse(response); |
|
32 if (mode == 'swap') { |
|
33 var origId = node.id; |
|
34 node = swapDOM(node, domnode); |
|
35 if (!node.id) { |
|
36 node.id = origId; |
|
37 } |
|
38 } else if (mode == 'replace') { |
|
39 jQuery(node).empty().append(domnode); |
|
40 } else if (mode == 'append') { |
|
41 jQuery(node).append(domnode); |
|
42 } |
|
43 // find sortable tables if there are some |
|
44 if (typeof(Sortable) != 'undefined') { |
|
45 Sortable.sortTables(node); |
|
46 } |
|
47 // find textareas and wrap them if there are some |
|
48 if (typeof(FCKeditor) != 'undefined') { |
|
49 buildWysiwygEditors(node); |
|
50 } |
|
51 |
|
52 if (typeof initFacetBoxEvents != 'undefined') { |
|
53 initFacetBoxEvents(node); |
|
54 } |
|
55 |
|
56 if (typeof buildWidgets != 'undefined') { |
|
57 buildWidgets(node); |
|
58 } |
|
59 |
|
60 while (jQuery.isFunction(callback)) { |
|
61 callback = callback.apply(this, [domnode]); |
|
62 } |
|
63 }); |
|
64 } |
|
65 |
|
66 |
|
67 |
|
68 /* finds each dynamic fragment in the page and executes the |
|
69 * the associated RQL to build them (Async call) |
|
70 */ |
|
71 function loadDynamicFragments() { |
|
72 var fragments = getElementsByTagAndClassName('div', 'dynamicFragment'); |
|
73 if (fragments.length == 0) { |
|
74 return; |
|
75 } |
|
76 if (typeof LOADING_MSG == 'undefined') { |
|
77 LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen |
|
78 } |
|
79 for(var i=0; i<fragments.length; i++) { |
|
80 var fragment = fragments[i]; |
|
81 fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>'; |
|
82 var rql = getNodeAttribute(fragment, 'cubicweb:rql'); |
|
83 var vid = getNodeAttribute(fragment, 'cubicweb:vid'); |
|
84 var extraparams = {}; |
|
85 var actrql = getNodeAttribute(fragment, 'cubicweb:actualrql'); |
|
86 if (actrql) { extraparams['actualrql'] = actrql; } |
|
87 var fbvid = getNodeAttribute(fragment, 'cubicweb:fallbackvid'); |
|
88 if (fbvid) { extraparams['fallbackvid'] = fbvid; } |
|
89 |
|
90 replacePageChunk(fragment.id, rql, vid, extraparams); |
|
91 } |
|
92 } |
|
93 |
|
94 jQuery(document).ready(loadDynamicFragments); |
|
95 |
|
96 //============= base AJAX functions to make remote calls =====================// |
|
97 |
|
98 |
|
99 /* |
|
100 * This function will call **synchronously** a remote method on the cubicweb server |
|
101 * @param fname: the function name to call (as exposed by the JSONController) |
|
102 * @param args: the list of arguments to pass the function |
|
103 */ |
|
104 function remote_exec(fname) { |
|
105 setProgressCursor(); |
|
106 var props = {'mode' : "remote", 'fname' : fname, 'pageid' : pageid, |
|
107 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; |
|
108 var result = jQuery.ajax({url: JSON_BASE_URL, data: props, async: false}).responseText; |
|
109 result = evalJSON(result); |
|
110 resetCursor(); |
|
111 return result; |
|
112 } |
|
113 |
|
114 function remoteCallFailed(err, req) { |
|
115 if (req.status == 500) { |
|
116 updateMessage(err); |
|
117 } else { |
|
118 updateMessage(_("an error occured while processing your request")); |
|
119 } |
|
120 } |
|
121 |
|
122 /* |
|
123 * This function is the equivalent of MochiKit's loadJSONDoc but |
|
124 * uses POST instead of GET |
|
125 */ |
|
126 function loadJSONDocUsingPOST(url, queryargs, mode) { |
|
127 mode = mode || 'remote'; |
|
128 setProgressCursor(); |
|
129 var dataType = (mode == 'remote') ? "json":null; |
|
130 var deferred = loadJSON(url, queryargs, 'POST', dataType); |
|
131 deferred = deferred.addErrback(remoteCallFailed); |
|
132 // if (mode == 'remote') { |
|
133 // deferred = deferred.addCallbacks(evalJSONRequest); |
|
134 // } |
|
135 deferred = deferred.addCallback(resetCursor); |
|
136 return deferred; |
|
137 } |
|
138 |
|
139 |
|
140 function _buildRemoteArgs(fname) { |
|
141 return {'mode' : "remote", 'fname' : fname, 'pageid' : pageid, |
|
142 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; |
|
143 } |
|
144 |
|
145 /* |
|
146 * This function will call **asynchronously** a remote method on the cubicweb server |
|
147 * This function is a low level one. You should use `async_remote_exec` or |
|
148 * `async_rawremote_exec` instead. |
|
149 * |
|
150 * @param fname: the function name to call (as exposed by the JSONController) |
|
151 * @param funcargs: the function's arguments |
|
152 * @param mode: rawremote or remote |
|
153 */ |
|
154 function _async_exec(fname, funcargs, mode) { |
|
155 setProgressCursor(); |
|
156 var props = {'mode' : mode, 'fname' : fname, 'pageid' : pageid}; |
|
157 var args = map(urlEncode, map(jQuery.toJSON, funcargs)); |
|
158 args.unshift(''); // this is to be able to use join() directly |
|
159 var queryargs = as_url(props) + args.join('&arg='); |
|
160 return loadJSONDocUsingPOST(JSON_BASE_URL, queryargs, mode); |
|
161 } |
|
162 |
|
163 /* |
|
164 * This function will call **asynchronously** a remote method on the cubicweb server |
|
165 * @param fname: the function name to call (as exposed by the JSONController) |
|
166 * additional arguments will be directly passed to the specified function |
|
167 * Expected response type is Json. |
|
168 */ |
|
169 function async_remote_exec(fname /* ... */) { |
|
170 return _async_exec(fname, sliceList(arguments, 1), 'remote'); |
|
171 } |
|
172 |
|
173 /* |
|
174 * This version of _async_exec doesn't expect a json response. |
|
175 * It looks at http headers to guess the response type. |
|
176 */ |
|
177 function async_rawremote_exec(fname /* ... */) { |
|
178 return _async_exec(fname, sliceList(arguments, 1), 'rawremote'); |
|
179 } |
|
180 |
|
181 /* |
|
182 * This function will call **asynchronously** a remote method on the cubicweb server |
|
183 * @param fname: the function name to call (as exposed by the JSONController) |
|
184 * @param varargs: the list of arguments to pass to the function |
|
185 * This is an alternative form of `async_remote_exec` provided for convenience |
|
186 */ |
|
187 function async_remote_exec_varargs(fname, varargs) { |
|
188 return _async_exec(fname, varargs, 'remote'); |
|
189 } |
|
190 |
|
191 /* emulation of gettext's _ shortcut |
|
192 */ |
|
193 function _(message) { |
|
194 return remote_exec('i18n', [message])[0]; |
|
195 } |
|
196 |
|
197 function rqlexec(rql) { |
|
198 return async_remote_exec('rql', rql); |
|
199 } |
|
200 |
|
201 function userCallback(cbname) { |
|
202 async_remote_exec('user_callback', cbname); |
|
203 } |
|
204 |
|
205 function unloadPageData() { |
|
206 // NOTE: do not make async calls on unload if you want to avoid |
|
207 // strange bugs |
|
208 remote_exec('unload_page_data'); |
|
209 } |
|
210 |
|
211 function openHash() { |
|
212 if (document.location.hash) { |
|
213 var nid = document.location.hash.replace('#', ''); |
|
214 var node = jQuery('#' + nid); |
|
215 if (node) { removeElementClass(node, "hidden"); } |
|
216 }; |
|
217 } |
|
218 jQuery(document).ready(openHash); |
|
219 |
|
220 function reloadComponent(compid, rql, registry, nodeid, extraargs) { |
|
221 registry = registry || 'components'; |
|
222 rql = rql || ''; |
|
223 nodeid = nodeid || (compid + 'Component'); |
|
224 extraargs = extraargs || {}; |
|
225 log('extraargs =', extraargs); |
|
226 var node = getNode(nodeid); |
|
227 var d = async_rawremote_exec('component', compid, rql, registry, extraargs); |
|
228 d.addCallback(function(result, req) { |
|
229 var domnode = getDomFromResponse(result); |
|
230 if (node) { |
|
231 // make sure the component is visible |
|
232 removeElementClass(node, "hidden"); |
|
233 swapDOM(node, domnode); |
|
234 } |
|
235 }); |
|
236 d.addCallback(resetCursor); |
|
237 d.addErrback(function(xxx) { |
|
238 updateMessage(_("an error occured")); |
|
239 log(xxx); |
|
240 }); |
|
241 return d; |
|
242 } |
|
243 |
|
244 /* XXX: HTML architecture of cubicweb boxes is a bit strange */ |
|
245 function reloadBox(boxid, rql) { |
|
246 reloadComponent(boxid, rql, 'boxes', boxid); |
|
247 } |
|
248 |
|
249 function userCallbackThenUpdateUI(cbname, compid, rql, msg, registry, nodeid) { |
|
250 var d = async_remote_exec('user_callback', cbname); |
|
251 d.addCallback(function() { |
|
252 reloadComponent(compid, rql, registry, nodeid); |
|
253 if (msg) { updateMessage(msg); } |
|
254 }); |
|
255 d.addCallback(resetCursor); |
|
256 d.addErrback(function(xxx) { |
|
257 updateMessage(_("an error occured")); |
|
258 log(xxx); |
|
259 return resetCursor(); |
|
260 }); |
|
261 } |
|
262 |
|
263 function userCallbackThenReloadPage(cbname, msg) { |
|
264 var d = async_remote_exec('user_callback', cbname); |
|
265 d.addCallback(function() { |
|
266 window.location.reload(); |
|
267 if (msg) { updateMessage(msg); } |
|
268 }); |
|
269 d.addCallback(resetCursor); |
|
270 d.addErrback(function(xxx) { |
|
271 updateMessage(_("an error occured")); |
|
272 log(xxx); |
|
273 return resetCursor(); |
|
274 }); |
|
275 } |
|
276 |
|
277 /* |
|
278 * unregisters the python function registered on the server's side |
|
279 * while the page was generated. |
|
280 */ |
|
281 function unregisterUserCallback(cbname) { |
|
282 d = async_remote_exec('unregister_user_callback', cbname); |
|
283 d.addCallback(function() {resetCursor();}); |
|
284 d.addErrback(function(xxx) { |
|
285 updateMessage(_("an error occured")); |
|
286 log(xxx); |
|
287 return resetCursor(); |
|
288 }); |
|
289 } |
|
290 |
|
291 |
|
292 /* executes an async query to the server and replaces a node's |
|
293 * content with the query result |
|
294 * |
|
295 * @param nodeId the placeholder node's id |
|
296 * @param rql the RQL query |
|
297 * @param vid the vid to apply to the RQL selection (default if not specified) |
|
298 * @param extraparmas table of additional query parameters |
|
299 */ |
|
300 function replacePageChunk(nodeId, rql, vid, extraparams, /* ... */ swap, callback) { |
|
301 var params = null; |
|
302 if (callback) { |
|
303 params = {callback: callback}; |
|
304 } |
|
305 |
|
306 var node = jQuery('#' + nodeId)[0]; |
|
307 var props = {}; |
|
308 if (node) { |
|
309 props['rql'] = rql; |
|
310 props['pageid'] = pageid; |
|
311 if (vid) { props['vid'] = vid; } |
|
312 if (extraparams) { jQuery.extend(props, extraparams); } |
|
313 // FIXME we need to do as_url(props) manually instead of |
|
314 // passing `props` directly to loadxml because replacePageChunk |
|
315 // is sometimes called (abusively) with some extra parameters in `vid` |
|
316 var mode = swap?'swap':'replace'; |
|
317 var url = JSON_BASE_URL + as_url(props); |
|
318 jQuery(node).loadxhtml(url, params, 'get', mode); |
|
319 } else { |
|
320 log('Node', nodeId, 'not found'); |
|
321 } |
|
322 } |
|
323 |
|
324 /* XXX: this function should go in edition.js but as for now, htmlReplace |
|
325 * references it. |
|
326 * |
|
327 * replace all textareas with fckeditors. |
|
328 */ |
|
329 function buildWysiwygEditors(parent) { |
|
330 jQuery('textarea').each(function () { |
|
331 if (this.getAttribute('cubicweb:type', 'wysiwyg')) { |
|
332 if (typeof FCKeditor != "undefined") { |
|
333 var fck = new FCKeditor(this.id); |
|
334 fck.Config['CustomConfigurationsPath'] = fckconfigpath; |
|
335 fck.Config['DefaultLanguage'] = fcklang; |
|
336 fck.BasePath = "fckeditor/"; |
|
337 fck.ReplaceTextarea(); |
|
338 } else { |
|
339 log('fckeditor could not be found.'); |
|
340 } |
|
341 } |
|
342 }); |
|
343 } |
|
344 |
|
345 jQuery(document).ready(buildWysiwygEditors); |
|
346 |
|
347 |
|
348 /* convenience function that returns a DOM node based on req's result. */ |
|
349 function getDomFromResponse(response) { |
|
350 if (typeof(response) == 'string') { |
|
351 return html2dom(response); |
|
352 } |
|
353 var doc = response.documentElement; |
|
354 var children = doc.childNodes; |
|
355 if (!children.length) { |
|
356 // no child (error cases) => return the whole document |
|
357 return doc.cloneNode(true); |
|
358 } |
|
359 if (children.length == 1) { |
|
360 // only one child => return it |
|
361 return children[0].cloneNode(true); |
|
362 } |
|
363 // several children => wrap them in a single node and return the wrap |
|
364 return DIV(null, map(methodcaller('cloneNode', true), children)); |
|
365 } |
|
366 |
|
367 function postJSON(url, data, callback) { |
|
368 return jQuery.post(url, data, callback, 'json'); |
|
369 } |
|
370 |
|
371 function getJSON(url, data, callback){ |
|
372 return jQuery.get(url, data, callback, 'json'); |
|
373 } |
|
374 |
|
375 CubicWeb.provide('ajax.js'); |