/* copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * contact http://www.logilab.fr/ -- mailto:contact@logilab.fr * * This file is part of CubicWeb. * * CubicWeb is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 2.1 of the License, or (at your option) * any later version. * * CubicWeb is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License along * with CubicWeb. If not, see <http://www.gnu.org/licenses/>. *//** * .. function:: Deferred * * dummy ultra minimalist implementation of deferred for jQuery */cw.ajax=newNamespace('cw.ajax');functionDeferred(){this.__init__(this);}jQuery.extend(Deferred.prototype,{__init__:function(){this._onSuccess=[];this._onFailure=[];this._req=null;this._result=null;this._error=null;},addCallback:function(callback){if((this._req.readyState==4)&&this._result){varargs=[this._result,this._req];jQuery.merge(args,cw.utils.sliceList(arguments,1));callback.apply(null,args);}else{this._onSuccess.push([callback,cw.utils.sliceList(arguments,1)]);}returnthis;},addErrback:function(callback){if(this._req.readyState==4&&this._error){callback.apply(null,[this._error,this._req]);}else{this._onFailure.push([callback,cw.utils.sliceList(arguments,1)]);}returnthis;},success:function(result){this._result=result;try{for(vari=0;i<this._onSuccess.length;i++){varcallback=this._onSuccess[i][0];varargs=[result,this._req];jQuery.merge(args,this._onSuccess[i][1]);callback.apply(null,args);}}catch(error){this.error(this._req,null,error);}},error:function(xhr,status,error){this._error=error;for(vari=0;i<this._onFailure.length;i++){varcallback=this._onFailure[i][0];varargs=[error,this._req];jQuery.merge(args,this._onFailure[i][1]);callback.apply(null,args);}}});varAJAX_PREFIX_URL='ajax';varJSON_BASE_URL=baseuri()+'json?';varAJAX_BASE_URL=baseuri()+AJAX_PREFIX_URL+'?';jQuery.extend(cw.ajax,{/* variant of jquery evalScript with cache: true in ajax call */_evalscript:function(i,elem){varsrc=elem.getAttribute('src');if(src){jQuery.ajax({url:src,async:false,cache:true,dataType:"script"});}else{jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");}if(elem.parentNode){elem.parentNode.removeChild(elem);}},evalscripts:function(scripts){if(scripts.length){jQuery.each(scripts,cw.ajax._evalscript);}},/** * returns true if `url` is a mod_concat-like url * (e.g. http://..../data??resource1.js,resource2.js) */_modconcatLikeUrl:function(url){varbase=baseuri();if(!base.endswith('/')){base+='/';}varmodconcat_rgx=newRegExp('('+base+'data/([a-z0-9]+/)?)\\?\\?(.+)');returnmodconcat_rgx.exec(url);},/** * decomposes a mod_concat-like url into its corresponding list of * resources' urls * >>> _listResources('http://foo.com/data/??a.js,b.js,c.js') * ['http://foo.com/data/a.js', 'http://foo.com/data/b.js', 'http://foo.com/data/c.js'] */_listResources:function(src){varresources=[];vargroups=cw.ajax._modconcatLikeUrl(src);if(groups==null){resources.push(src);}else{vardataurl=groups[1];$.each(cw.utils.lastOf(groups).split(','),function(){resources.push(dataurl+this);});}returnresources;},_buildMissingResourcesUrl:function(url,loadedResources){varresources=cw.ajax._listResources(url);varmissingResources=$.grep(resources,function(resource){return$.inArray(resource,loadedResources)==-1;});cw.utils.extend(loadedResources,missingResources);varmissingResourceUrl=null;if(missingResources.length==1){// only one resource missing: build a node with a single resource url// (maybe the browser has it in cache already)missingResourceUrl=missingResources[0];}elseif(missingResources.length>1){// several resources missing: build a node with a concatenated// resources urlvardataurl=cw.ajax._modconcatLikeUrl(url)[1];varmissing_path=$.map(missingResources,function(resource){returnresource.substring(dataurl.length);});missingResourceUrl=dataurl+'??'+missing_path.join(',');}returnmissingResourceUrl;},_loadAjaxStylesheets:function($responseHead,$head){$responseHead.find('link[href]').each(function(i){var$srcnode=$(this);varurl=$srcnode.attr('href');if(url){varmissingStylesheetsUrl=cw.ajax._buildMissingResourcesUrl(url,cw.loaded_links);// compute concat-like url for missing resources and append <link>// element to $headif(missingStylesheetsUrl){// IE has problems with dynamic CSS insertions. One symptom (among others)// is a "1 item remaining" message in the status bar. (cf. #2356261)// document.createStyleSheet needs to be used for this, although it seems// that IE can't create more than 31 additional stylesheets with// document.createStyleSheet.if($.browser.msie){document.createStyleSheet(missingStylesheetsUrl);}else{$srcnode.attr('href',missingStylesheetsUrl);$srcnode.appendTo($head);}}}});$responseHead.find('link[href]').remove();},_loadAjaxScripts:function($responseHead,$head){$responseHead.find('pre.script').each(function(i){var$srcnode=$(this);varurl=$srcnode.attr('src');if(url){varmissingScriptsUrl=cw.ajax._buildMissingResourcesUrl(url,cw.loaded_scripts);if(missingScriptsUrl){$srcnode.attr('src',missingScriptsUrl);/* special handling of <script> tags: script nodes appended by jquery * use uncached ajax calls and do not appear in the DOM * (See comments in response to Syt on // http://api.jquery.com/append/), * which cause undesired duplicated load in our case. We now handle * a list of already loaded resources, since bare DOM api gives bugs with the * server-response event, and we lose control on when the * script is loaded (jQuery loads it immediately). */cw.ajax.evalscripts($srcnode);}}else{// <script> contains inlined javascript code, node content// must be evaluatedjQuery.globalEval($srcnode.text());}});$responseHead.find('pre.script').remove();}});//============= utility function handling remote calls responses. ==============///** * .. function:: function loadAjaxHtmlHead(response) * * inspect dom response (as returned by getDomFromResponse), search for * a <div class="ajaxHtmlHead"> node and put its content into the real * document's head. * This enables dynamic css and js loading and is used by replacePageChunk */functionloadAjaxHtmlHead(response){var$head=jQuery('head');var$responseHead=jQuery(response).find('div.ajaxHtmlHead');// no ajaxHtmlHead found, no processing requiredif(!$responseHead.length){returnresponse;}cw.ajax._loadAjaxStylesheets($responseHead,$head);cw.ajax._loadAjaxScripts($responseHead,$head);// add any remaining children (e.g. meta)$responseHead.children().appendTo($head);// remove original container, which is now empty$responseHead.remove();// if there was only one actual node in the reponse besides// the ajaxHtmlHead, then remove the wrapper created by// getDomFromResponse() and return this single element// For instance :// 1/ CW returned the following content :// <div>the-actual-content</div><div class="ajaxHtmlHead">...</div>// 2/ getDomFromReponse() wrapped this into a single DIV to hold everything// in one, unique, dom element// 3/ now that we've removed the ajaxHtmlHead div, the only// node left in the wrapper if the 'real' node built by the view,// we can safely return this node. Otherwise, the view itself// returned several 'root' nodes and we need to keep the wrapper// created by getDomFromResponse()if(response.childNodes.length==1&&response.getAttribute('cubicweb:type')=='cwResponseWrapper'){returnresponse.firstChild;}returnresponse;}function_postAjaxLoad(node){// find textareas and wrap them if there are someif(typeof(FCKeditor)!='undefined'){buildWysiwygEditors();}if(typeofinitFacetBoxEvents!='undefined'){initFacetBoxEvents(node);}if(typeofbuildWidgets!='undefined'){buildWidgets(node);}if(typeofroundedCorners!='undefined'){roundedCorners(node);}if(typeofsetFormsTarget!='undefined'){setFormsTarget(node);}_loadDynamicFragments(node);// XXX [3.7] jQuery.one is now used instead jQuery.bind,// jquery.treeview.js can be unpatched accordingly.jQuery(cw).trigger('server-response',[true,node]);jQuery(node).trigger('server-response',[true,node]);}functionremoteCallFailed(err,req){cw.log(err);if(req.status==500){updateMessage(err);}else{updateMessage(_("an error occurred while processing your request"));}}//============= base AJAX functions to make remote calls =====================///** * .. function:: ajaxFuncArgs(fname, form, *args) * * extend `form` parameters to call the js_`fname` function of the json * controller with `args` arguments. */functionajaxFuncArgs(fname,form/* ... */){form=form||{};$.extend(form,{'fname':fname,'pageid':pageid,'arg':$.map(cw.utils.sliceList(arguments,2),jQuery.toJSON)});returnform;}/** * .. function:: loadxhtml(url, form, reqtype='get', mode='replace', cursor=false) * * build url given by absolute or relative `url` and `form` parameters * (dictionary), fetch it using `reqtype` method, then evaluate the * returned XHTML and insert it according to `mode` in the * document. Possible modes are : * * - 'replace' to replace the node's content with the generated HTML * - 'swap' to replace the node itself with the generated HTML * - 'append' to append the generated HTML to the node's content * * If `cursor`, turn mouse cursor into 'progress' cursor until the remote call * is back. */jQuery.fn.loadxhtml=function(url,form,reqtype,mode,cursor){if(this.size()>1){cw.log('loadxhtml called with more than one element');}elseif(this.size()<1){cw.log('loadxhtml called without an element');}varcallback=null;if(form&&form.callback){cw.log('[3.9] callback given through form.callback is deprecated, add '+'callback on the defered');callback=form.callback;deleteform.callback;}varnode=this.get(0);// only consider the first elementif(cursor){setProgressCursor();}vard=loadRemote(url,form,reqtype);d.addCallback(function(response){vardomnode=getDomFromResponse(response);domnode=loadAjaxHtmlHead(domnode);mode=mode||'replace';// make sure the component is visible$(node).removeClass("hidden");if(mode=='swap'){varorigId=node.id;node=cw.swapDOM(node,domnode);if(!node.id){node.id=origId;}}elseif(mode=='replace'){jQuery(node).empty().append(domnode);}elseif(mode=='append'){jQuery(node).append(domnode);}_postAjaxLoad(node);while(jQuery.isFunction(callback)){callback=callback.apply(this,[domnode]);}});d.addErrback(remoteCallFailed);if(cursor){d.addCallback(resetCursor);d.addErrback(resetCursor);}returnd;}/** * .. function:: loadRemote(url, form, reqtype='GET', sync=false) * * Asynchronously (unless `sync` argument is set to true) load an url or path * and return a deferred whose callbacks args are decoded according to the * Content-Type response header. `form` should be additional form params * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST'). */functionloadRemote(url,form,reqtype,sync){if(!url.toLowerCase().startswith(baseuri().toLowerCase())){url=baseuri()+url;}if(!sync){vardeferred=newDeferred();jQuery.ajax({url:url,type:(reqtype||'POST').toUpperCase(),data:form,traditional:true,async:true,beforeSend:function(xhr){deferred._req=xhr;},success:function(data,status){deferred.success(data);},error:function(xhr,status,error){try{if(xhr.status==500){varreason_dict=cw.evalJSON(xhr.responseText);deferred.error(xhr,status,reason_dict['reason']);return;}}catch(exc){cw.log('error with server side error report:'+exc);}deferred.error(xhr,status,null);}});returndeferred;}else{varresult;// jQuery.ajax returns the XHR object, even for synchronous requests,// but in that case, the success callback will be called before// jQuery.ajax returns. The first argument of the callback will be// the server result, interpreted by jQuery according to the reponse's// content-type (i.e. json or xml)jQuery.ajax({url:url,type:(reqtype||'GET').toUpperCase(),data:form,traditional:true,async:false,success:function(res){result=res;}});returnresult;}}//============= higher level AJAX functions using remote calls ===============///** * .. function:: _(message) * * emulation of gettext's _ shortcut */function_(message){returnloadRemote(AJAX_BASE_URL,ajaxFuncArgs('i18n',null,[message]),'GET',true)[0];}/** * .. function:: _loadDynamicFragments(node) * * finds each dynamic fragment in the page and executes the * the associated RQL to build them (Async call) */function_loadDynamicFragments(node){if(node){varfragments=jQuery(node).find('div.dynamicFragment');}else{varfragments=jQuery('div.dynamicFragment');}if(fragments.length==0){return;}if(typeofLOADING_MSG=='undefined'){LOADING_MSG='loading';// this is only a safety belt, it should not happen}for(vari=0;i<fragments.length;i++){varfragment=fragments[i];fragment.innerHTML='<h3>'+LOADING_MSG+' ... <img src="data/loading.gif" /></h3>';var$fragment=jQuery(fragment);// if cubicweb:loadurl is set, just pick the url et send it to loadxhtmlvarurl=$fragment.attr('cubicweb:loadurl');if(url){$fragment.loadxhtml(url);continue;}// else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc.varrql=$fragment.attr('cubicweb:rql');varitems=$fragment.attr('cubicweb:vid').split('&');varvid=items[0];varextraparams={};// case where vid='myvid¶m1=val1¶m2=val2': this is a deprecated abuse-caseif(items.length>1){cw.log("[3.5] you're using extraargs in cubicweb:vid "+"attribute, this is deprecated, consider using "+"loadurl instead");for(varj=1;j<items.length;j++){varkeyvalue=items[j].split('=');extraparams[keyvalue[0]]=keyvalue[1];}}varactrql=$fragment.attr('cubicweb:actualrql');if(actrql){extraparams['actualrql']=actrql;}varfbvid=$fragment.attr('cubicweb:fallbackvid');if(fbvid){extraparams['fallbackvid']=fbvid;}extraparams['rql']=rql;extraparams['vid']=vid;$fragment.loadxhtml(AJAX_BASE_URL,ajaxFuncArgs('view',extraparams));}}functionunloadPageData(){// NOTE: do not make async calls on unload if you want to avoid// strange bugsloadRemote(AJAX_BASE_URL,ajaxFuncArgs('unload_page_data'),'GET',true);}functionremoveBookmark(beid){vard=loadRemote(AJAX_BASE_URL,ajaxFuncArgs('delete_bookmark',null,beid));d.addCallback(function(boxcontent){$('#bookmarks_box').loadxhtml(AJAX_BASE_URL,ajaxFuncArgs('render',null,'ctxcomponents','bookmarks_box'));document.location.hash='#header';updateMessage(_("bookmark has been removed"));});}functionuserCallback(cbname){setProgressCursor();vard=loadRemote(AJAX_BASE_URL,ajaxFuncArgs('user_callback',null,cbname));d.addCallback(resetCursor);d.addErrback(resetCursor);d.addErrback(remoteCallFailed);returnd;}functionuserCallbackThenUpdateUI(cbname,compid,rql,msg,registry,nodeid){vard=userCallback(cbname);d.addCallback(function(){$('#'+nodeid).loadxhtml(AJAX_BASE_URL,ajaxFuncArgs('render',{'rql':rql},registry,compid));if(msg){updateMessage(msg);}});}functionuserCallbackThenReloadPage(cbname,msg){vard=userCallback(cbname);d.addCallback(function(){window.location.reload();if(msg){updateMessage(msg);}});}/** * .. function:: unregisterUserCallback(cbname) * * unregisters the python function registered on the server's side * while the page was generated. */functionunregisterUserCallback(cbname){setProgressCursor();vard=loadRemote(AJAX_BASE_URL,ajaxFuncArgs('unregister_user_callback',null,cbname));d.addCallback(resetCursor);d.addErrback(resetCursor);d.addErrback(remoteCallFailed);}//============= XXX move those functions? ====================================//functionopenHash(){if(document.location.hash){varnid=document.location.hash.replace('#','');varnode=jQuery('#'+nid);if(node){$(node).removeClass("hidden");}};}jQuery(document).ready(openHash);/** * .. function:: buildWysiwygEditors(parent) * *XXX: this function should go in edition.js but as for now, htmlReplace * references it. * * replace all textareas with fckeditors. */functionbuildWysiwygEditors(parent){jQuery('textarea').each(function(){if(this.getAttribute('cubicweb:type')=='wysiwyg'){// mark editor as instanciated, we may be called a number of times// (see _postAjaxLoad)this.setAttribute('cubicweb:type','fckeditor');if(typeofFCKeditor!="undefined"){varfck=newFCKeditor(this.id);fck.Config['CustomConfigurationsPath']=fckconfigpath;fck.Config['DefaultLanguage']=fcklang;fck.BasePath=baseuri()+"fckeditor/";fck.ReplaceTextarea();}else{cw.log('fckeditor could not be found.');}}});}jQuery(document).ready(buildWysiwygEditors);/** * .. function:: stripEmptyTextNodes(nodelist) * * takes a list of DOM nodes and removes all empty text nodes */functionstripEmptyTextNodes(nodelist){/* this DROPS empty text nodes */varstripped=[];for(vari=0;i<nodelist.length;i++){varnode=nodelist[i];if(isTextNode(node)){/* all browsers but FF -> innerText, FF -> textContent */vartext=node.innerText||node.textContent;if(text&&!text.strip()){continue;}}else{stripped.push(node);}}returnstripped;}/** * .. function:: getDomFromResponse(response) * * convenience function that returns a DOM node based on req's result. * XXX clarify the need to clone * */functiongetDomFromResponse(response){if(typeof(response)=='string'){vardoc=html2dom(response);}else{vardoc=response.documentElement;}varchildren=doc.childNodes;if(!children.length){// no child (error cases) => return the whole documentreturnjQuery(doc).clone().context;}children=stripEmptyTextNodes(children);if(children.length==1){// only one child => return itreturnjQuery(children[0]).clone().context;}// several children => wrap them in a single node and return the wrapreturnDIV({'cubicweb:type':"cwResponseWrapper"},$.map(children,function(node){returnjQuery(node).clone().context;}));}/* High-level functions *******************************************************//** * .. function:: reloadCtxComponentsSection(context, actualEid, creationEid=None) * * reload all components in the section for a given `context`. * * This is necessary for cases where the parent entity (on which the section * apply) has been created during post, hence the section has to be reloaded to * consider its new eid, hence the two additional arguments `actualEid` and * `creationEid`: `actualEid` is the eid of newly created top level entity and * `creationEid` the fake eid that was given as form creation marker (e.g. A). * * You can still call this function with only the actual eid if you're not in * such creation case. */functionreloadCtxComponentsSection(context,actualEid,creationEid){// in this case, actualEid is the eid of newly created top level entity and// creationEid the fake eid given as form creation marker (e.g. A)if(!creationEid){creationEid=actualEid;}var$compsholder=$('#'+context+creationEid);// reload the whole components section$compsholder.children().each(function(index){// XXX this.id[:-len(eid)]varcompid=this.id.replace("_",".").rstrip(creationEid);varparams=ajaxFuncArgs('render',null,'ctxcomponents',compid,actualEid);$(this).loadxhtml(AJAX_BASE_URL,params,null,'swap',true);});$compsholder.attr('id',context+actualEid);}/** * .. function:: reload(domid, compid, registry, formparams, *render_args) * * `js_render` based reloading of views and components. */functionreload(domid,compid,registry,formparams/* ... */){varajaxArgs=['render',formparams,registry,compid];ajaxArgs=ajaxArgs.concat(cw.utils.sliceList(arguments,4));varparams=ajaxFuncArgs.apply(null,ajaxArgs);return$('#'+domid).loadxhtml(AJAX_BASE_URL,params,null,'swap',true);}/* ajax tabs ******************************************************************/functionsetTab(tabname,cookiename){// set appropriate cookiejQuery.cookie(cookiename,tabname,{path:'/'});// trigger show + tabname eventtriggerLoad(tabname);}functionloadNow(eltsel,holesel,reloadable){varlazydiv=jQuery(eltsel);varhole=lazydiv.children(holesel);hole.show();if((hole.length==0)&&!reloadable){/* the hole is already filed */return;}lazydiv.loadxhtml(lazydiv.attr('cubicweb:loadurl'),{'pageid':pageid});}functiontriggerLoad(divid){jQuery('#lazy-'+divid).trigger('load_'+divid);}/* DEPRECATED *****************************************************************/preprocessAjaxLoad=cw.utils.deprecatedFunction('[3.9] preprocessAjaxLoad() is deprecated, use loadAjaxHtmlHead instead',function(node,newdomnode){returnloadAjaxHtmlHead(newdomnode);});reloadComponent=cw.utils.deprecatedFunction('[3.9] reloadComponent() is deprecated, use loadxhtml instead',function(compid,rql,registry,nodeid,extraargs){registry=registry||'components';rql=rql||'';nodeid=nodeid||(compid+'Component');extraargs=extraargs||{};varnode=cw.jqNode(nodeid);returnnode.loadxhtml(AJAX_BASE_URL,ajaxFuncArgs('component',null,compid,rql,registry,extraargs));});reloadBox=cw.utils.deprecatedFunction('[3.9] reloadBox() is deprecated, use loadxhtml instead',function(boxid,rql){returnreloadComponent(boxid,rql,'ctxcomponents',boxid);});replacePageChunk=cw.utils.deprecatedFunction('[3.9] replacePageChunk() is deprecated, use loadxhtml instead',function(nodeId,rql,vid,extraparams,/* ... */swap,callback){varparams=null;if(callback){params={callback:callback};}varnode=jQuery('#'+nodeId)[0];varprops={};if(node){props['rql']=rql;props['fname']='view';props['pageid']=pageid;if(vid){props['vid']=vid;}if(extraparams){jQuery.extend(props,extraparams);}// FIXME we need to do asURL(props) manually instead of// passing `props` directly to loadxml because replacePageChunk// is sometimes called (abusively) with some extra parameters in `vid`varmode=swap?'swap':'replace';varurl=AJAX_BASE_URL+asURL(props);jQuery(node).loadxhtml(url,params,'get',mode);}else{cw.log('Node',nodeId,'not found');}});loadxhtml=cw.utils.deprecatedFunction('[3.9] loadxhtml() function is deprecated, use loadxhtml method instead',function(nodeid,url,/* ... */replacemode){jQuery('#'+nodeid).loadxhtml(url,null,'post',replacemode);});functionremoteExec(fname/* ... */){setProgressCursor();varprops={fname:fname,pageid:pageid,arg:$.map(cw.utils.sliceList(arguments,1),jQuery.toJSON)};varresult=jQuery.ajax({url:AJAX_BASE_URL,data:props,async:false,traditional:true}).responseText;if(result){result=cw.evalJSON(result);}resetCursor();returnresult;}functionasyncRemoteExec(fname/* ... */){setProgressCursor();varprops={fname:fname,pageid:pageid,arg:$.map(cw.utils.sliceList(arguments,1),jQuery.toJSON)};// XXX we should inline the content of loadRemote herevardeferred=loadRemote(AJAX_BASE_URL,props,'POST');deferred=deferred.addErrback(remoteCallFailed);deferred=deferred.addErrback(resetCursor);deferred=deferred.addCallback(resetCursor);returndeferred;}jQuery(document).ready(function(){_loadDynamicFragments();// build loaded_scripts / loaded_links listscw.loaded_scripts=[];jQuery('head script[src]').each(function(i){cw.utils.extend(cw.loaded_scripts,cw.ajax._listResources(this.getAttribute('src')));});cw.loaded_links=[];jQuery('head link[href]').each(function(i){cw.utils.extend(cw.loaded_links,cw.ajax._listResources(this.getAttribute('href')));});});