|
1 /* copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 * contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 * |
|
4 * This file is part of CubicWeb. |
|
5 * |
|
6 * CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 * terms of the GNU Lesser General Public License as published by the Free |
|
8 * Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 * any later version. |
|
10 * |
|
11 * CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 * details. |
|
15 * |
|
16 * You should have received a copy of the GNU Lesser General Public License along |
|
17 * with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 */ |
|
19 |
|
20 /** |
|
21 * .. function:: Deferred |
|
22 * |
|
23 * dummy ultra minimalist implementation of deferred for jQuery |
|
24 */ |
|
25 |
|
26 cw.ajax = new Namespace('cw.ajax'); |
|
27 |
|
28 function Deferred() { |
|
29 this.__init__(this); |
|
30 } |
|
31 |
|
32 jQuery.extend(Deferred.prototype, { |
|
33 __init__: function() { |
|
34 this._onSuccess = []; |
|
35 this._onFailure = []; |
|
36 this._req = null; |
|
37 this._result = null; |
|
38 this._error = null; |
|
39 }, |
|
40 |
|
41 addCallback: function(callback) { |
|
42 if (this._req && (this._req.readyState == 4) && this._result) { |
|
43 var args = [this._result, this._req]; |
|
44 jQuery.merge(args, cw.utils.sliceList(arguments, 1)); |
|
45 callback.apply(null, args); |
|
46 } |
|
47 else { |
|
48 this._onSuccess.push([callback, cw.utils.sliceList(arguments, 1)]); |
|
49 } |
|
50 return this; |
|
51 }, |
|
52 |
|
53 addErrback: function(callback) { |
|
54 if (this._req && this._req.readyState == 4 && this._error) { |
|
55 callback.apply(null, [this._error, this._req]); |
|
56 } |
|
57 else { |
|
58 this._onFailure.push([callback, cw.utils.sliceList(arguments, 1)]); |
|
59 } |
|
60 return this; |
|
61 }, |
|
62 |
|
63 success: function(result) { |
|
64 this._result = result; |
|
65 for (var i = 0; i < this._onSuccess.length; i++) { |
|
66 var callback = this._onSuccess[i][0]; |
|
67 var args = [result, this._req]; |
|
68 jQuery.merge(args, this._onSuccess[i][1]); |
|
69 callback.apply(null, args); |
|
70 } |
|
71 }, |
|
72 |
|
73 error: function(xhr, status, error) { |
|
74 this._error = error; |
|
75 for (var i = 0; i < this._onFailure.length; i++) { |
|
76 var callback = this._onFailure[i][0]; |
|
77 var args = [error, this._req]; |
|
78 jQuery.merge(args, this._onFailure[i][1]); |
|
79 if (callback !== undefined) |
|
80 callback.apply(null, args); |
|
81 } |
|
82 } |
|
83 |
|
84 }); |
|
85 |
|
86 var AJAX_PREFIX_URL = 'ajax'; |
|
87 var JSON_BASE_URL = BASE_URL + 'json?'; |
|
88 var AJAX_BASE_URL = BASE_URL + AJAX_PREFIX_URL + '?'; |
|
89 |
|
90 |
|
91 jQuery.extend(cw.ajax, { |
|
92 /* variant of jquery evalScript with cache: true in ajax call */ |
|
93 _evalscript: function ( i, elem ) { |
|
94 var src = elem.getAttribute('src'); |
|
95 if (src) { |
|
96 jQuery.ajax({ |
|
97 url: src, |
|
98 async: false, |
|
99 cache: true, |
|
100 dataType: "script" |
|
101 }); |
|
102 } else { |
|
103 jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); |
|
104 } |
|
105 if ( elem.parentNode ) { |
|
106 elem.parentNode.removeChild( elem ); |
|
107 } |
|
108 }, |
|
109 |
|
110 evalscripts: function ( scripts ) { |
|
111 if ( scripts.length ) { |
|
112 jQuery.each(scripts, cw.ajax._evalscript); |
|
113 } |
|
114 }, |
|
115 |
|
116 /** |
|
117 * returns true if `url` is a mod_concat-like url |
|
118 * (e.g. http://..../data??resource1.js,resource2.js) |
|
119 */ |
|
120 _modconcatLikeUrl: function(url) { |
|
121 var modconcat_rgx = new RegExp('(' + BASE_URL + 'data/([a-z0-9]+/)?)\\?\\?(.+)'); |
|
122 return modconcat_rgx.exec(url); |
|
123 }, |
|
124 |
|
125 /** |
|
126 * decomposes a mod_concat-like url into its corresponding list of |
|
127 * resources' urls |
|
128 * >>> _listResources('http://foo.com/data/??a.js,b.js,c.js') |
|
129 * ['http://foo.com/data/a.js', 'http://foo.com/data/b.js', 'http://foo.com/data/c.js'] |
|
130 */ |
|
131 _listResources: function(src) { |
|
132 var resources = []; |
|
133 var groups = cw.ajax._modconcatLikeUrl(src); |
|
134 if (groups == null) { |
|
135 resources.push(src); |
|
136 } else { |
|
137 var dataurl = groups[1]; |
|
138 $.each(cw.utils.lastOf(groups).split(','), |
|
139 function() { |
|
140 resources.push(dataurl + this); |
|
141 } |
|
142 ); |
|
143 } |
|
144 return resources; |
|
145 }, |
|
146 |
|
147 _buildMissingResourcesUrl: function(url, loadedResources) { |
|
148 var resources = cw.ajax._listResources(url); |
|
149 var missingResources = $.grep(resources, function(resource) { |
|
150 return $.inArray(resource, loadedResources) == -1; |
|
151 }); |
|
152 cw.utils.extend(loadedResources, missingResources); |
|
153 var missingResourceUrl = null; |
|
154 if (missingResources.length == 1) { |
|
155 // only one resource missing: build a node with a single resource url |
|
156 // (maybe the browser has it in cache already) |
|
157 missingResourceUrl = missingResources[0]; |
|
158 } else if (missingResources.length > 1) { |
|
159 // several resources missing: build a node with a concatenated |
|
160 // resources url |
|
161 var dataurl = cw.ajax._modconcatLikeUrl(url)[1]; |
|
162 var missing_path = $.map(missingResources, function(resource) { |
|
163 return resource.substring(dataurl.length); |
|
164 }); |
|
165 missingResourceUrl = dataurl + '??' + missing_path.join(','); |
|
166 } |
|
167 return missingResourceUrl; |
|
168 }, |
|
169 |
|
170 _loadAjaxStylesheets: function($responseHead, $head) { |
|
171 $responseHead.find('link[href]').each(function(i) { |
|
172 var $srcnode = $(this); |
|
173 var url = $srcnode.attr('href'); |
|
174 if (url) { |
|
175 var missingStylesheetsUrl = cw.ajax._buildMissingResourcesUrl(url, cw.loaded_links); |
|
176 // compute concat-like url for missing resources and append <link> |
|
177 // element to $head |
|
178 if (missingStylesheetsUrl) { |
|
179 // IE has problems with dynamic CSS insertions. One symptom (among others) |
|
180 // is a "1 item remaining" message in the status bar. (cf. #2356261) |
|
181 // document.createStyleSheet needs to be used for this, although it seems |
|
182 // that IE can't create more than 31 additional stylesheets with |
|
183 // document.createStyleSheet. |
|
184 if ($.browser.msie) { |
|
185 document.createStyleSheet(missingStylesheetsUrl); |
|
186 } else { |
|
187 $srcnode.attr('href', missingStylesheetsUrl); |
|
188 $srcnode.appendTo($head); |
|
189 } |
|
190 } |
|
191 } |
|
192 }); |
|
193 $responseHead.find('link[href]').remove(); |
|
194 }, |
|
195 |
|
196 _loadAjaxScripts: function($responseHead, $head) { |
|
197 $responseHead.find('cubicweb\\:script').each(function(i) { |
|
198 var $srcnode = $(this); |
|
199 var url = $srcnode.attr('src'); |
|
200 if (url) { |
|
201 var missingScriptsUrl = cw.ajax._buildMissingResourcesUrl(url, cw.loaded_scripts); |
|
202 if (missingScriptsUrl) { |
|
203 $srcnode.attr('src', missingScriptsUrl); |
|
204 /* special handling of <script> tags: script nodes appended by jquery |
|
205 * use uncached ajax calls and do not appear in the DOM |
|
206 * (See comments in response to Syt on // http://api.jquery.com/append/), |
|
207 * which cause undesired duplicated load in our case. We now handle |
|
208 * a list of already loaded resources, since bare DOM api gives bugs with the |
|
209 * server-response event, and we lose control on when the |
|
210 * script is loaded (jQuery loads it immediately). */ |
|
211 cw.ajax.evalscripts($srcnode); |
|
212 } |
|
213 } else { |
|
214 // <script> contains inlined javascript code, node content |
|
215 // must be evaluated |
|
216 jQuery.globalEval($srcnode.text()); |
|
217 } |
|
218 }); |
|
219 $responseHead.find('cubicweb\\:script').remove(); |
|
220 } |
|
221 }); |
|
222 |
|
223 //============= utility function handling remote calls responses. ==============// |
|
224 /** |
|
225 * .. function:: function loadAjaxHtmlHead(response) |
|
226 * |
|
227 * inspect dom response (as returned by getDomFromResponse), search for |
|
228 * a <div class="ajaxHtmlHead"> node and put its content into the real |
|
229 * document's head. |
|
230 * This enables dynamic css and js loading and is used by replacePageChunk |
|
231 */ |
|
232 function loadAjaxHtmlHead(response) { |
|
233 var $head = jQuery('head'); |
|
234 var $responseHead = jQuery(response).find('div.ajaxHtmlHead'); |
|
235 // no ajaxHtmlHead found, no processing required |
|
236 if (!$responseHead.length) { |
|
237 return response; |
|
238 } |
|
239 cw.ajax._loadAjaxStylesheets($responseHead, $head); |
|
240 cw.ajax._loadAjaxScripts($responseHead, $head); |
|
241 // add any remaining children (e.g. meta) |
|
242 $responseHead.children().appendTo($head); |
|
243 // remove original container, which is now empty |
|
244 $responseHead.remove(); |
|
245 // if there was only one actual node in the reponse besides |
|
246 // the ajaxHtmlHead, then remove the wrapper created by |
|
247 // getDomFromResponse() and return this single element |
|
248 // For instance : |
|
249 // 1/ CW returned the following content : |
|
250 // <div>the-actual-content</div><div class="ajaxHtmlHead">...</div> |
|
251 // 2/ getDomFromReponse() wrapped this into a single DIV to hold everything |
|
252 // in one, unique, dom element |
|
253 // 3/ now that we've removed the ajaxHtmlHead div, the only |
|
254 // node left in the wrapper if the 'real' node built by the view, |
|
255 // we can safely return this node. Otherwise, the view itself |
|
256 // returned several 'root' nodes and we need to keep the wrapper |
|
257 // created by getDomFromResponse() |
|
258 if (response.childNodes.length == 1 && response.getAttribute('cubicweb:type') == 'cwResponseWrapper') { |
|
259 return response.firstChild; |
|
260 } |
|
261 return response; |
|
262 } |
|
263 |
|
264 function _postAjaxLoad(node) { |
|
265 // find textareas and wrap them if there are some |
|
266 if (typeof(FCKeditor) != 'undefined') { |
|
267 buildWysiwygEditors(); |
|
268 } |
|
269 if (typeof initFacetBoxEvents != 'undefined') { |
|
270 initFacetBoxEvents(node); |
|
271 } |
|
272 if (typeof buildWidgets != 'undefined') { |
|
273 buildWidgets(node); |
|
274 } |
|
275 if (typeof roundedCorners != 'undefined') { |
|
276 roundedCorners(node); |
|
277 } |
|
278 _loadDynamicFragments(node); |
|
279 jQuery(cw).trigger('server-response', [true, node]); |
|
280 jQuery(node).trigger('server-response', [true, node]); |
|
281 } |
|
282 |
|
283 function remoteCallFailed(err, req) { |
|
284 cw.log(err); |
|
285 if (req.status == 500) { |
|
286 updateMessage(err); |
|
287 } else { |
|
288 updateMessage(_("an error occurred while processing your request")); |
|
289 } |
|
290 } |
|
291 |
|
292 //============= base AJAX functions to make remote calls =====================// |
|
293 /** |
|
294 * .. function:: ajaxFuncArgs(fname, form, *args) |
|
295 * |
|
296 * extend `form` parameters to call the js_`fname` function of the json |
|
297 * controller with `args` arguments. |
|
298 */ |
|
299 function ajaxFuncArgs(fname, form /* ... */) { |
|
300 form = form || {}; |
|
301 $.extend(form, { |
|
302 'fname': fname, |
|
303 'pageid': pageid, |
|
304 'arg': $.map(cw.utils.sliceList(arguments, 2), JSON.stringify) |
|
305 }); |
|
306 return form; |
|
307 } |
|
308 |
|
309 /** |
|
310 * .. function:: loadxhtml(url, form, reqtype='get', mode='replace', cursor=false) |
|
311 * |
|
312 * build url given by absolute or relative `url` and `form` parameters |
|
313 * (dictionary), fetch it using `reqtype` method, then evaluate the |
|
314 * returned XHTML and insert it according to `mode` in the |
|
315 * document. Possible modes are : |
|
316 * |
|
317 * - 'replace' to replace the node's content with the generated HTML |
|
318 * - 'swap' to replace the node itself with the generated HTML |
|
319 * - 'append' to append the generated HTML to the node's content |
|
320 * |
|
321 * If `cursor`, turn mouse cursor into 'progress' cursor until the remote call |
|
322 * is back. |
|
323 */ |
|
324 jQuery.fn.loadxhtml = function(url, form, reqtype, mode, cursor) { |
|
325 if (this.size() > 1) { |
|
326 cw.log('loadxhtml called with more than one element'); |
|
327 } else if (this.size() < 1) { |
|
328 cw.log('loadxhtml called without an element'); |
|
329 } |
|
330 var node = this.get(0); // only consider the first element |
|
331 if (cursor) { |
|
332 setProgressCursor(); |
|
333 } |
|
334 var d = loadRemote(url, form, reqtype); |
|
335 d.addCallback(function(response) { |
|
336 var domnode = getDomFromResponse(response); |
|
337 domnode = loadAjaxHtmlHead(domnode); |
|
338 mode = mode || 'replace'; |
|
339 // make sure the component is visible |
|
340 $(node).removeClass("hidden"); |
|
341 if (mode == 'swap') { |
|
342 var origId = node.id; |
|
343 node = cw.swapDOM(node, domnode); |
|
344 if (!node.id) { |
|
345 node.id = origId; |
|
346 } |
|
347 } else if (mode == 'replace') { |
|
348 jQuery(node).empty().append(domnode); |
|
349 } else if (mode == 'append') { |
|
350 jQuery(node).append(domnode); |
|
351 } |
|
352 _postAjaxLoad(node); |
|
353 }); |
|
354 d.addErrback(remoteCallFailed); |
|
355 if (cursor) { |
|
356 d.addCallback(resetCursor); |
|
357 d.addErrback(resetCursor); |
|
358 } |
|
359 return d; |
|
360 } |
|
361 |
|
362 /** |
|
363 * .. function:: loadRemote(url, form, reqtype='POST', sync=false) |
|
364 * |
|
365 * Asynchronously (unless `sync` argument is set to true) load a URL or path |
|
366 * and return a deferred whose callbacks args are decoded according to the |
|
367 * Content-Type response header. `form` should be additional form params |
|
368 * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST'). |
|
369 */ |
|
370 function loadRemote(url, form, reqtype, sync) { |
|
371 if (!url.toLowerCase().startswith(BASE_URL.toLowerCase())) { |
|
372 url = BASE_URL + url; |
|
373 } |
|
374 if (!sync) { |
|
375 var deferred = new Deferred(); |
|
376 jQuery.ajax({ |
|
377 url: url, |
|
378 type: (reqtype || 'POST').toUpperCase(), |
|
379 data: form, |
|
380 traditional: true, |
|
381 async: true, |
|
382 |
|
383 beforeSend: function(xhr) { |
|
384 deferred._req = xhr; |
|
385 }, |
|
386 |
|
387 success: function(data, status) { |
|
388 deferred.success(data); |
|
389 }, |
|
390 |
|
391 error: function(xhr, status, error) { |
|
392 try { |
|
393 if (xhr.status == 500) { |
|
394 var reason_dict = cw.evalJSON(xhr.responseText); |
|
395 deferred.error(xhr, status, reason_dict['reason']); |
|
396 return; |
|
397 } |
|
398 } catch(exc) { |
|
399 cw.log('error with server side error report:' + exc); |
|
400 } |
|
401 deferred.error(xhr, status, null); |
|
402 } |
|
403 }); |
|
404 return deferred; |
|
405 } else { |
|
406 var result; |
|
407 // jQuery.ajax returns the XHR object, even for synchronous requests, |
|
408 // but in that case, the success callback will be called before |
|
409 // jQuery.ajax returns. The first argument of the callback will be |
|
410 // the server result, interpreted by jQuery according to the reponse's |
|
411 // content-type (i.e. json or xml) |
|
412 jQuery.ajax({ |
|
413 url: url, |
|
414 type: (reqtype || 'GET').toUpperCase(), |
|
415 data: form, |
|
416 traditional: true, |
|
417 async: false, |
|
418 success: function(res) { |
|
419 result = res; |
|
420 } |
|
421 }); |
|
422 return result; |
|
423 } |
|
424 } |
|
425 |
|
426 //============= higher level AJAX functions using remote calls ===============// |
|
427 |
|
428 var _i18ncache = {}; |
|
429 |
|
430 /** |
|
431 * .. function:: _(message) |
|
432 * |
|
433 * emulation of gettext's _ shortcut |
|
434 */ |
|
435 function _(message) { |
|
436 var form; |
|
437 if (!(message in _i18ncache)) { |
|
438 form = ajaxFuncArgs('i18n', null, [message]); |
|
439 _i18ncache[message] = loadRemote(AJAX_BASE_URL, form, 'GET', true)[0]; |
|
440 } |
|
441 return _i18ncache[message]; |
|
442 } |
|
443 |
|
444 /** |
|
445 * .. function:: _loadDynamicFragments(node) |
|
446 * |
|
447 * finds each dynamic fragment in the page and executes the |
|
448 * the associated RQL to build them (Async call) |
|
449 */ |
|
450 function _loadDynamicFragments(node) { |
|
451 if (node) { |
|
452 var fragments = jQuery(node).find('div.dynamicFragment'); |
|
453 } else { |
|
454 var fragments = jQuery('div.dynamicFragment'); |
|
455 } |
|
456 if (fragments.length == 0) { |
|
457 return; |
|
458 } |
|
459 if (typeof LOADING_MSG == 'undefined') { |
|
460 LOADING_MSG = 'loading'; // this is only a safety belt, it should not happen |
|
461 } |
|
462 for (var i = 0; i < fragments.length; i++) { |
|
463 var fragment = fragments[i]; |
|
464 fragment.innerHTML = '<h3>' + LOADING_MSG + ' ... <img src="data/loading.gif" /></h3>'; |
|
465 var $fragment = jQuery(fragment); |
|
466 // if cubicweb:loadurl is set, just pick the url et send it to loadxhtml |
|
467 var url = $fragment.attr('cubicweb:loadurl'); |
|
468 if (url) { |
|
469 $fragment.loadxhtml(url); |
|
470 continue; |
|
471 } |
|
472 // else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc. |
|
473 var rql = $fragment.attr('cubicweb:rql'); |
|
474 var items = $fragment.attr('cubicweb:vid').split('&'); |
|
475 var vid = items[0]; |
|
476 var extraparams = {}; |
|
477 // case where vid='myvid¶m1=val1¶m2=val2': this is a deprecated abuse-case |
|
478 if (items.length > 1) { |
|
479 cw.log("[3.5] you're using extraargs in cubicweb:vid " + |
|
480 "attribute, this is deprecated, consider using " + |
|
481 "loadurl instead"); |
|
482 for (var j = 1; j < items.length; j++) { |
|
483 var keyvalue = items[j].split('='); |
|
484 extraparams[keyvalue[0]] = keyvalue[1]; |
|
485 } |
|
486 } |
|
487 var actrql = $fragment.attr('cubicweb:actualrql'); |
|
488 if (actrql) { |
|
489 extraparams['actualrql'] = actrql; |
|
490 } |
|
491 var fbvid = $fragment.attr('cubicweb:fallbackvid'); |
|
492 if (fbvid) { |
|
493 extraparams['fallbackvid'] = fbvid; |
|
494 } |
|
495 extraparams['rql'] = rql; |
|
496 extraparams['vid'] = vid; |
|
497 $fragment.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('view', extraparams)); |
|
498 } |
|
499 } |
|
500 function unloadPageData() { |
|
501 // NOTE: do not make async calls on unload if you want to avoid |
|
502 // strange bugs |
|
503 loadRemote(AJAX_BASE_URL, ajaxFuncArgs('unload_page_data'), 'POST', true); |
|
504 } |
|
505 |
|
506 function removeBookmark(beid) { |
|
507 var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('delete_bookmark', null, beid)); |
|
508 d.addCallback(function(boxcontent) { |
|
509 $('#bookmarks_box').loadxhtml(AJAX_BASE_URL, |
|
510 ajaxFuncArgs('render', null, 'ctxcomponents', |
|
511 'bookmarks_box'), |
|
512 null, 'swap'); |
|
513 document.location.hash = '#header'; |
|
514 updateMessage(_("bookmark has been removed")); |
|
515 }); |
|
516 } |
|
517 |
|
518 |
|
519 //============= XXX move those functions? ====================================// |
|
520 function openHash() { |
|
521 if (document.location.hash) { |
|
522 var nid = document.location.hash.replace('#', ''); |
|
523 var node = jQuery('#' + nid); |
|
524 if (node) { |
|
525 $(node).removeClass("hidden"); |
|
526 } |
|
527 }; |
|
528 } |
|
529 jQuery(document).ready(openHash); |
|
530 |
|
531 /** |
|
532 * .. function:: buildWysiwygEditors(parent) |
|
533 * |
|
534 *XXX: this function should go in edition.js but as for now, htmlReplace |
|
535 * references it. |
|
536 * |
|
537 * replace all textareas with fckeditors. |
|
538 */ |
|
539 function buildWysiwygEditors(parent) { |
|
540 jQuery('textarea').each(function() { |
|
541 if (this.getAttribute('cubicweb:type') == 'wysiwyg') { |
|
542 // mark editor as instanciated, we may be called a number of times |
|
543 // (see _postAjaxLoad) |
|
544 this.setAttribute('cubicweb:type', 'fckeditor'); |
|
545 if (typeof FCKeditor != "undefined") { |
|
546 var fck = new FCKeditor(this.id); |
|
547 fck.Config['CustomConfigurationsPath'] = fckconfigpath; |
|
548 fck.Config['DefaultLanguage'] = fcklang; |
|
549 fck.BasePath = BASE_URL + "fckeditor/"; |
|
550 fck.ReplaceTextarea(); |
|
551 } else { |
|
552 cw.log('fckeditor could not be found.'); |
|
553 } |
|
554 } |
|
555 }); |
|
556 } |
|
557 jQuery(document).ready(buildWysiwygEditors); |
|
558 |
|
559 /** |
|
560 * .. function:: stripEmptyTextNodes(nodelist) |
|
561 * |
|
562 * takes a list of DOM nodes and removes all empty text nodes |
|
563 */ |
|
564 function stripEmptyTextNodes(nodelist) { |
|
565 /* this DROPS empty text nodes */ |
|
566 var stripped = []; |
|
567 for (var i = 0; i < nodelist.length; i++) { |
|
568 var node = nodelist[i]; |
|
569 if (isTextNode(node)) { |
|
570 /* all browsers but FF -> innerText, FF -> textContent */ |
|
571 var text = node.innerText || node.textContent; |
|
572 if (text && ! text.strip()) { |
|
573 continue; |
|
574 } |
|
575 } else { |
|
576 stripped.push(node); |
|
577 } |
|
578 } |
|
579 return stripped; |
|
580 } |
|
581 |
|
582 /** |
|
583 * .. function:: getDomFromResponse(response) |
|
584 * |
|
585 * convenience function that returns a DOM node based on req's result. |
|
586 * XXX clarify the need to clone |
|
587 * */ |
|
588 function getDomFromResponse(response) { |
|
589 if (typeof(response) == 'string') { |
|
590 var doc = html2dom(response); |
|
591 } else { |
|
592 var doc = response.documentElement; |
|
593 } |
|
594 var children = doc.childNodes; |
|
595 if (!children.length) { |
|
596 // no child (error cases) => return the whole document |
|
597 return jQuery(doc).clone().context; |
|
598 } |
|
599 children = stripEmptyTextNodes(children); |
|
600 if (children.length == 1) { |
|
601 // only one child => return it |
|
602 return jQuery(children[0]).clone().context; |
|
603 } |
|
604 // several children => wrap them in a single node and return the wrap |
|
605 return DIV({'cubicweb:type': "cwResponseWrapper"}, |
|
606 $.map(children, function(node) { |
|
607 return jQuery(node).clone().context;}) |
|
608 ); |
|
609 } |
|
610 |
|
611 /* High-level functions *******************************************************/ |
|
612 |
|
613 /** |
|
614 * .. function:: reloadCtxComponentsSection(context, actualEid, creationEid=None) |
|
615 * |
|
616 * reload all components in the section for a given `context`. |
|
617 * |
|
618 * This is necessary for cases where the parent entity (on which the section |
|
619 * apply) has been created during post, hence the section has to be reloaded to |
|
620 * consider its new eid, hence the two additional arguments `actualEid` and |
|
621 * `creationEid`: `actualEid` is the eid of newly created top level entity and |
|
622 * `creationEid` the fake eid that was given as form creation marker (e.g. A). |
|
623 * |
|
624 * You can still call this function with only the actual eid if you're not in |
|
625 * such creation case. |
|
626 */ |
|
627 function reloadCtxComponentsSection(context, actualEid, creationEid) { |
|
628 // in this case, actualEid is the eid of newly created top level entity and |
|
629 // creationEid the fake eid given as form creation marker (e.g. A) |
|
630 if (!creationEid) { creationEid = actualEid ; } |
|
631 var $compsholder = $('#' + context + creationEid); |
|
632 // reload the whole components section |
|
633 $compsholder.children().each(function (index) { |
|
634 // XXX this.id[:-len(eid)] |
|
635 var compid = this.id.replace("_", ".").rstrip(creationEid); |
|
636 var params = ajaxFuncArgs('render', null, 'ctxcomponents', |
|
637 compid, actualEid); |
|
638 $(this).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true); |
|
639 }); |
|
640 $compsholder.attr('id', context + actualEid); |
|
641 } |
|
642 |
|
643 |
|
644 /** |
|
645 * .. function:: reload(domid, compid, registry, formparams, *render_args) |
|
646 * |
|
647 * `js_render` based reloading of views and components. |
|
648 */ |
|
649 function reload(domid, compid, registry, formparams /* ... */) { |
|
650 var ajaxArgs = ['render', formparams, registry, compid]; |
|
651 ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4)); |
|
652 var params = ajaxFuncArgs.apply(null, ajaxArgs); |
|
653 return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true); |
|
654 } |
|
655 |
|
656 /* ajax tabs ******************************************************************/ |
|
657 |
|
658 function setTab(tabname, cookiename) { |
|
659 // set appropriate cookie |
|
660 jQuery.cookie(cookiename, tabname, {path: '/'}); |
|
661 // trigger show + tabname event |
|
662 triggerLoad(tabname); |
|
663 } |
|
664 |
|
665 function loadNow(eltsel, holesel, reloadable) { |
|
666 var lazydiv = jQuery(eltsel); |
|
667 var hole = lazydiv.children(holesel); |
|
668 hole.show(); |
|
669 if ((hole.length == 0) && ! reloadable) { |
|
670 /* the hole is already filed */ |
|
671 return; |
|
672 } |
|
673 lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'), {'pageid': pageid}); |
|
674 } |
|
675 |
|
676 function triggerLoad(divid) { |
|
677 jQuery('#lazy-' + divid).trigger('load_' + divid); |
|
678 } |
|
679 |
|
680 /* DEPRECATED *****************************************************************/ |
|
681 |
|
682 // still used in cwo and keyword cubes at least |
|
683 reloadComponent = cw.utils.deprecatedFunction( |
|
684 '[3.9] reloadComponent() is deprecated, use loadxhtml instead', |
|
685 function(compid, rql, registry, nodeid, extraargs) { |
|
686 registry = registry || 'components'; |
|
687 rql = rql || ''; |
|
688 nodeid = nodeid || (compid + 'Component'); |
|
689 extraargs = extraargs || {}; |
|
690 var node = cw.jqNode(nodeid); |
|
691 return node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('component', null, compid, |
|
692 rql, registry, extraargs)); |
|
693 } |
|
694 ); |
|
695 |
|
696 |
|
697 function remoteExec(fname /* ... */) { |
|
698 setProgressCursor(); |
|
699 var props = { |
|
700 fname: fname, |
|
701 pageid: pageid, |
|
702 arg: $.map(cw.utils.sliceList(arguments, 1), JSON.stringify) |
|
703 }; |
|
704 var result = jQuery.ajax({ |
|
705 url: AJAX_BASE_URL, |
|
706 data: props, |
|
707 async: false, |
|
708 traditional: true |
|
709 }).responseText; |
|
710 if (result) { |
|
711 result = cw.evalJSON(result); |
|
712 } |
|
713 resetCursor(); |
|
714 return result; |
|
715 } |
|
716 |
|
717 function asyncRemoteExec(fname /* ... */) { |
|
718 setProgressCursor(); |
|
719 var props = { |
|
720 fname: fname, |
|
721 pageid: pageid, |
|
722 arg: $.map(cw.utils.sliceList(arguments, 1), JSON.stringify) |
|
723 }; |
|
724 // XXX we should inline the content of loadRemote here |
|
725 var deferred = loadRemote(AJAX_BASE_URL, props, 'POST'); |
|
726 deferred = deferred.addErrback(remoteCallFailed); |
|
727 deferred = deferred.addErrback(resetCursor); |
|
728 deferred = deferred.addCallback(resetCursor); |
|
729 return deferred; |
|
730 } |
|
731 |
|
732 jQuery(document).ready(function() { |
|
733 _loadDynamicFragments(); |
|
734 // build loaded_scripts / loaded_links lists |
|
735 cw.loaded_scripts = []; |
|
736 jQuery('head script[src]').each(function(i) { |
|
737 cw.utils.extend(cw.loaded_scripts, cw.ajax._listResources(this.getAttribute('src'))); |
|
738 }); |
|
739 cw.loaded_links = []; |
|
740 jQuery('head link[href]').each(function(i) { |
|
741 cw.utils.extend(cw.loaded_links, cw.ajax._listResources(this.getAttribute('href'))); |
|
742 }); |
|
743 }); |