[Checkins] SVN: z3c.formjs/trunk/ A lot of more work. See CHANGES.txt.

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Jul 18 23:07:45 EDT 2007


Log message for revision 78132:
  A lot of more work. See CHANGES.txt.
  

Changed:
  U   z3c.formjs/trunk/CHANGES.txt
  U   z3c.formjs/trunk/buildout.cfg
  U   z3c.formjs/trunk/setup.py
  U   z3c.formjs/trunk/src/z3c/formjs/ajax.txt
  U   z3c.formjs/trunk/src/z3c/formjs/configure.zcml
  U   z3c.formjs/trunk/src/z3c/formjs/interfaces.py
  U   z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py
  U   z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
  U   z3c.formjs/trunk/src/z3c/formjs/jsevent.py
  U   z3c.formjs/trunk/src/z3c/formjs/jsevent.txt
  U   z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py
  U   z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt
  U   z3c.formjs/trunk/src/z3c/formjs/testing.py

-=-
Modified: z3c.formjs/trunk/CHANGES.txt
===================================================================
--- z3c.formjs/trunk/CHANGES.txt	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/CHANGES.txt	2007-07-19 03:07:45 UTC (rev 78132)
@@ -5,6 +5,15 @@
 Version 0.2.0 (??/??/2007)
 --------------------------
 
+- Feature: Registration of public AJAX server calls via a simple
+  decorator. The calls are made available via a special ``ajax`` view on the
+  original view.
+
+- Feature: Allow registering of JS subscriptions via a decorator within the
+  presentation component.
+
+- Feature: Added a new CSS selector.
+
 - Feature: Implementation of AJAX-driven widget value validation.
 
 - Restructure: Completely overhauled the entire API to be most easy to use and

Modified: z3c.formjs/trunk/buildout.cfg
===================================================================
--- z3c.formjs/trunk/buildout.cfg	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/buildout.cfg	2007-07-19 03:07:45 UTC (rev 78132)
@@ -1,5 +1,5 @@
 [buildout]
-develop = . z3c.form
+develop = .
 parts = test coverage
 
 [test]

Modified: z3c.formjs/trunk/setup.py
===================================================================
--- z3c.formjs/trunk/setup.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/setup.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -32,7 +32,7 @@
 
 setup (
     name='z3c.formjs',
-    version='0.1.0',
+    version='0.2.0',
     author = "Paul Carduner and the Zope Community",
     author_email = "zope3-dev at zope.org",
     description = "Javascript integration into ``z3c.form``",

Modified: z3c.formjs/trunk/src/z3c/formjs/ajax.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/ajax.txt	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/ajax.txt	2007-07-19 03:07:45 UTC (rev 78132)
@@ -31,7 +31,7 @@
   >>> from z3c.formjs import interfaces
   >>> import zope.interface
   >>> class PingForm(ajax.AJAXRequestHandler, form.Form):
-  ...     
+  ...
   ...     @ajax.handler
   ...     def pingBack(self):
   ...         message = self.request.get('message', 'Nothing to ping back.')
@@ -90,7 +90,7 @@
   >>> ping.update()
 
 Now we will instantiate a pluggable traverser providing our form as
-the context.  
+the context.
 
   >>> from z3c.traverser.browser import PluggableBrowserTraverser
   >>> traverser = PluggableBrowserTraverser(ping, request)

Modified: z3c.formjs/trunk/src/z3c/formjs/configure.zcml
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/configure.zcml	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/configure.zcml	2007-07-19 03:07:45 UTC (rev 78132)
@@ -3,40 +3,6 @@
     xmlns="http://namespaces.zope.org/zope"
     i18n_domain="z3c.formjs">
 
-  <!-- Action Managers and Handlers -->
-  <adapter
-      factory=".jsaction.JSButtonAction"
-      provides="z3c.form.interfaces.IButtonAction"
-      />
-
-  <subscriber
-      handler=".jsaction.createSubscriptionsForWidget"
-      />
-
-  <!-- JavaScript Validation -->
-  <view
-      for=".interfaces.IAJAXValidator"
-      type="zope.publisher.interfaces.http.IHTTPRequest"
-      provides="zope.publisher.interfaces.http.IHTTPPublisher"
-      factory="z3c.traverser.traverser.PluggableTraverser"
-      permission="zope.Public"
-      />
-
-  <view
-      for=".interfaces.IAJAXValidator"
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      provides="zope.publisher.interfaces.browser.IBrowserPublisher"
-      factory="z3c.traverser.browser.PluggableBrowserTraverser"
-      permission="zope.Public"
-      />
-
-  <subscriber
-      for=".interfaces.IAJAXValidator
-	   zope.publisher.interfaces.IPublisherRequest"
-      provides="z3c.traverser.interfaces.ITraverserPlugin"
-      factory=".jsvalidator.ValidateTraverser"
-      />
-
   <!-- AJAX Traversers -->
   <adapter
       trusted="True"
@@ -125,6 +91,16 @@
       component="z3c.formjs.jsevent.SUBMIT"
       />
 
+  <!-- Action Managers and Handlers -->
+  <adapter
+      factory=".jsaction.JSButtonAction"
+      provides="z3c.form.interfaces.IButtonAction"
+      />
+
+  <subscriber
+      handler=".jsaction.createSubscriptionsForWidget"
+      />
+
   <!-- Specific Javascript-backend implementations -->
 
   <include file="jqueryrenderer.zcml" />

Modified: z3c.formjs/trunk/src/z3c/formjs/interfaces.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/interfaces.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/interfaces.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -45,7 +45,15 @@
         description=u"Id of the DOM element to be selected.",
         required=True)
 
+class ICSSSelector(ISelector):
+    """Select a DOM element by a CSS selector expression."""
 
+    expr = zope.schema.TextLine(
+        title=u"Expression",
+        description=u"CSS selector pointing to a DOM element.",
+        required=True)
+
+
 class IJSSubscription(zope.interface.Interface):
     """A Subscription within Javascript."""
 
@@ -189,6 +197,36 @@
         """Return validation data."""
 
 
+# -----[ Widget Mode Switcher ]-----------------------------------------------
+
+class IWidgetModeSwitcher(zope.interface.Interface):
+    """A component that enables forms to switch between display and input
+    widgets."""
+
+    def getDisplayWidget():
+        """Return the rendered display widget.
+
+        The method expects to find a field called 'widget-name' in the request
+        containing the short name to the field/widget.
+        """
+
+    def getInputWidget():
+        """Return the rendered input widget.
+
+        The method expects to find a field called 'widget-name' in the request
+        containing the short name to the field/widget.
+        """
+
+    def saveWidgetValue():
+        """Save the new value of the widget and return any possible errors.
+
+        The method expects to find a field called 'widget-name' in the request
+        containing the short name to the field/widget. The request must also
+        contain all fields required for the widget to successfully extract the
+        value.
+        """
+
+
 # -----[ AJAX ]--------------------------------------------------------
 
 
@@ -215,3 +253,4 @@
 class IFormTraverser(zope.interface.Interface):
     """Marker interface for forms that can be traversed by the @@ajax
     view."""
+

Modified: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -102,7 +102,8 @@
             widget.__name__, widget.name, valueString)
         # build a js expression that joins form url, validate path, and query
         # string
-        ajaxURL = '"'+form.request.getURL() + '/validate" + ' + queryString
+        ajaxURL = '"'+form.request.getURL() + '/@@ajax/validate" + ' \
+                  + queryString
 
         return ajaxURL
 

Modified: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt	2007-07-19 03:07:45 UTC (rev 78132)
@@ -73,7 +73,13 @@
 
   >>> subscriptions = jsevent.JSSubscriptions()
   >>> subscriptions.subscribe(jsevent.CLICK, selector, handler)
+  <JSSubscription event=<JSEvent "click">,
+                  selector=<IdSelector "form-id">,
+                  handler=<function handler at ...>>
   >>> subscriptions.subscribe(jsevent.DBLCLICK, selector, handler)
+  <JSSubscription event=<JSEvent "dblclick">,
+                  selector=<IdSelector "form-id">,
+                  handler=<function handler at ...>>
 
 Let's now register the renderer:
 
@@ -124,7 +130,7 @@
   ...     (script, request), interfaces.IRenderer)
   >>> renderer.update()
   >>> print renderer.render()
-  $.get("http://127.0.0.1/validate" +
+  $.get("http://127.0.0.1/@@ajax/validate" +
             "?widget-name=zip&form.zip=" + $("#form-zip").val(),
         function(msg){applyErrorMessage("form-zip", msg)}
   )

Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -16,6 +16,7 @@
 $Id: $
 """
 __docformat__ = "reStructuredText"
+import sys
 import zope.component
 import zope.interface
 from zope.publisher.interfaces.browser import IBrowserRequest
@@ -66,6 +67,16 @@
         return '<%s "%s">' %(self.__class__.__name__, self.id)
 
 
+class CSSSelector(object):
+    zope.interface.implements(interfaces.ICSSSelector)
+
+    def __init__(self, expr):
+        self.expr = expr
+
+    def __repr__(self):
+        return '<%s "%s">' %(self.__class__.__name__, self.expr)
+
+
 class JSSubscription(object):
     zope.interface.implements(interfaces.IJSSubscription)
 
@@ -86,13 +97,24 @@
         self._subscriptions = []
 
     def subscribe(self, event, selector, handler):
-        self._subscriptions.append(
-            JSSubscription(event, selector, handler) )
+        subscription = JSSubscription(event, selector, handler)
+        self._subscriptions.append(subscription)
+        return subscription
 
     def __iter__(self):
         return iter(self._subscriptions)
 
 
+def subscribe(selector, event=CLICK):
+    """A decorator for defining a javascript event handler."""
+    def createSubscription(func):
+        frame = sys._getframe(1)
+        f_locals = frame.f_locals
+        subs = f_locals.setdefault('jsSubscriptions', JSSubscriptions())
+        return subs.subscribe(event, selector, func)
+    return createSubscription
+
+
 class JSSubscriptionsViewlet(viewlet.ViewletBase):
     """An viewlet for the JS viewlet manager rendering subscriptions."""
     zope.component.adapts(

Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.txt	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.txt	2007-07-19 03:07:45 UTC (rev 78132)
@@ -51,6 +51,9 @@
 We have finally all the pieces together to subscribe the event:
 
   >>> manager.subscribe(jsevent.CLICK, selector, showHelloWorldAlert)
+  <JSSubscription event=<JSEvent "click">,
+                  selector=<IdSelector "message">,
+                  handler=<function showHelloWorldAlert at ...>>
 
 So now we can see the subscription:
 
@@ -115,6 +118,40 @@
   })
 
 
+The Subscription Decorator
+--------------------------
+
+When defining JS event subscriptions from within a presentation component,
+using the low-level subscription API is somewhat cumbersome. Thus, there
+exists a decorator called ``subscribe``, which can convert a component method
+as a subscription handler. Let's have a look:
+
+  >>> class MyView(object):
+  ...
+  ...     @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.DBLCLICK)
+  ...     def alertUser(event, selector, request):
+  ...         return u"alert('`%s` event occured on DOM element `%s`');" %(
+  ...             event.name, selector.id)
+
+As you can see, the function is never really meant to be a method, but a
+subscription handler; thus no ``self`` as first argument. The subscription is
+now available in the subscriptions manager of the view:
+
+  >>> list(MyView.jsSubscriptions)
+  [<JSSubscription event=<JSEvent "dblclick">,
+                   selector=<IdSelector "myid">,
+                   handler=<function alertUser at ...>>]
+
+Let's now render the subscription:
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (list(MyView.jsSubscriptions)[0], request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  $("#myid").bind("dblclick",
+       function(){alert('`dblclick` event occured on DOM element `myid`');});
+
+
 Javascript Viewlet
 ------------------
 
@@ -141,6 +178,64 @@
   </script>
 
 
+Selectors
+---------
+
+The module provides several DOM element selectors. It is the responsibility of
+the corresponding rednerer to interpret the selector.
+
+Id Selector
+~~~~~~~~~~~
+
+The id selector selects a DOM element by id, as seen above. It is simply
+initialized using the the id:
+
+  >>> idselect = jsevent.IdSelector('myid')
+  >>> idselect
+  <IdSelector "myid">
+
+The id is also available as attribute:
+
+  >>> idselect.id
+  'myid'
+
+We already saw before how it gets rendered:
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (idselect, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> renderer.render()
+  u'#myid'
+
+CSS Selector
+~~~~~~~~~~~~
+
+The CSS selector selects a DOM element using an arbitrary CSS selector
+expression. This selector is initialized using the expression:
+
+  >>> cssselect = jsevent.CSSSelector('div.myclass')
+  >>> cssselect
+  <CSSSelector "div.myclass">
+
+The CSS selector expression is also available as attribute:
+
+  >>> cssselect.expr
+  'div.myclass'
+
+Let's now see an example on how the CSS selector can be rendered:
+
+  >>> zope.component.provideAdapter(testing.CSSSelectorRenderer)
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (cssselect, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> renderer.render()
+  u'div.myclass'
+
+Since most JS libraries support CSS selectors by default, the renderer simply
+converts the expression to unicode.
+
+
 Available Events
 ----------------
 

Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -20,18 +20,13 @@
 import zope.component
 from zope.publisher.interfaces import NotFound
 
-from z3c.traverser.traverser import SingleAttributeTraverserPlugin
-from z3c.traverser.interfaces import IPluggableTraverser, ITraverserPlugin
 from z3c.form.interfaces import IWidget, IField
 
-from z3c.formjs import interfaces
+from z3c.formjs import ajax, interfaces
 
-# Traverser Plugin to the ``validate()`` method of the ``IAJAXValidator``
-ValidateTraverser = SingleAttributeTraverserPlugin('validate')
+class BaseValidator(ajax.AJAXRequestHandler):
+    zope.interface.implements(interfaces.IAJAXValidator)
 
-class BaseValidator(object):
-    zope.interface.implements(interfaces.IAJAXValidator, IPluggableTraverser)
-
     # See ``interfaces.IAJAXValidator``
     ValidationScript = None
 
@@ -41,18 +36,7 @@
         self.updateWidgets()
         return self.widgets.extract()
 
-    def publishTraverse(self, request, name):
-        # Act like a pluggable traverser.
-        for traverser in zope.component.subscribers(
-                 (self, request), ITraverserPlugin):
-            try:
-                return traverser.publishTraverse(request, name)
-            except NotFound:
-                pass
 
-        raise NotFound(self.context, name, request)
-
-
 class MessageValidationScript(object):
     zope.interface.implements(interfaces.IMessageValidationScript)
 
@@ -69,6 +53,7 @@
     '''Validator that sends error messages for widget in questiodn.'''
     ValidationScript = MessageValidationScript
 
+    @ajax.handler
     def validate(self):
         data, errors = self._validate()
         if errors:

Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt	2007-07-19 03:07:45 UTC (rev 78132)
@@ -69,22 +69,31 @@
   ...     None, jsaction.WidgetSelector(edit.widgets['zip']), request)
   $.get('/validate', function(data){ alert(data) })
 
-Validators also support pluggable traverser plugins. So once we register the
-``validate`` traverser for any validator, ...
+Validators use AJAX handlers to communicate with the server. Commonly the AJAX
+handler is looked up via the "ajax" view -- see ``ajax.txt`` for more
+details. In this case we just create a small helper function:
 
-  >>> from z3c.formjs import interfaces
+  >>> from z3c.formjs import ajax
+
+  >>> def AJAXPlugin(view):
+  ...     return ajax.AJAXRequestTraverserPlugin(view, view.request)
+
+
+  >>> from z3c.formjs import ajax, interfaces
+  >>> from zope.publisher.interfaces.browser import IBrowserRequest
+
   >>> zope.component.provideSubscriptionAdapter(
-  ...     jsvalidator.ValidateTraverser,
-  ...     (interfaces.IAJAXValidator, TestRequest) )
+  ...     ajax.AJAXRequestTraverserPlugin,
+  ...     (interfaces.IFormTraverser, IBrowserRequest))
 
-we can traverse to the ``validate`` method and render it. Let's first render
-some valid input:
+we can traverse to the ``validate`` method from the "ajax" view and render
+it. Let's first render some valid input:
 
   >>> request = TestRequest(form={'widget-name' : 'zip',
   ...                             'form.widgets.zip' : u'29132'})
   >>> edit = AddressEditForm(None, request)
   >>> edit.update()
-  >>> edit.publishTraverse(request, 'validate')()
+  >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()
   u''
 
 As you can see there is no error message. Let's now provide an invalid ZIP
@@ -94,14 +103,14 @@
   ...                             'form.widgets.zip':'notazipcode'})
   >>> edit = AddressEditForm(None, request)
   >>> edit.update()
-  >>> edit.publishTraverse(request, 'validate')()
+  >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()
   u'The system could not process the given value.'
 
 Of course, one cannot just traverse to any attribute in the form:
 
-  >>> edit.publishTraverse(request, 'ValidationScript')
+  >>> AJAXPlugin(edit).publishTraverse(None, 'ValidationScript')()
   Traceback (most recent call last):
   ...
-  NotFound: Object: None, name: 'ValidationScript'
+  NotFound: Object: <AddressEditForm ...>, name: 'ValidationScript'
 
 And that's it.

Modified: z3c.formjs/trunk/src/z3c/formjs/testing.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/testing.py	2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/testing.py	2007-07-19 03:07:45 UTC (rev 78132)
@@ -39,6 +39,19 @@
     def render(self):
         return u'#' + self.selector.id
 
+class CSSSelectorRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(interfaces.ICSSSelector, IBrowserRequest)
+
+    def __init__(self, selector, request):
+        self.selector = selector
+
+    def update(self):
+        pass
+
+    def render(self):
+        return unicode(self.selector.expr)
+
 class SubscriptionRenderer(object):
     zope.interface.implements(interfaces.IRenderer)
     zope.component.adapts(interfaces.IJSSubscription, IBrowserRequest)



More information about the Checkins mailing list