Twisted Documentation: Controllers in Depth

# Controllers in Depth

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 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 provide the glue from a DOM node with a 'controller=' directive to an instance of a Controller class.
- handle is the method which is called on the Controller instance to handle a node.
- InputHandlers are Controllers which have (somewhat) convenient syntax for handling a node.
- Event handlers, when used with LivePage, are a brain-exploding way of handling JavaScript events in your pages with server-side Python code.

## 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