Catalyst-View-Template-Lace
view release on metacpan or search on metacpan
lib/Catalyst/View/Template/Lace/Tutorial.pod view on Meta::CPAN
is=>'ro',
lazy=>1,
default=>sub { $_[0]->container->maybe::errors->{$_[0]->name} },
);
sub process_dom {
my ($self, $dom) = @_;
# Set Label content
$dom->at('label')
->content($self->label)
->attr(for=>$self->name);
# Set Input attributes
$dom->at('input')->attr(
type=>$self->type,
value=>$self->value,
id=>$self->id,
name=>$self->name);
# Set Errors or remove error block
if($self->errors) {
$dom->ol('.errors', $self->errors);
} else {
$dom->at("div.error")->remove;
}
}
sub template {
my $class = shift;
return q[
<link href="css/main.css" />
<style id="min">
div { border: 1px }
</style>
<div class="field">
<label>LABEL</label>
<input />
</div>
<div class="ui error message">
<ol class='errors'>
<li>ERROR</li>
</ol>
</div>
];
}
1;
So there's a lot going on here! When run this will ask its containing component (in this case the view-form component) for its value and any errors. The view-form component, BTW got its values from the containing view, via attributes in the templ...
This example is not intended to propose a best practice but just to show how components can interact with one another. You might decide its simpler for the child components to get their arguments directly from the containing model, for example. Or ...
There's one more component at work in this example, the 'view-master' component. This is intended to be an example of a type of master page wrapper that controls the overall basic structure of many pages on on website. Lets look again at the top of...
<view-master title=\'title:content'
css=\'@link'
meta=\'@meta'
body=\'body:content'>
So this component declares four arguments, but how are the values for these arguments populated? In this case the '\' prepended to the value indicates that the value is a reference to a node (or nodes) in the contained DOM. The idea here is that th...
package MyApp::View::Master;
use Moo;
extends 'Catalyst::View::Template::Lace';
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;
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 ...
What might a controller that is invoking all this look like?
package MyApp::Controller::List;
use warnings;
use strict;
use base 'Catalyst::Controller';
sub display :Path('') Args(0) {
my ($self, $c) = @_;
$c->view('List',
copywrite => 2015,
form => {
fif => {
item => 'milk',
},
errors => {
item => ['too short', 'too similar it existing item'],
}
},
items => [
'Buy Milk',
'Walk Dog',
],
)->http_ok;
}
1;
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>
( run in 0.859 second using v1.01-cache-2.11-cpan-437f7b0c052 )