[Checkins] SVN: z3c.formjs/trunk/src/z3c/formjs/ajax. Made AJAX feature thread-safe.

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Aug 23 15:07:38 EDT 2007


Log message for revision 79168:
  Made AJAX feature thread-safe.
  

Changed:
  U   z3c.formjs/trunk/src/z3c/formjs/ajax.py
  U   z3c.formjs/trunk/src/z3c/formjs/ajax.txt

-=-
Modified: z3c.formjs/trunk/src/z3c/formjs/ajax.py
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/ajax.py	2007-08-23 19:06:37 UTC (rev 79167)
+++ z3c.formjs/trunk/src/z3c/formjs/ajax.py	2007-08-23 19:07:38 UTC (rev 79168)
@@ -38,27 +38,28 @@
     def __repr__(self):
         return "<%s %r>" % (self.__class__.__name__, self.keys())
 
-class AJAXRequestHandler(object):
-    zope.interface.implements(interfaces.IAJAXRequestHandler,
-                              interfaces.IFormTraverser)
 
-    ajaxRequestHandlers = AJAXHandlers()
-
-
-class AJAXHandler(BrowserPage):
+class AJAXHandler(object):
     zope.interface.implements(interfaces.IAJAXHandler)
 
-    context = None
-
     def __init__(self, func):
         self.func = func
 
-    def __call__(self):
-        return self.func(self.context)
+    def __call__(self, view):
+        return self.func(view)
 
     def __repr__(self):
         return "<%s %r>" % (self.__class__.__name__, self.func.__name__)
 
+
+class AJAXRequestHandler(object):
+    """Mix-in class for forms to support AJAX calls."""
+    zope.interface.implements(interfaces.IAJAXRequestHandler,
+                              interfaces.IFormTraverser)
+
+    ajaxRequestHandlers = AJAXHandlers()
+
+
 def handler(func):
     """A decorator for defining an AJAX request handler."""
     handler = AJAXHandler(func)
@@ -69,6 +70,18 @@
     return handler
 
 
+class AJAXView(BrowserPage):
+    """A wrapper class around AJAX handler to allow it to be publishable."""
+
+    def __init__(self, handler, request, view):
+        self.context = self.handler = handler
+        self.request = request
+        self.__parent__ = self.view = view
+
+    def __call__(self):
+        return self.handler(self.view)
+
+
 class AJAXRequestTraverserPlugin(object):
     """Allow access to methods registered as an ajax request handler."""
 
@@ -83,5 +96,4 @@
         if handler is None:
             raise NotFound(self.context, name, request)
 
-        handler.context = self.context
-        return handler
+        return AJAXView(handler, self.request, self.context)

Modified: z3c.formjs/trunk/src/z3c/formjs/ajax.txt
===================================================================
--- z3c.formjs/trunk/src/z3c/formjs/ajax.txt	2007-08-23 19:06:37 UTC (rev 79167)
+++ z3c.formjs/trunk/src/z3c/formjs/ajax.txt	2007-08-23 19:07:38 UTC (rev 79168)
@@ -2,8 +2,8 @@
 AJAX Helpers
 ============
 
-The formjs package also sports utilities for working with AJAX
-queries.  These utilities are provided by the ``ajax`` module.
+The formjs package also sports utilities for working with AJAX queries.  These
+utilities are provided by the ``ajax`` module.
 
   >>> from z3c.formjs import ajax
 
@@ -11,14 +11,14 @@
 AJAX Request Handlers
 ---------------------
 
-AJAX requests are sent from a client-side JavaScript script to the web
-server.  The request may contain form data or any other request data
-and the server sends back a response based on the request.  The
-functionality for handling requests and returning responses is already
-handled by browser views.  But browser views can be a bit overkill for
-handling very simple requests and responses that don't necessarily
-involve rendering full page templates.  The ``ajax`` module allows you
-to quickly build in short ajax request handlers into your form.
+AJAX requests are sent from a client-side JavaScript script to the web server.
+The request may contain form data or any other request data and the server
+sends back a response based on the request.  The functionality for handling
+requests and returning responses is already handled by browser views.  But
+browser views can be a bit overkill for handling very simple requests and
+responses that don't necessarily involve rendering full page templates.  The
+``ajax`` module allows you to quickly build short ajax request handlers into
+your form.
 
 We will first do the necessary setup steps:
 
@@ -35,14 +35,13 @@
   ...     @ajax.handler
   ...     def pingBack(self):
   ...         message = self.request.get('message', 'Nothing to ping back.')
-  ...         return "from %r: %s" % (self.context, message)
+  ...         return "from %r: %s" % (self, message)
 
 The ``AJAXRequestHandler`` class provides the ``IAJAXRequestHandler``
-interface.  This means that the PingForm class will have an
-``ajaxRequestHandlers`` selection manager.  When you use the
-``@ajax.handler`` decorator, the decorated function gets registered
-in the selection manager and is converted to an ``AJAXHandler``
-instance.
+interface.  This means that the ``PingForm`` class will have an
+``ajaxRequestHandlers`` selection manager.  When you use the ``@ajax.handler``
+decorator, the decorated function gets registered in the AJAX handlers manager
+and is converted to an ``AJAXHandler`` instance.
 
   >>> from z3c.form.testing import TestRequest
   >>> request = TestRequest()
@@ -54,44 +53,27 @@
   >>> ping.ajaxRequestHandlers['pingBack']
   <AJAXHandler 'pingBack'>
 
-When the ``AJAXHandler`` instance is created, it does not know about
-the context.  Thus, the handler function defined in the form will not
-have access to other attributes in the form (including the request)
-unless the context is explicitly set.
+Since the form or anything else of interest is not around during the creation
+of the ``AJAXHandler`` instance, we have to wrap the handler once the other
+components are available. When executing the AJAX handler itself, you must
+pass in the form explicitely:
 
-  >>> ping.pingBack()
-  Traceback (most recent call last):
-  ...
-  AttributeError: 'NoneType' object has no attribute 'request'
+  >>> print ping.pingBack(ping)
+  from <PingForm ...>: Nothing to ping back.
 
-So we need to set the context for the handler before we call it.
+Before the handler is called we put an ``AJAXView`` class around it:
 
-  >>> ping.pingBack.context = ping
-  >>> ping.pingBack()
-  'from None: Nothing to ping back.'
+  >>> pingView = ajax.AJAXView(ping.pingBack, ping.request, ping)
 
-Since the function is now a publishable object, it provides the
-``IBrowserPublisher`` interface
+The ping AJAX view can simply be called:
 
-  >>> from zope.publisher.interfaces.browser import IBrowserPublisher
-  >>> IBrowserPublisher.providedBy(ping.pingBack)
-  True
+  >>> print pingView()
+  from <PingForm ...>: Nothing to ping back.
 
-All of this machinery is best handled by a pluggable traverser.  First
-we will reinstantiate the form and give it a less boring context and
-request than ``None``.
+To hook up the AJAX handler as a public URL, we use a pluggable traverser
+traversal plugin. Let's first instantiate a pluggable traverser providing our
+form as the context.
 
-  >>> class SomeContext(object):
-  ...     def __repr__(self):
-  ...         return '<%s>' % self.__class__.__name__
-
-  >>> request = TestRequest(form={'message': u'hello'})
-  >>> ping = PingForm(SomeContext(), request)
-  >>> ping.update()
-
-Now we will instantiate a pluggable traverser providing our form as
-the context.
-
   >>> from z3c.traverser.browser import PluggableBrowserTraverser
   >>> traverser = PluggableBrowserTraverser(ping, request)
   >>> traverser.publishTraverse(request, 'pingBack')()
@@ -99,10 +81,9 @@
   ...
   NotFound: Object: <PingForm object at ...>, name: 'pingBack'
 
-We have not yet registered an plugin for the pluggable traverser.
-We will register the ``AJAXRequestTraverserPlugin`` which will only
-traverse to objects stored in the ``ajaxRequestHandlers`` selection
-manager.
+We have not yet registered a plugin for the pluggable traverser.  We will
+register the ``AJAXRequestTraverserPlugin`` which will only traverse to
+objects stored in the ``ajaxRequestHandlers`` selection manager.
 
   >>> import zope.component
   >>> from z3c.traverser.interfaces import ITraverserPlugin
@@ -114,20 +95,28 @@
 
 Now we will try traversing to our handler again.
 
-  >>> traverser.publishTraverse(request, 'pingBack')()
-  u'from <SomeContext>: hello'
+  >>> print traverser.publishTraverse(request, 'pingBack')()
+  from <PingForm ...>: Nothing to ping back.
 
-NOTE: The pluggable traverser itself can be registered in a number of
-ways.  But the best way is to register it as a view for the from in
-question.  Since forms generally inherit from the
-z3c.form.form.BaseForm object, which itself inherits from BrowserPage,
-most forms will already have a publishTraverse method which will
-override any attempt to adapt to a diferent traverser.  But if you
-provide the pluggable traverser as a view on the form, then using the
-@@ symbols to force a view lookup rather than a publishTraverse call
-will bypass BrowserPage's publishTraverse method.  In ZCML, the
-pluggable traverser gets registered as a named adatper like so:
+When providing a message, then the pin back is less silly:
 
+  >>> request = TestRequest(form={'message': 'hello'})
+  >>> ping = PingForm(None, request)
+  >>> traverser = PluggableBrowserTraverser(ping, request)
+  >>> print traverser.publishTraverse(request, 'pingBack')()
+  from <PingForm ...>: hello
+
+
+NOTE: The pluggable traverser itself can be registered in a number of ways.
+But the best way is to register it as a view for the from in question.  Since
+forms generally inherit from the ``z3c.form.form.BaseForm`` object, which
+itself inherits from BrowserPage, most forms will already have a
+publishTraverse method which will override any attempt to adapt to a diferent
+traverser.  But if you provide the pluggable traverser as a view on the form,
+then using the @@ symbols to force a view lookup rather than a publishTraverse
+call will bypass BrowserPage's publishTraverse method.  In ZCML, the pluggable
+traverser gets registered as a named adatper like so:
+
   <adapter
       trusted="True"
       for=".interfaces.IFormTraverser



More information about the Checkins mailing list