The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Catalyst::View::Valiant::HTMLBuilder - Per Request, strongly typed Views in code

SYNOPSIS

    package Example::View::HTML::Home;

    use Moo;
    use Catalyst::View::Valiant::HTMLBuilder
      -tags => qw(div blockquote form_for fieldset),
      -helpers => qw($sf),
      -views => 'HTML::Layout', 'HTML::Navbar';

    has info => (is=>'rw', predicate=>'has_info');
    has person => (is=>'ro', required=>1);

    sub render($self, $c) {
      html_layout page_title => 'Sign In', sub($layout) {
        html_navbar active_link=>'/',
        blockquote +{ if=>$self->has_info, 
          class=>"alert alert-primary", 
          role=>"alert" }, $self->info,
        div $self->person->$sf('Welcome {:first_name} {:last_name} to your Example Homepage');
        div {if=>$self->person->profile_incomplete}, [
          blockquote {class=>"alert alert-primary", role=>"alert"}, 'Please complete your profile',
          form_for $self->person, sub($self, $fb, $person) {
            fieldset [
              $fb->legend,
              div +{ class=>'form-group' },
                $fb->model_errors(+{show_message_on_field_errors=>'Please fix validation errors'}),
              div +{ class=>'form-group' }, [
                $fb->label('username'),
                $fb->input('username'),
                $fb->errors_for('username'),
              ],
              div +{ class=>'form-group' }, [
                $fb->label('password'),
                $fb->password('password'),
                $fb->errors_for('password'),
              ],
              div +{ class=>'form-group' }, [
                $fb->label('password_confirmation'),
                $fb->password('password_confirmation'),
                $fb->errors_for('password_confirmation'),
              ],
            ],
            fieldset $fb->submit('Complete Account Setup'),
          ],
        ],
      };
    }

    1;

DESCRIPTION

WARNINGS: Experimental code that I might need to break back compatibility in order to fix issues.

This is a Catalyst::View subclass that provides a way to write views in code that are strongly typed and per request. It also integrates with several of Valiant's HTML form generation code modules to make it easier to create HTML forms that properly synchronize with your Valiant models for displaying errors and performing validation.

Unlike most Catalyst views, this view is 'per request' in that it is instantiated for each request. This allows you to store per request state in the view object as well as localize view specific logic to the view object. In particular it allows you to avoid or reduce using the stash in order to pass values from the controller to the view. I think this can make your views more robust and easier to support for the long term. It builds upons Catalyst::View::BasePerRequest which provides the per request behavior so you should take a look at the documentation and example controller integration in that module in order to get the idea.

As a quick example here's a possible controller that might invoke the view given in the SYNOPSIS:

    package Example::Controller::Home;

    use Moose;
    use MooseX::MethodAttributes;

    extends 'Catalyst::Controller';

    sub index($self, $c) {
      my $view = $c->view('HTML::Home', person=>$c->user);
      if( # Some condition ) {
        $view->info('You have been logged in');
      }
    }

    1;

This will then work with the commonly used Catalyst::Action::RenderView or my Catalyst::ActionRole::RenderView to produce a view response and set it as the response body.

Additionally, this view allows you to import HTML tags from Valiant::HTML::Util::TagBuilder as well as HTML tag helper methods from Valiant::HTML::Util::FormTags and Valiant::HTML::Util::Form into your view code. You should take a look at the documentation for those modules to see what is available. Since Valiant::HTML::Util::TagBuilder includes basic flow control and logic this gives you a bare minimum templating system that is completely in code. You can import some utility methods as well as other views into your view (please see the "EXPORTS" section below for more details). This is currently lightly documented so I recommend also looking at the test cases as well as the example Catalyst application included in the distribution under the example/ directory.

ATTRIBUTES

This class inherits all of the attributes from Catalyst::View::BasePerRequest

METHODS

This class inherits all of the methods from Catalyst::View::BasePerRequest as well as:

form

Returns the current form object.

tags

A convenience method to get the tags object from the current form.

safe

Marks a string as safe to render by first escaping it and then wrapping it in a Valiant::HTML::SafeString object.

raw

Marks a string as safe to render by wrapping it in a Valiant::HTML::SafeString object.

safe_concat

Given one or more strings and / or Valiant::HTML::SafeString objects, returns a new Valiant::HTML::SafeString object that is the concatenation of all of the strings.

escape_html

Given a string, returns a new string that is the escaped version of the original string.

read_attribute_for_html

Given an attribute name, returns the value of that attribute if it exists. If the attribute does not exist, it will die.

attribute_exists_for_html

Given an attribute name, returns true if the attribute exists and false if it does notu.

formbuilder_class

    sub formbuilder_class { 'Example::FormBuilder' }

Provides an easy way to override the default formbuilder class. By default it will use Valiant::HTML::FormBuilder. You can override this method to return a different class via a subclass of this view.

EXPORTS

-tags

Export any HTML tag supported in Valiant::HTML::TagBuilder as well as tag helpers from Valiant::HTML::Util::FormTags and Valiant::HTML::Util::Form. Please note the tr tag must be imported by the trow name since tr is a reserved word in Perl.

-helpers

Export the following functions as well as any named method from the current controller and application context:

$user

The current logged in user if any (via $c->user)

$sf
    $person->$sf('Hi there {:first_name} {:last_name} !!')

Exports a coderef helper that wraps the sf method in Valiant::HTML::TagBuilder. Useful when you have an object whos methods you want as values in your view.

content
content_for
content_append
content_replace
content_around

Wraps the named methods from Catalyst::View::BasePerRequest for export. You can still call them directly on the view object if you prefer.

path

Given an instance of Catalyst::Action or the name of an action, returns the full path to that action as a url. Basically a wrapper over uri_for that will die if it can't find the action. It also properly support relatively named actions.

-views

Create export wrappers for the named Catalyst views. Export names will be snake cased versions of the given view names.

SUBCLASSING

You can subclass this view in order to provide your own default behavior and additional methods.

    package View::Example::View;

    use Moo;
    use Catalyst::View::Valiant
      -tags => qw(blockquote label_tag);

    sub formbuilder_class { 'Example::FormBuilder' }

    sub stuff2 {
      my $self = shift;
      $self->label_tag('test', sub {
        my $view = shift;
        die unless ref($view) eq ref($self);
      });
      return $self->tags->div('stuff2');
    }

    sub stuff3 :Renders {
      blockquote 'stuff3', 
      shift->div('stuff333')
    }

    1;

Then the view View::Example::View can be used in exactly the same way as this view.

TIPS & TRICKS

Creating render methods

Often you will want to break up your render method into smaller chunks. You can do this by creating methods that return Valiant::HTML::SafeString objects. You can then call these methods from your render method. Here's an example:

    sub simple :Renders {
      my $self = shift;
      return div "Hey";
    }

You can then call this method from another render method:

    sub complex :Renders {
      my $self = shift;
      return $self->simple;
    }

Or use it directly in your main render method:

    sub render {
      my $self = shift;
      return $self->simple;
    }

Please note you need to add the ':Renders' attribute to your method in order for it to be exported as a render method. You don't need to do that on the main render method in your class because we handle that for you.

Calling for view fragments

You can call for the response of any view's method wish is marked as a render method.

  package Example::View::Fragments;

    use Moo;
    use Catalyst::View::Valiant
      -tags => qw(div);

    sub stuff4 :Renders { div 'stuff4' }

    1;

Then in your main view:

  package Example::View::Hello;

    use Moo;
    use Catalyst::View::Valiant
      -views => qw(Fragments);

    sub render {
      my $self = shift;
      return fragment->stuff4;
    }

You can even call them in a controller:

    sub index :Path {
      my ($self, $c) = @_;
      $c->res->body($c->view('Fragments')->stuff4);
    }

SEE ALSO

Valiant, Valiant::HTML::Util::Form, Valiant::HTML::Util::FormTags, Valiant::HTML::Util::Tagbuilder, Valiant::HTML::SafeString.

AUTHOR

See Valiant

COPYRIGHT & LICENSE

See Valiant