Table Of Contents

Previous topic

Widget Packages

Next topic

Building Dynamic Option Lists From Data Objects

Using JavaScript and CSS with Widgets

Note

This document describes how to use JavaScript and CSS with TurboGears widgets. If you haven’t done so already, you might want to read the simple form widget tutorial and the form validation tutorial first.

Introduction

When you have worked with HTML form elements, you will have noticed that their standard appearance as displayed by web browsers is rather uninspriring and often differs greatly from browser to browser. Defining your forms with widgets, though it takes out the hard work, doesn’t make your forms any prettier. Luckily, there is now good CSS support in all modern browsers, so this can be easily remedied.

Additionally, many standard HTML form elements aren’t really perfect from a usability point of view and many common user interface controls are missing. Again, support in modern browser for JavaScript has matured so far that it can be used to enhance the user interface with reasonable effort. The TurboGears standard widgets already include some AJAX-enhanced controls, for example AutoCompleteField and LinkRemoteFunction, which you can use in your project without too much knowledge about what’s going on behind the scenes.

Sometimes, though, you want to customize the look and behaviour of your widgets beyond the provided capabilities and you need to attach some custom CSS and JavaScript code or files to your page. The TurboGears widget system helps you by taking care of the following issues:

  • JavaScript and CSS code and links get included in your page automatically as needed along with your widgets.
  • The URL references to external JavaScript or CSS files are independant of how your application is mounted.
  • JavaScript code can be inserted into your page in the HTML header section or at the top or bottom of the body.
  • TurboGears provides the superb MochiKit JavaScript library as stand-alone widget, which helps you to deal with many aspects of client-side scripting and AJAX.

Usage Overview

For the impatient, here’s a quick overview of the steps needed to include a JavaScript or CSS resource with a widget:

  1. Define your widget.
  2. Register a name for the directory where you place your static resources.
  3. Define the JavaScript and CSS widgets (see Class Reference), giving the name you registered in step 2 and a relative URL to the constructor.
  4. Append JS/CSS widgets to the javascript/css attribute of the containing widget.
  5. Place your widget in the dict returned by your controller method.
  6. Make sure that your template inherits from sitetemplate.kid (extending the standard master.kid will take care of that).

Here’s a code example for a simple search form definition with a single text entry field. The entry field has an onfocus JavaScript handler function, which is defined in an external JavaScript file. The link to this file is generated by the JSLink widget that is attached to the search form widget:

from turbogears import widgets
from pkg_resources import resource_filename

# find 'static' directory in package 'myproject'
static_dir = resource_filename('myproject', 'static')
# register directory under name 'myproject'
widgets.register_static_directory('myproject', static_dir)

searchform = widgets.ListForm(
    fields=[widgets.TextField('query',
        attrs=dict(onfocus='activate_searchform(this);'))
    ],
    action='search',
    submit_text='Search'
)
searchform.javascript.append(
    widgets.JSLink('myproject', 'javascript/searchform.js')
)

And the matching controller method:

@expose(template='.templates.search')
def search(self, query=None):
    if not query:
        return dict(form=searchform)
    # ... gather and return results here

Now, when your search template is rendered, it will contain the following code in the HTML head section:

<SCRIPT SRC="/tg_widgets/myproject/javascript/searchform.js"
  TYPE="text/javascript"></SCRIPT>

Note

You may wonder about the strange URL in the src attribute of the script element. See the the description of widgetsJSLink in the Class Reference section below for an explanation. For now, rest assured that it is enough to place the file searchfom.js in the static/javascript directory below your project’s package directory and everything will be fine.

In the complete example project you can see that this code will display a simple form with a text entry field labeled “Query” and a submit button. If you click in the “Query” field, it will be filled with text “Search form activated!” by the activate_searchform JavaScript function.

Tip

You can download example project 1 here.

How Does it Work?

Now we have seen, how JS/CSS widgets are used, here’s a little background information on how they end up being rendered in your page.

  1. When you return a dictionary from your exposed controller method, the expose decorator looks in the dictionary for any widget objects and uses their retrieve_css and retrieve_javascript methods to find attached attached JS/CSS resources and places them in the special template variables, tg_css, tg_js_head, tg_js_bodytop, and tg_js_bodybottom according to the location attribute of the widget (for JSSource and JSLink widgets). This only works when you return JS/CSS widgets directly as values in the dictionary (i.e. not nested in lists or other container objects) or when they are attached to other widgets in the dictionary. In newer TurboGears versions (later than 1.0.5), this works also for all widgets in WidgetsList instances passed directly as values in the dictionary.
  2. When the template is rendered, it looks for the special template variables mentioned above, and loops over the list of widgets in them to render each in the appropriate spot. The necessary logic resides in sitetemplate.kid from the turbogears.view package, which the default master.kid template extends. When you edit your templates, be sure to extend master.kid or copy the logic from sitetemplate.kid to your own templates.

Class Reference

The widgets module defines several classes derived from widgets.Resource that encapsulates resources of of your web page:

class Resource

A resource for your widget, like a link to external JS/CSS or inline source to include at the template the widget is displayed.

Inherits directly from the widgets.Widgets class, so all the constructor arguments common to widgets apply, and is the common base class of all classes described below.

class JSLink(mod, [name, [**params]])

A link to an external JavaScript file which will be rendered as a SCRIPT element with an appropriate SRC attribute.

Inherits from widgets.Link, the common base class for JSLink and CSSLink.

Parameters:

mod

The name of the module used in putting together the URL in the SRC attribute of the SCRIPT element. This should normally correspond to the first argument given to the register_static_dir function.

The constructed URL will look like this:

/<approot>tg_widgets/<mod>/<name>

where approot is the application’s webroot as set in the config file with server.webpath. For name see below.

name
The name of the resource appended to the URL in the SRC attribute of the SCRIPT element. This should normally be the path to the resource file, relative to the directory registered with register_static_dir and referenced via the mod argument. Defaults to "widget", which is probably not what you want.
location
The location in the HTML code where the SCRIPT element should be inserted. Expects one of the values of the enum object widgets.js_location: head, bodytop, or bodybottom. Defaults to js_location.head.

Usage example:

import pkg_resources
from turbogears.widgets import JSLink
static_dir = pkg_resources.resource_filename('mypackage', 'static')
register_static_dir('mypackage', static_dir)
js = JSLink('mypackage', 'javascript/myscript.js')
class JSSource(src, [location, [**params]])

Inline JavaScript code which will be rendered enclosed in a SCRIPT element.

Inherits from widgets.Source, the common base class for JSSource and CSSSource.

Parameters:

src
A string with the actual JavaScript code.
location
See JSLink.

Usage example:

from turbogears.widgets import JSSource, js_location
js = JSSource('page_loaded();', location=js_location.bodybottom)
class CSSLink(mod, [name, [media, [**params]]])

A link to an external CSS file which will be rendered as a LINK element with an appropriate HREF attribute.

Inherits from widgets.Link, the common base class for JSLink and CSSLink.

Parameters:

mod
The module name used in constructing the URL in the HREF attribute of the LINK element. See JSLink for details.
name
The path name used in constructing the URL in the HREF attribute of the LINK element. See JSLink for details.
media
The value of the MEDIA attribute of the LINK element. Defaults to "all".

Usage example:

import pkg_resources
from turbogears.widgets import CSSLink
static_dir = pkg_resources.resource_filename('mypackage', 'static')
register_static_dir('mypackage', static_dir)
css = CSSLink('mypackage', 'css/styles.js')
class CSSSource(src, [media, **params]])

Inline CSS styles which will be rendered enclosed in a STYLE element.

Inherits from widgets.Source, the common base class for JSSource and CSSSource.

Parameters:

src
A string with the actual JavaScript code.
media
The value of the MEDIA attribute of the STYLE element. Defaults to "all".

Usage example:

from turbogears.widgets import CSSSource
css = CSSSource('body {voice-family: male;}', media='aural')
enum js_location

An enum specifying the location for a JSLink or JSSource resource in the HTML output.

Values:

head
The resource will be places in the HEAD section of the HTML output.
bodytop
The resource will be placed immediately after the opening tag of the BODY element of the HTML output.
bodybottom
The resource will be placed immediately before the closing tag of the BODY element of the HTML output.
function register_static_directory(modulename, directory)

Sets up a static directory for JavaScript and CSS files. You can refer to this static directory in templates as ${tg.widgets}/modulename.

Parameters:

modulename
The name under which the the static directory should be registered. Defines a URL path component under /tg_widgets, i.e. /tg_widgets/<modulename>. Use this name as the first argument for the JSLink and CSSLink widget constructors.
directory
The path of the physical directory on the server that the registered URL will map to.

Usage example:

from turbogears.widgets import register_static_directory
static_dir = register_static_directory('mypackage',
   '/path/to/dir/on/server')

Where to Put Resource Files

Without Widgets

To be done...

Using pkg_resources

See this mailing list post in the thread: MochiKit 1.4 + TurboGears

Examples

A Simple Form with CSS Styling and AJAX Preview

To be done...

  • Adding CSS and JavaScript
  • Event handling

Packaging a JavaScript Library as a Stand-alone Widget

To be done...

  • Example: Packaging up RUZEE.events

Controlling Caching of CSS/JavaScript Files

Generally, a web browser will cache your CSS/JavaScript files automatically, so even when you have modified a CSS/JavaScript file, the browser won’t download the modified file unless the user refreshes the page manually. One useful trick is to send a last modified time parameter with the static file’s url, which looks like this:

/static/css/layout?mtime=1171959724

The idea is that every time we modify the file, we will get a new mtime value, so the browser will treat it as a new resource and download it. The greatest benefit is that the browser won’t reload the static file every time if we did not modify the static file.

Sounds good? Let’s see how to implement this (you may also see a syntax-highlighted version).

"""Widgets for CSS and JavaScript resources with dynamically updating URL.

You can put this module anywhere in your project package.
If, for example, you have a "widgets" subpackage and name the module
"resources", you would import it in your controller like this::

    from widgets resources import MCSSLink, MJSLink

Here's an example of how to use the MCSSLink widget::

    layout = MCSSLink('myproj', 'css/common/layout.css')

Here's an example of how to use the MJSLink widget::

    mochikit = MJSLink('myproj', 'javascript/MochiKit.js')

"""

import os
import time

import pkg_resources
from turbogears import widgets, validators

# Register static directories
static_dirs = {}

# Replace "myproj" with your project's package name
# on the following two lines
static_dirs['myproj'] = pkg_resources.resource_filename("myproj", "static")
widgets.register_static_directory("myproj", static_dirs['myproj'])

def _get_timestamp():
    return int(time.time())

def _get_mtime(proj, path):
    abspath = os.path.join(static_dirs[proj], path)
    try:
        return os.path.getmtime(abspath)
    except OSError:
        return _get_timestamp()


class MCSSLink(widgets.Link):
    template = """
    <link rel="stylesheet"
        type="text/css"
        href="${link}?mtime=$mtime"
        media="${media}"
    />
    """
    params = ["media", "mtime"]
    params_doc = {
        'media': "Specify the 'media' attribute for the css link tag",
        'mtime': 'The last modified time of the CSS source'
    }
    media = "all"

    retrieve_css = widgets.set_with_self

    def update_params(self, params):
        super(MCSSLink, self).update_params(params)
        params['mtime'] = _get_mtime(self.mod, self.name)


class MJSLink(widgets.Link):
    template = """
    <script type="text/javascript" src="${link}?mtime=$mtime"></script>
    """

    location = widgets.js_location.head
    params = ["mtime"]
    params_doc = {
        'mtime': 'The last modified time of the JavaScript source'
    }

    def __init__(self, *args, **kw):
        location = kw.pop('location', None)
        super(MJSLink, self).__init__(*args, **kw)
        if location:
            if location not in js_location:
                raise ValueError(
                   "JSLink location should be in %s" % js_location)
            self.location = location

    def add_for_location(self, location):
        return location == self.location

    retrieve_javascript = widgets.set_with_self

    def update_params(self, params):
        super(MJSLink, self).update_params(params)
        params['mtime'] = _get_mtime(self.mod, self.name)

What’s Left?

  • i18n
  • optimization (jsmin, packaging files together)
  • referencing images in CSS (use relative paths)