Recall::Template - new module proposal
Recall::Template - new module proposal
am 14.03.2007 20:21:16 von arbingersys
Hi, I've created a module template system that works, from what I can
tell, in a novel manner. I'd love to hear any comments you might have.
Recall::Template works using what I call a "reverse callback''
approach. A "callback'' template system (i.e. Mason, Apache::ASP)
generally includes template markup and code in the same file. The
template "calls'' out to the code where needed. Recall::Template works
in reverse. Rather than inserting code inside the template, the
template remains separate, but broken into sections. The sections are
then called from within the code at the appropriate times.
A template section is merely a file on disk (or a "marked'' section in
a single file). For instance, a section called 'prodrow' would
actually be 'prodrow.html' in a template directory, and might look
like
TEMPLATE_var_product |
TEMPLATE_var_description |
TEMPLATE_var_price |
If called in every iteration of a loop, you could build a set of
product data in HTML dynamically.
The Recall::Template::render() method is used to "call'' out to the
template sections. You create a hash of name/value pairs that
represent the template tags you wish to replace, and pass it along
with the template section, i.e.
$h{'TEMPLATE_var_product'} = 'bunny slippers';
$rt->render('prodrow', %h);
render() returns the template with the template tags replaced by the
key/value pairs in %h.
For more details, you can go to
http://www.arbingersys.com/boot/recall-template/article.html
You can download a demo from there that contains the module code, if
you'd like to take a closer look.
Thanks, James
Re: Recall::Template - new module proposal
am 14.03.2007 20:50:34 von Uri Guttman
>>>>> "ac" == arbingersys@gmail com writes:
ac> Recall::Template works using what I call a "reverse callback''
ac> approach. A "callback'' template system (i.e. Mason, Apache::ASP)
ac> generally includes template markup and code in the same file. The
ac> template "calls'' out to the code where needed. Recall::Template works
ac> in reverse. Rather than inserting code inside the template, the
ac> template remains separate, but broken into sections. The sections are
ac> then called from within the code at the appropriate times.
ac> A template section is merely a file on disk (or a "marked'' section in
ac> a single file). For instance, a section called 'prodrow' would
ac> actually be 'prodrow.html' in a template directory, and might look
ac> like
ac>
ac> TEMPLATE_var_product |
ac> TEMPLATE_var_description |
ac> TEMPLATE_var_price |
ac>
ac> If called in every iteration of a loop, you could build a set of
ac> product data in HTML dynamically.
ac> The Recall::Template::render() method is used to "call'' out to the
ac> template sections. You create a hash of name/value pairs that
ac> represent the template tags you wish to replace, and pass it along
ac> with the template section, i.e.
this sounds very similar to Template::Simple which is up on cpan. it
only has 4 markups and can do all the things you want in your module.
and you don't even need any code to handle loops as that is driven by
the data. so it is even simpler to use than your module for that.
ac> $h{'TEMPLATE_var_product'} = 'bunny slippers';
ac> $rt->render('prodrow', %h);
it even has the same basic api.
ac> render() returns the template with the template tags replaced by the
ac> key/value pairs in %h.
ac> For more details, you can go to
ac> http://www.arbingersys.com/boot/recall-template/article.html
i don't see nested sections, include files and various options such as
markup delimiters. again, check out Template::Simple and you may find it
is all you need and you don't need to add another templater to cpan. :)
uri
--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org
Re: Recall::Template - new module proposal
am 14.03.2007 22:29:48 von arbingersys
> this sounds very similar to Template::Simple which is up on cpan. it
> only has 4 markups and can do all the things you want in your module.
Well, at least the render() methods seem similar.
How about conditionals? For instance, because logic only exists in the
code file with Recall::Template, if during the loop you wanted to hide
the price of items over a certain amount (say $1000) with a "Please
call" message, it's quite simple:
for (@prods) {
my @pa = split(/,/, $_);
if ($pa[0] > 1000) {
$h{'TEMPLATE_var_price'} = 'Please call';
}
else
$h{'TEMPLATE_var_price'} = $pa[0];
...
$rt->render('prodrow', %h);
}
I realize that with Template::Simple would have to do this when you
construct the data, but that seems to imply generating the data in a
loop to handle the conditional, then passing this to the
Template::Simple::render() method, which would ultimately iterate over
it again against the template. Perhaps I'm wrong.
> and you don't even need any code to handle loops as that is driven by
> the data. so it is even simpler to use than your module for that.
Well, I guess that all depends on whether you consider creating a loop
a more difficult programming task than nesting hashes and arrays (and
arrays of hashes). :)
Since you brought it up, however, there is no reason that
Recall::Template, being an object, couldn't be extended to be passed a
complex data structure and iterate over it automatically.
I also anticipate that someone might even want to store the template
sections in a database, and call them from there. Objects and
extension, it's a good thing they go together so well.
> i don't see nested sections,
I hope I've been clear that the idea here is to keep all logic in the
code. If you have nested sections, i.e.
GROUP 1
DETAIL1 DETAIL2
GROUP 2
DETAIL1 DETAIL2
....
GROUP N
DETAIL1 DETAIL2
this is still done via a programming construct, perhaps as a nested
loop:
for (@groups) {
$rt->render('group', %group_data);
for(@details) {
$rt->render('details', %detail_data);
}
}
> include files and various options such as
> markup delimiters.
I'm not sure what you mean by "markup delimiters", but as far as
include files go, that's really all Recall::Template does -- include
template files at appropriate times in the code. As for including
additional logic (like modules), again, I think this belongs in the
arena of the programming language, rather than anywhere in the
template.
I appreciate your comments. At this point I still think
Recall::Template has enough to offer on its own to be a module, though.
Re: Recall::Template - new module proposal
am 15.03.2007 02:29:20 von Uri Guttman
>>>>> "ac" == arbingersys@gmail com writes:
>> this sounds very similar to Template::Simple which is up on cpan. it
>> only has 4 markups and can do all the things you want in your module.
ac> Well, at least the render() methods seem similar.
ac> How about conditionals? For instance, because logic only exists in the
ac> code file with Recall::Template, if during the loop you wanted to hide
ac> the price of items over a certain amount (say $1000) with a "Please
ac> call" message, it's quite simple:
ac> for (@prods) {
ac> my @pa = split(/,/, $_);
ac> if ($pa[0] > 1000) {
ac> $h{'TEMPLATE_var_price'} = 'Please call';
ac> }
ac> else
ac> $h{'TEMPLATE_var_price'} = $pa[0];
ac> ...
ac> $rt->render('prodrow', %h);
ac> }
ac> I realize that with Template::Simple would have to do this when you
ac> construct the data, but that seems to imply generating the data in a
ac> loop to handle the conditional, then passing this to the
ac> Template::Simple::render() method, which would ultimately iterate over
ac> it again against the template. Perhaps I'm wrong.
that is the general idea. but you are doing the same loop and
conditional but in a different place. doing it at one time to build a
data tree seems cleaner to me. and you can easily break up the tree
building into subs similarly to how you do it. conditionals are meant to
be in code and we agree on that.
>> and you don't even need any code to handle loops as that is driven by
>> the data. so it is even simpler to use than your module for that.
ac> Well, I guess that all depends on whether you consider creating a loop
ac> a more difficult programming task than nesting hashes and arrays (and
ac> arrays of hashes). :)
well, they have to be created somewhere. most sites have plenty of
nesting of structures. and i recently created CMS::Simple (not published
yet) which uses template::simple and makes it easier to share data and
templates across many pages. it also has content filtering (for links,
markup, escaping, etc.)
ac> Since you brought it up, however, there is no reason that
ac> Recall::Template, being an object, couldn't be extended to be passed a
ac> complex data structure and iterate over it automatically.
sure, steal my ideas! i will sic the riaa on you! :)
ac> I also anticipate that someone might even want to store the template
ac> sections in a database, and call them from there. Objects and
ac> extension, it's a good thing they go together so well.
that can be done with an include mechanism that searches a db or a dir
tree.
>> i don't see nested sections,
ac> I hope I've been clear that the idea here is to keep all logic in the
ac> code. If you have nested sections, i.e.
ac> GROUP 1
ac> DETAIL1 DETAIL2
ac> GROUP 2
ac> DETAIL1 DETAIL2
ac> ...
ac> GROUP N
ac> DETAIL1 DETAIL2
ac> this is still done via a programming construct, perhaps as a nested
ac> loop:
ac> for (@groups) {
ac> $rt->render('group', %group_data);
where is %group_data being set? what is in $_ in the loop? could be just
quickie code and the hash is in the array of hashes.
>> include files and various options such as
>> markup delimiters.
ac> I'm not sure what you mean by "markup delimiters", but as far as
ac> include files go, that's really all Recall::Template does -- include
ac> template files at appropriate times in the code. As for including
ac> additional logic (like modules), again, I think this belongs in the
ac> arena of the programming language, rather than anywhere in the
ac> template.
when you render a value you can't just replace any occurance of the name
token because it might be in the real text. so most/all templaters use a
delimiter to demark which are template tokens and
markup. template::simple does [%NAME%] which is a common style. but the
[% and %] can be changed with options to the constructor.
ac> I appreciate your comments. At this point I still think
ac> Recall::Template has enough to offer on its own to be a module, though.
to each their own. i did mine after years of quickie hacks which grew
into a neat 37 line unpublished module i gave lightning talks about this
past summer at yapc and oscon. someone from england liked it so much he
paid me (very little :) to clean it up for cpan (pod, options, tests,
etc.). the rule is everyone builds a templater in their career. i have
been noodling with them for decades until i came up with the style that
suited my needs and what i see as general templating needs without the
bloat of the large templaters.
uri
--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org
Re: Recall::Template - new module proposal
am 15.03.2007 16:45:58 von arbingersys
> that is the general idea. but you are doing the same loop and
> conditional but in a different place. doing it at one time to build a
> data tree seems cleaner to me. and you can easily break up the tree
> building into subs similarly to how you do it. conditionals are meant to
> be in code and we agree on that.
But, doesn't the data get iterated over twice, then? Once when you
build the data tree, and again when it gets rendered against the
template to build the rows between [%START%] and [%END%]? In my loop,
when you call render() the only thing happening is a s/// against the
template, then output. There is no further looping. So there is only
one iteration over the data.
> well, they have to be created somewhere. most sites have plenty of
> nesting of structures. and i recently created CMS::Simple (not published
> yet) which uses template::simple and makes it easier to share data and
> templates across many pages. it also has content filtering (for links,
> markup, escaping, etc.)
Yeah, I suppose that's true. I just know that in my own programming
odyssey, I knew what a loop was long before I understood (or even
wanted to know) what a array of a hash of an array was. I want
Recall::Template to behave as simply, or as complexly as the
programmer is ready for.
> sure, steal my ideas! i will sic the riaa on you! :)
Not planning on any infringement, so you don't have to worry.
Template::Simple does this well enough already.
I think Template::Simple is an interesting module with definite
usefulness. However, I think the motives behind it and
Recall::Template are different. Template::Simple seems driven by
building elegant data structures to be converted into templates with a
single call. Recall::Template is designed to keep everything but the
simplest logic (variables) out of the templates. This is why the
templates are broken into sections. I don't even want the templates to
be able to include other templates, or tell the code where to start
and end iterative output. I want everything to be decided by the code
-- e.g. which templates to include or exclude, in what order, and at
what time, what formatting to use for row output, etc.
> ac> for (@groups) {
>
> ac> $rt->render('group', %group_data);
>
> where is %group_data being set? what is in $_ in the loop? could be just
> quickie code and the hash is in the array of hashes.
Well, I obviously excluded a lot of details. I was just focusing on
the fact that nesting would work as it does normally in code, either
with nested loops, or recursion.
Let's say you query a table in SQL for
select distinct category from products order by category
and you store 'category' in @groups. Then, you will hit the database
(or maybe an in-memory data structure) again for the products in that
group during the loop, e.g.
for (@groups) {
my %h;
$h{'[%group%]'} = $_;
print $rt->render('groups', %h);
my @details = get_details($_); # Hit DB for products/details in
category
for (@details) {
print $rt->render('details', %{$_});
}
}
If the 'groups' template section looked like
[%group%] |
and the 'details' template section looked like
[%product%] |
[%inventory%] |
[%price%] |
you've just done simple nested output. You could even add a variable
to tally total product inventory at the end, in a template section
named 'tally', for instance.
> when you render a value you can't just replace any occurance of the name
> token because it might be in the real text. so most/all templaters use a
> delimiter to demark which are template tokens and
> markup. template::simple does [%NAME%] which is a common style. but the
> [% and %] can be changed with options to the constructor.
Gotcha. This is left entirely to the discretion of the programmer. As
you can see in my [reworking of the] example above, you can use
whatever tag pattern you want. Recall::Template doesn't care, and
neither do I.
> to each their own. i did mine after years of quickie hacks which grew
> into a neat 37 line unpublished module i gave lightning talks about this
> past summer at yapc and oscon. someone from england liked it so much he
> paid me (very little :) to clean it up for cpan (pod, options, tests,
> etc.). the rule is everyone builds a templater in their career.
I guess this is mine. :)
>i have been noodling with them for decades until i came up with the style that
> suited my needs and what i see as general templating needs without the
> bloat of the large templaters.
Your comments have been helpful.
Re: Recall::Template - new module proposal
am 15.03.2007 17:38:42 von Uri Guttman
>>>>> "ac" == arbingersys@gmail com writes:
>> that is the general idea. but you are doing the same loop and
>> conditional but in a different place. doing it at one time to build a
>> data tree seems cleaner to me. and you can easily break up the tree
>> building into subs similarly to how you do it. conditionals are meant to
>> be in code and we agree on that.
ac> But, doesn't the data get iterated over twice, then? Once when you
ac> build the data tree, and again when it gets rendered against the
ac> template to build the rows between [%START%] and [%END%]? In my loop,
ac> when you call render() the only thing happening is a s/// against the
ac> template, then output. There is no further looping. So there is only
ac> one iteration over the data.
you still have to build a data tree sometime. and if it is nested you
deal with that too in both places.
>> well, they have to be created somewhere. most sites have plenty of
>> nesting of structures. and i recently created CMS::Simple (not published
>> yet) which uses template::simple and makes it easier to share data and
>> templates across many pages. it also has content filtering (for links,
>> markup, escaping, etc.)
ac> I think Template::Simple is an interesting module with definite
ac> usefulness. However, I think the motives behind it and
ac> Recall::Template are different. Template::Simple seems driven by
ac> building elegant data structures to be converted into templates
ac> with a single call. Recall::Template is designed to keep
ac> everything but the simplest logic (variables) out of the
ac> templates. This is why the templates are broken into sections. I
ac> don't even want the templates to be able to include other
ac> templates, or tell the code where to start and end iterative
ac> output. I want everything to be decided by the code -- e.g. which
ac> templates to include or exclude, in what order, and at what time,
ac> what formatting to use for row output, etc.
that makes sense for some applications. i built CMS::Simple to much of
that on top of template::simple. it loads, parses and filters contents,
places it in data trees and then renders and publishes output.
ac> Let's say you query a table in SQL for
ac> select distinct category from products order by category
ac> and you store 'category' in @groups. Then, you will hit the database
ac> (or maybe an in-memory data structure) again for the products in that
ac> group during the loop, e.g.
ac> for (@groups) {
ac> my %h;
ac> $h{'[%group%]'} = $_;
ac> print $rt->render('groups', %h);
ac> my @details = get_details($_); # Hit DB for products/details in
ac> category
ac> for (@details) {
ac> print $rt->render('details', %{$_});
ac> }
ac> }
ac> If the 'groups' template section looked like
ac> [%group%] |
so it seems you look in the hash for its keys and then replace those
with their values. am i correct that only things found in the hash then
are replaced? if i have a [%foo%] in the template but not in the hash it
is unchanged?
ac>
ac> [%product%] |
ac> [%inventory%] |
ac> [%price%] |
ac>
ac> you've just done simple nested output. You could even add a variable
ac> to tally total product inventory at the end, in a template section
ac> named 'tally', for instance.
that is a basic loop expansion which is good. how would you handle it if
a had its own list or hash to render?
>> when you render a value you can't just replace any occurance of the name
>> token because it might be in the real text. so most/all templaters use a
>> delimiter to demark which are template tokens and
>> markup. template::simple does [%NAME%] which is a common style. but the
>> [% and %] can be changed with options to the constructor.
ac> Gotcha. This is left entirely to the discretion of the programmer. As
ac> you can see in my [reworking of the] example above, you can use
ac> whatever tag pattern you want. Recall::Template doesn't care, and
ac> neither do I.
this reinforces my understanding that it is the keys in the hash that
drive the expansion, not markup in the template. that is reversed from
how template::simple (and most other templaters) do it as they parse out
template markup and replace all of it.
uri
--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org
Re: Recall::Template - new module proposalam 15.03.2007 18:16:11 von arbingersys
> you still have to build a data tree sometime. and if it is nested you
> deal with that too in both places.
Yeah, like in my example of the nested loops. Sometimes you just can't
escape multiple passes over data.
> that makes sense for some applications. i built CMS::Simple to much of
> that on top of template::simple. it loads, parses and filters contents,
> places it in data trees and then renders and publishes output.
This is my feeling as well. Recall::Template isn't going to fit every
need, but probably some.
> so it seems you look in the hash for its keys and then replace those
> with their values. am i correct that only things found in the hash then
> are replaced? if i have a [%foo%] in the template but not in the hash it
> is unchanged?
Correct. However, render() could possibly strip out any tag not known
by the code. I don't know if would be good or whether it would just
mask a communication problem between designers and developers in a
given team.
>
> ac>
> ac> [%product%] |
> ac> [%inventory%] |
> ac> [%price%] |
> ac>
>
> ac> you've just done simple nested output. You could even add a variable
> ac> to tally total product inventory at the end, in a template section
> ac> named 'tally', for instance.
>
> that is a basic loop expansion which is good. how would you handle it if
> a had its own list or hash to render?
This would have to be handled in the design of the application. If the
third | above required a list of prices or something, it would have
to be its own "section", to be handled by a routine in the code. If
you had to do some arbitrary amount of nesting, recursion could be
used with logic to call appropriate template sections. But ultimately,
the code must handle it.
> ac> Gotcha. This is left entirely to the discretion of the programmer. As
> ac> you can see in my [reworking of the] example above, you can use
> ac> whatever tag pattern you want. Recall::Template doesn't care, and
> ac> neither do I.
>
> this reinforces my understanding that it is the keys in the hash that
> drive the expansion, not markup in the template. that is reversed from
> how template::simple (and most other templaters) do it as they parse out
> template markup and replace all of it.
Right. That's why I'm thinking that Recall::Template ("[Re]verse
[Call]back::Template") would actually be a pretty good name for the
module. I built this and came up with the name after reading Perrin
Harkin's article "Choosing a Templating System" --
http://perl.apache.org/docs/tutorials/tmpl/comparison/compar ison.html.
Re: Recall::Template - new module proposalam 15.03.2007 21:27:34 von Uri Guttman
>>>>> "ac" == arbingersys@gmail com writes:
ac> Correct. However, render() could possibly strip out any tag not known
ac> by the code. I don't know if would be good or whether it would just
ac> mask a communication problem between designers and developers in a
ac> given team.
but without a known tag style (the delimiters i mentioned) how can you
remove all tags unless your hash had them all?
>> that is a basic loop expansion which is good. how would you handle it if
>> a had its own list or hash to render?
ac> This would have to be handled in the design of the application. If the
ac> third | above required a list of prices or something, it would have
ac> to be its own "section", to be handled by a routine in the code. If
ac> you had to do some arbitrary amount of nesting, recursion could be
ac> used with logic to call appropriate template sections. But ultimately,
ac> the code must handle it.
i agree with the code handling it. i just feel that when you get more
complex nesting, making each section need its own call to rendering
seems like more work. but that is my opinion.
>> this reinforces my understanding that it is the keys in the hash that
>> drive the expansion, not markup in the template. that is reversed from
>> how template::simple (and most other templaters) do it as they parse out
>> template markup and replace all of it.
ac> Right. That's why I'm thinking that Recall::Template ("[Re]verse
ac> [Call]back::Template") would actually be a pretty good name for the
ac> module. I built this and came up with the name after reading Perrin
ac> Harkin's article "Choosing a Templating System" --
ac> http://perl.apache.org/docs/tutorials/tmpl/comparison/compar ison.html
just looked at that and he misses out on one template variant style
which is what template::simple does - no logic in the template at
all. he shows logic in embedded perl or a minilang. you and i agree on
this at least, logic should be outside the template itself. :)
uri
--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org
Re: Recall::Template - new module proposalam 16.03.2007 14:19:18 von arbingersys
> but without a known tag style (the delimiters i mentioned) how can you
> remove all tags unless your hash had them all?
There would have to be a known tag style, obviously. But whether the
tag looks like /TEMPLATE_var_\w+/ or /\[\%\s*\w+\s*\%\]/ doesn't
really matter to Recall::Template. It's up to the developer to define
what the tag style will be for his application.
> i agree with the code handling it. i just feel that when you get more
> complex nesting, making each section need its own call to rendering
> seems like more work. but that is my opinion.
In some cases, maybe. But it's no more work than the typical
"callback" approach, (Mason, Apache::ASP, ASP/VBScript, etc), and this
has proven to be a rather popular idiom.
> just looked at that and he misses out on one template variant style
> which is what template::simple does - no logic in the template at
> all. he shows logic in embedded perl or a minilang. you and i agree on
> this at least, logic should be outside the template itself. :)
I think Perrin's article was dealing with the most common approaches
to templating, which appear to be pipeline or callback.
Template::Simple and Recall::Template (or Template::Recall, as has
been suggested as a better module name), come at the problem a little
differently.
James
Re: Recall::Template - new module proposalam 16.03.2007 15:57:42 von Uri Guttman
>>>>> "ac" == arbingersys@gmail com writes:
ac> I think Perrin's article was dealing with the most common approaches
ac> to templating, which appear to be pipeline or callback.
ac> Template::Simple and Recall::Template (or Template::Recall, as has
ac> been suggested as a better module name), come at the problem a little
ac> differently.
which is why i will probably email him about that. the article footnote
claims it was updated only last week. now template::simple and your
module aren't nearly full web apps or apache integrated as the ones he
describes but we both think that isolation of template and logic are
big. even the bloated templaters (mason, TT) claim they can do that but
no one uses them that way. ours enforce that rule hard. :)
uri
--
Uri Guttman ------ uri@stemsystems.com -------- http://www.stemsystems.com
--Perl Consulting, Stem Development, Systems Architecture, Design and Coding-
Search or Offer Perl Jobs ---------------------------- http://jobs.perl.org
Re: Recall::Template - new module proposalam 16.03.2007 17:35:04 von arbingersys
> ours enforce that rule hard. :)
Not to mention pretty easily.
| | |