mojule-templating
Templating using HTML nodes rather than string templates. Heavily influenced by
mustache
, eg logicless and declarative. I had a
need to build and manipulate templates at runtime and came up with this as an
alternative to mustache. I initially considered parsing mustache templates into
some kind of AST so that I could do these runtime building/manipulation tasks
but decided that as I'm only dealing with HTML it would be easier to just design
something more suited to this task, particularly as I already have a ton of code
for dealing with trees.
Also, using HTML as the syntax for the templates rather than text with curly
braces, you can embed the templates directly in an HTML <template>
tag.
Quick start
Based on the examples in the mustache docs:
Template
Hello You have just won dollars! Well, dollars, after taxes.
Model
"name": "Chris" "value": 10000 "taxed_value": 10000 - 10000 * 04 "in_ca": true
Output
Hello Chris You have just won 10000 dollars! Well, 6000 dollars, after taxes.
If you don't want the extra noise in the output (eg the <span>
around the
name), you can use the non-standard <fragment data-text="name"></fragment>
element instead, these will be removed post populating the template, see below
for more details. The reason that we leave these elements in by default is that
we will probably enable two way data binding in future.
Token types
Tokens are specified by including a
data-
<token type>
="
<property name>
"
attribute on the target HTML
element with the value set to the name of the property on the model.
data-text
Replace the contents of the tag with the specified text from the model, or an
empty string if the property does not exist on the model. Any instances of <
in the model text are replaced with <
.
Template
Model
Output
Nik<b>Github</b>
data-html
Replace the contents of the tag with the specified text from the model, or an empty string if the property does not exist on the model. HTML is not escaped, and is also parsed for additional tokens.
Template
Model
Output
NikGithub
data-if
If the property on the model evaluates to
truthy, emit the block's
contents. If not, omit them. Compare to data-not
.
Template
Your name is Your age is
Model
Output
Your name is Nik
data-not
If the property on the model evaluates to
falsy, emit the block's
contents. If it's truthy, omit them. Compare to data-if
.
Template
A girl has no name.
Model
Output
A girl has no name.
data-each
If the property exists and is an array, outputs the block for each array
element, using the array element as the context for evaluating each block.
Compare to data-empty
.
Template
Model
Output
Jane Bob
data-empty
If the property exists and is an array but has no elements, emit this block
Template
A girl has no desires
Model
Output
A girl has no desires
data-context
Sets the current context to the property in the model with this name, and disallow searching upwards in the object tree.
Template
Model
Normally, the missing name in user would cause a search up the tree, but by
declaring data-context
we isolate this property from the rest of the model, so
no name
is found:
Output
user@example.com
data-include
Includes the named template, then populate it from the model
Main template
user
template
Model
Output
Alice alice@example.com Jane jane@example.com
data-tag
Used for populating the tagName or attributes of an object.
Template
Hello
Model
The model value is an object or array of objects where the object properties are
action names such as addClass
, and the values are a single argument for that
action, or an array of arguments.
Output
Hello
Action types
Note that the value can also be a single object rather than an array - we allow
an array here because you might want to use an action more than once, as in the
case of toggleClass
above.
Single object in model
The possible actions are:
tagName
A single string containing the tagName to use:
attr
An array with two elements, the first is the attribute to set, the second is the attribute value:
removeAttr
A single string with the attribute name to remove
addClass
A single string with the class to add:
removeClass
A single string with the class to remove:
toggleClass
A single string with the class to toggle - that is, add it if it does not already have it, and remove it if it does
clearAttrs
Remove all attributes from the tag
clearAttrs
Remove all attributes from the tag
clearClasses
Remove all classes from the tag
attributes
An object map of attribute names to values - removes all existing attributes, and replaces them from this map
TODO: this does remove and replace, right? Not extend existing?
<fragment>
element
The This non-standard element wrapper is removed from the output after all the tokens have been populated. This way you can wrap your blocks with something that won't appear in the final output.
Template
Model
Output
Alice
Differences and similarity with mustache
We operates on tree nodes rather than strings, and use more token types.
We use the same scoping as mustache, eg search the model's object tree upwards
from the current context until we find a variable matching the token name.
Unlike mustache, you can override this behaviour by explicitly setting the
context for a block using the data-context
attribute. This is useful if you
want to ensure you don't accidentally fill a missing value in with an unrelated
value with the same name which exists higher up in the object tree.
Like mustache, a variable miss results in an empty string.
We use more token types, as although mustache does a similar job with less types, some of them are overloaded (eg do different things depending on the type of the model data). After writing thousands of mustache templates I found in practise that it can be hard to tell what a template does just by looking at it, you have to find the models that are being used to populate them and I decided I'd rather have more tokens that are more explicit in what they do to aid readability.
Mustache variable
Normal variable replacement, eg escapes <
to <
.
{{something}}
mojule-templating:
Mustache variable, unescaped
Literal variable replacement, eg don't escape <
in html strings.
{{{something}}}
mojule-templating:
Mustache section
Overloaded in mustache. You can't always tell by looking at the template if
something
in this instance is a boolean/truthy value, if this is an array
section where the contents of the block are repeated, or if this is an object
value and the context is to be set to that object, so we have a seperate token
for each of these cases.
{{#something}} block text{{/something}}
mojule-templating:
block text block text block text
Mustache inverted section
Overloaded, using the same rules as a section but where a boolean is falsey or
an array is empty. Our equivalent tokens mirror those for sections, but there
is no opposite for data-context
as this is handled unambiguously by
data-not
.
{{^something}} block text{{/something}}
mojule templating:
block text block text
Mustacle partial
Just an include, finds the template named something
rather than a property on
the model, and replaces the contents of the block with the contents of the
included partial.
{{>something}}
mojule templating:
Future
Arrays
In mustache you can do something like this:
<ul> {{#people}} <li>{{.}}</li> {{/people}}</ul>
Could we allow this by introducing a data-current
token?
Or data-text="."
?
Or omitting a value defaults to the current context?
The above syntax would also allow a model to be something other than an object:
Two way binding
You could use it to do two-way binding as well, with a combination of extra
data
attributes and mutation observers
The data-
tokens are removed after they're populated, so you would have a
couple of extra tokens on the block to find them later:
Then after populating it would be:
Nik
Set up a mutation observer, if the text changes you can use the information in
data-type
and data-property
to repopulate the model and perhaps post the
model back to the server or do something else like that. You'd also probably
want to find other tags that point to the same property and update those as
well. You could also have something watching the model, or an api to update the
model or whatever than posted back to the nodes when the model changes for full
binding.