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.
>>> from z3c.formjs import interfaces, 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.
So first we need to create a subscription manager in which to collect the subscriptions:
>>> manager = jsevent.JSSubscriptions()
Initially, we have no registered events:
>>> list(manager)
[]
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.
The events are available in all capital letters, for example:
>>> jsevent.CLICK
<JSEvent "click">
The DOM element is selected using a selector, in our case an id selector:
>>> selector = jsevent.IdSelector('message')
>>> selector
<IdSelector "message">
The handler of the event is a callable accepting the event, selector and the request:
>>> def showHelloWorldAlert(event, selector, request):
... return u'alert("Hello World!")'
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:
>>> list(manager)
[<JSSubscription event=<JSEvent "click">,
selector=<IdSelector "message">,
handler=<function showHelloWorldAlert at ...>>]
We can also get a specific subscription from the manager.
>>> manager['showHelloWorldAlert']
[<JSSubscription event=<JSEvent "click">,
selector=<IdSelector "message">,
handler=<function showHelloWorldAlert at ...>>]
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.
So let’s define some renderers for the various components. We have already prepared renderers for testing purposes in the testing support module. The first one is for the id selector
>>> import zope.component
>>> from z3c.formjs import testing
>>> zope.component.provideAdapter(testing.IdSelectorRenderer)
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()>>> renderer = zope.component.getMultiAdapter( ... (selector, request), interfaces.IRenderer) >>> renderer.update() >>> renderer.render() u'#message'
Next we need a renderer for the subscription. Let’s assume we can bind the subscription as follows: $(<selector>).bind("<event>", <script>)
>>> zope.component.provideAdapter(testing.SubscriptionRenderer)
Rendering the subscription then returns this:
>>> renderer = zope.component.getMultiAdapter(
... (list(manager)[0], request), interfaces.IRenderer)
>>> renderer.update()
>>> print renderer.render()
$("#message").bind("click", function(){alert("Hello World!")});
And now to the grant finale. We create a renderer for the subscription manager.
>>> zope.component.provideAdapter(testing.ManagerRenderer)
Let’s now render the entire manager.
>>> renderer = zope.component.getMultiAdapter(
... (manager, request), interfaces.IRenderer)
>>> renderer.update()
>>> print renderer.render()
$(document).ready(function(){
$("#message").bind("click", function(){alert("Hello World!")});
})
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`');});
Subscribe-decorators can also be chained, so that the same handler can be used for multiple selectors and events:
>>> class MyView(object):
...
... @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.CLICK)
... @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)
In this example we register this handler for both the click and double click event for the DOM element with the id “myid”.
>>> list(MyView.jsSubscriptions)
[<JSSubscription event=<JSEvent "dblclick">,
selector=<IdSelector "myid">,
handler=<function alertUser at ...>>,
<JSSubscription event=<JSEvent "click">,
selector=<IdSelector "myid">,
handler=<function alertUser at ...>>]
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.
To use the viewlet we need a view that provides a subscription manager:
>>> class View(object):
... zope.interface.implements(interfaces.IHaveJSSubscriptions)
... jsSubscriptions = manager
We can now initialize, update, and finally render the viewlet:
>>> 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>
The module provides several DOM element selectors. It is the responsibility of the corresponding rednerer to interpret the 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'
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.
This package maps all of the available JavaScript events. Here is the complete list:
>>> 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">
These are also provided as utilities so they can be looked up by name.
>>> import zope.component
>>> zope.component.provideUtility(jsevent.CLICK, name='click')
Of course, we can now just look up the utility:
>>> zope.component.getUtility(interfaces.IJSEvent, 'click')
<JSEvent "click">