Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Allow SelectWidget to produce <optgroup> HTML tags. #87

Merged
merged 1 commit into from

3 participants

@dbaty

With the attached patch, it is now possible to display <optgroup> HTML tags with SelectWidget. The widget accepts a long_label_generator attribute/argument that can be used to generate a "label" attribute on the <option> tags for very old browsers that do not support the <optgroup> tag.

A few notes:

  1. There is probably no use-case where one would want to customize the optgroup_class attribute/argument. It is there only to make the OptGroup class available in the template.

  2. With the :members: option of the autoclass directive, Sphinx automatically generates a line of documentation for optgroup_class: "Alias of OptGroup". Because this is rather useless and we already document it ourselves, I removed the :members: option. I did not notice any side-effect.

  3. I took the liberty of changing the example for the two-tuples in values. I find the new example slightly clearer than the previous one (('true', 'True')).

Thanks to Jeff Dairiki who provided feedback on my previous attempt and suggested the OptGroup class and the long_label_generator option.

@kiorky
Collaborator

Thx for your contribution !
Can you add documentation and tests directly to deform and deformdemo codebases, please ?

@dbaty dbaty referenced this pull request in Pylons/deformdemo
Merged

Add views and tests for the new optgroup feature in deform #10

@dbaty

I have rebased my branch (it was old and was lagging behind master). It contains tests and documentation for "deform". As for "deformdemo", I have created another pull request (Pylons/deformdemo#10) that contains views and tests.

@mcdonc mcdonc merged commit a30267d into from
@mcdonc
Owner

Thanks, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 22, 2012
  1. @dbaty

    Allow SelectWidget to produce <optgroup> HTML tags.

    Damien Baty authored dbaty committed
This page is out of date. Refresh to see the latest.
View
21 deform/templates/readonly/select.pt
@@ -1,5 +1,16 @@
-<p tal:repeat="(value, description) values" i18n:domain="deform">
- <span>${description}</span>
- <em tal:condition="value == cstruct" i18n:translate="">Selected</em>
- <em tal:condition="value != cstruct" i18n:translate="">Not Selected</em>
-</p>
+<tal:loop tal:repeat="item values" i18n:domain="deform">
+ <tal:if tal:condition="isinstance(item, field.widget.optgroup_class)">
+ <p tal:condition="not field.widget.long_label_generator"
+ class="optgroup">${item.label}</p>
+ <p tal:repeat="(value, description) item.options">
+ <span tal:content="field.widget.long_label_generator and field.widget.long_label_generator(item.label, description) or description"/>
+ <em tal:condition="value == cstruct" i18n:translate="">Selected</em>
+ <em tal:condition="value != cstruct" i18n:translate="">Not Selected</em>
+ </p>
+ </tal:if>
+ <p tal:condition="not isinstance(item, field.widget.optgroup_class)">
+ <span>${item[1]}</span>
+ <em tal:condition="item[0] == cstruct" i18n:translate="">Selected</em>
+ <em tal:condition="item[0] != cstruct" i18n:translate="">Not Selected</em>
+ </p>
+</tal:loop>
View
31 deform/templates/select.pt
@@ -1,14 +1,27 @@
<input type="hidden" name="__start__" value="${field.name}:sequence"
tal:condition="field.widget.multiple" />
-<select name="${field.name}"
- id="${field.oid}"
- tal:attributes="size field.widget.size;
- class field.widget.css_class;
- multiple field.widget.multiple">
- <option tal:repeat="(value, description) values"
- tal:attributes="selected (field.widget.multiple and value in cstruct or value == cstruct) and 'selected';
- class field.widget.css_class"
- value="${value}">${description}</option>
+<select tal:attributes="name field.name;
+ id field.oid;
+ size field.widget.size;
+ class field.widget.css_class;
+ multiple field.widget.multiple">
+ <tal:loop tal:repeat="item values">
+ <optgroup tal:condition="isinstance(item, field.widget.optgroup_class)"
+ tal:attributes="label item.label">
+ <option tal:repeat="(value, description) item.options"
+ tal:attributes="
+ selected (field.widget.multiple and value in cstruct or value == cstruct) and 'selected';
+ class field.widget.css_class;
+ label field.widget.long_label_generator and description;
+ value value"
+ tal:content="field.widget.long_label_generator and field.widget.long_label_generator(item.label, description) or description"/>
+ </optgroup>
+ <option tal:condition="not isinstance(item, field.widget.optgroup_class)"
+ tal:attributes="
+ selected (field.widget.multiple and item[0] in cstruct or item[0] == cstruct) and 'selected';
+ class field.widget.css_class;
+ value item[0]">${item[1]}</option>
+ </tal:loop>
</select>
<input type="hidden" name="__end__" value="${field.name}:sequence"
tal:condition="field.widget.multiple" />
View
10 deform/tests/test_widget.py
@@ -1795,6 +1795,16 @@ def test_integer(self):
self.assertEqual(self._call(((1, 'description'),)),
[('1', 'description')])
+ def test_optgroup_and_tuple(self):
+ from deform.widget import OptGroup
+ optgroup = OptGroup('label', (2, 'two'))
+ normalized = self._call(((1, 'description'), optgroup))
+ self.assertEqual(len(normalized), 2)
+ self.assertEqual(normalized[0], ('1', 'description'))
+ self.assertTrue(isinstance(normalized[1], OptGroup))
+ self.assertEqual(normalized[1].label, 'label')
+ self.assertEqual(normalized[1].options, (('2', 'two'), ))
+
class DummyRenderer(object):
def __init__(self, result=''):
self.result = result
View
99 deform/widget.py
@@ -21,10 +21,15 @@
def _normalize_choices(values):
result = []
- for value, description in values:
- if not isinstance(value, string_types):
- value = str(value)
- result.append((value, description))
+ for item in values:
+ if isinstance(item, OptGroup):
+ normalized_options = _normalize_choices(item.options)
+ result.append(OptGroup(item.label, *normalized_options))
+ else:
+ value, description = item
+ if not isinstance(value, string_types):
+ value = str(value)
+ result.append((value, description))
return result
class Widget(object):
@@ -709,6 +714,27 @@ def deserialize(self, field, pstruct):
return self.false_val
return (pstruct == self.true_val) and self.true_val or self.false_val
+class OptGroup(object):
+ """
+ Used in the ``values`` argument passed to an instance of
+ ``SelectWidget`` to render an ``<optgroup>`` HTML tag.
+
+ **Attributes/Arguments**
+
+ label
+ The label of the ``<optgroup>`` HTML tag.
+
+ options
+ A sequence that describes the ``<options>`` HTML tag(s). It
+ must have the same structure as the ``values``
+ argument/parameter in the ``SelectWidget`` class, but should
+ not contain ``OptGroup`` instances since ``<optgroup>`` HTML
+ tags cannot be nested.
+ """
+ def __init__(self, label, *options):
+ self.label = label
+ self.options = options
+
class SelectWidget(Widget):
"""
Renders ``<select>`` field based on a predefined set of values.
@@ -716,12 +742,17 @@ class SelectWidget(Widget):
**Attributes/Arguments**
values
- A sequence of two-tuples (the first value must be of type
- string, unicode or integer, the second value must be string or
- unicode) indicating allowable, displayed values, e.g. ``(
- ('true', 'True'), ('false', 'False') )``. The first element
- in the tuple is the value that should be returned when the
- form is posted. The second is the display value.
+ A sequence of items where each item must be either:
+
+ - a two-tuple (the first value must be of type string, unicode
+ or integer, the second value must be string or unicode)
+ indicating allowable, displayed values, e.g. ``('jsmith',
+ 'John Smith')``. The first element in the tuple is the value
+ that should be returned when the form is posted. The second
+ is the display value;
+
+ - or an instance of ``optgroup_class`` (which is
+ ``deform.widget.OptGroup`` by default).
size
The ``size`` attribute of the select input field (default:
@@ -743,6 +774,52 @@ class SelectWidget(Widget):
multiple
Enable multiple on the select widget ( default: ``False`` )
+
+ optgroup_class
+ The class used to represent ``<optgroup>`` HTML tags. Default:
+ ``deform.widget.OptGroup``.
+
+ long_label_generator
+ A function that returns the "long label" used as the
+ description for very old browsers that do not support the
+ ``<optgroup>`` HTML tag. If a function is provided, the
+ ``label`` attribute will receive the (short) description,
+ while the content of the ``<option>`` tag will receive the
+ "long label". The function is called with two parameters: the
+ group label and the option (short) description.
+
+ For example, with the following widget:
+
+ .. code-block:: python
+
+ long_label_gener = lambda group, label: ' - '.join((group, label))
+ SelectWidget(
+ values=(
+ ('', 'Select your favorite musician'),
+ OptGroup('Guitarists',
+ ('page', 'Jimmy Page'),
+ ('hendrix', 'Jimi Hendrix')),
+ OptGroup('Drummers',
+ ('cobham', 'Billy Cobham'),
+ ('bonham', 'John Bonham'))),
+ long_label_generator=long_label_gener)
+
+ ... the rendered options would look like:
+
+ .. code-block:: html
+
+ <option value="">Select your favorite musician</option>
+ <optgroup label="Guitarists">
+ <option value="page" label="Jimmy Page">Guitarists - Jimmy Page</option>
+ <option value="hendrix" label="Jimi Hendrix">Guitarists - Jimi Hendrix</option>
+ </optgroup>
+ <optgroup label="Drummers">
+ <option value="cobham" label="Billy Cobham">Drummers - Billy Cobham</option>
+ <option value="bonham" label="John Bonham">Drummers - John Bonham</option>
+ </optgroup>
+
+ Default: ``None`` (which means that the ``label`` attribute is
+ not rendered).
"""
template = 'select'
readonly_template = 'readonly/select'
@@ -750,6 +827,8 @@ class SelectWidget(Widget):
values = ()
size = None
multiple = False
+ optgroup_class = OptGroup
+ long_label_generator = None
def serialize(self, field, cstruct, readonly=False):
if cstruct in (null, None):
View
6 docs/api.rst
@@ -92,9 +92,13 @@ Widget-Related
.. autoclass:: CheckboxChoiceWidget
:members:
-.. autoclass:: SelectWidget
+.. autoclass:: OptGroup
:members:
+.. autoclass:: SelectWidget
+.. no ':members:' here if we can avoid it, as it would generate
+.. a duplicate entry for 'SelectWidget.optgroup_class'.
+
.. autoclass:: RadioChoiceWidget
:members:
Something went wrong with that request. Please try again.