import string, types from DummyField import fields from DocumentTemplate.DT_Util import html_quote from DateTime import DateTime from helpers import is_sequence class Widget: """A field widget that knows how to display itself as HTML. """ property_names = ['title', 'description', 'default', 'css_class', 'alternate_name', 'hidden'] title = fields.StringField('title', title='Title', description=( "The title of this field. This is the title of the field that " "will appear in the form when it is displayed. Required."), default="", required=1) description = fields.TextAreaField('description', title='Description', description=( "Description of this field. The description property can be " "used to add a short description of what a field does; such as " "this one."), default="", width="20", height="3", required=0) css_class = fields.StringField('css_class', title='CSS class', description=( "The CSS class of the field. This can be used to style your " "formulator fields using cascading style sheets. Not required."), default="", required=0) alternate_name = fields.StringField('alternate_name', title='Alternate name', description=( "An alternative name for this field. This name will show up in " "the result dictionary when doing validation, and in the REQUEST " "if validation goes to request. This can be used to support names " "that cannot be used as Zope ids."), default="", required=0) hidden = fields.CheckBoxField('hidden', title="Hidden", description=( "This field will be on the form, but as a hidden field. The " "contents of the hidden field will be the default value. " "Hidden fields are not visible but will be validated."), default=0) # NOTE: for ordering reasons (we want extra at the end), # this isn't in the base class property_names list, but # instead will be referred to by the subclasses. extra = fields.StringField('extra', title='Extra', description=( "A string containing extra HTML code for attributes. This " "string will be literally included in the rendered field." "This property can be useful if you want " "to add an onClick attribute to use with JavaScript, for instance."), default="", required=0) def render(self, field, key, value, REQUEST): """Renders this widget as HTML using property values in field. """ return "[widget]" def render_hidden(self, field, key, value, REQUEST): """Renders this widget as a hidden field. """ try: extra = field.get_value('extra') except KeyError: # In case extra is not defined as in DateTimeWidget extra = '' return render_element("input", type="hidden", name=key, value=value, extra=extra) def render_view(self, field, value): """Renders this widget for public viewing. """ # default implementation if value is None: return '' return value class TextWidget(Widget): """Text widget """ property_names = Widget.property_names +\ ['display_width', 'display_maxwidth', 'extra'] default = fields.StringField('default', title='Default', description=( "You can place text here that will be used as the default " "value of the field, unless the programmer supplies an override " "when the form is being generated."), default="", required=0) display_width = fields.IntegerField('display_width', title='Display width', description=( "The width in characters. Required."), default=20, required=1) display_maxwidth = fields.IntegerField('display_maxwidth', title='Maximum input', description=( "The maximum input in characters that the widget will allow. " "Required. If set to 0 or is left empty, there is no maximum. " "Note that is client side behavior only."), default="", required=0) def render(self, field, key, value, REQUEST): """Render text input field. """ display_maxwidth = field.get_value('display_maxwidth') or 0 if display_maxwidth > 0: return render_element("input", type="text", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), maxlength=display_maxwidth, extra=field.get_value('extra')) else: return render_element("input", type="text", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), extra=field.get_value('extra')) def render_view(self, field, value): if value is None: return '' return value TextWidgetInstance = TextWidget() class PasswordWidget(TextWidget): def render(self, field, key, value, REQUEST): """Render password input field. """ display_maxwidth = field.get_value('display_maxwidth') or 0 if display_maxwidth > 0: return render_element("input", type="password", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), maxlength=display_maxwidth, extra=field.get_value('extra')) else: return render_element("input", type="password", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), extra=field.get_value('extra')) def render_view(self, field, value): return "[password]" PasswordWidgetInstance = PasswordWidget() class CheckBoxWidget(Widget): property_names = Widget.property_names + ['extra'] default = fields.CheckBoxField('default', title='Default', description=( "Default setting of the widget; either checked or unchecked. " "(true or false)"), default=0) def render(self, field, key, value, REQUEST): """Render checkbox. """ if value: return render_element("input", type="checkbox", name=key, css_class=field.get_value('css_class'), checked=None, extra=field.get_value('extra')) else: return render_element("input", type="checkbox", name=key, css_class=field.get_value('css_class'), extra=field.get_value('extra')) def render_view(self, field, value): if value: return 1 else: return 0 CheckBoxWidgetInstance = CheckBoxWidget() class TextAreaWidget(Widget): """Textarea widget """ property_names = Widget.property_names +\ ['width', 'height', 'extra'] default = fields.TextAreaField('default', title='Default', description=( "Default value of the text in the widget."), default="", width=20, height=3, required=0) width = fields.IntegerField('width', title='Width', description=( "The width (columns) in characters. Required."), default=40, required=1) height = fields.IntegerField('height', title="Height", description=( "The height (rows) in characters. Required."), default=5, required=1) def render(self, field, key, value, REQUEST): width = field.get_value('width') height = field.get_value('height') return render_element("textarea", name=key, css_class=field.get_value('css_class'), cols=width, rows=height, contents=html_quote(value), extra=field.get_value('extra')) def render_view(self, field, value): if value is None: return '' return value TextAreaWidgetInstance = TextAreaWidget() class LinesTextAreaWidget(TextAreaWidget): property_names = Widget.property_names +\ ['width', 'height', 'view_separator', 'extra'] default = fields.LinesField('default', title='Default', description=( "Default value of the lines in the widget."), default=[], width=20, height=3, required=0) view_separator = fields.StringField('view_separator', title='View separator', description=( "When called with render_view, this separator will be used to " "render individual items."), width=20, default='
\n', whitespace_preserve=1, required=1) def render(self, field, key, value, REQUEST): if is_sequence(value): value = string.join(value, "\n") return TextAreaWidget.render(self, field, key, value, REQUEST) def render_view(self, field, value): if value is None: return '' return string.join(value, field.get_value('view_separator')) def render_hidden(self, field, key, value, REQUEST): if value is None: return '' if is_sequence(value): value = '\n'.join(value) # reuse method from base class return Widget.render_hidden(self,field,key, value, REQUEST) LinesTextAreaWidgetInstance = LinesTextAreaWidget() class FileWidget(TextWidget): def render(self, field, key, value, REQUEST): """Render text input field. """ display_maxwidth = field.get_value('display_maxwidth') or 0 if display_maxwidth > 0: return render_element("input", type="file", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), maxlength=display_maxwidth, extra=field.get_value('extra')) else: return render_element("input", type="file", name=key, css_class=field.get_value('css_class'), value=value, size=field.get_value('display_width'), extra=field.get_value('extra')) def render_view(self, field, value): return "[File]" FileWidgetInstance = FileWidget() class ItemsWidget(Widget): """A widget that has a number of items in it. """ items = fields.ListTextAreaField('items', title='Items', description=( "Items in the field. Each row should contain an " "item. Use the | (pipe) character to separate what is shown " "to the user from the submitted value. If no | is supplied, the " "shown value for the item will be identical to the submitted value. " "Internally the items property returns a list. If a list item " "is a single value, that will be used for both the display and " "the submitted value. A list item can also be a tuple consisting " "of two elements. The first element of the tuple should be a string " "that is name of the item that should be displayed. The second " "element of the tuple should be the value that will be submitted. " "If you want to override this property you will therefore have " "to return such a list."), default=[], width=20, height=5, required=0) # NOTE: for ordering reasons (we want extra at the end), # this isn't in the base class property_names list, but # instead will be referred to by the subclasses. extra_item = fields.StringField('extra_item', title='Extra per item', description=( "A string containing extra HTML code for attributes. This " "string will be literally included each of the rendered items of the " "field. This property can be useful if you want " "to add a disabled attribute to disable all contained items, for " "instance."), default="", required=0) class SingleItemsWidget(ItemsWidget): """A widget with a number of items that has only a single selectable item. """ default = fields.StringField('default', title='Default', description=( "The default value of the widget; this should be one of the " "elements in the list of items."), default="", required=0) first_item = fields.CheckBoxField('first_item', title="Select First Item", description=( "If checked, the first item will always be selected if " "no initial default value is supplied."), default=0) def render_items(self, field, key, value, REQUEST): # get items items = field.get_value('items') # check if we want to select first item if not value and field.get_value('first_item') and len(items) > 0: if is_sequence(items[0]): text, value = items[0] else: value = items[0] css_class = field.get_value('css_class') extra_item = field.get_value('extra_item') # if we run into multiple items with same value, we select the # first one only (for now, may be able to fix this better later) selected_found = 0 rendered_items = [] for item in items: if is_sequence(item): item_text, item_value = item else: item_text = item item_value = item if item_value == value and not selected_found: rendered_item = self.render_selected_item(item_text, item_value, key, css_class, extra_item) selected_found = 1 else: rendered_item = self.render_item(item_text, item_value, key, css_class, extra_item) rendered_items.append(rendered_item) return rendered_items def render_view(self, field, value): if value is None or value == '': # check for empty string too, for situations # where no default value is set. return '' items = field.get_value('items') for item in items: if is_sequence(item): item_text, item_value = item else: item_text = item item_value = item if value == item_value: return item_text raise KeyError, "Wrong item value [[%s]]" % (value,) class MultiItemsWidget(ItemsWidget): """A widget with a number of items that has multiple selectable items. """ default = fields.LinesField('default', title='Default', description=( "The initial selections of the widget. This is a list of " "zero or more values. If you override this property from Python " "your code should return a Python list."), width=20, height=3, default=[], required=0) view_separator = fields.StringField('view_separator', title='View separator', description=( "When called with render_view, this separator will be used to " "render individual items."), width=20, default='
\n', whitespace_preserve=1, required=1) def render_items(self, field, key, value, REQUEST): # need to deal with single item selects if not is_sequence(value): value = [value] items = field.get_value('items') css_class = field.get_value('css_class') extra_item = field.get_value('extra_item') rendered_items = [] for item in items: if is_sequence(item): item_text, item_value = item else: item_text = item item_value = item if item_value in value: rendered_item = self.render_selected_item(item_text, item_value, key, css_class, extra_item) else: rendered_item = self.render_item(item_text, item_value, key, css_class, extra_item) rendered_items.append(rendered_item) return rendered_items def render_items_view(self, field, value): if not is_sequence(value): value = [value] items = field.get_value('items') d = {} for item in items: if is_sequence(item): item_text, item_value = item else: item_text = item item_value = item d[item_value] = item_text result = [] for e in value: result.append(d[e]) return result def render_view(self, field, value): if value is None: return '' return string.join(self.render_items_view(field, value), field.get_value('view_separator')) def render_hidden(self, field, key, value, REQUEST): if value is not None and not is_sequence(value): value = [value] # reuse method from base class render_item_hidden = Widget.render_hidden result = [] for v in value: result.append(render_item_hidden(self, field, key, v, REQUEST)) return ''.join(result) class ListWidget(SingleItemsWidget): """List widget. """ property_names = Widget.property_names +\ ['first_item', 'items', 'size', 'extra', 'extra_item'] size = fields.IntegerField('size', title='Size', description=( "The display size in rows of the field. If set to 1, the " "widget will be displayed as a drop down box by many browsers, " "if set to something higher, a list will be shown. Required."), default=5, required=1) def render(self, field, key, value, REQUEST): rendered_items = self.render_items(field, key, value, REQUEST) return render_element('select', name=key, css_class=field.get_value('css_class'), size=field.get_value('size'), contents=string.join(rendered_items, "\n"), extra=field.get_value('extra')) def render_item(self, text, value, key, css_class, extra_item): return render_element('option', contents=text, value=value, extra=extra_item) def render_selected_item(self, text, value, key, css_class, extra_item): return render_element('option', contents=text, value=value, selected=None, extra=extra_item) ListWidgetInstance = ListWidget() class MultiListWidget(MultiItemsWidget): """List widget with multiple select. """ property_names = Widget.property_names +\ ['items', 'size', 'view_separator', 'extra', 'extra_item'] size = fields.IntegerField('size', title='Size', description=( "The display size in rows of the field. If set to 1, the " "widget will be displayed as a drop down box by many browsers, " "if set to something higher, a list will be shown. Required."), default=5, required=1) def render(self, field, key, value, REQUEST): rendered_items = self.render_items(field, key, value, REQUEST) return render_element('select', name=key, multiple=None, css_class=field.get_value('css_class'), size=field.get_value('size'), contents=string.join(rendered_items, "\n"), extra=field.get_value('extra')) def render_item(self, text, value, key, css_class, extra_item): return render_element('option', contents=text, value=value, extra=extra_item) def render_selected_item(self, text, value, key, css_class, extra_item): return render_element('option', contents=text, value=value, selected=None, extra=extra_item) MultiListWidgetInstance = MultiListWidget() class RadioWidget(SingleItemsWidget): """radio buttons widget. """ property_names = Widget.property_names +\ ['first_item', 'items', 'orientation', 'extra_item'] orientation = fields.ListField('orientation', title='Orientation', description=( "Orientation of the radio buttons. The radio buttons will " "be drawn either vertically or horizontally."), default="vertical", required=1, size=1, items=[('Vertical', 'vertical'), ('Horizontal', 'horizontal')]) def render(self, field, key, value, REQUEST): rendered_items = self.render_items(field, key, value, REQUEST) orientation = field.get_value('orientation') if orientation == 'horizontal': return string.join(rendered_items, "  ") else: return string.join(rendered_items, "
") def render_item(self, text, value, key, css_class, extra_item): contents = render_element('input', type="radio", css_class=css_class, name=key, value=value, extra=extra_item) + text return render_element('label', contents=contents) def render_selected_item(self, text, value, key, css_class, extra_item): contents = render_element('input', type="radio", css_class=css_class, name=key, value=value, checked=None, extra=extra_item) + text return render_element('label', contents=contents) RadioWidgetInstance = RadioWidget() class MultiCheckBoxWidget(MultiItemsWidget): """multiple checkbox widget. """ property_names = Widget.property_names +\ ['items', 'orientation', 'view_separator', 'extra_item'] orientation = fields.ListField('orientation', title='Orientation', description=( "Orientation of the check boxes. The check boxes will " "be drawn either vertically or horizontally."), default="vertical", required=1, size=1, items=[('Vertical', 'vertical'), ('Horizontal', 'horizontal')]) def render(self, field, key, value, REQUEST): rendered_items = self.render_items(field, key, value, REQUEST) orientation = field.get_value('orientation') if orientation == 'horizontal': return string.join(rendered_items, "  ") else: return string.join(rendered_items, "
") def render_item(self, text, value, key, css_class, extra_item): return render_element('input', type="checkbox", css_class=css_class, name=key, value=value, extra=extra_item) + text def render_selected_item(self, text, value, key, css_class, extra_item): return render_element('input', type="checkbox", css_class=css_class, name=key, value=value, checked=None, extra=extra_item) + text MultiCheckBoxWidgetInstance = MultiCheckBoxWidget() class DateTimeWidget(Widget): property_names = Widget.property_names +\ ['default_now', 'date_separator', 'time_separator', 'input_style', 'input_order', 'date_only', 'hide_day', 'ampm_time_style'] default = fields.DateTimeField('default', title="Default", description=("The default datetime."), default=None, display_style="text", display_order="ymd", input_style="text", required=0) default_now = fields.CheckBoxField('default_now', title="Default to now", description=( "Default date and time will be the date and time at showing of " "the form (if the default is left empty)."), default=0) date_separator = fields.StringField('date_separator', title='Date separator', description=( "Separator to appear between year, month, day."), default="/", required=0, display_width=2, display_maxwith=2, max_length=2) time_separator = fields.StringField('time_separator', title='Time separator', description=( "Separator to appear between hour and minutes."), default=":", required=0, display_width=2, display_maxwith=2, max_length=2) input_style = fields.ListField('input_style', title="Input style", description=( "The type of input used. 'text' will show the date part " "as text, while 'list' will use dropdown lists instead."), default="text", items=[("text", "text"), ("list", "list")], size=1) input_order = fields.ListField('input_order', title="Input order", description=( "The order in which date input should take place. Either " "year/month/day, day/month/year or month/day/year."), default="ymd", items=[("year/month/day", "ymd"), ("day/month/year", "dmy"), ("month/day/year", "mdy")], required=1, size=1) date_only = fields.CheckBoxField('date_only', title="Display date only", description=( "Display the date only, not the time."), default=0) hide_day = fields.CheckBoxField('hide_day', title="Hide day field", description=( "Hide the day field."), default=0) ampm_time_style = fields.CheckBoxField('ampm_time_style', title="AM/PM time style", description=( "Display time in am/pm format."), default=0) # FIXME: do we want to handle 'extra'? def render(self, field, key, value, REQUEST): use_ampm = field.get_value('ampm_time_style') hide_day = field.get_value('hide_day') # FIXME: backwards compatibility hack: if not hasattr(field, 'sub_form'): from StandardFields import create_datetime_text_sub_form field.sub_form = create_datetime_text_sub_form() if value is None and field.get_value('default_now'): value = DateTime() # Allow subfields to get their values even when default_now is set. if REQUEST is not None and \ REQUEST.form.has_key(field.generate_subfield_key('year')): value = None if value is None: year = None month = None day = None hour = None minute = None ampm = None else: year = "%04d" % value.year() month = "%02d" % value.month() day = "%02d" % value.day() if use_ampm: hour = "%02d" % value.h_12() else: hour = "%02d" % value.hour() minute = "%02d" % value.minute() ampm = value.ampm() input_order = field.get_value('input_order') if input_order == 'ymd': order = [('year', year), ('month', month), ('day', day)] elif input_order == 'dmy': order = [('day', day), ('month', month), ('year', year)] elif input_order == 'mdy': order = [('month', month), ('day', day), ('year', year)] result = [] hidden_day_part = "" for sub_field_name, sub_field_value in order: if hide_day and (sub_field_name == 'day'): dayvalue = sub_field_value if dayvalue is None: dayvalue = "01" sub_key = field.generate_subfield_key(sub_field_name) sub_field = field.sub_form.get_field(sub_field_name) hidden_day_part = sub_field.widget.\ render_hidden(sub_field, sub_key, dayvalue, REQUEST) else: result.append(field.render_sub_field(sub_field_name, sub_field_value, REQUEST)) date_result = string.join(result, field.get_value('date_separator')) if hidden_day_part: date_result += hidden_day_part if not field.get_value('date_only'): time_result = (field.render_sub_field('hour', hour, REQUEST) + field.get_value('time_separator') + field.render_sub_field('minute', minute, REQUEST)) if use_ampm: time_result += ' ' + field.render_sub_field('ampm', ampm, REQUEST) return date_result + '   ' + time_result else: return date_result def render_hidden(self, field, key, value, REQUEST): result = [] if value is None and field.get_value('default_now'): value = DateTime() sub_values = {} subfields = ['year','month','day'] if value is not None: sub_values['year'] = '%04d' % value.year() sub_values['month'] = "%02d" % value.month() sub_values['day'] = "%02d" % value.day() if not field.get_value('date_only'): use_ampm = field.get_value('ampm_time_style') subfields.extend(['hour','minute']) if use_ampm: subfields.append('ampm') if value is not None: if use_ampm: sub_values['hour'] = "%02d" % value.h_12() sub_values['ampm'] = value.ampm() else: sub_values['hour'] = "%02d" % value.hour() sub_values['minute'] = "%02d" % value.minute() for subfield in subfields: # XXX it would be nicer to pass the hidden value somewhere # to the subfields, but ... sub_key = field.generate_subfield_key(subfield) sub_field = field.sub_form.get_field(subfield) result.append(sub_field.widget.render_hidden(sub_field, sub_key, sub_values.get(subfield), REQUEST)) return ''.join(result) def render_view(self, field, value): if value is None: return '' use_ampm = field.get_value('ampm_time_style') year = "%04d" % value.year() month = "%02d" % value.month() day = "%02d" % value.day() if use_ampm: hour = "%02d" % value.h_12() else: hour = "%02d" % value.hour() minute = "%02d" % value.minute() ampm = value.ampm() order = field.get_value('input_order') if order == 'ymd': output = [year, month, day] elif order == 'dmy': output = [day, month, year] elif order == 'mdy': output = [month, day, year] date_result = string.join(output, field.get_value('date_separator')) if not field.get_value('date_only'): time_result = hour + field.get_value('time_separator') + minute if use_ampm: time_result += ' ' + ampm return date_result + '   ' + time_result else: return date_result DateTimeWidgetInstance = DateTimeWidget() class LabelWidget(Widget): """Widget that is a label only. It simply returns its default value. """ property_names = ['title', 'description', 'default', 'css_class', 'hidden', 'extra'] default = fields.TextAreaField( 'default', title="Label text", description="Label text to render", default="", width=20, height=3, required=0) def render(self, field, key, value, REQUEST): return render_element("div", css_class=field.get_value('css_class'), contents=field.get_value('default')) # XXX should render view return the same information as render? def render_view(self, field, value): return field.get_value('default') LabelWidgetInstance = LabelWidget() def render_tag(tag, **kw): """Render the tag. Well, not all of it, as we may want to / it. """ attr_list = [] # special case handling for css_class if kw.has_key('css_class'): if kw['css_class'] != "": attr_list.append('class="%s"' % kw['css_class']) del kw['css_class'] # special case handling for extra 'raw' code if kw.has_key('extra'): extra = kw['extra'] # could be empty string but we don't care del kw['extra'] else: extra = "" # handle other attributes for key, value in kw.items(): if value is None: if key == 'value': value = '' else: value = key attr_list.append('%s="%s"' % (key, html_quote(value))) attr_str = string.join(attr_list, " ") return "<%s %s %s" % (tag, attr_str, extra) def render_element(tag, **kw): if kw.has_key('contents'): contents = kw['contents'] del kw['contents'] return "%s>%s" % (apply(render_tag, (tag, ), kw), contents, tag) else: return apply(render_tag, (tag, ), kw) + " />"