web/formwidgets.py
branchtls-sprint
changeset 1330 92343a468e2a
parent 1329 9c7cc717bb17
child 1336 2e552353c42a
equal deleted inserted replaced
1329:9c7cc717bb17 1330:92343a468e2a
     9 from datetime import date
     9 from datetime import date
    10 
    10 
    11 from cubicweb.common import tags
    11 from cubicweb.common import tags
    12 from cubicweb.web import stdmsgs
    12 from cubicweb.web import stdmsgs
    13 
    13 
       
    14 
    14 class FieldWidget(object):
    15 class FieldWidget(object):
       
    16     """abstract widget class"""
       
    17     # javascript / css files required by the widget
    15     needs_js = ()
    18     needs_js = ()
    16     needs_css = ()
    19     needs_css = ()
       
    20     # automatically set id and tabindex attributes ?
    17     setdomid = True
    21     setdomid = True
    18     settabindex = True
    22     settabindex = True
    19     
    23     
    20     def __init__(self, attrs=None, setdomid=None, settabindex=None):
    24     def __init__(self, attrs=None, setdomid=None, settabindex=None):
    21         self.attrs = attrs or {}
    25         if attrs is None:
       
    26             attrs = {}
       
    27         self.attrs = attrs
    22         if setdomid is not None:
    28         if setdomid is not None:
    23             # override class's default value
    29             # override class's default value
    24             self.setdomid = setdomid
    30             self.setdomid = setdomid
    25         if settabindex is not None:
    31         if settabindex is not None:
    26             # override class's default value
    32             # override class's default value
    32             form.req.add_js(self.needs_js)
    38             form.req.add_js(self.needs_js)
    33         if self.needs_css:
    39         if self.needs_css:
    34             form.req.add_css(self.needs_css)
    40             form.req.add_css(self.needs_css)
    35         
    41         
    36     def render(self, form, field):
    42     def render(self, form, field):
       
    43         """render the widget for the given `field` of `form`.
       
    44         To override in concrete class
       
    45         """
    37         raise NotImplementedError
    46         raise NotImplementedError
    38 
    47 
    39     def _render_attrs(self, form, field):
    48     def _render_attrs(self, form, field):
       
    49         """return html tag name, attributes and a list of values for the field
       
    50         """
    40         name = form.context[field]['name']
    51         name = form.context[field]['name']
    41         values = form.context[field]['value']
    52         values = form.context[field]['value']
    42         if not isinstance(values, (tuple, list)):
    53         if not isinstance(values, (tuple, list)):
    43             values = (values,)
    54             values = (values,)
    44         attrs = dict(self.attrs)
    55         attrs = dict(self.attrs)
    48             attrs['tabindex'] = form.req.next_tabindex()
    59             attrs['tabindex'] = form.req.next_tabindex()
    49         return name, values, attrs
    60         return name, values, attrs
    50 
    61 
    51 
    62 
    52 class Input(FieldWidget):
    63 class Input(FieldWidget):
       
    64     """abstract widget class for <input> tag based widgets"""
    53     type = None
    65     type = None
    54     
    66     
    55     def render(self, form, field):
    67     def render(self, form, field):
       
    68         """render the widget for the given `field` of `form`.
       
    69         
       
    70         Generate one <input> tag for each field's value
       
    71         """
    56         self.add_media(form)
    72         self.add_media(form)
    57         name, values, attrs = self._render_attrs(form, field)
    73         name, values, attrs = self._render_attrs(form, field)
    58         inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
    74         inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
    59                   for value in values]
    75                   for value in values]
    60         return u'\n'.join(inputs)
    76         return u'\n'.join(inputs)
    61 
    77 
    62 
    78 
       
    79 # basic html widgets ###########################################################
       
    80 
    63 class TextInput(Input):
    81 class TextInput(Input):
       
    82     """<input type='text'>"""
    64     type = 'text'
    83     type = 'text'
    65 
    84 
    66 
    85 
    67 class PasswordInput(Input):
    86 class PasswordInput(Input):
       
    87     """<input type='password'> and its confirmation field (using
       
    88     <field's name>-confirm as name)
       
    89     """
    68     type = 'password'
    90     type = 'password'
    69     
    91     
    70     def render(self, form, field):
    92     def render(self, form, field):
    71         self.add_media(form)
    93         self.add_media(form)
    72         name, values, attrs = self._render_attrs(form, field)
    94         name, values, attrs = self._render_attrs(form, field)
    83                                       **{'class': 'emphasis'})]
   105                                       **{'class': 'emphasis'})]
    84         return u'\n'.join(inputs)
   106         return u'\n'.join(inputs)
    85 
   107 
    86 
   108 
    87 class FileInput(Input):
   109 class FileInput(Input):
       
   110     """<input type='file'>"""
    88     type = 'file'
   111     type = 'file'
    89     
   112     
    90     def _render_attrs(self, form, field):
   113     def _render_attrs(self, form, field):
    91         # ignore value which makes no sense here (XXX even on form validation error?)
   114         # ignore value which makes no sense here (XXX even on form validation error?)
    92         name, values, attrs = super(FileInput, self)._render_attrs(form, field)
   115         name, values, attrs = super(FileInput, self)._render_attrs(form, field)
    93         return name, ('',), attrs
   116         return name, ('',), attrs
    94 
   117 
    95         
   118         
    96 class HiddenInput(Input):
   119 class HiddenInput(Input):
       
   120     """<input type='hidden'>"""
    97     type = 'hidden'
   121     type = 'hidden'
    98     setdomid = False # by default, don't set id attribute on hidden input
   122     setdomid = False # by default, don't set id attribute on hidden input
    99     settabindex = False
   123     settabindex = False
   100 
   124 
   101     
   125     
   102 class ButtonInput(Input):
   126 class ButtonInput(Input):
       
   127     """<input type='button'>
       
   128 
       
   129     if you want a global form button, look at the Button, SubmitButton,
       
   130     ResetButton and ImgButton classes below.
       
   131     """
   103     type = 'button'
   132     type = 'button'
   104 
   133 
   105 
   134 
   106 class TextArea(FieldWidget):
   135 class TextArea(FieldWidget):
       
   136     """<textarea>"""
   107     def render(self, form, field):
   137     def render(self, form, field):
   108         name, values, attrs = self._render_attrs(form, field)
   138         name, values, attrs = self._render_attrs(form, field)
   109         attrs.setdefault('onkeypress', 'autogrow(this)')
   139         attrs.setdefault('onkeypress', 'autogrow(this)')
   110         if not values:
   140         if not values:
   111             value = u''
   141             value = u''
   115             raise ValueError('a textarea is not supposed to be multivalued')
   145             raise ValueError('a textarea is not supposed to be multivalued')
   116         return tags.textarea(value, name=name, **attrs)
   146         return tags.textarea(value, name=name, **attrs)
   117 
   147 
   118 
   148 
   119 class FCKEditor(TextArea):
   149 class FCKEditor(TextArea):
       
   150     """FCKEditor enabled <textarea>"""
   120     def __init__(self, *args, **kwargs):
   151     def __init__(self, *args, **kwargs):
   121         super(FCKEditor, self).__init__(*args, **kwargs)
   152         super(FCKEditor, self).__init__(*args, **kwargs)
   122         self.attrs['cubicweb:type'] = 'wysiwyg'
   153         self.attrs['cubicweb:type'] = 'wysiwyg'
   123     
   154     
   124     def render(self, form, field):
   155     def render(self, form, field):
   125         form.req.fckeditor_config()
   156         form.req.fckeditor_config()
   126         return super(FCKEditor, self).render(form, field)
   157         return super(FCKEditor, self).render(form, field)
   127 
   158 
   128 
   159 
   129 class Select(FieldWidget):
   160 class Select(FieldWidget):
       
   161     """<select>, for field having a specific vocabulary""" 
   130     def __init__(self, attrs=None, multiple=False):
   162     def __init__(self, attrs=None, multiple=False):
   131         super(Select, self).__init__(attrs)
   163         super(Select, self).__init__(attrs)
   132         self.multiple = multiple
   164         self.multiple = multiple
   133         
   165         
   134     def render(self, form, field):
   166     def render(self, form, field):
   142         return tags.select(name=name, multiple=self.multiple,
   174         return tags.select(name=name, multiple=self.multiple,
   143                            options=options, **attrs)
   175                            options=options, **attrs)
   144 
   176 
   145 
   177 
   146 class CheckBox(Input):
   178 class CheckBox(Input):
       
   179     """<input type='checkbox'>, for field having a specific vocabulary. One
       
   180     input will be generated for each possible value.
       
   181     """ 
   147     type = 'checkbox'
   182     type = 'checkbox'
   148     
   183     
   149     def render(self, form, field):
   184     def render(self, form, field):
   150         name, curvalues, attrs = self._render_attrs(form, field)
   185         name, curvalues, attrs = self._render_attrs(form, field)
   151         options = []
   186         options = []
   159             options.append(tag + label)
   194             options.append(tag + label)
   160         return '<br/>\n'.join(options)
   195         return '<br/>\n'.join(options)
   161 
   196 
   162         
   197         
   163 class Radio(Input):
   198 class Radio(Input):
       
   199     """<input type='radio'>, for field having a specific vocabulary. One
       
   200     input will be generated for each possible value.
       
   201     """ 
   164     type = 'radio'
   202     type = 'radio'
   165     setdomid = False
   203     setdomid = False # by default, don't set id attribute on radio input
   166     
   204     
   167     def render(self, form, field):
   205     def render(self, form, field):
   168         name, curvalues, attrs = self._render_attrs(form, field)
   206         name, curvalues, attrs = self._render_attrs(form, field)
   169         options = []
   207         options = []
   170         for label, value in field.vocabulary(form):
   208         for label, value in field.vocabulary(form):
   176             tag += label + '<br/>'
   214             tag += label + '<br/>'
   177             options.append(tag)
   215             options.append(tag)
   178         return '\n'.join(options)
   216         return '\n'.join(options)
   179 
   217 
   180 
   218 
       
   219 # javascript widgets ###########################################################
       
   220 
   181 class DateTimePicker(TextInput):
   221 class DateTimePicker(TextInput):
       
   222     """<input type='text' + javascript date/time picker for date or datetime
       
   223     fields
       
   224     """
   182     monthnames = ('january', 'february', 'march', 'april',
   225     monthnames = ('january', 'february', 'march', 'april',
   183                   'may', 'june', 'july', 'august',
   226                   'may', 'june', 'july', 'august',
   184                   'september', 'october', 'november', 'december')
   227                   'september', 'october', 'november', 'december')
   185     daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
   228     daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
   186                 'friday', 'saturday', 'sunday')
   229                 'friday', 'saturday', 'sunday')
   217                 % (helperid, inputid, year, month,
   260                 % (helperid, inputid, year, month,
   218                    req.external_resource('CALENDAR_ICON'),
   261                    req.external_resource('CALENDAR_ICON'),
   219                    req._('calendar'), helperid) )
   262                    req._('calendar'), helperid) )
   220 
   263 
   221 
   264 
       
   265 # ajax widgets ################################################################
       
   266 
   222 class AjaxWidget(FieldWidget):
   267 class AjaxWidget(FieldWidget):
       
   268     """simple <div> based ajax widget"""
   223     def __init__(self, wdgtype, inputid=None, **kwargs):
   269     def __init__(self, wdgtype, inputid=None, **kwargs):
   224         super(AjaxWidget, self).__init__(**kwargs)
   270         super(AjaxWidget, self).__init__(**kwargs)
   225         self.attrs.setdefault('class', 'widget')
   271         self.attrs.setdefault('class', 'widget')
   226         self.attrs.setdefault('cubicweb:loadtype', 'auto')
   272         self.attrs.setdefault('cubicweb:loadtype', 'auto')
   227         self.attrs['cubicweb:wdgtype'] = wdgtype
   273         self.attrs['cubicweb:wdgtype'] = wdgtype
   231     def render(self, form, field):
   277     def render(self, form, field):
   232         self.add_media(form)
   278         self.add_media(form)
   233         attrs = self._render_attrs(form, field)[-1]
   279         attrs = self._render_attrs(form, field)[-1]
   234         return tags.div(**attrs)
   280         return tags.div(**attrs)
   235 
   281 
       
   282         
       
   283 class AutoCompletionWidget(TextInput):
       
   284     """ajax widget for StringField, proposing matching existing values as you
       
   285     type.
       
   286     """
       
   287     needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
       
   288     needs_css = ('jquery.autocomplete.css',)
       
   289     wdgtype = 'SuggestField'
       
   290     loadtype = 'auto'
       
   291     
       
   292     def _render_attrs(self, form, field):
       
   293         name, values, attrs = super(AutoCompletionWidget, self)._render_attrs(form, field)
       
   294         if not values[0]:
       
   295             values = (INTERNAL_FIELD_VALUE,)
       
   296         try:
       
   297             attrs['klass'] += ' widget'
       
   298         except KeyError:
       
   299             attrs['klass'] = 'widget'
       
   300         attrs.setdefault('cubicweb:wdgtype', self.wdgtype)
       
   301         attrs.setdefault('cubicweb:loadtype', self.loadtype)
       
   302         # XXX entity form specific
       
   303         attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity)
       
   304         return name, values, attrs
       
   305     
       
   306     def _get_url(self, entity):
       
   307         return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
       
   308                                 pageid=entity.req.pageid, mode='remote')
       
   309 
       
   310 
       
   311 class StaticFileAutoCompletionWidget(AutoCompletionWidget):
       
   312     """XXX describe me"""
       
   313     wdgtype = 'StaticFileSuggestField'
       
   314     
       
   315     def _get_url(self, entity):
       
   316         return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
       
   317 
       
   318 
       
   319 class RestrictedAutoCompletionWidget(AutoCompletionWidget):
       
   320     """XXX describe me"""
       
   321     wdgtype = 'RestrictedSuggestField'    
       
   322 
       
   323 
       
   324 # buttons ######################################################################
   236 
   325 
   237 class Button(Input):
   326 class Button(Input):
       
   327     """<input type='button'>, base class for global form buttons"""
   238     type = 'button'
   328     type = 'button'
   239     def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
   329     def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
   240                  setdomid=None, settabindex=None,
   330                  setdomid=None, settabindex=None,
   241                  name='', value='', onclick=None, cwaction=None):
   331                  name='', value='', onclick=None, cwaction=None):
   242         super(Button, self).__init__(attrs, setdomid, settabindex)
   332         super(Button, self).__init__(attrs, setdomid, settabindex)
   264             attrs['tabindex'] = form.req.next_tabindex()
   354             attrs['tabindex'] = form.req.next_tabindex()
   265         return tags.input(value=label, type=self.type, **attrs)
   355         return tags.input(value=label, type=self.type, **attrs)
   266 
   356 
   267     
   357     
   268 class SubmitButton(Button):
   358 class SubmitButton(Button):
       
   359     """<input type='submit'>, main button to submit a form"""
   269     type = 'submit'
   360     type = 'submit'
       
   361 
   270     
   362     
   271 class ResetButton(Button):
   363 class ResetButton(Button):
       
   364     """<input type='reset'>, main button to reset a form.
       
   365     You usually don't want this.
       
   366     """
   272     type = 'reset'
   367     type = 'reset'
   273 
   368 
       
   369 
   274 class ImgButton(object):
   370 class ImgButton(object):
       
   371     """<img> wrapped into a <a> tag with href triggering something (usually a
       
   372     javascript call)
       
   373     """
   275     def __init__(self, domid, href, label, imgressource):
   374     def __init__(self, domid, href, label, imgressource):
   276         self.domid = domid
   375         self.domid = domid
   277         self.href = href
   376         self.href = href
   278         self.imgressource = imgressource
   377         self.imgressource = imgressource
   279         self.label = label
   378         self.label = label
   281     def render(self, form, field=None):
   380     def render(self, form, field=None):
   282         self.imgsrc = form.req.external_resource(self.imgressource)
   381         self.imgsrc = form.req.external_resource(self.imgressource)
   283         return '<a id="%(domid)s" href="%(href)s"><img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % self.__dict__
   382         return '<a id="%(domid)s" href="%(href)s"><img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % self.__dict__
   284 
   383 
   285     
   384     
   286 # XXX EntityLinkComboBoxWidget, AddComboBoxWidget, AutoCompletionWidget,
   385 # XXX EntityLinkComboBoxWidget, AddComboBoxWidget, RawDynamicComboBoxWidget
   287 #     StaticFileAutoCompletionWidget, RestrictedAutoCompletionWidget...