NAME
Catalyst::View::ByCode - Templating using pure Perl code
VERSION
version 0.15
SYNOPSIS
# 1) use the helper to create your View
./script/myapp_create.pl view ByCode ByCode
# 2) inside your Controllers do business as usual:
sub index :Path :Args(0) {
my ($self, $c) = @_;
# unless defined as default_view in your config, specify:
$c->stash->{current_view} = 'ByCode';
$c->stash->{title} = 'Hello ByCode';
# if omitted, would default to
# controller_namespace / action_namespace .pl
$c->stash->{template} = 'hello.pl';
}
# 3) create a simple template eg 'root/bycode/hello.pl'
# REMARK:
# use 'c' instead of '$c'
# prefer 'stash->{...}' to 'c->stash->{...}'
template {
html {
head {
title { stash->{title} };
load Js => 'site.js';
load Css => 'site.css';
};
body {
div header.noprint {
ul.topnav {
li { 'home' };
li { 'surprise' };
};
};
div content {
h1 { stash->{title} };
div { 'hello.pl is running!' };
img(src => '/static/images/catalyst_logo.png');
};
};
};
};
# 274 characters without white space
# 4) expect to get this HTML generated:
<html>
<head>
<title>Hello ByCode!</title>
<script src="http://localhost:3000/js/site.js"
type="text/javascript">
</script>
<link rel="stylesheet"
href="http://localhost:3000/css/site.css"
type="text/css" />
</head>
<body>
<div id="header" style="noprint">
<ul class="topnav">
<li>home</li>
<li>surprise</li>
</ul>
</div>
<div class="content">
<h1>Hello ByCode!</h1>
<div>hello.pl is running!</div>
<img src="/static/images/catalyst_logo.png" />
</div>
</body>
</html>
# 453 characters without white space
DESCRIPTION
Catalyst::View::ByCode
tries to offer an efficient, fast and robust solution for generating HTML and XHTML markup using standard perl code encapsulating all nesting into code blocks.
Instead of typing opening and closing HTML-Tags we simply call a sub named like the tag we want to generate:
div { 'hello' }
generates:
<div>hello</div>
There is no templating language you will have to learn, no quirks with different syntax rules your editor might not correctly follow and no indentation problems.
The whole markup is initially constructed as a huge tree-like structure in memory keeping every reference as long as possible to allow greatest flexibility and enable deferred construction of every building block until the markup is actially requested.
Every part of the markup can use almost every type of data with some reasonable behavior during markup generation.
Tags
Every tag known in HTML (or defined in HTML::Tagset to be precise) gets exported to a template's namespace during its compilation and can be used as expected. However, there are some exceptions which would collide with CORE subs or operators
- choice
-
generates a <select> tag
- link_tag
-
generates a <link> tag
- trow
-
generates a <tr> tag
- tcol
-
generates a <td> tag
- subscript
-
generates a <sub> tag
- superscript
-
generates a <sup> tag
- meta_tag
-
generates a <meta> tag
- quote
-
generates a <q> tag
- strike
-
generates a <s> tag
- map_tag
-
generates a <map> tag
Internally, every tag subroutine is defined with a prototype like
sub div(;&@) { ... }
Thus, the first argument of this sub is expected to be a coderef, which allows to write code like the examples above. Nesting tags is just a matter of nesting calls into blocks.
Content
There are several ways to generate content which is inserted between the opening and the closing tag:
The return value of the last expression of a code block will get appended to the content inside the tag. The content will get escaped when needed.
To append any content (getting escaped) at any point of the markup generation, the
OUT
glob can be used:print OUT 'some content here.';
To append unescaped content eg JavaScript or the content of another markup-generating subsystem like
HTML::FormFu
simple use the <RAW> glob:print RAW '<?xxx must be here for internal reasons ?>';
Attributes
As usual for Perl, there is always more than one way to do it:
- old-school perl
-
# appending attributes after tag div { ... content ... } id => 'top', class => 'noprint silver', style => 'display: none';
the content goes into the curly-braced code block immediately following the tag. Every extra argument after the code block is converted into the tag's attributes.
- special content
-
# using special methods div { id 'top'; class 'noprint silver'; attr style => 'display: none'; 'content' };
Every attribute may be added to the latest opened tag using the
attr
sub. However, there are some shortcuts:- id 'name'
-
is equivalent to
attr id => 'name'
- class 'class'
-
is the same as
attr class => 'class'
However, the
class
method is special. It allows to specify a space-separated string, a list of names or a combination of both. Class names prefixed with-
or+
are treated special. After a minus prefixed class name every following name is subtracted from the previous list of class names. After a plus prefixed name all following names are added to the class list. A list of class names without a plus/minus prefix will start with an empty class list and then append all subsequentially following names.# will yield 'abc def ghi' div.foo { class 'abc def ghi' }; # will yield 'foo def xyz' div.foo { class '+def xyz' }; # will yield 'bar' div.foo { class '-foo +bar' };
- on handler => 'some javascript code'
-
produces the same result as
attr onhandler =
'some javascript code'>div { on click => q{alert('you clicked me')}; };
- tricky arguments
-
div top.noprint.silver(style => 'display: none') {'content'};
- even more tricky arguments
-
div top.noprint.silver(style => {display => 'none'}) {'content'};
- tricky arguments and CamelCase
-
div top.noprint.silver(style => {marginTop => '20px'}) {'foo'};
marginTop
ormargin_top
will get converted tomargin-top
.
Every attribute may have almost any datatype you might think of:
- scalar
-
Scalar values are taken verbatim.
- hashref
-
Hash references are converted to semicolon-delimited pairs of the key, a colon and a value. The perfect solution for building inline CSS. Well, I know, nobody should do something, but sometimes...
Keys consisting of underscore characters and CAPITAL letters are converted to dash-separated names.
dataTarget
ordata_target
both becomedata-target
. - arrayref
-
Array references are converted to space separated things.
- coderef -- FIXME: do we like this?
-
no idea if we like this
- other refs
-
all other references simply are stringified. This allows the various objects to forward stringification to their class-defined code.
Exported subs
- attr
-
Setter or Getter for attribute values. Using the
attr
sub refers to the latest open tag and sets or gets its attribute(s):div { attr(style => 'foo:bar'); # set 'style' attribute attr('id'); # get 'id' attribute (or undef) ... more things ... a { attr(href => 'http://foo.bar'); # refers to 'a' tag }; attr(lang => 'de'); # sets attribute in 'div' tag };
- block
-
define a block that may be uses like a tag. If a block is defined in a package, it is automatically added to the package's
@EXPORT
array.# define a block block navitem { my $id = attr('id'); my $href = attr('href'); li { id $id if ($id); a(href => $href || 'http://foo.bar') { block_content; }; }; }; # use a block like a tag block some_id (href => 'http://bar.baz') { # this gets rendered by block_content() -- see above 'some text or other content'; } # will generate: <li id="some_id"> <a href="http://bar.baz">some text or other content</a> </li>
- block_content
-
a simple shortcut to render the content of the block at a given point. See example above.
- c
-
holds the content of the
$c
variable. Simple writec->some_method
instead of$c->some_method
. - class
-
provides a shortcut for defining class names. All examples below will generate the same markup:
div { class 'class_name'; }; div { attr class => 'class_name'; }; div { attr('class', 'class_name'); }; div.class_name {};
Using the
class()
subroutine allows to prefix a class name with a+
or-
sign. Every class name written after a+
sign will get appended to the class, each name written after a-
sign will be erased from the class. - doctype
-
a very simple way to generate a DOCTYPE declatation. Without any arguments, a HTML 4.0 doctype declaration will be generated. The arguments (if any) will consist of either of the words
html
orxhtml
optionally followed by one or more version digits. The doctypes used are taken from http://hsivonen.iki.fi/doctype/.some examples:
doctype; # HTML 4.0 doctype 'html'; # HTML 4.01 doctype html => 4; # HTML 4.01 doctype 'html 4'; # HTML 4.01 doctype 'html 4s'; # HTML 4.01 strict doctype 'html 4strict'; # HTML 4.01 strict doctype 'xhtml'; # XHTML 1.0 doctype 'xhtml 1 1'; # XHTML 1.1
- id
-
provides a shortcut for defining id names. All examples here will generate the same markup:
div { id 'id_name'; }; div { attr id => 'id_name'; }; div { attr('id', 'id_name'); }; div id_name {};
- load
-
an easy way to include assets into a page. Assets currently are JavaScript or CSS. The first argument to this sub specifies the kind of asset, the second argument is the URI to load the asset from.
Some examples will clearify:
load js => '/static/js/jquery.js'; load css => '/static/css/site.css';
If you plan to develop your JavaScript or CSS files as multiple files and like to combine them at request-time (with caching of course...), you might like to use Catalyst::Controller::Combine. If your controllers are named
Js
andCss
, this will work as well:load Js => 'name_of_combined.js';
- on
-
provides a syntactic sugar for generating inline JavaScript handlers.
a(href => '#') { on click => q{alert('you clicked me'); return false}; };
- params
-
generates a series of
param
tags.applet ( ... ) { params( quality => 'foo', speed => 'slow', ); };
- stash
-
is a shortcut for
c->stash
. - template
-
essentially generates a sub named
RUN
as the main starting point of every template file. Both constructs will be identical:sub RUN { div { ... }; } template { div { ... }; };
Be careful to add a semicolon after the
template
definition if you add code after it!!! - yield
-
Without arguments,
yield
forwards exection to the next template in the ordinary execution chain. Typically this is the point in a wrapper template that includes the main template.With an argument, it forwards execution to the template given as the argument. These values are possible:
- just a symbolic name
-
if a symbolic name is given, this name is searched in the
stash->{yield}->{...}
hashref. If it is found, the file-name or subref stored there will be executed and included at the given point. - a path name
-
if a template file exists at the path name given as the argument, this template is compiled and executed.
- a code-ref
-
a code ref is directly executed.
If yield is not able to find something, simply nothing happens. This behavior could be useful to add hooks at specified positions in your markup that may get filled when needed.
Building Reusable blocks
You might build a reusable block line the following calls:
block 'block_name', sub { ... };
# or:
block block_name => sub { ... };
# or shorter:
block block_name { ... };
The block might get used like a tag:
block_name { ... some content ... };
If a block-call contains a content it can get rendered inside the block using the special sub block_content
. A simple example makes this clearer:
# define a block:
block infobox {
# attr() values must be read before generating markup
my $headline = attr('headline') || 'untitled';
my $id = attr('id');
my $class = attr('class');
# generate some content
div.infobox {
id $id if ($id);
class $class if ($class);
div.head { $headline };
div.info { block_content };
};
};
# later we use the block:
infobox some_id.someclass(headline => 'Our Info') {
'just my 2 cents'
};
# this HTML will get generated:
<div class="someclass" id="some_id">
<div class="head">Our Info</div>
<div class="info">just my 2 cents</div>
</div>
every block defined in a package is auto-added to the packages @EXPORT
array and mangled in a special way to make the magic calling syntax work after importing it into another package.
CONFIGURATION
A simple configuration of a derived Controller could look like this:
__PACKAGE__->config(
# Change extension (default: .pl)
extension => '.pl',
# Set the location for .pl files (default: root/bycode)
root_dir => cat_app->path_to( 'root', 'bycode' ),
# This is your wrapper template located in root_dir
# (default: wrapper.pl)
wrapper => 'wrapper.pl',
# all these modules are use()'d automatically
include => [Some::Module Another::Package],
);
By default a typical standard configuration setting is constructed by issuing the Helper-Module. It looks like this and describes all default settings:
__PACKAGE__->config(
# # Change default
# extension => '.pl',
#
# # Set the location for .pl files
# root_dir => 'root/bycode',
#
# # This is your wrapper template located in the 'root_dir'
# wrapper => 'wrapper.pl',
#
# # specify packages to use in every template
# include => [ qw(My::Package::Name Other::Package::Name) ]
);
The following configuration options are available:
- root_dir
-
With this option you may define a location that is the base of all template files. By default, the directory root/bycode inside your application will be used.
- extension
-
This is the default file extension for template files. As an example, if your Controller class is named
MyController
and your action method callsMyAction
then by default a template located at root_dir/mycontroller/myaction.pl will get used to render your markup. The path and file name will get determined by concatenating the controller-namespace, the action namespace and the extension configuration directive.If you like to employ another template, you may specifiy a different path using the stash variable
template
. See "STASH VARIABLES" below. - wrapper
-
A wrapper is a template that is rendered before your main template and includes your main template at a given point. It "wraps" something around your template. This might be useful if you like to avoid repeating the standard page-setup code for every single page you like to generate.
The default wrapper is named wrapper.pl and is found directoy inside root_dir.
See "Using a wrapper" in TRICKS below.
- include
-
As every template is a perl module, you might like to add other modules using Perl's
use
directive. Well, you may do that at any point inside your template. However, if you repeatedly need the same modules, you could simply add them as a hashref using this configuration option.
STASH VARIABLES
The following stash variables are used by Catalyst::View::ByCode
:
- template
-
If you like to override the default behavior, you can directly specify the template containing your rendering. Simply enter a relative path inside the root directory into this stash variable.
If the template stash variable is left empty, the template used to render your markup will be determined by concatenating the action's namespace and the extension.
- wrapper
-
Overriding the default wrapper is the job of this stash variable. Simply specify a relative path to a wrapping template into this stash variable.
- yield
-
Yielding is a powerful mechanism. The
yield
stash variable contains a hashref that contains a template or an array-ref of templates for certain keys. Every template might be a path name leading to a template or a code-ref able that should be executed as the rendering code.$c->stash->{yield}->{content}
is an entry that is present by default. It contains in execution order the wrapper and the template to get executed.Other keys may be defined and populated in a similar way in order to provide hooks to magic parts of your markup generation.
See "Setting hooks at various places" in TRICKS below.
TRICKS
Using a wrapper
If you construct a website that has lots of pages using the same layout, a wrapper will be your friend. Using the default settings, a simple file wrapper.pl sitting in the root directory of your templates will do the job. As two alternatives you could set the $c->stash->{wrapper}
variable to another path name or specify a wrapper path as a config setting.
# wrapper.pl
html {
head {
# whatever you need
};
body {
# maybe heading, etc.
# include your template here
yield;
};
};
Setting hooks at various places
If you need to sometimes add things at different places, simply mark these positions like:
# in your wrapper:
html {
head {
# whatever you need
# a hook for extra headings
yield 'head_extras';
};
body {
# a hook for something at the very beginning
yield 'begin';
# maybe heading, etc.
# a hook for something after your navigation block
yield 'after_navigation';
# include your template here
yield;
# a hook for something after your content
yield 'end';
};
};
# in an action of your controller:
$c->stash->{yield}->{after_navigation} = 'path/to/foo.pl';
In the example above, some hooks are defined. In a controller, for the hook after_navigation
, a path to a template is filled. This template will get executed at the specified position and its content added before continuing with the wrapper template. If a hook's name is not a part of the stash->{yield}
hashref, it will be ignored. However, an info log entry will be generated.
Avoiding repetitions
Every template is a perl module. It resides in its own package and every thing you are not used to type is mangled into your source code before compilation. It is up to you to use
every other module you like. A simple module could look like this:
package MyMagicPackage;
use strict;
use warnings;
use base qw(Exporter);
use Catalyst::View::ByCode::Renderer ':default';
our @EXPORT = qw(my_sub);
sub my_sub {
# do something...
}
block my_block {
# do something else
};
1;
Using the Renderer class above gives your module everything a template has. You can use every Tag-sub you want.
To use this module in every template you write within an application you simply populate the config of your View:
__PACKAGE__->config(
include => [ qw(MyMagicPackage) ]
);
Including FormFu or FormHandler
If you are using one of the above packages to render forms, generating the markup is done by the libraries. There are a couple of ways to get the generated markup into our code:
# assume stash->{form} contains a form object
# all of these ways will work:
# let the form object render itself
print RAW stash->{form}->render();
# use the form object's stringification
print RAW "${\stash->{form}}";
# inside any tag, let me auto-stringify
div { stash->{form} };
Create your own error page
Using ByCode markup for other things.
Very simple:
# in an action of your controller:
my $html = $c->forward('View::ByCode', render => [qw(list of files)]);
Shortcuts
Some attributes behave in a way that looks intuitive but also generates correct markup. The examples below do not need futher explanation.
# both things generate the same markup:
input(disabled => 1);
input(disabled => 'disabled');
input(checked => 1);
input(disabled => 'checked');
option(selected => 1);
option(selected => 'selected');
textarea(readonly => 1);
textarea(readonly => 'readonly');
# remember that choice() generates a E<lt>selectE<gt> tag...
choice(multiple => 1);
choice(multiple => 'multiple');
METHODS
render
will be called by process to render things. If render is called with extra arguments, they are treated as wrapper, template, etc...
returns the template result
process
fulfill the request (called from Catalyst)
AUTHOR
Wolfgang Kinkeldei, <wolfgang@kinkeldei.de>
LICENSE
This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.