[Jifty-commit] r7548 - in Template-Declare/branches/mixmaster: t

Jifty commits jifty-commit at lists.jifty.org
Tue Oct 13 20:51:11 EDT 2009


Author: theory
Date: Tue Oct 13 20:51:09 2009
New Revision: 7548

Modified:
   Template-Declare/branches/mixmaster/lib/Template/Declare.pm
   Template-Declare/branches/mixmaster/t/dispatch_order.t
   Template-Declare/branches/mixmaster/t/smart_tag_wrapper.t

Log:
Added a whole lot more documentation. There is now a list of terms and their definitions vis-a-vis Template::Declare, and sections covering:

* Basics
* A slightly more advanced example
* Postprocessing
* Inheritance
* Wrappers
  + Template wrappers
  + Tag wrappers
* Class search dispatching
* Mixins (not yet written)

I used the existing exmaples where I could, and wrote new ones for the new sections. I'm not altogether happy with the description of tag wrappers (using `smart_tag_wrapper()`, but I think it gets the general idea across). Otherwise, I think it's pretty good. I'll write the "Mixins" section next.



Modified: Template-Declare/branches/mixmaster/lib/Template/Declare.pm
==============================================================================
--- Template-Declare/branches/mixmaster/lib/Template/Declare.pm	(original)
+++ Template-Declare/branches/mixmaster/lib/Template/Declare.pm	Tue Oct 13 20:51:09 2009
@@ -123,9 +123,83 @@
 
 =head1 USAGE
 
-=head2 Basic usage
+First, some terminology:
 
-A simple HTML example is in the L</SYNOPSIS>. So let's do XUL!
+=over
+
+=item template class
+
+A subclass of Template::Declare in which one or more templates are defined
+using the C<template> keyword, or that inherits templates from a super class.
+
+=item template
+
+Created with the C<template> keyword, a template is a subroutine that uses
+C<tags> to generate output.
+
+=item attribute
+
+An XML element attribute. For example, in C<< <img src="foo.png" /> >>, C<src>
+is an attribute of the C<img> element.
+
+=item tag
+
+A subroutine that generates XML element-style output. Tag subroutines execute
+blocks that generate the output, and can call other tags to generate a
+properly hierarchical structure.
+
+=item tag set
+
+A set of tags defined in a subclass of L<Template::Declare::Tagset> for a
+particular purpose, and which can be imported into a template class. For
+example, L<Template::Declare::Tagset::HTML> defines tags for emitting HTML
+elements.
+
+=item wrapper
+
+A subroutine that wraps the output from a template. Useful for wrapping
+template output in common headers and footers, for example.
+
+=item dispatch class
+
+A template class that has been passed to L<C<init()>|/init> via the
+C<dispatch_to> parameter. When <show|/"show TEMPLATE"> is called, only
+templates defined in or mixed into the dispatch classes will be executed.
+
+=item path
+
+The name specified for a template it is created by the C<template> keyword, or
+when a a template is mixed into a template class.
+
+=item mixin
+
+A template mixed into a template class via C</mix>. Mixed-in templates may be
+mixed in under prefix paths to distinguish them from the templates defined in
+the dispatch classes.
+
+=item package variable
+
+Variables defined when mixing templates into a template class. These variables
+are available only to the mixed-in templates; they are not even accessible
+from the template class in which the templates were defined.
+
+=item helper
+
+A subroutine used in templates to assist in the generation of output, or in
+template classes to assit in the mixing-in of templates. Output helpers
+include C<outs()> for rending text output and C<xml_decl()> for rendering XML
+declarations. Mixin helpers include C<into> for specifying a template class to
+mix into, and C<under> for specifying a path prefix under which to mix
+templates.
+
+=back
+
+=head2 Basics
+
+Like other Perl templating systems, there are two parts to Template::Declare:
+the templates and the code that loads and executes the templates. Unlike other
+template systems, the templates are written in Perl classes. A simple HTML
+example is in the L</SYNOPSIS>. So let's do XUL!
 
     package MyApp::Templates;
     use base 'Template::Declare';
@@ -133,7 +207,11 @@
 
     template main => sub {
         xml_decl { 'xml', version => '1.0' };
-        xml_decl { 'xml-stylesheet', href => "chrome://global/skin/", type => "text/css" };
+        xml_decl {
+            'xml-stylesheet',
+            href => "chrome://global/skin/",
+            type => "text/css"
+        };
         groupbox {
             caption { attr { label => 'Colors' } }
             radiogroup {
@@ -150,11 +228,62 @@
         }
     };
 
+The first thing to do in a template class is to subclass Template::Declare
+itself. This is required so that Template::Declare always knows that it's
+dealing with templates. The second thing is to C<use Template::Declare::Tags>
+to import the set of tag subroutines you need to generate the output you want.
+In this case, we've imported tags to support the creation of XUL. Other tag
+sets incdlude HTML (the default), and RDF.
+
+Templates are created using the C<template> keyword:
+
+    template main => sub { ... };
+
+The first argument is the name of the template, also known as its I<path>. In
+this case, the template's path is C<main> (or C</main>, both are allowed to
+keep both PHP and Mason fans happy). The second argument is an anonymous
+subroutine that uses the tag subs (and any other necessary code) to generate
+the output for the template.
+
+The the tag subs imported into your class take blocks as arguments, while a
+number of helper subs take other arguments. For exmaple, the C<xml_decl>
+helper takes as its first argument the name of the XML declaration to be
+output, and then a hash reference of the attributes of that declaration:
+
+    xml_decl { 'xml', version => '1.0' };
+
+Tag subs are used by simply passing a block to them that generates the output.
+Said block may of course execute other tag subs in order to represent the
+hierarchy required in your output. Here, the C<radiogroup> tag calls the
+C<radio> tag for each of three different colors:
+
+    radiogroup {
+        for my $id ( qw< orange violet yellow > ) {
+            radio {
+                attr {
+                    id    => $id,
+                    label => ucfirst($id),
+                    $id eq 'violet' ? (selected => 'true') : ()
+                }
+            }
+        } # for
+    }
+
+Note the C<attr> sub. This helper function is used to add attributes to the
+element created by the tag in which they appear. In the previous example, the
+the C<id>, C<label>, and C<selected> attributes are added to each C<radio>
+output.
+
+Once you've written your templates, you'll want to execute them. You do so by
+telling Template::Declare what template classes to dispatch to and then asking
+it to show you the output from a template:
+
     package main;
     Template::Declare->init( dispatch_to => ['MyApp::Templates'] );
     print Template::Declare->show( 'main' );
 
-The output:
+The path passed to C<show> can be either C<main> or </main>, as you prefer. In
+either event, the output woud look like this:
 
  <?xml version="1.0"?>
  <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
@@ -171,21 +300,24 @@
 =head2 A slightly more advanced example
 
 In this example, we'll show off how to set attributes on HTML tags, how to
-call other templates and how to declare a I<private> template that can't be
-called directly. We'll also show passing arguments to templates.
+call other templates, and how to declare a I<private> template that can't be
+called directly. We'll also show passing arguments to templates. First, the
+template class:
 
     package MyApp::Templates;
-    use Template::Declare::Tags;
     use base 'Template::Declare';
+    use Template::Declare::Tags;
 
-    private template 'header' => sub {
+    private template 'util/header' => sub {
         head {
             title { 'This is a webpage' };
-            meta  { attr { generator => "This is not your father's frontpage" } }
+            meta  {
+                attr { generator => "This is not your father's frontpage" }
+            }
         }
     };
 
-    private template 'footer' => sub {
+    private template 'util/footer' => sub {
         my $self = shift;
         my $time = shift || gmtime;
 
@@ -200,7 +332,7 @@
         my $user = shift || 'world wide web';
 
         html {
-            show('header');
+            show('util/header');
             body {
                 img { src is 'hello.jpg' }
                 p {
@@ -208,16 +340,78 @@
                     "Hello, $user!"
                 };
             };
-            show('footer');
+            show('util/footer', 'noon');
         }
     };
 
+A few notes on this example:
+
+=over
+
+=item *
+
+Since no parameter was passed to C<use Template::Declare::Tags>, the HTML tags
+are imported by default.
+
+=item *
+
+The C<private> keyword indicates that a template is private. That means that
+it can only be executed by other templates within the template class in which
+it's declared.
+
+=item *
+
+The two private templates have longer paths than we've seen before:
+C<util/header> and C<util/footer>. They must of course be called by their full
+path names. You can put any characters you like into template names, but the
+use of Unix filesystem-style paths is the most common (following on the
+example of L<HTML::Mason|HTML::Mason>).
+
+=item *
+
+The first argument to a template is a class name. This can be useful for
+calling methods defined in the class.
+
+=item *
+
+The C<show> sub executes another template. In this example, the C<simple>
+template calls C<show('util/header')> and C<show('util/footer')> in order to
+execute those private templates in the appropriate places.
+
+=item *
+
+Additional arguments to C<show> are passed on to the template being executed.
+here, C<show('util/footer', 'noon')> is passing "noon" to the C<util/footer>
+template, with the result that the "last generated at" string will display
+"noon" instead of the default C<gmtime>.
+
+=item *
+
+In the same way, note that the C<simple> template expects an additional
+argument, a username.
+
+=item *
+
+In addition to using C<attr> to declare attributes for an element, you can
+use C<is>, as in
+
+    img { src is 'hello.jpg' }
+
+=back
+
+Now for executing the template:
+
     package main;
     use Template::Declare;
     Template::Declare->init( dispatch_to => ['MyApp::Templates'] );
-    print Template::Declare->show( 'simple', 'TD user');
+    print Template::Declare->show( '/simple', 'TD user');
 
-And the output:
+We've told Template::Declare to dispatch to templates defined in our template
+class. And note how an additional argument is passed to C<show()>; that
+argument, "TD user", will be passed to the C<simple> template, where it will
+be used in the C<$user> variable.
+
+The output looks like this:
 
  <html>
   <head>
@@ -231,9 +425,6 @@
   <div id="footer">Page last generated at Thu Sep  3 20:56:14 2009.</div>
  </html>
 
-For more options, especially the "native" XML namespace support, C<is> syntax
-for attributes, and more samples, see L<Template::Declare::Tags>.
-
 =head2 Postprocessing
 
 Sometimes you just want simple syntax for inline elements. The following shows
@@ -254,10 +445,21 @@
     };
 
     template after => sub {
-        h1 { "Welcome to _my_ site. It's _great_!" };
-        h2 { outs_raw "This is _not_ emphasized." };
+        h1  { "Welcome to _my_ site. It's _great_!" };
+        h2  { outs_raw "This is _not_ emphasized." };
+        img { src is '/foo/_bar_baz.png' };
     };
 
+Here we've defined two templates in our template class, with the paths
+C<before> and C<after>. The one new thing to note is the use of the C<outs>
+and C<outs_raw> subs. C<outs> XML-encodes its argument and outputs it. You can
+also just specify a string to be output within a tag call, but if you need to
+mix tags and plain text within a tag call, as in the C<before> template here,
+you'll need to use C<outs> to get things to output as you would expect.
+C<outs_raw> is the same, except that it does no XML encoding.
+
+Now let's have a look at how we use these templates with a post-processor:
+
     package main;
     use Template::Declare;
     Template::Declare->init(
@@ -265,8 +467,8 @@
         postprocessor => \&emphasize,
     );
 
-    print Template::Declare->show( 'before');
-    print Template::Declare->show( 'after');
+    print Template::Declare->show( 'before' );
+    print Template::Declare->show( 'after'  );
 
     sub emphasize {
         my $text = shift;
@@ -274,18 +476,30 @@
         return $text;
     }
 
-And the output:
+As usual, we've told Template::Declare to dispatch to our template class. A
+new parameter to C<init()> is C<postprocessor>, which is a code reference that
+should expect the template output as an argument. It can then transform that
+text however it sees fit before returning it for final output. In this
+example, the C<emphasize> subroutine looks for text that's emphasized using
+_underscores_ and turns them into C<< <em>emphasis</em> >> HTML elements.
+
+We then execute both the C<before> and the C<after> templates with the output
+ening up as:
 
  <h1>Welcome to
   <em>my</em> site. It&#39;s
   <em>great</em>!</h1>
  <h1>Welcome to <em>my</em> site. It&#39;s <em>great</em>!</h1>
  <h2>This is _not_ emphasized.</h2>
+ <img src="/foo/_bar_baz.png" />
+
+The thing to note here is that text passed to C<outs_raw> is not passed
+through the postprocessor, and neither are attribute values.
 
 =head2 Inheritance
 
 Templates are really just methods. You can subclass your template packages to
-override some of those methods. See also L<Jifty::View::Declare::CRUD>.
+override some of those methods:
 
     package MyApp::Templates::GenericItem;
     use Template::Declare::Tags;
@@ -312,6 +526,11 @@
         div { $post->body }
     };
 
+Here we have two template classes; the second, C<MyApp::Templates::BlogPost>,
+inherits from the firt, C<MyApp::Templates::GeniricItem>. Note also that
+C<MyApp::Templates::BlogPost> overrides the C<item> template. So execute these
+templates:
+
     package main;
     use Template::Declare;
 
@@ -324,7 +543,9 @@
     my $post = My::Post->new(title => 'Hello', body => 'first post');
     print Template::Declare->show( 'item', $post );
 
-And the output:
+First we execute the C<list> template in the base class, passing in some
+items, and then we re-C<init()> Template::Declare and execute I<its> C<list>
+template with an appropriate argument. Here's the output:
 
  <div>
   <span>foo</span>
@@ -335,6 +556,222 @@
  <h1>Hello</h1>
  <div>first post</div>
 
+So the override of the C<list> template in the subclass works as expected. For
+another example, see L<Jifty::View::Declare::CRUD>.
+
+=head2 Wrappers
+
+There are two levels of wrappers in Template::Declare: template wrappers and
+smart tag wrappers.
+
+=head3 Template Wrappers
+
+C<create_wrapper> declares a wrapper subroutine that can be called like a tag
+sub, but can optionally take arguments to be passed to the wrapper sub. For
+example, if you wanted to wrap all of the output of a template in the usual
+HTML headers and footers, you can do something like this:
+
+    package MyApp::Templates;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    BEGIN {
+        create_wrapper wrap => sub {
+            my $code = shift;
+            my %params = @_;
+            html {
+                head { title { outs "Hello, $params{user}!"} };
+                body {
+                    $code->();
+                    div { outs 'This is the end, my friend' };
+                };
+            }
+        };
+    }
+
+    template inner => sub {
+        wrap {
+            h1 { outs "Hello, Jesse, s'up?" };
+        } user => 'Jesse';
+    };
+
+Note how the C<wrap> wrapper function is available for calling after it has
+been declared in a C<BEGIN> block. Also note how you can pass arguments to the
+function after the closing brace (you don't need a comma there!).
+
+The output from the "inner" template will look something like this:
+
+ <html>
+  <head>
+   <title>Hello, Jesse!</title>
+  </head>
+  <body>
+   <h1>Hello, Jesse, s&#39;up?</h1>
+   <div>This is the end, my friend</div>
+  </body>
+ </html>
+
+=head3 Tag Wrappers
+
+Tag wrappers are similar to template wrappers, but mainly function as syntax
+sugar for creating subroutines that behave just like tags but are allowed to
+contain arbitrary Perl code and to dispatch to other tag. To create one,
+simply create a named subroutine with the prototype C<(&)> so that its
+interface is the same as tags. Within it, use
+L<C<smart_tag_wrapper>|Template::Declare::Tags/"smart_tag_wrapper"> to do the
+actual execution, like so:
+
+    package My::Template;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    sub myform (&) {
+        my $code = shift;
+
+        smart_tag_wrapper {
+            my %params = @_; # set using 'with'
+            form {
+                attr { %{ $params{attr} } };
+                $code->();
+                input { attr { type => 'submit', value => $params{value} } };
+            };
+        };
+    }
+
+    template edit_prefs => sub {
+        with(
+            attr  => { id => 'edit_prefs', action => 'edit.html' },
+            value => 'Save'
+        ), myform {
+            label { 'Time Zone' };
+            input { type is 'text'; name is 'tz' };
+        };
+    };
+
+Note in the C<edit_prefs> template that we've used
+L<C<with>|Template::Declare::Tags/"with"> to set up parameters to be passed to
+the smart wrapper. C<smart_tag_wrapper()> is the device that allows you to
+receive those parameters, and also handles the magic of making sure that the
+tags you execute within it are properly output. Here we've used C<myform>
+similarly to C<form>, only C<myform> does something different with the
+C<with()> arguments and outputs a submit element.
+
+Executing this template:
+
+    Template::Declare->init( dispatch_to => ['My::Template'] );
+    print Template::Declare->show('edit_prefs');
+
+Yields this output:
+
+ <form action="edit.html" id="edit_prefs">
+  <label>Time Zone</label>
+  <input type="text" name="tz" />
+  <input type="submit" value="Save" />
+ </form>
+
+=head2 Class Search Dispatching
+
+The classes passed via the C<dispatch_to> parameter to C<init()> specify all
+of the templates that can be executed by subsequent calls to C<show()>.
+Template searches through these classes in order to find those templates. Thus
+it can be useful, when you're creating your template classes and determining
+which to use for particular class to C<show()>, to have templates that
+override other templates. This is similar to how an operating system will
+search all the paths in the C<$PATH> environment variable for a program to
+run, and to Mason component roots or Template::Toolkit's C<INCLUDE_PATH>
+parameter.
+
+For example, say you have this template class that defines a template that
+you'll use for displaying images on your Web site.
+
+    package MyApp::UI::Standard;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    template image => sub {
+        my ($self, $src, $title) = @_;
+        img {
+            src is $src;
+            title is $title;
+        };
+    };
+
+As usual, you can use it like so:
+
+    my @template_classes = 'MyApp::UI::Standard';
+    Template::Declare->init( dispatch_to => \@template_classes );
+    print Template::Declare->show('image', 'foo.png', 'Foo');
+
+And the output will be:
+
+ <div class="std">
+  <img src="foo.png" title="Foo" />
+  <p class="caption"></p>
+ </div>
+
+But say that in some sections of your site you need to have a more formal
+treatment of your photos. Maybe you publish photos from a wire service and
+need to provide an appropriate credit. You might write the template class like
+so:
+
+    package MyApp::UI::Formal;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    template image => sub {
+        my ($self, $src, $title, $credit, $caption) = @_;
+        div {
+            class is 'formal';
+            img {
+                src is $src;
+                title is $title;
+            };
+            p {
+                class is 'credit';
+                outs "Photo by $credit";
+            };
+            p {
+                class is 'caption';
+                outs $caption;
+            };
+        };
+    };
+
+
+This, too, will work as expected, but the useful bit that comes in when you're
+mixing and matching template classes to pass to C<dispatch_to> before
+rendering a page. Maybe you always pass have MyApp::UI::Standard to
+C<dispatch_to> because it has all of your standard formatting templates.
+But when the code realizes that a particular page needs the more formal
+treatment, you can prepend the formal class to the list:
+
+    unshift @template_classes, 'MyApp::UI::Formal';
+    print Template::Declare->show(
+        'image',
+        'ap.png',
+        'AP Photo',
+        'Clark Kent',
+        'Big news'
+    );
+    shift @template_classes;
+
+In this way, made the formal C<image> template will be found first, yielding
+this output:
+
+ <div class="formal">
+  <img src="ap.png" title="AP Photo" />
+  <p class="credit">Photo by Clark Kent</p>
+  <p class="caption">Big news</p>
+ </div>
+
+At the end, we've shifted the formal template class off the C<dispatch_to>
+list in order to restore the template classes the default configuration, ready
+for the next request.
+
+=head2 Mixins
+
+
+
 =head1 METHODS
 
 =head2 init

Modified: Template-Declare/branches/mixmaster/t/dispatch_order.t
==============================================================================
--- Template-Declare/branches/mixmaster/t/dispatch_order.t	(original)
+++ Template-Declare/branches/mixmaster/t/dispatch_order.t	Tue Oct 13 20:51:09 2009
@@ -27,7 +27,8 @@
 
 ##############################################################################
 package main;
-use Test::More tests => 22;
+use Test::More tests => 24;
+#use Test::More 'no_plan';
 
 # Check template resolution with the deprecated `roots` parameterx.
 ok !Template::Declare->init( roots => ['Wifty::Foo', 'Wifty::Bar'] ),
@@ -193,3 +194,78 @@
 
 is +Template::Declare->show('hello'), 'hello from Bip',
     'Sifty::Bip should now have precedence';
+
+##############################################################################
+# Doc example.
+    package MyApp::UI::Standard;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    template image => sub {
+        my ($self, $src, $title) = @_;
+        img {
+            src is $src;
+            title is $title;
+        };
+    };
+
+    package MyApp::UI::Standard;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    template image => sub {
+        my ($self, $src, $title, $caption) = @_;
+        div {
+            class is 'std';
+            img {
+                src is $src;
+                title is $title;
+            };
+            p {
+                class is 'caption';
+                outs $caption;
+            };
+        };
+    };
+
+##############################################################################
+    package MyApp::UI::Formal;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    template image => sub {
+        my ($self, $src, $title, $credit, $caption) = @_;
+        div {
+            class is 'formal';
+            img {
+                src is $src;
+                title is $title;
+            };
+            p {
+                class is 'credit';
+                outs "Photo by $credit";
+            };
+            p {
+                class is 'caption';
+                outs $caption;
+            };
+        };
+    };
+
+##############################################################################
+package main;
+    my @template_classes = 'MyApp::UI::Standard';
+    Template::Declare->init( dispatch_to => \@template_classes );
+    is +Template::Declare->show('image', 'foo.png', 'Foo'), q{
+<div class="std">
+ <img src="foo.png" title="Foo" />
+ <p class="caption"></p>
+</div>}, 'Should get standard image output';
+
+    unshift @template_classes, 'MyApp::UI::Formal';
+    is +Template::Declare->show('image', 'ap.png', 'AP Photo', 'Clark Kent', 'Big news'), q{
+<div class="formal">
+ <img src="ap.png" title="AP Photo" />
+ <p class="credit">Photo by Clark Kent</p>
+ <p class="caption">Big news</p>
+</div>}, 'Should get formal image output';

Modified: Template-Declare/branches/mixmaster/t/smart_tag_wrapper.t
==============================================================================
--- Template-Declare/branches/mixmaster/t/smart_tag_wrapper.t	(original)
+++ Template-Declare/branches/mixmaster/t/smart_tag_wrapper.t	Tue Oct 13 20:51:09 2009
@@ -33,7 +33,7 @@
 use Template::Declare::Tags;
 Template::Declare->init( dispatch_to => ['Wifty::UI'] );
 
-use Test::More tests => 2;
+use Test::More tests => 4;
 require "t/utils.pl";
 
 my $simple = show('simple');
@@ -50,3 +50,47 @@
       . "\nSTART \nsecond\nEND\n",      #
     "got correct output for simple"
 );
+
+##############################################################################
+# Documentation example.
+    package My::Template;
+    use Template::Declare::Tags;
+    use base 'Template::Declare';
+
+    sub myform (&) {
+        my $code = shift;
+
+        smart_tag_wrapper {
+            my %params = @_; # set using 'with'
+            form {
+                attr { %{ $params{attr} } };
+                $code->();
+                input { attr { type => 'submit', value => $params{value} } };
+            };
+        };
+    }
+
+    template edit_prefs => sub {
+        with(
+            attr  => { id => 'edit_prefs', action => 'edit.html' },
+            value => 'Save'
+        ), myform {
+            label { 'Time Zone' };
+            input { type is 'text'; name is 'tz' };
+        };
+    };
+
+    package main;
+    Template::Declare->init( dispatch_to => ['My::Template'] );
+
+ok my $output = Template::Declare->show('edit_prefs'), 'Get edit_prefs output';
+is(
+    $output,
+    qq{
+
+<form action="edit.html" id="edit_prefs">
+ <label>Time Zone</label>
+ <input type="text" name="tz" />
+ <input type="submit" value="Save" />
+</form>}, "got correct output for simple"
+);


More information about the Jifty-commit mailing list