[Checkins] SVN: z3c.formjs/trunk/ A lot of more work. See
CHANGES.txt.
Stephan Richter
srichter at cosmos.phy.tufts.edu
Wed Jul 18 23:07:45 EDT 2007
Log message for revision 78132:
A lot of more work. See CHANGES.txt.
Changed:
U z3c.formjs/trunk/CHANGES.txt
U z3c.formjs/trunk/buildout.cfg
U z3c.formjs/trunk/setup.py
U z3c.formjs/trunk/src/z3c/formjs/ajax.txt
U z3c.formjs/trunk/src/z3c/formjs/configure.zcml
U z3c.formjs/trunk/src/z3c/formjs/interfaces.py
U z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py
U z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
U z3c.formjs/trunk/src/z3c/formjs/jsevent.py
U z3c.formjs/trunk/src/z3c/formjs/jsevent.txt
U z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py
U z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt
U z3c.formjs/trunk/src/z3c/formjs/testing.py
-=-
Modified: z3c.formjs/trunk/CHANGES.txt
===================================================================
--- z3c.formjs/trunk/CHANGES.txt 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/CHANGES.txt 2007-07-19 03:07:45 UTC (rev 78132)
@@ -5,6 +5,15 @@
Version 0.2.0 (??/??/2007)
--------------------------
+- Feature: Registration of public AJAX server calls via a simple
+ decorator. The calls are made available via a special ``ajax`` view on the
+ original view.
+
+- Feature: Allow registering of JS subscriptions via a decorator within the
+ presentation component.
+
+- Feature: Added a new CSS selector.
+
- Feature: Implementation of AJAX-driven widget value validation.
- Restructure: Completely overhauled the entire API to be most easy to use and
Modified: z3c.formjs/trunk/buildout.cfg
===================================================================
--- z3c.formjs/trunk/buildout.cfg 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/buildout.cfg 2007-07-19 03:07:45 UTC (rev 78132)
@@ -1,5 +1,5 @@
[buildout]
-develop = . z3c.form
+develop = .
parts = test coverage
[test]
Modified: z3c.formjs/trunk/setup.py
===================================================================
--- z3c.formjs/trunk/setup.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/setup.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -32,7 +32,7 @@
setup (
name='z3c.formjs',
- version='0.1.0',
+ version='0.2.0',
author = "Paul Carduner and the Zope Community",
author_email = "zope3-dev at zope.org",
description = "Javascript integration into ``z3c.form``",
Modified: z3c.formjs/trunk/src/z3c/formjs/ajax.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/ajax.txt 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/ajax.txt 2007-07-19 03:07:45 UTC (rev 78132)
@@ -31,7 +31,7 @@
>>> from z3c.formjs import interfaces
>>> import zope.interface
>>> class PingForm(ajax.AJAXRequestHandler, form.Form):
- ...
+ ...
... @ajax.handler
... def pingBack(self):
... message = self.request.get('message', 'Nothing to ping back.')
@@ -90,7 +90,7 @@
>>> ping.update()
Now we will instantiate a pluggable traverser providing our form as
-the context.
+the context.
>>> from z3c.traverser.browser import PluggableBrowserTraverser
>>> traverser = PluggableBrowserTraverser(ping, request)
Modified: z3c.formjs/trunk/src/z3c/formjs/configure.zcml
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/configure.zcml 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/configure.zcml 2007-07-19 03:07:45 UTC (rev 78132)
@@ -3,40 +3,6 @@
xmlns="http://namespaces.zope.org/zope"
i18n_domain="z3c.formjs">
- <!-- Action Managers and Handlers -->
- <adapter
- factory=".jsaction.JSButtonAction"
- provides="z3c.form.interfaces.IButtonAction"
- />
-
- <subscriber
- handler=".jsaction.createSubscriptionsForWidget"
- />
-
- <!-- JavaScript Validation -->
- <view
- for=".interfaces.IAJAXValidator"
- type="zope.publisher.interfaces.http.IHTTPRequest"
- provides="zope.publisher.interfaces.http.IHTTPPublisher"
- factory="z3c.traverser.traverser.PluggableTraverser"
- permission="zope.Public"
- />
-
- <view
- for=".interfaces.IAJAXValidator"
- type="zope.publisher.interfaces.browser.IBrowserRequest"
- provides="zope.publisher.interfaces.browser.IBrowserPublisher"
- factory="z3c.traverser.browser.PluggableBrowserTraverser"
- permission="zope.Public"
- />
-
- <subscriber
- for=".interfaces.IAJAXValidator
- zope.publisher.interfaces.IPublisherRequest"
- provides="z3c.traverser.interfaces.ITraverserPlugin"
- factory=".jsvalidator.ValidateTraverser"
- />
-
<!-- AJAX Traversers -->
<adapter
trusted="True"
@@ -125,6 +91,16 @@
component="z3c.formjs.jsevent.SUBMIT"
/>
+ <!-- Action Managers and Handlers -->
+ <adapter
+ factory=".jsaction.JSButtonAction"
+ provides="z3c.form.interfaces.IButtonAction"
+ />
+
+ <subscriber
+ handler=".jsaction.createSubscriptionsForWidget"
+ />
+
<!-- Specific Javascript-backend implementations -->
<include file="jqueryrenderer.zcml" />
Modified: z3c.formjs/trunk/src/z3c/formjs/interfaces.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/interfaces.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/interfaces.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -45,7 +45,15 @@
description=u"Id of the DOM element to be selected.",
required=True)
+class ICSSSelector(ISelector):
+ """Select a DOM element by a CSS selector expression."""
+ expr = zope.schema.TextLine(
+ title=u"Expression",
+ description=u"CSS selector pointing to a DOM element.",
+ required=True)
+
+
class IJSSubscription(zope.interface.Interface):
"""A Subscription within Javascript."""
@@ -189,6 +197,36 @@
"""Return validation data."""
+# -----[ Widget Mode Switcher ]-----------------------------------------------
+
+class IWidgetModeSwitcher(zope.interface.Interface):
+ """A component that enables forms to switch between display and input
+ widgets."""
+
+ def getDisplayWidget():
+ """Return the rendered display widget.
+
+ The method expects to find a field called 'widget-name' in the request
+ containing the short name to the field/widget.
+ """
+
+ def getInputWidget():
+ """Return the rendered input widget.
+
+ The method expects to find a field called 'widget-name' in the request
+ containing the short name to the field/widget.
+ """
+
+ def saveWidgetValue():
+ """Save the new value of the widget and return any possible errors.
+
+ The method expects to find a field called 'widget-name' in the request
+ containing the short name to the field/widget. The request must also
+ contain all fields required for the widget to successfully extract the
+ value.
+ """
+
+
# -----[ AJAX ]--------------------------------------------------------
@@ -215,3 +253,4 @@
class IFormTraverser(zope.interface.Interface):
"""Marker interface for forms that can be traversed by the @@ajax
view."""
+
Modified: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -102,7 +102,8 @@
widget.__name__, widget.name, valueString)
# build a js expression that joins form url, validate path, and query
# string
- ajaxURL = '"'+form.request.getURL() + '/validate" + ' + queryString
+ ajaxURL = '"'+form.request.getURL() + '/@@ajax/validate" + ' \
+ + queryString
return ajaxURL
Modified: z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jqueryrenderer.txt 2007-07-19 03:07:45 UTC (rev 78132)
@@ -73,7 +73,13 @@
>>> subscriptions = jsevent.JSSubscriptions()
>>> subscriptions.subscribe(jsevent.CLICK, selector, handler)
+ <JSSubscription event=<JSEvent "click">,
+ selector=<IdSelector "form-id">,
+ handler=<function handler at ...>>
>>> subscriptions.subscribe(jsevent.DBLCLICK, selector, handler)
+ <JSSubscription event=<JSEvent "dblclick">,
+ selector=<IdSelector "form-id">,
+ handler=<function handler at ...>>
Let's now register the renderer:
@@ -124,7 +130,7 @@
... (script, request), interfaces.IRenderer)
>>> renderer.update()
>>> print renderer.render()
- $.get("http://127.0.0.1/validate" +
+ $.get("http://127.0.0.1/@@ajax/validate" +
"?widget-name=zip&form.zip=" + $("#form-zip").val(),
function(msg){applyErrorMessage("form-zip", msg)}
)
Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -16,6 +16,7 @@
$Id: $
"""
__docformat__ = "reStructuredText"
+import sys
import zope.component
import zope.interface
from zope.publisher.interfaces.browser import IBrowserRequest
@@ -66,6 +67,16 @@
return '<%s "%s">' %(self.__class__.__name__, self.id)
+class CSSSelector(object):
+ zope.interface.implements(interfaces.ICSSSelector)
+
+ def __init__(self, expr):
+ self.expr = expr
+
+ def __repr__(self):
+ return '<%s "%s">' %(self.__class__.__name__, self.expr)
+
+
class JSSubscription(object):
zope.interface.implements(interfaces.IJSSubscription)
@@ -86,13 +97,24 @@
self._subscriptions = []
def subscribe(self, event, selector, handler):
- self._subscriptions.append(
- JSSubscription(event, selector, handler) )
+ subscription = JSSubscription(event, selector, handler)
+ self._subscriptions.append(subscription)
+ return subscription
def __iter__(self):
return iter(self._subscriptions)
+def subscribe(selector, event=CLICK):
+ """A decorator for defining a javascript event handler."""
+ def createSubscription(func):
+ frame = sys._getframe(1)
+ f_locals = frame.f_locals
+ subs = f_locals.setdefault('jsSubscriptions', JSSubscriptions())
+ return subs.subscribe(event, selector, func)
+ return createSubscription
+
+
class JSSubscriptionsViewlet(viewlet.ViewletBase):
"""An viewlet for the JS viewlet manager rendering subscriptions."""
zope.component.adapts(
Modified: z3c.formjs/trunk/src/z3c/formjs/jsevent.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsevent.txt 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsevent.txt 2007-07-19 03:07:45 UTC (rev 78132)
@@ -51,6 +51,9 @@
We have finally all the pieces together to subscribe the event:
>>> manager.subscribe(jsevent.CLICK, selector, showHelloWorldAlert)
+ <JSSubscription event=<JSEvent "click">,
+ selector=<IdSelector "message">,
+ handler=<function showHelloWorldAlert at ...>>
So now we can see the subscription:
@@ -115,6 +118,40 @@
})
+The Subscription Decorator
+--------------------------
+
+When defining JS event subscriptions from within a presentation component,
+using the low-level subscription API is somewhat cumbersome. Thus, there
+exists a decorator called ``subscribe``, which can convert a component method
+as a subscription handler. Let's have a look:
+
+ >>> class MyView(object):
+ ...
+ ... @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.DBLCLICK)
+ ... def alertUser(event, selector, request):
+ ... return u"alert('`%s` event occured on DOM element `%s`');" %(
+ ... event.name, selector.id)
+
+As you can see, the function is never really meant to be a method, but a
+subscription handler; thus no ``self`` as first argument. The subscription is
+now available in the subscriptions manager of the view:
+
+ >>> list(MyView.jsSubscriptions)
+ [<JSSubscription event=<JSEvent "dblclick">,
+ selector=<IdSelector "myid">,
+ handler=<function alertUser at ...>>]
+
+Let's now render the subscription:
+
+ >>> renderer = zope.component.getMultiAdapter(
+ ... (list(MyView.jsSubscriptions)[0], request), interfaces.IRenderer)
+ >>> renderer.update()
+ >>> print renderer.render()
+ $("#myid").bind("dblclick",
+ function(){alert('`dblclick` event occured on DOM element `myid`');});
+
+
Javascript Viewlet
------------------
@@ -141,6 +178,64 @@
</script>
+Selectors
+---------
+
+The module provides several DOM element selectors. It is the responsibility of
+the corresponding rednerer to interpret the selector.
+
+Id Selector
+~~~~~~~~~~~
+
+The id selector selects a DOM element by id, as seen above. It is simply
+initialized using the the id:
+
+ >>> idselect = jsevent.IdSelector('myid')
+ >>> idselect
+ <IdSelector "myid">
+
+The id is also available as attribute:
+
+ >>> idselect.id
+ 'myid'
+
+We already saw before how it gets rendered:
+
+ >>> renderer = zope.component.getMultiAdapter(
+ ... (idselect, request), interfaces.IRenderer)
+ >>> renderer.update()
+ >>> renderer.render()
+ u'#myid'
+
+CSS Selector
+~~~~~~~~~~~~
+
+The CSS selector selects a DOM element using an arbitrary CSS selector
+expression. This selector is initialized using the expression:
+
+ >>> cssselect = jsevent.CSSSelector('div.myclass')
+ >>> cssselect
+ <CSSSelector "div.myclass">
+
+The CSS selector expression is also available as attribute:
+
+ >>> cssselect.expr
+ 'div.myclass'
+
+Let's now see an example on how the CSS selector can be rendered:
+
+ >>> zope.component.provideAdapter(testing.CSSSelectorRenderer)
+
+ >>> renderer = zope.component.getMultiAdapter(
+ ... (cssselect, request), interfaces.IRenderer)
+ >>> renderer.update()
+ >>> renderer.render()
+ u'div.myclass'
+
+Since most JS libraries support CSS selectors by default, the renderer simply
+converts the expression to unicode.
+
+
Available Events
----------------
Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -20,18 +20,13 @@
import zope.component
from zope.publisher.interfaces import NotFound
-from z3c.traverser.traverser import SingleAttributeTraverserPlugin
-from z3c.traverser.interfaces import IPluggableTraverser, ITraverserPlugin
from z3c.form.interfaces import IWidget, IField
-from z3c.formjs import interfaces
+from z3c.formjs import ajax, interfaces
-# Traverser Plugin to the ``validate()`` method of the ``IAJAXValidator``
-ValidateTraverser = SingleAttributeTraverserPlugin('validate')
+class BaseValidator(ajax.AJAXRequestHandler):
+ zope.interface.implements(interfaces.IAJAXValidator)
-class BaseValidator(object):
- zope.interface.implements(interfaces.IAJAXValidator, IPluggableTraverser)
-
# See ``interfaces.IAJAXValidator``
ValidationScript = None
@@ -41,18 +36,7 @@
self.updateWidgets()
return self.widgets.extract()
- def publishTraverse(self, request, name):
- # Act like a pluggable traverser.
- for traverser in zope.component.subscribers(
- (self, request), ITraverserPlugin):
- try:
- return traverser.publishTraverse(request, name)
- except NotFound:
- pass
- raise NotFound(self.context, name, request)
-
-
class MessageValidationScript(object):
zope.interface.implements(interfaces.IMessageValidationScript)
@@ -69,6 +53,7 @@
'''Validator that sends error messages for widget in questiodn.'''
ValidationScript = MessageValidationScript
+ @ajax.handler
def validate(self):
data, errors = self._validate()
if errors:
Modified: z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/jsvalidator.txt 2007-07-19 03:07:45 UTC (rev 78132)
@@ -69,22 +69,31 @@
... None, jsaction.WidgetSelector(edit.widgets['zip']), request)
$.get('/validate', function(data){ alert(data) })
-Validators also support pluggable traverser plugins. So once we register the
-``validate`` traverser for any validator, ...
+Validators use AJAX handlers to communicate with the server. Commonly the AJAX
+handler is looked up via the "ajax" view -- see ``ajax.txt`` for more
+details. In this case we just create a small helper function:
- >>> from z3c.formjs import interfaces
+ >>> from z3c.formjs import ajax
+
+ >>> def AJAXPlugin(view):
+ ... return ajax.AJAXRequestTraverserPlugin(view, view.request)
+
+
+ >>> from z3c.formjs import ajax, interfaces
+ >>> from zope.publisher.interfaces.browser import IBrowserRequest
+
>>> zope.component.provideSubscriptionAdapter(
- ... jsvalidator.ValidateTraverser,
- ... (interfaces.IAJAXValidator, TestRequest) )
+ ... ajax.AJAXRequestTraverserPlugin,
+ ... (interfaces.IFormTraverser, IBrowserRequest))
-we can traverse to the ``validate`` method and render it. Let's first render
-some valid input:
+we can traverse to the ``validate`` method from the "ajax" view and render
+it. Let's first render some valid input:
>>> request = TestRequest(form={'widget-name' : 'zip',
... 'form.widgets.zip' : u'29132'})
>>> edit = AddressEditForm(None, request)
>>> edit.update()
- >>> edit.publishTraverse(request, 'validate')()
+ >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()
u''
As you can see there is no error message. Let's now provide an invalid ZIP
@@ -94,14 +103,14 @@
... 'form.widgets.zip':'notazipcode'})
>>> edit = AddressEditForm(None, request)
>>> edit.update()
- >>> edit.publishTraverse(request, 'validate')()
+ >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()
u'The system could not process the given value.'
Of course, one cannot just traverse to any attribute in the form:
- >>> edit.publishTraverse(request, 'ValidationScript')
+ >>> AJAXPlugin(edit).publishTraverse(None, 'ValidationScript')()
Traceback (most recent call last):
...
- NotFound: Object: None, name: 'ValidationScript'
+ NotFound: Object: <AddressEditForm ...>, name: 'ValidationScript'
And that's it.
Modified: z3c.formjs/trunk/src/z3c/formjs/testing.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/testing.py 2007-07-19 02:41:05 UTC (rev 78131)
+++ z3c.formjs/trunk/src/z3c/formjs/testing.py 2007-07-19 03:07:45 UTC (rev 78132)
@@ -39,6 +39,19 @@
def render(self):
return u'#' + self.selector.id
+class CSSSelectorRenderer(object):
+ zope.interface.implements(interfaces.IRenderer)
+ zope.component.adapts(interfaces.ICSSSelector, IBrowserRequest)
+
+ def __init__(self, selector, request):
+ self.selector = selector
+
+ def update(self):
+ pass
+
+ def render(self):
+ return unicode(self.selector.expr)
+
class SubscriptionRenderer(object):
zope.interface.implements(interfaces.IRenderer)
zope.component.adapts(interfaces.IJSSubscription, IBrowserRequest)
More information about the Checkins
mailing list