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.
Contents
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:
For the impatient, here’s a quick overview of the steps needed to include a JavaScript or CSS resource with a widget:
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.
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.
The widgets module defines several classes derived from widgets.Resource that encapsulates resources of of your web page:
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.
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:
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.
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')
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:
Usage example:
from turbogears.widgets import JSSource, js_location
js = JSSource('page_loaded();', location=js_location.bodybottom)
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:
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')
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:
Usage example:
from turbogears.widgets import CSSSource
css = CSSSource('body {voice-family: male;}', media='aural')
An enum specifying the location for a JSLink or JSSource resource in the HTML output.
Values:
Sets up a static directory for JavaScript and CSS files. You can refer to this static directory in templates as ${tg.widgets}/modulename.
Parameters:
Usage example:
from turbogears.widgets import register_static_directory
static_dir = register_static_directory('mypackage',
'/path/to/dir/on/server')
To be done...
See this mailing list post in the thread: MochiKit 1.4 + TurboGears
To be done...
To be done...
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)