All the Ways to Skin the Cat: How Reaction renders your Page

When you build a web application with Catalyst, your controller will retrieve data from your model (or operate on it), prepare it, and put it in the stash so the view can access it. Your layout will be organized by templates that load more or less reusable subtemplates.

Reaction on the other hand comes shipped with its own UI system. In this post I will try to describe its components and how they interact.

Catalyst and Reaction

Reaction is kind of a meta framework on top of Catalyst. Its Root controller (a base class used in place of the Root controller generated by Catalyst) will set up a UI Window in the stash. The controllers push ViewPorts onto the Window’s FocusStack.

ViewPorts

Now, the concept of ViewPorts is new to those who use Catalyst in the traditional way. What we usually do is stash away the values we got from the model in a way our templates expect them to. Then the view will pickup a template and parse it, and that template can include other templates, and so forth.

In Reaction, you push ViewPorts onto a stack. A ViewPort can be thought of as the logical representation of a region on your output. The typical first ViewPort is the SiteLayout, building the pages surroundings, setting up meta informations, static CSS files, the title and so forth. An example base action setting up the SiteLayout can be implemented like this:

use aliased 'Reaction::UI::ViewPort::SiteLayout';
...

sub base: Chained PathPart('') CaptureArgs(0) {
    my ($self, $ctx) = @_;
    $self->push_viewport(SiteLayout,
        title           => 'My Site Title',
        static_base_uri => $ctx->uri_for('/static')->as_string,
    );
}

A CRUD controller for example would then add a ListView ViewPort that provides paging and a Grid containing the Interface Model Collection it should render.

I’ll give you a quick example of that too:

use aliased 'Reaction::UI::ViewPort::ListView';
...

sub list: Chained('/base') Args(0) {
    my ($self, $ctx) = @_;
    $self->push_viewport(ListView,
        collection => $self->model('IM::Item'),
    );
}

Skins

When the ViewPorts are all prepared, the Window will be flushed and the application’s Skin comes into play. This is typically configured in the config file below the View section like this (I am assuming here that your view is named “Site” and that you’re using Config::General):

<View Site>
    skin_name myskin
</View>

Since you probably want your application to have a different style, or use different Widgets than those that ship with Reaction, you should really create your own Skin. But first, what is a Skin? A Skin is basically:

  • A collection of LayoutSets
  • A collection of static files
  • Subclassable

Yes, you read that last one right, you can subclass Skins. But not only Skins; in Reaction you can gradually extend every used ViewPort, Skin, LayoutSet or Widget. And of course you can define completely new ones as well. But we’ll talk about that later. Let’s take the above points step by step.

A small thing to mention here: Reaction does not use the root/ directory like Catalyst does, it stores its shared data below share/ instead.

Reaction will assume to find your Skin at share/skin/$skinname. It will also assume your LayoutSets to be found in share/skin/$skinname/layout. The LayoutSets determine how your page will be represented. The convention is to put your files for static delivery in share/skin/$skin_name/web, but you will have to configure your Static::Simple and webserver to point there yourself. Here is an example for the Static::Simple config file entry:

<static>
    include_path __path_to(share/skin/myskin/web)__
    include_path __path_to(share/web)__
</static>

Now, what was that with subclassing? Your skin will be configured by a file expected at share/skin/$skin_name/skin.conf. Here you can tell Reaction that this Skin is an extension of another Skin:

extends Reaction/default

This would mean that the LayoutSets in this Skin extend those in Reaction’s default Skin. Of course you can always extend one of your own Skins in your own share/skin/ directory too:

extends myparentskin

LayoutSets and Layouts

Do you remember the ViewPorts we gave to Reaction earlier? To every ViewPort belongs a LayoutSet. This is either specified with the ViewPort directly, or automatically discovered by the name of the ViewPort. Compare, for example:

$ctx->push_viewport(SiteLayout);

which will load a LayoutSet called sitelayout. With the typical and recommended TT renderer this will be found under share/skin/$skinname/layout/site_layout.tt. If you give it a layout argument, it will be used instead:

$ctx->push_viewport(SiteLayout, layout => 'my_layout');

will load share/skin/$skinname/layout/mylayout.tt.

(Note that I used the skin paths here as an example only. If your Skin extends, for example, Reactions default Skin, the above file would be searched at $reaction/share/skin/default/layout/my_layout.tt.

Here is a very simple site_layout LayoutSet as an example to illustrate:

=extends NEXT

=widget MySiteLayout

=for layout body

[% inner %]

<div id="rendered-at">
    [% rendered_at %]
</div>

=cut

As you can see, a LayoutSet is built using POD-like directives. They contain layout fragments, with a fragment named widget always being at the root. The =extends directive at the top has the value NEXT. This means this LayoutSet extends the one with the same name in the parent Skin. We now only have to customize those fragments of the LayoutSet that we want to be different.

In the example above, I added a block after the actual content (the inner variable will contain the rendered output of the item one step deeper in the FocusStack). The sitelayout in the default Skin itself inherits from Reaction’s base Skin. There are many fragments involved, including doctype, head, headmeta, head_style and the above customized body.

You probably also noticed the =widget directive with the value MySiteLayout. This is the widget that will be used to render the ViewPort with this LayoutSet. This is an importan point: The Widget is determined by the LayoutSet. The Widget is not specified by its full name. Instead, the name specified is used to search for the class in the paths configured in a file located at share/skin/defaults.conf:

widget_search_path MyApp::Web::Widget
widget_search_path Reaction::UI::Widget

With our directive above this would mean Reaction would first look for a widget called MyApp::Web::Widget::MySiteLayout and then, if it couldn’t find it, try Reaction::UI::Widget::MySiteLayout.

If we called our Widget MyApp::Web::Widget::SiteLayout instead, we could skip the =widget specification and our LayoutSet would inherit the widget value from its parent. Of course, then you could not easily use the original SiteLayout. How you implement this depends on your application’s needs. I would usually tend to extend the SiteLayout directly and make other subclasses for my special cases. I used a different name for the Widget here solely so you can more easily distinguish them.

Widgets

The LayoutSet above would still fail when used like this, because there is no Widget named MySiteLayout yet. So, let’s write one and put it into a file named lib/MyApp/Web/Widget/MySiteLayout.pm:

package MyApp::Web::Widget::MySiteLayout;
use Reaction::UI::WidgetClass;

extends 'Reaction::UI::Widget::SiteLayout';

before fragment body {
    arg rendered_at => $_{viewport}->rendered_at;
};

1;

That’s it. The use Reaction::UI::WidgetClass will setup the package with everything the widget class will need. The extends line means we’re extending Reaction’s own SiteLayout Widget.

While you might know the before from Moose, the fragment part is new. The code means that before the body fragment is rendered, a value in the layout fragment named renderedat will be set to whatever is returned from the renderedat method on the ViewPort (which can always be found in % under ${viewport}).

For completeness sake, let’s add the ViewPort providing this value:

package MyApp::Web::ViewPort::MySiteLayout;
use Reaction::Class;

use DateTime;

extends 'Reaction::UI::ViewPort::SiteLayout';

has rendered_at => (
    is        => 'rw',
    isa       => 'DateTime',
    default   => sub { DateTime->now },
    required  => 1,
    lazy      => 1,
);

As you can see, the ViewPorts are, like all the other classes, implemented via Moose, only that some have a bit more sugar. You can push this ViewPort like usual and either supply a rendered_at argument, or it will use the current DateTime as value.

Further Reading

There are some documents that give more insight on how Reaction renders pages:

No TrackBacks

TrackBack URL: http://www.catalyzed.org/mt/mt-tb.fcgi/25

1 Comment

| Leave a comment

Great overview! With the Reaction tutorial stuff and more blogs like this, I think we are actually starting to see more Reaction pickup.

Leave a comment

All comments are moderated. Spammers don't waste your time

Sponsored By


Ionzero: Rescue your dev project.
OpenID accepted here Learn more about OpenID

Following

Not following anyone

Note to spammers: all comments are moderated. Don't waste your time