[webtests/views_errorform] use the new connection api
/** * Functions dedicated to widgets. * * :organization: Logilab * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr * * */// widget namespaceWidgets={};/** * .. function:: buildWidget(wdgnode) * * this function takes a DOM node defining a widget and * instantiates / builds the appropriate widget class */functionbuildWidget(wdgnode){varwdgclass=Widgets[wdgnode.getAttribute('cubicweb:wdgtype')];if(wdgclass){returnnewwdgclass(wdgnode);}returnnull;}/** * .. function:: buildWidgets(root) * * This function is called on load and is in charge to build * JS widgets according to DOM nodes found in the page */functionbuildWidgets(root){root=root||document;jQuery(root).find('.widget').each(function(){if(this.getAttribute('cubicweb:loadtype')=='auto'){buildWidget(this);}});}jQuery(document).ready(function(){buildWidgets();});functionpostJSON(url,data,callback){returnjQuery.post(url,data,callback,AJAX_BASE_URL);}functiongetJSON(url,data,callback){returnjQuery.get(url,data,callback,AJAX_BASE_URL);}(function($){vardefaultSettings={initialvalue:'',multiple:false,mustMatch:false,delay:50,limit:50};functionsplit(val){returnval.split(/\s*,\s*/);}functionextractLast(term){returnsplit(term).pop();}functionallButLast(val){varterms=split(val);terms.pop();returnterms;}varmethods={__init__:function(suggestions,options){returnthis.each(function(){// here, `this` refers to the DOM element (e.g. input) being wrapped// by cwautomplete pluginvarinstanceData=$(this).data('cwautocomplete');if(instanceData){// already initializedreturn;}varsettings=$.extend({},defaultSettings,options);instanceData={initialvalue:settings.initialvalue,userInput:this,hiddenInput:null};varhiHandlers=methods.hiddenInputHandlers;$(this).data('cwautocomplete',instanceData);// in case of an existing value, the hidden input must be initialized even if// the value is not changedif(($(instanceData.userInput).attr('cubicweb:initialvalue')!==undefined)&&!instanceData.hiddenInput){hiHandlers.initializeHiddenInput(instanceData);}$.ui.autocomplete.prototype._value=methods._value;$.data(this,'settings',settings);if(settings.multiple){$.ui.autocomplete.filter=methods.multiple.makeFilter(this);$(this).bind({autocompleteselect:methods.multiple.select,autocompletefocus:methods.multiple.focus,keydown:methods.multiple.keydown});}// XXX katia we dont need it if minLength == 0, but by setting minLength = 0// we probably break the backward compatibility$(this).bind('blur',methods.blur);if($.isArray(suggestions)){// precomputed list of suggestionssettings.source=hiHandlers.checkSuggestionsDataFormat(instanceData,suggestions);}else{// url to call each time something is typedsettings.source=function(request,response){vard=loadRemote(suggestions,{q:request.term,limit:settings.limit},'POST');d.addCallback(function(suggestions){suggestions=hiHandlers.checkSuggestionsDataFormat(instanceData,suggestions);response(suggestions);if((suggestions.length)==0){methods.resetValues(instanceData);}});};}$(this).autocomplete(settings);if(settings.mustMatch){$(this).keypress(methods.ensureExactMatch);}});},_value:function(){/* We extend the widget with the ability to lookup and handle several terms at once ('multiple' option). E.g.: toto, titi, tu.... The autocompletion must be performed only on the last of such a list of terms. */varsettings=$(this.element).data('settings');varvalue=this.valueMethod.apply(this.element,arguments);if(settings.multiple&arguments.length===0){returnextractLast(value);}returnvalue},multiple:{focus:function(){// prevent value inserted on focusreturnfalse;},select:function(event,ui){varterms=allButLast(this.value);// add the selected itemterms.push(ui.item.value);// add placeholder to get the comma-and-space at the endterms.push("");this.value=terms.join(", ");returnfalse;},keydown:function(evt){if(evt.keyCode==$.ui.keyCode.TAB){evt.preventDefault();}},makeFilter:function(userInput){returnfunction(array,term){// remove already entered terms from suggestion listarray=cw.utils.difference(array,allButLast(userInput.value));varmatcher=newRegExp($.ui.autocomplete.escapeRegex(term),"i");return$.grep(array,function(value){returnmatcher.test(value.label||value.value||value);});};}},blur:function(evt){varinstanceData=$(this).data('cwautocomplete');if($(instanceData.userInput).val().strip().length==0){methods.resetValues(instanceData);}},ensureExactMatch:function(evt){varinstanceData=$(this).data('cwautocomplete');if(evt.keyCode==$.ui.keyCode.ENTER||evt.keyCode==$.ui.keyCode.TAB){varvalidChoices=$.map($('ul.ui-autocomplete li'),function(li){return$(li).text();});if($.inArray($(instanceData.userInput).val(),validChoices)==-1){$(instanceData.userInput).val('');$(instanceData.hiddenInput).val(instanceData.initialvalue||'');}}},resetValues:function(instanceData){$(instanceData.userInput).val('');$(instanceData.hiddenInput).val('');},hiddenInputHandlers:{/** * `hiddenInputHandlers` defines all methods specific to handle the * hidden input created along the standard text input. * An hiddenInput is necessary when displayed suggestions are * different from actual values to submit. * Imagine an autocompletion widget to choose among a list of CWusers. * Suggestions would be the list of logins, but actual values would * be the corresponding eids. * To handle such cases, suggestions list should be a list of JS objects * with two `label` and `value` properties. **/suggestionSelected:function(evt,ui){varinstanceData=$(this).data('cwautocomplete');instanceData.hiddenInput.value=ui.item.value;instanceData.value=ui.item.label;returnfalse;// stop propagation},suggestionFocusChanged:function(evt,ui){varinstanceData=$(this).data('cwautocomplete');instanceData.userInput.value=ui.item.label;returnfalse;// stop propagation},needsHiddenInput:function(suggestions){returnsuggestions[0].label!==undefined;},initializeHiddenInput:function(instanceData){varuserInput=instanceData.userInput;varhiddenInput=INPUT({type:"hidden",name:userInput.name,// XXX katia : this must be handeled in .SuggestField widget, but// it seems not to be used anymorevalue:$(userInput).attr('cubicweb:initialvalue')||userInput.value});$(userInput).removeAttr('name').after(hiddenInput);instanceData.hiddenInput=hiddenInput;$(userInput).bind({autocompleteselect:methods.hiddenInputHandlers.suggestionSelected,autocompletefocus:methods.hiddenInputHandlers.suggestionFocusChanged});},/* * internal convenience function: old jquery plugin accepted to be fed * with a list of couples (value, label). The new (jquery-ui) autocomplete * plugin expects a list of objects with "value" and "label" properties. * * This function converts the old format to the new one. */checkSuggestionsDataFormat:function(instanceData,suggestions){// check for old (value, label) formatif($.isArray(suggestions)&&suggestions.length&&$.isArray(suggestions[0])){if(suggestions[0].length==2){cw.log('[3.10] autocomplete init func should return {label,value} dicts instead of lists');suggestions=$.map(suggestions,function(sugg){return{value:sugg[0],label:sugg[1]};});}else{if(suggestions[0].length==1){suggestions=$.map(suggestions,function(sugg){return{value:sugg[0],label:sugg[0]};});}}}varhiHandlers=methods.hiddenInputHandlers;if(suggestions.length&&hiHandlers.needsHiddenInput(suggestions)&&!instanceData.hiddenInput){hiHandlers.initializeHiddenInput(instanceData);hiHandlers.fixUserInputInitialValue(instanceData,suggestions);}// otherwise, assume data shape is correctreturnsuggestions;},fixUserInputInitialValue:function(instanceData,suggestions){// called when the data is loaded to reset the correct displayed// value in the visible input field (typically replacing an eid// by a displayable value)varcurvalue=instanceData.userInput.value;if(!curvalue){return;}for(vari=0,length=suggestions.length;i<length;i++){varsugg=suggestions[i];if(sugg.value==curvalue){instanceData.userInput.value=sugg.label;return;}}}}};$.fn.cwautocomplete=function(data,options){returnmethods.__init__.apply(this,[data,options]);};})(jQuery);Widgets.SuggestField=defclass('SuggestField',null,{__init__:function(node,options){options=options||{};varmulti=node.getAttribute('cubicweb:multi');options.multiple=(multi=="yes")?true:false;vard=loadRemote(node.getAttribute('cubicweb:dataurl'));d.addCallback(function(data){$(node).cwautocomplete(data,options);});}});Widgets.StaticFileSuggestField=defclass('StaticSuggestField',[Widgets.SuggestField],{__init__:function(node){Widgets.SuggestField.__init__(this,node,{method:'get'// XXX});}});Widgets.RestrictedSuggestField=defclass('RestrictedSuggestField',[Widgets.SuggestField],{__init__:function(node){Widgets.SuggestField.__init__(this,node,{mustMatch:true});}});//remote version of RestrictedSuggestFieldWidgets.LazySuggestField=defclass('LazySuggestField',[Widgets.SuggestField],{__init__:function(node,options){varself=this;options=options||{};options.delay=50;// multiple selection not supported yet (still need to formalize correctly// initial values / display values)varinitialvalue=cw.evalJSON(node.getAttribute('cubicweb:initialvalue')||'null');if(!initialvalue){initialvalue=node.value;}options.initialvalue=initialvalue;Widgets.SuggestField.__init__(this,node,options);}});/** * .. class:: Widgets.SuggestForm * * suggestform displays a suggest field and associated validate / cancel buttons * constructor's argumemts are the same that BaseSuggestField widget */Widgets.SuggestForm=defclass("SuggestForm",null,{__init__:function(inputid,initfunc,varargs,validatefunc,options){this.validatefunc=validatefunc||noop;this.sgfield=newWidgets.BaseSuggestField(inputid,initfunc,varargs,options);this.oklabel=options.oklabel||'ok';this.cancellabel=options.cancellabel||'cancel';bindMethods(this);connect(this.sgfield,'validate',this,this.entryValidated);},show:function(parentnode){varsgnode=this.sgfield.builddom();varbuttons=DIV({'class':"sgformbuttons"},[A({'href':"javascript: noop();",'onclick':this.onValidateClicked},this.oklabel),' / ',A({'href':"javascript: noop();",'onclick':this.destroy},escapeHTML(this.cancellabel))]);varformnode=DIV({'class':"sgform"},[sgnode,buttons]);appendChildNodes(parentnode,formnode);this.sgfield.textinput.focus();this.formnode=formnode;returnformnode;},destroy:function(){signal(this,'destroy');this.sgfield.destroy();removeElement(this.formnode);},onValidateClicked:function(){this.validatefunc(this,this.sgfield.taglist());},/* just an indirection to pass the form instead of the sgfield as first parameter */entryValidated:function(sgfield,taglist){this.validatefunc(this,taglist);}});/** * .. function:: toggleTree(event) * * called when the use clicks on a tree node * - if the node has a `cubicweb:loadurl` attribute, replace the content of the node * by the url's content. * - else, there's nothing to do, let the jquery plugin handle it. */functiontoggleTree(event){varlinode=jQuery(this);varurl=linode.attr('cubicweb:loadurl');if(url){linode.find('ul.placeholder').remove();vard=linode.loadxhtml(url,null,'post','append');d.addCallback(function(domnode){linode.removeAttr('cubicweb:loadurl');linode.find('> ul.treeview').treeview({toggle:toggleTree,prerendered:true});returnnull;});}}/** * .. class:: Widgets.TimelineWidget * * widget based on SIMILE's timeline widget * http://code.google.com/p/simile-widgets/ * * Beware not to mess with SIMILE's Timeline JS namepsace ! */Widgets.TimelineWidget=defclass("TimelineWidget",null,{__init__:function(wdgnode){vartldiv=DIV({id:"tl",style:'height: 200px; border: 1px solid #ccc;'});wdgnode.appendChild(tldiv);vartlunit=wdgnode.getAttribute('cubicweb:tlunit')||'YEAR';vareventSource=newTimeline.DefaultEventSource();varbandData={eventPainter:Timeline.CubicWebEventPainter,eventSource:eventSource,width:"100%",intervalUnit:Timeline.DateTime[tlunit.toUpperCase()],intervalPixels:100};varbandInfos=[Timeline.createBandInfo(bandData)];this.tl=Timeline.create(tldiv,bandInfos);varloadurl=wdgnode.getAttribute('cubicweb:loadurl');Timeline.loadJSON(loadurl,function(json,url){eventSource.loadJSON(json,url);});}});Widgets.TemplateTextField=defclass("TemplateTextField",null,{__init__:function(wdgnode){this.variables=jQuery(wdgnode).attr('cubicweb:variables').split(',');this.options={name:wdgnode.getAttribute('cubicweb:inputid'),rows:wdgnode.getAttribute('cubicweb:rows')||40,cols:wdgnode.getAttribute('cubicweb:cols')||80};// this.variableRegexp = /%\((\w+)\)s/;this.errorField=DIV({'class':"errorMessage"});this.textField=TEXTAREA(this.options);jQuery(this.textField).bind('keyup',{'self':this},this.highlightInvalidVariables);jQuery('#substitutions').prepend(this.errorField);jQuery('#substitutions .errorMessage').hide();wdgnode.appendChild(this.textField);},/* signal callbacks */highlightInvalidVariables:function(event){varself=event.data.self;vartext=self.textField.value;varunknownVariables=[];varit=0;vargroup=null;varvariableRegexp=/%\((\w+)\)s/g;// emulates rgx.findAll()while((group=variableRegexp.exec(text))){if($.inArray(group[1],self.variables)==-1){unknownVariables.push(group[1]);}it++;if(it>5){break;}}varerrText='';if(unknownVariables.length){errText="Detected invalid variables : "+unknownVariables.join(', ');jQuery('#substitutions .errorMessage').show();}else{jQuery('#substitutions .errorMessage').hide();}self.errorField.innerHTML=errText;}});cw.widgets={/** * .. function:: insertText(text, areaId) * * inspects textarea with id `areaId` and replaces the current selected text * with `text`. Cursor is then set at the end of the inserted text. */insertText:function(text,areaId){vartextarea=jQuery('#'+areaId);if(document.selection){// IEvarselLength;textarea.focus();varsel=document.selection.createRange();selLength=sel.text.length;sel.text=text;sel.moveStart('character',selLength-text.length);sel.select();}elseif(textarea.selectionStart||textarea.selectionStart=='0'){// mozillavarstartPos=textarea.selectionStart;varendPos=textarea.selectionEnd;// insert text so that it replaces the [startPos, endPos] parttextarea.value=textarea.value.substring(0,startPos)+text+textarea.value.substring(endPos,textarea.value.length);// set cursor pos at the end of the inserted texttextarea.selectionStart=textarea.selectionEnd=startPos+text.length;textarea.focus();}else{// safety belt for other browserstextarea.value+=text;}}};// InOutWidget This contains specific InOutnWidget javascript// IE things can not handle hide/show options on select, this cloned list solition (should propably have 2 widgets)(function($){varmethods={__init__:function(fromSelect,toSelect){// closed over statevarstate={'$fromNode':$(cw.escape('#'+fromSelect)),'$toNode':$(cw.escape('#'+toSelect)),'name':this.attr('id')};functionsortoptions($optionlist){var$sorted=$optionlist.find('option').sort(function(opt1,opt2){return$(opt1).text()>$(opt2).text()?1:-1;});// this somehow translates to an inplace sort$optionlist.append($sorted);};sortoptions(state.$fromNode);sortoptions(state.$toNode);// will move selected options from one list to the other// and call an option handler on each optionfunctionmoveoptions($fromlist,$tolist,opthandler){$fromlist.find('option:selected').each(function(index,option){var$option=$(option);// add a new option to the target list$tolist.append(OPTION({'value':$option.val()},$option.text()));// process callback on the optionopthandler.call(null,$option);// remove option from the source list$option.remove();});// re-sort both listssortoptions($fromlist);sortoptions($tolist);};functionaddvalues(){moveoptions(state.$fromNode,state.$toNode,function($option){// add an hidden input for the edit controllervarhiddenInput=INPUT({type:'hidden',name:state.name,value:$option.val()});state.$toNode.parent().append(hiddenInput);});};functionremovevalues(){moveoptions(state.$toNode,state.$fromNode,function($option){// remove hidden inputs for the edit controllervarselector='input[name='+cw.escape(state.name)+']'state.$toNode.parent().find(selector).each(function(index,input){if($(input).val()==$option.val()){$(input).remove();}});});};var$this=$(this);$this.find('.cwinoutadd').bind(// 'add >>>' symbol'click',{'state':state},addvalues);$this.find('.cwinoutremove').bind(// 'remove <<<' symbol'click',{'state':state},removevalues);state.$fromNode.bind('dblclick',{'state':state},addvalues);state.$toNode.bind('dblclick',{'state':state},removevalues);}};$.fn.cwinoutwidget=function(fromSelect,toSelect){returnmethods.__init__.apply(this,[fromSelect,toSelect]);};})(jQuery);