Your app's web page will need to include something like this:
<script src="zynii/zynii.js"> </script>
Change the path to match the actual location of the zynii.js file on your setup.
The most important method in ZYNII is newApp(model, schema, options)
. You
use this to create a single app object which then handles your data-model
and displaying of data in the HTML. Typically you would use:
var app; //global scope function load() { app = ZYNII.newApp(model, schema, options); }
app
.
This sets up the initial values of the model. It is probably a good idea to setup a skeleton data structure using this. This is especially true if using the schema to load data from remote sources.
schema
is optional. It may be omitted, replaced by
null
or an empty object {}
. If supplied, the
schema consists of an object. Each key should be a path in the data model.
var schema = { '@local' : {persist:'local', localKey:'TEST1'}, '@web' : {persist:'http', getURL:'service.php', loadInterval:600} };
The value is an object with the following properties:
.persist
local
| http
| file
. Specifies
whether data is stored in localStorage, on the server or in a file. The last
option only works under nodeJS..localKey
.pathName
.getURL
.postURL
.loadInterval
.storeInterval
If loadInterval/storeInterval
are omitted data must be loaded/stored manually
using app.model.load(path)/store(path)
.
The options
object is optional. It may be
omitted, replaced by null
or an empty object {}
.
If supplied, options may contain any of these properties:
.mode
blur
| click
. User interface behaviour. User changes
take effect either when the user leaves a control (blur) or immediately on any change..tick
newApp(model, scema, optons)
.model
addFunc(name, kind, func)
makeDirty(path)
app.bind(el)
el
and it's descendants to the data-model.
If el is omitted document.body is used.app.unbind(el)
subscribe(f)
unsubscribe(f)
debug()
You can add your own functions to the model. These functions may be
used to draw HTML, calculate values, sort or filter content. kind
is a string defining the type of function. It is (currently) optional except
for 'sort' and 'filter' for those kinds of functions. Add a function
with:
binder.addFunc('myFuncName', 'filter', function(..) { ... custom code goes here ... });
Each app object owns it's own data-model at app.model
.
This supports the following methods:
.execJSON(path,value)
path
to the
new value
..asJSON(path)
path
..exec(cmd, path, value)
cmd
is one of C|R|U|D
. Standing for
create, read, update and delete..load(path)
.store(path)
.debug()
.setOnChange(f)
Your app object owns a single data-model. This may be thought of as being
something like a single JSON data structure.
You get/set data in the model using zynii paths. The path is the same string
you would have used in javascript code to refer to that item, but prefixed with
@
. So if the data-model looks like:
{ 'a': [0,1,2], 'b': {'x':0, 'y':2} }
valid paths might be @
@a
, @a[1]
and @b.x
. Zynii is slightly stricter than javascript. If the
item is an element in an array you must
use [n]
, if it's a property of an object you must use .name
.
So @a.1
or @b['x']
are forbidden.
It is probably simplest to think of the data-model as being a JSON
object. However there are one or two minor (but quite sensible) syntax
differences. When setting values you are actually using javascript
not JSON. So you are not bound by the strict syntax rules of JSON. This
means you can use either '
or "
for strings.
And even omit them on object key names.
Also, in javascript property keys can be any string. Including
strings with spaces or other non-alphanumeric characters. In Zynii you
retrieve data by paths. This means you should only use key names that can
be written into a path. @x.longKeyName
is
fine, @x.long key-name
isn't.
Much of the power of zynii comes from the ability to include zynii
expressions in the page's HTML or a template in the page. Expressions are
made up of one or more short statements contained in a data-zynii
or data-zynii-xxx
attribute.
An expression is made up of one or more statements separated by
;
. Statements typically consist of a token (lhs) followed by
=
and then an expression on the rhs. The lhs is the name of
some part of an element whose value will be set. This may be an
attribute/property or an event handler. If the lhs and =
is omitted
the rhs is bound to the element's value or it's innerHTML (depends on the name of the element).
The syntax is somewhat like javascript. But note, this is not simply javascript code, instead it is a declaration of a relationship between the element and the model.
The rhs may be a simple value, e.g. a string, number or boolean value. It may be a more complex JSON style object or array. These have the same syntax as in javascript. In addition, the rhs can be a zynii path or a function defined in or added to the app. Functions can be passed arguments and these can be any valid rhs expression. So functions can be passed other functions, paths, javascript values.
Strings and paths also support a substitution facility. A path/string can include { .. }
containing a valid expression. Each time the expressions is evaluated, the substitution will
be evaluated first.
Notes: Although the syntax is supposed to be javascript-like. There are a few differences.
Paths are text sequences prefixed with an @
. For this reason @ is forbidden
in function and property names. Since the expressions will be placed within an
html attribute string, it helps to be able to use either form of string
'
or "
. So the syntax for values is more
permissive than strict JSON.
Simplified grammar (BNF) for zynii expressions:
program = statement [ ; statement ]* [ ; ] statement = ( assignment | expr ) [ : type ] assignment = token = expr expr = boolean | number | string | array | object | path | function boolean = false | true // as javascript syntax number = [+|-]numeric+[.numeric+][e|E[+|-]numeric+] // as javascript syntax string = " [A | S | subst]* " | ' [A | S | subst]* ' array = \[ [expr[ , expr]*] \] object = { [ key : expr [, key : expr]*] } key = simpleString | token simpleString = " [ alpha | numeric | S ]* " | ' [alpha | numeric | S ]* ' path = @ [A | subst]+ function = token ( [ expr [ , expr]*] ) subst = { expr } A = alpha | numeric | . | [ | ] // everything except whitespace and "'(),{} type = 'boolean' | 'number' | 'string' | 'array' | 'object' token = alpha [ alpha | numeric]* alpha = A..Za..z_$ numeric = 0..9 S = whitespace // ' ' \r \n \t
Zynii supports a small number of HTML attributes. These are used to setup a binding between an HTML element and data in the model.
attribute | type | value |
---|---|---|
data-zynii |
statement(s) | bind value, events and attributes/properties. |
data-zynii-format |
type | string|number|boolean|eN|fN |
data-zynii-lookup |
zynii path | location of array of lookup values |
data-zynii-draw |
name | name of template/function that draws a child |
data-zynii-sort |
function | one or more sort/filter functions |
The data-path
attribute holds a path string that
identifies the location in the model that holds the value to be linked
to this element. The format of path strings is documented in a later section.
Binds a function to the element's onclick event. For example:
<button data-zynii-click="doIt(@x.y, 'test')" ...
every time the button is clicked the function doIt
will
be called. It will be passed the value at path location @x.y
and the string 'test'
. The called function might look like:
function doIt(a, b) { // do something with a and b return false; }
Note: inside the function, this
refers to the element the handler
is attached to.
HTML values are always strings and a javascript value can hold any of a number of different types. To ensure data is formatted correctly and also stored in the model in the desired type, you can specify the format. This can be one of:
boolean
string
number
fN
eN
boolean
, string
and number
ensure
that the value saved into the model are of those types. For numeric values
you can, instead, use fN
or eN
. These will display
the number either in fixed notation or exponential notation with N being the number
of digits after the decimal point.
data-zynii-format
is optional. If omitted the data is
considered to be a string.
This attribute is typically used by a <select>
element. It provides a path to a lookup array in the model. The array is
a list of string values.
["option one","option two","option three","option four","option five"]
Usually simply the name of the template to be used to draw any child entries. Can also be the name of a function to be used instead.
One or more functions that sort/filter the data. The output of each function is fed into the next.
The data-model is likely to include items that have variable numbers of child items. It is useful to be able to generate HTML for these automatically.
Zynii provides the option of specifying a template for the HTML. This template is then used to generate repeating HTML for elements of an array or for properties of an object.
A template is defined in the HTML, using the HTML5 <template>
tag. This needs to have a unique name specified in the id
attribute.
<template id="drawRow"> <tr> <td data-zynii="@@[0]" data-zynii-format="string"></td> <td data-zynii="@@[1]" data-zynii-format="string" contentEditable="true"></td> <td data-zynii="@@[2]" data-zynii-format="number"></td> <td data-zynii="@@[3]" data-zynii-format="number" contentEditable="true"></td> </tr> </template>
You can put any HTML you like inside the template element. However, you
probably shouldn't nest <template>
elements. You can use any of the
data-zynii-xxx attributes as in the main HTML. Within these the string @@
is a placeholder for the current path. The generated HTML will have the item's
path inserted to replace the @@
. The string @@@
acts as a placeholder for the current index/key of the current entry. If
the data is in an array this will be a number 0 or higher. If the data is in
an object this will be the key/property name.
Note: templates will often be located in the HTML - where the generated HTML will be placed. But this isn't a requirement. Templates can be placed anywhere where they will be found by bind(). They must, however, have a unique id.
<template>
tag is not fully supported in all browsers (not supported in Safari or IE).
Thererfore for most situations the alternative outlined below should be
used.
The alternative to using the <template>
tag, is to use some other
tag but with it's class set to template
. However there are
a number of limitations imposed by certain browsers.
You can use any tag name for the template's outer element. However it has to
appropriate to the child elements you are using. If you are creating a
list of li
elements your template should be a ul
or ol
tag. If your content were table rows, it might be best to make the
template a <tbody>
tag.
However, some browsers will fail if the element is
incompatible with the content. If the content of your template is an <li>
element, you should use a <ul>
or <ol>
element for the
template. If the content were a row, it might be best to make the
template a <tbody>
element. For example:
<tbody class="template" id="drawRow"> <tr> <td data-zynii="@@[0]" data-zynii-format="string"></td> <td data-zynii="@@[1]" data-zynii-format="string" contentEditable="true"></td> <td data-zynii="@@[2]" data-zynii-format="number"></td> <td data-zynii="@@[3]" data-zynii-format="number" contentEditable="true"></td> </tr> </tbody>
Once you have defined a template you need to ensure that it is called
when required. This is done using the data-zynii-draw
attribute.
Like so:
<tbody data-zynii="@x" data-zynii-draw="drawRow"> </tbody>
In this case, if x
was an array, there would be one row in
the table for each element in x
. If x
were an object
there would be one row for each child property.
It is also possible to call a javascript function to generate the HTML rather than using a template. Instead of defining a template with a given name, add a function to the app with that name. (see addFunc() function for details.)
This is more complex than using a template but offers more options - i.e. the javascript language. One situation where this might be useful, is if the children of an array/object were not all of the same format. You might then want to generate different HTML depending on the type/structure of each child.
z.iif(flag, a, b)
z.alphaSort(a, b, reverse)
z.numericSort(a, b, reverse)
z.sum( args )
calculation functions are simple javascript functions. They should take a certain
number of arguments and return the calculated result. These functions can then
be used in expressions in data-zynii-xxx
attributes.
A draw function returns HTML. This can be a string, which will be automatically converted, or it can be an actual HTMLElement. Multiple nested HTML can be created, but there should only be a single HTMLElement at the top-level.
function userDraw(path) { return '<label data-zynii-path="' + path + '">A: <input type="text" data-zynii-format="string" /></label>'; }
The top-level element should have a data-zynii
attribute.
You can mark a different element as being the one that holds the value
using data-zynii-format
or data-zynii-at
.
A sort function takes a minimum of two arguments and then returns -1,0,1 depending on
the result of comparing the first two arguments. This is the same mechanism as used by
Javascript's Array.sort()
method:
function customSort(a, b) { var result = .... return result; // -1, 0, -1 }
a
and b
are the child items being compared.
Within the custom sort function this.
refers to the parent object.
function sortX(a, b, x, y, z) { var result = .... return result; // -1, 0, -1 }
In this second example x, y, z
are the current values of
the expressions provided where the sort function is called. For example:
<ul data-zynii-path="sortX(@x, sum(@a[2],@b[2]), @z)" ... > ...
A filter function takes a single element and returns true if it should be included in the result. It is possible to apply several functions, in which case they are ANDed together. Only values that match all filters are returned.
Filter functions work in a similar way to Array.filter()
functions in javascript. Function is called repeatedly for each element in array.
The first three arguments are the item it's index in the array and the entire array.
Your own arguments follow after those three. The function should then return
true if the item is to be included in the results.
function customFilter(item, index, x, ... user arguments ...) { var result = .... // your filter return result; //true|false }
Event functions are of the form:
function(e [, args ...]) { // your code here. }
The first argument of the function is the event object. This is the same one
that is usually passed to an event handler in javascript. Inside the function
this.
refers to the HTML element to which the handler has been
attached. This is the same as for normal javascript development.
One caveat. In Javascript it is possible to attach multiple event handlers to a single event. Zynii does NOT support this. You can only add a single event handler using a zynii expression. Also, for this reason setting an event handler via zynii and also via javascript is likely to lead to unpredictable behaviour.
ZYNII.transform(html, json);
html
should be an HTMLDocument. json
is a
javascript data structure. The function performs a transformation of the supplied
HTML merging it with the json data. The HTML can contain all the same zynii attributes
and templates. However the process is one way, changes to values in the HTML are ignored.
It is intended as a way of generating report style HTML pages from the data.
Note: The transformation is actually performed on the supplied HTMLDocument. If you want to repeat the transform, you will need to reload the html or clone it each time before use. (This may change in the future!)
A basic installation of zynii consists of the following files:
zynii.js
debug.html
dump.html
zynii.js is a single javascript fie containing all of the code required to use zynii in a web application. It has no external dependencies.
debug.html is a web page/application that acts as a debugger for your zynii project. (It is itself a zynii app.) You can link your project to it during development. dump.html is a much simpler debug tool. It simply lists the data used by debug.html.
Zynii is built from a set of source files. There are also a number of test files. You should only download these files if you are intending to contribute to Zynii development.
As well as the standard deployment files above, the source includes the following javascript files:
app.js
model.js
parser.js
poly.js
require.js
transform.js
transform(html, json)
function performs a transformation of the supplied
HTML merging it with the json data. The HTML can contain all the same zynii attributes
and templates. However the process is one way, changes to values in the HTML are ignored.
It is intended as a way of generating report style HTML pages from the data.utils.js
zAll.js
These files are typically organised as node modules. They can be loaded and
tested using nodeJS. zynii.js
is generated by assembling These files are assembled
into the final zynii.js
using browserify.
Source includes the following test harness files:
app.test.js
model.test.js
parser.test.js
require.test.js
transform.test.js
utils.test.js
miniJasmine.js
app.tester.html
model.tester.html
parser.tester.html
poly.tester.html
require.tester.html
transform.tester.html
utils.tester.html
testframe.html