Templating

TIBET Templating

wins

  • Client-side templating with extended syntax and string substitutions.
  • Integrated client-side templating and formatting at the markup level.
  • Server-side handlebars integration via the TDS.template function.

concepts

The ability to mix static content with live data is a common operation in web applications. As a core feature, TIBET's templating engine is tightly integrated with the rest of the framework. Tight integration allows TIBET's templating subsystem to leverage a number of TIBET-specific features and to work seamlessly with data binding and TIBET's other features.

Templating in TIBET takes advantage of sophisticated 'access path' accessors and polymorphic behavior woven throughout the framework. We'll talk more about this below.

First, though, let's see some simple 'format' expressions that don't rely on those capabilities but instead rely on what we refer to as "substitutions" in TIBET…simple string formats you can use in templated and untemplated code and markup.

Simple format expressions

TIBET supports simple formatting such as character replacement, number formatting, and keyed formatting. Let's take a look at example of each one of these:

Character replacement formatting

To perform simple character replacement formatting, provide a String format delimited by '@{' and '}'. The initial @{ signifies this is a character substitution. Within the @{ } string all '@' characters are replaced in order. For example:

'4582022'.format('My phone number is: @{@@@-@@@@}');

Produces:

My phone number is: 458-2022

Numeric replacement formatting

To perform a numeric replacement provide a String format delimited by '#{' and '}'.

Within the #{ } structure you can use the '#' character, the '0' character or the '?' character as the 'replacement' character. The character you choose will depend on the behavior you want the format to take.

The '#' character:

If the pattern has exactly the same number of '#' characters as the source number has digits, the number is formatted appropriately:

(123.45).format('#{###.##}'); // -> 123.45

If the pattern has fewer '#' characters to the right of the decimal point than the source number has digits, the source number is rounded to fit the pattern:

(123.456).format('#{###.##}'); // -> 123.46

If the pattern has fewer '#' characters to the left of the decimal point than the source number has digits, the extra digits from the source number are placed anyway:

(9123.45).format('#{###.##}'); // -> 9123.45

If the pattern has '#' characters to the right of the decimal and the source number has no digits to the right of the decimal, the decimal point is still displayed:

(123).format('#{###.##}'); // -> 123.

If the pattern has '#' characters to the right of the decimal and the source number has a decimal point or a decimal point followed by a zero, only the decimal point is still displayed:

(123).format('#{###.##}');   // -> 123.
(123.0).format('#{###.##}'); // -> 123.

If the pattern has '#' characters to the left of the decimal and the source number has no digits to the left of the decimal other than '0', no digits are displayed to the right of decimal:

(.45).format('#{###.##}');  // -> .45
(0.45).format('#{###.##}'); // -> .45

If the pattern has no '#' characters to the right of the decimal and the source number has digits to the right of the decimal and the first digit is 5 or greater, the number to the left of the decimal will be rounded up by 1:

(123.55).format('#{###}');  // -> 124

The '0' character:

This character follows the same rules as the '#' character in some cases:

(123.45).format('#{000.00}');  // -> 123.45
(123.456).format('#{000.00}'); // -> 123.46
(9123.45).format('#{000.00}'); // -> 9123.45

…except that if the source number has fewer digits than the pattern (either to the right or the left of the decimal place), then a '0' is placed in that spot:

(123).format('#{000.00}');   // -> 123.00
(123.4).format('#{000.00}'); // -> 123.40
(.45).format('#{000.00}');   // -> 000.45
(0.45).format('#{000.00}');  // -> 000.45
(12.34).format('#{000.00}'); // -> 012.34

The '?' character:

This character follows the same rules as the '0' character, except that a space will be placed instead of a '0' when the source number has fewer digits than the pattern (either to the right or the left of the decimal place) (here, a space is denoted by an underscore '_'):

(123).format('#{???.??}');   // -> 123.__
(123.4).format('#{???.??}'); // -> 123.4_
(.45).format('#{???.??}');   // -> ___.45
(0.45).format('#{???.??}');  // -> ___.45
(12.34).format('#{???.??}'); // -> _12.34

Note that using this character in patterns can sometimes produce a result that can not be converted back into a number:

(123).format('#{???.?0}'); // -> 123._0

The ',' character:

This character is unique in that it acts as a 'toggle' to switch on thousands grouping'. Therefore, the mere presence of this character anywhere in the format 'switches on' thousands grouping. Note that the actual character used for the thousands separator and the grouping sizes are determined by the Number type, which uses locale information if it is loaded:

(9123.45).format('#{#,###.##}'); // -> 9,123.45
(9123.45).format('#{###,#.##}'); // -> 9,123.45

Here is one last example that shows a commonly used format and that is for U.S. currency:

(89123.4).format('$#{#,###.00}'); // -> $89,123.40

Keyed replacement formatting

To perform a keyed replacement formatting, provide a String format delimited by '%{' and '}' and the content of the expression as a 'key' into the supplied key source to the format call.

Keyed replacement formatting is a bit different in that the object being formatted is only used if a Function is supplied (where it is used as a data source by the Function). Otherwise, it is ignored. It is easier to explain by example:

//  Simple keyed formatting - a blank object is used as the receiver. It is
//  completely ignored.

//  TP.oc() is a shortcut for TP.lang.Object.construct() - the topmost supertype
//  in TIBET.

TP.oc().format('My age is: %{aKey}', TP.hc('aKey', 32));

Produces:

My age is: 32

//  More complex keyed formatting - a Date object is used as the receiver and
//  the supplied keys are keys into a hash of Function objects:

'Bill'.format('What\'s your name? %{aKey}', TP.hc('aKey', function (item) {return 'My name is: ' + item;}));

Produces:

What's your name? My name is: Bill

TIBET has sets of built-in hashes of Function objects to do various kinds of formatting. A popular one is the set accessed on Date.LOCALTIME_TOKENS:

//  Another keyed formatting - a Date object is used as the receiver and
//  the supplied keys are keys into a hash of Function objects (in this
//  case, a set of Function that can produce various parts of a Date object in
//  different formats):

//  TP.dc() is a shortcut for Date.construct() (or new Date()) if you prefer)

TP.dc().format('The day of the month is: %{d}', Date.LOCALTIME_TOKENS);

Produces:

The day of the month is: 19 <- current day of the month (not always 19)

Full templating expressions

For more complex templating, TIBET's format() call also supports a much more powerful formatting syntax than simple formatting.

Template expressions

Template expressions in TIBET are delimited by {{ and }}, just like the popular Handlebars templating engine:

//  TP.hc() creates a hash in TIBET - much better than trying to use a regular
//  Object as a hash (or dictionary) - and is a shortcut for
//  TP.lang.Hash.construct()

TP.hc('firstName', 'Bill').format('Hi there {{firstName}}';

Produces:

Hi there Bill

Note that it is not necessary for us to use a plain JS object (or, in this case, a TP.lang.Hash) as the data source for a template. Here is an example of a 'locally' programmed object that supplies data to a template:

//  Create a blank object
newObj = TP.lang.Object.construct();
//  OR
newObj = TP.oc();

//  'Locally' program it
newObj.defineAttribute('firstName');
newObj.set('firstName', 'Scott');

//  Print the value of 'firstName'
newObj.format('Hi there ');

//  Set 'firstName' to a different value
newObj.set('firstName', 'Bill');

//  Print it again
newObj.format('Hi there ');

There are a number of differences in templating between TIBET and Handlebars, but one of the most distinct is the notion of invoking additional formatting on a value before it is inserted into its place in the template. This is done with a dot-percent ('.%') syntax:

//  Note that the whitespace around the '.%' is *not* significant but it is
//  highly recommended for clarity:

TP.hc('foo', '<bar/>').format('hi: {{foo .% escapedHTML}}');

Produces:

hi: &lt;bar/&gt;

So, we can say that we have a 'template value' on the left-hand side of the dot-percent ('.%') and a 'template format' on the right-hand side.

As an aside, while template formats can use a variety of forms, the one being used here is an as() method on the template value (which is obtained via get() from the data source). Therefore, the equivalent form in TIBET code would be:

'hi: ' + TP.hc('foo', '<bar/>').get('foo').as('escapedHTML');

See below for a much more detailed discussion of template formats.

Template values

Template values occur to the left of any dot-percent ('.%') in the template expression. They represent a reference to the data being templated. They can be 'simple' or 'complex', because the standard TIBET get() call can not only take in simple names, but complex path constructs to access data (so called 'access paths').

Simple template values

A simple template value in TIBET is simply the name of the 'aspect' that the data can be found under in the supplied object. So, here we supply the String 'Hi there' to a format and get this output:

'Hi there'.format('Bill says: {{value}}');

produces:

Bill says: Hi there

The templating engine substitutes the value 'Hi there' into the template (the standard aspect name for the 'whole value' in TIBET is 'value' - messaging an object with ".get('value')" typically returns the object itself).

Earlier, we saw an example of using values from a TP.lang.Hash:

TP.hc('firstName', 'Bill').format('Hi there {{firstName}}';

Produces:

Hi there Bill

And here's one from using values from an Array:

TP.ac(1, 2, 3).format('The value at: 1 is: {{1}}');

Produces:

The value at: 1 is: 2

Note the use of the completely numeric 'aspect' - which works for Arrays.

'Access path' template values

Like Handlebars, TIBET also supports 'object traversal' within its templates:

Assume this data object:

dataObj = TP.hc('data', TP.hc('firstName', 'Bill', 'lastName', 'Edney', 'age', 47));

Templating expressions such as this can be written:

dataObj.format('Hi there {{data.firstName}} {{data.lastName}}. You are {{data.age}} years old');

Note that TIBET does not use standard JavaScript object traversal when retrieving data here. This is a point worth repeating: although dots (.) are used in this syntax, TIBET is not performing standard JavaScript object traversal. Instead, it is using TIBET's get() call, which computes an 'access path' to the data. Sophisticated forms of access paths (discussed below) can help with retrieving template values that are deeply nested in data structures.

In this case, since the data is JavaScript, this call:

.get('data.firstName');

translates into:

.get('data').get('firstName');

Why is it important to know this?

As you might know from having read other TIBET documentation, TIBET's get() (and set()) call uses a mechanism whereby it will look for get<Property>() before accessing the <property> slot on the object. Therefore, if the object produced by the get('data') call implements a getFirstName() method, that method will be called (and it's return value used) instead of retrieving the value at the firstName slot on that object:

TP.lang.Object.defineSubtype('MyType');

MyType.Inst.defineAttribute('foo');

//  Not strictly required, since we provide a custom 'get' accessor below
MyType.Inst.defineAttribute('bar');

//  Custom 'get' accessor for 'bar'
MyType.Inst.defineMethod('getBar',
function() {
    return 'An alternate bar value';
});

//  Allocate and initialize one
var myObj = MyType.construct();

//  Set a value for 'foo' - this will be used.
myObj.set('foo', 'A foo value');

//  Set a value for 'bar' - this will never be used.
myObj.set('bar', 'A bar value');

// No special 'get' accessor - just return the value of the slot
myObj.get('foo');   //  -> 'A foo value'

//  Special 'get' accessor provided - this causes 'getBar' above to be called
myObj.get('bar');   //  -> 'An alternate bar value'

This provides for unlimited customization 'under the covers' whereby custom get() and set() accessors can be provided on object types that are then vended to templates (or other places in the system) that don't have to know that that custom accessor has been implemented. This can even occur after other code or template authors start using these objects.

To further demonstrate, let's use the 'local programming' technique we saw above to provide an object that has both a value for a particular <property> slot and a custom get() accessor:

newObj = TP.lang.Object.construct();
newObj.defineAttribute('firstName');

//  This value won't matter - a custom get accessor is provided: getFirstName
newObj.set('firstName', 'Scott');

//  A custom 'get' accessor - this is what the 'get('firstName') will return.
newObj.defineMethod('getFirstName',
                      function ()
                      {
                          return 'Rob';
                      });

newObj.format('Hi there {{firstName}}');

the result of this will be:

Hi there Rob

Now that we know the power of get(), it is useful to know that there are a whole host of powerful syntax expressions we can use. In TIBET speak, these are known as 'access paths' and a variety of syntaxes can be used, some for JavaScript-based data and some for markup-based data:

  • Used for JavaScript-based data (i.e. objects produced from JSON, etc.)

    • Simple JavaScript accessor notation ('.' separated paths)
    • Extended slicing and dicing JavaScript accessor notation.
  • Used for markup-based data

    • XPath expressions
    • CSS selector expressions.

While we will show a few examples of these below, to see the full range of expressions that can be used, see TIBET Access Paths.

More complex template values

Using these more complex access path notations, we can extract data in very sophisticated ways in templates:

//  Note how we create a *TP.core.ElementNode* wrapper around the native Element
//  here. This ensures that we can send 'get()' to this data source.
dataElem = TP.tpelem('<foo><baz bar="moo"/></foo>');

//  Note the embedded XPath expression here (which queries the data source to
//  retrieve the element under the supplied element that has a 'bar' attribute):
dataElem.format('The element with a bar attribute is: {{./*[@bar]}}');

This produces:

The element with a bar attribute is: <baz bar="moo"/>

Here's one that uses TIBET's 'extended slicing and dicing' notation:

Assume this data object:

//  Just like TP.hc() creates a hash, TP.ac() creates a standard JavaScript
//  Array - like 'new Array()' would, but we recommend using TP.ac()
dataObj = TP.hc('data', TP.ac('first', 'second', 'third'));

Templating expressions such as this can be written:

dataObj.format('The second item is: {{data.1}} and the other two are: {{data[0,2]}}');

Produces:

The second item is: second and the other two are: first, third

Template formats

Just as there are a variety of ways to access data in template values, there are a variety of ways to format the resultant value before it is placed into the template. These are written to the 'right hand' side of a 'dot percent' expression (a '.%' or '.%*' - we'll discuss the difference below).

One thing to note is that omitting the template value (the 'left hand' side of a 'dot percent' expression) in a template will cause the template engine to default the aspect it is trying to retrieve to 'value'. All objects in TIBET can respond to get('value').

//  This defaults the access path to an aspect of 'value' - most objects respond
//  to get('value') with a reference to themselves.
'<bar/>'.format('hi: {{.% escapedHTML}}');

Using simple formatting expressions.

Simple formatting expressions can be used inside of a templating expression to format a value:

dataObj = TP.hc('data', TP.hc('firstName', 'Bill', 'lastName', 'Edney', 'age', 47, 'phone', '4582002', 'salary', 100000));
dataObj.format('Hi there {{data.firstName}} {{data.lastName}}. Your phone number is {{data.phone .% @{@@@-@@@@}}}.');

Produces:

Hi there Bill Edney. Your phone number is 458-2002.

Here's another (assuming the dataObj above):

dataObj.format('Hi there {{data.firstName}} {{data.lastName}}. Your salary is {{data.salary .% $#{#,###.00}}}.');

Produces:

Hi there Bill Edney. Your salary is  $100,000.00.

Using 'as()'

Many objects in TIBET can respond to what TIBET calls 'as' methods. There are three ways these methods get resolved:

First, the system will try to look up a method matching that name on the receiving object. So, 'bill'.as('startUpper'); will try to invoke .asStartUpper() on the String 'bill'.

Second, the system will try to resolve that name to a type somewhere in the system. So, (42).as('String'); will try to invoke '.asString() on the Number 42, not because the Number type necessarily has an .asString() method, but because 'String' is a valid system type, so the type conversion will be attempted.

Third, if a 'format method' cannot be found, then the expression will be 'turned around' so that the system will try to find a transform<TypeOfReceivingObject>() method on String type. The system leverages this capability to provide numerous capabilities, including type-specific formatting (such as Dates providing their formats - see below) and 'callable named templates' - which is one way to provide 'nested templates' (or 'partials') within TIBET.

Using named 'as() methods'

There are a host of built-in as() methods in TIBET that can provide a variety of formatting. We saw one of these earlier (asEscapedHTML()). Here are some more:

//  Strings respond to as('startUpper');
'bill'.format('Your name title cased: {{value .% startUpper}}');

producing:

Your name title cased: Bill

All sorts of objects respond to .as('JSONSource'):

//  Therefore, we can do this (note that omitting the template value here defaults it to 'value'):
TP.ac(1, 2, TP.ac(4, 5, 6)).format('The array as JSON: {{.% JSONSource}}');

producing:

The array as JSON: [1,2,[4,5,6]]

Other kinds of 'representations' that all objects respond to include:

//      <obj>.as('String')
TP.ac(1, 2, TP.ac(4, 5, 6)).format('The array as a String: {{value .% String}}');

//      <obj>.as('Source')
TP.ac(1, 2, TP.ac(4, 5, 6)).format('The array as JS source: {{value .% Source}}');

//      <obj>.as('DumpString')
TP.ac(1, 2,TP.ac(4, 5, 6)).format('The array as a dump String: {{value .% DumpString}}');

//      <obj>.as('HTMLString')
TP.ac(1, 2,TP.ac(4, 5, 6)).format('The array as an HTML String: {{value .% HTMLString}}');

//      <obj>.as('PrettyString')
TP.ac(1, 2,TP.ac(4, 5, 6)).format('The array as a pretty String: {{value .% PrettyString}}');

//      <obj>.as('XMLString')
TP.ac(1, 2,TP.ac(4, 5, 6)).format('The array as an XML String: {{value .% XMLString}}');

As stated before, if an as...() method cannot be found on the receiving object's type, the system will look for transform<TypeOfReceivingObject>() method on the String type.

The String type overrides transformDate() to format dates by using the FORMAT strings found as a constant on the Date type. This allows for as() formatting expressions like:

TP.dc().as('YYYY'); // -> 2014

which, in templating, can be used like this:

TP.dc().format('The year is: {{value .% YYYY}}');

producing:

The year is: 2014

Using named 'as() types'

The second way an 'as' method can get resolved (if a method cannot be found on the type of the receiving object and the supplied method is a TIBET type), is for the system to try to convert the receiving object to an object of that type.

This simplest of these is as('String'):

(27).as('String'); // -> '27'

Converting a Number to a String is easy as well:

'56.23'.as('Number'); //  -> 56.23

So these can be used in template as you would use a as() method:

(47).format('Your age was: {{value .% String}}');

Note also that there are a number of TIBET types that can be used to convert data. One is TP.core.XMLRPCNode:

TP.ac(1, 2 , TP.hc('foo', 'bar')).as('TP.core.XMLRPCNode');

Produces:

//  NB: Note that the output type here is an XML Element, stringified to show in
//  the console.
<array><data><value><double>1</double></value><value><double>2</double></value><value><struct><member><name>foo</name><value><string>bar</string></value></member></struct></value></data></array>

Using this in a template:

TP.ac(1, 2 , TP.hc('foo', 'bar')).format('The content as XML-RPC is: {{value .% TP.core.XMLRPCNode}}');

Produces:

The content as XML-RPC is: <array><data><value><double>1</double></value><value><double>2</double></value><value><struct><member><name>foo</name><value><string>bar</string></value></member></struct></value></data></array>

Using iteration

Note that any TIBET type can be the target of a conversion. Many of the 'html' tag types supplied with TIBET can respond to the as() method:

TP.ac(1, 2, 'that\'s cool').as('html:ul', TP.hc('repeat', true));

Produces:

The list is <html:ul><html:li>1</html:li><html:li>2</html:li><html:li>that's cool</html:li></html:ul>

Note here how we've provided an extra parameter to the as() call - a hash of 'formatting control parameters'. This hash contains a single parameter, 'repeat', with a value of 'true'. This tells the receiving type to iterate over each item given. Without it, the entire content of the Array would be placed inside of a single html:li entry (which is probably not the desired effect).

When templating, the same effect can be achieved by using the 'dot-percent splat' syntax (.%*):

TP.ac(1, 2, 'that\'s cool').format('The list is {{value .%* html:ul}}');

That .%* serves the same purpose as supplying a value of 'true' for the 'repeat' formatting control parameter.

Using named sub templates

As stated before, if an as...() method cannot be found on the receiving object's type, the system will look for transform<TypeOfReceivingObject>() method on the String type.

While we saw earlier that the String type implements .transformDate() to provide Date formatting, it will, by default, try to find a template registered under that name. It is possible to register pre-compiled templates under a particular 'name' using the .compile() method of String (note that the true as the second parameter here is used to tell the system whether to update any entries cached under the same name with this new template):

'This is a row value: {{value}}\n'.compile('rowTemplate', true);

We can then invoke the template:

TP.ac('first', 'second', 'third').at(1).as('rowTemplate');

Produces:

This is a row value: second

Of course, we probably want to invoke the rowTemplate template as a 'partial' from another template. Given prior examples, this works how you might expect - just invoke it with our 'dot-percent' syntax using the name that we registered the template under as the expression format. Because we're formatting the whole Array, we need to iterate over each entry to format it, so we use 'dot-percent-splat':

TP.ac('first', 'second', 'third').format('Here is some row data:\n{{value .%* rowTemplate}}');

Produces:

Here is some row data:
This is a row value: first
This is a row value: second
This is a row value: third

Using inline sub templates

In addition to having 'named' sub templates, it is also possible to have 'inline' sub templates - templates that occur directly in the template format part of the template expression. Here's an example using an XML data source and an XPath access path to get the data and a sub template to format it with:

dataElem = TP.tpelem('<foo><baz bar="moo"/></foo>');

dataElem.format('The name of the element with a bar attribute is: {{./*[@bar] .% "It really is: {{localName .% startUpper}}"}}')

Produces:

The name of the element with a bar attribute is: It really is: Baz

As you can see, the template format here is itself another template. The data 'scope' is set to the object that is resolved according to the template value part of the template expression.

Template helpers

Authors can invoke 'helpers' inside of templates to assist in managing data, looping, etc. These helpers are custom pieces of logic that are invoked by the templating engine. Authors who write JavaScript can also author their own custom helpers for use in templates.

with

Let's take a look at a simple example of a helper: {{:with}}.

Many times you are dealing with nested data and you would rather not repeat the 'entire path' in each template expression. Here is our example earlier:

dataObj = TP.hc('data', TP.hc('firstName', 'Bill', 'lastName', 'Edney', 'age', 47));
dataObj.format('Hi there {{data.firstName}} {{data.lastName}}. You are {{data.age}} years old');

It would be much nicer to not have to repeat the 'data.' in each expression. This is where the {{:with}} helper can help:

dataObj.format('Hi there {{:with data}} {{firstName}} {{lastName}} {{/:with}}. You are {{data.age}} years old');

Note how both expressions inside of the :with do not have to qualify their access to the data object with 'data.', but the last one does because it is outside of the {{:with}}...{{/with}} block (the {{/:with}} ends the 'block' of statements covered under that {{:with}}).

Also note that the {{:with}} itself can contain a sophisticated access path:

dataObj.format('Hi there {{:with foo.bar.baz}} {{stuff}} {{morestuff}} {{/:with}}.');

Another way helpers are used are as control structures.

for

To loop over data provided, template authors can use the {{:for}}...{{/:for}} helper. Just like the {{:with}} helper, the {{:for}} helper sets the 'scope' of the data enclosed by it to resolve against. Here's an example:

dataObj = TP.hc('firstName', 'Bill', 'zipCodes', TP.ac('60031', '87544', '95014', '63139', '63109'));
dataObj.format('Hi {{firstName}}. {{:for zipCodes}}You\'ve lived in zip: {{value}}\n{{/:for}}');

Note how we use the word 'value' inside of the {{:for}} block to refer to each individual value. As stated earlier, this refers to the 'whole value' of the value being provided (in this case, the item at that spot in the array).

Just like with the {{:with}} statement, the {{:for}} itself can contain a sophisticated access path:

dataObj.format('Hi there {{:for foo.bar.baz}} {{stuff}} {{morestuff}} {{/:for}}.');

{{:for}} special variables and block parameters

Note that expressions inside of {{:for}} constructs in TIBET templates can use two special variables to access the item being processed and it's index in it's overall collection. These two variables are named item and index respectively.

dataObj = TP.hc('world', 'Earth', 'words', TP.ac('Where', 'will', 'we', 'go?'));
dataObj.format('Hello {{world}}. {{:for words}}{{item}} is at: {{index}} {{/:for}}');

Note that using item is the same as using value as we've demonstrated previously (to access the 'whole value').

Also, if you prefer, you can define two parameters to the {{:for}} block that will be set to the 'item' and 'index' values, so that you can use alternate names:

dataObj = TP.hc('world', 'Earth', 'words', TP.ac('Where', 'will', 'we', 'go?'));
dataObj.format('Hello {{world}}. {{:for (thing, position) words}}{{thing}} is at: {{position}} {{/:for}}');

if…else

To control template generation based on conditional expressions, template authors can use the {{:if}}...{{/:if}} helper. Unlike the {{:with}} and {{:for}} helpers, this helper does not change the 'scope' of the data. Here's an example:

dataObj = TP.hc('data', TP.hc('firstName', 'Bill', 'lastName', 'Edney', 'age', 47));
dataObj.format('Hi there {{data.firstName}}.{{:if data.age}} You are {{data.age}} years old.{{/:if}}');

As you might imagine, the section of the template stating 'You are XX years old' will only be printed if the data object given has a value at the access path 'data.age'.

The {{:if}} helper can also use the {{:else}} 'sub' helper to support an 'else' style construct. Here's an extension of our above example. Note how it also shows that we can supply expressions to the {{:if}} helper:

dataObj = TP.hc('data', TP.hc('firstName', 'Bill', 'lastName', 'Edney', 'age', 47));
dataObj.format('Hi there {{data.firstName}}.{{:if data.age > 37}} You are old.{{:else}} You are not so old.{{/:if}}');

Escaping templating characters

Sometimes it is desirable to use bracket characters ('{' and '}') as literal characters in the output of a template. It is easy enough to do this: simply use the backslash to escape the bracket, as you would in standard JavaScript:

//  Note the double-backslash here because we're in Javascript ;-)
TP.dc().format('The year is: \\{{{value .% YYYY}}\\}');

Produces:

The year is: {2014}

Using templates in URIs as template formats

It is possible to use URIs as template formats when templating data. This is another way of doing a 'partial' template in TIBET. To do this, a JavaScript or X(HT)ML template must be set as the resource content of a URI. That URI can then be referenced directly in the template as the template format and will be invoked to transform the data when the template is executed.

Although this template URI will usually be a remote URL of some sort, in TIBET it is possible to register template content (a regular JavaScript String or markup element) as the content of a URN, which is useful for our demonstration here:

TP.uc('urn:tibet:rowTemplate').setResource('This is a row value: {{value}}\n');

While it is possible to invoke this using the transform() method on the TP.core.URI type:

TP.uc('urn:tibet:rowTemplate').transform(TP.ac('first', 'second', 'third'));

it is more likely that you'll want to reference and invoke it directly inline in a template:

'The data is: {{value .% urn:tibet:rowTemplate}}'.transform(TP.ac('first', 'second', 'third'));

Partial templates

It is also possible to reference part of a template that contains markup using XPointer syntax. Again, we use the content of a URN as a template (note that it is necessary to TP.wrap() the native XHTML node returned by TP.xhtmlnode() into a TIBET type to use as the template):

//  Define a template with 2 elements in it.
templateElem = TP.wrap(TP.xhtmlnode('<span><span id="header">The header is: {{value}}</span><span id="body">The body is: {{value}}</span></span>'));
TP.uc('urn:tibet:tableTemplate').setResource(templateElem);

and then use it (note the use of an XPointer 'barename' - an ID reference - here):

//  Invoke the template, but refer only to the 'body' element
'{{value .% urn:tibet:tableTemplate#body}}'.transform('Hi there'));

As you can see, templating in TIBET is exceptionally powerful through the combination of TIBET URIs, XPointers, XPath, JSON Path, TIBET's custom paths, get, and numerous other features.

cookbook

See the 'concepts' section for examples. We won't duplicate them here.

code

String substitution is largely coded in ~lib/src/tibet/kernel/TIBETNativeTypes.js. See the `substitute and substitution-related methods in that file for more details.

TIBET client-side templating is largely coded in ~lib/src/tibet/kernel/TIBETTemplating.js however aspects related to templating and tag processing will also be found in other TIBET kernel files.

TIBET Data Server (TDS) templating is found in ~lib/tds/tds_base.js and is effectively just pure handlebars integration and extensions for processing JSON more cleanly.