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 theTDS.template
function.
Contents
Concepts
Cookbook
Code
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 {{firstName}}');
// Set 'firstName' to a different value
newObj.set('firstName', 'Bill');
// Print it again
newObj.format('Hi there {{firstName}}');
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: <bar/>
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.