The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Catalyst::View::Text::MicroTemplate::PerRequest - JSON View that owns its data

SYNOPSIS

    MyApp->inject_components(
      'View::HTML' => { from_component => 'Catalyst::View::Text::MicroTemplate::PerRequest' }
    );

    # In a controller...

    sub root :Chained(/) CaptureArgs(0) {
      my ($self, $c) = @_;
      $c->view('HTML')->data->set(z=>1);
    }

    sub midpoint :Chained(root) CaptureArgs(0) {
      my ($self, $c) = @_;
      $c->view('HTML')->data->set(y=>1);
    }

    sub endpoint :Chained(midpoint) Args(0) {
      my ($self, $c) = @_;
      $c->view('JSON')->created({
        a => 1,
        b => 2,
        c => 3,
      });
    }

    # template $HOME/root/endpoint.mt
    
    This is my template
    Here's some placeholders
    a => <?= $a ?>
    b => <?= $b ?>
    c => <?= $c ?>
    y => <?= $y ?>
    z => <?= $z ?>

DESCRIPTION

This is a Catalyst::View that uses Text::MicroTemplate::Extended which is a pure Perl templating engine that uses Perl code instead of a dedicated template domain specific language. You may find this a bad idea for large projects where you have a dedicated front end development team (or just a bad idea in general). However I find for smaller projects where the back end programmer does double duty as a UI developer its useful to avoid having to remember yet another DSL just to do output formating. Its reasonable fast and if you control yourself you won't make too much of a mess. You should review Text::MicroTemplate::Extended and Text::MicroTemplate for more on how the template engine works.

It differs from other Catalyst views on CPAN (including Catayst::View::Text::MicroTemplate) In that it is a 'per request view' that lets you define a view owned data model for passing information to the view. You may find this a better solution than using the stash although you may also use the stash if you like (there's a template setting for this.)

It also generates some local response helpers. You may or may not find this approach leads to cleaner code. Lastly we allow you to more easily override how we generate a default template name. In general I consider this view to be a somewhat experimental approach to solve some common problems that have really irked me about the commonly used approach to views in Catalyst However I do use this in product so I commit to making this stable and safe (just this is not the official proscribed way so you might find some learning curve).

A similar style view that produces JSON is Catalyst::View::JSON::PerRequest. In fact this was written so that I could have a view that did HTML with an identical interface as that JSON view, to make it easier in situations where I want to choose a view based on content negotiation (like when doing an API) but would like to keep the rest of the code the same.

I consider this approach to be an interesting experiment in alternative ways to use Catalyst views.

METHODS

This view defines the following methods

template

Used to override the default template, which is derived from the terminating action. You can set this to a particular template name, or an $action.

data (?$model)

Used to set the view data model, and/or to called methods on it (for example to set attributes that will later be used in the response.).

The default is an injected model based on Catalyst::Model::Text::MicroTemplate::ViewData which you should review for basic usage. I recommend setting it to a custom model that better encapsulates your view data. You may use any model in your Catalyst application. We recommend the model provide a method 'TO_HASH' to provide a read only view of the data suitable for use in a template, but that is optional.

You may only set the view data model once. If you don't set it and just call methods on it, the default view model is automatically used.

extra_template_args

This is an optional method you may defined in your view class. This is used to add addtional template arguments at request time. Example:

    sub extra_template_args {
      my ($self, $view, $c) = @_;
      return (
        user => $c->user,
      )
    }

res

response

    $view->response($status, @headers, \%data||$object);
    $view->response($status, \%data||$object);
    $view->response(\%data||$object);
    $view->response($status);
    $view->response($status, @headers);

Used to setup a response. Calling this method will setup an http status, finalize headers and set a body response.

Method '->response' Helpers

We map status codes from HTTP::Status into methods to make sending common request types more simple and more descriptive. The following are the same:

    $c->view->response(200, @args);
    $c->view->ok(@args);

    do { $c->view->response(200, @args); $c->detach };
    $c->view->detach_ok(@args);

See HTTP::Status for a full list of all the status code helpers.

render ($data)

Given a Perl data will return a string based on the current template.

    my $html = $c->view->render(@data);

process

used as a target for $c->forward. This is mostly here for compatibility with some existing methodology. For example allows using this view with the Renderview action class (common practice). I'd consider it a depracated approach, personally.

template_factory

Inherits a default value from "default_template_factory".

This is a subroutine reference that is used to generate a template name when you don't set one via the "template" setter (this is the common case, to let the view make the template choice for you). The common way to do this is to choose a template name base on the terminal action name, for example here is the default setting for this attribute:

    return sub {
      my ($view, $ctx) = @_;
      return $ctx->stash->{template} 
        || "${\$ctx->action}";      
    };

The subroutine reference will be called with two arguments, the view instance object and the current context object. This should allow you to experiment with new ideas in choosing a default. For example I often find it valuable to distinguish templates not just based on the action path, but also on the response status. That way I can set a default based on something like:

    return sub {
      my ($view, $ctx) = @_;
      return $ctx->response->status > 299 ?
        "${\$ctx->action}_${\$ctx->res->status}" :
          "${\$ctx->action}";
    };

That way if an action returns for various status, you can distinguish between OK and error or exception responses.

BTW, this complication points out that it is possible that the idea of allowing a view to set a default template via its internal logic is suspect. The issue here is that if you are seeking to write actions that can be used across different views (like an HTML view that needs a template and a JSON view that doesn't) you really prefer to not have code in your action that dictates a template choice.

ATTRIBUTES

This View defines the following attributes that can be set during configuration

merge_stash

Boolean, defaults to false. If enabled merges anything in $c->stash to the template arguments. Useful if you love the stash or have a situation where you want to start using this view in an existing application that makes extensive use of stash.

path_base

Location of your templates. Defaults to whatever $application->config->{root} is.

extension

File extention that your templates use. Defaults to ".mt". Please note the '.' is required.

content_type

The default content type of your view. Used when providing a response and the content type is currently undefined. Defaults to 'text/plain'.

template_args

An option hashref of arguments that are always provided to your template. See https://metacpan.org/pod/Text::MicroTemplate::Extended#template_args1 for more.

Your template always gets an arg '$c' which is the current context. You may also provide per context template args by providing a method 'extra_template_args'. The advantage of that approach is that method will get the context and other objects as arguments which makes it easier to provide context sensitive values.

default_template_factory

Default value for "template_factory". Useful if you want a global override for this.

macros

See https://metacpan.org/pod/Text::MicroTemplate::Extended#Macro

mt

This is the Text::MicroTemplate::Extended object. You probably want to leave this alone but you can set it as you wish as long as you provide a compatible interface. You may find this useful for things like mocking objects during testing.

default_view_model

The Catalyst model that is the default model for holding information to pass to the view. The default is Catalyst::Model::Text::MicroTemplate::ViewData which exposes a stash like interface based on Data::Perl::Role::Collection::Hash which is good as a basic interface but you may prefer to try a define a more strict view model interface.

If you create your own view model, it should define a method, 'TO_HASH' to provide a hash suitable to pass as arguments to the template. This allows you to separate the view model from the data passed. If you don't define such a method your view model object will be passed to the template as the first argument in '@_';

mt_class

The class used to create the "mt" object. Defaults to Text::MicroTemplate::Extended You can set this to whatever you wish but it should be a compatible interface.

mt_init_args

Arguments used to initialize the "mt_class" in "mt". Should be a hashref. See Text::MicroTemplate::Extended for available options

handle_process_error

A reference to a subroutine that is called when there is a failure to render the data given. This can be used globally as an attribute on the defined configuration for the view, and you can set it or overide the global settings on a context basis.

Setting this optional attribute will capture and handle error conditions. We will NOT bubble the error up to the global Catalyst error handling (we don't set $c->error for example). If you want that you need to set it yourself in a custom handler, or don't define one.

The subroutine receives two arguments: the view object and the exception. You must setup a new, valid response. For example:

    package MyApp::View::HTML;

    use Moo;
    extends 'Catalyst::View::Text::MicroTemplate::PerRequest';

    package MyApp;

    use Catalyst;

    MyApp->config(
      default_view =>'HTML',
      'View::HTML' => {
        handle_process_error => sub {
          my ($view, $err) = @_;
          $view->template('500bad_request'); # You need to create this template...
          $view->detach_bad_request({ err => "$err"});
        },
      },
    );

    MyApp->setup;

Or setup/override per context. Useful when you want to control the error message carefully based on URL.

    sub error :Local Args(0) {
      my ($self, $c) = @_;

      $c->view->handle_process_error(sub {
          my ($view, $err) = @_;
          $view->template('500bad_request');
          $view->detach_bad_request({ err => "$err"});
        });

      $c->view->ok( $bad_data );
    }

NOTE If you mess up the return value (you return something that can't be encoded) a second exception will occur which will NOT be handled and will then bubble up to the main application.

NOTE The view package contains a global function to a usable default error handler, should you wish to use something consistent and reasonably valid. Example:

    MyApp->config(
      default_view =>'HTML',
      'View::HtML' => {
        handle_encode_error => \&Catalyst::View::Text::MicroTemplate::HANDLE_PROCESS_ERROR,
      },
    );

The example handler is defined like this:

  sub HANDLE_PROCESS_ERROR {
    my ($view, $err) = @_;
    $view->template('500'); # you need to create a '500.mt' in your template root directory
    $view->detach_internal_server_error({ error => "$err"});
  }

UTF-8 NOTES

Generally a view should not do any encoding since the core Catalyst framework handles all this for you. Text::MicroTemplate opens your files with the utf-8 IO layer so you should be able to include wide character literals in your templates and everything should 'just work' with any recent Catalyst. As a result this template offers no method to do character encoding. Please raise an issue in the bug tracker if you have special unmet needs.

SEE ALSO

Catalyst, Catalyst::View, Catalyst::View::Text::MicroTemplate, CatalystX::InjectComponent, Catalyst::Component::InstancePerContext, Text::MicroTemplate::Extended

AUTHOR

John Napiorkowski email:jjnapiork@cpan.org

COPYRIGHT & LICENSE

Copyright 2016, John Napiorkowski email:jjnapiork@cpan.org

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.