Justin Hunter > Reaction > Reaction::Manual::Tutorial

Download:
Reaction-0.002005.tar.gz

Annotate this POD

CPAN RT

New  1
Open  1
Stalled  1
View/Report Bugs
Source  

NAME ^

Reaction::Manual::Tutorial - Step by Step Tutorial

DESCRIPTION ^

This document aims at giving simple step-by-step leading to an example application using the common functionality provided by Reaction.

CREATING A NEW APPLICATION ^

At first we have to create a new application. For this we use the catalyst.pl script as we would for any other Catalyst application:

  $ catalyst.pl MyApp
  [lots "created ..." messages]

There is nothing to change in the application class file.

As you work through this tutorial you'll be creating several new files in various directories. You can save some time by creating the directories now, like this:

  mkdir -p share/skin/myapp/layout lib/MyApp/View/Site/Widget lib/MyApp/Schema lib/MyApp/InterfaceModel

THE VIEW ^

Since we are not just rendering templates with Reaction, but layouts and widgets, a simple TT view won't suffice. We need to create our own lib/MyApp/View/Site.pm:

  package MyApp::View::Site;
  use Reaction::Class;
  
  use namespace::autoclean;
  
  extends 'Reaction::UI::View::TT';
  
  __PACKAGE__->meta->make_immutable;
  
  1;

The use Reaction::Class line will import Moose, strict and warnings into our file and might perform some Reaction specific setups.

We make sure that we don't provide imported functions as methods at runtime by using namespace::autoclean.

In its simplest version, our view just needs to do a extends 'Reaction::UI::View::TT' to make a new subclass of it.

We chose to call make_immutable on the class' meta class instance to have it inline methods for runtime speed improvements.

THE ROOT CONTROLLER ^

As usual in Catalyst, our root controller (at lib/MyApp/Controller/Root.pm represents the root namespace for our application. For this purpose, it should look like this:

  package MyApp::Controller::Root;
  use strict;
  use warnings;
  use parent 'Reaction::UI::Controller::Root';
  
  use aliased 'Reaction::UI::ViewPort';
  use aliased 'Reaction::UI::ViewPort::SiteLayout';
  
  use namespace::autoclean;
  
  __PACKAGE__->config(
      view_name       => 'Site',
      window_title    => 'MyApp Window',
      namespace       => '',
  );
  
  sub base: Chained('/') PathPart('') CaptureArgs(0) {
      my ($self, $ctx) = @_;
      $self->push_viewport(SiteLayout,
          title           => 'MyApp Test Title',
          static_base_uri => join('', $ctx->uri_for('/static')),
          meta_info       => {
              http_header   => {
                  'Content-Type' => 'text/html;charset=utf-8',
              },
          },
      );
  }
  
  sub root: Chained('base') PathPart('') Args(0) {
      my ($self, $ctx) = @_;
      $self->push_viewport(ViewPort, layout => 'root');
  }
  
  1;

The effects of strict, warnings, parent, aliased and namespace::autoclean should be clear by now. Let's take a look at the configuration.

The view_name determines which view to use. We set it to Site, which is our only view by now. Be careful to set view_name and not view, which would fail telling you it expected an object.

The window_title is the title given to the Reaction::UI::Window instance that will be stored in $ctx->stash->{window} by the begin action provided by Reaction::UI::Controller::Root.

The namespace setting anchors the root controller at /.

The base action here acts as a general point all other actions can chain off of. It pushes the Reaction::UI::ViewPort::SiteLayout viewport onto the focus stack. As arguments we see a title that will be used as page title later. The static_base_uri is used for static links like CSS and JavaScript files. Since we didn't specify a layout site_layout will be used.

We also defined a root action serving as application index. It chains off the base action. It is only pushing the root viewport Reaction::UI::ViewPort on the focus stack, but this time we specified a layout named root.

Reaction will try to find our layout files in share/skin/$skin_name/layout/*, so the next thing to do is to create a new skin and the layout files.

A NEW SKIN ^

If your version of Catalyst still creates a root instead of a share directory, you might want to rename it. This is regarded as a best practice and follows the conventions of this tutorial and other Reaction documentation.

First we need to create a directory for our new skin:

  $ mkdir -p share/skin/myapp/layout

Next we need to configure our new skin. This is done in the share/skin/myapp/skin.conf file. At the moment, all it should contain is

  extends /Reaction/default

Note that this extends specification contains the distribution name of the library or application of which to use the templates as base. You can also give it a relative name like

  extends foo

and it would try to extend a skin named foo in your own application's share/skin directory.

In general, the Reaction distribution provides two skin choices to build upon: /Reaction/default and /Reaction/base. base is Reaction's most basic skin while the default skin extends the base by embellishing the layouts with more XHTML. The default skin is designed to be a working skin.

In other words, if you want to write most of the low-level XHTML yourself then put the following in your skin.conf:

  extends /Reaction/base

If you want to use more XHTML out of the box, then configure your application to extend the default skin.

It's worth noting that you are not bound to the XHTML of the default or base skin layouts, because you are able to override layout fragments in your own skin.

Next we create share/skin/defaults.conf to allow settings that concern all skins of the application. It should contain only this:

  widget_search_path MyApp::View::Site::Widget
  widget_search_path Reaction::UI::Widget

This will tell Reaction to look in Reaction::UI::Widget::* and MyApp::View::Site::Widget::* for widget classes. That means that our layout named root will check for MyApp::View::Site::Widget::Root first and then look if Reaction::UI::Widget exists.

We want the first line to be able to create our own widgets and the second line to have Reaction find its own widgets.

Now we need to tell Reaction what skin it should use. We do this by adding this section to our myapp.conf:

  <View Site>
      skin_name myapp
  </View>

The value should be the name of the target directory under share/skin/.

LAYOUTS ^

We will need two layout files to begin with. One controlling the site layout and one for the root action.

The first will be created as share/skin/myapp/layout/site_layout.tt:

  =extends NEXT

  =for layout body

      <h1>Welcome to MyApp</h1>

      <div id="content">
          [% inner %]
      </div>

  =cut

The =extends directive specifies that this layout file is an extension of another layout file. The NEXT value here tells Reaction that this extends the site_layout layout in the base skin, which we have defined as /Reaction/default. That means, you can take a look at the layout we are extending at share/skin/default/layout/site_layout.tt in the Reaction distribution.

The =for layout directives allows us to set a layout fragment. We define a body fragment containing the common body for all pages using this site layout. The [% inner %] is where the deeper parts of the stack will be included, in the case of our root action that would be the Reaction::UI::ViewPort with the root layout.

If we want to override a specific fragment, we can do just that. There are two choices when overriding a specific fragment. One can disregard the parent fragment all together and start from scratch or they could reuse the parent fragment and add to it. The key to re-using a parent (extended) layout fragment is the keyword call_next. Specifically, we insert [% call_next %] to include the layout fragment of the parent.

The parent layout fragment is found either in another layout template named in the =extends directive or it's in a subclass of Reaction::UI::Widget. In the latter case, the Widget subclass name matches the name of the template or the widget name posed at the top of the layout file via the =widget directive.

Once you know the hierarchy call_next follows, then you know where to examine the details of the parent layouts or widgets.

The layout representing the root action is called share/skin/myapp/layout/root.tt:

  =for layout widget

      <p>Hello, World!</p>

  =cut

This one is rather simple. The =for layout widget directive is special in that the widget fragment will always be where the rendering starts. In fact, our site_layout layout too contains a widget fragment, you just don't see it because you inherited it from your base skin (or your base skin's base skin, for that matter) instead of defining it yourself.

A SIMPLE WIDGET ^

If we wanted to use a different kind of widget than that assumed automatically by Reaction, we could add a

  =widget ClassName

directive at the top of the layout file. But for now, we will instead create our own widget at lib/MyApp/View/Site/Widget/Root.pm:

  package MyApp::View::Site::Widget::Root;
  use Reaction::UI::WidgetClass;
  
  use namespace::autoclean;
  
  __PACKAGE__->meta->make_immutable;
  
  1;

This adds no new functionality at the moment. It just uses Reaction::UI::WidgetClass to ease and automate the setup of a new widget class. The widget can provide functionality and fragments to the layout. In a way, it can be seen as the Perl code backend to the layout file.

You can now start your script/myapp_server.pl and visit

  http://localhost:3000/

to view your "Hello, World" page.

ADDING A SCHEMA ^

The next part of the tutorial will be about adding data storage to our application. While most Catalyst web applications today (or at least they should) abstract their database schema with DBIx::Class::Schema into a separate module separated from the webapplication, Reaction takes this one step further by introducing so called interface models. The interface model defines the layer between your application and your domain model (in this case, the DBIx::Class schema).

The first thing we will need is a schema class in lib/MyApp/Schema.pm:

  package MyApp::Schema;
  use strict;
  use warnings;
  
  use parent 'DBIx::Class::Schema';
  
  __PACKAGE__->load_classes;
  
  1;

The schema class itself is built like a typical DBIx::Class::Schema. The difference in class definition starts at the result classes. For the example's sake, let's make a SQLite database called example.sqlite:

  $ cat > example.sqlite.sql
  CREATE TABLE foo (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    first_name  VARCHAR NOT NULL,
    last_name   VARCHAR NOT NULL
  );
  <Ctrl-D>

  $ sqlite3 example.sqlite < example.sqlite.sql
  $

The result class for this table combines the usual style of DBIx::Class with Moose meta data additions in lib/MyApp/Schema/Foo.pm:

  package MyApp::Schema::Foo;
  use Moose;
  use MooseX::Types::Moose  qw( Int );
  use Reaction::Types::Core qw( NonEmptySimpleStr );
  
  use namespace::autoclean;
  
  extends 'DBIx::Class';
  
  has id => 
      (is => 'ro', isa => Int, required => 1);
  
  has first_name => 
      (is => 'rw', isa => NonEmptySimpleStr, required => 1);
  
  has last_name => 
      (is => 'rw', isa => NonEmptySimpleStr, required => 1);
  
  __PACKAGE__->load_components(qw( IntrospectableM2M Core ));
  __PACKAGE__->table('foo');
  
  __PACKAGE__->add_columns(
      id => { 
          data_type         => 'integer', 
          is_auto_increment => 1,
      },
      first_name => { data_type => 'varchar' },
      last_name  => { data_type => 'varchar' },
  );
  
  __PACKAGE__->set_primary_key('id');
  
  1;

The MooseX::Types::Moose and Reaction::Types::Core modules export Moose type constraints (See also MooseX::Types and Moose::Util::TypeConstraints). Note that we are using "extends" in Moose here instead of base or parent to extend DBIx::Class.

Next we see our columns in form of Moose attribute definitions. The is, isa and required attribute parameters will all be used for introspection and interface building later. The required is rather straight-forward. The is will decide whether this attribute (or column) can be edited (ro means that it can't, rw means it can). The isa attribute will be used for validation and rendering of input fields.

The imported NonEmptySimpleStr for example gives us a simple single-line input box, while a Str from MooseX::Types::Moose would give us a textbox.

Following that, we have the usual DBIx::Class result class definitions. The only thing different might be the new DBIx::Class::IntrospectableM2M which will allow us to inspect many-to-many relations later on.

CREATING AN INTERFACE MODEL ^

The interface model should be separated from the application and the schema, since it will tie both together. In this case, we will use a reflector to set up the usual interface model actions for our schema (Create, Update, Delete, DeleteAll) in lib/MyApp/InterfaceModel/DBIC.pm:

  package MyApp::InterfaceModel::DBIC;
  
  # keep this on top
  use parent 'Reaction::InterfaceModel::Object';
  
  use Reaction::Class;
  use Reaction::InterfaceModel::Reflector::DBIC;
  
  use namespace::autoclean;
  
  my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new;
  
  $reflector->reflect_schema(
      model_class     => __PACKAGE__,
      schema_class    => 'MyApp::Schema',
  );
  
  __PACKAGE__->meta->make_immutable;
  
  1;

The parent import must happen before the Reaction::Class one in this case. Other than that, the only thing we do here is create a new Reaction::InterfaceModel::Reflector::DBIC and call reflect_schema to build our MyApp::InterfaceModel::DBIC::* namespace out of our MyApp::Schema.

TIEING THE INTERFACE MODEL TO THE APPLICATION ^

Next on the list is the integration of our new interface model into our application. For this we create a simple catalyst model in lib/MyApp/Model/DBIC.pm:

  package MyApp::Model::DBIC;
  use Reaction::Class;
  
  use namespace::autoclean;
  
  extends 'Catalyst::Model::Reaction::InterfaceModel::DBIC';
  
  __PACKAGE__->meta->make_immutable;
  
  __PACKAGE__->config(
      im_class    => 'MyApp::InterfaceModel::DBIC',
      db_dsn      => 'dbi:SQLite:example.sqlite',
  );
  
  1;

This model extends the Catalyst::Model::Reaction::InterfaceModel::DBIC base class shipped with Reaction. It's configuration must contain the im_class naming our interface model and the db_dsn. If you're using a different kind of database then you may neeb to add config for db_user, db_password, and db_params. All these get passed to the schema's connect() method.

Of course, since this is Catalyst, all this can also easily be specified via your application config file under the Model::DBIC key.

BUILDING A SIMPLE CRUD CONTROLLER ^

Now, since we have defined our interface model as well as our domain model including meta data, it isn't very hard (at least not for us) to build a basic (but extendable) CRUD controller in lib/MyApp/Controller/Foo.pm:

  package MyApp::Controller::Foo;
  use strict;
  use warnings;
  
  use parent 'Reaction::UI::Controller::Collection::CRUD';
  use Reaction::Class;
  
  use namespace::autoclean;
  
  __PACKAGE__->config(
      model_name      => 'DBIC',
      collection_name => 'Foo',
      actions => {
          base => { Chained => '/base', PathPart => 'foo' },
      },
  );
  
  1;

This controller subclasses Reaction::UI::Controller::Collection::CRUD, which is itself a subclass of Reaction::UI::Controller::Collection, a class to ease the creation of controllers who act on collections of things.

As you can see, for the simplest case we don't need any code; we simply configure our controller.

The model_name is the name of our interface model sans the MyApp::Model:: prefix. This means this entry points to MyApp::Model::DBIC in this case. The collection_name is the name of the collection in the specified interface model. For us, this would be Foo, to match the result class we created above and want to manage.

The actions part of the configuration is not Reaction, but rather Catalyst specific. This configures the actions inherited from Reaction::UI::Controller::Collection::CRUD. For it to work, we only need to tell the base action where to chain off from, and what PathPart to use. We chain it to the base action in our root controller. The foo path part is rather obvious.

Now you can restart your application and visit

  http://localhost:3000/foo

and you have a complete CRUD interface for Foo with listing, viewing, creating, updating and deleting capabilities.

WHERE TO GO NEXT ^

Reaction::Manual::Templates

When a viewport tries to render a layout, it will involve the view to figure out the corresponding template (or any other kind of GUI description) and render it. The template documentation is concerned mostly with the Reaction::UI::View::TT implementation allowing the developer to use the Template engine to render the layouts.

Reaction::Manual::Widgets

A widget is the backend Perl object providing the Perl view logic to a layout. What the rendered output actually looks like is determined by the layout. The widget is concerned with storing, providing and managing data used and rendered by the layout.

SEE ALSO ^

AUTHORS ^

See Reaction::Class for authors.

LICENSE ^

See Reaction::Class for the license.

syntax highlighting: