[dancer-users] Advent 2015, medium-scale Dancer, part 3: Views

Warren Young wyml at etr-usa.com
Fri Nov 6 23:29:51 GMT 2015


The prior parts of this series are here:

  http://lists.preshweb.co.uk/pipermail/dancer-users/2015-October/005237.html
  http://lists.preshweb.co.uk/pipermail/dancer-users/2015-October/005238.html

The final two parts will be coming shortly, after I finish my editing pass.


======================


The first two parts of this article series broke the monolithic `lib/App.pm` file up into a series of Perl modules, each focused on some functional centroid of your application.

If we describe the default `lib/App.pm` file as monolithic, then we might get away with describing the view files as polylithic, because we do not need to break the view files up; they naturally map to web pages, so the app's URL scheme effectively keeps them separated by purpose.

Although we do not start this part of the series needing to break up an overly large file, I can pass along some tips along that might cause you to rethink the current structure of your app's `views/*` tree.


**The View Naming Scheme Should Match the URL Scheme**

Older web frameworks like PHP, ASP, and JSP define the web app's URL structure in the file system. The route scheme defined in the previous part of this article series would look like this in PHP:

    index.php
    mf/index.php
    mf/subfeature/index.php
    
The view code — HTML and inline JavaScript — is intermixed with the server-side code in the same file. Worse, single files like `mf/subfeature.index.asp` have to handle multiple HTTP verbs; in our example, `GET`, `POST`, `PUT`, and `DELETE`. If each verb returns a different web page, you end up with a huge top-level `if/elseif/else` blocks dividing the file up into a bunch of logically separate chunks. There are other bad consequences to this web framework design choice, such as that if you want your text editor to do syntax coloring correctly, it needs to be able to switch among PHP, JavaScript, and HTML syntax, all within the same file. The programmer ends up needing to do the same sort of context switching, too.

Simply put, this is a mess.

[Template::Toolkit](http://www.template-toolkit.org/) — Dancer's default template system — solves part of this problem by separating the view files from the server-side Perl code. Modern web app development frameworks like [Angular](https://angularjs.org/) solve the rest of it by encouraging you to move all of your JavaScript code into separate `*.js` files, rather than put it inline in the HTML file.

There is nothing about the way Dancer works that enforces or even encourages any particular `views/*` file naming scheme. Thus this tip: for your own sanity, I recommend that you name your view files after the corresponding route. The Dancer scheme corresponding to the above PHP scheme might look something like this:

    views/top.tt
    views/mf/top.tt
    views/mf/subfeature/get.tt
    views/mf/subfeature/post.tt
    views/mf/subfeature/put.tt
    views/mf/subfeature/delete.tt
    
This maps Dancer routes 1:1 to view files, using a naming scheme that is easy to remember, since it's nearly identical to the URL scheme. It's so simple a mapping that you could write a Perl one-liner to do the transform. By restricting yourself to a simple naming convention, you have removed a whole class of details that you have to keep in mind while working on your web app; you don't have to guess where the view file for a given route handler lives, you already *know* where it lives. You will quickly rediscover this simple mapping years from now when you have to work on the web app again.

This naming scheme is very much a suggestion, rather than a prescription. For one thing, you may find that the `DELETE /mf/subfeature` route needs no view, because it just deletes one database record and then redirects you to `GET /mf/subfeature` to show the change in the normal view. In that case, you would not need `views/mf/subfeature/delete.tt`.

The important thing is to *have* a well-thought-out naming scheme, not necessarily to follow the one I've laid out above.


**Factor Common UI Elements Out**

If you reuse a particular UI element across different parts of your web app, put its view file into a special view directory. I use `views/parts` for this. 

If you have a web page that is incorporated into your web app dynamically on the client side via Ajax, you should have a corresponding Dancer route for it, and probably a Perl module for it, too:

    views/parts/component.tt
    lib/App/Parts/Component.pm
     
Then in `lib/App/Parts/Component.pm`:

    sub _get {
        # ...work out how to build the component
        
        return Dancer::template '/parts/component' => {
            stuff => 'goes here'
        };
    }

    prefix '/parts' => sub {
        get 'component' => sub { _get(); };
    };

I chose the naming scheme for the function carefully. I want to be able to use short names here that correspond to the HTTP verb that the function handles without colliding with the short names exported by Dancer's DSL. The [standard Perl style rules](http://perldoc.perl.org/perlstyle.html) say that a leading underscore marks a function that isn't meant to be called from outside the module, which is exactly correct here; only Dancer should be calling that function.

You may prefer a different scheme, such as `Get()`, even though this is unconventional Perl style. As I've said in previous parts of this series, the important thing is to *have* a consistent style, not that you use the style I've chosen here.

One argument in favor of `Get()` over `_get()` is that you might need to be able to reuse this route handler in another of your Perl modules, bodily incorporating the HTML fragment within another web page on the server side, thus saving an Ajax round trip the first time that that web page is served. 

For example, `lib/App/MajorFeature.pm` might have something like this in it:

    sub Get {
        my ($ctx) = @_;		# see Part 2 for the definition of context()
        
        # Work out how to build the main part of the top-level /mf page
        ...Perl code here...

        # Assemble the pieces
        return Dancer::template '/mf/top' => {
            component   => App::Parts::Component::Get(),
            other_stuff => 'goes here',
        };
    }
    
    prefix '/mf' => sub {
        get '/' => sub { Get(context); };
        # other route handlers here  
    };

Then within `views/mf/top.tt`, you can reference `$component` to bodily include that component's HTML fragment into the larger page.

Now that we have sorted out the purely server-side HTML and Perl code files, we will look at the Dancer app design decisions that affect its presentation to the browser.


More information about the dancer-users mailing list