[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