[Checkins] SVN: z3c.form/trunk/ - implemented the missing widget layout concept.

Roger Ineichen cvs-admin at zope.org
Mon Aug 6 01:38:40 UTC 2012


Log message for revision 127425:
  - implemented the missing widget layout concept. 
  Now we can call a widget and an additional layout template get used. 
  See CHANGES.txt for more info
  

Changed:
  U   z3c.form/trunk/CHANGES.txt
  U   z3c.form/trunk/setup.py
  U   z3c.form/trunk/src/z3c/form/browser/README.txt
  U   z3c.form/trunk/src/z3c/form/browser/button.py
  U   z3c.form/trunk/src/z3c/form/browser/checkbox.py
  U   z3c.form/trunk/src/z3c/form/browser/configure.zcml
  U   z3c.form/trunk/src/z3c/form/browser/file.py
  U   z3c.form/trunk/src/z3c/form/browser/image.py
  U   z3c.form/trunk/src/z3c/form/browser/interfaces.py
  U   z3c.form/trunk/src/z3c/form/browser/multi.py
  U   z3c.form/trunk/src/z3c/form/browser/object.py
  U   z3c.form/trunk/src/z3c/form/browser/password.py
  U   z3c.form/trunk/src/z3c/form/browser/radio.py
  U   z3c.form/trunk/src/z3c/form/browser/select.py
  U   z3c.form/trunk/src/z3c/form/browser/submit.py
  U   z3c.form/trunk/src/z3c/form/browser/text.py
  U   z3c.form/trunk/src/z3c/form/browser/textarea.py
  U   z3c.form/trunk/src/z3c/form/browser/widget.py
  A   z3c.form/trunk/src/z3c/form/browser/widget.zcml
  A   z3c.form/trunk/src/z3c/form/browser/widget_layout.pt
  A   z3c.form/trunk/src/z3c/form/browser/widget_layout_hidden.pt
  U   z3c.form/trunk/src/z3c/form/interfaces.py
  U   z3c.form/trunk/src/z3c/form/meta.zcml
  U   z3c.form/trunk/src/z3c/form/testing.py
  U   z3c.form/trunk/src/z3c/form/tests/simple_edit_with_providers.pt
  U   z3c.form/trunk/src/z3c/form/util.txt
  U   z3c.form/trunk/src/z3c/form/widget.py
  U   z3c.form/trunk/src/z3c/form/zcml.py

-=-
Modified: z3c.form/trunk/CHANGES.txt
===================================================================
--- z3c.form/trunk/CHANGES.txt	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/CHANGES.txt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -2,12 +2,38 @@
 CHANGES
 =======
 
-2.7.1 (unreleased)
+2.8.0 (2012-08-06)
 ------------------
 
-- Nothing changed yet.
+- Feature: Implemented widget layout concept similar to z3c.pagelet. The new
+  layout concept allows to register layout templates additional to the widget
+  templates. Such a layout template only get used if a widget get called.
+  This enhacement is optional and compatible with all previous z3c.form
+  versions and dosn't affect existing code and customm implementations
+  except if you implemented a own __call__ method for widgets which
+  wasn't implemented in previous versions. The new __call__ method will lookup
+  and return a layout template which supports additional HTML code used as
+  a wrapper for the HTML code returned from the widget render method.
+  This concept allows to define additional HTML construct provided for all
+  widget and render specific CSS classes arround the widget per context, view,
+  request, etc discriminators. Such a HTML constuct was normaly supported in
+  form macros which can't get customized on a per widget, view or context base.
 
+  Summary; the new layout concept allows us to define a wrapper CSS elements
+  for the widget element (label, widget, error) on a per widgte base and skip
+  the generic form macros offered from z3c.formui.
 
+  Note; you only could get into trouble if you define a widget in tal without
+  to prefix them with ``nocall:`` e.g. tal:define="widget view/widgets/foo"
+  Just add a nocall like tal:define="widget nocall:view/widgets/foo" if your
+  rendering engine calls the __call__method by default. Also note that the
+  following will also call the __call__ method tal:define="widget myWidget".
+
+- Fixed content type extraction test which returned different values. This
+  probably depends on a newer version of guess_content_type. Just allow
+  image/x-png and image/pjpeg as valid values.
+
+
 2.7.0 (2012-07-11)
 ------------------
 

Modified: z3c.form/trunk/setup.py
===================================================================
--- z3c.form/trunk/setup.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/setup.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -45,7 +45,7 @@
 
 setup(
     name='z3c.form',
-    version='2.7.1.dev0',
+    version='2.8.0',
     author="Stephan Richter, Roger Ineichen and the Zope Community",
     author_email="zope-dev at zope.org",
     description="An advanced form and widget framework for Zope 3",

Modified: z3c.form/trunk/src/z3c/form/browser/README.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/README.txt	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/README.txt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -65,7 +65,47 @@
   <span id="foo" class="textarea-widget required ascii-field">This is
    ASCII.</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="textarea-widget required ascii-field">This is
+   ASCII.</span>
+  </div>
+  </div>
+
+As you can see, we will get an additional error class called ``row-error`` if
+we render a widget with an error view assinged:
+
+  >>> class DummyErrorView(object):
+  ...    def render(self):
+  ...        return u'Dummy Error'
+  >>> widget.error = (DummyErrorView())
+  >>> print widget()
+  <div id="foo-row" class="row-error row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="textarea-widget required ascii-field">This is
+   ASCII.</span>
+  </div>
+    <div class="error">
+      Dummy Error
+    </div>
+  </div>
+
+
 ASCIILine
 ---------
 
@@ -87,6 +127,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required asciiline-field">An ASCII line.</span>
 
+Calling the widget will return the widget including the layout
+
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required asciiline-field">An ASCII line.</span>
+  </div>
+  </div>
+
+
 Bool
 ----
 
@@ -122,6 +178,21 @@
   <span id="foo" class="radio-widget required bool-field"><span
       class="selected-option">yes</span></span>
 
+Calling the widget will return the widget including the layout
+
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span>Check me</span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="radio-widget required bool-field"><span class="selected-option">yes</span></span>
+  </div>
+  </div>
+
 For the boolean, the checkbox widget can be used as well:
 
   >>> from z3c.form.browser import checkbox
@@ -152,6 +223,21 @@
   <span id="foo" class="checkbox-widget required bool-field"><span
       class="selected-option">yes</span></span>
 
+Calling the widget will return the widget including the layout
+
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span>Check me</span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="checkbox-widget required bool-field"><span class="selected-option">yes</span></span>
+  </div>
+  </div>
+
 We can also have a single checkbox button for the boolean.
 
   >>> widget = checkbox.SingleCheckBoxFieldWidget(field, TestRequest())
@@ -181,7 +267,22 @@
   >>> widget.label
   u''
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="single-checkbox-widget required bool-field"><span class="selected-option">Check me</span></span>
+  </div>
+  </div>
+
+
 Button
 ------
 
@@ -243,7 +344,22 @@
   >>> widget.render().strip('\n')
   u'<span id="foo" class="file-widget required bytes-field">Default bytes</span>'
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="file-widget required bytes-field">Default bytes</span>
+  </div>
+  </div>
+
+
 BytesLine
 ---------
 
@@ -265,7 +381,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required bytesline-field">A Bytes line.</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required bytesline-field">A Bytes line.</span>
+  </div>
+  </div>
+
+
 Choice
 ------
 
@@ -296,7 +427,22 @@
   <span id="foo" class="select-widget required choice-field"><span
     class="selected-option">Yes</span></span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="select-widget required choice-field"><span class="selected-option">Yes</span></span>
+  </div>
+  </div>
+
+
 Date
 ----
 
@@ -319,7 +465,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required date-field">07/04/01</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required date-field">07/04/01</span>
+  </div>
+  </div>
+
+
 Datetime
 --------
 
@@ -341,7 +502,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required datetime-field">07/04/01 12:00</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required datetime-field">07/04/01 12:00</span>
+  </div>
+  </div>
+
+
 Decimal
 -------
 
@@ -364,7 +540,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required decimal-field">1,265.87</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required decimal-field">1,265.87</span>
+  </div>
+  </div>
+
+
 Dict
 ----
 
@@ -393,7 +584,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required dottedname-field">z3c.form</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required dottedname-field">z3c.form</span>
+  </div>
+  </div>
+
+
 Float
 -----
 
@@ -415,7 +621,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required float-field">1,265.8</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required float-field">1,265.8</span>
+  </div>
+  </div>
+
+
 FrozenSet
 ---------
 
@@ -447,7 +668,21 @@
     class="selected-option">1</span>, <span
     class="selected-option">3</span></span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+  <span id="foo" class="select-widget required frozenset-field"><span class="selected-option">1</span>, <span class="selected-option">3</span></span>
+  </div>
+  </div>
+
 Id
 --
 
@@ -469,7 +704,22 @@
   >>> print widget.render()
   <span id="foo" class="text-widget required id-field">z3c.form</span>
 
+Calling the widget will return the widget including the layout
 
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required id-field">z3c.form</span>
+  </div>
+  </div>
+
+
 ImageButton
 -----------
 
@@ -3066,3 +3316,18 @@
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
   <span id="foo" class="text-widget required uri-field">http://zope.org</span>
+
+Calling the widget will return the widget including the layout
+
+  >>> print widget()
+  <div id="foo-row" class="row-required row">
+    <div class="label">
+      <label for="foo">
+        <span></span>
+        <span class="required">*</span>
+      </label>
+    </div>
+    <div class="widget">
+      <span id="foo" class="text-widget required uri-field">http://zope.org</span>
+  </div>
+  </div>

Modified: z3c.form/trunk/src/z3c/form/browser/button.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/button.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/button.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -29,6 +29,7 @@
     zope.interface.implementsOnly(interfaces.IButtonWidget)
 
     klass = u'button-widget'
+    css = u'button'
 
     def update(self):
         # We do not need to use the widget's update method, because it is

Modified: z3c.form/trunk/src/z3c/form/browser/checkbox.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/checkbox.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/checkbox.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -34,6 +34,7 @@
     zope.interface.implementsOnly(interfaces.ICheckBoxWidget)
 
     klass = u'checkbox-widget'
+    css = u'checkbox'
     items = ()
 
     def isChecked(self, term):

Modified: z3c.form/trunk/src/z3c/form/browser/configure.zcml
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/configure.zcml	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/configure.zcml	2012-08-06 01:38:29 UTC (rev 127425)
@@ -18,4 +18,6 @@
   <include file="textlines.zcml" />
   <include file="object.zcml" />
 
+  <include file="widget.zcml" />
+
 </configure>

Modified: z3c.form/trunk/src/z3c/form/browser/file.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/file.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/file.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -29,6 +29,7 @@
     zope.interface.implementsOnly(interfaces.IFileWidget)
 
     klass = u'file-widget'
+    css = u'file'
 
     # Filename and headers attribute get set by ``IDataConverter`` to the widget
     # provided by the FileUpload object of the form.

Modified: z3c.form/trunk/src/z3c/form/browser/image.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/image.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/image.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -31,8 +31,11 @@
 class ImageWidget(button.ButtonWidget):
     """A image button of a form."""
     zope.interface.implementsOnly(interfaces.IImageWidget)
+
     src = FieldProperty(IHTMLImageWidget['src'])
+
     klass = u'image-widget'
+    css = u'image'
 
     def extract(self, default=interfaces.NO_VALUE):
         """See z3c.form.interfaces.IWidget."""

Modified: z3c.form/trunk/src/z3c/form/browser/interfaces.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/interfaces.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/interfaces.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -20,6 +20,30 @@
 import zope.schema
 
 
+class IWidgetLayoutSupport(zope.interface.Interface):
+
+    css = zope.schema.TextLine(
+        title=u'Widget layout CSS class name(s)',
+        description=(u'This attribute defines one or more layout class names.'),
+        default=u'row',
+        required=False)
+
+    def getCSSClass(klass=None, error=None, required=None,
+        classPattern='%(class)s', errorPattern='%(class)s-error',
+        requiredPattern='%(class)s-required'):
+        """Setup given css class (klass) with error and required postfix
+
+        If no klass name is given the widget.wrapper class name/names get used.
+        It is also possible if more then one (empty space separated) names 
+        are given as klass argument.
+
+        This method can get used from your form or widget template or widget
+        layout template without to re-implement the widget itself just because
+        you a different CSS class concept. 
+
+        """
+
+
 class IHTMLCoreAttributes(zope.interface.Interface):
     """The HTML element 'core' attributes."""
 
@@ -126,8 +150,9 @@
 
 class IHTMLFormElement(IHTMLCoreAttributes,
                        IHTMLI18nAttributes,
-                       IHTMLEventsAttributes):
-    """A generic form-related element."""
+                       IHTMLEventsAttributes,
+                       IWidgetLayoutSupport):
+    """A generic form-related element including layout template support."""
 
     disabled = zope.schema.Choice(
         title=u'Disabled',

Modified: z3c.form/trunk/src/z3c/form/browser/multi.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/multi.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/multi.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -40,6 +40,7 @@
 
     prefix = 'widget'
     klass = u'multi-widget'
+    css = u'multi'
     items = ()
 
     showLabel = True # show labels for item subwidgets or not

Modified: z3c.form/trunk/src/z3c/form/browser/object.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/object.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/object.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -29,6 +29,7 @@
     zope.interface.implements(interfaces.IObjectWidget)
 
     klass = u'object-widget'
+    css = u'object'
 
 @zope.component.adapter(zope.schema.interfaces.IObject, interfaces.IFormLayer)
 @zope.interface.implementer(interfaces.IFieldWidget)

Modified: z3c.form/trunk/src/z3c/form/browser/password.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/password.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/password.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -28,6 +28,7 @@
     zope.interface.implementsOnly(interfaces.IPasswordWidget)
 
     klass = u'password-widget'
+    css = u'password'
 
 
 @zope.component.adapter(zope.schema.interfaces.IPassword, interfaces.IFormLayer)

Modified: z3c.form/trunk/src/z3c/form/browser/radio.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/radio.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/radio.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -33,6 +33,7 @@
     zope.interface.implementsOnly(interfaces.IRadioWidget)
 
     klass = u'radio-widget'
+    css = u'radio'
     items = ()
 
     def isChecked(self, term):

Modified: z3c.form/trunk/src/z3c/form/browser/select.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/select.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/select.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -33,6 +33,7 @@
     zope.interface.implementsOnly(interfaces.ISelectWidget)
 
     klass = u'select-widget'
+    css = u'select'
     prompt = False
 
     noValueMessage = _('no value')

Modified: z3c.form/trunk/src/z3c/form/browser/submit.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/submit.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/submit.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -27,7 +27,9 @@
 class SubmitWidget(button.ButtonWidget):
     """A submit button of a form."""
     zope.interface.implementsOnly(interfaces.ISubmitWidget)
+
     klass = u'submit-widget'
+    css = u'submit'
 
 
 @zope.component.adapter(interfaces.IButton, interfaces.IFormLayer)

Modified: z3c.form/trunk/src/z3c/form/browser/text.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/text.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/text.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -29,6 +29,7 @@
     zope.interface.implementsOnly(interfaces.ITextWidget)
 
     klass = u'text-widget'
+    css = u'text'
     value = u''
 
     def update(self):

Modified: z3c.form/trunk/src/z3c/form/browser/textarea.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/textarea.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/textarea.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -30,6 +30,7 @@
     zope.interface.implementsOnly(interfaces.ITextAreaWidget)
 
     klass = u'textarea-widget'
+    css = u'textarea'
     value = u''
 
     def update(self):

Modified: z3c.form/trunk/src/z3c/form/browser/widget.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/widget.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/browser/widget.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -22,7 +22,99 @@
 from z3c.form.interfaces import IFieldWidget
 from z3c.form.browser import interfaces
 
-class HTMLFormElement(object):
+
+class WidgetLayoutSupport(object):
+    """Widget layout support"""
+
+    def wrapCSSClass(self, klass, pattern='s(class)%'):
+        """Return a list of css class names wrapped with given pattern"""
+        if klass is not None and pattern is not None:
+            return [pattern % {'class': k} for k in klass.split()]
+        else:
+            return []
+
+    def getCSSClass(self, klass=None, error=None, required=None,
+        classPattern='%(class)s', errorPattern='%(class)s-error',
+        requiredPattern='%(class)s-required'):
+        """Setup given css class (klass) with error and required postfix
+        
+        If no klass name is given the widget.wrapper class name/names get used.
+        It is also possible if more then one (empty space separated) names 
+        are given as klass argument.
+
+        This method can get used from your form or widget template or widget
+        layout template without to re-implement the widget itself just because
+        you a different CSS class concept. 
+
+        The following sample:
+        
+        <div tal:attributes="class python:widget.getCSSClass('foo bar')">
+          label widget and error
+        </div>
+        
+        will render a div tag if the widget field defines required=True:
+        
+        <div class="foo-error bar-error foo-required bar-required foo bar">
+          label widget and error
+        </div>
+
+        And the following sample:
+        
+        <div tal:attributes="class python:widget.getCSSClass('row')">
+          label widget and error
+        </div>
+        
+        will render a div tag if the widget field defines required=True
+        and an error occurs:
+        
+        <div class="row-error row-required row">
+          label widget and error
+        </div>
+
+        Note; you need to define a globale widget property if you use
+        python:widget (in your form template). And you need to use the
+        view scope in your widget or layout templates.
+
+        Note, you can set the pattern to None for skip error or required
+        rendering. Or you can use a pattern like 'error' or 'required' if
+        you like to skip postfixing your default css klass name for error or
+        required rendering.
+
+        """
+        classes = []
+        # setup class names
+        if klass is not None:
+            kls = klass
+        else:
+            kls = self.css
+
+        # setup error class names
+        if error is not None:
+            error = error
+        else:
+            error = kls
+
+        # setup required class names
+        if required is not None:
+            required = required
+        else:
+            required = kls
+
+        # append error class names
+        if self.error is not None:
+            classes += self.wrapCSSClass(error, errorPattern)
+        # append required class names
+        if self.required:
+            classes += self.wrapCSSClass(required, requiredPattern)
+        # append given class names
+        classes += self.wrapCSSClass(kls, classPattern)
+        # remove duplicated class names but keep order
+        unique = []
+        [unique.append(kls) for kls in classes if kls not in unique]
+        return ' '.join(unique)
+
+
+class HTMLFormElement(WidgetLayoutSupport):
     zope.interface.implements(interfaces.IHTMLFormElement)
 
     id = FieldProperty(interfaces.IHTMLFormElement['id'])
@@ -49,6 +141,9 @@
     onblur = FieldProperty(interfaces.IHTMLFormElement['onblur'])
     onchange = FieldProperty(interfaces.IHTMLFormElement['onchange'])
 
+    # layout support
+    css = FieldProperty(interfaces.IHTMLFormElement['css'])
+
     def addClass(self, klass):
         """See interfaces.IHTMLFormElement"""
         if not self.klass:

Added: z3c.form/trunk/src/z3c/form/browser/widget.zcml
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/widget.zcml	                        (rev 0)
+++ z3c.form/trunk/src/z3c/form/browser/widget.zcml	2012-08-06 01:38:29 UTC (rev 127425)
@@ -0,0 +1,27 @@
+<configure
+    xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c.form">
+
+  <!-- widget layout templates -->
+  <z3c:widgetLayout
+      mode="display"
+      widget="z3c.form.interfaces.IWidget"
+      layer="z3c.form.interfaces.IFormLayer"
+      template="widget_layout.pt"
+      />
+  <z3c:widgetLayout
+      mode="input"
+      widget="z3c.form.interfaces.IWidget"
+      layer="z3c.form.interfaces.IFormLayer"
+      template="widget_layout.pt"
+      />
+  <z3c:widgetLayout
+      mode="hidden"
+      widget="z3c.form.interfaces.IWidget"
+      layer="z3c.form.interfaces.IFormLayer"
+      template="widget_layout_hidden.pt"
+      />
+
+</configure>


Property changes on: z3c.form/trunk/src/z3c/form/browser/widget.zcml
___________________________________________________________________
Added: svn:keywords
   + Date Author Id Revision
Added: svn:eol-style
   + native

Added: z3c.form/trunk/src/z3c/form/browser/widget_layout.pt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/widget_layout.pt	                        (rev 0)
+++ z3c.form/trunk/src/z3c/form/browser/widget_layout.pt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -0,0 +1,23 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal"
+     tal:omit-tag="">
+<div id="" class="row"
+     tal:attributes="id string:${view/id}-row;
+                     class python:view.getCSSClass('row')">
+  <div class="label">
+    <label tal:attributes="for view/id">
+      <span i18n:translate=""
+          tal:content="view/label">label</span>
+      <span class="required"
+            tal:condition="view/required">*</span>
+    </label>
+  </div>
+  <div class="widget" tal:content="structure view/render">
+    <input type="text" size="24" value="" />
+  </div>
+  <div class="error"
+       tal:condition="view/error">
+    <span tal:replace="structure view/error/render">error</span>
+  </div>
+</div>
+</div>


Property changes on: z3c.form/trunk/src/z3c/form/browser/widget_layout.pt
___________________________________________________________________
Added: svn:keywords
   + Date Author Id Revision
Added: svn:eol-style
   + native

Added: z3c.form/trunk/src/z3c/form/browser/widget_layout_hidden.pt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/widget_layout_hidden.pt	                        (rev 0)
+++ z3c.form/trunk/src/z3c/form/browser/widget_layout_hidden.pt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -0,0 +1,5 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal"
+     tal:omit-tag="">
+<tal:block replace="structure view/render"></tal:block>
+</div>


Property changes on: z3c.form/trunk/src/z3c/form/browser/widget_layout_hidden.pt
___________________________________________________________________
Added: svn:keywords
   + Date Author Id Revision
Added: svn:eol-style
   + native

Modified: z3c.form/trunk/src/z3c/form/interfaces.py
===================================================================
--- z3c.form/trunk/src/z3c/form/interfaces.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/interfaces.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -379,7 +379,17 @@
         """Return a default object created to be populated.
         """
 
+# ----[ Widget layout template ]----------------------------------------------
 
+class IWidgetLayoutTemplate(zope.interface.Interface):
+    """Widget layout template marker used for render the widget layout.
+    
+    It is important that we don't inherit this template from IPageTemplate.
+    otherwise we will get into trouble since we lookup an IPageTemplate
+    in the widget/render method.
+
+    """
+
 # ----[ Widgets ]------------------------------------------------------------
 
 class IWidget(ILocation):
@@ -427,6 +437,7 @@
         required=False)
 
     template = zope.interface.Attribute('''The widget template''')
+    layout = zope.interface.Attribute('''The widget layout template''')
 
     ignoreRequest = zope.schema.Bool(
         title=_('Ignore Request'),
@@ -470,9 +481,12 @@
         """Setup all of the widget information used for displaying."""
 
     def render():
-        """Return the widget's text representation."""
+        """Render the plain widget without additional layout"""
 
+    def __call__():
+        """Render a layout template which is calling widget/render"""
 
+
 class ISequenceWidget(IWidget):
     """Term based sequence widget base.
 

Modified: z3c.form/trunk/src/z3c/form/meta.zcml
===================================================================
--- z3c.form/trunk/src/z3c/form/meta.zcml	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/meta.zcml	2012-08-06 01:38:29 UTC (rev 127425)
@@ -11,6 +11,12 @@
         />
 
     <meta:directive
+        name="widgetLayout"
+        schema=".zcml.IWidgetTemplateDirective"
+        handler=".zcml.widgetLayoutTemplateDirective"
+        />
+
+    <meta:directive
         name="objectWidgetTemplate"
         schema=".zcml.IObjectWidgetTemplateDirective"
         handler=".zcml.objectWidgetTemplateDirective"

Modified: z3c.form/trunk/src/z3c/form/testing.py
===================================================================
--- z3c.form/trunk/src/z3c/form/testing.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/testing.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -271,7 +271,23 @@
     zope.component.provideAdapter(
         text.TextFieldWidget,
         adapts=(zope.schema.interfaces.IURI, interfaces.IFormLayer))
+
+    # Widget Layout
     zope.component.provideAdapter(
+        widget.WidgetLayoutFactory(getPath('widget_layout.pt'), 'text/html'),
+        (None, None, None, None, interfaces.IWidget),
+        interfaces.IWidgetLayoutTemplate, name=interfaces.INPUT_MODE)
+    zope.component.provideAdapter(
+        widget.WidgetLayoutFactory(getPath('widget_layout.pt'), 'text/html'),
+        (None, None, None, None, interfaces.IWidget),
+        interfaces.IWidgetLayoutTemplate, name=interfaces.DISPLAY_MODE)
+    zope.component.provideAdapter(
+        widget.WidgetLayoutFactory(getPath('widget_layout_hidden.pt'), 'text/html'),
+        (None, None, None, None, interfaces.IWidget),
+        interfaces.IWidgetLayoutTemplate, name=interfaces.HIDDEN_MODE)
+
+    # Text Field Widget
+    zope.component.provideAdapter(
         widget.WidgetTemplateFactory(getPath('text_input.pt'), 'text/html'),
         (None, None, None, None, interfaces.ITextWidget),
         IPageTemplate, name=interfaces.INPUT_MODE)

Modified: z3c.form/trunk/src/z3c/form/tests/simple_edit_with_providers.pt
===================================================================
--- z3c.form/trunk/src/z3c/form/tests/simple_edit_with_providers.pt	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/tests/simple_edit_with_providers.pt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -17,7 +17,7 @@
       <div class="row" tal:define="is_widget snippet/id | nothing">
 
         <tal:widget condition="is_widget"
-                    define="widget snippet">
+                    define="widget nocall:snippet">
         <b tal:condition="widget/error"
            tal:content="structure widget/error/render"
         /><label tal:condition="widget/id"
@@ -28,7 +28,7 @@
         </tal:widget>
 
         <tal:provider condition="not:is_widget"
-                      define="contentprovider snippet"
+                      define="contentprovider nocall:snippet"
                       replace="structure contentprovider/render" />
 
       </div>

Modified: z3c.form/trunk/src/z3c/form/util.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/util.txt	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/util.txt	2012-08-06 01:38:29 UTC (rev 127425)
@@ -229,6 +229,11 @@
 There is also a method which is able to extract the content type for a given
 file upload. We can use the stub form from the previous test.
 
+Not sure if this an error but on my windows system this test returns
+image/pjpeg (progressive jpeg) for foo.jpg and image/x-png for foo.png. So
+let's allow this too since this depends on guess_content_type and is not
+really a part of z3c.form.
+
   >>> uploadForm = FileUploadFormStub()
   >>> uploadForm.setFakeFileName('foo.txt')
   >>> util.extractContentType(uploadForm, 'form.widgets.data')
@@ -240,11 +245,11 @@
 
   >>> uploadForm.setFakeFileName('foo.jpg')
   >>> util.extractContentType(uploadForm, 'form.widgets.data')
-  'image/jpeg'
+  'image/...jpeg'
 
   >>> uploadForm.setFakeFileName('foo.png')
   >>> util.extractContentType(uploadForm, 'form.widgets.data')
-  'image/png'
+  'image/...png'
 
   >>> uploadForm.setFakeFileName('foo.tif')
   >>> util.extractContentType(uploadForm, 'form.widgets.data')

Modified: z3c.form/trunk/src/z3c/form/widget.py
===================================================================
--- z3c.form/trunk/src/z3c/form/widget.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/widget.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -51,6 +51,7 @@
     error = FieldProperty(interfaces.IWidget['error'])
     value = FieldProperty(interfaces.IWidget['value'])
     template = None
+    layout = None
     ignoreRequest = FieldProperty(interfaces.IWidget['ignoreRequest'])
     setErrors = FieldProperty(interfaces.IWidget['setErrors'])
     showDefault = FieldProperty(interfaces.IWidget['showDefault'])
@@ -134,8 +135,12 @@
                 if value is not None:
                     setattr(self, attrName, value.get())
 
+    def extract(self, default=interfaces.NO_VALUE):
+        """See z3c.form.interfaces.IWidget."""
+        return self.request.get(self.name, default)
+
     def render(self):
-        """See z3c.form.interfaces.IWidget."""
+        """Render the plain widget without additional layout"""
         template = self.template
         if template is None:
             template = zope.component.getMultiAdapter(
@@ -143,9 +148,14 @@
                 IPageTemplate, name=self.mode)
         return template(self)
 
-    def extract(self, default=interfaces.NO_VALUE):
-        """See z3c.form.interfaces.IWidget."""
-        return self.request.get(self.name, default)
+    def __call__(self):
+        """Get and return layout template which is calling widget/render"""
+        layout = self.layout
+        if layout is None:
+            layout = zope.component.getMultiAdapter(
+                (self.context, self.request, self.form, self.field, self),
+                interfaces.IWidgetLayoutTemplate, name=self.mode)
+        return layout(self)
 
     def __repr__(self):
         return '<%s %r>' % (self.__class__.__name__, self.name)
@@ -454,6 +464,25 @@
         return self.template
 
 
+class WidgetLayoutFactory(object):
+    """Widget layout template factory."""
+
+    def __init__(self, filename, contentType='text/html',
+                 context=None, request=None, view=None,
+                 field=None, widget=None):
+        self.template = ViewPageTemplateFile(filename, content_type=contentType)
+        zope.component.adapter(
+            util.getSpecification(context),
+            util.getSpecification(request),
+            util.getSpecification(view),
+            util.getSpecification(field),
+            util.getSpecification(widget))(self)
+        zope.interface.implementer(interfaces.IWidgetLayoutTemplate)(self)
+
+    def __call__(self, context, request, view, field, widget):
+        return self.template
+
+
 class WidgetEvent(object):
     zope.interface.implements(interfaces.IWidgetEvent)
 

Modified: z3c.form/trunk/src/z3c/form/zcml.py
===================================================================
--- z3c.form/trunk/src/z3c/form/zcml.py	2012-08-03 09:32:49 UTC (rev 127424)
+++ z3c.form/trunk/src/z3c/form/zcml.py	2012-08-06 01:38:29 UTC (rev 127425)
@@ -30,6 +30,7 @@
 from z3c.form.i18n import MessageFactory as _
 from z3c.form.widget import WidgetTemplateFactory
 from z3c.form.object import ObjectWidgetTemplateFactory
+from z3c.form.widget import WidgetLayoutFactory
 
 
 class IWidgetTemplateDirective(zope.interface.Interface):
@@ -110,6 +111,25 @@
         (for_, layer, view, field, widget), name=mode)
 
 
+def widgetLayoutTemplateDirective(
+    _context, template, for_=zope.interface.Interface,
+    layer=IDefaultBrowserLayer, view=None, field=None, widget=None,
+    mode=interfaces.INPUT_MODE, contentType='text/html'):
+
+    # Make sure that the template exists
+    template = os.path.abspath(str(_context.path(template)))
+    if not os.path.isfile(template):
+        raise ConfigurationError("No such file", template)
+
+    factory = WidgetLayoutFactory(template, contentType)
+    zope.interface.directlyProvides(factory, interfaces.IWidgetLayoutTemplate)
+
+    # register the template
+    zope.component.zcml.adapter(_context, (factory,),
+        interfaces.IWidgetLayoutTemplate, (for_, layer, view, field, widget),
+        name=mode)
+
+
 def objectWidgetTemplateDirective(
     _context, template, for_=zope.interface.Interface,
     layer=IDefaultBrowserLayer, view=None, field=None, widget=None,



More information about the checkins mailing list