[javascript] make inlinedform-added event bindable
$(form).trigger('inlinedform-added') was not usable since form
had no existence outside the inner callback.
Use the general CubicWeb object to trigger the event so it's easy
to bind a callback wherever it's needed.
"""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.commonimporttags,uilibfromcubicweb.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=True# does this widget expect a vocabularyvocabulary_widget=Falsedef__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,renderer):"""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,renderer):"""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,renderer):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,renderer):name,values,attrs=self._render_attrs(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=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,renderer):form.req.fckeditor_config()returnsuper(FCKEditor,self).render(form,field,renderer)classSelect(FieldWidget):"""<select>, for field having a specific vocabulary"""vocabulary_widget=Truedef__init__(self,attrs=None,multiple=False):super(Select,self).__init__(attrs)self._multiple=multipledefrender(self,form,field,renderer):name,curvalues,attrs=self._render_attrs(form,field)ifnot'size'inattrs:attrs['size']=self._multipleand'5'or'1'options=[]optgroup_opened=Falseforoptioninfield.vocabulary(form):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=Trueelifvalueincurvalues: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>')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'vocabulary_widget=Truedefrender(self,form,field,renderer):name,curvalues,attrs=self._render_attrs(form,field)domid=attrs.pop('id',None)sep=attrs.pop('separator',u'<br/>\n')options=[]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=name,type=self.type,value=value,**iattrs)options.append(tag+label)returnsep.join(options)classRadio(CheckBox):"""<input type='radio'>, for field having a specific vocabulary. One input will be generated for each possible value. """type='radio'# compound widgets #############################################################classIntervalWidget(FieldWidget):"""custom widget to display an interval composed by 2 fields. This widget is expected to be used with a CompoundField containing the two actual fields. Exemple usage::from uicfg import autoform_field, autoform_sectionautoform_field.tag_attribute(('Concert', 'minprice'), CompoundField(fields=(IntField(name='minprice'), IntField(name='maxprice')), label=_('price'), widget=IntervalWidget() ))# we've to hide the other field manually for nowautoform_section.tag_attribute(('Concert', 'maxprice'), 'generated') """defrender(self,form,field,renderer):actual_fields=field.fieldsassertlen(actual_fields)==2returnu'<div>%s%s%s%s</div>'%(form.req._('from_interval_start'),actual_fields[0].render(form,renderer),form.req._('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. """defrender(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)# 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,renderer):txtwidget=super(DateTimePicker,self).render(form,field,renderer)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,renderer):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(field.name).objects(entity.e_schema)[0]attrs['cubicweb:etype_from']=etype_fromreturnname,values,attrsdefrender(self,form,field,renderer):returnsuper(AddComboBoxWidget,self).render(form,field,renderer)+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,renderer=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,renderer=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