Updated inner sub-forms management
authorThierry Florac <tflorac@ulthar.net>
Sat, 28 Feb 2015 15:10:46 +0100
changeset 2 0f135e3c84b1
parent 1 dfa0508e4055
child 3 9dbd2ca24a55
Updated inner sub-forms management
src/pyams_form/form.py
src/pyams_form/group.py
src/pyams_form/templates/form.pt
src/pyams_form/templates/inner-form.pt
src/pyams_form/templates/widget-form.pt
--- a/src/pyams_form/form.py	Sat Feb 28 13:33:20 2015 +0100
+++ b/src/pyams_form/form.py	Sat Feb 28 15:10:46 2015 +0100
@@ -18,7 +18,7 @@
 
 # import interfaces
 from pyams_form.interfaces.form import IFormLayer, IForm, IAJAXForm, IInnerSubForm, IInnerTabForm, \
-    ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent
+    ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent, IInnerForm
 from pyams_form.interfaces.form import IAddFormButtons, IModalAddFormButtons, IEditFormButtons, \
     IModalEditFormButtons, IModalDisplayFormButtons
 from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent
@@ -30,6 +30,7 @@
 
 # import packages
 from pyams_form.group import GroupsBasedForm
+from pyams_pagelet.interfaces import PageletCreatedEvent
 from pyams_skin.skin import apply_skin
 from pyramid.decorator import reify
 from pyramid.events import subscriber
@@ -40,7 +41,7 @@
 from z3c.form.form import applyChanges, \
     Form, AddForm as BaseAddForm, EditForm as BaseEditForm, DisplayForm as BaseDisplayForm
 from zope.component import queryUtility
-from zope.interface import implementer, alsoProvides
+from zope.interface import implementer, alsoProvides, Interface
 from zope.lifecycleevent import Attributes, ObjectCreatedEvent
 from zope.schema.fieldproperty import FieldProperty
 
@@ -104,13 +105,15 @@
     @reify
     def subforms(self):
         registry = self.request.registry
-        return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerSubForm)),
+        return sorted((adapter[1]
+                       for adapter in registry.getAdapters((self.context, self.request, self), IInnerSubForm)),
                       key=get_form_weight)
 
     @reify
     def tabforms(self):
         registry = self.request.registry
-        return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerTabForm)),
+        return sorted((adapter[1]
+                       for adapter in registry.getAdapters((self.context, self.request, self), IInnerTabForm)),
                       key=get_form_weight)
 
     @property
@@ -273,8 +276,13 @@
     """AJAX add form"""
 
     def __call__(self):
-        self.updateWidgets()
-        data, errors = self.extractData()
+        self.request.registry.notify(PageletCreatedEvent(self))
+        data, errors = {}, ()
+        for form in self.forms:
+            form.updateWidgets()
+            form_data, form_errors = form.extractData()
+            data.update(form_data)
+            errors = errors + form_errors
         if errors or self.errors:
             return self.get_ajax_errors()
         result = self.createAndAdd(data)
@@ -293,6 +301,24 @@
     dialog_class = 'modal-medium'
 
 
+@implementer(IInnerForm)
+class InnerAddForm(AddForm):
+    """Inner add form"""
+
+    @property
+    def id(self):
+        return self.__class__.__name__
+
+    buttons = Buttons(Interface)
+
+    def __init__(self, context, request, view):
+        super(InnerAddForm, self).__init__(context, request)
+        self.parent_form = view
+
+    def get_form_action(self):
+        return None
+
+
 #
 # Edit forms
 #
@@ -327,8 +353,13 @@
     """AJAX edit form"""
 
     def __call__(self):
-        self.updateWidgets()
-        data, errors = self.extractData()
+        self.request.registry.notify(PageletCreatedEvent(self))
+        data, errors = {}, ()
+        for form in self.forms:
+            form.updateWidgets()
+            form_data, form_errors = form.extractData()
+            data.update(form_data)
+            errors = errors + form_errors
         if errors or self.errors:
             return self.get_ajax_errors()
         changes = self.applyChanges(data)
@@ -354,6 +385,24 @@
     dialog_class = 'modal-medium'
 
 
+@implementer(IInnerForm)
+class InnerEditForm(EditForm):
+    """Inner edit form"""
+
+    @property
+    def id(self):
+        return self.__class__.__name__
+
+    buttons = Buttons(Interface)
+
+    def __init__(self, context, request, view):
+        super(InnerEditForm, self).__init__(context, request)
+        self.parent_form = view
+
+    def get_form_action(self):
+        return None
+
+
 #
 # Display forms
 #
--- a/src/pyams_form/group.py	Sat Feb 28 13:33:20 2015 +0100
+++ b/src/pyams_form/group.py	Sat Feb 28 15:10:46 2015 +0100
@@ -30,6 +30,7 @@
 
     id = FieldProperty(IFormWidgetsGroup['id'])
     widgets = FieldProperty(IFormWidgetsGroup['widgets'])
+    bordered = FieldProperty(IFormWidgetsGroup['bordered'])
     legend = FieldProperty(IFormWidgetsGroup['legend'])
     help = FieldProperty(IFormWidgetsGroup['help'])
     _css_class = FieldProperty(IFormWidgetsGroup['css_class'])
@@ -39,11 +40,12 @@
     checkbox_widget = FieldProperty(IFormWidgetsGroup['checkbox_widget'])
     hide_if_empty = FieldProperty(IFormWidgetsGroup['hide_if_empty'])
 
-    def __init__(self, id, widgets=None, legend=None, help=None, css_class='', switch=False,
+    def __init__(self, id, widgets=None, bordered=True, legend=None, help=None, css_class='', switch=False,
                  checkbox_switch=False, checkbox_field=None, hide_if_empty=False):
         assert (not checkbox_switch) or checkbox_field, "You must define checkbox field when using checkbox switch"
         self.id = id
         self.widgets = widgets or []
+        self.bordered = bordered
         self.legend = id if legend is None else legend
         self.help = help
         self._css_class = css_class
@@ -67,7 +69,7 @@
         if self.checkbox_field is None:
             return None
         for widget in self.widgets:
-            if widget.field is self.checkbox_field.field:
+            if widget.field is self.checkbox_field:
                 return widget
 
     @reify
@@ -94,7 +96,7 @@
     @property
     def visible_widgets(self):
         for widget in self.widgets:
-            if (self.checkbox_field is None) or (widget.field is not self.checkbox_field.field):
+            if (self.checkbox_field is None) or (widget.field is not self.checkbox_field):
                 yield widget
 
     @property
@@ -110,10 +112,10 @@
         return 'on' if self.visible else 'off'
 
 
-def NamedWidgetsGroup(id, widgets, names=(), legend=None, help=None, css_class='', switch=False,
+def NamedWidgetsGroup(id, widgets, names=(), bordered=True, legend=None, help=None, css_class='', switch=False,
                       checkbox_switch=False, checkbox_field=None, hide_if_empty=False):
     """Create a widgets group based on widgets names"""
-    return FormWidgetsGroup(id, [widgets.get(name) for name in names], legend, help, css_class, switch,
+    return FormWidgetsGroup(id, [widgets.get(name) for name in names], bordered, legend, help, css_class, switch,
                             checkbox_switch, checkbox_field, hide_if_empty)
 
 
--- a/src/pyams_form/templates/form.pt	Sat Feb 28 13:33:20 2015 +0100
+++ b/src/pyams_form/templates/form.pt	Sat Feb 28 15:10:46 2015 +0100
@@ -46,13 +46,18 @@
 						 tal:content="structure prefix">Widgets prefix</div>
 					<tal:loop repeat="group view.groups">
 						<fieldset tal:define="legend group.legend"
-								  tal:omit-tag="not:legend">
+								  tal:omit-tag="not:legend"
+								  tal:attributes="class 'bordered' if group.bordered else None">
 							<tal:if condition="group.checkbox_switch">
-								<legend tal:condition="legend"
-										tal:content="legend"
+								<legend data-ams-checker-value="selected"
+										tal:condition="legend"
 										tal:attributes="class group.css_class;
-														data-ams-checker-fieldname group.checker_field.getName();
-														data-ams-checker-state group.checker_state;">Legend</legend>
+														data-ams-checker-fieldname '{0}:list'.format(group.checkbox_widget.name);
+														data-ams-checker-readonly 'readonly' if group.checkbox_widget.mode == 'display' else None;
+														data-ams-checker-marker '{0}-empty-marker'.format(group.checkbox_widget.name);
+														data-ams-checker-state group.checker_state;">
+									<label tal:content="legend">Legend</label>
+								</legend>
 							</tal:if>
 							<tal:if condition="not:group.checkbox_switch">
 								<legend tal:condition="legend"
@@ -73,7 +78,7 @@
 								<tal:if condition="widget.mode != 'hidden'">
 									<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
 										 tal:attributes="class string:form-group ${required}">
-										<label tal:attributes="class view.label_css_class">
+										<label tal:attributes="class group.label_css_class | view.label_css_class">
 											<span>
 												<tal:var content="widget.label" />
 												<i class="fa fa-question-circle hint" title="Input hint"
@@ -83,7 +88,7 @@
 																   data-ams-hint-html '<' in description;"></i>
 											</span>
 										</label>
-										<div tal:attributes="class widget.input_css_class | view.input_css_class">
+										<div tal:attributes="class widget.input_css_class | group.input_css_class | view.input_css_class">
 											<label class="input"
 												   tal:attributes="class widget.label_css_class | default;
 																   data-ams-data extension:object_data(widget);
@@ -114,7 +119,8 @@
 						 tal:condition="view.tabforms">
 						<ul class="nav nav-tabs">
 							<li tal:repeat="tabform view.tabforms"
-								tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
+								tal:attributes="class 'small {active} {errors}'.format(active='active' if repeat['tabform'].start() else '',
+																					   errors='state-error' if tabform.widgets.errors else '')">
 								<a data-toggle="tab"
 								   tal:attributes="href string:#${tabform.id}"
 								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
@@ -122,10 +128,10 @@
 						</ul>
 						<div class="tab-content">
 							<div class="tab-pane fade in"
-								 tal:repeat="tabform view.tabforms">
-								 tal:attributes="id tabform.id"
-								 tal:content="structure tabform.render()" />
-							</div>
+								 tal:repeat="tabform view.tabforms"
+								 tal:attributes="id tabform.id;
+												 class 'tab-pane {active} fade in'.format(active='active' if repeat['tabform'].start() else '');"
+								 tal:content="structure tabform.render()"></div>
 						</div>
 					</div>
 				</fieldset>
--- a/src/pyams_form/templates/inner-form.pt	Sat Feb 28 13:33:20 2015 +0100
+++ b/src/pyams_form/templates/inner-form.pt	Sat Feb 28 15:10:46 2015 +0100
@@ -3,9 +3,11 @@
 		 tal:replace="structure prefix">Form prefix</div>
 	<form method="post"
 		  data-async
+		  tal:define="action view.get_form_action()"
+		  tal:omit-tag="not:action"
 		  tal:attributes="id view.id;
 						  name view.name;
-						  action view.get_form_action();
+						  action action;
 						  method view.method;
 						  enctype view.enctype;
 						  acceptCharset view.acceptCharset;
@@ -17,105 +19,109 @@
 						  data-ams-form-options view.get_form_options() | nothing;
 						  data-ams-form-submit-target view.form_target | nothing;
 						  data-ams-warn-on-change view.warn_on_change;">
-		<div class="modal-viewport">
-			<fieldset>
-				<legend tal:define="legend view.legend"
-						tal:condition="legend">
-					<i tal:attributes="class view.icon_css_class | nothing"></i>
-					<tal:var content="legend">Legend</tal:var>
-				</legend>
-				<tal:var content="structure provider:content_help" />
-				<div class="widgets-prefix"
-					 tal:define="prefix provider:widgets_prefix"
-					 tal:condition="prefix"
-					 tal:content="structure prefix">Widgets prefix</div>
-				<tal:loop repeat="group view.groups">
-					<fieldset tal:define="legend group.legend"
-							  tal:omit-tag="not:legend">
-						<tal:if condition="group.checkbox_switch">
-							<legend tal:condition="legend"
-									tal:content="legend"
-									tal:attributes="class group.css_class;
-													data-ams-checker-fieldname group.checker_field.getName();
-													data-ams-checker-state group.checker_state;">Legend</legend>
-						</tal:if>
-						<tal:if condition="not:group.checkbox_switch">
-							<legend tal:condition="legend"
-									tal:content="legend"
-									tal:attributes="class group.css_class;
-													data-ams-switcher-state group.switcher_state;">Legend</legend>
+		<fieldset tal:define="legend view.legend"
+				  tal:omit-tag="not:legend">
+			<legend tal:condition="legend">
+				<i tal:attributes="class view.icon_css_class | nothing"></i>
+				<tal:var content="legend">Legend</tal:var>
+			</legend>
+			<tal:var content="structure provider:content_help" />
+			<div class="widgets-prefix"
+				 tal:define="prefix provider:widgets_prefix"
+				 tal:condition="prefix"
+				 tal:content="structure prefix">Widgets prefix</div>
+			<tal:loop repeat="group view.groups">
+				<fieldset tal:define="legend group.legend"
+						  tal:omit-tag="not:legend"
+						  tal:attributes="class 'bordered' if group.bordered else None">
+					<tal:if condition="group.checkbox_switch">
+						<legend data-ams-checker-value="selected"
+								tal:condition="legend"
+								tal:attributes="class group.css_class;
+												data-ams-checker-fieldname '{0}:list'.format(group.checkbox_widget.name);
+												data-ams-checker-readonly 'readonly' if group.checkbox_widget.mode == 'display' else None;
+												data-ams-checker-marker '{0}-empty-marker'.format(group.checkbox_widget.name);
+												data-ams-checker-state group.checker_state;">
+							<label tal:content="legend">Legend</label>
+						</legend>
+					</tal:if>
+					<tal:if condition="not:group.checkbox_switch">
+						<legend tal:condition="legend"
+								tal:content="legend"
+								tal:attributes="class group.css_class;
+												data-ams-switcher-state group.switcher_state;">Legend</legend>
+					</tal:if>
+					<tal:var define="help group.help" condition="help">
+						<div class=""
+							 tal:define="html import:pyams_utils.text.text_to_html;
+										 i18n_help html(request.localizer.translate(help));"
+							 tal:content="structure i18n_help"></div>
+					</tal:var>
+					<tal:loop repeat="widget group.visible_widgets">
+						<input type="hidden"
+							   tal:condition="python:widget.mode == 'hidden'"
+							   tal:replace="structure widget.render()" />
+						<tal:if condition="widget.mode != 'hidden'">
+							<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
+								 tal:attributes="class string:form-group ${required}">
+								<label tal:attributes="class group.label_css_class | view.label_css_class">
+									<span>
+										<tal:var content="widget.label" />
+										<i class="fa fa-question-circle hint" title="Input hint"
+										   tal:define="description widget.field.description"
+										   tal:condition="description"
+										   tal:attributes="title description;
+														   data-ams-hint-html '<' in description;"></i>
+									</span>
+								</label>
+								<div tal:attributes="class widget.input_css_class | group.input_css_class | view.input_css_class">
+									<label class="input"
+										   tal:attributes="class widget.label_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+							</div>
 						</tal:if>
-						<tal:var define="help group.help" condition="help">
-							<div class=""
-								 tal:define="html import:pyams_utils.text.text_to_html;
-											 i18n_help html(request.localizer.translate(help));"
-								 tal:content="structure i18n_help"></div>
-						</tal:var>
-						<tal:loop repeat="widget group.visible_widgets">
-							<input type="hidden"
-								   tal:condition="python:widget.mode == 'hidden'"
-								   tal:replace="structure widget.render()" />
-							<tal:if condition="widget.mode != 'hidden'">
-								<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
-									 tal:attributes="class string:form-group ${required}">
-									<label tal:attributes="class view.label_css_class">
-										<span>
-											<tal:var content="widget.label" />
-											<i class="fa fa-question-circle hint" title="Input hint"
-											   tal:define="description widget.field.description"
-											   tal:condition="description"
-											   tal:attributes="title description;
-															   data-ams-hint-html '<' in description;"></i>
-										</span>
-									</label>
-									<div tal:attributes="class widget.input_css_class | view.input_css_class">
-										<label class="input"
-											   tal:attributes="class widget.label_css_class | default;
-															   data-ams-data extension:object_data(widget);
-															   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
-											<input tal:replace="structure widget.render()" />
-										</label>
-									</div>
-								</div>
-							</tal:if>
-						</tal:loop>
-					</fieldset>
-				</tal:loop>
-				<div class="widgets-suffix"
-					 tal:define="suffix provider:widgets_suffix"
-					 tal:condition="suffix"
-					 tal:content="structure suffix">Widgets suffix</div>
-				<div class="subforms"
-					 tal:condition="view.subforms">
-					<fieldset tal:define="title view.subforms_legend"
-							  tal:omit-tag="not:title">
-						<legend tal:condition="title" tal:content="title" i18n:translate="">legend</legend>
-						<tal:loop repeat="subform view.subforms">
-							<tal:var replace="structure subform.render()" />
-						</tal:loop>
-					</fieldset>
+					</tal:loop>
+				</fieldset>
+			</tal:loop>
+			<div class="widgets-suffix"
+				 tal:define="suffix provider:widgets_suffix"
+				 tal:condition="suffix"
+				 tal:content="structure suffix">Widgets suffix</div>
+			<div class="subforms"
+				 tal:condition="view.subforms">
+				<fieldset tal:define="title view.subforms_legend"
+						  tal:omit-tag="not:title">
+					<legend tal:condition="title" tal:content="title" i18n:translate="">legend</legend>
+					<tal:loop repeat="subform view.subforms">
+						<tal:var replace="structure subform.render()" />
+					</tal:loop>
+				</fieldset>
+			</div>
+			<div class="tabforms"
+				 tal:condition="view.tabforms">
+				<ul class="nav nav-tabs">
+					<li tal:repeat="tabform view.tabforms"
+						tal:attributes="class '{active} {errors}'.format(active='active' if repeat['tabform'].start() else '',
+																		 errors='state-error' if tabform.widgets.errors else '')">
+						<a data-toggle="tab"
+						   tal:attributes="href string:#${tabform.id}"
+						   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+					</li>
+				</ul>
+				<div class="tab-content">
+					<div class="tab-pane fade in"
+						 tal:repeat="tabform view.tabforms"
+							 tal:attributes="id tabform.id;
+											 class 'tab-pane {active} fade in'.format(active='active' if repeat['tabform'].start() else '');"
+							 tal:content="structure tabform.render()"></div>
 				</div>
-				<div class="tabforms"
-					 tal:condition="view.tabforms">
-					<ul class="nav nav-tabs">
-						<li tal:repeat="tabform view.tabforms"
-							tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
-							<a data-toggle="tab"
-							   tal:attributes="href string:#${tabform.id}"
-							   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
-						</li>
-					</ul>
-					<div class="tab-content">
-						<div class="tab-pane fade in"
-							 tal:repeat="tabform view.tabforms">
-							 tal:attributes="id tabform.id"
-							 tal:content="structure tabform.render()" />
-						</div>
-					</div>
-				</div>
-			</fieldset>
-		</div>
-		<footer>
+			</div>
+		</fieldset>
+		<footer tal:condition="view.actions">
 			<button tal:repeat="action view.actions.values()"
 					tal:replace="structure action.render()">Action</button>
 		</footer>
--- a/src/pyams_form/templates/widget-form.pt	Sat Feb 28 13:33:20 2015 +0100
+++ b/src/pyams_form/templates/widget-form.pt	Sat Feb 28 15:10:46 2015 +0100
@@ -34,13 +34,18 @@
 						 tal:content="structure prefix">Widgets prefix</div>
 					<tal:loop repeat="group view.groups">
 						<fieldset tal:define="legend group.legend"
-								  tal:omit-tag="not:legend">
+								  tal:omit-tag="not:legend"
+								  tal:attributes="class 'bordered' if group.bordered else None">
 							<tal:if condition="group.checkbox_switch">
-								<legend tal:condition="legend"
-										tal:content="legend"
+								<legend data-ams-checker-value="selected"
+										tal:condition="legend"
 										tal:attributes="class group.css_class;
-														data-ams-checker-fieldname group.checker_field.getName();
-														data-ams-checker-state group.checker_state;">Legend</legend>
+														data-ams-checker-fieldname '{0}:list'.format(group.checkbox_widget.name);
+														data-ams-checker-readonly 'readonly' if group.checkbox_widget.mode == 'display' else None;
+														data-ams-checker-marker '{0}-empty-marker'.format(group.checkbox_widget.name);
+														data-ams-checker-state group.checker_state;">
+									<label tal:content="legend">Legend</label>
+								</legend>
 							</tal:if>
 							<tal:if condition="not:group.checkbox_switch">
 								<legend tal:condition="legend"
@@ -61,7 +66,7 @@
 								<tal:if condition="widget.mode != 'hidden'">
 									<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
 										 tal:attributes="class string:form-group ${required}">
-										<label tal:attributes="class view.label_css_class">
+										<label tal:attributes="class group.label_css_class | view.label_css_class">
 											<span>
 												<tal:var content="widget.label" />
 												<i class="fa fa-question-circle hint" title="Input hint"
@@ -71,7 +76,7 @@
 																   data-ams-hint-html '<' in description;"></i>
 											</span>
 										</label>
-										<div tal:attributes="class widget.input_css_class | view.input_css_class">
+										<div tal:attributes="class widget.input_css_class | group.input_css_class | view.input_css_class">
 											<label class="input"
 												   tal:attributes="class widget.label_css_class | default;
 																   data-ams-data extension:object_data(widget);
@@ -102,7 +107,8 @@
 						 tal:condition="view.tabforms">
 						<ul class="nav nav-tabs">
 							<li tal:repeat="tabform view.tabforms"
-								tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
+								tal:attributes="class 'small {active} {errors}'.format(active='active' if repeat['tabform'].start() else '',
+																					   errors='state-error' if tabform.widgets.errors else '')">
 								<a data-toggle="tab"
 								   tal:attributes="href string:#${tabform.id}"
 								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
@@ -110,10 +116,10 @@
 						</ul>
 						<div class="tab-content">
 							<div class="tab-pane fade in"
-								 tal:repeat="tabform view.tabforms">
-								 tal:attributes="id tabform.id"
-								 tal:content="structure tabform.render()" />
-							</div>
+								 tal:repeat="tabform view.tabforms"
+								 tal:attributes="id tabform.id;
+												 class 'tab-pane {active} fade in'.format(active='active' if repeat['tabform'].start() else '');"
+								 tal:content="structure tabform.render()"></div>
 						</div>
 					</div>
 				</fieldset>