We started with (
javascript template engine ) that it’s convenient to use the same template on the client side and on the server side. The finished rationalization of this approach cat wept. Blood rushes to the head, we decide to write our decision.
Outline the source data and the task
Suppose we have templates from our template engine. On the server, they are processed by perl. In the end, we want to use some of the available templates, or parts of them that are
not transferred to a single file, on the client side. It is necessary to reproduce the html generation procedures with minimal expenses (in the loss of functionality). As a result of processing templates, we want to get a set of predefined templates and blocks, designed in the form of a javascript library. No sooner said than done.
Disclaimer on ...
')
Below are examples of the implementation of the described parser, with the amendment that the
description of the parser in the server language is simply not there ... That is, now you can look at the work, source code and examples of the template engine, according to the scheme: templates on the server -> compilation in tmpl. js -> html generation based on data in JSON format. Well, then, if the plane takes off, you can go to the sixteenth act of Merleonsky ballet.
What do we need?
The template engine runs under perl. We will need:
- a configuration file in which we describe the matching of templates with .js libraries (we do not want all the templates in one file) and some necessary data for the template engine;
- the .pm module itself which contains various methods for parsing templates;
- the script, running which we will compile our libraries;
- oddly enough, patterns;
In accordance with the requirements, we have the following structure of files and directories (if you do not mind, then I will not rewrite the path to anonymous, in order to simplify your life, and immediately bring my working version):
#
/Users/zaur/www/blacktiger
#
/Users/zaur/www/blacktiger/bin/Core/JS/Config.pm
#
/Users/zaur/www/blacktiger/bin/Core/JS/Parse.pm
#
/Users/zaur/www/blacktiger/bin/jsgen
#
/Users/zaur/www/blacktiger/tpl
/Users/zaur/www/blacktiger/inc
# ( )
/Users/zaur/www/blacktiger/source_js
# ,
/Users/zaur/www/blacktiger/js/tpl
# html ,
/Users/zaur/www/blacktiger/html
* This source code was highlighted with Source Code Highlighter .
What can we realize?
Before describing the functional, I will tell you a little about what is actually happening. We compiled javascript code. By calling various functions, operators, conditions using a simple concatenation of alternating strings and variables, the variable value of the output variable (out) is formed. In the course of executing the code, we iterate the elements of the arrays (we mostly run through the arrays of hashes, which in turn also contain references to nested structures) and fill the
stack with the current hashes,
inside which we are. In the examples, I will try to decipher this puzzle, by outputting the values of internal variables. Cheer up.
Here is a list of what we can embody in our templates, having in addition to the above listed JSON data:
- access to variables with different levels of nesting;
- iteration of array elements;
- conditional operators if / else if / else;
- inclosions;
- reuse of blocks within the template;
- use for generating html blocks allocated in templates (analog of the inclusion, with the only exception that you do not need to create a separate file);
Let's get started ...
Template
/Users/zaur/www/blacktiger/tpl/sambuka.htm
- < h3 > {$ cfg.host} </ h3 >
- < h4 > {#title} </ h4 >
- < ul >
- {% topics}
- < li id = "li - {# id}" {? # id == 3 } class = "sel" {/?} > {#title} </ li >
- {/% topics}
- </ ul >
* This source code was highlighted with Source Code Highlighter .
Our javascript code and data:
var d1 = {
title: ' – !' ,
topics: [
{title: '' , id: 1},
{title: '' , id: 2},
{title: '' , id: 3},
{title: '' , id: 4}
]
};
var jstpl = new myfirst();
var html1 = jstpl.tpl( 'tpl_sambuka' ,d1);
* This source code was highlighted with Source Code Highlighter .
Result html
< h3 > blacktiger </ h3 >
< h4 > – ! </ h4 >
< ul >
< li id ="li-1" > </ li >
< li id ="li-2" > </ li >
< li id ="li-3" class ="sel" > </ li >
< li id ="li-4" > </ li >
</ ul >
* This source code was highlighted with Source Code Highlighter .
We will understand ...
Go through the lines of the template:
1. In the Config.pm file there is a hash% jsd. By writing {$ cfg.host}, we got the value of $ jsd {'host'}. If we complicate this expression like {$ cfg.path.to.element}, then we will substitute the value $ jsd {'path'} {'to'} {'element'}. This is
access to variables with different levels of access .
2. In the template, we passed the hash
d1 . The {#title} entry is equivalent to d1.title in this case. In this case, jstpl.data.cur_arg.title was actually transferred there.
3. Easy, this is a simple html.
4., 6. {% topics} {/% topics} Turned into a type construction
for ( var i=0; i<d1.topics.length; i++) {
//...
}
* This source code was highlighted with Source Code Highlighter .
Weighed down with utility operations to stack, iterator calculations, etc.
This construct is referred to as
a data object . This is usually a hash array, but perhaps using an array of arrays. The following describes the complete syntax for invoking an object, including specifying the data source, slice, method, executed at each iteration.
5. a. We see the variable call {#title}. Unlike the previous time, here the context of calling this variable is the current iterated hash of the topics array. How within this context to get d1.title value? You need to use the template argument 'tpl_sambuka', it looks like this {@ tpl_sambuka.title}. The variable d1.topics [i] .title has the full syntax of the call {# topics.title}, i.e. it always starts with the name of the object. Accessing an object's nested elements always begins with specifying the object name.
5 B. Conditional if (exp) {then} operator. As
exp , the above described variables (but without braces) and javascript operators can be used. And here you need to be careful. We remember that templates are also used by the server language, so you need to remember about compatibility.
7. And here it is, again html, just html.
Now a little more about the operators
- Variables, what they eat and how you can use:
1. {$ cfg.path.to.element} access to $ jsd {'path'} {'to'} {'element'}
{#title}, {# topics.title}, {# _current.title} are all references to the same variable.
{# list.path.to.element} nested elements of the list object
2. {@irow [# topics._level_irow-1] title} stack of current object hashes and pattern arguments. # topics._level_irow nesting level of the current hash of the object of topics. The total is the value of the d1.title variable.
3. Variable loop iterator.
{#}, {# topics. #}, {#_iterator}, {# topics._iterator}
These are all the values of one variable (i in the cycle) with the only condition (for human, not computer logic) that starts from 1. That is, for a cycle with indices 0 ... 5, the variable will take the value 1 ... 6
4. Full object syntax
{% nameobj => # source.obj [3..5]: myloopfunction} ... {/% nameobj}
This is the complete syntax for calling an object.
If we do not specify the source (# source.obj), then the data will be taken from the {#nameobj} variable of the current hash.
5. Blocks
If we enclose some part of the code in the tags {@myblockname} html {/ @ myblockname}, then later in the same template we can use this block {@ block.myblockname.html} - this place will be replaced by what we have framed tags earlier. In this case, the modifier .sub will insert a framed area, while performing the template enclosed in tags.
6. Blocks from external templates
An example of our entry in Config.pm
@jslib = (
{
name = > 'myfirst',
list_tpl = > [
'tpl_sambuka'
],
blocks = > {
tpl_auth = > [
'auth_form'
]
}
}
);
* This source code was highlighted with Source Code Highlighter .
We recorded that we want to use the auth_form block from the tpl_auth template. In the template, you can use
{@ block.tpl_auth.auth_form.html} insert simply as html
{@ block.tpl_auth.auth_form.sub} insert into the template by executing the specified code
6. Inclodes
{& tpl_myother_tpl}
Inserts the tpl_myother_tpl template into the current template, while the
current hash will be the hash from the called context in it. Oh, how confusing.
{& tpl_myother_tpl => # source.data}
By invoking an incloud in this way - we indicate to it as the current hash # source.data, this will also be included in the arguments
{@ tpl_myother_tpl.title} within the myother template is equivalent to # source.data.title
Example with blocks, inclusions and recursion
Here is the last example in which we will use blocks from external templates, inclusions, blocks from the current template to create a recursive walk through the
tree.Tpl / area.htm template
- < h1 > {#title} </ h1 >
- {& inc_header = > #headerinfo}
- {@tree}
- < ul >
- {% menu = > #childs}
- < li id = "li - {# id}" >
- < p > [{#}] {#title} </ p >
- {? #childs}
- {@ block.tree.sub}
- {/?}
- </ li >
- {/% menu}
- </ ul >
- {/ @ tree}
- < h4 > Here’s a block from another template </ h4 >
- {@ block.tpl_auth.auth_form.html}
* This source code was highlighted with Source Code Highlighter .
The auth_form block from the tpl / auth.htm template
{@auth_form}
< div id ="d-auth" >
: < input type ="text" value ="" >< br >
: < input type ="password" value ="" >< br >
< input type ="button" value ="" >
</ div >
{/@auth_form}
* This source code was highlighted with Source Code Highlighter .
Javascript code and data
var d2 = {
headerinfo : {
title: ' '
},
title: ' – !' ,
childs : [
{ id : 1, title : ' ' },
{
id : 2,
title : ' ' ,
childs : [
{ id : 5, title : ' ' },
{
id : 6,
title : ' ' ,
childs : [
{ id : 8, title : ' ' },
{ id : 9, title : ' ' }
]
},
{ id : 7, title : ' ' }
]
},
{ id : 3, title : ' ' },
{ id : 4, title : ' ' }
]
};
var jstpl = new myfirst();
var html2 = jstpl.tpl( 'tpl_area' ,d2);
* This source code was highlighted with Source Code Highlighter .
Let's get this html:
< h1 > – ! </ h1 >
< h3 > </ h3 >
< ul >
< li id ="li-1" >< p > [ 1 ] </ p ></ li >
< li id ="li-2" >
< p > [ 2 ] </ p >
< ul >
< li id ="li-5" >< p > [ 1 ] </ p ></ li >
< li id ="li-6" >
< p > [ 2 ] </ p >
< ul >
< li id ="li-8" >< p > [ 1 ] </ p ></ li >
< li id ="li-9" >< p > [ 2 ] </ p ></ li >
</ ul >
</ li >
< li id ="li-7" >< p > [ 3 ] </ p ></ li >
</ ul >
</ li >
< li id ="li-3" >< p > [ 3 ] </ p ></ li >
< li id ="li-4" >< p > [ 4 ] </ p ></ li >
</ ul >
< h4 > </ h4 >
< div id ="d-auth" >
: < input id ="login" class ="inp" value ="" type ="text" >
: < input id ="pswd" class ="inp " value ="" type ="password" >
< input id ="btn-login" value ="" type ="button" >
</ div >
* This source code was highlighted with Source Code Highlighter .
Let's figure it out ...
I have never mentioned anywhere that after editing the templates, you need to run the script ./bin/jsgen every time - this is one of the dark sides of the solution. Only modified templates will be regenerated (timestamp of file modification time is tracked). To force regeneration of all templates, you need to run a script with the -all key
./bin/jsgen -all
Do not forget to set the rights to jsgen corresponding
chmod 775
One more thing. In Config.pm we have an array
@templates = (
{ pm = > "Tpl", ext = > '.htm', dir = > "tpl" },
{ pm = > "Inc", ext = > '.inc', dir = > "inc" }
);
* This source code was highlighted with Source Code Highlighter .
He tells us that there are two folders in the root directory,
tpl and
inc , which contain template files with the extensions .htm and .inc, respectively. There may be subfolders in these folders (this doesn’t need to be reflected in the config file). Suppose we created a file
inc / news / rubric / index.inc
Refer to this template need to name 'inc_news_rubric_index'
The file myfirst.js is located in the / js / tpl directory (Config.pm => $ cfg {jstpl})
As mentioned above, we want to do different assemblies of templates.
I’ll give an example of our entry in Config.pm again.
@jslib = (
{
name = > 'myfirst',
list_tpl = > [ ... ],
blocks = > {...}
},{
name = > 'mysecond',
list_tpl = > [ ... ],
blocks = > {...}
}
);
* This source code was highlighted with Source Code Highlighter .
The @jslib array contains information about such assemblies.
name - the name of the file and class at the same time.
list_tpl - all templates (both from the tpl folder, and from inc, and any other ones added by you (not embedded in the data) and described in the config file)
blocks - names of templates and blocks in them that will be used in the assembly
By line numbers in the area.htm template:
2. Call incl header / inc.inc and transfer it to d2.headerinfo as the current hash
4., 15. Declare a tree block
6., 13. We declare a menu object, the #childs from the current hash is passed as the data source. For recursion, it is important that this is equally indicated in the root element and in the descendants. Sample data is shown above.
10. Call the indicated block as a function. That is, all code framed by block tags will be executed with the current hash (the condition checks for the descendants of #childs)
18. Call the external block auth_form from the template tpl / auth.htm as a simple html
the end
So, rather cumbersome in appearance, we get a rather multi-functional template for javascript. It is not much less than the template engine that runs on server scripts (its own nuances). And due to the absence of regular expressions in the compiled code, it can boast a solid speed. The minus that kills me sometimes is the need to run the regeneration script after changing the template ...
I apologize if everything is complicatedly explained, there is no detailed documentation yet. If someone is interested, you can continue the description, but already with the template engine on perl on the server side, to cover the whole bunch
Source code of all mentioned files,
linkDo not forget to fix the file bin / Core / JS / Config.pm