Controllers in Depth

  1. Main Concepts
  2. Controller factories
  3. Handle
  4. InputHandlers
  5. Event handlers
Note:

This HOWTO documents the Controller objects, part of the Woven framework. The Woven framework should not be used for new projects. The newer Nevow framework, available as part of the Quotient project, is a simpler framework with consistent semantics and better testing and is strongly recommended over Woven.

The Woven documentation below is maintained only for users with an existing Woven codebase.

Controller

Controller objects are a way to generalize and reuse input handling logic. In Twisted Web, form input is passed to a Resource instance in request.args. You can create controller classes to encapsulate generic request.args handling, and perform validation and Model updating tasks.

Main Concepts

Controller factories

Controller factories provide the glue between a controller= directive on a DOM node and a Controller instance. When a DOM node with a controller= directive is encountered, Woven looks for a corresponding wcfactory_ method on your Page instance. A Controller factory is required to return an object which implements the interface IController.

class MyController(controller.Controller):
        pass

    class MyPage(page.Page):
      def wcfactory_foo(self, request, node, model):
        return MyController(model)

Handle

Handle is the API your controller must implement to handle a node. It's return value may be a Deferred if you wish to pause the rendering of the View until some data is ready, or it may be None

class MyController(controller.Controller):
      def handle(self, request, node):
        name = request.args.get("name", [None])[0]
        print "HOORJ! YOUR NAME IS %s" % name

InputHandlers

InputHandlers are defined in woven.input. They were an early attempt to create a class which made it easy to create new input validators and input committers. It is usable in its current state, although the API is a bit baroque. Subclasses of input.InputHandler can override the following methods to decide what to do with data

initialize()
initialize this Controller. This is most useful for registering event handlers on the View with addEventHandler, discussed below.
getInput(self, request)
get input from the request and return it. Return None to indicate no data was available for this InputHandler to handle.
check(self, request, data)
Check the input returned from getInput and return:
  • None if no data was submitted (data was None), or
  • True if the data that was submitted was valid, or
  • False if the data that was submitted was not valid.
handleValid(self, request, data)
handle the valid submission of some data. By default this calls self.parent.aggregateValid.
aggregateValid(self, request, inputhandler, data)
Some input was validated by a child Controller. This is generally implemented on a controller which is placed on a <form> to gather input from controllers placed on <input> nodes.
handleInvalid(self, request, data)
handle the invalid submission of some data. By default this calls self.parent.aggregateInvalid.
aggregateInvalid(self, request, inputhandler, data)
Some input was declared invalid by a child Controller. This is generally implemented on a controller which is placed on a <form> to gather input from controllers placed on <input> nodes.
commit(self, request, node, data)
Enough valid input was gathered to allow us to change the Model.

InputHandlers have been parameterized enough so you may simply use a generic InputHandler rather than subclassing and overriding:

class MyPage(page.Page):
    def checkName(self, request, name):
      if name is None: return None
      # No fred allowed
      if name == 'fred':
        return False
      return True
    
    def commitName(self, request, name=""):
      ctx = getContext()
      ctx.execute("insert into people (name) values %s", name)

    def wcfactory_addPerson(self, request, node, model):
        return input.InputHandler(
            model, 
            name="name", # The name of the argument in the request to check
            check=self.checkName, 
            commit=self.commitName)

Event handlers

Note:

In order for Event Handlers to work, you must be using LivePage, and include the webConduitGlue View in your HTML template.

Event handlers give you the powerful ability to respond to in-browser JavaScript event handlers with server-side Python code. Event handlers are registered on the View instance; in some cases, it may make most sense for your View instances to implement their own event handlers. However, in order to support good separation of concerns and code reuse, you may want to consider implementing your event handlers on a Controller instance.

The easiest way to achieve this is to subclass input.Anything (XXX: this should just be controller.Controller) and override initialize (XXX: this should be setUp):

class MyEventHandler(input.Anything):
    def initialize(self):
        self.view.addEventHandler("onclick", self.onClick)
        self.view.addEventHandler("onmouseover", self.onMouseOver, "'HELLO'")
    
    def onClick(self, request, widget):
        print self, "CLICKED!!!"

    def onMouseOver(self, request, widget, argument):
        print self, "MOUSE OVER!!!", argument

Note that the first argument to addEventHandler is the JavaScript event name, and the second argument is the python function or method which will handle this event. You may also pass any additional arguments you desire. These arguments must be valid JavaScript, and will be evaluated in the browser context. The results of these JavaScript expressions will be passed to your Python event handler.

Note that when we passed an extra argument when adding an onmouseover event handler, we passed a string enclosed in two sets of quotes. This is because the result of evaluating "'HELLO'" as JavaScript in the browser is the string 'HELLO', which is then passed to the Python event handler. If we had simply passed "HELLO" to addEventHandler, Woven would have evaluated "HELLO" in the browser context, resulting in an error because the variable HELLO is not defined.

Any normal client-side JavaScript object may be accessed, such as document and window. Also, the JavaScript variable node is defined as the DOM node on which the event handler is operating. This is useful for examining the current value of an <input> node.

Here are some examples of useful Event handlers:

class Redirect(input.Anything):
    def initialize(self):
        self.view.addEventHandler(
            "onclick", 
            self.onClick, 
            "window.location = 'http://www.google.com'")

    def onClick(self, request, widget, arg):
        print "The window was redirected."

  class OnChanger(input.Anything):
    def initialize(self):
        self.view.addEventHandler(
            "onchange",
            self.changed,
            "node.value")
    
    def changed(self, request, widget, newValue):
        print "The input box changed to", newValue

Index

Version: 1.3.0