[Checkins] SVN: z3c.formjs/trunk/ Complete overhaul of the entire code base.

Stephan Richter srichter at cosmos.phy.tufts.edu
Fri Jul 6 17:16:22 EDT 2007


Log message for revision 77537:
  Complete overhaul of the entire code base.
  

Changed:
  U   z3c.formjs/trunk/CHANGES.txt
  U   z3c.formjs/trunk/src/z3c/formjs/README.txt
  D   z3c.formjs/trunk/src/z3c/formjs/browser/
  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
  A   z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
  A   z3c.formjs/trunk/src/z3c/formjs/jsaction.py
  A   z3c.formjs/trunk/src/z3c/formjs/jsaction.txt
  D   z3c.formjs/trunk/src/z3c/formjs/jsbutton.py
  D   z3c.formjs/trunk/src/z3c/formjs/jsbutton.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
  D   z3c.formjs/trunk/src/z3c/formjs/jswidget.py
  A   z3c.formjs/trunk/src/z3c/formjs/jswidget.txt
  U   z3c.formjs/trunk/src/z3c/formjs/testing.py
  A   z3c.formjs/trunk/src/z3c/formjs/tests/
  A   z3c.formjs/trunk/src/z3c/formjs/tests/__init__.py
  A   z3c.formjs/trunk/src/z3c/formjs/tests/buttons_form.pt
  A   z3c.formjs/trunk/src/z3c/formjs/tests/simple_edit.pt
  A   z3c.formjs/trunk/src/z3c/formjs/tests/test_doc.py
  D   z3c.formjs/trunk/src/z3c/formjs/tests.py

-=-
Modified: z3c.formjs/trunk/CHANGES.txt
===================================================================
--- z3c.formjs/trunk/CHANGES.txt	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/CHANGES.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -7,7 +7,10 @@
 
 - Feature: Implementation of AJAX-driven widget value validation.
 
+- Restructure: Completely overhauled the entire API to be most easy to use and
+  have the most minimal implementation.
 
+
 Version 0.1.0 (6/29/2007)
 -------------------------
 

Modified: z3c.formjs/trunk/src/z3c/formjs/README.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/README.txt	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/README.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -9,10 +9,10 @@
 
 The documents are ordered in the way they should be read:
 
-- ``jsbutton.txt`` [must read]
+- ``jsaction.txt`` [must read]
 
   This document describes how JS scripts can be connected to events on a
-  button.
+  any widget, inclduing buttons.
 
 - ``jsvalidator.txt`` [must read]
 
@@ -23,3 +23,8 @@
 
   This documents describes the generalization that allows hooking up script to
   events on any field.
+
+- ``jqueryrenderer.txt`` [advanced users]
+
+  This document demonstrates all necessary backend renderer components
+  necessary to accomplish any of the features of this package.

Modified: z3c.formjs/trunk/src/z3c/formjs/configure.zcml
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/configure.zcml	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/configure.zcml	2007-07-06 21:16:21 UTC (rev 77537)
@@ -8,16 +8,8 @@
       provides="z3c.form.interfaces.IFieldWidget"
       />
 
-  <adapter
-      factory=".jsevent.JSEventsRenderer"
-      />
-
-  <adapter
-      factory=".jsevent.JSFormEventsRenderer"
-      />
-
   <!-- JavaScript Validation -->
-  <browser:view
+  <view
       for=".interfaces.IAJAXValidator"
       type="zope.publisher.interfaces.http.IHTTPRequest"
       provides="zope.publisher.interfaces.http.IHTTPPublisher"
@@ -25,7 +17,7 @@
       permission="zope.Public"
       />
 
-  <browser:view
+  <view
       for=".interfaces.IAJAXValidator"
       type="zope.publisher.interfaces.browser.IBrowserRequest"
       provides="zope.publisher.interfaces.browser.IBrowserPublisher"
@@ -40,117 +32,68 @@
       factory=".jsvalidator.ValidateTraverser"
       />
 
-  <!-- Widget Factory -->
-  <adapter
-      factory=".jswidget.JSEventsWidget"
-      />
-
-  <!-- Path adapter -->
-  <class class="z3c.formjs.jsevent.JSEventPath">
-    <allow interface="zope.traversing.interfaces.ITraversable" />
-  </class>
-
-  <adapter
-      for="z3c.form.interfaces.IForm"
-      name="jsevents"
-      provides="zope.traversing.interfaces.IPathAdapter"
-      factory="z3c.formjs.jsevent.JSEventPath"
-      trusted="True"
-      />
-
   <!-- JavaScript Event Utitilities -->
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="click"
       component="z3c.formjs.jsevent.CLICK"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="dblclick"
       component="z3c.formjs.jsevent.DBLCLICK"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="change"
       component="z3c.formjs.jsevent.CHANGE"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="load"
       component="z3c.formjs.jsevent.LOAD"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="blur"
       component="z3c.formjs.jsevent.BLUR"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="focus"
       component="z3c.formjs.jsevent.FOCUS"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="keydown"
       component="z3c.formjs.jsevent.KEYDOWN"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="keyup"
       component="z3c.formjs.jsevent.KEYUP"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="mousedown"
       component="z3c.formjs.jsevent.MOUSEDOWN"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="mousemove"
       component="z3c.formjs.jsevent.MOUSEMOVE"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="mouseout"
       component="z3c.formjs.jsevent.MOUSEOUT"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="mouseover"
       component="z3c.formjs.jsevent.MOUSEOVER"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="mouseup"
       component="z3c.formjs.jsevent.MOUSEUP"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="resize"
       component="z3c.formjs.jsevent.RESIZE"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="select"
       component="z3c.formjs.jsevent.SELECT"
       />
-
   <utility
-      provides="z3c.formjs.interfaces.IJSEvent"
       name="submit"
       component="z3c.formjs.jsevent.SUBMIT"
       />

Modified: z3c.formjs/trunk/src/z3c/formjs/interfaces.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/interfaces.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/interfaces.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -16,132 +16,137 @@
 $Id: $
 """
 __docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
 
-
-from zope.interface import Interface
-from zope import schema
-
 from z3c.form.interfaces import IButton, IButtonHandler, IManager, IWidget
 from z3c.form.interfaces import ISelectionManager, IForm
 
-class IJSEvent(Interface):
+
+# -----[ Event Subscription ]------------------------------------------------
+
+class IJSEvent(zope.interface.Interface):
     """An interface for javascript event objects."""
 
-    name = schema.TextLine(
+    name = zope.schema.TextLine(
         title=u"Event Name",
         description=u"The name of an event (i.e. click/dblclick/changed).",
         required=True)
 
 
-class IJSEvents(ISelectionManager):
-    """Selection manager for javascript event objects."""
+class ISelector(zope.interface.Interface):
+    """An object describing the selection of DOM Elements."""
 
+class IIdSelector(ISelector):
+    """Select a DOM element by Id."""
 
-class IJSEventRenderer(Interface):
-    """A renderer that produces javascript code for connecting DOM elements
-    to events.
-    """
-
-    event = schema.Object(
-        title=u"The type of event to be rendered.",
-        schema=IJSEvent,
+    id = zope.schema.TextLine(
+        title=u"Id",
+        description=u"Id of the DOM element to be selected.",
         required=True)
 
-    def render(handler, id, form):
-        """render javascript to link DOM element with given id to the
-        code produced by the given handler.
-        """
 
-class IJSEventsRenderer(Interface):
-    """A renderer that produces javascript code for connecting DOM elements
-    to events.
-    """
+class IJSSubscription(zope.interface.Interface):
+    """A Subscription within Javascript."""
 
-    events = schema.Object(
-        title=u"The set of events to be rendered.",
-        schema=IJSEvents,
+    event = zope.schema.Object(
+        title=u"Event",
+        description=u"The event.",
+        schema = IJSEvent,
         required=True)
 
-    def render(widget, form):
-        """render javascript events on widget.
-        """
+    selector = zope.schema.Object(
+        title=u"Selector",
+        description=u"The DOM element selector.",
+        schema = ISelector,
+        required=True)
 
+    handler = zope.schema.Field(
+        title=u"Handler",
+        description=(u"A callable nneding three argument: event, selector, "
+                     u"and request."),
+        required=True)
 
-class IJSFormEventsRenderer(Interface):
-    """A renderer that produces javascript code for connecting DOM
-    elements related to a form to javascript events."""
 
-    form = schema.Object(
-        title=u"The form",
-        schema=IForm,
+class IJSSubscriptions(zope.interface.Interface):
+    """A manager of Javascript event subscriptions."""
+
+    def subscribe(event, selector, handler):
+        """Subscribe an event for a DOM element executing the handler's
+        result."""
+
+    def __iter__(self):
+        """Return an iterator of all subscriptions."""
+
+
+class IRenderer(zope.interface.Interface):
+    """Render a component in the intended output format."""
+
+    def update(self):
+        """Update renderer."""
+
+    def render(self):
+        """Render content."""
+
+# -----[ Wiidgets ]-----------------------------------------------------------
+
+class IWidgetSelector(ISelector):
+    """Select a DOM element using the action."""
+
+    action = zope.schema.Field(
+        title=u"Action",
+        description=u"The action being selected.",
         required=True)
 
-    def render():
-        """Render the javascript events."""
+# -----[ Views and Forms ]----------------------------------------------------
 
+class IHaveJSSubscriptions(zope.interface.Interface):
+    """An component that has a subscription manager .
 
-class IJSEventsWidget(Interface):
-    """Offers a jsEvents attribute."""
+    This component is most often a view component. When rendering a page this
+    interface is used to check whether any subscriptions must be rendered.
+    """
 
-    jsEvents = schema.Field(
-        title=u"JavaScript Events",
-        description=u"The javascript events associated with this widget.",
+    jsSubscriptions = zope.schema.Object(
+        title=u"Javascript Subscriptions",
+        description=u"Attribute holding the JS Subscription Manager.",
+        schema = IJSSubscriptions,
         required=True)
 
 
+# -----[ Buttons and Handlers ]----------------------------------------------
+
+
 class IJSButton(IButton):
     """A button that just connects to javascript handlers."""
 
 
-class IButtonWidget(IWidget):
-    """Button widget."""
-
-
 class IJSEventHandler(IButtonHandler):
     """A button handler for javascript buttons."""
 
-    def __call__(form, id):
+    def __call__(selector):
         """call the handler, passing it the form."""
 
 
-class IJSAction(Interface):
-    """Action"""
+# -----[ Validator ]--------------------------------------------------------
 
-    __name__ = schema.TextLine(
-        title=u'Name',
-        description=u'The object name.',
-        required=False,
-        default=None)
 
-    title = schema.TextLine(
-        title=u'Title',
-        description=u'The action title.',
-        required=True)
+class IValidationScript(zope.interface.Interface):
+    """Component that renders the script doing the validation."""
 
+    def render():
+        """Render the js expression."""
 
-class IJSActions(IManager):
-    """A action manager"""
+class IMessageValidationScript(IValidationScript):
+    """Causes a message to be returned at validation."""
 
-    def update():
-        """Setup actions."""
 
-
-class IAJAXValidator(Interface):
+class IAJAXValidator(zope.interface.Interface):
     """A validator that sends back validation data sent from an ajax request."""
 
-    ValidationRenderer = schema.Object(
-                    schema=Interface,
-                    title=u"Validation Renderer")
+    ValidationScript = zope.schema.Object(
+        title=u"Validation Script",
+        schema=IValidationScript)
 
     def validate():
-        """return validation data."""
-
-
-class IJSMessageValidationRenderer(Interface):
-    """renders a js expression for sending/processing ajax validation requests."""
-
-    def __init__(form, field, request):
-        """store the form field and request, because this adapts those items."""
-
-    def render():
-        """Render the js expression."""
+        """Return validation data."""

Modified: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -24,55 +24,93 @@
 from z3c.formjs import interfaces
 
 
-class JQueryEventRenderer(object):
-    """IJSEventRenderer implementation.
+class JQueryIdSelectorRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(
+        interfaces.IIdSelector, IJQueryJavaScriptBrowserLayer)
 
-    See ``interfaces.IJSEventRenderer``.
-    """
-    zope.interface.implements(interfaces.IJSEventRenderer)
+    def __init__(self, selector, request):
+        self.selector = selector
+
+    def update(self):
+        pass
+
+    def render(self):
+        return u'#' + self.selector.id
+
+
+class JQuerySubscriptionRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
     zope.component.adapts(
-        interfaces.IJSEvent, IJQueryJavaScriptBrowserLayer)
+        interfaces.IJSSubscription, IJQueryJavaScriptBrowserLayer)
 
-    def __init__(self, event, request):
+    def __init__(self, subscription, request):
+        self.subscription = subscription
         self.request = request
-        self.event = event
 
-    def render(self, handler, id, form):
-        return '$("#%s").bind("%s", function(){%s});' % (
-                          id, self.event.name, handler(form, id))
+    def update(self):
+        self.selectorRenderer = zope.component.getMultiAdapter(
+            (self.subscription.selector, self.request), interfaces.IRenderer)
+        self.selectorRenderer.update()
 
+    def render(self):
+        return u'$("%s").bind("%s", function(){%s});' %(
+            self.selectorRenderer.render(),
+            self.subscription.event.name,
+            self.subscription.handler(
+                self.subscription.event,
+                self.subscription.selector,
+                self.request) )
 
-class JQueryBaseValidationRenderer(object):
 
-    def __init__(self, form, field, request):
-        self.form = form
-        self.field = field
+class JQuerySubscriptionsRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(
+        interfaces.IJSSubscriptions, IJQueryJavaScriptBrowserLayer)
+
+    def __init__(self, manager, request):
+        self.manager = manager
         self.request = request
 
+    def update(self):
+        self.renderers = []
+        for subscription in self.manager:
+            renderer = zope.component.getMultiAdapter(
+                (subscription, self.request), interfaces.IRenderer)
+            renderer.update()
+            self.renderers.append(renderer)
+
+    def render(self):
+        return '$(document).ready(function(){\n  %s\n}' %(
+            '\n  '.join([r.render() for r in self.renderers]) )
+
+
+class JQueryBaseValidationScriptRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+
+    def __init__(self, script, request):
+        self.script = script
+        self.request = request
+
     def _ajaxURL(self):
-        widget = self.form.widgets[self.field.__name__]
+        widget = self.script.widget
+        form = self.script.form
         # build js expression for extracting widget value
-        # XXX: Maybe we should adapt the widget to IJSValueExtractorRenderer?
         valueString = '$("#%s").val()' % widget.id
-
         # build a js expression that joins valueString expression
         queryString = '"?widget-id=%s&%s=" + %s' % (
             widget.id, widget.name, valueString)
-
         # build a js expression that joins form url, validate path, and query
         # string
-        ajaxURL = '"'+self.form.request.getURL() + '/validate" + ' + queryString
+        ajaxURL = '"'+form.request.getURL() + '/validate" + ' + queryString
 
         return ajaxURL
 
 
-class JQueryMessageValidationRenderer(JQueryBaseValidationRenderer):
+class JQueryMessageValidationScriptRenderer(JQueryBaseValidationScriptRenderer):
+    zope.component.adapts(
+        interfaces.IMessageValidationScript, IJQueryJavaScriptBrowserLayer)
 
-    zope.interface.implements(interfaces.IJSMessageValidationRenderer)
-    zope.component.adapts(interfaces.IAJAXValidator,
-                          zope.interface.Interface,
-                          IJQueryJavaScriptBrowserLayer)
-
     def render(self):
         ajaxURL = self._ajaxURL()
         # build a js expression that shows the user the error message

Added: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,92 @@
+====================
+Renderers for JQuery
+====================
+
+The ``jqueryrenderer`` module implements the backend javascript code using the
+JQuery Javascript library. All renderers assume that JQuery has been loaded.
+
+  >>> from z3c.formjs import interfaces, jqueryrenderer
+
+
+``IdSelector`` Renderer
+-----------------------
+
+JQuery uses CSS-selector syntax to select DOM elements. This makes the id
+selector renderer a very simple component:
+
+  >>> from z3c.formjs import jsevent
+  >>> selector = jsevent.IdSelector('form-id')
+
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+
+  >>> from jquery.layer import IJQueryJavaScriptBrowserLayer
+  >>> import zope.interface
+  >>> zope.interface.alsoProvides(request, IJQueryJavaScriptBrowserLayer)
+
+Let's now register the renderer:
+
+  >>> import zope.component
+  >>> zope.component.provideAdapter(jqueryrenderer.JQueryIdSelectorRenderer)
+
+Like any view component, the renderer must be updated before being rendered:
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (selector, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  #form-id
+
+
+``JSSubscription`` Renderer
+---------------------------
+
+The renderer for the subscription must correctly hook up the script (handler)
+as the event listener for the element. So let's create a subscription:
+
+  >>> def handler(event, selector, request):
+  ...    return 'alert("Here!");'
+
+  >>> subscription = jsevent.JSSubscription(
+  ...     jsevent.DBLCLICK, selector, handler)
+
+Let's now register the renderer:
+
+  >>> import zope.component
+  >>> zope.component.provideAdapter(jqueryrenderer.JQuerySubscriptionRenderer)
+
+Now we can render the subscription:
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (subscription, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  $("#form-id").bind("dblclick", function(){alert("Here!");});
+
+
+``JSSubscriptions`` Renderer
+----------------------------
+
+The subscriptions manager renderer must then be able to combine all
+subscriptions and make sure that they are loaded once the document is ready
+for them.
+
+  >>> subscriptions = jsevent.JSSubscriptions()
+  >>> subscriptions.subscribe(jsevent.CLICK, selector, handler)
+  >>> subscriptions.subscribe(jsevent.DBLCLICK, selector, handler)
+
+Let's now register the renderer:
+
+  >>> import zope.component
+  >>> zope.component.provideAdapter(jqueryrenderer.JQuerySubscriptionsRenderer)
+
+Now we can render the subscriptions:
+
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (subscriptions, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  $(document).ready(function(){
+    $("#form-id").bind("click", function(){alert("Here!");});
+    $("#form-id").bind("dblclick", function(){alert("Here!");});
+  }


Property changes on: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: z3c.formjs/trunk/src/z3c/formjs/jsaction.py (from rev 77485, z3c.formjs/trunk/src/z3c/formjs/jsbutton.py)
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsaction.py	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/jsaction.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,131 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Javascript Form Framework Button Framework.
+
+$Id: $
+"""
+__docformat__ = "reStructuredText"
+import sys
+import zope.component
+import zope.interface
+import zope.location
+from z3c.form import button, action
+from z3c.form.browser.button import ButtonWidget
+from z3c.form.interfaces import IFormLayer, IFieldWidget, IFormAware
+from z3c.form.interfaces import IButtonAction, IAfterWidgetUpdateEvent
+
+from z3c.formjs import interfaces, jsevent
+
+
+class WidgetSelector(jsevent.IdSelector):
+    zope.interface.implements(interfaces.IWidgetSelector)
+
+    def __init__(self, widget):
+        self.widget = widget
+
+    @property
+    def id(self):
+        return self.widget.id
+
+
+class JSButton(button.Button):
+    """A simple javascript button in a form."""
+    zope.interface.implements(interfaces.IJSButton)
+
+
+class JSButtonAction(action.Action, ButtonWidget, zope.location.Location):
+    """A button action specifically for JS buttons."""
+    zope.interface.implements(IButtonAction)
+    zope.component.adapts(IFormLayer, interfaces.IJSButton)
+
+    def __init__(self, request, field):
+        action.Action.__init__(self, request, field.title)
+        ButtonWidget.__init__(self, request)
+        self.field = field
+
+    @property
+    def accesskey(self):
+        return self.field.accessKey
+
+    @property
+    def value(self):
+        return self.title
+
+    @property
+    def id(self):
+        return self.name.replace('.', '-')
+
+    def update(self):
+        super(JSButtonAction, self).update()
+        # Step 1: Get the handler.
+        handler = self.form.handlers.getHandler(self.field)
+        # Step 2: Create a selector.
+        selector = WidgetSelector(self)
+        # Step 3: Make sure that the form has JS subscriptions, otherwise add
+        #         it.
+        if not interfaces.IHaveJSSubscriptions.providedBy(self.form):
+            self.form.jsSubscriptions = jsevent.JSSubscriptions()
+            zope.interface.alsoProvides(
+                self.form, interfaces.IHaveJSSubscriptions)
+        # Step 4: Add the subscription to the form:
+        self.form.jsSubscriptions.subscribe(handler.event, selector, handler)
+
+
+class JSHandler(object):
+    zope.interface.implements(interfaces.IJSEventHandler)
+
+    def __init__(self, button, func, event=jsevent.CLICK):
+        self.button = button
+        self.func = func
+        self.event = event
+
+    def __call__(self, event, selector, request):
+        return self.func(selector.widget.form, selector)
+
+    def __repr__(self):
+        return '<%s for %r>' %(self.__class__.__name__, self.button)
+
+
+def handler(field, **kwargs):
+    """A decorator for defining a javascript event handler."""
+    def createHandler(func):
+        handler = JSHandler(field, func, **kwargs)
+        frame = sys._getframe(1)
+        f_locals = frame.f_locals
+        handlers = f_locals.setdefault('handlers', button.Handlers())
+        handlers.addHandler(field, handler)
+        return handler
+    return createHandler
+
+
+ at zope.interface.implementer(zope.interface.Interface)
+ at zope.component.adapter(IAfterWidgetUpdateEvent)
+def createSubscriptionsForWidget(event):
+    widget = event.widget
+    if not (IFieldWidget.providedBy(widget) and IFormAware.providedBy(widget)):
+        return
+    # Step 1: Get the handler.
+    handler = widget.form.handlers.getHandler(widget.field)
+    if handler is None:
+        return
+    # Step 2: Create a selector.
+    selector = WidgetSelector(widget)
+    # Step 3: Make sure that the form has JS subscriptions, otherwise add
+    #         it.
+    if not interfaces.IHaveJSSubscriptions.providedBy(widget.form):
+        widget.form.jsSubscriptions = jsevent.JSSubscriptions()
+        zope.interface.alsoProvides(
+            widget.form, interfaces.IHaveJSSubscriptions)
+    # Step 4: Add the subscription to the form:
+    widget.form.jsSubscriptions.subscribe(handler.event, selector, handler)

Copied: z3c.formjs/trunk/src/z3c/formjs/jsaction.txt (from rev 77495, z3c.formjs/trunk/src/z3c/formjs/jsbutton.txt)
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsaction.txt	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/jsaction.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,374 @@
+=============================
+Javascript Events for Buttons
+=============================
+
+In the ``z3c.form`` package buttons are most commonly rendered as "submit"
+input fields within a form, meaning that the form will always be
+submitted. When working with Javascript, on the other hand, a click on the
+button often simply executes a script. The ``jsaction`` module of this package
+is designed to implement the latter kind.
+
+  >>> from z3c.formjs import jsaction
+
+
+Javascript Buttons
+------------------
+
+Before we can write a form that uses Javascript buttons, we have to define
+them first. One common way to define buttons in ``z3c.form`` is to write an
+schema describing them, so let's do that now:
+
+  >>> import zope.interface
+  >>> class IButtons(zope.interface.Interface):
+  ...     hello = jsaction.JSButton(title=u'Hello World!')
+  ...     dblhello = jsaction.JSButton(title=u'Double Hello World!')
+
+Instead of declaring ``z3c.form.button.Button`` fields, we are now using a
+derived Javascript button field. While there is no difference initially, they
+will later be rendered differently.
+
+
+Widget Selector
+---------------
+
+The widget selector, in contrast to the id selector, accepts a widget and
+provides a selector API.
+
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+
+  >>> from z3c.form.browser import text
+  >>> msg = text.TextWidget(request)
+  >>> msg.id = 'form-msg'
+
+  >>> selector = jsaction.WidgetSelector(msg)
+  >>> selector
+  <WidgetSelector "form-msg">
+
+Since the widget selector can determine the widget's id, it is also an id
+selector:
+
+  >>> from z3c.formjs import interfaces
+  >>> interfaces.IIdSelector.providedBy(selector)
+  True
+  >>> selector.id
+  'form-msg'
+
+This has the advantage that we do not need another renderer for this
+selector. Thus, within a form we can use the following pattern to subscribe a
+widget to an event:
+
+  >>> def showSelectedWidget(event, selector, request):
+  ...     return 'alert("%r");' %(selector.widget)
+
+  >>> import zope.interface
+  >>> from z3c.formjs import jsevent
+
+  >>> class Form(object):
+  ...     zope.interface.implements(interfaces.IHaveJSSubscriptions)
+  ...     jsSubscriptions = jsevent.JSSubscriptions()
+  ...
+  ...     def update(self):
+  ...         self.jsSubscriptions.subscribe(
+  ...             jsevent.CLICK, selector, showSelectedWidget)
+
+  >>> form = Form()
+  >>> form.update()
+
+After registering the renderers,
+
+  >>> from z3c.formjs import testing
+  >>> testing.setupRenderers()
+
+we can use the viewlet to check the subscription output:
+
+  >>> viewlet = jsevent.JSSubscriptionsViewlet(None, request, form, None)
+  >>> viewlet.update()
+  >>> print viewlet.render()
+  <script type="text/javascript">
+    $(document).ready(function(){
+      $("#form-msg").bind("click", function(){alert("<TextWidget None>");});
+    }
+  </script>
+
+
+Forms with Javascript Buttons
+-----------------------------
+
+The next step is create the form. Luckily we do not need any fields to render
+a form. Also, instead of using usual ``z3c.form.button.handler()`` function,
+we now have a special handler decorator that connects a button to a Javascript
+event. The output of the handler itself is a string that is used as the
+Javascript script that is executed.
+
+  >>> from z3c.form import button, form
+
+  # XXX: the handler should also receive the event
+
+  >>> class Form(form.Form):
+  ...     buttons = button.Buttons(IButtons)
+  ...
+  ...     @jsaction.handler(buttons['hello'])
+  ...     def showHelloWorldMessage(self, selector):
+  ...         return 'alert("%s");' % selector.widget.title
+  ...
+  ...     @jsaction.handler(buttons['dblhello'], event=jsevent.DBLCLICK)
+  ...     def showDoubleHelloWorldMessage(self, selector):
+  ...         return 'alert("%s");' % selector.widget.title
+
+The ``handler()`` decorator takes two arguments, the button (acting as the DOM
+element selector) and the event to which to bind the action. By default the
+event is ``jsevent.CLICK``.
+
+And that's really everything that is required from a user's point of
+view. Let's now see how those handler declarations are converted into actions
+and Javascript subscriptions. First we need to initialize the form:
+
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+
+  >>> demoform = Form(None, request)
+
+We also need to register an adapter to create an action from a button:
+
+  >>> from z3c.form.interfaces import IButtonAction
+  >>> zope.component.provideAdapter(
+  ...     jsaction.JSButtonAction, provides=IButtonAction)
+
+Action managers are instantiated using the form, request, and
+context/content. A button-action-manager implementation is avaialble in the
+``z3c.form.button`` package:
+
+  >>> actions = button.ButtonActions(demoform, request, None)
+  >>> actions.update()
+
+Once the action manager is updated, the buttons should be available as
+actions:
+
+  >>> actions.keys()
+  ['hello', 'dblhello']
+  >>> actions['hello']
+  <JSButtonAction 'form.buttons.hello' u'Hello World!'>
+
+Since these are special Javascript actions, updating them has also caused the
+form to become an ``IHaveJSSubscriptions`` view:
+
+  >>> from z3c.formjs import interfaces
+
+  >>> interfaces.IHaveJSSubscriptions.providedBy(demoform)
+  True
+  >>> demoform.jsSubscriptions
+  <z3c.formjs.jsevent.JSSubscriptions object at ...>
+
+The interesting part about button subscriptions is the selector.
+
+  >>> selector = list(demoform.jsSubscriptions)[0].selector
+  >>> selector
+  <WidgetSelector "form-buttons-hello">
+
+This special action selector is a derivative of the id selector and keeps the
+action.
+
+  >>> selector.id
+  'form-buttons-hello'
+  >>> selector.widget
+  <JSButtonAction 'form.buttons.hello' u'Hello World!'>
+
+
+Rendering the Form
+------------------
+
+Let's now see what we need to do to make the form render correctly and
+completly.
+
+  >>> demoform = Form(None, request)
+
+First we need some of the standard ``z3c.form`` registrations:
+
+  >>> from z3c.form import field, button
+  >>> zope.component.provideAdapter(field.FieldWidgets)
+  >>> zope.component.provideAdapter(button.ButtonActions)
+
+Next we need to register the template for our button actions:
+
+  >>> from zope.pagetemplate.interfaces import IPageTemplate
+  >>> from z3c.form import widget
+  >>> from z3c.form.interfaces import IButtonWidget, INPUT_MODE
+  >>> from z3c.form.testing import getPath
+
+  >>> zope.component.provideAdapter(
+  ...     widget.WidgetTemplateFactory(getPath('button_input.pt'), 'text/html'),
+  ...     (None, None, None, None, IButtonWidget),
+  ...     IPageTemplate, name=INPUT_MODE)
+
+We also need to setup a Javascript viewlet manager and register the
+subscription viewlet for it. (This is a bit tedious to do using the Python
+API, but using ZCML this is much simpler.)
+
+* Hook up the "provider" TALES expression type:
+
+  >>> from zope.app.pagetemplate.engine import TrustedEngine
+  >>> from zope.contentprovider import tales
+  >>> TrustedEngine.registerType('provider', tales.TALESProviderExpression)
+
+* Create a viewlet manager that does not require security to be setup:
+
+  >>> from zope.viewlet import manager
+  >>> class JSViewletManager(manager.ViewletManagerBase):
+  ...     def filter(self, viewlets):
+  ...         return viewlets
+
+* Register the viewlet manager as a content provider known as "javascript":
+
+  >>> from z3c.form.interfaces import IFormLayer
+  >>> from zope.contentprovider.interfaces import IContentProvider
+  >>> zope.component.provideAdapter(
+  ...     JSViewletManager,
+  ...     (None, IFormLayer, None),
+  ...     IContentProvider,
+  ...     name='javascript')
+
+* Register the JS Subscriber viewlet for this new viewlet manager:
+
+  >>> from zope.viewlet.interfaces import IViewlet
+  >>> zope.component.provideAdapter(
+  ...     jsevent.JSSubscriptionsViewlet,
+  ...     (None, IFormLayer, interfaces.IHaveJSSubscriptions,
+  ...      JSViewletManager), IViewlet, name='subscriptions')
+
+Finally, we need a template for our form:
+
+  >>> testing.addTemplate(demoform, 'buttons_form.pt')
+
+We can now render the form:
+
+  >>> demoform.update()
+  >>> print demoform.render()
+  <html>
+    <head>
+      <script type="text/javascript">
+        $(document).ready(function(){
+          $("#form-buttons-hello").bind("click",
+              function(){alert("Hello World!");});
+          $("#form-buttons-dblhello").bind("dblclick",
+              function(){alert("Double Hello World!");});
+        }
+      </script>
+    </head>
+    <body>
+      <div class="action">
+        <input type="button" id="form-buttons-hello"
+               name="form.buttons.hello" class="buttonWidget jsbutton-field"
+               value="Hello World!" />
+      </div>
+      <div class="action">
+        <input type="button" id="form-buttons-dblhello"
+               name="form.buttons.dblhello" class="buttonWidget jsbutton-field"
+               value="Double Hello World!" />
+      </div>
+    </body>
+  </html>
+
+
+Multiple Handlers
+-----------------
+
+Currently it is not possible to have multiple handlers for one dom element,
+even though the event is different!!!
+
+XXX: to be done
+
+
+Submit and Javascript Buttons Together
+--------------------------------------
+
+XXX: to be done
+
+
+Attaching Events to Form Fields
+-------------------------------
+
+Javascript handlers do not only work for buttons, but also for fields. Let's
+create a simple schema that we can use to create a form:
+
+  >>> import zope.schema
+
+  >>> class IPerson(zope.interface.Interface):
+  ...     name = zope.schema.TextLine(title=u'Name')
+  ...     age = zope.schema.Int(title=u'Age')
+
+Even though somewhat pointless, whenever the age field is clicked on or the
+name changed, we would like to get an alert:
+
+  XXX: Ugliness! fields['age'].field --> fields['age']
+
+  >>> class PersonAddForm(form.AddForm):
+  ...     fields = field.Fields(IPerson)
+  ...
+  ...     @jsaction.handler(fields['age'].field)
+  ...     def ageClickEvent(self, selector):
+  ...         return 'alert("The Age was Clicked!");'
+  ...
+  ...     @jsaction.handler(fields['name'].field, event=jsevent.CHANGE)
+  ...     def nameChangeEvent(self, selector):
+  ...         return 'alert("The Name was Changed!");'
+
+We also need to register all the default form registrations:
+
+  >>> from z3c.form.testing import setupFormDefaults
+  >>> setupFormDefaults()
+
+After adding a simple template for the form, it can be rendered:
+
+  >>> addform = PersonAddForm(None, request)
+  >>> testing.addTemplate(addform, 'simple_edit.pt')
+  >>> addform.update()
+  >>> print addform.render()
+  <html>
+    <head>
+    </head>
+    ...
+  </html>
+
+As you can see there were no subscriptions rendererd. This reason is that we
+have not yet registered the event listener to the ``IAfterWidgetUpdateEvent``
+event:
+
+  >>> zope.component.provideHandler(jsaction.createSubscriptionsForWidget)
+
+So, let's try this again:
+
+  >>> addform.update()
+  >>> print addform.render()
+  <html>
+    <head>
+      <script type="text/javascript">
+        $(document).ready(function(){
+          $("#form-widgets-name").bind("change",
+              function(){alert("The Name was Changed!");});
+          $("#form-widgets-age").bind("click",
+              function(){alert("The Age was Clicked!");});
+        }
+      </script>
+    </head>
+    <body>
+  <BLANKLINE>
+  <BLANKLINE>
+      <form action=".">
+        <div class="row">
+          <label for="form-widgets-name">Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="textWidget textline-field" value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="textWidget int-field" value="" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-add" name="form.buttons.add"
+                 class="submitWidget button-field" value="Add" />
+        </div>
+      </form>
+    </body>
+  </html>

Deleted: z3c.formjs/trunk/src/z3c/formjs/jsbutton.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsbutton.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsbutton.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -1,75 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Javascript Form Framework Button Framework.
-
-$Id: $
-"""
-__docformat__ = "reStructuredText"
-import sys
-import zope.schema
-import zope.interface
-import zope.location
-import zope.component
-from jquery.layer import IJQueryJavaScriptBrowserLayer
-from zope.app.pagetemplate import ViewPageTemplateFile
-from z3c.form import button, action, widget
-from z3c.form.interfaces import IButton, IButtonHandlers, IButtonForm
-from z3c.form.interfaces import IFieldWidget, IValue, IFormLayer
-
-from z3c.formjs import interfaces, jsevent
-
-
-class ButtonWidget(widget.Widget):
-    """A submit button of a form."""
-    zope.interface.implementsOnly(interfaces.IButtonWidget)
-
-    css = u'buttonWidget'
-    accesskey = None
-    template = ViewPageTemplateFile("browser/button_input.pt")
-
- at zope.component.adapter(IButton, IJQueryJavaScriptBrowserLayer)
- at zope.interface.implementer(IFieldWidget)
-def ButtonFieldWidget(field, request):
-    button = widget.FieldWidget(field, ButtonWidget(request))
-    button.value = field.title
-    return button
-
-
-class JSButton(button.Button):
-    """A simple javascript button in a form."""
-    zope.interface.implements(interfaces.IJSButton)
-
-
-class JSButtonAction(action.Action, ButtonWidget, zope.location.Location):
-    zope.interface.implements(IFieldWidget)
-    zope.component.adapts(
-        IJQueryJavaScriptBrowserLayer,
-        interfaces.IJSButton)
-
-    def __init__(self, request, field):
-        action.Action.__init__(self, request, field.title)
-        ButtonWidget.__init__(self, request)
-        self.field = field
-
-    @property
-    def accesskey(self):
-        return self.field.accessKey
-
-    @property
-    def value(self):
-        return self.title
-
-    @property
-    def id(self):
-        return self.name.replace('.', '-')

Deleted: z3c.formjs/trunk/src/z3c/formjs/jsbutton.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsbutton.txt	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsbutton.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -1,124 +0,0 @@
-==================
-JavaScript Buttons
-==================
-
-Buttons
-=======
-
-z3c.form defines buttons that always submit forms.  It is also highly
-useful to have buttons in your form that modify that perform a client
-side action using javascript.  z3c.formjs.button provides buttons with
-javascript event hooks.
-
-  >>> from z3c.formjs import jsbutton, jsevent
-  >>> from z3c.formjs import interfaces as jsinterfaces
-  >>> from z3c.formjs.testing import TestRequest
-
-JSButton
---------
-
-Just as in z3c.form, we can define buttons in a schema.
-
-  >>> import zope.interface
-  >>> class IButtons(zope.interface.Interface):
-  ...     apply = jsbutton.JSButton(title=u'Apply')
-  ...     cancel = jsbutton.JSButton(title=u'Cancel')
-
-From the button creation aspect, everything works exactly as in
-z3c.form.  The difference comes with the actions.  We will create a
-form that provides these buttons with javascript actions.
-
-  >>> from z3c.form import button, form
-  >>> from z3c.form import interfaces
-  >>> class Form(form.AddForm):
-  ...     buttons = button.Buttons(IButtons)
-  ...     prefix = 'form'
-  ...
-  ...     @jsevent.handler(buttons['apply'])
-  ...     def apply(self, id):
-  ...         return 'alert("You Clicked the Apply Button!");'
-  ...
-  ...     @jsevent.handler(buttons['cancel'], event=jsevent.DBLCLICK)
-  ...     def cancel(self, id):
-  ...         return 'alert("You Double Clicked the Cancel Button!");'
-
-Notice that the jsbutton.handler decorator takes the keyword argument
-event, which specifies what type of javascript event this handler will
-be attached to.
-
-Now we can create the button action manager just as we do with regular
-buttons
-
-Let' now create an action manager for the button manager in the form. To do
-that we first need a request and a form instance:
-
-  >>> request = TestRequest()
-  >>> addform = Form(None, request)
-
-Action managers are instantiated using the form, request, and
-context/content. A special button-action-manager implementation is avaialble
-in the ``z3c.form.button`` package:
-
-  >>> actions = button.ButtonActions(addform, request, None)
-  >>> actions.update()
-
-Once the action manager is updated, the buttons should be available as
-actions:
-
-  >>> actions.keys()
-  ['apply', 'cancel']
-  >>> actions['apply']
-  <JSButtonAction 'form.buttons.apply' u'Apply'>
-
-JSButton actions are locations:
-
-  >>> apply = actions['apply']
-  >>> apply.__name__
-  'apply'
-  >>> apply.__parent__
-  <ButtonActions None>
-
-A button action is also a button widget. The attributes translate as follows:
-
-  >>> jsinterfaces.IButtonWidget.providedBy(apply)
-  True
-
-Next we want to display our button actions. To be able to do this, we have to
-register a template for the button widget:
-
-  >>> from z3c.formjs import testing as jstesting
-  >>> from z3c.form import widget
-  >>> templatePath = jstesting.getPath('button_input.pt')
-  >>> factory = widget.WidgetTemplateFactory(templatePath, 'text/html')
-
-  >>> from zope.pagetemplate.interfaces import IPageTemplate
-  >>> zope.component.provideAdapter(factory,
-  ...     (zope.interface.Interface, TestRequest, None, None,
-  ...      jsinterfaces.IButtonWidget),
-  ...     IPageTemplate, name='input')
-
-A widget template has many discriminators: context, request, view, field, and
-widget. We can now render each action:
-
-  >>> print actions['apply'].render()
-  <input type="button" id="form-buttons-apply"
-         name="form.buttons.apply" class="buttonWidget"
-         value="Apply" />
-
-  >>> print actions['cancel'].render()
-  <input type="button" id="form-buttons-cancel"
-         name="form.buttons.cancel" class="buttonWidget"
-         value="Cancel" />
-
-Another way to render the events is completely separate from the
-buttons themselves.
-
-  >>> request = TestRequest()
-  >>> addform = Form(None, request)
-  >>> addform.update()
-  >>> zope.component.provideAdapter(jsevent.JSFormEventsRenderer)
-  >>> print jsinterfaces.IJSFormEventsRenderer(addform).render()
-  $("#form-buttons-apply").bind("click",
-      function(){alert("You Clicked the Apply Button!");});
-  $("#form-buttons-cancel").bind("dblclick",
-      function(){alert("You Double Clicked the Cancel Button!");});

Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -16,25 +16,18 @@
 $Id: $
 """
 __docformat__ = "reStructuredText"
-import sys
 import zope.component
-from zope.interface import implements
+import zope.interface
 from zope.publisher.interfaces.browser import IBrowserRequest
-from zope.traversing.interfaces import IPathAdapter, ITraversable
-from z3c.form import util, button
-from z3c.form.interfaces import IForm
-from jquery.layer import IJQueryJavaScriptBrowserLayer
+from zope.viewlet import viewlet
 
 from z3c.formjs import interfaces
 
 
 class JSEvent(object):
-    """IJSEvent implementation.
+    """Javascript Event"""
+    zope.interface.implements(interfaces.IJSEvent)
 
-    See ``interfaces.IJSEvent``.
-    """
-    implements(interfaces.IJSEvent)
-
     def __init__(self, name):
         self.name = name
 
@@ -60,140 +53,61 @@
 SUBMIT = JSEvent("submit")
 
 
-class JSEvents(util.SelectionManager):
-    """Selection manager for IJSEvents."""
+class IdSelector(object):
+    zope.interface.implements(interfaces.IIdSelector)
 
-    implements(interfaces.IJSEvents)
+    def __init__(self, id):
+        self.id = id
 
-    def __init__(self, *args, **kwargs):
-        super(JSEvents, self).__init__(*args, **kwargs)
-        for kw in kwargs:
-            self._data_keys.append(kw)
-            self._data_values.append(kwargs[kw])
-            self._data[kw] = kwargs[kw]
+    def __repr__(self):
+        return '<%s "%s">' %(self.__class__.__name__, self.id)
 
 
-class JSEventsRenderer(object):
-    """IJSEventsRenderer implementation"""
-    implements(interfaces.IJSEventsRenderer)
-    zope.component.adapts(interfaces.IJSEvents,
-                          IBrowserRequest)
+class JSSubscription(object):
+    zope.interface.implements(interfaces.IJSSubscription)
 
-    def __init__(self, events, request):
-        self.request = request
-        self.events = events
+    def __init__(self, event, selector, handler):
+        self.event = event
+        self.selector = selector
+        self.handler = handler
 
-    def render(self, widget, form):
-        result = ''
-        for eventName, handler in self.events.items():
-            event = zope.component.getUtility(
-                interfaces.IJSEvent, name=eventName)
-            renderer = zope.component.getMultiAdapter(
-                (event, self.request), interfaces.IJSEventRenderer)
-            result += renderer.render(handler, widget.id, form) + '\n'
-        return result
+    def __repr__(self):
+        return '<%s event=%r, selector=%r, handler=%r>' % (
+            self.__class__.__name__, self.event, self.selector, self.handler)
 
 
-class JSFormEventsRenderer(object):
-    """IJSEventsRenderer implementation"""
-    implements(interfaces.IJSFormEventsRenderer)
-    zope.component.adapts(IForm)
+class JSSubscriptions(object):
+    zope.interface.implements(interfaces.IJSSubscriptions)
 
-    def __init__(self, form):
-        self.form = form
-        self.request = form.request
+    def __init__(self):
+        self._subscriptions = []
 
-    def render(self):
-        result = ''
-        #first render events attached to widgets
-        for widget in filter(interfaces.IJSEventsWidget.providedBy,
-                             self.form.widgets.values()):
-            renderer = zope.component.getMultiAdapter(
-                (widget.jsEvents, self.request), interfaces.IJSEventsRenderer)
-            result += renderer.render(widget, self.form)
-        # render events attached to fields
-        if hasattr(self.form, 'jshandlers'):
-            for field in self.form.fields.values():
-                handler = self.form.jshandlers.getHandler(field)
-                if handler is not None:
-                    renderer = zope.component.getMultiAdapter(
-                        (handler.event, self.request),
-                        interfaces.IJSEventRenderer)
-                    # XXX: is this a safe way to get ids?
-                    # Answer: Yes it is, because field is a z3c.form Field!
-                    id = self.form.widgets[field.__name__].id
-                    result += renderer.render(handler, id, self.form) + '\n'
-            #render events attached to buttons
-            if hasattr(self.form, 'buttons'):
-                for key, button in self.form.buttons.items():
-                    handler = self.form.jshandlers.getHandler(button)
-                    if handler is not None:
-                        renderer = zope.component.getMultiAdapter(
-                            (handler.event, self.request),
-                            interfaces.IJSEventRenderer)
-                        # XXX: is this a safe way to get ids?
-                        id = self.form.actions[key].id
-                        result += renderer.render(handler, id, self.form) + '\n'
+    def subscribe(self, event, selector, handler):
+        self._subscriptions.append(
+            JSSubscription(event, selector, handler) )
 
-        return result
+    def __iter__(self):
+        return iter(self._subscriptions)
 
 
-class Handlers(button.Handlers):
-    """Event Handlers for Javascript Buttons."""
+class JSSubscriptionsViewlet(viewlet.ViewletBase):
+    """An viewlet for the JS viewlet manager rendering subscriptions."""
+    zope.component.adapts(
+        zope.interface.Interface,
+        IBrowserRequest,
+        interfaces.IHaveJSSubscriptions,
+        zope.interface.Interface)
 
-    def addHandler(self, button, handler):
-        """See z3c.form.interfaces.IButtonHandlers"""
-        # Create a specification for the button
-        buttonSpec = util.getSpecification(button)
-        if isinstance(buttonSpec, util.classTypes):
-            buttonSpec = zope.interface.implementedBy(buttonSpec)
-        # Register the handler
-        self._registry.register(
-            (buttonSpec,), interfaces.IJSEventHandler, '', handler)
-        self._handlers += ((button, handler),)
+    # This viewlet wants to be very heavy, so that it is rendered after all
+    # the JS libraries are loaded.
+    weight = 1000
 
-    def getHandler(self, button):
-        """See z3c.form.interfaces.IButtonHandlers"""
-        buttonProvided = zope.interface.providedBy(button)
-        return self._registry.lookup1(
-            buttonProvided, interfaces.IJSEventHandler)
+    def update(self):
+        self.renderer = zope.component.getMultiAdapter(
+            (self.__parent__.jsSubscriptions, self.request),
+            interfaces.IRenderer)
+        self.renderer.update()
 
-
-class Handler(object):
-    zope.interface.implements(interfaces.IJSEventHandler)
-
-    def __init__(self, button, func, event=CLICK):
-        self.button = button
-        self.func = func
-        self.event = event
-
-    def __call__(self, form, id):
-        return self.func(form, id)
-
-    def __repr__(self):
-        return '<%s for %r>' %(self.__class__.__name__, self.button)
-
-
-def handler(button, **kwargs):
-    """A decorator for defining a javascript event handler."""
-    def createHandler(func):
-        handler = Handler(button, func, event=kwargs.get('event', CLICK))
-        frame = sys._getframe(1)
-        f_locals = frame.f_locals
-        jshandlers = f_locals.setdefault('jshandlers', Handlers())
-        jshandlers.addHandler(button, handler)
-        return handler
-    return createHandler
-
-
-class JSEventPath(object):
-
-    zope.component.adapts(None)
-    zope.interface.implements(IPathAdapter, ITraversable)
-
-    def __init__(self, context):
-        self.context = context
-
-    def traverse(self, name, furtherPath=[]):
-        if name == 'renderer':
-            return interfaces.IJSFormEventsRenderer(self.context)
+    def render(self):
+        content = self.renderer.render()
+        return u'<script type="text/javascript">\n%s\n</script>' % content

Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.txt	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -1,282 +1,247 @@
-=================
-JavaScript Events
-=================
+===============================
+Connecting to Javascript Events
+===============================
 
-Events
-======
+The ``jsevent`` module of this package implements a mechanism to connect a
+Javascript script to an event of a DOM element. So let's have a look at how
+this works.
 
-z3c.formjs.jsevent provides tools for working with javascript events.
+  >>> from z3c.formjs import interfaces, jsevent
 
-  >>> from z3c.formjs import interfaces
-  >>> from z3c.formjs import jsevent
+To implement this functionality, we need to model three components: events,
+DOM elements (selector), and the script (handler). We will also need a manager
+to keep track of all the mappings. This is indeed somewhat similar to the Zope
+3 event model, though we do not need DOM elements to connect the events there.
 
-All the javascript event types are reproduced in python:
 
-  >>> jsevent.CLICK
-  <JSEvent "click">
-  >>> jsevent.DBLCLICK
-  <JSEvent "dblclick">
-  >>> jsevent.CHANGE
-  <JSEvent "change">
-  >>> jsevent.LOAD
-  <JSEvent "load">
-  >>> jsevent.BLUR
-  <JSEvent "blur">
-  >>> jsevent.FOCUS
-  <JSEvent "focus">
-  >>> jsevent.KEYDOWN
-  <JSEvent "keydown">
-  >>> jsevent.KEYUP
-  <JSEvent "keyup">
-  >>> jsevent.MOUSEDOWN
-  <JSEvent "mousedown">
-  >>> jsevent.MOUSEMOVE
-  <JSEvent "mousemove">
-  >>> jsevent.MOUSEOUT
-  <JSEvent "mouseout">
-  >>> jsevent.MOUSEOVER
-  <JSEvent "mouseover">
-  >>> jsevent.MOUSEUP
-  <JSEvent "mouseup">
-  >>> jsevent.RESIZE
-  <JSEvent "resize">
-  >>> jsevent.SELECT
-  <JSEvent "select">
-  >>> jsevent.SUBMIT
-  <JSEvent "submit">
+Subscription Manager
+--------------------
 
-These are actually objects that implement IJSEvent.
+So first we need to create a subscription manager in which to collect the
+subscriptions:
 
-  >>> interfaces.IJSEvent.providedBy(jsevent.CLICK)
-  True
+  >>> manager = jsevent.JSSubscriptions()
 
-You can create your own by just instantiating a new ``JSEvent``
-object:
+Initially, we have no registered events:
 
-  >>> MyEvent = jsevent.JSEvent("myevent")
-  >>> MyEvent
-  <JSEvent "myevent">
+  >>> list(manager)
+  []
 
-These are also provided as utilities so they can be looked up by name.
+We now want to subscribe to the "click" event of a DOM element with the id
+"message". When the event occurs, we would like to display a simple "Hello
+World" message.
 
-  >>> import zope.component
-  >>> zope.component.getUtility(interfaces.IJSEvent, 'click')
+The events are available in all capital letters, for example:
+
+  >>> jsevent.CLICK
   <JSEvent "click">
 
-Just to make sure, we'll see if all of these are in fact registered as
-utilities.
+The DOM element is selected using a selector, in our case an id selector:
 
-  >>> for event in ['click', 'dblclick', 'change', 'load', 'blur', 'focus',
-  ...	            'keydown', 'keyup', 'mousedown', 'mousemove',
-  ...               'mouseout', 'mouseover', 'mouseup', 'resize',
-  ...               'select', 'submit']:
-  ...    e = zope.component.getUtility(interfaces.IJSEvent, event)
-  ...    if not interfaces.IJSEvent.providedBy(e):
-  ...        print "This shouldn't be printed in the test."
+  >>> selector = jsevent.IdSelector('message')
+  >>> selector
+  <IdSelector "message">
 
-These events have javascript handlers which can be dynamically
-generated so we will define a handler using a function.
+The handler of the event is a callable accepting the event, selector and the
+request:
 
-  >>> def simpleHandler(form, id):
-  ...     return ('alert("Some event was called '
-  ...                    'for the element with id %s '
-  ...                    'and for the form %s");'
-  ...                    % (id, form))
+  >>> def showHelloWorldAlert(event, selector, request):
+  ...     return u'alert("Hello World!")'
 
-Another aspect of javascript events is that they get attached to a
-specific dom element using an id.  So let us make an imaginary dom
-element id.
+We have finally all the pieces together to subscribe the event:
 
-  >>> id = "form-field-age"
+  >>> manager.subscribe(jsevent.CLICK, selector, showHelloWorldAlert)
 
-Different javascript libraries handle events in different ways, so we
-have to specify which javascript library we want to use to handle the
-events so as to render the javascript correctly.  This is done using
-browser layers.  The formjs framework implements renderers for
-jquery.  The renderers are registered as adapters as follows.
+So now we can see the subscription:
 
-  >>> import zope.component
-  >>> from z3c.formjs import jqueryrenderer
-  >>> zope.component.provideAdapter(jqueryrenderer.JQueryEventRenderer)
+  >>> list(manager)
+  [<JSSubscription event=<JSEvent "click">,
+                   selector=<IdSelector "message">,
+                   handler=<function showHelloWorldAlert at ...>>]
 
-  >>> from z3c.formjs.testing import TestRequest
-  >>> from jquery.layer import IJQueryJavaScriptBrowserLayer
-  >>> request = TestRequest()
-  >>> IJQueryJavaScriptBrowserLayer.providedBy(request)
-  True
+So now, how does this get rendered into Javascript code? Since this package
+strictly separates definition from rendering, a renderer will be responsible
+to produce the output.
 
-  >>> renderer = zope.component.getMultiAdapter(
-  ...     (jsevent.CLICK, request), interfaces.IJSEventRenderer)
-  >>> renderer.render(simpleHandler, id, None)
-  '$("#form-field-age").bind("click", function(){alert("Some event was
-  called for the element with id form-field-age and for the form None");});'
 
+Renderers
+---------
 
-=======
-Widgets
-=======
+So let's define some renderers for the various components. We start with the
+selector. Let's say that the javascript framework supports CSS selectors, then
+the renderer is simple:
 
-Buttons are not the only dom elements that can have events attached to
-them, in reality we should be able to attach events to any element of
-the form; that is, to any widget in the form.
+  >>> import zope.component
+  >>> import zope.interface
+  >>> from zope.publisher.interfaces.browser import IBrowserRequest
 
-Creating a Widget and Attaching an Event
-----------------------------------------
+  >>> class IdSelectorRenderer(object):
+  ...     zope.interface.implements(interfaces.IRenderer)
+  ...     zope.component.adapts(interfaces.IIdSelector, IBrowserRequest)
+  ...
+  ...     def __init__(self, selector, request):
+  ...         self.selector = selector
+  ...
+  ...     def update(self):
+  ...         pass
+  ...
+  ...     def render(self):
+  ...         return u'#' + self.selector.id
 
-Taking from the widget.txt file in z3c.form, we will set up a widget
-with its own widget template, et cetera, to work with.
+  >>> zope.component.provideAdapter(IdSelectorRenderer)
 
-  >>> from z3c.form import widget
+Of course, like all view components, the renderer supports the update/render
+pattern. We can now render the selector:
+
+  >>> from zope.publisher.browser import TestRequest
   >>> request = TestRequest()
-  >>> age = widget.Widget(request)
 
-  >>> age.name = 'age'
-  >>> age.label = u'Age'
-  >>> age.value = '39'
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (selector, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> renderer.render()
+  u'#message'
 
-  >>> import tempfile
-  >>> textWidgetTemplate = tempfile.mktemp('text.pt')
-  >>> open(textWidgetTemplate, 'w').write('''\
-  ... <input type="text" name="" value=""
-  ...        tal:attributes="id view/name;
-  ...                        name view/name;
-  ...                        value view/value;" />\
-  ... ''')
+Next we need a renderer for the subscription. Let's assume we can bind the
+subscription as follows: ``$(<selector>).bind("<event>", <script>)``
 
-  >>> from z3c.form.widget import WidgetTemplateFactory
-  >>> factory = WidgetTemplateFactory(
-  ...     textWidgetTemplate, widget=widget.Widget)
+  >>> class SubscriptionRenderer(object):
+  ...     zope.interface.implements(interfaces.IRenderer)
+  ...     zope.component.adapts(interfaces.IJSSubscription, IBrowserRequest)
+  ...
+  ...     def __init__(self, subscription, request):
+  ...         self.subscription = subscription
+  ...         self.request = request
+  ...
+  ...     def update(self):
+  ...         self.selectorRenderer = zope.component.getMultiAdapter(
+  ...             (self.subscription.selector, request), interfaces.IRenderer)
+  ...         self.selectorRenderer.update()
+  ...
+  ...     def render(self):
+  ...         return u'$("%s").bind("%s", function(){%s});' %(
+  ...             self.selectorRenderer.render(),
+  ...             self.subscription.event.name,
+  ...             self.subscription.handler(
+  ...                 self.subscription.event,
+  ...                 self.subscription.selector,
+  ...                 self.request) )
 
-  >>> from z3c.form.interfaces import INPUT_MODE
-  >>> age.mode is INPUT_MODE
-  True
+  >>> zope.component.provideAdapter(SubscriptionRenderer)
 
-  >>> import zope.component
-  >>> zope.component.provideAdapter(factory, name=INPUT_MODE)
+Rendering the subscription then return this:
 
-Now for the magic.  We can attach events to this widget by adapting
-it to ``IJSEventWidget``.  First we will create the events we want to
-add to it.
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (list(manager)[0], request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  $("#message").bind("click", function(){alert("Hello World!")});
 
-  >>> def ageClickHandler(widget, id):
-  ...     return 'alert("This Widget was Clicked!");'
-  >>> def ageDblClickHandler(widget, id):
-  ...     return 'alert("This Widget was Double Clicked!");'
-  >>> events = jsevent.JSEvents(click=ageClickHandler,
-  ...                           dblclick=ageDblClickHandler)
-  >>> age = zope.component.getMultiAdapter(
-  ...     (events, age), interfaces.IJSEventsWidget)
+And now to the grant finale. We create a renderer for the subscription manager.
 
-Now we can update and render this widget.
+  >>> class ManagerRenderer(object):
+  ...     zope.interface.implements(interfaces.IRenderer)
+  ...     zope.component.adapts(interfaces.IJSSubscriptions, IBrowserRequest)
+  ...
+  ...     def __init__(self, manager, request):
+  ...         self.manager = manager
+  ...
+  ...     def update(self):
+  ...         self.renderers = []
+  ...         for subscription in self.manager:
+  ...             renderer = zope.component.getMultiAdapter(
+  ...                 (subscription, request), interfaces.IRenderer)
+  ...             renderer.update()
+  ...             self.renderers.append(renderer)
+  ...
+  ...     def render(self):
+  ...         return '$(document).ready(function(){\n  %s\n}' %(
+  ...             '\n  '.join([r.render() for r in self.renderers]) )
 
-  >>> age.update()
-  >>> print age.render()
-  <input type="text" name="age" value="39" id="age" />
+  >>> zope.component.provideAdapter(ManagerRenderer)
 
-And then render the widget's events.
+Let's now render the entire manager.
 
-  >>> zope.component.provideAdapter(jsevent.JSEventsRenderer)
-  >>> request = TestRequest()
-  >>> renderer = zope.component.getMultiAdapter((events, request),
-  ...                                           interfaces.IJSEventsRenderer)
-  >>> age.id = 'age'
-  >>> print renderer.render(age, None)
-  $("#age").bind("click",
-      function(){alert("This Widget was Clicked!");});
-  $("#age").bind("dblclick",
-      function(){alert("This Widget was Double Clicked!");});
+  >>> renderer = zope.component.getMultiAdapter(
+  ...     (manager, request), interfaces.IRenderer)
+  >>> renderer.update()
+  >>> print renderer.render()
+  $(document).ready(function(){
+    $("#message").bind("click", function(){alert("Hello World!")});
+  }
 
 
-Rendering Widgets with Attached Events
---------------------------------------
+Javascript Viewlet
+------------------
 
-There is an easier way to render a bunch of widgets at a time to have
-events hooked up to them.  This involves adapting the widget manager
-to IJSEventWidgetManager.
+Putting in the Javascript by hand in every layout is a bit lame. Instead we
+can just register a viewlet for the JS viewlet manager that renders the
+subscriptions if a manager is found.
 
-Here we will create an interface for which we want to have a form.
+To use the viewlet we need a view that provides a subscription manager:
 
-  >>> import zope.interface
-  >>> import zope.schema
-  >>> class IPerson(zope.interface.Interface):
-  ...
-  ...     name = zope.schema.TextLine(
-  ...         title=u'Name',
-  ...         required=True)
-  ...
-  ...     gender = zope.schema.Choice(
-  ...         title=u'Gender',
-  ...         values=('male', 'female'),
-  ...         required=False)
-  ...
-  ...     age = zope.schema.Int(
-  ...         title=u'Age',
-  ...         description=u"The person's age.",
-  ...         min=0,
-  ...         default=20,
-  ...         required=False)
+  >>> class View(object):
+  ...     zope.interface.implements(interfaces.IHaveJSSubscriptions)
+  ...     jsSubscriptions = manager
 
-  >>> from z3c.form import field
-  >>> from z3c.form import form
-  >>> class PersonEditForm(form.AddForm):
-  ...
-  ...     fields = field.Fields(IPerson)
-  ...
-  ...     def ageClickEvent(self, form, id):
-  ...         return 'alert("The Age was Clicked!");'
-  ...
-  ...     def genderChangeEvent(self, form, id):
-  ...         return 'alert("The Gender was Changed!");'
-  ...
-  ...     def updateWidgets(self):
-  ...         super(PersonEditForm, self).updateWidgets()
-  ...         age = zope.component.getMultiAdapter(
-  ...              (jsevent.JSEvents(click=self.ageClickEvent),
-  ...               self.widgets['age']), interfaces.IJSEventsWidget)
-  ...         gender = zope.component.getMultiAdapter(
-  ...              (jsevent.JSEvents(change=self.genderChangeEvent),
-  ...               self.widgets['gender']), interfaces.IJSEventsWidget)
+We can now initialize, update, and finally render the viewlet:
 
-Now we can update this form and render the widget event handler.
+  >>> viewlet = jsevent.JSSubscriptionsViewlet(
+  ...     object(), request, View(), object())
+  >>> viewlet.update()
+  >>> print viewlet.render()
+  <script type="text/javascript">
+  $(document).ready(function(){
+    $("#message").bind("click", function(){alert("Hello World!")});
+  }
+  </script>
 
-  >>> request = TestRequest()
-  >>> edit = PersonEditForm(root, request)
-  >>> edit.update()
 
-  >>> zope.component.provideAdapter(jsevent.JSFormEventsRenderer)
-  >>> print interfaces.IJSFormEventsRenderer(edit).render()
-  $("#form-widgets-gender").bind("change",
-      function(){alert("The Gender was Changed!");});
-  $("#form-widgets-age").bind("click",
-      function(){alert("The Age was Clicked!");});
+Available Events
+----------------
 
+This package maps all of the available JavaScript events. Here is the complete
+list:
 
-Attaching Events to Form Fields
--------------------------------
+  >>> jsevent.CLICK
+  <JSEvent "click">
+  >>> jsevent.DBLCLICK
+  <JSEvent "dblclick">
+  >>> jsevent.CHANGE
+  <JSEvent "change">
+  >>> jsevent.LOAD
+  <JSEvent "load">
+  >>> jsevent.BLUR
+  <JSEvent "blur">
+  >>> jsevent.FOCUS
+  <JSEvent "focus">
+  >>> jsevent.KEYDOWN
+  <JSEvent "keydown">
+  >>> jsevent.KEYUP
+  <JSEvent "keyup">
+  >>> jsevent.MOUSEDOWN
+  <JSEvent "mousedown">
+  >>> jsevent.MOUSEMOVE
+  <JSEvent "mousemove">
+  >>> jsevent.MOUSEOUT
+  <JSEvent "mouseout">
+  >>> jsevent.MOUSEOVER
+  <JSEvent "mouseover">
+  >>> jsevent.MOUSEUP
+  <JSEvent "mouseup">
+  >>> jsevent.RESIZE
+  <JSEvent "resize">
+  >>> jsevent.SELECT
+  <JSEvent "select">
+  >>> jsevent.SUBMIT
+  <JSEvent "submit">
 
-The above method is pretty ugly, and we would really prefer something
-like what is done with buttons.  So here is how it would work.
+These are also provided as utilities so they can be looked up by name.
 
-  >>> class PersonEditForm(form.AddForm):
-  ...
-  ...     fields = field.Fields(IPerson)
-  ...
-  ...     @jsevent.handler(fields['age'])
-  ...     def ageClickEvent(self, id):
-  ...         return 'alert("The Age was Clicked!");'
-  ...
-  ...     @jsevent.handler(fields['gender'], event=jsevent.CHANGE)
-  ...     def genderChangeEvent(self, id):
-  ...         return 'alert("The Gender was Changed!");'
+  >>> import zope.component
+  >>> zope.component.provideUtility(jsevent.CLICK, name='click')
 
-Now we can update this form and render the widget event handler.
+Of course, we can now just look up the utility:
 
-  >>> request = TestRequest()
-  >>> edit = PersonEditForm(root, request)
-  >>> edit.update()
-  >>> zope.component.provideAdapter(jsevent.JSFormEventsRenderer)
-  >>> print interfaces.IJSFormEventsRenderer(edit).render()
-  $("#form-widgets-gender").bind("change",
-      function(){alert("The Gender was Changed!");});
-  $("#form-widgets-age").bind("click",
-      function(){alert("The Age was Clicked!");});
+  >>> zope.component.getUtility(interfaces.IJSEvent, 'click')
+  <JSEvent "click">

Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -26,28 +26,13 @@
 from z3c.formjs import interfaces
 
 
-class MessageValidationRenderer(object):
-    """An intermediate class that performs adapter look ups.
+ValidateTraverser = SingleAttributeTraverserPlugin('validate')
 
-    This way you don't have to do as many adapter look ups in your Form class.
-    """
-
-    def __init__(self, form, field):
-        self.form = form
-        self.field = field
-
-    def render(self):
-        jsrenderer = zope.component.queryMultiAdapter(
-            (self.form, self.field, self.form.request),
-            interfaces.IJSMessageValidationRenderer)
-        return jsrenderer.render()
-
-
 class BaseValidator(object):
-    zope.interface.implements(interfaces.IAJAXValidator,
-                              IPluggableTraverser)
+    zope.interface.implements(interfaces.IAJAXValidator, IPluggableTraverser)
 
-    ValidationRenderer = None
+    # See IAJAXValidator
+    ValidationScript = None
 
     def _validate(self):
         # XXX: Hard coded. Need a better approach.
@@ -58,7 +43,7 @@
         return self.widgets.extract()
 
     def publishTraverse(self, request, name):
-        # 1. Look at all the traverser plugins, whether they have an answer.
+        # Act like a pluggable traverser.
         for traverser in zope.component.subscribers((self, request),
                                                     ITraverserPlugin):
             try:
@@ -67,9 +52,21 @@
                 pass
 
 
+class MessageValidationScript(object):
+    zope.interface.implements(interfaces.IMessageValidationScript)
+
+    def __init__(self, form, widget):
+        self.form = form
+        self.widget = widget
+
+    def render(self):
+        renderer = zope.component.queryMultiAdapter(
+            (self, self.form.request), interfaces.IRenderer)
+        return renderer.render()
+
 class MessageValidator(BaseValidator):
     '''Validator that sends error messages for widget in questiodn.'''
-    ValidationRenderer = MessageValidationRenderer
+    ValidationScript = MessageValidationScript
 
     def validate(self):
         data, errors = self._validate()
@@ -77,4 +74,3 @@
             return errors[0].message
         return u'' # all OK
 
-ValidateTraverser = SingleAttributeTraverserPlugin('validate')

Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -10,7 +10,7 @@
 This validator returns an error message for a given widget.
 
     >>> from z3c.formjs import interfaces as jsinterfaces
-    >>> from z3c.formjs import jsvalidator, jsevent
+    >>> from z3c.formjs import jsvalidator, jsevent, jsaction
     >>> from z3c.form import form, field, interfaces
 
     >>> from z3c.form.testing import setupFormDefaults
@@ -30,12 +30,16 @@
     >>> class AddressEditForm(jsvalidator.MessageValidator, form.AddForm):
     ...     fields = field.Fields(IAddress)
     ...
-    ...     @jsevent.handler(interfaces.IField, event=jsevent.CHANGE)
-    ...     def fieldValidator(self, field):
-    ...         return self.ValidationRenderer(self, field).render()
+    ...     @jsaction.handler(interfaces.IField, event=jsevent.CHANGE)
+    ...     def fieldValidator(self, selector):
+    ...         return self.ValidationScript(self, selector.widget).render()
 
-    >>> from z3c.formjs.testing import TestRequest
+    >>> from z3c.form.testing import TestRequest
     >>> request = TestRequest()
+    >>> from jquery.layer import IJQueryJavaScriptBrowserLayer
+    >>> import zope.interface
+    >>> zope.interface.alsoProvides(request, IJQueryJavaScriptBrowserLayer)
+
     >>> edit = AddressEditForm(None, request)
     >>> edit.update()
 
@@ -44,9 +48,10 @@
     >>> import zope.component
     >>> from z3c.formjs import jqueryrenderer
     >>> zope.component.provideAdapter(
-    ...     jqueryrenderer.JQueryMessageValidationRenderer)
+    ...     jqueryrenderer.JQueryMessageValidationScriptRenderer)
 
-    >>> print edit.fieldValidator(edit, edit.fields['zip'])
+    >>> print edit.fieldValidator(
+    ...     None, jsaction.WidgetSelector(edit.widgets['zip']), request)
     $.get("http://127.0.0.1/validate" +
           "?widget-id=form-widgets-zip&form.widgets.zip=" +
           $("#form-widgets-zip").val(),

Deleted: z3c.formjs/trunk/src/z3c/formjs/jswidget.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jswidget.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/jswidget.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -1,13 +0,0 @@
-import zope.component
-import zope.interface
-from z3c.form.interfaces import IWidget
-import interfaces
-
- at zope.component.adapter(interfaces.IJSEvents, IWidget)
- at zope.interface.implementer(interfaces.IJSEventsWidget)
-def JSEventsWidget(events, widget):
-    """Set the events for the widget."""
-    widget.jsEvents = events
-    if not interfaces.IJSEventsWidget.providedBy(widget):
-        zope.interface.alsoProvides(widget, interfaces.IJSEventsWidget)
-    return widget

Added: z3c.formjs/trunk/src/z3c/formjs/jswidget.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jswidget.txt	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/jswidget.txt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,71 @@
+=============================
+Javascript Events for Widgets
+=============================
+
+Instead of using the very low-level API of ``jsevent`` to connect widgets to
+Javascript events, this package provides some high-level features that make
+the integration simpler to use.
+
+Widget Selector
+---------------
+
+The widget selector, in contrast to the id selector, accepts a widget and
+provides a selector API.
+
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+
+  >>> from z3c.form.browser import text
+  >>> msg = text.TextWidget(request)
+  >>> msg.id = 'form-msg'
+
+  >>> from z3c.formjs import jswidget
+  >>> selector = jswidget.WidgetSelector(msg)
+  >>> selector
+  <WidgetSelector "form-msg">
+
+Since the widget selector can determine the widget's id, it is also an id
+selector:
+
+  >>> from z3c.formjs import interfaces
+  >>> interfaces.IIdSelector.providedBy(selector)
+  True
+  >>> selector.id
+  'form-msg'
+
+This has the advantage that we do not need another renderer for this
+selector. Thus, within a form we can use the following pattern to subscribe a
+widget to an event:
+
+  >>> def showSelectedWidget(event, selector, request):
+  ...     return 'alert("%r");' %(selector.widget)
+
+  >>> import zope.interface
+  >>> from z3c.formjs import jsevent
+
+  >>> class Form(object):
+  ...     zope.interface.implements(interfaces.IHaveJSSubscriptions)
+  ...     jsSubscriptions = jsevent.JSSubscriptions()
+  ...
+  ...     def update(self):
+  ...         self.jsSubscriptions.subscribe(
+  ...             jsevent.CLICK, selector, showSelectedWidget)
+
+  >>> form = Form()
+  >>> form.update()
+
+After registering the renderers,
+
+  >>> from z3c.formjs import testing
+  >>> testing.setupRenderers()
+
+we can use the viewlet to check the subscription output:
+
+  >>> viewlet = jsevent.JSSubscriptionsViewlet(None, request, form, None)
+  >>> viewlet.update()
+  >>> print viewlet.render()
+  <script type="text/javascript">
+    $(document).ready(function(){
+      $("#form-msg").bind("click", function(){alert("<TextWidget None>");});
+    }
+  </script>


Property changes on: z3c.formjs/trunk/src/z3c/formjs/jswidget.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: z3c.formjs/trunk/src/z3c/formjs/testing.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/testing.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/testing.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -16,58 +16,89 @@
 $Id: $
 """
 __docformat__ = 'restructuredtext'
-import jquery.layer
 import os.path
-import z3c.form.interfaces
-import z3c.form.testing
+import zope.component
 import zope.interface
-import zope.component
-from z3c.form.interfaces import IWidget, IFormLayer
-from zope.publisher.browser import TestRequest
+from zope.app.pagetemplate import viewpagetemplatefile
 from zope.app.testing import setup
+from zope.publisher.interfaces.browser import IBrowserRequest
 
-from z3c.formjs import jsbutton, jswidget, jsevent
-from z3c.formjs import interfaces, browser, jqueryrenderer
+import z3c.formjs.tests
+from z3c.formjs import interfaces
 
-class TestRequest(TestRequest):
-    zope.interface.implements(jquery.layer.IJQueryJavaScriptBrowserLayer,
-                              IFormLayer)
+class IdSelectorRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(interfaces.IIdSelector, IBrowserRequest)
 
-def getPath(filename):
-    return os.path.join(os.path.dirname(browser.__file__), filename)
+    def __init__(self, selector, request):
+        self.selector = selector
 
+    def update(self):
+        pass
 
-def setUpEventUtilities():
-    ## Event Utilities
-    zope.component.provideUtility(jsevent.CLICK, name='click')
-    zope.component.provideUtility(jsevent.DBLCLICK, name='dblclick')
-    zope.component.provideUtility(jsevent.LOAD, name='load')
-    zope.component.provideUtility(jsevent.CHANGE, name='change')
-    zope.component.provideUtility(jsevent.BLUR, name='blur')
-    zope.component.provideUtility(jsevent.FOCUS, name='focus')
-    zope.component.provideUtility(jsevent.KEYDOWN, name='keydown')
-    zope.component.provideUtility(jsevent.KEYUP, name='keyup')
-    zope.component.provideUtility(jsevent.MOUSEDOWN, name='mousedown')
-    zope.component.provideUtility(jsevent.MOUSEMOVE, name='mousemove')
-    zope.component.provideUtility(jsevent.MOUSEOUT, name='mouseout')
-    zope.component.provideUtility(jsevent.MOUSEOVER, name='mouseover')
-    zope.component.provideUtility(jsevent.MOUSEUP, name='mouseup')
-    zope.component.provideUtility(jsevent.RESIZE, name='resize')
-    zope.component.provideUtility(jsevent.SELECT, name='select')
-    zope.component.provideUtility(jsevent.SUBMIT, name='submit')
+    def render(self):
+        return u'#' + self.selector.id
 
+class SubscriptionRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(interfaces.IJSSubscription, IBrowserRequest)
 
-def setUp(test):
-    test.globs = {'root': setup.placefulSetUp(True)}
-    z3c.form.testing.setupFormDefaults()
-    zope.component.provideAdapter(
-        jsbutton.JSButtonAction, provides=z3c.form.interfaces.IFieldWidget)
-    zope.component.provideAdapter(
-        jswidget.JSEventsWidget, provides=interfaces.IJSEventsWidget)
-    zope.component.provideAdapter(jqueryrenderer.JQueryEventRenderer)
+    def __init__(self, subscription, request):
+        self.subscription = subscription
+        self.request = request
 
-    setUpEventUtilities()
+    def update(self):
+        self.selectorRenderer = zope.component.getMultiAdapter(
+            (self.subscription.selector, self.request), interfaces.IRenderer)
+        self.selectorRenderer.update()
 
+    def render(self):
+        return u'$("%s").bind("%s", function(){%s});' %(
+            self.selectorRenderer.render(),
+            self.subscription.event.name,
+            self.subscription.handler(
+                self.subscription.event,
+                self.subscription.selector,
+                self.request) )
 
+class ManagerRenderer(object):
+    zope.interface.implements(interfaces.IRenderer)
+    zope.component.adapts(interfaces.IJSSubscriptions, IBrowserRequest)
+
+    def __init__(self, manager, request):
+        self.manager = manager
+        self.request = request
+
+    def update(self):
+        self.renderers = []
+        for subscription in self.manager:
+            renderer = zope.component.getMultiAdapter(
+                (subscription, self.request), interfaces.IRenderer)
+            renderer.update()
+            self.renderers.append(renderer)
+
+    def render(self):
+        return '$(document).ready(function(){\n  %s\n}' %(
+            '\n  '.join([r.render() for r in self.renderers]) )
+
+
+def setupRenderers():
+    zope.component.provideAdapter(IdSelectorRenderer)
+    zope.component.provideAdapter(SubscriptionRenderer)
+    zope.component.provideAdapter(ManagerRenderer)
+
+
+def addTemplate(form, filename):
+    path = os.path.join(os.path.dirname(z3c.formjs.tests.__file__), filename)
+    form.template = viewpagetemplatefile.BoundPageTemplate(
+        viewpagetemplatefile.ViewPageTemplateFile(path), form)
+
+
+def getPath(filename):
+    return os.path.join(os.path.dirname(browser.__file__), filename)
+
+def setUp(test):
+    setup.placelessSetUp(test)
+
 def tearDown(test):
-    setup.placefulTearDown()
+    setup.placelessTearDown(test)

Added: z3c.formjs/trunk/src/z3c/formjs/tests/__init__.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/tests/__init__.py	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/tests/__init__.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1 @@
+# Make a package


Property changes on: z3c.formjs/trunk/src/z3c/formjs/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formjs/trunk/src/z3c/formjs/tests/buttons_form.pt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/tests/buttons_form.pt	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/tests/buttons_form.pt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,10 @@
+<html>
+  <head>
+    <script tal:replace="structure provider:javascript" />
+  </head>
+  <body>
+    <div class="action" tal:repeat="action view/actions/values">
+      <input type="submit" tal:replace="structure action/render" />
+    </div>
+  </body>
+</html>


Property changes on: z3c.formjs/trunk/src/z3c/formjs/tests/buttons_form.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formjs/trunk/src/z3c/formjs/tests/simple_edit.pt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/tests/simple_edit.pt	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/tests/simple_edit.pt	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,27 @@
+<html>
+  <head>
+    <script tal:replace="structure provider:javascript" />
+  </head>
+  <body>
+    <i tal:condition="view/status" tal:content="view/status" />
+    <ul tal:condition="view/widgets/errors">
+      <li tal:repeat="error view/widgets/errors">
+        <tal:block replace="error/widget/label"
+        />: <tal:block replace="structure error/render" />
+      </li>
+    </ul>
+    <form action=".">
+      <div class="row" tal:repeat="widget view/widgets/values">
+        <b tal:condition="widget/error"
+           tal:content="structure widget/error/render"
+        /><label for=""
+               tal:attributes="for widget/id"
+               tal:content="widget/label" />
+        <input type="text" tal:replace="structure widget/render"
+      /></div>
+      <div class="action" tal:repeat="action view/actions/values">
+        <input type="submit" tal:replace="structure action/render"
+      /></div>
+    </form>
+  </body>
+</html>


Property changes on: z3c.formjs/trunk/src/z3c/formjs/tests/simple_edit.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formjs/trunk/src/z3c/formjs/tests/test_doc.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/tests/test_doc.py	                        (rev 0)
+++ z3c.formjs/trunk/src/z3c/formjs/tests/test_doc.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Javascript Form Framework Interfaces.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import unittest
+import zope.testing.doctest
+
+from z3c.form import testing
+
+def test_suite():
+    return unittest.TestSuite((
+        zope.testing.doctest.DocFileSuite(
+            '../jsevent.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
+                        zope.testing.doctest.ELLIPSIS),
+        zope.testing.doctest.DocFileSuite(
+            '../jsaction.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
+                        zope.testing.doctest.ELLIPSIS),
+        zope.testing.doctest.DocFileSuite(
+            '../jqueryrenderer.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
+                        zope.testing.doctest.ELLIPSIS),
+        zope.testing.doctest.DocFileSuite(
+            '../jsvalidator.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
+                        zope.testing.doctest.ELLIPSIS),
+        ))


Property changes on: z3c.formjs/trunk/src/z3c/formjs/tests/test_doc.py
___________________________________________________________________
Name: svn:keywords
   + Id

Deleted: z3c.formjs/trunk/src/z3c/formjs/tests.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/tests.py	2007-07-06 21:13:41 UTC (rev 77536)
+++ z3c.formjs/trunk/src/z3c/formjs/tests.py	2007-07-06 21:16:21 UTC (rev 77537)
@@ -1,23 +0,0 @@
-import unittest
-import zope.testing.doctest
-
-import testing
-
-def test_suite():
-    return unittest.TestSuite((
-        zope.testing.doctest.DocFileSuite(
-            'jsevent.txt',
-            setUp=testing.setUp, tearDown=testing.tearDown,
-            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
-                        zope.testing.doctest.ELLIPSIS),
-        zope.testing.doctest.DocFileSuite(
-            'jsvalidator.txt',
-            setUp=testing.setUp, tearDown=testing.tearDown,
-            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
-                        zope.testing.doctest.ELLIPSIS),
-        zope.testing.doctest.DocFileSuite(
-            'jsbutton.txt',
-            setUp=testing.setUp, tearDown=testing.tearDown,
-            optionflags=zope.testing.doctest.NORMALIZE_WHITESPACE |
-                        zope.testing.doctest.ELLIPSIS),
-        ))



More information about the Checkins mailing list