NAME ==== Template::Anti - The anti-template templating tool [![Build Status](https://travis-ci.org/zostay/Template-Anti.svg)](https://travis-ci.org/zostay/Template-Anti) SYNOPSIS ======== use Template::Anti; class MyApp::View { has $.token; # custom attributes, if you want them method hello($dom, :$title, :$welcome-message) is anti-template(:source) { $dom('title', :one).content($title); $dom('.welcome-message', :one).content($welcome-message); $dom('input#api_token', :one).val($!token); } # Or put the logic into people.html: # method sith($dom, $_) is anti-template(:source) { ... # yada signals embedded logic } } # The source files are just plain old HTML. Nothing special about them. # Construct a library of templates my $ta = Template::Anti::Library.new( path => , views => { main => MyApp::View.new(:token), }, ); # Use .process() to render the templates with input say $ta.process('main.hello', :title, :welcome-message); say $ta.process('main.site', %sith-lords); DESCRIPTION =========== It is a generally accepted principle that you should avoid mixing code with presentation. Yet, whenever a software engineer needs to render some custom HTML or text or something, the first tool she pulls out of her toolbelt is a templating engine, which does that very thing. Rather than building a file that is either some nice programming language like Perl 6 or a decent document language like HTML5, she ends up with some evil hybrid that: * Confuses tools made to read either of those languages, * Adds training overhead and burdensome syntax for your front-end developers to work around, * Generally uglifies your code making your templates harder to read as they mix two or three language syntaxes together inline. This "templating" engine allows you to put an end to that. This module, [Template::Anti](Template::Anti), is the anti-templating engine. This tool splits your presentation from your code in a way that is familiar to many front-end developers, using a select-and-modify methodology similar to jQuery. It borrows a few ideas from tools like [Template::Pure](https://metacpan.org/pod/HTML::Zoom), [Template::Semantic](https://metacpan.org/pod/Template::Semantic), and [pure.js](https://beebole.com/pure/). Template Source =============== To build a template you need two components: * You need some HTML or XML source to work with. * You need a block of code to execute against the parsed representation of that source. From there, Template::Anti provides two different ways to process your templates, inline (a.k.a. embedded) and out-of-line. Let's consider the latter first. Out-of-Line Processing ---------------------- This is the "pure" use-case that completely separates your template from your view processor, which should maximize reuse for most applications. To do this, you create an HTML original and then a method to apply a set of rules and modifications to it, like this: class MyTemplate { method index($dom, :$title) is anti-template(:source) { $dom('title')».content($title); } } The `is anti-template` trait associates a source file name with your method. When it comes time to process the template, this will be found within the search paths setup in [/Template::Anti::Library](/Template::Anti::Library). The trait takes two optional named parameters: * The `source` parameter will name the original file to associate with this processing method. * The `format` parameter names the format to use when parsing the original. It defaults to "html" but may be set to "xml" instead. The first positional argument to the method will always be the DOM object parsed from the original source file. The remaining parameters are whatever you want to pass to your views. Inline Processing ----------------- At times, it may be convenient to keep your processing code within the original file. In this case, your original file will include zero or more `` blocks with the type attribute set to "application/anti+perl6". For example, here is a simple template you might have in your assets directory: Hello

Hello

The `type="application/anti+perl6"` attirbute is required. The `data-dom` and `data-stash` attributes are optional. These attributes the names of the template variables the engine will provide to the block. The `data-dom` names the variable to use for the DOM representation within the script-tag. (The DOM will be provided without these script-tags present.) The default `data-dom` name is `"$dom"`. The `data-stash` attribute names the variable to use for the remaining captured arguments. The default `data-stash` name is `"$_"`. It is up to the code within the script-tag to handle any further argument handling. This works whether the template is HTML or XML. However, when templating with XML, it is also recommended that you wrap your code in a `` section to avoid problems with greater than signs (">"), less than signs ("<"), and ampersands in your code confusing the parser. Finally, the processing method for this template looks like the following: class MyTemplate { method index(|) is anti-template(:source) { ... } } The yada (...) signals that this the method logic will be filled in by scripts embedded within the original. The capture bar (|) is shown here because the method itself is just a placeholder and won't actually be called or used. The arguments you set here do not matter at all, so it is recommended you leave them blank in whichever way you prefer. Template::Anti::Library ======================= This class provides tools for locating original source files for parsing and for grouping your processing methods together. You do not have to use it. If you only need a single template or want to provide your own mechanism for locating and reading the files and calling the templates, see [/One-off Templates](/One-off Templates) for details. method path ----------- method path() returns Array:D This is the accessor for the paths set when Template::Anti::Library is constructed. my $ta = Template::Anti::Library.new( path => , ... ); These paths name the directories that are searched when locating an original source file. method views ------------ method views() returns Hash:D This is the accessor for the views set when Template::Anti::Library is constructed. my $ta = Template::Anti::Library.new( views => { user => MyApp::View::User.new, page => MyApp::View::Page.new, book => MyApp::View::Book.new, }, ... ); This is a map of names to objects that each should contain one or more methods that have been tagged with the `is anti-template` trait. The names are used by the [/method process](/method process) as part of the name used to look up the template to process. method process -------------- method process(Str $template, |c) returns Str:D This is the workhorse of the system. If the named template has never been processed before, the source template will be located, read, and parsed according to the format for that template. This setup happens once and the result is then cached. It will then process the arguments passed (any arguments after the template name are passed as is through to the processing method). The `$template` name itself should be composed of two names separated by a period ("."). The first name is name given to the [/method views](/method views) parameter. The second is the name of the processing method to call on that object. Exported Routines ================= trait is anti-template ---------------------- sub trait_mod: (Routine $r, :$anti-template) This marks a method as being a Template::Anti processing method. It takes two named arguments. method tmpl($dom, *%stash) is anti-template( :source, :format, ) { ... } The `:source` is the name of the file to load. The location of the file is relatives to the paths set on the `path` attribute on [/Template::Anti::Library](/Template::Anti::Library). The `:format` is the format to use, usually "html" or "xml" unless you have defined your own custom formats. A method declared with this trait that is a yada-method (i.e., it has no code in the block, just a yada (...), will cause Template::Anti to assume that the code is embedded within the original source file named in `:source`. multi get-anti-format-object ---------------------------- multi get-anti-format-object('html') multi get-anti-format-object('xml') These each return a format object that will parse the source using a slightly extended version of [DOM::Tiny](DOM::Tiny) and return it. They will also extract embedded processing code from ` END_OF_SOURCE my &hello-again = anti-template :source($emb-source), :html, :embedded; print hello-again(%vars); Advanced Formats ================ While this library has been built using [DOM::Tiny](DOM::Tiny) to implement XML and HTML parsing and rendering of template sources, it is possible to extend Template::Anti to support parsing sources in any other format. To do this, you need to define a custom `multi sub` named `get-anti-format-object` in your code. For example, here is one built with a couple anonymous classes that will work with plain text files that contain specially formatted blanks. multi sub get-anti-format-object('blanktext') { class { method parse($source) { class { has $.source is rw; method set($blank, $value) { $!source ~~ s:g/ < "_$blank_" > /$value/; Mu } method Str { $.source } }.new(:$source); } method embedded-source($struct) { my $code; ($struct.source, $code) = $struct.source.split("\n__CODE__\n", 2); use MONKEY-SEE-NO-EVAL; my $sub = $code.EVAL; $sub; } } } This also adds support for embedding the code part of the template in the source following a `__CODE__` annotation. Here's a couple examples using this custom object. In `welcome.txt`, we could have this: Subject: Welcome _name_ to the Dark Side _name_ Welcome to the Dark Side. Enclosed you will find instructions on how to reach the Sith planet to begin your training. Love, _dark-lord_ And in `welcome-embedded.html`, we could have this: Subject: Welcome _name_ to the Dark Side _name_ Welcome to the Dark Side. Enclosed you will find instructions on how to reach the Sith planet to begin your training. Love, _dark-lord_ __CODE__ sub ($email, *%data) { $email.set($var, %data{ $var }) for ; } And in our code, we can write this: use Template::Anti; class MyEmails { method hello($email, *%data) is anti-template(:source) { $email.set($var, %data{ $var }) for ; } method hello-embedded($email, %adata) is anti-template(:source) { ... } } my $ta = Template::Anti::Library.new( path => , views => { :email(MyEmails.new) }, ); say $ta.process('email.welcome', :name, :dark-lord); say $ta.process('email.welcome-embedded', :name, :dark-lord); This way, you can get code separated from your templates in any format you like. If your format object does not have an `embedded-source` method defined, attempting to us the embedded form of `anti-template` will result in an exception. Finally, whatever object is used as the parsed structure for your template (i.e., takes the place of the [DOM::Tiny](DOM::Tiny) object provided with the built-in "xml" and "html" formats) must supply a `Str` method to render the object to string.