Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

- Sequence widgets now accept a min_len and a max_len argument, which

  influences its display of close and add buttons.
  • Loading branch information...
commit f59fd332bdad640d6413774a80d3191bf1e19634 1 parent 211f599
@mcdonc mcdonc authored
View
3  CHANGES.txt
@@ -13,6 +13,9 @@ Next release
- Removed wufoo css, added a minimal css.
+- Sequence widgets now accept a min_len and a max_len argument, which
+ influences its display of close and add buttons.
+
0.8.1 (2010-12-17)
------------------
View
6 deform/static/css/form.css
@@ -19,9 +19,7 @@
padding: 3px;
}
-/* deform additions*/
-
-.deformClosebutton {
+.deformClosebuttonActive {
border: 0;
cursor: pointer;
float: left;
@@ -30,7 +28,7 @@
/* margin: .5em; */
}
-.deformClosebutton:before {
+.deformClosebuttonActive:before {
content:"";
color:red;
margin-right: 2px;
View
51 deform/static/scripts/deform.js
@@ -87,15 +87,54 @@ var deform = {
});
deform.clearCallbacks();
+ var old_len = parseInt(before.attributes['now_len'].value||'0');
+ before.attributes['now_len'].value = old_len + 1;
//deform.maybeScrollIntoView('#' + anchorid);
},
- appendSequenceItem: function(start_node) {
- deform.addSequenceItem(
- $(start_node).parent().children('.deformProto')[0],
- $(start_node).parent().children('.deformInsertBefore')[0]
- );
- return false;
+ appendSequenceItem: function(node) {
+ var $oid_node = $(node).parent();
+ var proto_node = $oid_node.find('.deformProto')[0];
+ var before_node = $oid_node.find('.deformInsertBefore')[0];
+ var min_len = parseInt(before_node.attributes['min_len'].value||'0');
+ var max_len = parseInt(before_node.attributes['max_len'].value||'9999');
+ var now_len = parseInt(before_node.attributes['now_len'].value||'0');
+ if (now_len < max_len) {
+ deform.addSequenceItem(proto_node, before_node);
+ deform.processSequenceButtons($oid_node, min_len, max_len,
+ now_len+1);
+ };
+ return false;
+ },
+
+ removeSequenceItem: function(clicked) {
+ debugger;
+ var $item_node = $(clicked).parent();
+ var $oid_node = $item_node.parent().parent();
+ var before_node = $oid_node.find('.deformInsertBefore')[0];
+ var min_len = parseInt(before_node.attributes['min_len'].value||'0');
+ var max_len = parseInt(before_node.attributes['max_len'].value||'9999');
+ var now_len = parseInt(before_node.attributes['now_len'].value||'0');
+ if (now_len > min_len) {
+ before_node.attributes['now_len'].value = now_len - 1;
+ $item_node.remove();
+ deform.processSequenceButtons($oid_node, min_len, max_len,
+ now_len-1);
+ };
+ return false;
+ },
+
+ processSequenceButtons: function(oid_node, min_len, max_len, now_len) {
+ var $ul = oid_node.children('ul');
+ var $lis = $ul.children('li');
+ $lis.find('.deformClosebutton').removeClass('deformClosebuttonActive');
+ oid_node.children('.deformSeqAdd').show();
+ if (now_len > min_len) {
+ $lis.find('.deformClosebutton').addClass('deformClosebuttonActive');
+ };
+ if (now_len >= max_len) {
+ oid_node.children('.deformSeqAdd').hide();
+ };
},
maybeScrollIntoView: function(element_id) {
View
31 deform/templates/sequence.pt
@@ -1,21 +1,31 @@
<div class="deformSeq"
+ id="${field.oid}"
tal:define="rndr field.renderer;
- tmpl field.widget.item_template">
+ item_tmpl field.widget.item_template;
+ min_len field.widget.min_len or 0;
+ max_len field.widget.max_len or 100000;
+ now_len len(subfields);
+ prototype field.widget.prototype(field)">
+
<!-- sequence -->
<input type="hidden" name="__start__" value="${field.name}:sequence"
class="deformProto"
- tal:attributes="prototype field.widget.prototype(field)"/>
+ tal:attributes="prototype prototype"/>
<ul>
<div tal:repeat="tup subfields"
- tal:replace="structure rndr(tmpl, field=tup[1], cstruct=tup[0],
+ tal:replace="structure rndr(item_tmpl, field=tup[1], cstruct=tup[0],
parent=field)"/>
+
+ <span class="deformInsertBefore"
+ tal:attributes="min_len min_len;
+ max_len max_len;
+ now_len now_len"></span>
+
</ul>
- <span class="deformInsertBefore"></span>
-
<a href="#"
class="deformSeqAdd"
id="${field.oid}-seqAdd"
@@ -23,6 +33,17 @@
<small id="${field.oid}-addtext">${add_subitem_text}</small>
</a>
+ <script type="text/javascript">
+ deform.addCallback(
+ '${field.oid}',
+ function(oid) {
+ oid_node = $('#'+ oid);
+ deform.processSequenceButtons(oid_node, ${min_len},
+ ${max_len}, ${now_len});
+ }
+ )
+ </script>
+
<input type="hidden" name="__end__" value="${field.name}:sequence"/>
<!-- /sequence -->
View
2  deform/templates/sequence_item.pt
@@ -7,7 +7,7 @@
id="${field.oid}-close"
tal:condition="not field.widget.hidden"
title="Remove"
- onclick="javascript:$(this).parent().remove();"></span>
+ onclick="javascript:deform.removeSequenceItem(this);"></span>
<span tal:replace="structure field.serialize(cstruct)"/>
View
32 deform/tests/test_widget.py
@@ -1099,6 +1099,38 @@ def test_serialize_null_render_initial_item(self):
self.assertEqual(renderer.kw['cstruct'], [null])
self.assertEqual(renderer.template, widget.template)
+ def test_serialize_null_min_len_larger_than_cstruct(self):
+ from colander import null
+ renderer = DummyRenderer('abc')
+ schema = DummySchema()
+ field = DummyField(schema, renderer)
+ inner = DummyField()
+ field.children=[inner]
+ widget = self._makeOne()
+ widget.min_len = 2
+ result = widget.serialize(field, ['abc'])
+ self.assertEqual(result, 'abc')
+ self.assertEqual(len(renderer.kw['subfields']), 2)
+ self.assertEqual(renderer.kw['field'], field)
+ self.assertEqual(renderer.kw['cstruct'], ['abc', null])
+ self.assertEqual(renderer.template, widget.template)
+
+ def test_serialize_null_min_one(self):
+ from colander import null
+ renderer = DummyRenderer('abc')
+ schema = DummySchema()
+ field = DummyField(schema, renderer)
+ inner = DummyField()
+ field.children=[inner]
+ widget = self._makeOne()
+ widget.min_len = 1
+ result = widget.serialize(field, null)
+ self.assertEqual(result, 'abc')
+ self.assertEqual(len(renderer.kw['subfields']), 1)
+ self.assertEqual(renderer.kw['field'], field)
+ self.assertEqual(renderer.kw['cstruct'], [null])
+ self.assertEqual(renderer.template, widget.template)
+
def test_serialize_add_subitem_value(self):
from colander import null
renderer = DummyRenderer('abc')
View
53 deform/widget.py
@@ -842,8 +842,7 @@ class FormWidget(MappingWidget):
readonly_template = 'readonly/form'
class SequenceWidget(Widget):
- """
- Renders a sequence (0 .. N widgets, each the same as the other)
+ """Renders a sequence (0 .. N widgets, each the same as the other)
into a set of fields.
**Attributes/Arguments**
@@ -877,10 +876,24 @@ class SequenceWidget(Widget):
Default: ``Add ${subitem_title}``.
render_initial_item
- Boolean attribute indicating whether, on the first rendering
- of a form including a sequence widget, a child widget
- rendering should be performed. Default: ``False``.
-
+ Deprecated boolean attribute indicating whether, on the first
+ rendering of a form including this sequence widget, a single child
+ widget rendering should be performed. Default: ``False``. This
+ attribute is honored for backwards compatibility only: in new
+ applications, please use ``min_len=1`` instead.
+
+ min_len
+ Integer indicating minimum number of acceptable subitems. Default:
+ ``None`` (meaning no minimum). On the first rendering of a form
+ including this sequence widget, at least this many subwidgets will be
+ rendered. The JavaScript sequence management will not allow fewer
+ than this many subwidgets to be present in the sequence.
+
+ max_len
+ Integer indicating maximum number of acceptable subwidgets. Default:
+ ``None`` (meaning no maximum). The JavaScript sequence management
+ will not allow more than this many subwidgets to be added to the
+ sequence.
"""
template = 'sequence'
readonly_template = 'readonly/sequence'
@@ -889,6 +902,8 @@ class SequenceWidget(Widget):
error_class = None
add_subitem_text_template = _('Add ${subitem_title}')
render_initial_item = False
+ min_len = None
+ max_len = None
requirements = ( ('deform', None), )
def prototype(self, field):
@@ -903,18 +918,27 @@ def prototype(self, field):
return proto
def serialize(self, field, cstruct, readonly=False):
+ if (self.render_initial_item and self.min_len is None):
+ # This is for compat only: ``render_initial_item=True`` should
+ # now be spelled as ``min_len = 1``
+ self.min_len = 1
+
if cstruct in (null, None):
- if self.render_initial_item:
- cstruct = [null]
+ if self.min_len is not None:
+ cstruct = [null] * self.min_len
else:
cstruct = []
+ cstructlen = len(cstruct)
+
+ if self.min_len is not None and (cstructlen < self.min_len):
+ cstruct = list(cstruct) + ([null] * (self.min_len-cstructlen))
+
item_field = field.children[0]
if getattr(field, 'sequence_fields', None):
- # this serialization is assumed to be performed as a
- # result of a validation failure (``deserialize`` was
- # previously run)
+ # this serialization is being performed as a result of a
+ # validation failure (``deserialize`` was previously run)
assert(len(cstruct) == len(field.sequence_fields))
subfields = zip(cstruct, field.sequence_fields)
else:
@@ -929,8 +953,11 @@ def serialize(self, field, cstruct, readonly=False):
subitem_name=item_field.name)
add_subitem_text = _(self.add_subitem_text_template,
mapping=add_template_mapping)
- return field.renderer(template, field=field, cstruct=cstruct,
- subfields=subfields, item_field=item_field,
+ return field.renderer(template,
+ field=field,
+ cstruct=cstruct,
+ subfields=subfields,
+ item_field=item_field,
add_subitem_text=add_subitem_text)
def deserialize(self, field, pstruct):
View
28 deformdemo/app.py
@@ -534,8 +534,7 @@ class Schema(colander.Schema):
uploads = Sequence()
schema = Schema()
form = deform.Form(schema, buttons=('submit',))
- form['uploads'].widget = deform.widget.SequenceWidget(
- render_initial_item=True)
+ form['uploads'].widget = deform.widget.SequenceWidget(min_len=1)
return self.render_form(form, success=tmpstore.clear)
@bfg_view(renderer='templates/form.pt', name='sequence_of_mappings')
@@ -567,8 +566,7 @@ class Schema(colander.Schema):
people = People()
schema = Schema()
form = deform.Form(schema, buttons=('submit',))
- form['people'].widget = deform.widget.SequenceWidget(
- render_initial_item=True)
+ form['people'].widget = deform.widget.SequenceWidget(min_len=1)
return self.render_form(form)
@bfg_view(renderer='templates/form.pt',
@@ -609,12 +607,28 @@ class Schema(colander.Schema):
schema = Schema()
form = deform.Form(schema, buttons=('submit',))
outer = form['names_and_titles_sequence']
- outer.widget = deform.widget.SequenceWidget(
- render_initial_item=True)
+ outer.widget = deform.widget.SequenceWidget(min_len=1)
outer['names_and_titles'].widget = deform.widget.SequenceWidget(
- render_initial_item=True)
+ min_len=1)
return self.render_form(form)
+ @bfg_view(renderer='templates/form.pt', name='sequence_of_constrained_len')
+ @demonstrate('Sequence of Constrained Min and Max Lengths')
+ def sequence_of_constrained_len(self):
+ class Names(colander.SequenceSchema):
+ name = colander.SchemaNode(colander.String())
+ class Schema(colander.Schema):
+ names = Names(
+ validator = colander.Length(2, 4),
+ title = 'At Least 2 At Most 4 Names',
+ widget=deform.widget.SequenceWidget(
+ min_len=2,
+ max_len=4)
+ )
+ schema = Schema()
+ form = deform.Form(schema, buttons=('submit',))
+ return self.render_form(form)
+
@bfg_view(renderer='templates/form.pt', name='file')
@demonstrate('File Upload Widget')
def file(self):
View
1  deformdemo/templates/main.pt
@@ -15,7 +15,6 @@
<link rel="stylesheet" href="${app_url}/static_demo/pylons.css" type="text/css" />
<link rel="stylesheet" href="${app_url}/static_demo/deformdemo.css" type="text/css" />
<link rel="stylesheet" href="${app_url}/pygments.css" type="text/css" />
- <link rel="stylesheet" href="${app_url}/pylon.css" type="text/css" />
<tal:block repeat="reqt css_links|[]">
<link rel="stylesheet" href="${static}/${reqt}" type="text/css" />
</tal:block>
Please sign in to comment.
Something went wrong with that request. Please try again.