Mike Orr avatar Mike Orr committed 5a860a8

Add magic 'id' attribute to form input helpers.

Comments (0)

Files changed (5)

 WebHelpers ChangeLog
 
 0.6.4 (tip)
+* text(), password(), checkbox(), textarea(), and select() have a
+  magic 'id attribute. If not specified it defaults to the name. To suppress
+  the ID entirely, pass ``id=""``.  This is to help set the ID for title().
+  radio() doesn't do this because it generates the ID another way.  hidden()
+  doesn't because hidden fields aren't used with labels.
 
 0.6.3 (10/7/2008)
 * Bugfix in distribute() found by Randy Syring.

tests/test_modeltags.py

     def test_check_box(self):
         self.assertEqual(
             self.m.checkbox("fulltime"),
-            u'<input checked="checked" name="fulltime" type="checkbox" value="1" />',
+            u'<input checked="checked" id="fulltime" name="fulltime" type="checkbox" value="1" />',
         )
 
     def test_hidden_field(self):
     def test_password_field(self):
         self.assertEqual(
             self.m.password('name'), 
-            u'<input name="name" type="password" value="Jim" />'
+            u'<input id="name" name="name" type="password" value="Jim" />'
         )
     def test_file_field(self):
         self.assertEqual(
             self.m.file('name'), 
-            u'<input name="name" type="file" value="Jim" />'
+            u'<input id="name" name="name" type="file" value="Jim" />'
         )
 
     def test_radio_button(self):
     def test_text_area(self):
         self.assertEqual(
             self.m.textarea("longtext"),
-            u'<textarea name="longtext">lorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\n</textarea>'
+            u'<textarea id="longtext" name="longtext">lorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\n</textarea>'
         )
 
     def test_text_field(self):
         self.assertEqual(
             self.m.text("name"),
-            u'<input name="name" type="text" value="Jim" />'
+            u'<input id="name" name="name" type="text" value="Jim" />'
         )
     def test_select(self):
         self.assertEqual(
             self.m.select("lang", [("en", "English"), ("de", "German"), ("jp", "Japanese")]),
-            u'<select name="lang">\n<option selected="selected" value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
+            u'<select id="lang" name="lang">\n<option selected="selected" value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
         )
 
 class TestModelTagsHelperWithDict(TestModelTagsHelperWithObject):
     def test_check_box(self):
         self.assertEqual(
             self.m.checkbox("fulltime"),
-            u'<input checked="checked" name="fulltime" type="checkbox" value="1" />',
+            u'<input checked="checked" id="fulltime" name="fulltime" type="checkbox" value="1" />',
         )
 
     def test_hidden_field(self):
     def test_password_field(self):
         self.assertEqual(
             self.m.password('name'), 
-            u'<input name="name" type="password" value="Jim" />'
+            u'<input id="name" name="name" type="password" value="Jim" />'
         )
     def test_file_field(self):
         self.assertEqual(
             self.m.file('name'), 
-            u'<input name="name" type="file" value="Jim" />'
+            u'<input id="name" name="name" type="file" value="Jim" />'
         )
 
     def test_radio_button(self):
     def test_text_area(self):
         self.assertEqual(
             self.m.textarea("longtext"),
-            u'<textarea name="longtext">lorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\n</textarea>'
+            u'<textarea id="longtext" name="longtext">lorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\nlorem ipsum lorem ipsum\n</textarea>'
         )
 
     def test_text_field(self):
         self.assertEqual(
             self.m.text("name"),
-            u'<input name="name" type="text" value="Jim" />'
+            u'<input id="name" name="name" type="text" value="Jim" />'
         )
     def test_select(self):
         self.assertEqual(
             self.m.select("lang", [("en", "English"), ("de", "German"), ("jp", "Japanese")]),
-            u'<select name="lang">\n<option selected="selected" value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
+            u'<select id="lang" name="lang">\n<option selected="selected" value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
         )
 
 class TestModelTagsHelperWithIdGeneration(TestModelTagsHelperWithObject):
     def test_check_box(self):
         self.assertEqual(
             self.m.checkbox("fulltime"),
-            u'<input name="fulltime" type="checkbox" value="1" />',
+            u'<input id="fulltime" name="fulltime" type="checkbox" value="1" />',
         )
 
     def test_hidden_field(self):
     def test_password_field(self):
         self.assertEqual(
             self.m.password('name'), 
-            u'<input name="name" type="password" value="" />'
+            u'<input id="name" name="name" type="password" value="" />'
         )
     def test_file_field(self):
         self.assertEqual(
             self.m.file('name'), 
-            u'<input name="name" type="file" value="" />'
+            u'<input id="name" name="name" type="file" value="" />'
         )
 
     def test_radio_button(self):
     def test_text_area(self):
         self.assertEqual(
             self.m.textarea("longtext"),
-            u'<textarea name="longtext"></textarea>'
+            u'<textarea id="longtext" name="longtext"></textarea>'
         )
 
     def test_text_field(self):
         self.assertEqual(
             self.m.text("name"),
-            u'<input name="name" type="text" value="" />'
+            u'<input id="name" name="name" type="text" value="" />'
         )
     def test_select(self):
         self.assertEqual(
             self.m.select("lang", [("en", "English"), ("de", "German"), ("jp", "Japanese")]),
-            u'<select name="lang">\n<option value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
+            u'<select id="lang" name="lang">\n<option value="en">English</option>\n<option value="de">German</option>\n<option value="jp">Japanese</option>\n</select>'
         )        
 if __name__ == '__main__':
     suite = map(unittest.makeSuite, [

tests/test_tags.py

     def test_check_box(self):
         self.assertEqual(
             checkbox("admin"),
-            u'<input name="admin" type="checkbox" value="1" />',
+            u'<input id="admin" name="admin" type="checkbox" value="1" />',
         )
 
     def test_form(self):
     def test_password_field(self):
         self.assertEqual(
             password("password"), 
-            u'<input name="password" type="password" />'
+            u'<input id="password" name="password" type="password" />'
         )
 
     def test_radio_button(self):
 
     def test_submit(self):
         self.assertEqual(
-            u'<input name="commit" type="submit" value="Save changes" />',
+            u'<input id="commit" name="commit" type="submit" value="Save changes" />',
             submit("commit", "Save changes")
         )
 
     def test_text_area(self):
         self.assertEqual(
             textarea("aa", ""),
-            u'<textarea name="aa"></textarea>'
+            u'<textarea id="aa" name="aa"></textarea>'
         )
         self.assertEqual(
             textarea("aa", None),
-            u'<textarea name="aa"></textarea>'
+            u'<textarea id="aa" name="aa"></textarea>'
         )
         self.assertEqual(
             textarea("aa", "Hello!"),
-            u'<textarea name="aa">Hello!</textarea>'
+            u'<textarea id="aa" name="aa">Hello!</textarea>'
         )
 
     def test_text_area_size_string(self):
         self.assertEqual(
             textarea("body", "hello world", cols=20, rows=40),
-            u'<textarea cols="20" name="body" rows="40">hello world</textarea>'
+            u'<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>'
         )
 
     def test_text_field(self):
         self.assertEqual(
             text("title", ""),
-            u'<input name="title" type="text" value="" />'
+            u'<input id="title" name="title" type="text" value="" />'
         )
         self.assertEqual(
             text("title", None),
-            u'<input name="title" type="text" />'
+            u'<input id="title" name="title" type="text" />'
         )
         self.assertEqual(
             text("title", "Hello!"),
-            u'<input name="title" type="text" value="Hello!" />'
+            u'<input id="title" name="title" type="text" value="Hello!" />'
         )
 
     def test_text_field_class_string(self):
         self.assertEqual(
             text( "title", "Hello!", class_= "admin"),
-            u'<input class="admin" name="title" type="text" value="Hello!" />'
+            u'<input class="admin" id="title" name="title" type="text" value="Hello!" />'
         )
 
     def test_boolean_options(self):
         self.assertEqual(     
             checkbox("admin", 1, True, disabled = True, readonly="yes"),
-            u'<input checked="checked" disabled="disabled" name="admin" readonly="readonly" type="checkbox" value="1" />'
+            u'<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />'
         )
         self.assertEqual(
             checkbox("admin", 1, True, disabled = False, readonly = None),
-            u'<input checked="checked" name="admin" type="checkbox" value="1" />'
+            u'<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />'
         )
 
     

webhelpers/html/form_layout.py

     this sets the class to "field".
 
     
-    >>> field("TITLE", True, "WIDGET")
+    **** Disabled doctests: not maintained. ***
+
+    >> field("TITLE", True, "WIDGET")
     literal(u'<div class="field">\\n<label><span class="required">TITLE&nbsp;<span class="required-symbol">*</span></span>\\n<div class="field-body">WIDGET</div></label>\\n</div>')
-    >>> field("TITLE", False, tags.text("notes"))
+    >> field("TITLE", False, tags.text("notes"))
     literal(u'<div class="field">\\n<label><span class="not-required">TITLE</span>\\n<div class="field-body"><input name="notes" type="text" /></div></label>\\n</div>')
-    >>> field("TITLE", True, tags.text("notes"), "HINT", "ERROR", id="my-id")
+    >> field("TITLE", True, tags.text("notes"), "HINT", "ERROR", id="my-id")
     literal(u'<div class="field" id="my-id">\\n<label><span class="required">TITLE&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:
 
     Currently it just explains that "*" means the field is required.
 
-    >>> form_legend()
+    ***Disabled doctests, not maintained.***
+
+    >> form_legend()
     literal(u'<span class="required"><span class="required-symbol">*</span> = required</span>')
-    >>> form_legend(style="font-size: x-small")
+    >> form_legend(style="font-size: x-small")
     literal(u'<span class="required" style="font-size: x-small"><span class="required-symbol">*</span> = required</span>')
     """
     attrs.setdefault("class_", "required")

webhelpers/html/tags.py

     return literal("</form>")
 
 
-def text(name, value=None, **attrs):
+def text(name, value=None, id=None, **attrs):
     """Create a standard text field.
     
     ``value`` is a string, the content of the text field.
+
+    ``id`` is the HTML ID attribute, and should be passed as a keyword
+    argument.  By default the ID is the same as the name filtered through
+    ``_make_safe_id_component()``.  Pass the empty string ("") to suppress the
+    ID attribute entirely.
+
     
     Options:
     
     The remaining keyword args will be standard HTML attributes for the tag.
     
     """
-    set_input_attrs(attrs, "text", name, value)
+    _set_input_attrs(attrs, "text", name, value)
+    _set_id_attr(attrs, id, name)
     convert_boolean_attrs(attrs, ["disabled"])
     return HTML.input(**attrs)
 
 def hidden(name, value=None, **attrs):
     """Create a hidden field.
     """
-    set_input_attrs(attrs, "hidden", name, value)
+    _set_input_attrs(attrs, "hidden", name, value)
     return HTML.input(**attrs)
 
 
-def file(name, value=None, **attrs):
+def file(name, value=None, id=None, **attrs):
     """Create a file upload field.
     
     If you are using file uploads then you will also need to set the 
     Example::
 
         >>> file('myfile')
-        literal(u'<input name="myfile" type="file" />')
+        literal(u'<input id="myfile" name="myfile" type="file" />')
     
     """
-    set_input_attrs(attrs, "file", name, value)
+    _set_input_attrs(attrs, "file", name, value)
+    _set_id_attr(attrs, id, name)
     return HTML.input(**attrs)
 
 
-def password(name, value=None, **attrs):
+def password(name, value=None, id=None, **attrs):
     """Create a password field.
     
-    Takes the same options as text_field.
+    Takes the same options as ``text()``.
     
     """
-    set_input_attrs(attrs, "password", name, value)
+    _set_input_attrs(attrs, "password", name, value)
+    _set_id_attr(attrs, id, name)
     return HTML.input(**attrs)
 
 
-def textarea(name, content="", **attrs):
+def textarea(name, content="", id=None, **attrs):
     """Create a text input area.
     
     Example::
     
         >>> textarea("body", "", cols=25, rows=10)
-        literal(u'<textarea cols="25" name="body" rows="10"></textarea>')
+        literal(u'<textarea cols="25" id="body" name="body" rows="10"></textarea>')
     
     """
     attrs["name"] = name
+    _set_id_attr(attrs, id, name)
     return HTML.textarea(content, **attrs)
 
 
-def checkbox(name, value="1", checked=False, label=None, **attrs):
+def checkbox(name, value="1", checked=False, label=None, id=None, **attrs):
     """Create a check box.
 
     Arguments:
 
     ``label`` -- a text label to display to the right of the box.
 
+    ``id`` is the HTML ID attribute, and should be passed as a keyword
+    argument.  By default the ID is the same as the name filtered through
+    ``_make_safe_id_component()``.  Pass the empty string ("") to suppress the
+    ID attribute entirely.
+
+d
     The following HTML attributes may be set by keyword argument:
 
     * ``disabled`` - If true, checkbox will be grayed out.
     Example::
     
         >>> checkbox("hi")
-        literal(u'<input name="hi" type="checkbox" value="1" />')
+        literal(u'<input id="hi" name="hi" type="checkbox" value="1" />')
     """
-    set_input_attrs(attrs, "checkbox", name, value)
-    attrs["type"] = "checkbox"
-    attrs["name"] = name
-    attrs["value"] = value
+    _set_input_attrs(attrs, "checkbox", name, value)
+    _set_id_attr(attrs, id, name)
     if checked:
         attrs["checked"] = "checked"
     convert_boolean_attrs(attrs, ["disabled", "readonly"])
     ``label`` -- a text label to display to the right of the button.
     
     The id of the radio button will be set to the name + '_' + value to 
-    ensure its uniqueness.  An ``id`` keyword arg overrides this.
+    ensure its uniqueness.  An ``id`` keyword arg overrides this.  (Note
+    that this behavior is unique to the ``radio()`` helper.)
     
     To arrange multiple radio buttons in a group, see
     webhelpers.containers.distribute().
     """
-    set_input_attrs(attrs, "radio", name, value)
+    _set_input_attrs(attrs, "radio", name, value)
     if checked:
         attrs["checked"] = "checked"
     if not "id" in attrs:
     return widget
 
 
-def submit(name, value, **attrs):
+def submit(name, value, id=None, **attrs):
     """Create a submit button with the text ``value`` as the caption."""
-    set_input_attrs(attrs, "submit", name, value)
+    _set_input_attrs(attrs, "submit", name, value)
+    _set_id_attr(attrs, id, name)
     return HTML.input(**attrs)
 
 
-def select(name, selected_values, options, **attrs):
+def select(name, selected_values, options, id=None, **attrs):
     """Create a dropdown selection box.
 
     * ``name`` -- the name of this control.
       to the application if that option is chosen.  If you pass a string or int
       instead of a 2-tuple, it will be used for both the value and the label.
 
+    ``id`` is the HTML ID attribute, and should be passed as a keyword
+    argument.  By default the ID is the same as the name.  filtered through
+    ``_make_safe_id_component()``.  Pass the empty string ("") to suppress the
+    ID attribute entirely.
+
+
       CAUTION: the old rails helper ``options_for_select`` had the label first.
       The order was reversed because most real-life collections have the value
       first, including dicts of the form ``{value: label}``.  For those dicts
     Examples (call, result)::
     
         >>> select("currency", "$", [["$", "Dollar"], ["DKK", "Kroner"]])
-        literal(u'<select name="currency">\\n<option selected="selected" value="$">Dollar</option>\\n<option value="DKK">Kroner</option>\\n</select>')
+        literal(u'<select id="currency" name="currency">\\n<option selected="selected" value="$">Dollar</option>\\n<option value="DKK">Kroner</option>\\n</select>')
         >>> select("cc", "MasterCard", [ "VISA", "MasterCard" ], id="cc", class_="blue")
         literal(u'<select class="blue" id="cc" name="cc">\\n<option value="VISA">VISA</option>\\n<option selected="selected" value="MasterCard">MasterCard</option>\\n</select>')
         >>> select("cc", ["VISA", "Discover"], [ "VISA", "MasterCard", "Discover" ])
-        literal(u'<select name="cc">\\n<option selected="selected" value="VISA">VISA</option>\\n<option value="MasterCard">MasterCard</option>\\n<option selected="selected" value="Discover">Discover</option>\\n</select>')
+        literal(u'<select id="cc" name="cc">\\n<option selected="selected" value="VISA">VISA</option>\\n<option value="MasterCard">MasterCard</option>\\n<option selected="selected" value="Discover">Discover</option>\\n</select>')
         >>> select("currency", None, [["$", "Dollar"], ["DKK", "Kroner"]], prompt="Please choose ...")
-        literal(u'<select name="currency">\\n<option selected="selected" value="">Please choose ...</option>\\n<option value="$">Dollar</option>\\n<option value="DKK">Kroner</option>\\n</select>')
+        literal(u'<select id="currency" name="currency">\\n<option selected="selected" value="">Please choose ...</option>\\n<option value="$">Dollar</option>\\n<option value="DKK">Kroner</option>\\n</select>')
         
     """
+    _set_id_attr(attrs, id, name)
     attrs["name"] = name
     convert_boolean_attrs(attrs, ["multiple"])
     # Accept None as selected_values meaning that no option is selected
         elif attrs.has_key(a):
             del attrs[a]
 
-def set_input_attrs(attrs, type, name, value):
+def _set_input_attrs(attrs, type, name, value):
     attrs["type"] = type
     attrs["name"] = name
     attrs["value"] = value
 
+def _set_id_attr(attrs, id, name):
+    if id is None:
+        attrs["id"] = _make_safe_id_component(name)
+    elif id != "":
+        attrs["id"] = id
+
 def _make_safe_id_component(idstring):
     """Make a string safe for including in an id attribute.
     
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.