"""This module contains the FormUtil class."""
__docformat__ = "restructuredtext"
# Created: Mon May 3 13:22:08 PDT 2004
# Author: Bob VanZant
# Email: bob@norcalttora.com, jjinux@users.sourceforge.net
#
# Copyright (c) Bob VanZant. All rights reserved.
import re
from aquarium.util.AquariumClass import AquariumClass
from aquarium.util.HasFriends import HasFriends
class FormUtil(AquariumClass, HasFriends):
"""This is a utility to build consistent HTML forms.
The purpose of this class is not to "abstract" HTML. Its purpose is to
provide "macros" so that you can get things done more quickly and
consistently. If your needs are very generic, you can use high-level
macros like ``field``. In your needs are less generic, you can use
middle-level macros like ``textField`` and ``label``. At the bottom are
very low-level macros like ``getErrorMsg`` and ``getDefault``. Naturally,
you can subclass this class as needed.
This class also mixes in the ``HasFriends`` mixin. That means if you need
to add a new method ``fooField``, simply create a new module called
``aquarium.widget.formutil.fooField`` containing a function called
``fooField``. The ``fooField`` function should accept a ``FormUtil``
instance called ``self`` as its first argument (i.e. it *acts* like a
method in this class). Then ``fooField`` will *just work*.
The following attributes are used:
defaults
This is a list of dicts. When looking for a default value, the list will
be traversed, and the first dict to define the given name will be used
for the value.
errors
This is a dict where each key is a field name and each value is the error
message associated with that field. Fields that do not have an error
will not be present in this dict. If left to its default value in the
constructor, I'll look in ``actionResults.errors``. I'll look for
``actionResults`` in both ``ctx`` and ``session``.
The following class attributes are used:
labelClass
This is the CSS class for labels.
errorClass
This is the CSS class for errors.
"""
labelClass = "label"
errorClass = "error"
def __init__(self, ctx, defaults=None, errors=None):
"""Accept the arguments."""
AquariumClass.__init__(self, ctx)
if not defaults:
defaults = []
if not errors:
try:
errors = ctx.actionResults.errors
except:
pass
if not errors:
try:
errors = ctx.session["actionResults"].errors
except:
pass
if not errors:
errors = {}
self.defaults = defaults
self.errors = errors
def field(self, label, name, type, **attributes):
"""Build a field with a label in a table row.
type
This can be any "blah" for which there exists a "blahField".
"""
label = self.label(name, label)
field = getattr(self, "%sField" % type)(name, **attributes)
return '<tr valign="top"><td>%s</td><td>%s</td></tr>' % (label, field)
def textField(self, name, **attributes):
"""Returns a string representing a text field."""
return self.inputField(name, "text", **attributes)
def passwordField(self, name, providePasswordDefault=False, **attributes):
"""Returns a string representing a password field."""
if not providePasswordDefault:
attributes["value"] = ""
return self.inputField(name, "password", **attributes)
def fileField(self, name, **attributes):
"""Returns a string representing a file upload field."""
return self.inputField(name, "file", **attributes)
def radioField(self, name, value, **attributes):
"""Returns a string representing a radio button.
value
This will be used for the value attribute of the field. It should
not be confused with the value coming from ``self.defaults``.
"""
if str(value) == str(self.getDefault(name)):
attributes["checked"] = "checked"
return self.inputField(name, "radio", value=value, **attributes)
def checkBoxField(self, name, value, **attributes):
"""Returns a string representing a checkbox.
value
This will be used for the value attribute of the field. It should
not be confused with the value coming from ``self.defaults``.
"""
if name.endswith('[]'):
default = self.getDefault(name[:-len('[]')])
else:
default = self.getDefault(name)
if isinstance(default, list):
checked = str(value) in default
else:
checked = str(value) == str(default)
if checked:
attributes["checked"] = "checked"
return self.inputField(name, "checkbox", value=value, **attributes)
def inputField(self, name, type, **attributes):
"""Returns a string representing an input field of type ``type``."""
htmlent = self._ctx.htmlent
value = attributes.get("value", self.getDefault(name))
className = self.ifError(name, self.errorClass)
attributes.update({
"name": htmlent(name),
"type": type,
"value": htmlent(value),
"class": className
})
return "<input %s />" % self.attributes(attributes)
def selectField(self, name, options, **attributes):
"""Returns a string representing a fully fledged select box.
options
See the option argument of ``optionField``.
"""
htmlent = self._ctx.htmlent
if name.endswith('[]'):
default = self.getDefault(name[:-len('[]')])
else:
default = self.getDefault(name)
attributes["name"] = htmlent(name)
attributes["class"] = self.ifError(name, self.errorClass)
return "\n".join(
['<select %s>' % self.attributes(attributes)] +
[self.optionField(option, default)
for option in options] +
['</select>']
)
def optionField(self, option, selectedValue):
"""Return an option for use within a select.
option
This is either a single value to be used as both the label
and the value or a tuple of the form ``(label, value)``.
"""
htmlent = self._ctx.htmlent
if isinstance(option, tuple):
(label, value) = option
else:
(label, value) = (option, option)
selected = ""
if isinstance(selectedValue, list):
if str(value) in selectedValue:
selected = ' selected="selected"'
else:
if str(value) == str(selectedValue):
selected = ' selected="selected"'
return '<option value="%s"%s>%s</option>' % (htmlent(value), selected,
htmlent(label))
def textAreaField(self, name, **attributes):
"""Returns a string representing a textarea."""
htmlent = self._ctx.htmlent
attributes["name"] = htmlent(name)
return "<textarea %s>%s</textarea>" % (self.attributes(attributes),
htmlent(self.getDefault(name)))
def attributes(self, attributes):
"""Return a set of HTML tag attributes."""
return " ".join(['%s="%s"' % (key, attributes[key])
for key in attributes.keys()])
def getErrorMsg(self, name, brk=True, **attributes):
"""Return the error message for a field, if present.
name
The name of the field we're dealing with.
brk
If True, use a div, otherwise use a span.
attributes
These will be used for the div or span.
I will wrap it with the CSS class ``self.errorClass``.
"""
if brk:
tag = "div"
else:
tag = "span"
attributes["class"] = self.errorClass
if self.errors.has_key(name):
return '<%s %s>%s</%s>' % (tag, self.attributes(attributes),
self.errors[name], tag)
return ""
def getDefault(self, name):
"""Return the default value for the given field.
I will *not* run htmlent on it. If no default can be found, I'll
return "".
"""
reIndex = re.compile(r"\[([\w\s]+)\]*")
reName = re.compile(r"(.*?)\[")
# Parse out trailing dict notation
idxs = reIndex.findall(name)
m = reName.search(name)
if m:
idxs = [m.group(1)] + idxs
for source in self.defaults:
if idxs:
tmp_source = source
for idx in idxs:
if not tmp_source.has_key(idx):
break
else:
tmp_source = tmp_source[idx]
else:
return tmp_source
else:
if source.has_key(name):
if source[name] is not None:
return source[name]
else:
return ""
return ""
def label(self, name=None, label=None, brk=True, **attributes):
"""Return a label and the error message defined by the validator.
Wrap it with the CSS class ``self.labelClass``.
name
If None do not attempt to display an error message.
label
If None do not show a label but only show the error message.
brk
If True, use a div, otherwise use a span.
attributes
These will be used for the div or span of the label, but they won't
be passed onto ``self.getErrorMsg``.
"""
ret = ""
# Using name == "null" is a common no-error-msgs-please trick
if name and name != "null":
tag = "label"
attributes["for"] = name
else:
tag = "span"
if brk:
brkTag = "<br />"
else:
brkTag = ""
attributes["class"] = self.labelClass
if label:
ret += '<%s %s>%s</%s>%s' % (tag, self.attributes(attributes),
label, tag, brkTag)
if name:
ret += self.getErrorMsg(name, brk)
return ret
def ifError(self, name, if_, else_=""):
"""Return ``if_`` if there was an error, ``else_`` otherwise."""
if self.errors.has_key(name):
return if_
else:
return else_
def focusField(self, form, field):
"""Output the JavaScript to focus a field.
Use this to set the default focus of a form. Call this *after* you
have output the given field.
form
This is the name attribute of the ``<form>`` tag.
field
This is the name or ``id`` attribute of the field.
"""
htmlent = self._ctx.htmlent
return """\
<script type="text/javascript"><!--
document.%s.%s.focus();
// --></script>""" % (htmlent(form), htmlent(field))
|