"""widget classes for form construction:organization: Logilab:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"fromdatetimeimportdatefromwarningsimportwarnfromcubicweb.commonimporttagsfromcubicweb.webimportstdmsgs,INTERNAL_FIELD_VALUEclassFieldWidget(object):"""abstract widget class"""# javascript / css files required by the widgetneeds_js=()needs_css=()# automatically set id and tabindex attributes ?setdomid=Truesettabindex=Truedef__init__(self,attrs=None,setdomid=None,settabindex=None):ifattrsisNone:attrs={}self.attrs=attrsifsetdomidisnotNone:# override class's default valueself.setdomid=setdomidifsettabindexisnotNone:# override class's default valueself.settabindex=settabindexdefadd_media(self,form):"""adds media (CSS & JS) required by this widget"""ifself.needs_js:form.req.add_js(self.needs_js)ifself.needs_css:form.req.add_css(self.needs_css)defrender(self,form,field):"""render the widget for the given `field` of `form`. To override in concrete class """raiseNotImplementedErrordef_render_attrs(self,form,field):"""return html tag name, attributes and a list of values for the field """name=form.context[field]['name']values=form.context[field]['value']ifnotisinstance(values,(tuple,list)):values=(values,)attrs=dict(self.attrs)ifself.setdomid:attrs['id']=form.context[field]['id']ifself.settabindexandnot'tabindex'inattrs:attrs['tabindex']=form.req.next_tabindex()returnname,values,attrsclassInput(FieldWidget):"""abstract widget class for <input> tag based widgets"""type=Nonedefrender(self,form,field):"""render the widget for the given `field` of `form`. Generate one <input> tag for each field's value """self.add_media(form)name,values,attrs=self._render_attrs(form,field)# ensure something is renderedifnotvalues:values=(INTERNAL_FIELD_VALUE,)inputs=[tags.input(name=name,value=value,type=self.type,**attrs)forvalueinvalues]returnu'\n'.join(inputs)# basic html widgets ###########################################################classTextInput(Input):"""<input type='text'>"""type='text'classPasswordInput(Input):"""<input type='password'> and its confirmation field (using <field's name>-confirm as name) """type='password'defrender(self,form,field):self.add_media(form)name,values,attrs=self._render_attrs(form,field)assertlen(values)==1id=attrs.pop('id')try:confirmname='%s-confirm:%s'%tuple(name.rsplit(':',1))exceptTypeError:confirmname='%s-confirm'%nameinputs=[tags.input(name=name,value=values[0],type=self.type,id=id,**attrs),'<br/>',tags.input(name=confirmname,value=values[0],type=self.type,**attrs),' ',tags.span(form.req._('confirm password'),**{'class':'emphasis'})]returnu'\n'.join(inputs)classPasswordSingleInput(Input):"""<input type='password'> without a confirmation field"""type='password'classFileInput(Input):"""<input type='file'>"""type='file'def_render_attrs(self,form,field):# ignore value which makes no sense here (XXX even on form validation error?)name,values,attrs=super(FileInput,self)._render_attrs(form,field)returnname,('',),attrsclassHiddenInput(Input):"""<input type='hidden'>"""type='hidden'setdomid=False# by default, don't set id attribute on hidden inputsettabindex=FalseclassButtonInput(Input):"""<input type='button'> if you want a global form button, look at the Button, SubmitButton, ResetButton and ImgButton classes below. """type='button'classTextArea(FieldWidget):"""<textarea>"""defrender(self,form,field):name,values,attrs=self._render_attrs(form,field)attrs.setdefault('onkeypress','autogrow(this)')attrs.setdefault('cols',80)attrs.setdefault('rows',20)ifnotvalues:value=u''eliflen(values)==1:value=values[0]else:raiseValueError('a textarea is not supposed to be multivalued')returntags.textarea(value,name=name,**attrs)classFCKEditor(TextArea):"""FCKEditor enabled <textarea>"""def__init__(self,*args,**kwargs):super(FCKEditor,self).__init__(*args,**kwargs)self.attrs['cubicweb:type']='wysiwyg'defrender(self,form,field):form.req.fckeditor_config()returnsuper(FCKEditor,self).render(form,field)classSelect(FieldWidget):"""<select>, for field having a specific vocabulary"""def__init__(self,attrs=None,multiple=False):super(Select,self).__init__(attrs)self._multiple=multipledefrender(self,form,field):name,curvalues,attrs=self._render_attrs(form,field)ifnot'size'inattrsandself._multiple:attrs['size']='5'options=[]optgroup_opened=Falseforlabel,valueinfield.vocabulary(form):ifvalueisNone:# handle separatorifoptgroup_opened:options.append(u'</optgroup>')options.append(u'<optgroup label="%s">'%(labelor''))optgroup_opened=Trueelifvalueincurvalues:options.append(tags.option(label,value=value,selected='selected'))else:options.append(tags.option(label,value=value))ifoptgroup_opened:options.append(u'</optgroup>')returntags.select(name=name,multiple=self._multiple,options=options,**attrs)classCheckBox(Input):"""<input type='checkbox'>, for field having a specific vocabulary. One input will be generated for each possible value. """type='checkbox'defrender(self,form,field):name,curvalues,attrs=self._render_attrs(form,field)domid=attrs.pop('id',None)sep=attrs.pop('separator',u'<br/>')options=[]fori,(label,value)inenumerate(field.vocabulary(form)):iattrs=attrs.copy()ifi==0anddomidisnotNone:iattrs['id']=domidifvalueincurvalues:iattrs['checked']=u'checked'tag=tags.input(name=name,type=self.type,value=value,**iattrs)options.append(tag+label+sep)return'\n'.join(options)classRadio(CheckBox):"""<input type='radio'>, for field having a specific vocabulary. One input will be generated for each possible value. """type='radio'# javascript widgets ###########################################################classDateTimePicker(TextInput):"""<input type='text' + javascript date/time picker for date or datetime fields """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"""# import here to avoid dependancy from cubicweb-common to simplejson_=req._monthnames=[_(mname)formnameincls.monthnames]daynames=[_(dname)fordnameincls.daynames]req.html_headers.define_var('MONTHNAMES',monthnames)req.html_headers.define_var('DAYNAMES',daynames)defrender(self,form,field):txtwidget=super(DateTimePicker,self).render(form,field)self.add_localized_infos(form.req)cal_button=self._render_calendar_popup(form,field)returntxtwidget+cal_buttondef_render_calendar_popup(self,form,field):value=form.form_field_value(field)ifnotvalue:value=date.today()inputid=form.context[field]['id']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.req.external_resource('CALENDAR_ICON'),form.req._('calendar'),helperid))# ajax widgets ################################################################definit_ajax_attributes(attrs,wdgtype,loadtype=u'auto'):try:attrs['klass']+=u' widget'exceptKeyError:attrs['klass']=u'widget'attrs.setdefault('cubicweb:wdgtype',wdgtype)attrs.setdefault('cubicweb:loadtype',loadtype)classAjaxWidget(FieldWidget):"""simple <div> based ajax widget"""def__init__(self,wdgtype,inputid=None,**kwargs):super(AjaxWidget,self).__init__(**kwargs)init_ajax_attributes(self.attrs,wdgtype)ifinputidisnotNone:self.attrs['cubicweb:inputid']=inputiddefrender(self,form,field):self.add_media(form)attrs=self._render_attrs(form,field)[-1]returntags.div(**attrs)classAutoCompletionWidget(TextInput):"""ajax widget for StringField, proposing matching existing values as you type. """needs_js=('cubicweb.widgets.js','jquery.autocomplete.js')needs_css=('jquery.autocomplete.css',)wdgtype='SuggestField'loadtype='auto'def__init__(self,*args,**kwargs):try:self.autocomplete_initfunc=kwargs.pop('autocomplete_initfunc')exceptKeyError:warn('use autocomplete_initfunc argument of %s constructor ''instead of relying on autocomplete_initfuncs dictionary on ''the entity class'%self.__class__.__name__,DeprecationWarning)self.autocomplete_initfunc=Nonesuper(AutoCompletionWidget,self).__init__(*args,**kwargs)def_render_attrs(self,form,field):name,values,attrs=super(AutoCompletionWidget,self)._render_attrs(form,field)init_ajax_attributes(attrs,self.wdgtype,self.loadtype)# XXX entity form specificattrs['cubicweb:dataurl']=self._get_url(form.edited_entity,field)returnname,values,attrsdef_get_url(self,entity,field):ifself.autocomplete_initfuncisNone:# XXX for bw compatfname=entity.autocomplete_initfuncs[field.name]else:fname=self.autocomplete_initfuncreturnentity.req.build_url('json',fname=fname,mode='remote',pageid=entity.req.pageid)classStaticFileAutoCompletionWidget(AutoCompletionWidget):"""XXX describe me"""wdgtype='StaticFileSuggestField'def_get_url(self,entity,field):ifself.autocomplete_initfuncisNone:# XXX for bw compatfname=entity.autocomplete_initfuncs[field.name]else:fname=self.autocomplete_initfuncreturnentity.req.datadir_url+fnameclassRestrictedAutoCompletionWidget(AutoCompletionWidget):"""XXX describe me"""wdgtype='RestrictedSuggestField'classAddComboBoxWidget(Select):def_render_attrs(self,form,field):name,values,attrs=super(AddComboBoxWidget,self)._render_attrs(form,field)init_ajax_attributes(self.attrs,'AddComboBox')# XXX entity form specificentity=form.edited_entityattrs['cubicweb:etype_to']=entity.e_schemaetype_from=entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]attrs['cubicweb:etype_from']=etype_fromdefrender(self,form,field):returnsuper(AddComboBoxWidget,self).render(form,field)+u'''<div id="newvalue"> <input type="text" id="newopt" /> <a href="javascript:noop()" id="add_newopt"> </a></div>'''# buttons ######################################################################classButton(Input):"""<input type='button'>, base class for global form buttons note label is a msgid which will be translated at form generation time, you should not give an already translated string. """type='button'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)self.label=labelself.name=nameself.value=''self.onclick=onclickself.cwaction=cwactionself.attrs.setdefault('klass','validateButton')defrender(self,form,field=None):label=form.req._(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']=nameifself.setdomid:attrs['id']=self.nameifself.settabindexandnot'tabindex'inattrs:attrs['tabindex']=form.req.next_tabindex()returntags.input(value=label,type=self.type,**attrs)classSubmitButton(Button):"""<input type='submit'>, main button to submit a form"""type='submit'classResetButton(Button):"""<input type='reset'>, main button to reset a form. You usually don't want this. """type='reset'classImgButton(object):"""<img> wrapped into a <a> tag with href triggering something (usually a javascript call) note label is a msgid which will be translated at form generation time, you should not give an already translated string. """def__init__(self,domid,href,label,imgressource):self.domid=domidself.href=hrefself.imgressource=imgressourceself.label=labeldefrender(self,form,field=None):label=form.req._(self.label)imgsrc=form.req.external_resource(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}# XXX EntityLinkComboBoxWidget, [Raw]DynamicComboBoxWidget