Catalyst-View-Template-Pure

 view release on metacpan or  search on metacpan

lib/Catalyst/View/Template/Pure.pm  view on Meta::CPAN

    $c->view('Hello',
      title => 'Hello to You!',
      name => 'John Napiorkowski',
    )->http_ok;

We are setting $c->res->status(200).  For people that prefer the actual code numbers
there is also ->http_200 injected if you are better with the number codes instead of
the friendly names but I recommend you choose one or the other approach for your project!

Please keep in mind that calling ->http_ok (or any of the helper methods) does not
immediately finalize your response.  If you want to immediately finalize the
response (say for example you are returning an error and want to stop processing the
remaining actions) you will need to $c->detach like normal.  To make this a little
easier you can chain off the response helper like so:

    $c->view('NotFound')
      ->http_404
      ->detach;

Sending a request that hits the 'say_hello' action would result in:

    <html>
      <head>
        <title>Hello to You!</title>
      </head>
      <body>
        <p>Hello <span id='name'>John Napiorkowski</span>!<p>
        <p>This page was generated on: Tue Aug  2 09:17:48 2016</p>
      </body>
    </html>  

(Of course the timestamp will vary based on when you run the code, this
was the result I got only at the time of writing this document).

=head1 USING THE STASH

If you are used to using the L<Catalyst> stash to pass information to your view
or you have complex chaining and like to build up data over many actions into the
stash, you may continue to do that.  For example:

    sub say_hello :Path('') Args(0) {
      my ($self, $c) = @_;
      $c->stash(
        title => 'Hello to You!',
        name => 'John Napiorkowski',
      );
      $c->view('Hello')->http_ok;
    }

Would be the functional equal to the earlier example.  However as noted those
arguments are not passed directly to the template as data, but rather passed as
initialization arguments to the ->new method when calling the view the first time
in a request.  So you may still use the stash, but because the view is mediating
the stash data I believe we mitigate some of the stash's downsides (such as a lack
of strong typing, missing defined interface and issues with typos, for example).

=head1 CHAINING TEMPLATE TRANFORMATIONS

There are several ways to decompose your repeated or options template transforms
into reusable chunks, at the View level.  Please see L<Template::Pure> for more
abour includes, wrappers and overlays.  However there are often cases when the
decision to use or apply changes to your template best occur at the controller
level.  For example you may wish to add some messaging to your template if a form
has incorrect data.  In those cases you may apply additional Views.  Applied views
will use as its starting template the results of the previous view.  For example:

    sub process_form :POST Path('') Args(0) {
      my ($self, $c) = @_;
      my $v = $c->view('Login');

      if($c->model('Form')->is_valid) {
        $v->http_ok;
      } else {
        $v->apply('IncorrectLogin')
          ->http_bad_request
          ->detach;
      }
    }

You may chain as many applied views as you like, even using this technique to build up
an entire page of results.  Chaining transformations this way can help you to avoid some
of the messy, complex logic that often creeps into our templates.

=head1 MAPPING TEMPLATE ARGS FROM AN OBJECT

Generally you send arguments to the View via the stash or via arguments on the view
call itself.  This might sometimes lead to highly verbose calls:

    sub user :Path Args(1) {
      my ($self, $c, $id) = @_:
      my $user = $c->model('Schema::User')->find($id) ||
        $c->view('NoUser')->http_bad_request->detach;

      $c->view('UserProfile',
        name => $user->name,
        age => $user->age,
        location => $user->location,
        ...,
      );
    }

Listing each argument has the advantage of clarity but the verbosity can be distracting
and waste programmer time.  So, in the case where a source object provides an interface
which is identical to the interface required by the view, you may just pass the object
and we will map required attributes for the view from method named on the object.  For
example:

    sub user :Path Args(1) {
      my ($self, $c, $id) = @_:
      my $user = $c->model('Schema::User')->find($id) ||
        $c->view('NoUser')->http_bad_request
          ->detach;

      $c->view(UserProfile => $user)
        ->http_ok;
    }

It is up to you to decide if this is creating too much structual binding between your
view and its model.  You may or may not find it a useful convention.

=head1 COMMON VIEW TASKS

lib/Catalyst/View/Template/Pure.pm  view on Meta::CPAN

an example of an include which creates a time stamp element in your page:

    package  MyApp::View::Include;

    use Moose;

    extends 'Catalyst::View::Template::Pure';

    sub now { scalar localtime }

    __PACKAGE__->config(
      template => q{
        <div class="timestamp">The Time is now: </div>
      },
      directives => [
        '.timestamp' => 'now'
      ],
    );

    __PACKAGE__->meta->make_immutable;

Since this include is not intended to be used 'stand alone' we didn't bother to
set a 'returns_status' configuration.

So there's a few ways to use this in a template.

    package  MyApp::View::Hello;

    use Moose;
    use HTTP::Status qw(:constants);

    extends 'Catalyst::View::Template::Pure';

    has 'name' => (is=>'ro', required=>1);

    __PACKAGE__->config(
      returns_status => [HTTP_OK],
      template => q{
        <html>
          <head>
            <title>Hello</title>
          </head>
          <body>
            <p id='hello'>Hello There </p>
            <?pure-include src='Views.Include'?>
          </body>
        </html>
      },
      directives => [
        '#hello' => 'name',
      ],
    );

    __PACKAGE__->meta->make_immutable;

In this example we set the C<src> attribute for the include processing
instruction to a path off 'Views' which is a special method on the view that
returns access to all the other views that are loaded.  So essentially any
view could serve as a source.

The same approach would be used to set overlays and wrappers via processing
instructions.

If using the C<Views> helper seems too flimsy an interface, you may instead
specify a view via an accessor, just like any other data.

    package  MyApp::View::Hello;

    use Moose;
    use HTTP::Status qw(:constants);

    extends 'Catalyst::View::Template::Pure';

    has 'name' => (is=>'ro', required=>1);

    sub include {
      my $self = shift;
      $self->ctx->view('Include');
    }

    __PACKAGE__->config(
      returns_status => [HTTP_OK],
      template => q{
        <html>
          <head>
            <title>Hello</title>
          </head>
          <body>
            <p id='hello'>Hello There </p>
            <?pure-include src='include' ?>
          </body>
        </html>
      },
      directives => [
        '#hello' => 'name',
      ],
    );

    __PACKAGE__->meta->make_immutable;

Just remember if your include expects arguments (and most will) you should pass
them in the view call.

In fact you could allow one to pass the view C<src> include (or wrapper, or overlay)
from the controller, if you need more dynamic control:

    package  MyApp::View::Hello;

    use Moose;
    use HTTP::Status qw(:constants);

    extends 'Catalyst::View::Template::Pure';

    has 'name' => (is=>'ro', required=>1);
    has 'include' => (is=>'ro', required=>1);

    __PACKAGE__->config(
      returns_status => [HTTP_OK],
      template => q{
        <html>
          <head>
            <title>Hello</title>
          </head>
          <body>
            <p id='hello'>Hello There </p>
            <?pure-include src='include' ?>
          </body>
        </html>
      },
      directives => [
        '#hello' => 'name',
      ],
    );

    __PACKAGE__->meta->make_immutable;

    package MyApp::Controller::Hello;

    use Moose;
    use MooseX::Attributes;

    extends 'Catalyst::Controller';

    sub hello :Path('') {
      my ($self, $ctx) = @_;
      $ctx->view('Hello',
        name => 'John',
        include => $ctx->view('Include'));
    }

    __PACKAGE__->meta->make_immutable;

Even more fancy approaches could include setting up the required bits via
dependency injection (approaches for this in Catalyst are still somewhat
experimental, see L<Catalyst::Plugin::MapComponentDependencies>

=head1 METHODS

This class defines the following methods.  Please note that response helpers
will be generated as well (http_ok, http_200, etc.) based on the contents of
your L<\returns_status> configuration settings.

=head2 apply

Takes a view name and optionally arguments that are passed to ->new.  Used to
apply a view over the results of a previous one, allowing for chained views.
For example:

    $c->view('Base', %args)
      ->apply('Sidebar', items => \@menu_items)
      ->apply('Footer', copyright => 2016)
      ->http_ok;

When a view is used via 'apply', the result of the previous template becomes
the 'template' argument, even if that view defined its own template via
configuration.  This is so that you can use the same view as standalone or as
part of a chain of transformations.

Useful when you are building up a view over a number of actions in a chain or
when you need to programmatically control how a view is created from the
controller.  You may also consider the use of includes and overlays inside your
view, or custom directive actions for more complex view building.

=head2 wrap

Used to pass the response on a template to another template, via a 'content'
argument. Similar to the 'wrapper' processing instruction.  Example:

    package MyApp::View::Users;

    use Moose;

    extends 'Catalyst::View::Template::Pure';

    has [qw/name age location/] => (is=>'ro', required=>1);

    __PACKAGE__->config(
      returns_status => [200],
      template => q[
        <dl>
          <dt>Name</dt>
          <dd id='name'></dd>
          <dt>Age</dt>
          <dd id='age'></dd>
          <dt>Location</dt>
          <dd id='location'></dd>
        </dl>
      ],
      directives => [
        '#name' => 'name',
        '#age' => 'age',
        '#location' => 'location',
      ]
    );

    package MyApp::View::HeaderFooter;

    use Moose;

    extends 'Catalyst::View::Template::Pure';

    has 'title' => (is=>'ro', isa=>'String');
    has 'content' => (is=>'ro');

    __PACKAGE__->config(
      returns_status => [200],
      template => q[
        <html>
          <head>
            <title>TITLE GOES HERE</title>
          </head>
          <body>
            CONTENT GOES HERE
          </body>
        </html>
      ],
      directives => [
        title => 'title',
        body => 'content',
      ]
    );



( run in 1.287 second using v1.01-cache-2.11-cpan-39bf76dae61 )