BASIC USAGE:
==============
var T = new Zen(syntax, lazy_load):
syntax: zen/emmet syntax
lazy_load: if true, delays syntax parsing until template is built
var T = new Zen(zen_object)
clone a template
T.DOM:
get the cached dom
use T.DOM.cloneNode(true) to reuse the template
T.build(anonvars, ..., {namedvars}):
for one time usage (or to set default parameters)
returns T.DOM
SYNTAX OVERVIEW:
================
Template syntax is based on Emmet/Zen syntax. It is basically CSS selectors, used to define html documents.
See http://docs.emmet.io/abbreviations/syntax/ to learn Emmet/Zen.
Currently supports the following operators:
+: sibling
>: child
^: climb-up (parent's sibling)
(): grouping
[]: attributes
=: set attribute value
#: id attribute shortcut
.: class attribute shortcut
{}: text content
"': quoted strings
%: template variable
\t\n\r\v\f : whitespace
\: escape character
\0: reserved character, DO NOT USE!!!
~,;*@$&<|!?: in the future, I may extend the grammar to include additional
operators; so, its probably a good idea to treat these symbols as
reserved characters (if you're worried about forward compatability)
FORMAL GRAMMAR:
==================
Emmet/Zen's documentation is a little ambiguous, so here is a formal grammar:
START ::= START "+" START | START ">" START ("^" START)? | "(" START ")" | WHITESPACE* START WHITESPACE* | NODE
NODE ::= TAGNAME? ATTR* TEXT?
TAGNAME ::= CHARACTER*
ATTR ::= ID | CLASS | "[" LIST? "]" | WHITESPACE*
ID ::= "#" CHARACTER*
CLASS ::= "." CHARACTER*
LIST ::= VAL WHITESPACE+ LIST | VAL
VAL ::= STRING | STRING WHITESPACE* "=" WHITESPACE* STRING
TEXT ::= "{" WHITESPACE* STRING_WS? WHITESPACE* "}"
WHITESPACE ::= /[\t\n\r\v\f ]/
STRING_WS ::= STRING | WHITESPACE if no WHITESPACE lookahead
STRING ::= "'" CHARACTER+ "'" | '"' CHARACTER '"' | CHARACTER STRING
CHARACTER ::= non-operator character | operator character not in lookahead | escaped operator character except \0 | VAR
VAR ::= "%" /\w/? "%"
A couple things to note about this grammar, especially changes I made to Emmet/Zen's original syntax:
- multiple attribute definitions are concatenated together into a single space-separated value;
they are concatenated in the order they were defined; for example:
#id1#id2 = #id1[id=id2] = [id=id1 id=id2] = #id1\ id2
.class1.class2 = .class1\ class2
- whitespace:
- for pretty formatting, you may insert whitespace anywhere outside a node definition (TAGNAME, ATTR, TEXT)
- whitespace at start/end of TEXT definition is ignored
- to include whitespace in a node definition (TAGNAME, ATTR, TEXT), use a backslash to escape the whitespace;
for LIST attributes (stuff inside brackets) or TEXT definitions, you can also use a quoted string
- template variables can be inserted anywhere in a node definition (TAGNAME, ATTR, TEXT)
- ID and CLASS can be empty; however, attribute names/values cannot be empty
div[=test]: INVALID
div[blah=]: INVALID, use div[blah] instead
div#.##.[blah=test]: VALID
The minified version does no error checking, so be careful with empty values! div[blah=test =man] means test=man, not blah=test
- if no TAGNAME is specified, "div" is used by default
- NODE can be empty; if NODE is empty, it is treated as "div"
"p>>" = "p>div>div"
- to be forward/backward-compatible with HTML/XML, there are no character restrictions for TAGNAME/ATTR
- strings can be a mix of quoted and unquoted text; use a backslash to interpret quotes literally
unquote"quote"unquote = unquotequoteunquote
unquote\"quote\"unquote = unquote"quote"unquote
- TEXT is interpreted as a DOM TextNode; this means div{"test"} is just a shortcut for div>{"test"}^;
since it's a TextNode, it cannot contain any HTML code; use div[innerHTML="test"] instead- be
warned though, that this will have undefined behavior if the node has children:
div[innerHTML="X"]{Y} = ????
div[innerHTML="X"]>div.Y = ????
Likewise, textContent/nodeValue attributes will have similar undefined behavior.
QUOTED VALUES:
================
Attribute values and text content can contain a quoted string:
["type"="javascript"]{"content"}
Backslashes are special characters; so you need to escape them:
{"backslash\\"}
That means you'll need to double escape them inside a javascript string:
"{\"backslash\\\\\"}"
If you need a lot of backslashes, use a template variable instead
"{%backslash%}"
Or use the Template.escape method:
"{"+Template.escape("backslash\\")+"}"
UNQUOTED VALUES:
====================
If you don't want to quote the string, but the content begins with a quote, escape the starting quote:
[type=\'sup]{\'cuz}
Make sure you have the correct number of backslashes:
{'test'} = "test"
{\'test} = "'test"
{\\'test} = "\'test"
Or you can just use the Template.escape method, as shown above
TEMPLATE VARIABLES:
=====================
To define template variables, use %VAR_NAME% - VAR_NAME can be [-\._a-zA-Z0-9]
var t = new Zen("div{%text%}");
var dom = t.build({text: "Hello world!"});
document.body.appendChild(dom);
To reuse the template, you can clone the output DOM:
var dom = t.build({text: "Hello world!"}).cloneNode()
If you need a fresh copy of the template (assuming no variables have been applied), use the copy constructor
var t = new Zen(other_zen_template)
To access the cached template DOM:
var cache = t.DOM;
If you append the cached DOM directly, you can update variables still:
document.body.appendChild(t.DOM);
t.setValue("name", "dynamically updated on page")
Text variables rely on text nodes. If text nodes on your page could be normalized, and you need to
update the variable values after they are added to the DOM, wrap the variables in a separate tag:
var t = new Zen("{Name = }+span{%name%}");
You don't have to have named variables; use %% for unnamed vars:
var t = new Zen("div{%%}");
var dom = t.build("Hello world!");
To use unnamed and named, use this syntax:
t.build(unnamed, ..., {named});
Variables for "dataset", "style", and "attributes" must be specified as objects (except style, which
can be a string, in which case it is set to the string directly):
var t = new Zen("[dataset=%data% style=%styles% attributes=%attrs%]")
var dom = t.build({
data: {x: "data-x value"},
style: {border: "1px solid black"},
attributes: {contentEditable: true}
})
Note that these values may overwrite ones explicitly declared, if they are set afterwards:
var t = new Zen("[data-x=test dataset=%data%]")
var dom = t.build({
data: {x: "this will override the previous value, 'test'"}
})
If the template variable is not surrounded by text, it will be interpreted literally (only for attribute values):
div[data=%%]: data can be any JS object
div[data=text%%]: data will be a JS string, since "text" gets prepended
Currently, variable attribute names cannot be combined as a space-separated list. They simply override any previous values.
div[%x%=7 %y%=8]: when %x% == %y%, %y% overrides %x%
#id[%x%=override]: when %x% == id, the previous value ("id") gets overriden
You may use template variables for attribute names, and attribute values.
tagname #%id% [%name% = %value%] {%text%}
Variable attribute names are a bit more expensive, than variable values. If performance
is a concern, avoid variable attribute names. Of course, using no template variables all together
will be fastest. Variables inside tagnames used to be allowed, but I have since removed it, since
it wouldn't have had very many use cases (and would be an expensive computation).
FRAGMENT MULTIPLIERS (NOT IMPLEMENTED YET):
============================================
You can repeat a fragment of your template using a multiplier operator:
"div*5" -> repeats div five times
"(div+button)*5" -> repeats the group five times
You can specify a variable as the multiplier:
var t = new Zen("div*%x%")
t.build({x: 5}); -> repeats div five times
The multipler value can also be zero, thus making the fragment optional:
t.build({x: 0}); -> div will not be included in the template
If there are variables inside fragment, you can specify their values using an array:
var t = new Zen("#%id%*%x%");
t.build({x:[
{id: "id0"},
{id: "id1"},
{id: "id2"},
]}); -> creates three divs, each with different ids
TODO:
========
*: for multiplication
!{}: for html comments
named DOM nodes for adding listeners and stuff, maybe with & or @ symbol before node definition
disable quotes in {} ??? maybe make values either quoted or not quoted; can't be both
quotes for states 1-2 ???
allow tags as input