EasyWidgets Tutorial

Intro

EasyWidgets is a minimalistic reimplementation of TurboGears widgets. It does not, however, depend on TurboGears being installed in order to be useful. A widget provides one or more of the following features, all bundled together:

  • HTML markup (via a templating engine)
  • Validation (for forms)
  • Static resources to be injected into the rendered HTML page

This tutorial will demonstrate how to integrate widgets into a simple TurboGears application as well as how to create your own widgets.

Prerequisites

EasyWidgets is designed to work with Python 2.6 or higher and the excellent FormEncode form validation library. To install EasyWidgets, you can use pip or easy_install:

easy_install EasyWidgets
pip install EasyWidgets

You should also install a templating language such as kajiki or jinja2 (also available via pip or easy_install).

Your First Widget

The most basic widget does absolutely nothing and has no visible representation. This is boring, so let’s do a simple “hello, world” style widget:

>>> import ew
>>> w = ew.Widget(template=ew.Snippet('Hello, world!'))
>>> w.display()
literal(u'Hello, world!')

Two things to note here. First is that widgets can be configured to use either snippets (python strings containing template text) or files (filenames of templates on disk). You can also specify a template engine to use for your template. In this case, we omitted the template engine name and thus are using the core-ew engine, which provides simple expression substitution.

Note

Template Engines

You can also use one of the following engines, based on which template languages you have installed:

json
render a jsonified dictionary
core-ew
basic expression substitution using ${...}
genshi
Genshi templating engine
jinja2
Jinja2 templating engine
kajiki
Kajiki templating engine, with output type autodetect
kajiki-text
Kajiki text templating
kajiki-html4
Kajiki html4 templating
kajiki-html5
Kajiki html5 templating
kajiki-xml
Kajiki xml/xhtml templating

Many of the examples in this tutorial will use the Kajiki templating engine, though they should be trivially translatable to other engines.

The second thing to note in the example above is the use of the literal object to wrap the output of the template. This is the webhelpers.html.literal subclass of unicode, and provides an __html__ method which is used by many web frameworks to mark the strings as html-safe. (Since templates are used for rendering html, they are safe to embed without further escaping into your output page.)

Let’s add a tiny bit of interactivity to the template by using a variable:

>>> w = ew.Widget(template=ew.Snippet('Hello, ${name}!'))
>>> w.display(name=None)
literal(u'Hello, None!')
>>> w.display(name='Rick')
literal(u'Hello, Rick!')

Here we see that we can customize the output of the widget by passing in variables. But that “Hello, None” is ugly. Wouldn’t it be better to use a default value? Let’s try that, in the process looking at an alternative method for defining templates that you’ll probably use a good bit more than ew.Widget:

>>> class Hello(ew.Widget):
...     template=ew.Snippet('Hello, $name')
...     defaults=dict(name='World')
...
>>> Hello().display()
literal(u'Hello, World')
>>> Hello(name='Rick').display()
literal(u'Hello, Rick')
>>> Hello(name='Rick').display(name='Paul')
literal(u'Hello, Paul')

Here, we see a couple of things. First, we see the declarative widget definition. We can declare a ew.Widget subclass that provides additional functionality. Using the declarative method, we are also able to provide defaults to the widget via the defaults class variable.

You should also note that the defaults can be overridden in either the constructor or the display method, with display being the highest priority, followed by the constructor, followed by the defaults.

Form Generation and Validation

Although reusable html generation is useful in its own right, most templating languages already provide constructs to support this. Where EasyWidgets begins to be really useful is when you link form generation and validation together. To that end, EasyWidgets provides several base form classes that you can use to build and validate your forms. Here is a sample form:

import ew.kajiki_ew as kew
class MyForm(kew.SimpleForm):
    fields = [
        kew.TextField(name='name', label='Your Name'),
        kew.Checkbox(name='opt_in', label='Receive awesome offers?') ]

f = MyForm()
print f.display()

If we run this, our output will be similar to the following:

<form method="POST">
  <label for="name">Your Name</label><br>
  <input name="name" type="text"><br>
  <input type="checkbox" name="opt_in"><label for="opt_in">Receive awesome offers?</label>
  <br>
  <input value="Submit" type="submit" class="submit">
</form>

Again, nice that EasyWidgets provides HTML generation, but what about validation?

>>> f.validate(dict(name='Some Name', opt_in='on'), None)
{'opt_in': True, 'name': u'Some Name'}
>>> f.validate(dict(name='Some Name'), None)
{'opt_in': False, 'name': u'Some Name'}

Note in particular how the ‘on’ value, as might be submitted by an HTML form, is converted to the Python True value. Omitting it, rather than causing the field to be omitted from the output, substitutes a False Value. Form validation also works in reverse:

print f.display(value=dict(name='Other Name', opt_in=True))
<form method="POST">
  <label for="name">Your Name</label><br>
  <input name="name" value="Other Name" type="text"><br>
  <input type="checkbox" CHECKED name="opt_in"><label for="opt_in">Receive awesome offers?</label>
  <br>
  <input value="Submit" type="submit" class="submit">
</form>

Note in the above output that the values for ‘name’ and ‘opt_in’ have been substituted into the form appropriately. This is especially useful when there are validation errors. Let’s update our form just a bit:

>>> from formencode import validators as fev
>>> class MyForm2(kew.SimpleForm):
...     fields = [
...         kew.TextField(
...             name='name',
...             label='Your Name',
...             validator=fev.UnicodeString(min=3)),
...         kew.Checkbox(
...             name='opt_in',
...             label='Receive awesome offers?') ]
...
...
>>> f = MyForm2()

Here, we have added an explicit validator to our name field, requiring that the name be at least 3 characters long. When trying to validate bad data, we get:

>>> f.validate(dict(name='a'), None)
Traceback (most recent call last):
...
Invalid: name: Enter a value 3 characters long or more

That’s nice, but best practices for the web say you should present your user’s erroneous input to them and allow them to correct it. Which is exactly what happens if we display a form after receiving a validation error:

print f.display()
<form method="POST">
  <label for="name">Your Name</label><br>
  <span class="fielderror">Enter a value 3 characters long or more</span><br>
  <input name="name" value="a" type="text"><br>
  <input type="checkbox" name="opt_in"><label for="opt_in">Receive awesome offers?</label>
  <br>
  <input value="Submit" type="submit" class="submit">
</form>

Note in particular that the error message from our validator is nicely arranged next to the field with the erroneous value, and that the data the user entered is redisplayed to allow the user to correct the form.

Including Static Resources

In addition to encapsulating HTML generation and validation, EasyWidgets also provides a convenient method of including static resources such as CSS and Javascript in your pages automatically when using widgets. In order to use this feature effectively, you will need to make a couple of changes to the main templates you use (not the widget templates) to generate your site HTML.

For instance, if you are using Kajiki, you may have a master.html template from which all your page templates inherit that looks something like this:

<html>
    <head>
        <py:block name="head"/>
    </head>
    <body>
        <py:block name="titlebar"/>
        <py:block name="body"/>
    </body>
</html>

(Of course, your actual template probably has a shared header and footer, styling, etc. But for simplicity we’ll assume the tiny fragment above.)

In order to allow EasyWidgets to inject static resources into your page, you’ll need to place a couple of constructs in the template. Once we’ve “widgetized” the template, it looks something like this:

<html>
    <head>
        <py:block name="head"/>
        <?python g.resource_manager.register_widgets(c) ?>
        <py:for each="blob in g.resource_manager.emit('head_css')">$blob</py:for>
        <py:for each="blob in g.resource_manager.emit('head_js')">$blob</py:for>
    </head>
    <body>
        <py:for each="blob in g.resource_manager.emit('body_top_js')">$blob</py:for>
        <py:block name="titlebar"/>
        <py:block name="body"/>
        <py:for each="blob in g.resource_manager.emit('body_js')">$blob</py:for>
        <py:for each="blob in g.resource_manager.emit('body_js_tail')">$blob</py:for>
    </body>
</html>

Note

The ResourceManager

In the example above, we use a Python variable g which has an attribute resource_manager. This is inspired by the Pylons web framework, which makes a “application globals” object available to all templates. The actual resource manager provided by EasyWidgets is available as ew.widget_context.resource_manager.

Note

Widget Registration

In the example above, we use a Python variable c. This is inspired by the Pylons web framework, which makes a “context” object available to all templates. In this example, it is assumed that all the widgets used in the page will be attached to the c object as attributes. The register_widgets call is responsible for scanning the widgets attached to the c object and discovering the static resources they require. Note that the register_widgets call must precede any resource_manager.emit calls to ensure that all the resources required have been collected by the resource manager.

Now that we’ve widgetized our template, let’s see what happens when we render it:

<html>
    <head>
        <!-- ew:head_css -->
        <!-- /ew:head_css -->
        <!-- ew:head_js -->
        <!-- /ew:head_js -->
    <body>
        <!-- ew:body_top_js -->
        <!-- /ew:body_top_js -->
        <!-- ew:body_js -->
        <!-- /ew:body_js -->
        <!-- ew:body_js_tail -->
        <!-- /ew:body_js_tail -->

Here, we can see that EasyWidgets inserted several comments. Let’s try actually using a widget now, assuming our template is loaded as Template:

import ew.kajiki_ew as kew
class TestWidget(ew.Widget):
    template=ew.Snippet('Hello, world')

    def resources(self):
        yield kew.JSLink('js/script.js')
        yield kew.JSScript('// no args')
        yield kew.JSScript('// head_js', location='head_js')
        yield kew.JSScript('// body_js (default)', location='body_js')
        yield kew.JSScript('// body_top_js', location='body_top_js')
        yield kew.JSScript('// body_js_tail', location='body_js_tail')
        yield kew.CSSLink('css/styles.css')
        yield kew.CSSScript('/* Here would be some inline styles */')

c.widget = TestWidget()
g.resource_manager = ew.ResourceManager(compress=False)
print Template(dict(c=c,g=g)).render()

Our output will be something like the following:

<!DOCTYPE html><html>
        <head>
            <!-- ew:head_css -->
            <link href="/_ew_resources/css/styles.css" type="text/css" rel="stylesheet"><style>/* Here would be some inline styles */</style>
            <!-- /ew:head_css -->
            <!-- ew:head_js -->
            <script type="text/javascript">// head_js</script>
            <!-- /ew:head_js -->
        <body>
            <!-- ew:body_top_js -->
            <script type="text/javascript">// body_top_js</script>
            <!-- /ew:body_top_js -->
            <!-- ew:body_js -->
            <script src="/_ew_resources/js/script.js" type="text/javascript"></script><script type="text/javascript">// body_js (default)</script>
            <!-- /ew:body_js -->
            <!-- ew:body_js_tail -->
            <script type="text/javascript">// no args</script><script type="text/javascript">// body_js_tail</script>
            <!-- /ew:body_js_tail -->

Now that we finally have some actual generated HTML, there are several things to discuss.

  • Our widget specifies the resources it requires via the resources() generator method. This
  • Wherever we have a JSLink or a CSSLink resource referenced, EasyWidgets has prefixed it with an “/_ew_resources/” path. This is a “hook” that allows the EasyWidgets resource middleware (covered below) to serve up the resource. We will see how to map such URLs to actual files below.
  • The various “locations” specified in our widget’s resources() generator correspond to various locations in the resource_manager.emit() calls.

Resource Middleware

EasyWidgets provides a helpful hook into the WSGI stack for serving up static resources required by widgets: the ew.middleware.WidgetMiddleware class. In order to really investigate it, we’ll need to build a little WSGI application. The bottle web framework is helpful for such examples. Here is our example bottle application, test_app.py:

from bottle import Bottle, run

import ew
import ew.kajiki_ew as kew

import kajiki

# Set up our template
Template=kajiki.XMLTemplate(filename='data/master.html')

# Set up our widget
class TestWidget(ew.Widget):                                                                                                                 
    template=ew.Snippet('Hello, world')                                                                                                      
                                                                                                                                               
    def resources(self):               
        yield kew.JSLink('js/script.js')                                                                                                        
        yield kew.JSLink('js/script2.js')                                                                                                        
        yield kew.JSScript('// no args')                                                                                                     
        yield kew.JSScript('// head_js', location='head_js')                                                                                 
        yield kew.JSScript('// body_js (default)', location='body_js')                                                                       
        yield kew.JSScript('// body_top_js', location='body_top_js')                                                                       
        yield kew.JSScript('// body_js_tail', location='body_js_tail')                                                                       
        yield kew.CSSLink('css/style.css')                                                                                                      
        yield kew.CSSScript('/* Here would be some inline styles */')

# Set up the bottle application
myapp = Bottle()

@myapp.route('/')
def hello():
    # Fake the pylons global objects
    class g:
        resource_manager = ew.widget_context.resource_manager
    class c: pass
    c.widget = TestWidget()
    return Template(dict(c=c, g=g)).render()

myapp.catchall = False

# Wrap it in middleware
app = ew.WidgetMiddleware(myapp, register_resources=False)

# Register our static resources
ew.widget_context.resource_manager.register_directory(
    'js', 'data/js')
ew.widget_context.resource_manager.register_directory(
    'css', 'data/css')

if __name__ == '__main__':
    run(app=app, host='localhost', port=8080)

Note in particular the calls to ew.widget_context.resource_manager.register_directory. This is where we let the resource manager know that resources living under the URL “/_ew_resources/js” can be found at “data/js” and resources living under the URL “/_ew_resources/css” can be found at “data/css”. If we place resources at those locations, we will find that indeed our little test application can find and serve them up.

Encapsulation of widgets is nice, but if you’ve looked at optimizing client-side performance of the web, you probably have noticed hints such as “concatenate your css and javascript” and “minify your css and javascript.” Luckily, EasyWidgets has support for both. For minification, you simply install JSMin and CSSMin, turn on compression, and let EasyWidgets know it can use these libraries. This will change our middleware declaration to the following:

1
2
3
4
5
6
7
app = ew.WidgetMiddleware(
    myapp,
    register_resources=False,
    compress=True,
    use_jsmin=True,
    use_cssmin=True
    )

Now, if we view the page we will see the following cryptic links:

What’s happening here is that EasyWidgets has rewritten the URLs so that they now refer to its concatenation and minification servers, located under _slim/css and _slim/js. These servers will concatenate (and minify, if using jsmin/cssmin) the resources passed to them. If we investigate the output of these two urls, we will see that the CSS and javascript served up are indeed minified and concatenated.

Automatic Resource Registration

You may have noticed in our sample application that we told the WidgetMiddleware not to register resources, and then went on to manually register our own resources. EasyWidgets provides a way for your application (or widget libraries) to specify locations for static resources using setuptools-style entry points. The section searched for is [easy_widgets.resources], and it can specify one or more entry points that point to functions responsible for letting the resource manager know about static resources. For instance, you might have an entry points section in your setup.py similar to the following:

setup(
...
    entry_points='''
    [easy_widgets.resources]
    res=my.package.name:register_ew_resources
    '''
 ... )

Then in the Python module my.package.name, you would define a function `register_ew_resources:

def register_ew_resources(resource_manager):
    manager.register_directory(...)
    manager.register_directory(...)
    manager.register_directory(...)

The expectation is that this mechanism will be used in the creation of widget libraries to make their integration into your application easier.