[rset] support for delitem on repeat list (may be necessary in pyro source)
/* copyright 2003-2010 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 */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){if(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){if(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.xhr,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);}}});varJSON_BASE_URL=baseuri()+'json?';//============= utility function handling remote calls responses. ==============//function_loadAjaxHtmlHead($node,$head,tag,srcattr){varjqtagfilter=tag+'['+srcattr+']';if(cw['loaded_'+srcattr]===undefined){cw['loaded_'+srcattr]=[];varloaded=cw['loaded_'+srcattr];jQuery('head '+jqtagfilter).each(function(i){loaded.push(this.getAttribute(srcattr));});}else{varloaded=cw['loaded_'+srcattr];}$node.find(tag).each(function(i){varurl=this.getAttribute(srcattr);if(url){if(jQuery.inArray(url,loaded)==-1){// take care to <script> tags: jQuery append method script nodes// don't appears in the DOM (See comments on// http://api.jquery.com/append/), which cause undesired// duplicated load in our case. After trying to use bare DOM api// to avoid this, we switched to handle a list of already loaded// stuff ourselves, since bare DOM api gives bug with the// server-response event, since we loose control on when the// script is loaded (jQuery load it immediatly).loaded.push(url);jQuery(this).appendTo($head);}}else{jQuery(this).appendTo($head);}});$node.find(jqtagfilter).remove();}/** * .. 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;}_loadAjaxHtmlHead($responseHead,$head,'script','src');_loadAjaxHtmlHead($responseHead,$head,'link','href');// 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 sortable tables if there are someif(typeof(Sortable)!='undefined'){Sortable.sortTables(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(CubicWeb).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=true) * * 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 was called with more than one 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 `async` argument is set to false) 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.startswith(baseuri())){url=baseuri()+url;}if(!sync){vardeferred=newDeferred();jQuery.ajax({url:url,type:(reqtype||'GET').toUpperCase(),data:form,async:true,beforeSend:function(xhr){deferred._req=xhr;},success:function(data,status){if(deferred._req.getResponseHeader("content-type")=='application/json'){data=cw.evalJSON(data);}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({url:url,type:(reqtype||'GET').toUpperCase(),data:form,async:false});// check result.responseText instead of result to avoid error encountered with IEif(result.responseText){// XXX no good reason to force json here, // it should depends on request content-typeresult=cw.evalJSON(result.responseText);}returnresult;}}//============= higher level AJAX functions using remote calls ===============///** * .. function:: _(message) * * emulation of gettext's _ shortcut */function_(message){returnloadRemote('json',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('json',ajaxFuncArgs('view',extraparams));}}jQuery(document).ready(function(){_loadDynamicFragments();});functionunloadPageData(){// NOTE: do not make async calls on unload if you want to avoid// strange bugsloadRemote('json',ajaxFuncArgs('unload_page_data'),'GET',true);}functionremoveBookmark(beid){vard=loadRemote('json',ajaxFuncArgs('delete_bookmark',null,beid));d.addCallback(function(boxcontent){$('#bookmarks_box').loadxhtml('json',ajaxFuncArgs('render',null,'boxes','bookmarks_box'));document.location.hash='#header';updateMessage(_("bookmark has been removed"));});}functionuserCallback(cbname){setProgressCursor();vard=loadRemote('json',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('json',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('json',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="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;}));}/* 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('json',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,'boxes',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=JSON_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);});remoteExec=cw.utils.deprecatedFunction('[3.9] remoteExec() is deprecated, use loadRemote instead',function(fname/* ... */){setProgressCursor();varprops={'fname':fname,'pageid':pageid,'arg':$.map(cw.utils.sliceList(arguments,1),jQuery.toJSON)};varresult=jQuery.ajax({url:JSON_BASE_URL,data:props,async:false}).responseText;if(result){result=cw.evalJSON(result);}resetCursor();returnresult;});asyncRemoteExec=cw.utils.deprecatedFunction('[3.9] asyncRemoteExec() is deprecated, use loadRemote instead',function(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(JSON_BASE_URL,props,'POST');deferred=deferred.addErrback(remoteCallFailed);deferred=deferred.addErrback(resetCursor);deferred=deferred.addCallback(resetCursor);returndeferred;});