Obviel Template

Introduction

Obviel Template integrates with Obviel in an important way: sub-views can be rendered directly from the template. This allows these views to respond to events in the standard Obviel way, including responding to object events. Obviel Template can do this because it’s DOM-based, not text-based. Doing this with a text-based template language (such as JSON Template) isn’t possible, as it isn’t aware of DOM elements at all.

Another powerful feature of Obviel Template is that it supports i18n markers directly in the template for the purpose of internationalization (i18n) of template texts. This way automatic extraction tools can be used to maintain translations of strings in the template. This feature is inspired by some server-side template languages (such as Zope Page Templates), but this is not commonly supported by client-side template languages.

Obviel Template is deliberately a very minimal template language. Logic should be kept in code, not templates. If you need logic, preprocess the object that goes into the template instead.

Variables

Obviel-Template supports variable interpolation using {} markers in text. It looks like this:

<p>{content}</p>

If you combine that template with a JavaScript object with the following structure:

{
  content: "Hello world!"
}

it will result in a rendered template like this:

<p>Hello world!</p>

What if the content property is not available in the JavaScript object, and is thus undefined? Obviel Template considers this an error and the template will fail with a RenderError exception.

Dotted names in variables

Objects can be nested:

{
  a: {
    b: "The value"
  }
}

You can indicate such nested values in a variable using a dotted name:

<p>{a.b}</p>

Rendering the template with the object will result in this:

<p>The value</p>

If you refer to a dotted name that does not exist in the data, this is an error and the template won’t render; you get a RenderError exception.

Dotted names also work in directives like data-with, data-if and data-each, which we’ll see next.

Interpolating the current object: @.

In Obviel Template, special variables start with @, and @. means “the current object”. This can be useful for debugging templates: you can see what the current object is somewhere. The current object will be serialized to JSON into the rendered template.

Consider this data:

{
  title: "Foo"
}

and this template:

<div>
  {@.}
</div>

Rendering the template with the data will result in this:

<div>
  {
    title: "Foo"
  }
</div>

data-with for scope control

You can change the object in which variables are found with the data-with directive. Given the same nested structure as before:

{
  a: {
    b: "The value"
  }
}

you can express the template like:

<div>
  <p data-with="a">{b}</p>
</div>

Rendering the template with the object will result in this:

<div>
  <p>The value</p>
</div>

data-with="a" indicates that within the p element, lookups will be performed in the b object primarily (that is the scope of that element).

data-with must point to an object; it cannot point to strings or arrays or other values, and cannot point to a missing value either: these are RenderError errors.

data-if for conditionals

Sometimes you want to include an element only if a given value is true or not. For the purposes of data-if, a value is false if:

  • it is the false
  • it is null
  • it is undefined
  • it is 0
  • it is an empty string ""
  • it is an empty array
  • the value is missing entirely

Otherwise, the value is considered true.

With !, the behavior of data-if can be reversed: values that evaluate to false will be included.

Consider this template:

<p data-if="person">Hello {person.name}!</p>
<p data-if="!person">{person.name} is not present</p>

Here’s a data structure with person present:

{
  person: {
    name: "Evan"
  }
}

rendering the template will result in this:

<p>Hello Evan!</p>

but if you have a data structure without person present, such as this:

{

}

the template will render like this:

<p>Evan is not present</p>

data-each for repetition

If you point to an array with data-each, it will render each entry in the array into the DOM. So with a data structure like this:

{
  persons: [
     {
       name: "Bob"
     },
     {
       name: "Steven"
     }
  ]
}

and a template like this:

<ul>
  <li data-each="persons">{name}</li>
</ul>

the result will be this:

<ul>
  <li>Bob</li>
  <li>Steven</li>
</ul>

Note that data-each changes the scope (like data-with). data-each changes the object in which the variables are looked up to each entry in the array, one by one.

If the array is empty, nothing will be rendered at all. So, this data:

{
  persons: [
  ]
}

will result in this:

<ul>
</ul>

You can use the special variable @. to render simple arrays where each item is not an object in itself. For instance:

{
  persons: [
    "Bob", "Steven", "Jay"
  ]
}

and this template:

<ul>
  <li data-each="persons">{@.}</li>
</ul>

will render like this:

<ul>
  <li>Bob</li>
  <li>Steven</li>
  <li>Jay</li>
</ul>

@each variable

data-each supports the special variable @each. This variable contains some useful properties during the construction of loops:

  • index - the index of the current iteration, starting at 0
  • number - the number of the current iteration, starting at 1
  • length` - the length of the list
  • even - true if the index is an even number
  • oddtrue if the index is an odd number

For example:

<ul>
  <li data-each="persons">{@each.index}</li>
</ul>

will render (with three items in the list) as follows:

<ul>
  <li>0</li>
  <li>1</li>
  <li>2/li>
</ul>

To support nested use of data-each, @each also supports a variable named after the variable that is currently being looped through, in this example persons:

<ul>
  <li data-each="persons">{@each.persons.index}</li>
</ul>

which will do the same as the previous template.

Let’s look at how this is used in a nested data-each. Here’s the data:

{outer: [
  {inner: ['a', 'b', 'c']},
  {inner: ['d', 'e']}
]}

And here’s a template:

<ul>
  <li data-each="outer">
    <ul>
      <li data-each="inner">
      value: {@.} outer: {@each.outer.number} inner: {@each.inner.number}
      </li>
    </ul>
  </li>
</ul>

If you used this template with the data, you would get this output:

<ul>
  <li>
    <ul>
      <li>
      value: a outer: 1 inner: 1
      </li>
      <li>
      value: b outer: 1 inner: 2
      </li>
      <li>
      value: c outer: 1 inner: 3
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
      value: d outer: 2 inner: 1
      </li>
      <li>
      value: e outer: 2 inner: 2
      </li>
    </ul>
  </li>
</ul>

What if the variable data-each goes through is a dotted name - how is it used with data-each? You can addres it by replacing the periods by underscores. For instance, given this data:

{
  something: {
    persons: ["Bob", "Steven", "Jay"]
  }
}

and this template:

<ul>
  <li data-each="something.persons">{@each.something_persons.number}</li>
</ul>

you will get this as a result:

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

So to address something.persons in a loop, you use something_persons, with the . replaced by _.

Sometimes, to support various user interface designs, you want to add a special class into the DOM depending on some @each value, for instance when the index is even or odd. Obviel Template supports such special use cases with data-func, about which we’ll talk in a later section.

Nested scoping

Values in outer scopes are available for variables in inner scopes. Consider this structure of objects:

{
 a: {
   b: "The value"
 },
 c: "Outer value"
 d: {
   e: "Value in outer object"
 }
}

In the scope a, we can still access c and d (and even a again):

<p>b in outer scope: {a.b}</p>
<p>c in outer scope: {c}</p>
<p>e in outer scope: {e}</p>
<p data-with="a">b in scope a: {b}</p>
<p data-with="a">c in scope a: {c}</p>
<p data-with="a">e in scope a: {d.e}</p>
<p data-with="a">b in a in scope a: {a.a.b}</p>

But if you redefine a name in an inner scope, you cannot access the value in the outer scope:

{
  a: {
    b: 'redefined'
  }
  b: 'original'
}

<p data-with="a">b in scope a: {b}</p>

will therefore get you:

<p>b in scope a: redefined</p>

data-view

Consider the following data:

{
  greeting: "Hello",
  who: {
    iface: 'person',
    name: "Pat Pending",
    url: "http://www.example.com/pat_pending"
  }
}

and this template:

<p>{greeting} <span><a data-with="who" href="{url}">{name}</a></span></p>

it will render like this:

<p>Hello <span><a href="http://www.example.com/pat_pending">Pat Pending</a></span></p>

This works. But we could also arrange our code so that we have a view for the person iface and use that instead. The advantage of that is that we could reuse this view in other places, and in addition we can use an Obviel view to hook up to events and so on. Let’s consider a view:

obviel.view({
   iface: 'person',
   obvt: '<a href="{url}">{name}</a>'
});

This is a view for the iface person. We use the Obviel Template (indicated by obvt) to render the view.

Now we can change our template to render who as a view:

<p>{greeting} <span data-view="who"></span></p>

The result is still the same:

<p>Hello <span><a href="http://www.example.com/pat_pending">Pat Pending</a></span></p>

By using data-view, you have made the outer template more generic. If the object indicated by the name who has another iface, the template will still work unchanged. For example, here is a data structure where who is a robot:

{
  greeting: "Greetings",
  who: {
    iface: 'robot',
    designation: "OVL-R 4711",
  }
}

and this is a view for the iface robot:

obviel.view({
   iface: 'robot',
   obvt: '{designation}'
});

and here is our template again:

<p>{greeting} <span data-view="who"></span></p>

when we render the data now, we get this:

<p>Greetings <span>OVL-R 4711</span></p>

in other words, if you add data-view to an element el to render object who, Obviel Template will do the equivalent of this:

$(el).render(who);

Sometimes you have multiple views for an iface, distinguished by name. You can indicate the name used by data-view with a | character. Here’s another view for person with the name extended:

obviel.view({
   iface: 'person',
   name: 'extended',
   obvt: 'The person has the name <a href="{url}">{name}</a>'
});

and here is how you refer to this extended view:

<p>{greeting} <span data-view="who|extended"></span></p>

By default, data-view looks up a view named default – the default view name for Obviel. If you want to change the way templates look up views to use another view globally you can do so in JavaScript:

obviel.template.set_default_view_name('summary');

from now on if you don’t specify the view name explicitly, Obviel will look up views by the name of summary by default. If you decide to do this, you would want to do this once per web page before template rendering takes place, for instance when the page has just been loaded. Note that this does not alter the default view name of Obviel itself when you use el.render(obj); this will remain default.

data-view must point to an object. It cannot point to an array or other value.

Formatters

Besides views to insert HTML, for text manipulation Obviel Template also supports formatters. Formatters can be registered globally with Obviel Template like this:

obviel.template.register_formatter('upper', function(value) {
   return value.toUpperCase();
});

This registers a formatter upper which when used will turn any string variable to upper case. Here’s how we would use it in a template:

<p>{foo|upper}</p>

if we render this with the data structure:

{foo: 'hello'}

we’d get the following output:

<p>HELLO</p>

Again, formatters cannot be used to include HTML, only text; for including HTML you can use views. To do some special DOM manipulation, you can also consider data-func.

You would register formatters only once per web page before template rendering takes place, for instance when the page has just been loaded.

block: element that disappears

Obviel Template has element-based directives (data-if, data-each, etc). But sometimes you just want to conditionally or repeatedly include a piece of text, not an element. You can support this use case by using the special block element. Once the template has rendered, the block element will disappear from the result.

So:

Hello? <block data-if="again">HELLO?</block>

with this data:

{ again: true }

will render like this:

Hello? HELLO?

and with again set to false:

{again: false}

will render like this:

Hello?

element: dynamically generating elements

Normally you’d generate an element by simply including it in the template, for instance a p element like this:

<p>Hello world</p>

In some cases however you want to be able to dynamically decide the name of the element based on the data. Obviel Template supports a special element element that lets you do this. This is the equivalent of the above template:

<element data-name="p">Hello world</element>

When this is rendered, you’ll see this just like with the first example:

<p>Hello world</p>

What this lets you do is dynamically generate element names:

<element data-name="{my_element_name}"></element>

You could use data-repeat to generate a whole series of elements:

<element data-repeat="tags" data-name="{@.}"></element>

attribute: dynamically generating attributes

Normally you’d generate a dynamic attribute like this:

<a href="{url}">{title}</a>

but sometimes this isn’t good enough: in particular, we sometimes want to be able to generate attributes that are only there if some condition is true. Obviel Template has a special attribute element that lets you do this. The equivalent of the above example using the attribute element looks like this:

<a><attribute data-name="url" data-value="{url}"/>{title}</a>

What will happen when this template is rendered is the same as in the first example:

<a href="/some/url">Some Title</a>

attribute will always apply to the element it is directly contained in.

This construct is helpful because you can use data-if for conditional attributes now:

<a><attribute data-if="flag" data-name="url" data-value="{url}" />{title}</a>

Now, the url attribute will appear only if flag is true.

In combination with data-each this can be used to dynamically generate multiple attributes from data:

<a><attribute data-each="attributes" data-name="{name}" data-value="{value}" />{title}</a>

You can generate the same attribute multiple times, in this case the content is added to the existing attribute, with a space character for a separator. This is for instance useful when you want to add multiple classes dynamically to an element and have some show up only if a condition is true. This:

<div>
  <attribute data-name="class" data-value="{first}" />
  <attribute data-if="flag" data-name="class" data-value="{second}" />
</div>

combined with this data:

{
  first: 'first',
  second: 'second',
  flag: true
}

will result in this output:

<div class="first second"></div>

but with this data:

{
  first: 'first',
  second: 'second',
  flag: false
}

you’d get this output:

<div class="first"></div>

data-func

In some cases it is more convenient to use JavaScript to manipulate the DOM directly (using jQuery) than it would be to use Obviel Template constructions to do the job. For these cases Obviel Template offers a special directive to plug custom functions into the template rendering process directly, called data-func. data-func should be used as a facility of last resort: if in doubt, use the normal Obviel Template functionality, because the template will probably be easier to understand.

To see what data-func does, let’s consider a simple (but pretty useless) case first. We register the func magic:

obviel.template.register_func('magic', function(el) {
   el.attr('magic', 'Magic!');
});

Once this registration has been made, we can use it within a template, for instance this one:

<p data-func="magic">Hello world!</p>

Once the template is rendered, we’ll see this:

<p magic="Magic!">Hello world!</p>

Of course, this functionality is pretty useless, as we could just have written a template like this:

<p magic="Magic!">Hello world!</p>

data-func really starts to be useful in complex cases in combination with data-each. Let’s consider the case where we want to add an even and odd class to each iteration. We could write a template like this:

<ul>
  <li data-each="list">
    <p data-if="@each.even" class="even">{@.}</p>
    <p data-if="@each.odd" class="odd">{@.}</p>
  </li>
</ul>

This template has various problems:

  • we have to write the p bit twice
  • what if we wanted to place the class on li instead of on p?

We could also use dynamically generated attributes to solve this case:

<ul>
  <li data-each="list">
    <p>
      <attribute data-if="@each.even" data-name="class" data-value="even"/>
      <attribute data-if="@each.odd" data-name="class" data-value="odd" />
      {@.}
    </p>
  </li>
</ul>

This would also allow us to generate the class attribute on the li:

<ul>
  <li data-each="list">
    <attribute data-if="@each.even" data-name="class" data-value="even"/>
    <attribute data-if="@each.odd" data-name="class" data-value="odd" />
    <p>{@.}</p>
  </li>
</ul>

This a reasonable solution. But it’s still pretty verbose, and it’s just so easy to express a rule like this in JavaScript with jQuery:

var evenodd = function(el, variable) {
   if (variable('@each.even')) {
      el.addClass('even');
   } else {
      el.addClass('odd');
   }
};

With data-func you can plug this functionality into template rendering. First we need to register the useful function globally for our application:

obviel.template.register_func('evenodd', evenodd);

And then we can use it on p:

<ul>
  <li data-each="list">
    <p data-func="evenodd">{@.}</p>
  </li>
</ul>

Or to use it to add the class to li directly:

<ul>
  <li data-each="list" data-func="evenodd">
    <p>{@.}</p>
  </li>
</ul>

We can now also support more complex situations, where we don’t want to add a class attribute on even or odd, but every third item:

var everythird = function(el, variable) {
   if (variable('@each.index') % 3 === 0) {
      el.addClass('third');
   }
};

obviel.template.register_func('everythird', everythird);

and then use it:

<ul>
  <li data-each="list" data-func="everythird">
    <p>{@.}</p>
  </li>
</ul>

everythird cannot be expressed using Obviel Template alone, but is easy to express using a custom fuction.

Using data-func you can supplement Obviel Template with functionality particular to your application. This is especially useful for generic functionalities that extend the template language, such as adding classes in data-each, where we can rely on the existence of a special variable @each to exist.

Note that you should only use data-func to change the element, for instance by manipulating its attributes, not to add or remove child elements (or text-nodes). Most DOM manipulations by adding, moving or removing elements or text modes are unsafe: it can break Obviel Template in unexpected ways, as it relies on the order of child nodes to stay the same.

id support

Obviel templates should be valid HTML fragments. In valid HTML, a id should be unique. You can just use ids like for any other HTML:

<div id="foo"></div>

But variables in ids are not allowed, because it would be too easy to have a HTML fragment with the same id (such {my_id}) in multiple id attributes. So this is illegal:

<div id="{not_allowed}"></div>

To generate an id, you can instead use the data-id directive:

<div data-id="{some_id}"></div>

This works just like any other attribute, so you can put things before and after the variable:

<div data-id="prefix{some_id}postfix"></div>

This will generate HTML with proper id attributes.

Order

You can combine the various data- directive attributes on an element:

<p data-if="a" data-each="b" data-with="c" data-view="d"></p>

The order of execution is as follows :

  • data-if
  • data-each
  • data-with
  • data-view or data-trans
  • data-func

This means that if the value a is present and true, the p element will be included in the rendered template (at all). After this, for each of the entries in b (so b scope, iteratively), we render the object d in the c scope with a view.

This order holds no matter in what order you define the attributes.

data-trans, which we’ll introduce next, also follows after the other statements. It’s not allowed on an element with data-view.

data-func is always executed last for an element.

data-trans for i18n

Obviel Template offers the ability to write a template that can be shown in other languages as well: you can internationalize a template. We will discuss the basics of marking up a template so it can be translated here, but much more detail is in a separate document about template i18n.

If we have a template like this:

<p data-trans="">Hello world!</p>

we can also show this template in the Dutch language if the appropriate translation is available:

<p>Hallo wereld!</p>

data-trans is used to mark up those parts of the template that should be translatable, such as attributes and texts. These translatable values can contain variables as well:

<p data-trans="">Hello {who}!</p>

By default, data-trans is used to mark the textual content of a tag for translation. If you want to mark an attribute as translatable you can do it like this:

<div data-trans="title" title="Hello" />

You can also mark multiple attributes:

<div data-trans="title other" title="Hello" other="Something" />

You can indicate attributes and textual element content with the special name .:

<div data-trans=”. title” title=”Hello”>Contents</div>

Read Obviel Template i18n for much more detail.

Literals

Injecting the characters { and } can be done using a special markup:

{@open}

{@close}

Unsupported XML constructs

The HTML CDATA section implementation in browsers is so inconsistent we didn’t think it was worth spending time supporting this in templates, given its limited utility. Obviel Template therefore has undefined behavior in the presence of a CDATA section in your template: we recommend you don’t use it. If you want to insert extended sequences of text in an element you can already do so with a simple variable.

Process instructions are also not supported.