Template-Lace

 view release on metacpan or  search on metacpan

lib/Template/Lace.pm  view on Meta::CPAN

      <input type='text' name='user' />
      <input type='text' name='age' />
      <input type='text' name='motto' />
      <input type='submit' />
    </lace-form>

Canonically a component is a tag in the form of '$prefix-$name' (like <prefix-name ...>)
and typically will contain HTML attributes (like 'attr1="Literal"') and it may also
have content, as in "<lace-form><input id='content' type="submit"/></lace-form>".  When you render
a template containing components, any HTML attributes will be converted to a real value
and passed to the component as initialization arguments.  There are three different
ways your attributes will be processed:

=over4

=item literal values

Example: <prefix-name attr='1984'/>

When a value is a simple literal that value is passed as is.

=item a path from the model instance

Example: <prefix-name attr='$.foo'/>

This returns the value of "$self->foo", where $self is the model instance that the factory
created.  You can follow a data path similarly to Template Toolkit, for example:

    <prefix-name attr="$.foo.bar.baz" />

Would be the value of "$self->foo->bar->baz" (or possibly $self->foo->{bar}{baz} since we
follow either a method name or the key of a hash).  Currently we do not follow arrayrefs.

You'll probably use this quite often to pass instance information to your component.  If
the path does not exist that will return a run time error.

=item a CSS match 

Examples: <prefix-name title=\'title:content' css=\'@link' />

When the value of the attribute begins with a '\' that means we want to get the value of
a CSS match to the current DOM.  In Perl when a variable starts with a '\' the means its
a reference; so you can think of this as a reference to a point in the current DOM.

In general the value here is just a normal CSS match specification (see L<Mojo::DOM58> for
details on the match specifications supported).  However we have added a few minor bits to
how L<Mojo::DOM58> works to make some types matching easier.  First, if a match specification
ends in ':content' that means 'match the content, not the full node'.  In the example case
"title=\'title:content'" that would get the text value of the title tag.  This could also be
used to get a stringified form of a matches content when the content is complex such as a match
like "body=\'body:content'.  If you want the contents of a match in the form of a collection of
nodes instead using "body=\'body:nodes'.  You might wish to do this if the contents are large
and you want to avoid unnessary stringification and parsing.

Lastly, in the case where you want the match to return a collection of nodes all matching the 
specification, you would prepend a '@' to the front of it (think in Perl @variable means an
array variable). In the given example "css=\'@link'" we want the attribute 'css' to be a
collection of all the linked stylesheets in the current DOM.

You will use this type of value when you are making components that do complex layout
and overlays of the current DOM (such as when you are creating a master layout page for
your website).

=back

In addition to any attributes you pass to a component via a declaration as described above
all components could get some of the following automatic atttributes:

=over 4

=item content

If the component has content as in the following example

    <prefix-name
        attr1='literal value'
        attr2='$.foo'
        attr3=\'title:content'>
      [some addtional content such as HTML markup and text]
    </prefix-name>

That content will be sent to the component under the 'content' attribute

=item container 

If the component is a subcomponent it will receive the instance model of its
parent as the 'container' attribute.

=item model

All components get the 'model' attribute, which is the model instance that contains
the template in which they appear.  I would use this carefully since I think that
you would prefer to pass information from the model to the component via attributes.

=back

If you need to pass complex or structured data to your arguments you may do so using
JSON:

    <prefix-name scalar='constant value'
        hash={"q":"The query string"}
        array=["1","2","3"] >
      [some addtional content such as HTML markup and text]
    </prefix-name>

Components can be an instance of any class that does C<create> and C<process_dom>
but generally you will make your components out of other L<Template::Lace> models
since that provides the most features and template reusability. Components are added to
the Factory at the time you construct it:

    my $factory = Template::Lace::Factory->new(
      model_class=>'Local::Template::List',
      component_handlers=>+{
        layout => sub {
          my ($name, $args, %attrs) = @_;
          $name = ucfirst $name;
          return Template::Lace::Factory->new(model_class=>"Local::Template::$name");
        },
        lace => {
          form => Template::Lace::Factory->new(model_class=>'Local::Template::Form'),
          input => Template::Lace::Factory->new(model_class=>'Local::Template::Input'),

lib/Template/Lace.pm  view on Meta::CPAN

of Perl is available!

Finally, lets look at the 'view-master' component, which is a type of layout component
to add common header / footer information to your page (a standard enough thing to do
when building a website:


    package  MyApp::View::Master;

    use Moo;

    has title => (is=>'ro', required=>1);
    has css => (is=>'ro', required=>1);
    has meta => (is=>'ro', required=>1);
    has body => (is=>'ro', required=>1);

    sub on_component_add {
    my ($self, $dom) = @_;
    $dom->title($self->title)
      ->head(sub { $_->append_content($self->css->join) })
      ->head(sub { $_->prepend_content($self->meta->join) })
      ->body(sub { $_->at('h1')->append($self->body) })
      ->at('#header')
        ->content($self->title);
    }

    sub template {
    my $class = shift;
    return q[
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta content="width=device-width, initial-scale=1" name="viewport" />
          <title>Page Title</title>
          <link href="/static/base.css" rel="stylesheet" />
          <link href="/static/index.css" rel="stylesheet"/ >
        </head>
        <body id="body">
          <h1 id="header">Intro</h1>
        </body>
      </html>        
      ];
    }

    1;

This is a type of component intended to perform layout work for you.  In this case we are creating a common
header and footer and some internal market.  The values for it attributes come not from the model class
but from the contained DOM itself.  Lets look again at the top of the component declaration:

    <view-master title=\'title:content'
        css=\'@link'
        meta=\'@meta'
        body=\'body:content'>

So four attributes, all coming from the DOM associated with the 'content' area of this component.  We
grab the content of the title tag and the content of the HTML body tag, as well as the collection (if
any) of the link takes (for css style sheets) and any template specific meta tags.

If you are looking carefully you have noticed instead of a 'process_dom' method we have a 'on_component_add' method.  We could do this with 'process_dom' but that method runs for every request and since this overlay contains no dynamic request bound ...

Here's a sample of the actual result, rendering all the components (you can peek at the repository which has all the code for these examples to see how it all works)

    <html>
      <head>
        <meta startup="Fri Mar 31 08:43:24 2017">
        <meta charset="utf-8">
        <meta content="width=device-width, initial-scale=1" name="viewport">
        <title>
          Things To Do
        </title>
        <link href="/static/base.css" rel="stylesheet" type="text/css">
        <link href="/css/input.min.css" rel="stylesheet" type="text/css">
        <script src="/js/input.min.js" type="text/javascript"></script>
      </head>
      <body id="body">
        <h1>
          Things To Do
        </h1>
        <form id="newtodo">
          <div class="field">
            <label for="item">Todo</label>
              <input id="item" name="item" type="text" value="milk">
          </div>
          <div class="ui error message">
            <ol class="errors">
              <li>too short
              </li>
              <li>too similar it existing item
              </li>
            </ol>
          </div>
         </form>
        <ol id="todos">
          <li>Buy Milk
          </li>
          <li>Walk Dog
          </li>
        </ol>
        <section id="footer">
          <hr>
          <p id="copy">
            copyright 2017
          </p>
        </section>
      </body>
    </html>

So even though we have a page with a lot happening, we can write a model class that focuses
just on the primary task (display the list of Todos) and let components handle the other work.
A complex template can be logically divided into clear chunks, each dedicated to one function
and each with a clearly defined, strongly typed interface.  I believe this leads to well
organized and concise templates that are maintainable over the long term.

You can review the documentation for each of the main classes in this distribution, and/or
review the test cases for more examples.  Or if you want to use this for building web sites
immediately, see L<Catalyst::View::Template::Lace> as you quickest path.

=head1 LOCAL COMPONENTS (Experimental)



( run in 0.823 second using v1.01-cache-2.11-cpan-7fcb06a456a )