# copyright 2003-2013 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/>."""Widgets~~~~~~~.. Note:: A widget is responsible for the display of a field. It may use more than one HTML input tags. When the form is posted, a widget is also reponsible to give back to the field something it can understand. Of course you can not use any widget with any field..... autoclass:: cubicweb.web.formwidgets.FieldWidgetHTML <input> based widgets''''''''''''''''''''''''''.. autoclass:: cubicweb.web.formwidgets.HiddenInput.. autoclass:: cubicweb.web.formwidgets.TextInput.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput.. autoclass:: cubicweb.web.formwidgets.FileInput.. autoclass:: cubicweb.web.formwidgets.ButtonInputOther standard HTML widgets'''''''''''''''''''''''''''.. autoclass:: cubicweb.web.formwidgets.TextArea.. autoclass:: cubicweb.web.formwidgets.Select.. autoclass:: cubicweb.web.formwidgets.CheckBox.. autoclass:: cubicweb.web.formwidgets.RadioDate and time widgets'''''''''''''''''''''.. autoclass:: cubicweb.web.formwidgets.DateTimePicker.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker.. autoclass:: cubicweb.web.formwidgets.JQueryTimePickerAjax / javascript widgets'''''''''''''''''''''''''.. autoclass:: cubicweb.web.formwidgets.FCKEditor.. autoclass:: cubicweb.web.formwidgets.AjaxWidget.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget.. autoclass:: cubicweb.web.formwidgets.InOutWidget.. kill or document StaticFileAutoCompletionWidget.. kill or document LazyRestrictedAutoCompletionWidget.. kill or document RestrictedAutoCompletionWidgetOther widgets'''''''''''''.. autoclass:: cubicweb.web.formwidgets.PasswordInput.. autoclass:: cubicweb.web.formwidgets.IntervalWidget.. autoclass:: cubicweb.web.formwidgets.BitSelect.. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget.. autoclass:: cubicweb.web.formwidgets.EditableURLWidgetForm controls'''''''''''''Those classes are not proper widget (they are not associated to field) but areused as form controls. Their API is similar to widgets except that `field`argument given to :meth:`render` will be `None`... autoclass:: cubicweb.web.formwidgets.Button.. autoclass:: cubicweb.web.formwidgets.SubmitButton.. autoclass:: cubicweb.web.formwidgets.ResetButton.. autoclass:: cubicweb.web.formwidgets.ImgButton"""__docformat__="restructuredtext en"fromfunctoolsimportreducefromdatetimeimportdatefromwarningsimportwarnfromlogilab.mtconverterimportxml_escapefromlogilab.common.deprecationimportdeprecatedfromlogilab.common.dateimporttodatetimefromcubicwebimporttags,uilibfromcubicweb.utilsimportjson_dumpsfromcubicweb.webimportstdmsgs,INTERNAL_FIELD_VALUE,ProcessFormErrorclassFieldWidget(object):"""The abstract base class for widgets. **Attributes** Here are standard attributes of a widget, that may be set on concrete class to override default behaviours: :attr:`needs_js` list of javascript files needed by the widget. :attr:`needs_css` list of css files needed by the widget. :attr:`setdomid` flag telling if HTML DOM identifier should be set on input. :attr:`settabindex` flag telling if HTML tabindex attribute of inputs should be set. :attr:`suffix` string to use a suffix when generating input, to ease usage as a sub-widgets (eg widget used by another widget) :attr:`vocabulary_widget` flag telling if this widget expect a vocabulary Also, widget instances takes as first argument a `attrs` dictionary which will be stored in the attribute of the same name. It contains HTML attributes that should be set in the widget's input tag (though concrete classes may ignore it). .. currentmodule:: cubicweb.web.formwidgets **Form generation methods** .. automethod:: render .. automethod:: _render .. automethod:: values .. automethod:: attributes **Post handling methods** .. automethod:: process_field_data """needs_js=()needs_css=()setdomid=Truesettabindex=Truesuffix=None# does this widget expect a vocabularyvocabulary_widget=Falsedef__init__(self,attrs=None,setdomid=None,settabindex=None,suffix=None):ifattrsisNone:attrs={}self.attrs=attrsifsetdomidisnotNone:# override class's default valueself.setdomid=setdomidifsettabindexisnotNone:# override class's default valueself.settabindex=settabindexifsuffixisnotNone:self.suffix=suffixdefadd_media(self,form):"""adds media (CSS & JS) required by this widget"""ifself.needs_js:form._cw.add_js(self.needs_js)ifself.needs_css:form._cw.add_css(self.needs_css)defrender(self,form,field,renderer=None):"""Called to render the widget for the given `field` in the given `form`. Return a unicode string containing the HTML snippet. You will usually prefer to override the :meth:`_render` method so you don't have to handle addition of needed javascript / css files. """self.add_media(form)returnself._render(form,field,renderer)def_render(self,form,field,renderer):"""This is the method you have to implement in concrete widget classes. """raiseNotImplementedError()defformat_value(self,form,field,value):returnfield.format_value(form._cw,value)defattributes(self,form,field):"""Return HTML attributes for the widget, automatically setting DOM identifier and tabindex when desired (see :attr:`setdomid` and :attr:`settabindex` attributes) """attrs=dict(self.attrs)ifself.setdomid:attrs['id']=field.dom_id(form,self.suffix)ifself.settabindexandnot'tabindex'inattrs:attrs['tabindex']=form._cw.next_tabindex()returnattrsdefvalues(self,form,field):"""Return the current *string* values (i.e. for display in an HTML string) for the given field. This method returns a list of values since it's suitable for all kind of widgets, some of them taking multiple values, but you'll get a single value in the list in most cases. Those values are searched in: 1. previously submitted form values if any (on validation error) 2. req.form (specified using request parameters) 3. extra form values given to form.render call (specified the code generating the form) 4. field's typed value (returned by its :meth:`~cubicweb.web.formfields.Field.typed_value` method) Values found in 1. and 2. are expected te be already some 'display value' (eg a string) while those found in 3. and 4. are expected to be correctly typed value. 3 and 4 are handle by the :meth:`typed_value` method to ease reuse in concrete classes. """values=Noneifnotfield.ignore_req_params:qname=field.input_name(form,self.suffix)# value from a previous post that has raised a validation errorifqnameinform.form_previous_values:values=form.form_previous_values[qname]# value specified using form parameterselifqnameinform._cw.form:values=form._cw.form[qname]eliffield.name!=qnameandfield.nameinform._cw.form:# XXX compat: accept attr=value in req.form to specify value of# attr-subjectvalues=form._cw.form[field.name]ifvaluesisNone:values=self.typed_value(form,field)ifvalues!=INTERNAL_FIELD_VALUE:values=self.format_value(form,field,values)ifnotisinstance(values,(tuple,list)):values=(values,)returnvaluesdeftyped_value(self,form,field):"""return field's *typed* value specified in: 3. extra form values given to render() 4. field's typed value """qname=field.input_name(form)forkeyin((field,form),qname):try:returnform.formvalues[key]exceptKeyError:continueiffield.name!=qnameandfield.nameinform.formvalues:returnform.formvalues[field.name]returnfield.typed_value(form)defprocess_field_data(self,form,field):"""Return process posted value(s) for widget and return something understandable by the associated `field`. That value may be correctly typed or a string that the field may parse. """posted=form._cw.formval=posted.get(field.input_name(form,self.suffix))ifisinstance(val,basestring):val=val.strip()returnval# XXX deprecatesdefvalues_and_attributes(self,form,field):returnself.values(form,field),self.attributes(form,field)classInput(FieldWidget):"""abstract widget class for <input> tag based widgets"""type=Nonedef_render(self,form,field,renderer):"""render the widget for the given `field` of `form`. Generate one <input> tag for each field's value """values,attrs=self.values_and_attributes(form,field)# ensure something is renderedifnotvalues:values=(INTERNAL_FIELD_VALUE,)inputs=[tags.input(name=field.input_name(form,self.suffix),type=self.type,value=value,**attrs)forvalueinvalues]returnu'\n'.join(inputs)# basic html widgets ###########################################################classTextInput(Input):"""Simple <input type='text'>, will return an unicode string."""type='text'classPasswordSingleInput(Input):"""Simple <input type='password'>, will return an utf-8 encoded string. You may prefer using the :class:`~cubicweb.web.formwidgets.PasswordInput` widget which handles password confirmation. """type='password'defprocess_field_data(self,form,field):value=super(PasswordSingleInput,self).process_field_data(form,field)ifvalueisnotNone:returnvalue.encode('utf-8')returnvalueclassPasswordInput(Input):"""<input type='password'> and a confirmation input. Form processing will fail if password and confirmation differs, else it will return the password as an utf-8 encoded string. """type='password'def_render(self,form,field,renderer):assertself.suffixisNone,'suffix not supported'values,attrs=self.values_and_attributes(form,field)assertlen(values)==1domid=attrs.pop('id')inputs=[tags.input(name=field.input_name(form),value=values[0],type=self.type,id=domid,**attrs),'<br/>',tags.input(name=field.input_name(form,'-confirm'),value=values[0],type=self.type,**attrs),' ',tags.span(form._cw._('confirm password'),**{'class':'emphasis'})]returnu'\n'.join(inputs)defprocess_field_data(self,form,field):passwd1=super(PasswordInput,self).process_field_data(form,field)passwd2=form._cw.form.get(field.input_name(form,'-confirm'))ifpasswd1==passwd2:ifpasswd1isNone:returnNonereturnpasswd1.encode('utf-8')raiseProcessFormError(form._cw._("password and confirmation don't match"))classFileInput(Input):"""Simple <input type='file'>, will return a tuple (name, stream) where name is the posted file name and stream a file like object containing the posted file data. """type='file'defvalues(self,form,field):# ignore value which makes no sense here (XXX even on form validation error?)return('',)classHiddenInput(Input):"""Simple <input type='hidden'> for hidden value, will return an unicode string. """type='hidden'setdomid=False# by default, don't set id attribute on hidden inputsettabindex=FalseclassButtonInput(Input):"""Simple <input type='button'>, will return an unicode string. If you want a global form button, look at the :class:`Button`, :class:`SubmitButton`, :class:`ResetButton` and :class:`ImgButton` below. """type='button'classTextArea(FieldWidget):"""Simple <textarea>, will return an unicode string."""def_render(self,form,field,renderer):values,attrs=self.values_and_attributes(form,field)attrs.setdefault('onkeyup','autogrow(this)')ifnotvalues:value=u''eliflen(values)==1:value=values[0]else:raiseValueError('a textarea is not supposed to be multivalued')lines=value.splitlines()linecount=len(lines)forlineinlines:linecount+=len(line)/80attrs.setdefault('cols',80)attrs.setdefault('rows',min(15,linecount+2))returntags.textarea(value,name=field.input_name(form,self.suffix),**attrs)classFCKEditor(TextArea):"""FCKEditor enabled <textarea>, will return an unicode string containing HTML formated text. """def__init__(self,*args,**kwargs):super(FCKEditor,self).__init__(*args,**kwargs)self.attrs['cubicweb:type']='wysiwyg'def_render(self,form,field,renderer):form._cw.fckeditor_config()returnsuper(FCKEditor,self)._render(form,field,renderer)classSelect(FieldWidget):"""Simple <select>, for field having a specific vocabulary. Will return an unicode string, or a list of unicode strings. """vocabulary_widget=Truedefault_size=10def__init__(self,attrs=None,multiple=False,**kwargs):super(Select,self).__init__(attrs,**kwargs)self._multiple=multipledef_render(self,form,field,renderer):curvalues,attrs=self.values_and_attributes(form,field)options=[]optgroup_opened=Falsevocab=field.vocabulary(form)foroptioninvocab:try:label,value,oattrs=optionexceptValueError:label,value=optionoattrs={}ifvalueisNone:# handle separatorifoptgroup_opened:options.append(u'</optgroup>')oattrs.setdefault('label',labelor'')options.append(u'<optgroup %s>'%uilib.sgml_attributes(oattrs))optgroup_opened=Trueelifself.value_selected(value,curvalues):options.append(tags.option(label,value=value,selected='selected',**oattrs))else:options.append(tags.option(label,value=value,**oattrs))ifoptgroup_opened:options.append(u'</optgroup>')ifnot'size'inattrs:ifself._multiple:size=unicode(min(self.default_size,len(vocab)or1))else:size=u'1'attrs['size']=sizereturntags.select(name=field.input_name(form,self.suffix),multiple=self._multiple,options=options,**attrs)defvalue_selected(self,value,curvalues):returnvalueincurvaluesclassInOutWidget(Select):needs_js=('cubicweb.widgets.js',)default_size=10template="""<table id="%(widgetid)s"> <tr> <td>%(inoutinput)s</td> <td><div style="margin-bottom:3px">%(addinput)s</div> <div>%(removeinput)s</div> </td> <td>%(resinput)s</td> </tr></table>"""add_button=('<input type="button" class="wdgButton cwinoutadd" ''value=">>" size="10" />')remove_button=('<input type="button" class="wdgButton cwinoutremove" ''value="<<" size="10" />')def__init__(self,*args,**kwargs):super(InOutWidget,self).__init__(*args,**kwargs)self._multiple=Truedefrender_select(self,form,field,name,selected=False):values,attrs=self.values_and_attributes(form,field)options=[]inputs=[]foroptioninfield.vocabulary(form):try:label,value,_oattrs=optionexceptValueError:label,value=optionifselected:# add valuesifvalueinvalues:options.append(tags.option(label,value=value))# add hidden inputsinputs.append(tags.input(value=value,name=field.dom_id(form),type="hidden"))else:options.append(tags.option(label,value=value))if'size'notinattrs:attrs['size']=self.default_sizeif'id'inattrs:attrs.pop('id')returntags.select(name=name,multiple=self._multiple,id=name,options=options,**attrs)+'\n'.join(inputs)def_render(self,form,field,renderer):domid=field.dom_id(form)jsnodes={'widgetid':domid,'from':'from_'+domid,'to':'to_'+domid}form._cw.add_onload(u'$(cw.jqNode("%s")).cwinoutwidget("%s", "%s");'%(jsnodes['widgetid'],jsnodes['from'],jsnodes['to']))field.required=Truereturn(self.template%{'widgetid':jsnodes['widgetid'],# helpinfo select tag'inoutinput':self.render_select(form,field,jsnodes['from']),# select tag with resultats'resinput':self.render_select(form,field,jsnodes['to'],selected=True),'addinput':self.add_button%jsnodes,'removeinput':self.remove_button%jsnodes})classBitSelect(Select):"""Select widget for IntField using a vocabulary with bit masks as values. See also :class:`~cubicweb.web.facet.BitFieldFacet`. """def__init__(self,attrs=None,multiple=True,**kwargs):super(BitSelect,self).__init__(attrs,multiple=multiple,**kwargs)defvalue_selected(self,value,curvalues):mask=reduce(lambdax,y:int(x)|int(y),curvalues,0)returnint(value)&maskdefprocess_field_data(self,form,field):"""Return process posted value(s) for widget and return something understandable by the associated `field`. That value may be correctly typed or a string that the field may parse. """val=super(BitSelect,self).process_field_data(form,field)ifisinstance(val,list):val=reduce(lambdax,y:int(x)|int(y),val,0)elifval:val=int(val)else:val=0returnvalclassCheckBox(Input):"""Simple <input type='checkbox'>, for field having a specific vocabulary. One input will be generated for each possible value. You can specify separator using the `separator` constructor argument, by default <br/> is used. """type='checkbox'default_separator=u'<br/>\n'vocabulary_widget=Truedef__init__(self,attrs=None,separator=None,**kwargs):super(CheckBox,self).__init__(attrs,**kwargs)self.separator=separatororself.default_separatordef_render(self,form,field,renderer):curvalues,attrs=self.values_and_attributes(form,field)domid=attrs.pop('id',None)# XXX turn this as initializer argumenttry:sep=attrs.pop('separator')warn('[3.8] separator should be specified using initializer argument',DeprecationWarning)exceptKeyError:sep=self.separatoroptions=[]fori,optioninenumerate(field.vocabulary(form)):try:label,value,oattrs=optionexceptValueError:label,value=optionoattrs={}iattrs=attrs.copy()iattrs.update(oattrs)ifi==0anddomidisnotNone:iattrs.setdefault('id',domid)ifvalueincurvalues:iattrs['checked']=u'checked'tag=tags.input(name=field.input_name(form,self.suffix),type=self.type,value=value,**iattrs)options.append(u'%s %s'%(tag,label))returnsep.join(options)classRadio(CheckBox):"""Simle <input type='radio'>, for field having a specific vocabulary. One input will be generated for each possible value. You can specify separator using the `separator` constructor argument, by default <br/> is used. """type='radio'# javascript widgets ###########################################################classDateTimePicker(TextInput):"""<input type='text'> + javascript date/time picker for date or datetime fields. Will return the date or datetime as an unicode string. """monthnames=('january','february','march','april','may','june','july','august','september','october','november','december')daynames=('monday','tuesday','wednesday','thursday','friday','saturday','sunday')needs_js=('cubicweb.calendar.js',)needs_css=('cubicweb.calendar_popup.css',)@classmethoddefadd_localized_infos(cls,req):"""inserts JS variables defining localized months and days"""_=req._monthnames=[_(mname)formnameincls.monthnames]daynames=[_(dname)fordnameincls.daynames]req.html_headers.define_var('MONTHNAMES',monthnames)req.html_headers.define_var('DAYNAMES',daynames)def_render(self,form,field,renderer):txtwidget=super(DateTimePicker,self)._render(form,field,renderer)self.add_localized_infos(form._cw)cal_button=self._render_calendar_popup(form,field)returntxtwidget+cal_buttondef_render_calendar_popup(self,form,field):value=field.typed_value(form)ifnotvalue:value=date.today()inputid=field.dom_id(form)helperid='%shelper'%inputidyear,month=value.year,value.monthreturn(u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper"><img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""%(helperid,inputid,year,month,form._cw.uiprops['CALENDAR_ICON'],form._cw._('calendar'),helperid))classJQueryDatePicker(FieldWidget):"""Use jquery.ui.datepicker to define a date picker. Will return the date as an unicode string. """needs_js=('jquery.ui.js',)needs_css=('jquery.ui.css',)default_size=10def__init__(self,datestr=None,**kwargs):super(JQueryDatePicker,self).__init__(**kwargs)self.value=datestrdef_render(self,form,field,renderer):req=form._cwifreq.lang!='en':req.add_js('jquery.ui.datepicker-%s.js'%req.lang)domid=field.dom_id(form,self.suffix)# XXX find a way to understand every formatfmt=req.property_value('ui.date-format')fmt=fmt.replace('%Y','yy').replace('%m','mm').replace('%d','dd')req.add_onload(u'cw.jqNode("%s").datepicker(''{buttonImage: "%s", dateFormat: "%s", firstDay: 1,'' showOn: "button", buttonImageOnly: true})'%(domid,req.uiprops['CALENDAR_ICON'],fmt))returnself._render_input(form,field)def_render_input(self,form,field):ifself.valueisNone:value=self.values(form,field)[0]else:value=self.valueattrs=self.attributes(form,field)attrs.setdefault('size',unicode(self.default_size))returntags.input(name=field.input_name(form,self.suffix),value=value,type='text',**attrs)classJQueryTimePicker(JQueryDatePicker):"""Use jquery.timePicker to define a time picker. Will return the time as an unicode string. """needs_js=('jquery.timePicker.js',)needs_css=('jquery.timepicker.css',)default_size=5def__init__(self,timestr=None,timesteps=30,separator=u':',**kwargs):super(JQueryTimePicker,self).__init__(timestr,**kwargs)self.timesteps=timestepsself.separator=separatordef_render(self,form,field,renderer):domid=field.dom_id(form,self.suffix)form._cw.add_onload(u'cw.jqNode("%s").timePicker({step: %s, separator: "%s"})'%(domid,self.timesteps,self.separator))returnself._render_input(form,field)classJQueryDateTimePicker(FieldWidget):"""Compound widget using :class:`JQueryDatePicker` and :class:`JQueryTimePicker` widgets to define a date and time picker. Will return the date and time as python datetime instance. """def__init__(self,initialtime=None,timesteps=15,**kwargs):super(JQueryDateTimePicker,self).__init__(**kwargs)self.initialtime=initialtimeself.timesteps=timestepsdef_render(self,form,field,renderer):"""render the widget for the given `field` of `form`. Generate one <input> tag for each field's value """req=form._cwdateqname=field.input_name(form,'date')timeqname=field.input_name(form,'time')ifdateqnameinform.form_previous_values:datestr=form.form_previous_values[dateqname]timestr=form.form_previous_values[timeqname]else:datestr=timestr=u''iffield.nameinreq.form:value=req.parse_datetime(req.form[field.name])else:value=self.typed_value(form,field)ifvalue:datestr=req.format_date(value)timestr=req.format_time(value)elifself.initialtime:timestr=req.format_time(self.initialtime)datepicker=JQueryDatePicker(datestr=datestr,suffix='date')timepicker=JQueryTimePicker(timestr=timestr,timesteps=self.timesteps,suffix='time')returnu'<div id="%s">%s%s</div>'%(field.dom_id(form),datepicker.render(form,field,renderer),timepicker.render(form,field,renderer))defprocess_field_data(self,form,field):req=form._cwdatestr=req.form.get(field.input_name(form,'date')).strip()orNonetimestr=req.form.get(field.input_name(form,'time')).strip()orNoneifdatestrisNone:returnNonetry:date=todatetime(req.parse_datetime(datestr,'Date'))exceptValueErrorasexc:raiseProcessFormError(unicode(exc))iftimestrisNone:returndatetry:time=req.parse_datetime(timestr,'Time')exceptValueErrorasexc:raiseProcessFormError(unicode(exc))returndate.replace(hour=time.hour,minute=time.minute,second=time.second)# ajax widgets ################################################################definit_ajax_attributes(attrs,wdgtype,loadtype=u'auto'):try:attrs['class']+=u' widget'exceptKeyError:attrs['class']=u'widget'attrs.setdefault('cubicweb:wdgtype',wdgtype)attrs.setdefault('cubicweb:loadtype',loadtype)classAjaxWidget(FieldWidget):"""Simple <div> based ajax widget, requiring a `wdgtype` argument telling which javascript widget should be used. """def__init__(self,wdgtype,inputid=None,**kwargs):super(AjaxWidget,self).__init__(**kwargs)init_ajax_attributes(self.attrs,wdgtype)ifinputidisnotNone:self.attrs['cubicweb:inputid']=inputiddef_render(self,form,field,renderer):attrs=self.values_and_attributes(form,field)[-1]returntags.div(**attrs)classAutoCompletionWidget(TextInput):"""<input type='text'> based ajax widget, taking a `autocomplete_initfunc` argument which should specify the name of a method of the json controller. This method is expected to return allowed values for the input, that the widget will use to propose matching values as you type. """needs_js=('cubicweb.widgets.js','jquery.ui.js')needs_css=('jquery.ui.css',)default_settings={}def__init__(self,*args,**kwargs):self.autocomplete_settings=kwargs.pop('autocomplete_settings',self.default_settings)self.autocomplete_initfunc=kwargs.pop('autocomplete_initfunc')super(AutoCompletionWidget,self).__init__(*args,**kwargs)defvalues(self,form,field):values=super(AutoCompletionWidget,self).values(form,field)ifnotvalues:values=('',)returnvaluesdef_render(self,form,field,renderer):entity=form.edited_entitydomid=field.dom_id(form).replace(':',r'\\:')ifcallable(self.autocomplete_initfunc):data=self.autocomplete_initfunc(form,field)else:data=xml_escape(self._get_url(entity,field))form._cw.add_onload(u'$("#%s").cwautocomplete(%s, %s);'%(domid,json_dumps(data),json_dumps(self.autocomplete_settings)))returnsuper(AutoCompletionWidget,self)._render(form,field,renderer)def_get_url(self,entity,field):fname=self.autocomplete_initfuncreturnentity._cw.build_url('ajax',fname=fname,mode='remote',pageid=entity._cw.pageid)classStaticFileAutoCompletionWidget(AutoCompletionWidget):"""XXX describe me"""wdgtype='StaticFileSuggestField'def_get_url(self,entity,field):returnentity._cw.data_url(self.autocomplete_initfunc)classRestrictedAutoCompletionWidget(AutoCompletionWidget):"""XXX describe me"""default_settings={'mustMatch':True}classLazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget):"""remote autocomplete """defvalues_and_attributes(self,form,field):"""override values_and_attributes to handle initial displayed values"""values,attrs=super(LazyRestrictedAutoCompletionWidget,self).values_and_attributes(form,field)assertlen(values)==1,"multiple selection is not supported yet by LazyWidget"ifnotvalues[0]:values=form.cw_extra_kwargs.get(field.name,'')ifnotisinstance(values,(tuple,list)):values=(values,)try:values=list(values)values[0]=int(values[0])attrs['cubicweb:initialvalue']=values[0]values=(self.display_value_for(form,values[0]),)except(TypeError,ValueError):passreturnvalues,attrsdefdisplay_value_for(self,form,value):entity=form._cw.entity_from_eid(value)returnentity.view('combobox')# more widgets #################################################################classIntervalWidget(FieldWidget):"""Custom widget to display an interval composed by 2 fields. This widget is expected to be used with a :class:`CompoundField` containing the two actual fields. Exemple usage:: class MyForm(FieldsForm): price = CompoundField(fields=(IntField(name='minprice'), IntField(name='maxprice')), label=_('price'), widget=IntervalWidget()) """def_render(self,form,field,renderer):actual_fields=field.fieldsassertlen(actual_fields)==2returnu'<div>%s%s%s%s</div>'%(form._cw._('from_interval_start'),actual_fields[0].render(form,renderer),form._cw._('to_interval_end'),actual_fields[1].render(form,renderer),)classHorizontalLayoutWidget(FieldWidget):"""Custom widget to display a set of fields grouped together horizontally in a form. See `IntervalWidget` for example usage. """def_render(self,form,field,renderer):ifself.attrs.get('display_label',True):subst=self.attrs.get('label_input_substitution','%(label)s%(input)s')fields=[subst%{'label':renderer.render_label(form,f),'input':f.render(form,renderer)}forfinfield.subfields(form)]else:fields=[f.render(form,renderer)forfinfield.subfields(form)]returnu'<div>%s</div>'%' '.join(fields)classEditableURLWidget(FieldWidget):"""Custom widget to edit separatly an url path / query string (used by default for the `path` attribute of `Bookmark` entities). It deals with url quoting nicely so that the user edit the unquoted value. """def_render(self,form,field,renderer):assertself.suffixisNone,'not supported'req=form._cwpathqname=field.input_name(form,'path')fqsqname=field.input_name(form,'fqs')# formatted query stringifpathqnameinform.form_previous_values:path=form.form_previous_values[pathqname]fqs=form.form_previous_values[fqsqname]else:iffield.nameinreq.form:value=req.form[field.name]else:value=self.typed_value(form,field)ifvalue:try:path,qs=value.split('?',1)exceptValueError:path=valueqs=''else:path=qs=''fqs=u'\n'.join(u'%s=%s'%(k,v)fork,vinreq.url_parse_qsl(qs))attrs=dict(self.attrs)ifself.setdomid:attrs['id']=field.dom_id(form)ifself.settabindexandnot'tabindex'inattrs:attrs['tabindex']=req.next_tabindex()# ensure something is renderedinputs=[u'<table><tr><th>',req._('i18n_bookmark_url_path'),u'</th><td>',tags.input(name=pathqname,type='string',value=path,**attrs),u'</td></tr><tr><th>',req._('i18n_bookmark_url_fqs'),u'</th><td>']ifself.setdomid:attrs['id']=field.dom_id(form,'fqs')ifself.settabindex:attrs['tabindex']=req.next_tabindex()attrs.setdefault('cols',60)attrs.setdefault('onkeyup','autogrow(this)')inputs+=[tags.textarea(fqs,name=fqsqname,**attrs),u'</td></tr></table>']# surrounding div necessary for proper error localizationreturnu'<div id="%s">%s</div>'%(field.dom_id(form),u'\n'.join(inputs))defprocess_field_data(self,form,field):req=form._cwvalues={}path=req.form.get(field.input_name(form,'path'))ifisinstance(path,basestring):path=path.strip()ifpathisNone:path=u''fqs=req.form.get(field.input_name(form,'fqs'))ifisinstance(fqs,basestring):fqs=fqs.strip()orNoneiffqs:fori,lineinenumerate(fqs.split('\n')):line=line.strip()ifline:try:key,val=line.split('=',1)exceptValueError:raiseProcessFormError(req._("wrong query parameter line %s")%(i+1))# value will be url quoted by build_url_paramsvalues.setdefault(key.encode(req.encoding),[]).append(val)ifnotvalues:returnpathreturnu'%s?%s'%(path,req.build_url_params(**values))# form controls ######################################################################classButton(Input):"""Simple <input type='button'>, base class for global form buttons. Note that `label` is a msgid which will be translated at form generation time, you should not give an already translated string. """type='button'css_class='validateButton'def__init__(self,label=stdmsgs.BUTTON_OK,attrs=None,setdomid=None,settabindex=None,name='',value='',onclick=None,cwaction=None):super(Button,self).__init__(attrs,setdomid,settabindex)ifisinstance(label,tuple):self.label=label[0]self.icon=label[1]else:self.label=labelself.icon=Noneself.name=nameself.value=''self.onclick=onclickself.cwaction=cwactionself.attrs.setdefault('class',self.css_class)defrender(self,form,field=None,renderer=None):label=form._cw._(self.label)attrs=self.attrs.copy()ifself.cwaction:assertself.onclickisNoneattrs['onclick']="postForm('__action_%s', \'%s\', \'%s\')"%(self.cwaction,self.label,form.domid)elifself.onclick:attrs['onclick']=self.onclickifself.name:attrs['name']=self.nameifself.setdomid:attrs['id']=self.nameifself.settabindexandnot'tabindex'inattrs:attrs['tabindex']=form._cw.next_tabindex()ifself.icon:img=tags.img(src=form._cw.uiprops[self.icon],alt=self.icon)else:img=u''returntags.button(img+xml_escape(label),escapecontent=False,value=label,type=self.type,**attrs)classSubmitButton(Button):"""Simple <input type='submit'>, main button to submit a form"""type='submit'classResetButton(Button):"""Simple <input type='reset'>, main button to reset a form. You usually don't want to use this. """type='reset'classImgButton(object):"""Simple <img> wrapped into a <a> tag with href triggering something (usually a javascript call). """def__init__(self,domid,href,label,imgressource):self.domid=domidself.href=hrefself.imgressource=imgressourceself.label=labeldefrender(self,form,field=None,renderer=None):label=form._cw._(self.label)imgsrc=form._cw.uiprops[self.imgressource]return'<a id="%(domid)s" href="%(href)s">'\'<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>'%{'label':label,'imgsrc':imgsrc,'domid':self.domid,'href':self.href}