Difference between revisions of "Routing and Template Toolkit"

From Dreamwidth Notes
Jump to: navigation, search
(References: update links to github)
(Replaced content with "Dreamwidth is currently in the process of moving away from BML and towards a system of routing that employs [http://template-toolkit.org/ Template Toolkit] for templat...")
Line 1: Line 1:
Dreamwidth is currently in the process of moving away from [[BML]] and towards a system of routing that employs [http://template-toolkit.org/ Template Toolkit] for templates.
Dreamwidth is currently in the process of moving away from [[BML]] and towards a system of routing that employs [http://template-toolkit.org/ Template Toolkit] for templates.
== The Basics ==
More information on how to use this system can be found in:
Making a page with the routing and Template Toolkit system requires a few things:
* [[Routing and Template Cookbook]]
# Define a URL string or pattern
# Create a handler function, attached to the URL string or pattern
# Make any needed templates
The URL(s) and handler function(s) go into the controller file, which will be somewhere in <tt>$LJHOME/cgi-bin/DW/Controller</tt>.
The template(s) will go into <tt>$LJHOME/views</tt>.
=== Defining a URL ===
First, you need to define a URL in a controller file in <tt>$LJHOME/cgi-bin/DW/Controller</tt>.  This will tell the system what handler function to use for a given URL pattern.
Here are some examples of registering a URL.  There are two main functions to do this with, <tt>register_string</tt> and <tt>register_regex</tt>.
<source lang="perl">
# This registers a static string, which is an application page.
DW::Routing->register_string( '/misc/whereami', \&whereami_handler,
    app => 1 );
# This registers a regular expression.  Later, we can get the
# contents inside the parentheticals.  This lets us make "clean" urls
# like /nav/read and /nav/create that are equivalent to
# /nav?cat=create
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler,
    app => 1 );
# This registers a user page, so, for example, it would be accessed at
# username.dreamwidth.org/data/edges
# This also explicitly sets our default output format to be JSON.
DW::Routing->register_string( "/data/edges", \&edges_handler,
    user => 1, format => 'json' );</source>
==== Defining an index page ====
<source lang="perl">
# This is all you have to do
DW::Routing->register_string( '/something/index', \&index_handler,
    app => 1 );
This registers all of /something, /something/ and /something/index
=== Creating a handler function ===
The handler function tells the system what to do with the browser request it is receiving.  It's also defined in the controller file you register the URL in, in <tt>$LJHOME/cgi-bin/DW/Controller</tt>.  The handler function will look up things, define variables we need for the page, and pass them onto the template.
I'm going to use the Nav controller as an example.  You can see the entire page at [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Controller/Nav.pm DW::Controller::Nav.pm].
We define our URL in the same file we place the handler function, so we'll start there:
<source lang="perl">package DW::Controller::Nav;
use strict;
use warnings;
use DW::Controller;
use DW::Routing;
use DW::Template;
use DW::Logic::MenuNav;
use JSON;
# Defines the URL for routing.  I could use register_string( '/nav' ... ) if I didn't want to capture arguments
# This is an application page, not a user styled page, and the default format is HTML (ie, /nav gives /nav.html)
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler, app => 1 );</source>
First, I want to get the options, subpatterns, and request:
<source lang="perl"># handles menu nav pages
sub nav_handler {
    my ( $opts, $cat ) = @_;
    my $r = DW::Request->get;</source>
Remember that subpattern I made in my URL regex?  I'll figure out if I'm using it or an argument here.
<source lang="perl">
    # Check for a category like nav/read, then for a ?cat=read argument, else no category
    $cat ||= $r->get_args->{cat} || '';</source>
Here, I'm doing error checking.  If I don't get back the array of menu hashes, I'm going to serve an error page that contains a translated error message.  Notice how I have to define the whole path to the error message, and can't just use ".error.invalidcat".
<source lang="perl">    # this function returns an array reference of menu hashes
    my $menu_nav = DW::Logic::MenuNav->get_menu_display( $cat )
        or return error_ml( '/nav.tt.error.invalidcat' );</source>
I'm getting some cruft that is needed by the real menu that I don't want to display on this page, so I'll go through all the menus and strip out HTML from the titles.  Ideally there would be a Template Toolkit filter that could do this, but I have not found one yet; we may have to make one.
<source lang="perl">    # this data doesn't need HTML in the titles, like in the real menu
    for my $menu ( @$menu_nav ) {
        for my $item ( @{ $menu->{items} } ) {
            $item->{text} = LJ::strip_html( $item->{text} );
This is a nifty part.  I can display the contents of this page either as HTML or as JSON (which is made for machine parsing), with very very little additional code.
The first one is how to return something as JSON. I just print out the object conversion to the request and return OK.
<source lang="perl">    # display according to the format
    my $format = $opts->format;
    if ( $format eq 'json' ) {
        # this prints out the menu navigation as JSON and returns
        $r->print( JSON::objToJson( $menu_nav ) );
        return $r->OK;</source>
The HTML format takes a bit more work.  We want to pass in more variables to the template and return the rendered template.
Here we go preparing the variables:
<source lang="perl">    } elsif ( $format eq 'html' ) {
        # these variables will get passed to the template
        my $vars = {
            menu_nav => $menu_nav,
            cat => $cat,
        $vars->{cat_title} = $menu_nav->[0]->{title} if $cat;</source>
And this is the call to render our template (more on making that next):
<source lang="perl">        # Now we tell it what template to render and pass in our variables
        return DW::Template->render_template( 'nav.tt', $vars );</source>
If my format isn't HTML or JSON, we throw a 404.
<source lang="perl">    } else {
        # return 404 for an unknown format
        return $r->NOT_FOUND;
And that's the handler!
=== Creating a template ===
The template will be placed somewhere logical in <tt>$LJHOME/views</tt>.  This one will be <tt>[http://hg.dwscoalition.org/dw-free/file/tip/views/nav.tt nav.tt]</tt>.  Currently, translation strings will go in <tt>nav.tt.text</tt>.
This section is a comment:
<source lang="html4strict">
[%# nav.tt
Page that shows the sub-level navigation links given the top-level navigation header
This section sets the title of the page.  If we have a category, it will be the category title.  Otherwise, it will use the ML translation of the title for the page.
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source>
This section creates the menu sections, applying code for each menu and each item in each menu.  If this isn't only displaying a category, it makes the title.  You can see the [http://template-toolkit.org/docs/manual/Filters.html#section_html html filter] being used in places like <tt>[% menu.title | html %]</tt> to ensure that all HTML is safe, and the [http://template-toolkit.org/docs/manual/Filters.html#section_url url filter] being used in <tt>[% menu_item.url | url %]</tt> to make sure that URL will be validly encoded.
<source lang="html4strict">[% FOREACH menu = menu_nav %]
    [% IF NOT cat %]<h2 class="[% menu.name %]">[% menu.title | html %]</h2>[% END %]
    [% FOREACH menu_item = menu.items %]
        <li><a href="[% menu_item.url | url %]">[% menu_item.text | html %]</a></li>
    [% END %]
[% END %]
That template is all that's required to render the HTML for the nav page!  You can see it [http://www.dreamwidth.org/nav live on Dreamwidth].
== Standard tricks ==
=== Inserting variables into translation strings ===
The example above sneaked in the way to use the translation system from within templates, by doing:
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source>
But what if you needed to insert something, such as the sitename or a username, into a string? Here's a fragment that does the latter, drawn from [http://hg.dwscoalition.org/dw-free/file/tip/views/misc/pubkey.tt views/misc/pubkey.tt]:
<source lang="html4strict">[% '.label' | ml(user = u.ljuser_display) %]</source>
=== Including a file with a temporary ML scope change ===
<source lang="html4strict">
[% dw.scoped_include('stats/site.tt'); %]
=== Specifying needed CSS/JS files ===
<source lang="html4strict">
[%- CALL dw.set_active_resource_group( 'jquery' ) -%]
[%- dw.need_res( 'stc/kitten.css' ) -%]
[%- dw.need_res( 'js/ponies.js' ) -%]
[%- dw.need_res( ( group => 'jquery' ), 'js/sparkle.js' ) -%]
Note: ignore the first and last if your page doesn't use jQuery.
For CSS, remember to used the [http://www.dreamwidth.org/dev/classes standardized classes] as much as possible.
=== Adding code to the head ===
Use this section code to add things to the head of the page.
<source lang="html4strict">
[% sections.head = BLOCK %]
<style type="text/css">
dt { font-size: bigger; font-weight: bold; margin-bottom: .2em; }
dd { margin-bottom: 1em; }
[% END  # sections.head %]
=== Dumping variables for debugging ===
If you're trying to debug problems with your template, you might find the [http://template-toolkit.org/docs/modules/Template/Plugin/Dumper.html Dumper] plugin useful to peek inside the contents of your variables.
<source lang="html4strict">
[% USE Dumper %]
[% Dumper.dump(variable) %]
== References ==
* [http://template-toolkit.org/docs/index.html Template Toolkit Documentation]
* [http://docs.dreamwidth.net/DW/Request.html DW::Request API Documentation]
* [http://docs.dreamwidth.net/DW/Routing.html DW::Routing API Documentation]
* [http://docs.dreamwidth.net/DW/Template.html DW::Template API Documentation]
* [http://docs.dreamwidth.net/DW/Template/Plugin.html Plugin Documentation]
* [http://docs.dreamwidth.net/DW/Template/Filters.html Filter Documentation]
=== Code Modules ===
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Controller.pm DW::Controller]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Request.pm DW::Request]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Routing.pm DW::Routing]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Template.pm DW::Template]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Template/Plugin.pm DW::Template::Plugin]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Template/Filters.pm DW::Template::Filters]
* [https://github.com/dreamwidth/dw-free/blob/develop/cgi-bin/DW/Template/Plugin/FormHTML.pm DW::Template::Plugin::FormHTML]
[[Category: Development]]
[[Category: Development]]

Latest revision as of 21:11, 24 February 2013

Dreamwidth is currently in the process of moving away from BML and towards a system of routing that employs Template Toolkit for templates.

More information on how to use this system can be found in: