Skip to content

Commit

Permalink
Change the radio control workaround; it was broken when used in seque…
Browse files Browse the repository at this point in the history
…nces still.
  • Loading branch information
mcdonc committed Sep 2, 2010
1 parent dc863f1 commit c52f7d0
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 80 deletions.
19 changes: 13 additions & 6 deletions CHANGES.txt
Expand Up @@ -58,9 +58,11 @@ Features
as a ``values`` input, the remote URL must return a JSON structure
instead of a ``\n``-delimited list of values.

- It is now acceptable to use the field *oid* as the ``name`` of an
input element that is part of a mapping instead of its name (its
name also continues to work).
- The ``name`` of an input element that is part of a widget used as a
subwidget of mapping widget may now include the special suffix
``-###<anything>``. ``<anything>`` means any character. This
allows radio button choice widgets to work. For more information,
see "Radio Control Workarounds" in the "Widgets" chapter.

Requirements
~~~~~~~~~~~~
Expand Down Expand Up @@ -88,9 +90,11 @@ Bug Fixes

- To make radio choice widgets work within sequences, the
deform.addSequenceItem JavaScript method needed to be changed. It
will now change the value of ``name`` attributes which include a
field marker (e.g. ``deformField1``) in the same way it already
changed the value of ``id`` attributes which include a field marker.
will now change the value of ``name`` attributes which contain a
``-###`` field marker (e.g. ``email-###-deformField1``) to include a
random component (e.g. ``email-###-s7Ys8W-deformField``). See the
section entited "Radio Control Workarounds" in the "Widgets" chapter
for more information as to why this needs be done.

- The mapping and sequence item templates now correctly display errors
with ``msg`` values that are lists. Previously, a repr of a Python
Expand Down Expand Up @@ -167,6 +171,9 @@ Documentation
- Add new top-level sections named ``Widget Templates`` and ``Widget
JavaScript`` to the "Widgets" chapter.

- Added a section named "Radio Control Workarounds" to the
"Widgets" chapter.

0.5 (2010-08-25)
----------------

Expand Down
6 changes: 4 additions & 2 deletions deform/static/scripts/deform.js
Expand Up @@ -40,6 +40,7 @@ var deform = {
// htmlFor attribute to the new id.

var fieldmatch = /deformField(\d+)/;
var namematch = /(.+)?-[#]{3}/;
var code = protonode.attributes['prototype'].value;
var html = decodeURIComponent(code);
var $htmlnode = $(html);
Expand All @@ -62,12 +63,13 @@ var deform = {
$fornodes.attr('htmlFor', newid);
});

// replace names containing ``deformField`` (radio and checkbox choices)
// replace names a containing ``###`` marker (radio button
// names usually)

$namednodes.each(function(idx, node) {
var $node = $(node);
var oldname = $node.attr('name');
var newname = oldname.replace(fieldmatch, "deformField$1-" + genid);
var newname = oldname.replace(namematch, "$1-###-" + genid);
$node.attr('name', newname);
});

Expand Down
2 changes: 1 addition & 1 deletion deform/templates/radio_choice.pt
Expand Up @@ -6,7 +6,7 @@
<input tal:attributes="checked value == cstruct;
class field.widget.css_class"
type="radio"
name="${field.oid}"
name="${field.name}-###-${field.oid}"
value="${value}"
id="${field.oid}-${repeat.choice.index}"/>
<label for="${field.oid}-${repeat.choice.index}">${title}</label>
Expand Down
19 changes: 2 additions & 17 deletions deform/tests/test_widget.py
Expand Up @@ -999,31 +999,16 @@ def test_deserialize_non_null(self):
result = widget.deserialize(field, pstruct)
self.assertEqual(result, {'a':1})

def test_deserialize_name_is_oid(self):
def test_deserialize_name_contains_dash_triplehash(self):
widget = self._makeOne()
field = DummyField()
inner_field = DummyField()
inner_field.name = 'a'
inner_field.oid = 'deformField12'
inner_widget = DummyWidget()
inner_widget.name = 'a'
inner_field.widget = inner_widget
field.children = [inner_field]
pstruct = {'deformField12':1}
result = widget.deserialize(field, pstruct)
self.assertEqual(result, {'a':1})

def test_deserialize_name_is_fuzzy_oid(self):
widget = self._makeOne()
field = DummyField()
inner_field = DummyField()
inner_field.name = 'a'
inner_field.oid = 'deformField12'
inner_widget = DummyWidget()
inner_widget.name = 'a'
inner_field.widget = inner_widget
field.children = [inner_field]
pstruct = {'deformField12-H7Sh27':1}
pstruct = {'a-###':1}
result = widget.deserialize(field, pstruct)
self.assertEqual(result, {'a':1})

Expand Down
77 changes: 43 additions & 34 deletions deform/widget.py
Expand Up @@ -800,46 +800,55 @@ def deserialize(self, field, pstruct):

for num, subfield in enumerate(field.children):
name = subfield.name
oid = subfield.oid
subval = pstruct.get(name, null)
if subval is null:
# Golly, HTML forms are so much fun! Sit down and let
# me tell you a little story. Get a blanket and some
# hot cocoa.
#
# Some widgets (radio choice widgets) use HTML
# elements where the input ``name`` attribute is used
# as a grouping key. When controls from two unrelated
# widgets have the same ``name``, HTML selection of an
# element from this grouping will break (the
# selections made from one logical grouping will cause
# "another's" to change). And indeed, when a form is
# generated from a complex schema, there may be more
# than one unrelated control on the form with the same
# ``name`` value, and it may happen that both are
# controls that are part of a grouping. To work
# around this, we allow widgets to use ``field.oid``
# (e.g. ``deformField2``) rather than the field name
# as a ``name`` value (e.g. ``pepper``) in its
# constituent controls.
subval = pstruct.get(oid, null)
if subval is null:
# But wait! The fun doesn't end there! When a widget
# uses a control that includes an oid in its name, and
# that widget is added to a *sequence*, that control's
# ``name`` value is changed during the
# ``deform.addSequenceItem`` JavaScript function in
# order to prevent the unrelated-control-group-by-name
# problem problem described above. Each ``name``
# value is mutated so that e.g. ``deformField2``
# becomes ``deformField2-H7gshj``. We cope with that
# here by comparing a prefix rather than looking for
# only ``oid`` as a pstruct key.
prefix = oid + '-'
for k, v in pstruct.items():
if (k.startswith(prefix)):
subval = v
break
# Radio button controls in HTML must have a ``name``
# attribute; its value is used as a grouping key.
# When two radio controls from two unrelated widgets
# have the same ``name``, HTML selection of an element
# from this grouping will break (the selections made
# from one logical grouping will cause "another
# widget's buttons" to change). And indeed, when a
# form is generated from an arbitrary schema, there
# may very well be two unrelated controls on the form
# with the same ``name`` value, and it may happen that
# both are radio button controls. Therefore, it's
# Deform's job to provide facilities so that radio
# button controls may be given a form-unambiguous
# ``name`` value, so that their browser selection
# behavior is not broken.
#
# However, meanwhile, the ``name`` attribute of every
# form control is *also* submitted as the key portion
# of the value sent to the server within the form
# submission. Usually, it suffices within any
# particular pstruct for this ``name`` to be simply
# the field's ``name`` attribute; the mapping widget
# will find that field's substructure by looking up
# its ``name`` in the mapping's pstruct dict. But due
# to the radio button behavior detailed above, it is
# not possible to rely on the ``name`` value of a
# particular radio button group to be the same as the
# ``name`` of its Deform field.
#
# To work around this, we allow widgets to use a
# special ``name`` value for an input control. These
# specially named controls must contain the string
# ``-###``. Characters to the left of ``-###`` in the
# ``name`` must represent the "real" field name, and
# characters to the right of ``-###`` are ignored.
# ``pepper-###`` or ``pepper-###-Uw7jdh`` are both
# examples of valid specially-named fields.
name_marker = '-###'
for k, v in pstruct.items():
idx = k.find(name_marker)
if idx != -1 and k[:idx] == name:
subval = v
break

try:
result[name] = subfield.deserialize(subval)
Expand Down
90 changes: 70 additions & 20 deletions docs/widget.rst
Expand Up @@ -540,24 +540,46 @@ value as the ``name`` input element of the primary control in the
widget, and the ``field.oid`` value as the ``id`` element of the
primary control in the widget.

Grouping Elements
+++++++++++++++++

When some HTML controls (radio controls, in particular) are used, the
value of the control ``name`` attribute across multiple controls is
used as a grouping key.

When controls from two unrelated widgets have the same ``name``, HTML
selection of an element from this grouping will break (the selections
made from one logical grouping will cause "another's" to change).
When a form is generated from a complex schema, there may be more than
one unrelated control on the form with the same ``name`` value, and it
may happen that both are controls that are part of a grouping.

Because this would cause user input to not work properly, to work
around this, a widget template may use the value of ``field.oid``
rather than the value of ``field.name`` as a ``name`` attribute value.
For example, rather than this:
Radio Control Workarounds
+++++++++++++++++++++++++

Radio button controls in HTML must have a ``name`` attribute; the
value of a radio button's ``name`` attribute is used as a grouping
key. Radio button groups are identified as radio buttons which share
the same ``name`` value.

When two radio controls from two unrelated Deform widgets have the
same ``name``, HTML selection of an element from this grouping will
break (the selections made from one logical grouping will cause
"another widget's buttons" to change). And indeed, when a form is
generated from an arbitrary schema, there may very well be two
unrelated controls on the form with the same ``name`` value, and it
may happen that both are radio button controls. Therefore, it's
Deform's job to provide facilities so that radio button controls may
be designated a form-unambiguous ``name`` value, so that their browser
selection behavior is not broken.

However, meanwhile, the ``name`` attribute of every form control is
*also* submitted as the key portion of the value sent to the server
within the form submission. Usually, it suffices within any
particular :term:`pstruct` for this ``name`` to be simply the field's
``name`` attribute; a containing mapping widget will find the
field's substructure by looking up its ``name`` in the mapping's
pstruct dict. But due to the radio button behavior detailed above, it
is not possible to rely on the ``name`` value of a particular radio
button group to be the same as the ``name`` of its Deform field.

To work around this, Deform allows widgets which are used as
subwidgets of a mapping (or form) widget to use a special ``name``
value for an input control. These specially named controls must
contain the string ``-###``. Characters to the left of ``-###`` in
the ``name`` must represent the "real" field name, and characters to
the right of ``-###`` are ignored. ``pepper-###`` or
``pepper-###-Uw7jdh`` are both examples of valid specially-named
fields.

This means, for example, rather than using ``name`` in a template as
the value of a radio button control's ``name``, like this:

.. code-block:: xml
:linenos:
Expand All @@ -567,14 +589,42 @@ For example, rather than this:
value="someval"
id="${field.oid}"/>
A widget template might do this:
A widget template that renders radio button controls should do this:

.. code-block:: xml
:linenos:
<input type="radio"
name="${field.oid}"
name="${field.name}-###-${field.oid}"
value="someval"
id="${field.oid}"/>
The widget template used ``${field.name}-###-${field.oid}`` as the
value of the ``name`` attribute of the control because:

#. The control ``name`` attribute must begin with the "real" field
name, thus it starts with ``${field.name}``.

#. The control ``name`` attribute must contain the marker ``-###`` in
it (preceded by the "real" field name) in order to be recognized as
a specially named field by the mapping widget. Thus it has that
string within it, directly after ``${field.name}``.

#. The control ``name`` attribute must not be ambiguous when rendered
into a form, to allow radio groupings to work independently. Thus
``-${field.oid}`` is included at the end of the ``name`` attribute
because the oid is a value unique to the field within all the
controls rendered by any widget on the form. Anything to the right
of ``-###`` is ignored at submit time, so it's not meaningful
during deserialization; it exists purely to allow browser selection
of radio buttons to group properly.

When an input element that has a name with ``-###`` in it is used as a
member of a sequence, when the element is added to the DOM, its
``name`` value will be further mutated: the ``-###`` string will be
replaced with ``-###-<randomid>``. This suffices to disambiguate the
control sufficiently when it is used within the prototype of a
sequence widget.



0 comments on commit c52f7d0

Please sign in to comment.