Mike Orr avatar Mike Orr committed b3d1d16

Experimental field() helper. Refactor form_legend().

Comments (0)

Files changed (2)

 * webhelpers.html.tags:
   - Options tuple contains Option objects for select/checkbox/radio groups.
     select() now uses this automatically.
+* Experimental form layout helpers in webhelpers.html.tags (SUBJECT TO CHANGE
   - field() provides a simple <div> based layout for a form widget with its
     label, required-ness, and optional hint and error message.
   - form_legend() returns a message about required fields.


 NL = literal("\n")
 BR = literal("<br />\n")
+REQUIRED_SYMBOL = HTML.span("*", class_="required-symbol")
 def form(url, method="post", multipart=False, **attrs):
     """An open tag for a form that will submit to ``url``.
         return (x.label for x in self)
 def field(label, required, widget, hint=None, error=None, **attrs):
-    """A simple formatter for form fields.
+    """A simple non-table formatter for form fields.
+    *This helper is still experimental and subject to change. 2008-10-01*
+    This helper decorates a form field with its label, required-ness,
+    help text, and error message.  It puts the entire field in a 
+    *<div class="field">*, which contains the label and an inner
+    *<div class="field-body">* containing the error message, input widget,
+    and help text.  Visually these are all stacked vertically.    
+    If you put multiple widgets in the ``widget`` arg (say a checkbox group
+    or radio group), they will all be inside the *<label>*.  This is 
+    technically incorrect because a label should contain only one widget.
+    If each widget has its own label, there would be a label inside a label.
+    We're not sure how to resolve this, so you're on your own for it.
+    Arguments:
+    ``label`` -- the field title.
+    ``required`` -- True if the field must be filled in.  This just affects how
+    the field is displayed; you'll have to use Javascript or a server-side
+    validator to enforce the constraint.
+    ``widget`` -- HTML for the input control. Typically you'd pass the result of
+    a form helper or ModelTags helper here.
+    ``hint`` -- Extra HTML to display beneath the control.  Typically used for
+    help text.  You could also pass invisible Javascript here to make the
+    widget more interactive.  Wrap the value in ``literal()`` if it
+    contains intentional HTML markup.
+    ``error`` -- An error message.  If using FormEncode, leave this at
+    the default and instead filter the rendered form through ``htmlfill``.
+    ``attrs`` -- Extra HTML attributes for the outermost *<div>*.  By default
+    this sets the class to "field".
     >>> field("LABEL", True, "WIDGET")
-    literal(u'<div class="field">\\n<label><span class="required">LABEL&nbsp;*</span>\\n<div class="field-body">WIDGET</div></label>\\n</div>')
+    literal(u'<div class="field">\\n<label><span class="required">LABEL&nbsp;<span class="required-symbol">*</span></span>\\n<div class="field-body">WIDGET</div></label>\\n</div>')
     >>> field("LABEL", False, text("notes"))
     literal(u'<div class="field">\\n<label><span class="not-required">LABEL</span>\\n<div class="field-body"><input name="notes" type="text" /></div></label>\\n</div>')
     >>> field("LABEL", True, text("notes"), "HINT", "ERROR", id="my-id")
-    literal(u'<div class="field" id="my-id">\\n<label><span class="required">LABEL&nbsp;*</span>\\n<div class="field-body"><span class="error-message">ERROR</span><br />\\n<input name="notes" type="text" /><br />\\n<div class="hint">HINT</div></div></label>\\n</div>')
+    literal(u'<div class="field" id="my-id">\\n<label><span class="required">LABEL&nbsp;<span class="required-symbol">*</span></span>\\n<div class="field-body"><span class="error-message">ERROR</span><br />\\n<input name="notes" type="text" /><br />\\n<div class="hint">HINT</div></div></label>\\n</div>')
     Here's a sample stylesheet to accompany the fields:
+    /* Make sure there's a space above the field. */
     .field {
         margin-top: 1em;
+    /* Required symbol (*) is red. */
+    .required-symbol {
+        color: red;
+        font-weight: bold;
+        }
+    /* Required field labels are bold. */
     .required {
         color: red;
         font-weight: bold;
+    /* Non-required fields are normal.  (Set to bold if desired.) */
+    .not-required {
+        }
+    /* The field body is indented under its label. */
     .field-body {
         margin-left: 1em;
+    /* Error messages are very obvious: large bold red italic text, with a
+     * reddish-pink background and some padding.  */
     .error-message {
         color: #cc0000;
         background-color: #ffeeee;
         font-style: italic;
         padding: 4px;
+    /* The hint is green and italic. */
     .hint {
         color: #006400;
         font-style: italic;
+    /* Add this class to lay out multiple fields horizontally:
+     * field(..., class_="field horizontal")
+     * Put the entire row in a <div> to prevent other things from being
+     * laid out alongside it. */
+    div.horizontal {
+        float: left;
+        margin-right: 2em;
+        }
     if required:
-        label_span = HTML.span(label, literal("&nbsp;*"), class_="required")
+        label_span = HTML.span(
+            label, 
+            literal("&nbsp;"),
+            REQUIRED_SYMBOL,
+            class_="required")
         label_span = HTML.span(label, class_="not-required")
     body = []
     if hint:
         body.append(HTML.div(hint, class_="hint"))
+    attrs.setdefault("class_", "field")
     return HTML.div(
             HTML.div(c=body, class_="field-body"),
-        class_="field", **attrs)
+        **attrs)
 def form_legend(**attrs):
     """Return a span containing standard form instructions.
+    *This helper is experimental and subject to change.  2008-10-01*
     Currently it just explains that "*" means the field is required.
     >>> form_legend()
-    literal(u'<span><span class="required">*</span> = required</span>')
+    literal(u'<span class="required"><span class="required-symbol">*</span> = required</span>')
     >>> form_legend(style="font-size: x-small")
-    literal(u'<span style="font-size: x-small"><span class="required">*</span> = required</span>')
+    literal(u'<span class="required" style="font-size: x-small"><span class="required-symbol">*</span> = required</span>')
+    attrs.setdefault("class_", "required")
     return HTML.span(
-        HTML.span("*", class_="required"),
         " = required",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.