Rendering system

Overview

The goal of the rendering system is to adapt the presentation of content for the device requesting it. Here is an example of the same content rendered for three different type of devices :

Basic rendering for simple phones :

_images/person_basic.png

Standard rendering for Touch phone (Blackberry, Windows mobile, ...)

_images/person_standard.png

Advanced rendering (iPhone, Android, Fennec, ...)

_images/person_advanced.png

Widgets

Widgets are classes and snippets of HTML that are rendered for a target set of devices. The goal of a widget is to render a piece of content like a phone number link, or an address, in the most suitable way for the device that issued the request.

For example one widget is responsible for rendering the address of a person.

For basic devices, the address is rendered as simple text. For standard devices the address is rendered with a static image using the the Google static map API. For advanced devices, able to run Javascript, it uses the Google map Javascript API that allows users to zoom in or out, move and so on.

_images/mobi-widgets.png

The purpose of the widget system is simple. You ask for the address to be rendered by a widget and the system will choose the best widget for the device.

<div class="person">
  <div class="name">${person.name}</div>
  <div class="address" tal:content="structure widget:person.address" />
</div>

Technical overview

The rendering system is implemented in the mobi.rendering module. It’s based on the Chameleon templating markup language and Zope component architecture.

The rendering system has only one base entity : Widget.

There are three types of widgets :

  • Base widget : renders an arbitrary object
  • Page widget : renders a page with some other widgets inside
  • Field widget : renders a field or attribute (zope.schema) of an object

Widgets are special types of view adapters that adapt three components : a content, a parent widget, and a device type.

Writing a widget

Writing a widget consists of three steps:

  • Writing a Python class for the widget
  • Writing a Chameleon template
  • Registering it in the Zope component architecture

As an example we will implement two widgets for rendering an address for basic and standard devices.

An address is an object with the following zope.schema interface.

class IAddress(Interface):
    """ Interface to present an address
    """

    title       = TextLine(title=u"Title", description=u"The title of "\
    u"the address may be used for display")
    street      = TextLine(title=u"Street address and number")
    postal_code = TextLine(title=u"Postal Code")
    city        = TextLine(title=u"City")
    country     = TextLine(title=u"Country")

And its implementation :

class Address(object):
    implements(IAddress)

    title       = u""
    street      = u""
    postal_code = u""
    city        = u""
    country     = u""

The basic widget will only render an address as text. We create the widget view adapter and its html snippet template:

from mobi.interfaces.devices import IBasicDeviceType
from mobi.rendering.widget import Widget
from zope.component import adapts

class AddressWidgetBasic(Widget):
    adapts(IAddress, None, IBasicDeviceType)

The template will be named address_widget_basic.html (the underscore transformation of the widget class name) and be placed in a skin directory:

<div class="address">
  <div class="title">
    ${context.title}
  </div>
  <div class="street">${context.street}</div>
  <div>
    <span class="postal_code">${context.postal_code}</span>
    <span class="city">${context.city}</span>
  </div>
  <div>${context.country}</div>
</div>

Then we create the widget for the standard device type using the Google static image API:

import urllib
from mobi.interfaces.devices import IStandardDeviceType
from mobi.rendering.widget import Widget
from zope.component import adapts

class AddressWidgetStandard(Widget):
    adapts(IAddress, None, IStandardDeviceType)

    api_key = "xxxxxxxxxxxxxxxxxxxxx"
    size = '200x300'

    def get_gm_address(self):
        address = self.context
        return ",".join([address.street, address.postal_code,
            address.city, address.country])

    def get_quoted_gm_address(self):
        return urllib.quote(self.get_gm_address())

    def gm_image_url():
        return "http://maps.google.com/maps/api/staticmap?size=%s" \
          "&maptype=roadmap&amp;markers=size:mid|color:red|%s" \
          "&amp;mobile=true&amp;sensor=false&amp;key=%s" % \
            (self.size, self.get_quoted_gm_address(), self.api_key,)

and its html snippet address_widget_standard.html:

<div class="address">
  <div class="title">${context.title}</div>
  <div class="street">${context.street}</div>
  <div>
    <span class="postal_code">${context.postal_code}</span>
    <span class="city">${context.city}</span>
  </div>
  <div>${context.country}</div>
</div>
<div class="location-image">
  <img src="${view.gm_image_url()}" />
</div>

The only thing left is to register our widgets in the zope component architecture via a configure.zcml file at the root of your package. Here we presume that your widget’s code is in a widgets module.

<configure xmlns="http://namespaces.zope.org/zope">
  <!-- tell which packages we depend on -->
  <include package="mobi.interfaces" />
  <include package="mobi.rendering" />

  <!-- register the widgets -->
  <adapter factory=".widgets.AddressWidgetBasic"
    provides="mobi.interfaces.rendering.IWidget" />
  <adapter factory=".widgets.AddressWidgetStandard"
    provides="mobi.interfaces.rendering.IWidget" />

</configure>

You’re done. Now if you ask for a widget inside any Chameleon template it will render the widget according to the requested device, assuming the person.address instruction returns a IAddress implementation.

<div class="person">
  <div class="name">${person.name}</div>
  <div class="address" tal:content="structure widget:person.address" />
</div>

Table Of Contents

Previous topic

Device detection

This Page