"""widget classes for form construction
:organization: Logilab
:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from datetime import date
from cubicweb.common import tags
from cubicweb.web import stdmsgs
class FieldWidget(object):
needs_js = ()
needs_css = ()
setdomid = True
settabindex = True
def __init__(self, attrs=None, setdomid=None, settabindex=None):
self.attrs = attrs or {}
if setdomid is not None:
# override class's default value
self.setdomid = setdomid
if settabindex is not None:
# override class's default value
self.settabindex = settabindex
def add_media(self, form):
"""adds media (CSS & JS) required by this widget"""
if self.needs_js:
form.req.add_js(self.needs_js)
if self.needs_css:
form.req.add_css(self.needs_css)
def render(self, form, field):
raise NotImplementedError
def _render_attrs(self, form, field):
name = form.context[field]['name']
values = form.context[field]['value']
if not isinstance(values, (tuple, list)):
values = (values,)
attrs = dict(self.attrs)
if self.setdomid:
attrs['id'] = form.context[field]['id']
if self.settabindex and not 'tabindex' in attrs:
attrs['tabindex'] = form.req.next_tabindex()
return name, values, attrs
class Input(FieldWidget):
type = None
def render(self, form, field):
self.add_media(form)
name, values, attrs = self._render_attrs(form, field)
inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
for value in values]
return u'\n'.join(inputs)
class TextInput(Input):
type = 'text'
class PasswordInput(Input):
type = 'password'
def render(self, form, field):
self.add_media(form)
name, values, attrs = self._render_attrs(form, field)
assert len(values) == 1
id = attrs.pop('id')
confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
inputs = [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'})]
return u'\n'.join(inputs)
class FileInput(Input):
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)
return name, ('',), attrs
class HiddenInput(Input):
type = 'hidden'
setdomid = False # by default, don't set id attribute on hidden input
settabindex = False
class ButtonInput(Input):
type = 'button'
class TextArea(FieldWidget):
def render(self, form, field):
name, values, attrs = self._render_attrs(form, field)
attrs.setdefault('onkeypress', 'autogrow(this)')
if not values:
value = u''
elif len(values) == 1:
value = values[0]
else:
raise ValueError('a textarea is not supposed to be multivalued')
return tags.textarea(value, name=name, **attrs)
class FCKEditor(TextArea):
def __init__(self, *args, **kwargs):
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
def render(self, form, field):
form.req.fckeditor_config()
return super(FCKEditor, self).render(form, field)
class Select(FieldWidget):
def __init__(self, attrs=None, multiple=False):
super(Select, self).__init__(attrs)
self.multiple = multiple
def render(self, form, field):
name, curvalues, attrs = self._render_attrs(form, field)
options = []
for label, value in field.vocabulary(form):
if value in curvalues:
options.append(tags.option(label, value=value, selected='selected'))
else:
options.append(tags.option(label, value=value))
return tags.select(name=name, multiple=self.multiple,
options=options, **attrs)
class CheckBox(Input):
type = 'checkbox'
def render(self, form, field):
name, curvalues, attrs = self._render_attrs(form, field)
options = []
for label, value in field.vocabulary(form):
if value in curvalues:
tag = tags.input(name=name, value=value, type=self.type,
checked='checked', **attrs)
else:
tag = tags.input(name=name, value=value, type=self.type,
**attrs)
options.append(tag + label)
return '<br/>\n'.join(options)
class Radio(Input):
type = 'radio'
setdomid = False
def render(self, form, field):
name, curvalues, attrs = self._render_attrs(form, field)
options = []
for label, value in field.vocabulary(form):
if value in curvalues:
tag = tags.input(name=name, type=self.type, value=value,
checked='checked', **attrs)
else:
tag = tags.input(name=name, type=self.type, value=value, **attrs)
tag += label + '<br/>'
options.append(tag)
return '\n'.join(options)
class DateTimePicker(TextInput):
monthnames = ('january', 'february', 'march', 'april',
'may', 'june', 'july', 'august',
'september', 'october', 'november', 'december')
daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday')
needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js')
needs_css = ('cubicweb.calendar_popup.css',)
@classmethod
def add_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) for mname in cls.monthnames]
daynames = [_(dname) for dname in cls.daynames]
req.html_headers.define_var('MONTHNAMES', monthnames)
req.html_headers.define_var('DAYNAMES', daynames)
def render(self, form, field):
txtwidget = super(DateTimePicker, self).render(form, field)
self.add_localized_infos(form.req)
cal_button = self._render_calendar_popup(form, field)
return txtwidget + cal_button
def _render_calendar_popup(self, form, field):
req = form.req
value = form.context[field]['rawvalue']
inputid = form.context[field]['id']
helperid = '%shelper' % inputid
if not value:
value = date.today()
year, month = value.year, value.month
return (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,
req.external_resource('CALENDAR_ICON'),
req._('calendar'), helperid) )
class AjaxWidget(FieldWidget):
def __init__(self, wdgtype, inputid=None, **kwargs):
super(AjaxWidget, self).__init__(**kwargs)
self.attrs.setdefault('class', 'widget')
self.attrs.setdefault('cubicweb:loadtype', 'auto')
self.attrs['cubicweb:wdgtype'] = wdgtype
if inputid is not None:
self.attrs['cubicweb:inputid'] = inputid
def render(self, form, field):
self.add_media(form)
attrs = self._render_attrs(form, field)[-1]
return tags.div(**attrs)
class Button(Input):
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 = label
self.name = name
self.value = ''
self.onclick = onclick
self.cwaction = cwaction
self.attrs.setdefault('klass', 'validateButton')
def render(self, form, field=None):
label = form.req._(self.label)
attrs = self.attrs.copy()
if self.cwaction:
assert self.onclick is None
attrs['onclick'] = "postForm('__action_%s', \'%s\', \'%s\')" % (
self.cwaction, self.label, form.domid)
elif self.onclick:
attrs['onclick'] = self.onclick
if self.name:
attrs['name'] = name
if self.setdomid:
attrs['id'] = self.name
if self.settabindex and not 'tabindex' in attrs:
attrs['tabindex'] = form.req.next_tabindex()
return tags.input(value=label, type=self.type, **attrs)
class SubmitButton(Button):
type = 'submit'
class ResetButton(Button):
type = 'reset'
class ImgButton(object):
def __init__(self, domid, href, label, imgressource):
self.domid = domid
self.href = href
self.imgressource = imgressource
self.label = label
def render(self, form, field=None):
self.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>' % self.__dict__
# XXX EntityLinkComboBoxWidget, AddComboBoxWidget, AutoCompletionWidget,
# StaticFileAutoCompletionWidget, RestrictedAutoCompletionWidget...