How to define template directives (from an API perspective)?
Posted
by
Ralph
on Programmers
See other posts from Programmers
or by Ralph
Published on 2011-01-11T19:49:52Z
Indexed on
2011/01/11
19:59 UTC
Read the original article
Hit count: 297
Preface
I'm writing a template language (don't bother trying to talk me out of it), and in it, there are two kinds of user-extensible nodes. TemplateTags and TemplateDirectives.
A TemplateTag closely relates to an HTML tag -- it might look something like
div(class="green") { "content" }
And it'll be rendered as
<div class="green">content</div>
i.e., it takes a bunch of attributes, plus some content, and spits out some HTML.
TemplateDirectives are a little more complicated. They can be things like for
loops, ifs
, includes
, and other such things. They look a lot like a TemplateTag, but they need to be processed differently. For example,
@for($i in $items) {
div(class="green") { $i }
}
Would loop over $items
and output the content with the variable $i
substituted in each time.
So.... I'm trying to decide on a way to define these directives now.
Template Tags
The TemplateTags are pretty easy to write. They look something like this:
[TemplateTag]
static string div(string content = null, object attrs = null)
{
return HtmlTag("div", content, attrs);
}
Where content
gets the stuff between the curly braces (pre-rendered if there are variables in it and such), and attrs
is either a Dictionary<string,object>
of attributes, or an anonymous type used like a dictionary. It just returns the HTML which gets plunked into its place. Simple! You can write tags in basically 1 line.
Template Directives
The way I've defined them now looks like this:
[TemplateDirective]
static string @for(string @params, string content)
{
var tokens = Regex.Split(@params, @"\sin\s").Select(s => s.Trim()).ToArray();
string itemName = tokens[0].Substring(1);
string enumName = tokens[1].Substring(1);
var enumerable = data[enumName] as IEnumerable;
var sb = new StringBuilder();
var template = new Template(content);
foreach (var item in enumerable)
{
var templateVars = new Dictionary<string, object>(data) { { itemName, item } };
sb.Append(template.Render(templateVars));
}
return sb.ToString();
}
(Working example). Basically, the stuff between the (
and )
is not split into arguments automatically (like the template tags do), and the content
isn't pre-rendered either.
The reason it isn't pre-rendered is because you might want to add or remove some template variables or something first. In this case, we add the $i
variable to the template variables,
var templateVars = new Dictionary<string, object>(data) { { itemName, item } };
And then render the content manually,
sb.Append(template.Render(templateVars));
Question
I'm wondering if this is the best approach to defining custom Template Directives. I want to make it as easy as possible. What if the user doesn't know how to render templates, or doesn't know that he's supposed to?
Maybe I should pass in a Template
instance pre-filled with the content
instead? Or maybe only let him tamper w/ the template variables, and then automatically render the content at the end?
OTOH, for things like "if" if the condition fails, then the template wouldn't need to be rendered at all. So there's a lot of flexibility I need to allow in here.
Thoughts?
© Programmers or respective owner