ZPT-JS is a Javascript implementation of Zope Page Templates (ZPT). Because ZPT-JS isn't running in the context of Zope and isn't written with Python, there are necessarily some differences between ZPT-JS and ZPT. This document will concentrate on the ways that ZPT-JS differs from ZPT. For an introduction to ZPT refer to the chapter Using Zope Page Templates in the Zope Book. For a complete reference to ZPT, refer to the ZPT Reference.
How can we use ZPT-JS? The dictionary
is used to define global variables.
An example of invoking ZPT-JS using CommonJS in a browser environment:
sample.js"use strict"; var zpt = require( 'zpt' ); var dictionary = { aString: "string", doggy: false, number1: 1, number100: 100, user: { name: "Bob", age: function( ){ return 25; } }, items: [ 'item0', 'item1', 'item2' ] }; zpt.run({ root: document.body, dictionary: dictionary });
Then browserify that file to build the final js file. And the html file is:
sample.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Some ZPT-JS examples</title> <script src="sample.js" defer></script> </head> <body> <h1>Some expressions</h1> <ol> <li data-tcontent="user/name">xxx</li> <li data-tcontent="string:help my ${user/name}">xxx</li> <li data-tcontent="doggy">not false</li> <li data-tcontent="not:doggy">not false</li> <li data-tcontent="eq: number1 number100">not true</li> <li data-tcontent="user/name | string:no friends">any friends?</li> <li data-tcontent="user2/name | string:no friends">any friends?</li> <li data-tcontent="items[0]">an item</li> <li data-tcontent="user/age()">user/age()</li> </ol> </body> </html>
The invokation of ZPT-JS is at:
zpt.run({ root: document.body, dictionary: dictionary });
The first argument sets the DOM node where ZPT-JS will search its custom HTML tags: it can be a string or an array of them. The second one is the dictionary, a Javascript object that will be available to all custom HTML tags of ZPT-JS. You can also run ZPT-JS as a Jquery plugin:
"use strict"; var $ = require( 'jquery' ); require( '../../../js/app/jqueryPlugin.js' ); var dictionary = { ... }; // Your dictionary entries $( 'body' ).zpt({ dictionary: dictionary });
If we don't use external macros ZPT-JS executes synchronously: no external file needs to be loaded. But if we use at least one external macro ZPT-JS needs to load one or more external files using HTTP. This makes ZPT-JS code executes asynchronously. Keep in mind this! An alternative method to deal with this is to call ZPT-JS like this:
var zptParser = zpt.buildParser({ root: document.body, dictionary: dictionary, declaredRemotePageUrls: [ 'externalMacros-definitions2.html', 'externalMacros-definitions3.html' ] }); zptParser.init( function(){ zptParser.run(); [ your code here ] } );
That's OK. But... how can we use ZPT-JS at the server side (using node.js)? node-jsdom is needed when no browser is available:
"use strict"; var jsdom = require( 'node-jsdom' ).jsdom; jsdom.env( '<!doctype html>' + '<html>' + '<body><h1 id="t1" data-tcontent="string:hello">a text</h1></body>' + '</html>', [ 'http://code.jquery.com/jquery.min.js' ], function( err, window ) { // Check if an error occurs if ( err ) { console.error( err ); return 1; } // Copy window to global global.window = window; // Copy from window to local vars var $ = window.$; var document = window.document; // Parse template var zpt = require( 'zpt' ); zpt.run({ root: document.body, dictionary: {} }); console.log( 'Done!' ); } );
Take a look at jsdom docs for more info about jsdom.
The name of tags are not the same because if they were the resulting HTML douments will not be well formed. The list of tags with their equivalences are:
ZPT tag name | ZPT-JS tag name | Description |
---|---|---|
metal:define-macro | data-mdefine-macro | Defines a macro |
metal:define-slot | data-mdefine-slot | Defines a slot to make possible to fill it later |
metal:fill-slot | data-mfill-slot | Fills a slot |
metal:use-macro | data-muse-macro | Invokes a macro |
tal:attributes | data-tattributes | Replace the value of one or more attributes |
tal:condition | data-tcondition | Display or hide nodes depending on a condition |
tal:content | data-tcontent | Replace the content of the element |
tal:define | data-tdefine | Define one or more variables |
tal:on-error | data-ton-error | Handle errors |
tal:omit-tag | data-tomit-tag | Remove an element, leaving the content of the element |
tal:repeat | data-trepeat | Repeat an element |
tal:replace | data-treplace | Replace the content of an element and remove the element leaving the content |
The tags can be changed. Customize the defaultTag
variable in js/app/context.js
file. You can also use context.setTags( tags )
to define the tags programmatically.
Important: after this point we will refer to tags as the ZPT-JS tag name.
The data-* attributes is used to store custom data private to the page or application. These are the standard in HTML5. If you prefer to use the original ZPT's attributes use context.useOriginalTags()
method:
"use strict"; var zpt = require( 'zpt' ); var dictionary = { aString: "string", doggy: false, number1: 1, number100: 100, user: { name: "Bob", age: function( ){ return 25; } }, items: [ 'item0', 'item1', 'item2' ] }; // Don't forget to declare to use original tags! zpt.context.useOriginalTags(); zpt.run({ root: document.body, dictionary: dictionary });original-tags-sample.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Some ZPT-JS examples</title> <script src="original-tags-sample.js" defer></script> </head> <body> <h1>Some expressions</h1> <ol> <li tal:content="user/name">xxx</li> </ol> </body> </html>
The order evaluation of attributes in ZPT-JS is not equal than ZPT's. The new order is:
data-trepeat
data-ton-error
data-mdefine-macro
data-tdefine
data-tcondition
data-tomit-tag
data-treplace
data-tattributes
data-tcontent
data-muse-macro
The first element in a path expression must be a variable, a method call, a function call or a literal.
Integer, float and boolean literals are defined in the same way as in the Javascript language. String literals are delimited by single quotes. Some example literals:
9
(integer literal)9.0
(floating point literal)true
(boolean literal)'foobar'
(string literal)
A variable is either predefined, defined via a data-tdefine
attribute, or passed in to the template at runtime. The following variables are predefined:
repeat
see ZPT reference
The here
variable (the context) is not defined in ZPT-JS; it is implicit. The scope of all variables in dictionary is global. Take a look to Invoking ZPT-JS to understand how you can define variables of the dictionary.
The scope of the variables defined via a data-tdefine
attribute is local. An example:
<div data-tdefine="a number"> <span data-treplace="a">5</span> </div> <span data-treplace="a">null</span>
At the first replace the value in number
will be used. At the second replace, a null
value (the a
variable is out of scope).
Global variables are also implemented. Their scope is all document after their definition:
<div data-tdefine="global a number"> <span data-treplace="a">5</span> </div> <span data-treplace="a">5</span>
The following variables are defined in ZPT but not in ZPT-JS:
here, template, resolver, options, CONTEXTS, root, container, request, user, modules
. The following variables are defined in ZPT but aren't yet implemented in ZPT-JS:
nothing, default, attrs
. If you need these, holler.
Following the initial path element, path elements are either properties or methods of the preceding object. Examples:
object/name
will search for a property name
in the passed object.object/name()
will search for a method name()
in the passed object.object/name( arg1, arg2 )
will search for a method name()
in the passed object and pass arg1
and arg2
as parameters.object/name()/name2()
will search for a method name()
in the passed object and then search for a method name2()
.
The last element in a path expression may resolve to null
, but if an intermediate element resolves to null
an Exception
will be thrown.
Array members may be accessed using the same syntax as in Javascript. Any number of dimensions are supported. The expression inside the array accessor may be any TALES expression and must evaluate to an integer value.
people[ 2 ]
people[ index ]
Object properties may be accessed using the same syntax as in Javascript too.
user[ 'name' ]
user[ member ]
Javascript expressions work just like Python expressions in ZPT except that the Javascript language is used instead of Python. Any legal Javascript expression may be evaluated. Some examples:
js: 4 + 5
returns the int 9js: ${aNumber} + 1
returns the result of adding 1 to the value of aNumber
variable.It evaluates as a boolean expression. If the expression is any of the next:
undefined
null
'false'
false
0
the expression evaluates to false
. Otherwise the expression evaluates to true
. If an exception is thrown trying to evaluate the expression, it evaluates to false
.
Not expressions work more or less like in ZPT. The expression to which not:
is applied must first be cast to a boolean. The result is then negated.
You can do some math operations using this expressions:
+: x y
-> add x and y-: x y
-> subtract y from x*: x y
-> multiply x and y/: x y
-> divide x by y%: x y
-> x mod y
In all cases, x
and y
are assumed integers. You can use more than 2 values to operate. You can use parenthesis. Some examples:
-: assets liabilities
*: 2 children
+: 1 number1 number2
+: 1 ( *: number1 number2 )
The list of available boolean expressions are:
or: x y
-> boolean or of expressions x and yand: x y
-> boolean and of expressions x and ycond: x y z
-> evaluate as boolean x; if true return y, otherwise return zAll operators uses lazy evaluation. OR and AND expressions support 2 or more operators. COND expression only support 3. An example:
<p data-tcondition="and: ( exists:pets()/dog() ) ( not:pets()/dog()/badDog() )">
Good Dog</p>
<p data-tcondition="or: isFridayNight isSaturday isSunday">
Yeah!</p>
<p data-tcontent="cond: isFridayNight 'Yeah!' 'Oh!'">
What?</p>
The 4 available comparison expressions are:
equals:
Checks if 2 or more integers are equals. If the values are not numbers, it checks if they are equals.in:
Checks if a value is in a list of expressions.greater:
Checks if a number is greater than another.lower:
Checks if a number is lower than another.Some examples:
equals: assets liabilities anObject
-> True
if assets
, liabilities
and anObject
are all equals.in: value 'option1' 'option2' 'option3'
-> True
if value
is 'option1'
, 'option2'
or 'option3'
.greater: 10 ( +: children pets )
-> True
if 10 > ( children + pets)
.ZPT-JS supports some jquery expressions:
<div> <div>value1 = <span id="value1" class="values">10</span></div> <div>value2 = <span id="value2" class="values">20</span></div> <div>value3 = <span id="value3" class="values">30</span></div> </div> <div> <div data-tcontent="$('#value1')">must be 10</div> <div data-tcontent="$('.values')">must be 10,20,30</div> <div data-tcontent="+: $('.values')">must be 10 + 20 + 30 = 60</div> <div data-tcontent="+: 100 $( '.values' )">must be 100 + 10 + 20 + 30 = 160</div> </div>
String expressions behave exactly as in ZPT. Python expressions are not supported in ZPT-JS.
ZPT-JS register the window object automatically, so global variables defined via javascript can be used easily:
<div data-tcontent="window/globalVar">a string</div>
All TAL statements behave almost exactly as in ZPT, except:
tal:no-call
: not yet implementeddata-tcondition
and data-tomit-tag
: must cast their expression to a boolean, which follows the rules described for Not expressions.data-treplace
and data-tomit-tag
: because ZPT-JS modifies directly the template (ZPT generates a new document), these tags are REMOVED when they are not inside a macro definition. So if you update the dictionary a run ZPT-JS a second time these tags does not exist yet. If you need this to work place data-treplace
and data-tomit-tag
inside a macro definition and invoke it. See macros.
There are a few minor variations for tal:repeat
. The repeat expression must evaluate to an array.
A list is defined as an enumeration of items in brackets. Some examples:
[1 2 3]
evaluates as 1,2,3[1 2 3 number1]
evaluates as 1,2,3,1 (if the value of number1
is 1)You can iterate through lists using loops:
data-trepeat="c [10 20 30]"
iterates using c
through 10, 20 and 30data-trepeat="mixed ['yes!' 'no!' aNumber]"
iterates using mixed
Another ways of using lists is as range expressions:
data-trepeat="c [1:5]"
iterates using c
through 1,2,3,4 and 5data-trepeat="c [1:7:2]"
iterates using c
through 1,3,5 and 7data-trepeat="c [:5]"
iterates using c
through 0,1,2,3,4 and 5Lists are very versatile:
[1:3 10]
evaluates to 1,2,3 and 10[4 1:3 10]
evaluates to 4,1,2,3 and 10...and can be used in arithmethic operations:
+: [1:3 10]
evaluates to 1+2+3+10 = 16+: [4 1:3 10]
evaluates to 4+1+2+3+10 = 20[ 1 : 3 ]
will not work!
ZPT's version of data-tattributes
forces to uses key/values pairs. ZPT-JS also allows to use javascript objects. If we define a dictionary this way:
var dictionary = { textareaAttrs: { rows: 10, cols: 100 } };
...we can use this in a template:
<textarea data-tattributes="placeholder 'Write something here!'; textareaAttrs; maxlength 200"></textarea>
METAL statements behave exactly as in ZPT. The main difference, which is really a difference in Path expressions, is the means of finding another template which contains macros. There is no Zope tree in which to locate templates. Use-macro tag uses expressions (ZPT's version does not, it uses literals).
Local macros are defined at the same HTML file where they are invoked. An example of definition of a local macro:
<ul data-mdefine-macro="list"> <li data-trepeat="item items"> <span data-tcontent="item">An item</span> </li> </ul>
That macro generates an unordered list iterating through the items
variable. Let's invoke them; to do this, the next HTML code must be at the same file.
<div data-tdefine="items [10 20 30]" data-muse-macro="'list'"> Macro goes here </div>
ZPT-JS allows to uses expressions when using macros. The next HTML code invokes the same macro if the value of listMacro
variable is list
:
<div data-tdefine="items [10 20 30]" data-muse-macro="'listMacro'"> Macro goes here </div>
External macros are defined at a different page that where they are invoked. They only differ how they are invoked; if we want to invoke the macro list
defined in macros.html
file:
<div data-tdefine="items [10 20 30]" data-muse-macro="'list@macros.html'"> Macro goes here </div>
External macro files must be preloaded before the template is rendered, so code is async:
var zptParser = zpt.buildParser({ root: document.body, dictionary: dictionary }); zptParser.init( function(){ zptParser.run(); [ your code here ] } );
Because external macro files must be preloaded before the template is rendered, ZPT-JS must to know the list of external files invoked in the template. If we use literal string expressions or expressions that can be evaluated using only dictionary there is nothing to do. But if we use an expression that can not be resolved at first like this:
<div data-muse-macro="anObject/templateName"> Macro goes here </div>
If anObject/templateName
evaluates to aMacro@macros.html
and there is no rederence to macros.html
in other macro invokations, that macro invokation will throw an exception: Macros in URL 'macros.html' not preloaded!
. To resolve this issue we must set manually the list of external macro files we want to use when executing the ZPT call:
var zptParser = zpt.buildParser({ root: document.body, dictionary: dictionary, declaredRemotePageUrls: [ 'macros.html', 'moreMacros.html' ] }); zptParser.init( function(){ zptParser.run(); [ your code here ] } );
URLs are by default relative to current URL. You can also use absolute URLs:
var zptParser = zpt.buildParser({ root: document.body, dictionary: dictionary, declaredRemotePageUrls: [ '/path/to/your/macro/macros.html' ] }); ...
And in your HTML code:
<div data-muse-macro="'myMacro@/path/to/your/macro/macros.html'"> Macro goes here </div>
Context object provides a method to set a prefix to all relative URLs:
context.setExternalMacroPrefixURL( '/path/to/your/templates/' );
Then if you use an URL like macros.html it will be replaced by /path/to/your/templates/macros.html.
A formatter
allows to format the evaluation of an expression. ZPT-JS includes some formatters:
format: lowerCase 'Test'
evaluates as 'test'format: upperCase 'TEST'
evaluates as 'TEST'format: fix 1.371 2
evaluates as '1.37'format: fix (/: 1 3) 2
evaluates as '0.33'You can register other formatters of your own:
context.registerFormatter( 'myCustomFormatter', function( value ){ return "$" + value; } );
After registering it you will be able to use it as usual:
format: myCustomFormatter aNumber
It is possible to run custom formatters without registering them. They must be functions defined in the dictionary:
format: customFormatter aNumber
formats aNumber using a function in the dictionary called customFormatter
format: customFormatterName aNumber
formats aNumber using a function in the dictionary called as the value of the customFormatterName
variableNowadays ZPT-JS has some i18n capabilities. How do they work? Let's see an example:
i18n.js"use strict"; var zpt = require( '../../../js/app/main.js' ); var I18n = require( '../../../js/app/i18n/i18n.js' ); var I18nBundle = require( '../../../js/app/i18n/i18nBundle.js' ); /* I18n maps init */ var msg = { en : {}, es : {} }; /* English i18n messages */ msg.en[ '/CONF/' ] = { language: 'en', locale: 'en-US' }; msg.en[ 'Hello world!' ] = 'Hello world!'; msg.en[ 'Results msg' ] = '{GENDER, select, male{He} female{She} other{They} }' + ' found ' + '{RES, plural, =0{no results} one{1 result} other{# results} }'; /* Spanish i18n messages */ msg.es[ '/CONF/' ] = { language: 'es', locale: 'es-ES' }; msg.es[ 'Hello world!' ] = '¡Hola mundo!'; msg.es[ 'Results msg' ] = '{ GENDER, select, male{Él} female{Ella} other{Ellos} }' + ' ' + '{ RES, plural, =0{no } other{} }' + '{ GENDER, select, male{ha} female{ha} other{han} }' + ' encontrado ' + '{ RES, plural, =0{ningún resultado} one{un único resultado} other{# resultados} }'; // Create I18n and i18nBundle instances var i18nES = new I18n( 'es', msg[ 'es' ] ); var i18nEN = new I18n( 'en', msg[ 'en' ] ); var i18nBundle = new I18nBundle( i18nES, i18nEN ); // Init dictionary var dictionary = { 'i18nBundle': i18nBundle }; // Parse template zpt.run({ root: document.body, dictionary: dictionary });i18n.html
<!DOCTYPE html> <html lang="es"> <head> <meta charset="utf-8"> <title>Some I18n examples</title> <script src="i18n.js"></script> </head> <body> <h1>Some I18n expressions</h1> <ol data-ilanguage="'en'" data-idomain="i18nBundle"> <li> ¡Hola mundo! = <span data-tcontent="tr: 'Hello world!'">Must be ¡Hola mundo!</span> </li> <li> Él ha encontrado 10 resultados = <span data-tcontent="tr: 'Results msg' ( GENDER 'male'; RES 10 )">Must be 'Él ha encontrado 10 resultados'</span> </li> </ol> </body> </html>
Some remarks about this:
I18n
class. Each instance includes all i18n strings in an i18n file in JSON format. The constructor of I18n
class accepts 2 arguments; the first is the language of the messages and the second is a map (keys are the id of the messages and values the message themselves). The format of the messages must complain ICU standards.I18n
classes.I18nBundle
class. An instance of this class groups an instance of I18n
class per supported language. They are useful to support using several languages in a template.data-idomain
attribute defines the source of i18n resources to use. It can be one or several instances of I18n
or I18nBundle
classes or an array of them.data-ilanguage
attribute defines the current language to use. It is needed when data-idomain
contains a I18nBundle
instance, it is useless when we use I18n
instances.tr
expression evaluates an expression but then it searches that value into the messages.'Hello world!'
is a literal, but 'Results msg' ( GENDER 'male'; RES 10 )
is a little more complex:
'Results msg'
.GENDER 'male'
defines a GENDER
variable with 'male'
as value.RES 10
defines a RES
variable with 10
as value.Some examples of i18n tags in action:
<img src="image.png" data-tattributes="title tr: 'Hello world!'">
adds an i18n title to the image.
<li data-tdefine="msg tr: 'Hello world!'">
defines a msg
variable with the i18n message of 'Hello world!'
.
<body data-ton-error="tr: 'Oh, noooo!'">
sets the i18n message of 'Oh, noooo!'
as the message to show if an error occurs.
<span data-treplace="tr: 'Hello world!'">
replaces the span
tag by the i18n message of 'Hello world!'
.
Some examples of valid data-idomain
attributes:
data-idomain="i18nES1"
Defines an instance of I18n
class as the source of i18n strings.data-idomain="i18nES2 i18nES1"
Defines 2 instances of I18n
class as the source of i18n strings. ZPT will use the i18nES2 instance first; if the string is not found will be use i18nES1 instance.data-idomain="i18nBundle1"
Defines an instance of I18nBundle
class as the source of i18n strings.data-idomain="i18nBundle2 i18nBundle1"
Defines 2 instances of I18nBundle
class as the source of i18n strings.data-idomain="i18nESArray"
Defines an array of instances of I18n
class as the source of i18n strings. ZPT will use the first instance in the array; if the string is not found will be use the next instance until it is found.
In the previous example the domain was a simple I18nBundle
instance. This forces to use big maps with all the messages of one language. This can be awful if the amount of messages is big. data-idomain
tag supports also a list of I18nBundle
instances, so the messages will be searched in the same order.
Therefore, data-idomain="i18nBundle1 i18nBundle2"
allows to organise your i18n messages in 2 maps. The first one can contain general messages and the second one more particular messages (for example).
data-idomain
also supports nested definitions:
<div data-idomain="i18nBundle1"> <span data-tcontent="tr: 'Hello world!'"> ¡Hola mundo! </span> <span data-idomain="i18nBundle2" data-tcontent="tr: 'Hello world!'"> ¡¡¡Hola mundo 2!!! </span> </div>
The first data-tcontent
will search only in i18nBundle1
. The second one will search first in i18nBundle2
and if it is not found will search in i18nBundle1
.
ZPT-JS makes it easy loading i18n messages from JSON files:
"use strict"; var zpt = require( '../../../js/app/main.js' ); var dictionary = { ... }; var zptParser = zpt.buildParser({ root: document.body, dictionary: dictionary, i18n: { urlPrefix: './i18n/', files: { 'es': [ 'es1.json', 'es2.json' ], 'en': [ 'en1.json', 'en2.json' ] } } }); zptParser.init( function(){ // Add I18nBundle instances to dictionary var dictionaryExtension = { i18nBundle1: new I18nBundle( dictionary.i18nES1, dictionary.i18nEN1 ), i18nBundle2: new I18nBundle( dictionary.i18nES2, dictionary.i18nEN2 ) }; $.extend( true, dictionary, dictionaryExtension ); zptParser.run(); } );
ZPT will add to dictionary these vars:
The order in arrays is inverted: first the last files, then the first.
The bundles (i18nBundle1 and i18nBundle2) are not required, add to the dictionary if you need them.
ZPT-JS uses Intl as i18n API for numbers. Let's see some examples:
<span data-tcontent="trNumber: 1355.23">
formats that number depending on the active i18n domain (1,355.23 in english, 1.355,23 in spanish...).
<span data-tcontent="trNumber: 1355.23643 ( maximumFractionDigits 3 )">
formats that number depending on the active i18n domain and using a maximum of 3 fraction digits (1,355.236 in english, 1.355,236 in spanish...).
<span data-tcontent="trNumber: 1355.23643 ( maximumFractionDigits 3; minimumIntegerDigits 6 )">
formats that number depending on the active i18n domain and using a maximum of 3 fraction digits and a minimum of 6 integer digits (001,355.236 in english, 001.355,236 in spanish...).
Take a look on NumberFormat options to see all available options.
ZPT-JS uses Intl as i18n API for currencies. Let's see some examples:
<span data-tcontent="trCurrency: 'EUR' 1355.23">
formats that number depending on the active i18n domain and using that currency (€ 1,355.23 in english, 1.355,23 € in spanish...).
<span data-tcontent="trCurrency: 'USD' 1355.23 ( currencyDisplay 'name' )">
uses the name of the currency (1,355.23 US dollars in english, 1.355,23 € dólares estadounidenses in spanish...).
Options are the same as numbers (NumberFormat options) plus some specific options of currencies.
ZPT-JS uses Intl as i18n API for date and times. Let's see some examples:
<span data-tcontent="trDate: ( js: new Date( Date.UTC( 2012, 11, 21, 3, 0, 0 ) ) )">
formats that date depending on the active i18n domain (12/21/2012 in english, 21/12/2012 in spanish...).
<span data-tcontent="trDate: ( js: new Date( Date.UTC( 2012, 11, 21, 3, 0, 0 ) ) ) ( weekday 'long'; year 'numeric'; month 'long'; day 'numeric' )">
formats that date with some options (Friday, December 21, 2012 in english, viernes, 21 de diciembre de 2012 in spanish...).
<span data-tcontent="trDate: ( js: new Date( Date.UTC( 2012, 11, 21, 3, 0, 0 ) ) ) ( hour 'numeric'; minute 'numeric'; second 'numeric' )">
formats that date with some options (4:00:00 AM in english, 4:00:00 in spanish...).
Take a look on DateTimeFormat options to see all available options.
ZPT-JS makes it easy to register custom expression managers. For example, we want to develop an expression manager that operates similarly to the ++
C operator. First we must implement the class (incExpression.js file):
"use strict"; var context = require( '../../../js/app/context.js' ); var evaluateHelper = require( '../../../js/app/expressions/evaluateHelper.js' ); var $ = require( 'jquery' ); var IncExpression = function( varNameToApply ) { var varName = varNameToApply; var evaluate = function( scope ){ var value = scope.get( varName ); if ( ! evaluateHelper.isNumber( value ) ) { throw 'Error trying to inc number, not a number!'; } scope.set( varName, ++value, true ); return value; }; return { evaluate: evaluate }; }; IncExpression.removePrefix = true; IncExpression.getPrefix = function() { return '++' + context.getConf().expressionSuffix; }; IncExpression.getId = IncExpression.getPrefix; IncExpression.build = function( string ) { return new IncExpression( string.trim() ); } module.exports = IncExpression;
Don't forget to register that class!
var expressionBuilder = require( '../../../js/app/expressions/expressionBuilder.js' ); var IncExpression = require( './incExpression.js' ); expressionBuilder.register( require( IncExpression ) );
At first use the next command to initialize a beefy server (port 9966) to make it easy to test ZPT-JS without browserifying by hand and a HTTP server (port 9000) to serve static files (HTML, CSS and JSON files).
$ npm run start
ZPT-JS uses QUnit as testing framework. For testing ZPT-JS open test/index.html
or http://localhost:9000/test/ with your favourite browser. All tests passed using last versions of Mozilla Firefox (55.0.2, 64-bit). Perhaps some tests fail using other browsers!
ZPT-JS uses Grunt as task runner tool. If you want to modify and rebuild ZPT-JS the next command lines are useful:
$ grunt browserify // Build all test js files; also build a standalone version $ grunt browserify:[file] // Build a single js file; use 'standalone' to build a standalone version $ grunt compress // Compress files and folders of this project into dist/zpt_yyyy-mm-dd_hhmm.tar.gz
Please, take a look to test files in test/
to view more ZPT-JS examples.