The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

An overview of Data Templates in Pinwheel

Data view templates are Perl scripts (with a few restrictions). The contents of the template is wrapped in an anonymous sub, and on each call the parameters in the 'template' Context object are exposed as local variables. The template has access to a TAG function, and AUTOLOAD is set to call TAG for any undefined function calls.

The template must return whatever is returned from the outermost TAG call. Since the calling context is not defined, it is recommended that the template simply end with the final TAG call.

The TAG function takes a tag name ("schedule", "dc:title", etc), an optional list of key/value pairs, and optional content. For example:

  TAG("br");
  TAG("body", class => "episode");
  TAG("strong", "important content");
  TAG("a", href => "/foo", "Link to foo");

Due to the AUTOLOAD, these could all be written as:

  br();
  body(class => "episode");
  strong("important content");
  a(href => "/foo", "Link to foo");

Serialised as XML these would be:

  <br/>
  <body class="episode"/>
  <strong>important content</strong>
  <a href="/foo">Link to foo</a>

And as JSON:

  {"br":null}
  {"body":{"class":"episode"}}
  {"strong":"important content"}
  {"a":{"href":"/foo","$":"Link to foo"}}

Note the last two lines. If a tag contains attributes, the JSON value will be a hash containing those attributes. If a tag doesn't contain attributes, the JSON value will be either null or a string. If a tag contains both attributes and a string value, the string will be merged in as an attribute with the key "$".

The ":" in namespace prefixes is converted to a "$" in the JSON/YAML serialisations, eg:

  TAG("po:microsite", "rdf:resource" => "/foo");

As XML:

  <po:microsite rdf:resource="/foo"/>

And as JSON:

  {"po$microsite":{"rdf$resource":"/foo"}}

This keeps the keys valid JavaScript identifiers, so you could reach "/foo" with <object>.po$microsite.rdf$resource

Nested tags are created by using an anonymous sub for the content, eg:

  div(sub { span("foo") });
  body(id => "foo", sub { p("content") });

As XML:

  <div><span>foo</span></div>
  <body id="foo"><p>content</p></body>

And as JSON:

  {"div":{"span":"foo"}}
  {"body":{"id":"foo","p":"content"}}

Note that attributes and child tags are combined in the JSON (and YAML) outputs -- if you have attributes with the same name as child tags, you'll lose one or the other.

To represent a list of items you might do something like this:

  list(sub {
    item("one");
    item("two");
    item("three");
  });

As XML you'd get:

  <list><item>one</item><item>two</item><item>three</item></list>

But this wouldn't work for the JSON and YAML outputs, because each new "item" key would overwrite the previous one. To fix this you need to add an underscore to the end of the "list" tag name:

  list_(sub {
    item("one");
    item("two");
    item("three");
  });

The trailing underscore is removed before output, and has no effect on the XML serialisation, which remains the same as above. But for the JSON and YAML serialisations it means that the contents should be represented by an array, ignoring the child tag names:

  {"list":["one","two","three"]}

Another example, where the items have attributes:

  list_(sub {
    item(n => 4, "four");
    item(n => 2, "two");
  });

As XML:

  <list><item n="4">four</item><item n="2">two</item></list>

And as JSON:

  {"list":[{"n":4,"$":"four"},{"n":"2","$":"two"}]}

Parameters from the "template" context are made available as local variables with the same name. For example, given this:

  Context::set("template", msg => "hello");

And this template:

  text($msg);

You'd get (XML/JSON):

  <text>hello</text>
  {"text":"hello"}

Any undeclared variable in the template is assumed to be a parameter from the current "template" context, and you'll get an error if you don't set values for all the undeclared variables in the template. However, the parser is very simple minded and sees $something[0] as a use of $something rather than @something, so it's advisable to stick to scalar variables in the template.

As a data template has multiple serialisations, the render call is slightly different. The template is selected with via => "hash", and the output format (xml, json, yaml) with the format parameter or inherited from a respond_to container. For example...

Use "foo/bar.hash.data" to generate a JSON result:

  render(template => "foo/bar", format => "json", via => "hash");

Use "foo/bar.hash.data" to generate an XML or JSON result:

  respond_to(
    xml => sub { render(template => "foo/bar", via => "hash") },
    json => sub { render(template => "foo/bar", via => "hash") }
  );

Similar to other calls, if the template name matched the current controller and action the above could be simplified down to:

  respond_to(
    xml => sub { render(via => "hash") },
    json => sub { render(via => "hash") }
  );