:[object Object]
[object Object][object Object]/**
JSL is yet another templating framework, inspired by XSL, and the contemporary trend of using JSON
data which are transformed to HTML either on the client-side or the server-side. The fundamendal concept is to
exploit the powerful modern JavaScript engines in an efficient way to write templates with a minimal footprint.
From JSL's point of view native JavaScript has quite accomodating semantincs for a native template engine.
Consider a function DIV(arg1...argN) that concatenates its arguments into a string and wraps it with the
HTML tags <div> and </div>. Now consider the same for all known HTML elements...
Consider also a function jsl.transform that writes HTML source to a stream or DOMNode
What would you expect the following script to do?
*/
var topics=document.querySelectorAll('main > nav a');
jsl.transform(
DIV(
H1('Is this JSL?'),
P('Is it so simple to write an ',I('HTML'),' template?'),
P(
B(topics.length),' topics of documentation for this?'
),
P('What about attributes?',INPUT({type:'checkbox',checked:true})),
A({href:"#documentation"},BUTTON('Continue reading...'))
)
);/**Basic example, illustrating JSL2. What would you expect the following script to do? */
var topics=document.querySelectorAll('main > nav a');
jsl.transform(
DIV(
H1('Is this JSL?...basic example continued'),
P('What about css templates?',{class:'styled-paragraph'}),
STYLE(jsl.CSS({
'.styled-paragraph' : {
border:"1px solid black"
},
'#documentation_out div':{
margin:"0.5em"
},
'#documentation_out button':{
minHeight:"2.5em",
border:"1px solid",
boxSizing:"border-box"
},
'#documentation_out button:hover':{
color:'#5e4709',
borderWidth:"2px",
},
'#dowload-link > button':{
textDecoration:'underline',
'background-color':'inherit'
}
})),
P('What about iterations?'),
UL(
jsl.forEach(topics,function(topic,index){
if(index<4){
return LI(topic.innerText);
}
}),
LI('...and ',(topics.length-4),'more')
),
DIV(
BUTTON('What about event handlers?',
{
onclick:function(event){
alert('Check out in the documentation.');
location.href='#function_properties';
}
}
),' ',
A({id:"dowload-link",href:"/jsl"+jsl.version+".js",target:"_blank"},
BUTTON('Got it! Just download JSL')
),
),
)
);/**Every HTML Element has a corresponding JSL2 tag-function.
tag-functions take any number of arguments and return formatted HTML code.
Check in the following sections for a detailed explanation on how the arguments are treated depending on their types.
*/
jsl.transform(
BUTTON(), //yields <button></button>
BR(), //yields <br/>
INPUT(), //yields <input/>
TEXTAREA()//yields <textarea></textarea>
)function tagExec(tagName,singleton,orgArgs){
var str="<"+tagName,body=[],atts={
/*intermediate storage, each attribute as array of values that we will merge later*/
};
var lastArg=orgArgs[orgArgs.length-1];
for(var i=0;i<orgArgs.length;i++){
tagArgumentProcess(tagName,orgArgs[i],atts,body);
}
for(var m in atts)if(atts.hasOwnProperty(m)){
if((atts[m]=="")&&(!jsl.options.producexml)){
str+=" "+m;
}else{
str+=" "+m+"=\""+atts[m].join(" ").replace(/"/g, '"')+"\"";
}
}
body=body.join('');
if(singleton){
if(body){
console.warn("tag \""+tagName+"\" is a singleton and should not have any content");
}else{
if(lastArg !== jslNs.OPEN){
str+="/>";
};
return str;
}
}
var closeTag=(lastArg === jslNs.OPEN)?"":"</"+tagName+">";
str+='>'+body+closeTag;
return str;
}/**
The arguments passed to the tag-functions are evaluated and generate the content(innerHTML) of the resulting element. In this example, several nested tag-functions are invoked with string parameters1.
*/
jsl.transform(
DIV(
H1('Hello JSL!'),
P(
'Every HTML Element has a corresponding jsl tag-function.',
'The arguments passed to the tag-functions are evaluated ',
'and generate the content of the resulting element.'
),
P('In this example we are using:'),
OL(
LI('DIV'),
LI('H1'),
LI('P'),
LI('OL'),
LI('LI'),
)
)
);
/**
1JSL tag-functions themselves return strings. This allows JSL to work both on the client-side and the server-side without any external dependencies.
*/function tagArgumentProcess(tagName,argV,atts,body){
var argT=typeof argV;
if(argT=="function"){//[function-arguments]
if(tagName=='script'){//a function inside a script implies that we want to generate a script in the output
if(argV.name){
htmlNs[argV.name]=htmlNs[argV.name]||argV;
body.push(argV.toString()+";\n");
}else{
body.push(jslNs.javascript(argV));
}
//no return here, the function might have properties
}else{
/**if we wanted a string we could have used argV.toString()
if we wanted to call the function we could have called the function
let's do something else;-) extract its cdata.
*/
if(jslNs.options.fnametoid){
if(argV.name && !atts.id){
atts.id=[argV.name];
}
}
if(jslNs.options.autocdata){
body.push(jslNs.cdata(argV));
}
/**
invoke the function with the arguments as we have them...
this could allow for example a function to filter the body and attributes before they are joined
*/
argV(tagName,atts,body);
}
};
if(argV && typeIsObjectOrFunction[argT]){/*argV will become element attributes*/
{//[array-arguments]
var isArray=Array.isArray(argV);
if(isArray){
for(var i=0;i<argV.length;i++){
tagArgumentProcess(tagName,argV[i],atts,body);
}
};//no else here, the array might have named properties
}
//now process the named properties
objectToElementAttributes(tagName,argV,isArray,atts,body);
}else
if( argV || argV==0){//same, but faster than (argV || typeIsEnumerated[argT] )
body.push(argV);
}
}/**
String, Numeric and Boolean arguments of tag-functions are concatenated and become the inner HTML of the resulting html element. Undefined or Null arguments are omitted from the results.
For example:
*/
var x=5.3;
jsl.transform(DIV(
x //yields '5.3'
,'<' //yields '<'
,x+1 //yields '6.3'
,'?' //yields '?'
,x<x+1 //yields 'true'
,null //yields nothing
, //yields nothing
));/**
Arrays passed as arguments are flattened recursivelly, and their items are evaluated like the rest of arguments.
For example, the following:
*/
jsl.transform(DIV([1,[2,[3,[4,5],6],7],8]));
/*
will produce the same results as:
*/
jsl.transform(DIV( 1, 2, 3, 4, 5, 6, 7, 8));var isArray=Array.isArray(argV);
if(isArray){
for(var i=0;i<argV.length;i++){
tagArgumentProcess(tagName,argV[i],atts,body);
}
};//no else here, the array might have named properties /**
The values of the object properties whose property-name is an integer string, are treated as normal arguments.
The named properties of an object passed as argument to a tag-function, are converted to HTML attributes.
The name of the property determines the attribute's name, and the value of the property determines the attributes's value.
Properties that are found in multiple object arguments, are concatenated.
For example:
*/
jsl.transform(
//the values of 0:,1: and 2: will be treated as normal arguments
H4({0:"This is ",1:"the ",2:["HTML5 ","logo "]}),
//an attribute 'src' with value '/html5logo.png' will be generated
IMG({src:'/html5logo.png'}),
//the class attribute will be the concatenation "blue centered box"
DIV(
{class:'blue'},
'blue centered box',
{class:'centered'},
{class:'box'}
)
);function objectToElementAttributes(tagName,o,isArray,attributeBag,body){
var keys=Object.keys(o);
if(isArray){//we already used the faster way of enumerating the array's items
keys=keys.slice(o.length);
}
var remaining=keys.length,i=0;
while(remaining--){
var m=keys[i++];
var v=o[m];
var t=typeof v;
if((t=='boolean')&& !enumeratedBooleanAttributes[m]){
//contenteditable, spellcheck are enumerated attributes so don't treat them like Boolean
//what about data-* attributes? we can use a regular expression to test /^data-/.test(m))
// but it is faster, and seems proper to treat data-* properties with Boolean values as Boolean attributes
if(v){
v=jsl.options.producexml?m:"";
}else
if(!v){//don't add the value in the attribute-bag
continue;
}
}else
if(!isNaN(parseInt(m))){
tagArgumentProcess(tagName,v,attributeBag,body);
continue;
}else
if(m=="style"){
v=valueToCSSstyle(v);
}else{
switch(t){
case "object"://objects in attributes make sense only as JSON
if(/^data\-/.test(m)){
v=JSON.stringify(v);
}else{
if(Array.isArray(v)){
v=v.join(' ');
}else{
v=v.toString();
}
};
break;
case "function"://functions in attributes make sense only as events...
v=jslNs.listener(v);
break;
}
};
if(!attributeBag[m]){
attributeBag[m]=[];
};
attributeBag[m].push(v);
}
}/**HTML boolean attributes indicate their value with their presence. The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
Following this premise, boolean property values become corresponding boolean attributes(1)
For example:
*/
jsl.transform(
H4('Boolean values result in Boolean attributes',SUP('(1)')),
LABEL("Checked and disabled:",
INPUT({//will generate <input type="checkbox" checked disabled/>
type:"checkbox",
checked:true,
disabled:true
})
),BR(),
LABEL("Unhecked and enabled:",
INPUT({//will generate <input type="checkbox"/>
type:"checkbox",
checked:false,
disabled:false
})
),BR()
);
/**
Important reminder!
The && and || operators actually return the value of one of the specified operands. So if these operators are used with non-Boolean values, they may return a non-Boolean value.
To force a property to generate a Boolean Attribute, the property value has to be typecast to Boolean.
In this example the expression (a&&b) evaluates to 0. We are using double negation to make sure that it evaluates as a Boolean.
*/
var a=0,b=false;
jsl.transform(
H4('The && and || operators are tricky...'),
DIV({
hidden :(!!(a&&b)),//'hidden' will be omitted because (!!(a&&b))==false
'data-hidden':(a&&b),//'data-hidden' will be present because typeof (a&&b) == 'number'
},
'You can see this div. (!!(a&&b)) evaluates\
to the Boolean value false. Therefore\
hidden is treated as a Boolean attribute,\
and thus omitted from the HTML markup.\
Had we not used the casting, the generated\
markup would include the attribute hidden="0"\
which by its presence implies to hide the div.'
)
)
/**
(1)The exception being when the property's name is 'contenteditable' or 'spellcheck'.
According to the standards, these two attributes may have the value true or false, thus JSL copies the Boolean value as Attribute value;
For example:
*/
jsl.transform(
H4(SUP('(1)'),'Except when the property name is ',I('contenteditable'),' or ',I('spellcheck')),
INPUT({spellcheck:false,value:'no spell check heeere.'})
);
/*will generate
<input spellcheck="false" value="no spell check heeere."/>
keeping the attribute 'spellcheck' with its value set to false.
*//**
When the property name is data-* the property value(including arrays) is converted to a JSON string attribute.
In all other cases the string representation of the property value is used as attribute value(1)(2).
For example:
*/
jsl.transform(
DIV({
'class':['blue','centered','box'],//join the items of the array
'data-point':{x:1,y:2},//convert object to JSON and use as attribute value
'data-colors':['r','g','b'],//convert array to JSON and use as attribute value
},'div with JSON data')
);
/*will generate the equivalent:
<div data-point="{"x":1,"y":2}" data-colors="["r","g","b"]">
div with JSON data
</div>
*/
/**
(1)when the property name is style, property values are handled differently.
(2)when the property value is an Array, its items are concatenated using white-space as separator.
*//**Function property values are treated as event handlers. For that, they are wrapped in a call expression, so that at invokation they get the correct context of this and event.
For example:
*/
jsl.transform(
INPUT({
value:"click to conceal",
onclick:function(event){
this.type="password";
}
})
);
/*will generate the equivalent:
<button onclick="(function(){ this.disabled=true; }).call(this,event);">
click to conceal
</button>
*/
/**For those cases that we want to pass additional parameters
to an event handler, JSL provides the utility function jsl.listener
*//**When the name of an object property in the arguments of a jsl tag-function is style its value is converted to a CSS style declaration as follows:
- Primitive values are converted to strings and are treated as if they are correct style definitions.
- Object values are treated as if they are CSS style declarations.
The properties of the object, become CSS properties.
You can use the camel case property notation or the css property name notation.
In the example below, fontSize corresponds to the CSS property font-size
*/
jsl.transform(
DIV({
style : 'font-size:12pt; color:blue' /** style="font-size:12pt; color blue;" */
},'bar'),
DIV({
style:{/** style="font-size:20pt;text-align:right; */
fontSize : '20pt', /** font-size:20pt; */
'text-align': 'right' /** text-align:right */
}
},'bar'),
);function valueToCSSstyle(o){
var t;
while((t=typeof o)=="function"){
//o is a function, it makes no sense a style declaration so let's expand it and hope for the best
o=o();
if(!clientSide){
//on the server side, perhaps we have a function that returns a function which should be used in the client
break;
}
};
if(t!="object"){
//o is a literal, let's hope it is a valid style declaration
return o+";";
}
if(Array.isArray(o)){
//o is an array, let's hope it's items resolves to a valid style declaration, so that we can merge them
str="";
for(i=0;i<o.length;i++){
str+=valueToCSSstyle(o);
}
return str;
}
var str="";
for(var m in o)if(o.hasOwnProperty(m)){
var v=o[m];
if(Array.isArray(v)){//maybe something like a font-family declaration ["arial","sans serif"]...
v=v.join(" ");
};
/*convert javascript css camelCase propertyNames to css property-names*/
str+=(m=="cssFloat"?"float":(m.replace(/([A-Z])/g,"-$1").toLowerCase()))+":"+v+"; ";
}
return str;
}/**
A function object passed as argument to any JSL tag-function other than SCRIPT(...), is treated like all normal object arguments.
...With a useful twist though.
-
The function's comments are used as cdata and copied to the output.
This feature can be disabled with jsl.options.autocdata
-
The function's name is used as attribute id, if id is not already defined.
This feature can be disabled with jsl.options.fnametoid
-
The function itself is invoked assuming it is of the form function(tagName, tagAttr, tagBody).
This allows it to change the tag-function attributes and body before the tag-function returns.
A function object passed as argument to SCRIPT(...), implies that we want to generate a <script>...</script> tag containing that function.
- named functions are copied to the output, thus become available in the runtime as normal client-side functions.
- anonymous functions are wrapped as scripts to be executed in the output document's flow.
For that, JSL uses internally the utility function jsl.javascript
*/
function add(x,y){
var vx=x.r||x,vy=y.r||y,vr=vx+vy;
var tx=x.t||x,ty=y.t||y,tr=tx+'+'+ty+'='+vr;
return {r:vr, t:tr};
}
function targetForScript(){/***The name of this function(targetForScript) will become the id attribute of the generated tag in the following javascript.These comments will become the body of it.
*/};
targetForScript.style={color:'red',border:'1px solid'};//add also a property for style
jsl.transform(
P(targetForScript),//the function 'targetForScript' will be treated as a normal object(...with a twist)
SCRIPT(
add,//the named function 'add' will be copied to the body of the generated <script> tag
function mul(x,y){//locally defined named function 'mul', will be copied to the generated <script> tag
var vx=x.r||x,vy=y.r||y,vr=vx*vy;
var tx=x.t||x,ty=y.t||y,tr='('+tx+')*('+ty+')='+vr;
return {r:vr,t:tr};
},
function(){//anonymous function, will be wrapped as a 'function call'
var e=mul(add(1,2),add(3,4));
document.getElementById("targetForScript").innerHTML+=e.t;
alert(e.t+"\nCheck out the DOM tree and HTML output");
}
),
//using a function to filter the results before output
P(
"This is a parenthetic remark",
filterFunction=function(tagName,tagAttr,tagBody){/*color:black;padding:4pt;border:1px dotted gray;*/
var myComments=tagBody.pop();
var prevMarkup=tagBody.pop();
if(prevMarkup.indexOf("parenthetic")>=0){
tagAttr.style=myComments;//pop my comments from the tagBody, and use them as style
tagBody.push('('+prevMarkup+')');//pop the previous argument's construct, and place it in parentheses.
}else
if(prevMarkup.indexOf("obsolete")<0){
tagBody.push(prevMarkup);
}else{
//remove obsolete
}
},
"The filter-function did the job.",
filterFunction,//does nothing
"Therefore this text is obsolete",
filterFunction//removes the previous line
)
);if(tagName=='script'){//a function inside a script implies that we want to generate a script in the output
if(argV.name){
htmlNs[argV.name]=htmlNs[argV.name]||argV;
body.push(argV.toString()+";\n");
}else{
body.push(jslNs.javascript(argV));
}
//no return here, the function might have properties
}else{
/**if we wanted a string we could have used argV.toString()
if we wanted to call the function we could have called the function
let's do something else;-) extract its cdata.
*/
if(jslNs.options.fnametoid){
if(argV.name && !atts.id){
atts.id=[argV.name];
}
}
if(jslNs.options.autocdata){
body.push(jslNs.cdata(argV));
}
/**
invoke the function with the arguments as we have them...
this could allow for example a function to filter the body and attributes before they are joined
*/
argV(tagName,atts,body);
}/**JSL provides a number of utilities that in combination with the tag-functions create a powerful framework for creating HTML and CSS templates both on the client-side and the server-side.
In all the examples so far you may have noticed frequent calls to jsl.transform and jsl.forEach
*//**
collection:: any non primitive object
callback :: function(value,nameOrIndex,source,results){ ... }
should return a new item for the results, or add it manually and return nothing.
The helper function jsl.forEach iterates(1) through the properties of the object provided as first argument and passes them to a callback function as below:
*/
jsl.transform(
H3("4 singleton tags"),
UL(//iterate through the jsl.defs object and generate a list of the singleton tags
jsl.forEach(jsl.defs,function(value,name,source,results){
if((value.singleton)&&(results.length<4)){
return LI(name);
}
})
),
H3("Triangle sides"),
UL(//iterate through an Array and generate some list items
jsl.forEach(['A','B','C'],function(item,index,source){
var next=source[(index+1)%(source.length)];
return LI(item,next);
})
),
H3("Some custom attributes"),
P(//iterate through an object's properties and generate some custom data-attributes
'Should have ',
jsl.forEach({x:1,y:2,z:3},function(value,name,source,results){
if(name){
results['data-'+name]=value;
}
if(results.length){
results.push(name?', ':' and ');
}
results.push(I("data-"+name));
}),
' attributes.'
)
);
/** (1)When you know you are iterating an array,
perhaps it is faster to use instead the build in function Array.prototype.map
*/function(collection,callback,options){
var returnAsArray=(options !== false);
if(Array.isArray(collection)){
var results=[];
for(var i=0;i<collection.length;i++){
var cbResult=callback(collection[i],i,collection,results);
if(typeof(cbResult)!='undefined'){
results.push(cbResult);
}
}
return results;
};
if(collection && typeIsObjectOrFunction[typeof collection]){/*all other objects(and functions)*/
if(returnAsArray){
var keys=Object.keys(collection);
var results=[];
for(var i=0;i<keys.length;i++){
var m=keys[i],cbResult;
try{
cbResult=callback(collection[m],m,collection,results,i,keys.length);
}catch(e1){
cbResult=callback(e1,m,collection,results,i);
}
if(typeof(cbResult)!='undefined'){
results.push(cbResult);
}
}
return results;
}
//return as an object with the same members..
var results={};
for(var m in collection)if(collection.hasOwnProperty(m)){
var cbResult=callback(collection[m],m,collection,results);
if(typeof(cbResult)!='undefined'){
results[m]=cbResult;
}
}
return results;
}
console.warn("jsl.forEach requires an iterable collection!",typeof collection);
return callback(collection);
}/**
jsl.transform transforms its arguments to an HTML fragment, and appends it
to a target DOMNode(1) depending on the invokation context(2)
- Before the document is loaded,jsl.transform appends its fragment to the
end of the document using document.write().
- After the document is loaded, jsl.transform by default inserts its fragment
before the end of the document body.
- The function can be bound using call or apply to a CSS
selector string or DOM Node object as this context to define the target where the results should be appended.
- Alternativelly, the jsl.target property can be
used to define the target context.
For example all the following statements are equivalent:
*/
jsl.transform.call('footer .extra',/**using a css selector as this*/
P('jsl',SUP(jsl.version))
);
jsl.transform.call(document.querySelector('footer .extra'),/**using a DOM Node as this*/
P('jsl',SUP(jsl.version))
);
jsl.target='footer .extra';/**assigning a css selector to jsl.target*/
jsl.transform(P('jsl',SUP(jsl.version)));
jsl.target=document.querySelector('footer .extra');/**assigning a DOM Node to jsl.target*/
jsl.transform(P('jsl',SUP(jsl.version)));
/**
(1)The function has slightly different usage on the
server-side.
(2)in this example, before the invokation,
the target of jsl.transform was set via the jsl helper property jsl.target
to the panel on the right.
*/function(/*the arguments specify the fragment*/){
var fragment="";
for(var i=0;i<arguments.length;i++){
var v=arguments[i];
if((v === null)||(v == undefined)){
//skip void argument
}else{
if(typeof v == 'function'){/*this is undocumented, and it better stay... call v like this?*/
v=v();
};
if(Array.isArray(v)){
v=flatJoin(v);
}
fragment+=v;
}
}
if(/^<html/.test(fragment)){
fragment='<!DOCTYPE html>'+fragment;
}
if(!clientSide){//[server-side] nodejs server-side
if(((this==jslNs)||(!this))&&(!jslNs.target)){
return fragment;//i.e. this.transform(...)
}
var target=this == jslNs?jslNs.target:this;
if(typeof target == 'object'){
if(target.write){//i.e. jsl.transform.call(response,...);
target.write(fragment);
return true;
}else
if(this.exports){//this is a module
this.exports=fragment;
return;
}
}
throw new Error('I don\'t know what to do with this');
}
if((this==jslNs)&&(!jslNs.target)){
var s1=scriptTag();
if(s1 && (s1.ownerDocument.readyState=="loading")){//the page is still loading, so we can call document.write
s1.ownerDocument.write(fragment);
}else{/*after the page is loaded, we can no longer use document.write*/
var q=document.createRange().createContextualFragment(fragment);
document.body.insertBefore(q,s1);
}
if(s1){
s1.outerHTML="";
}
}else{//we invoke transform with a diferent this binding... e.g. jsl.transform.call("body > main",P("new paragraph"))..
var target=this == jslNs?jslNs.target:this;
var replace=false;
//in strict mode a string this is not boxed
if(target.constructor==String){
target=target.valueOf();
};
if(typeof target == 'string'){
if(replace=(/^\\/.test(target))){//replace instead of insert
target=target.substr(1);
};
target=document.querySelector(target);
};
var q=document.createRange().createContextualFragment(fragment);
if(target.write){
target.write(fragment);
}else
if(target!=window){/*target==window implies that the calling statement was jsl.transform.call(null,...)*/
if(replace){
target.replaceChildren(q);
}else{
target.insertBefore(q,null);
}
};
return q;
};
}/** JSL on nodejs is quite like the client.
You can set the jsl.target to a response
or any object that has a method write before calling jsl.transform
or you can invoke jsl.transform using call or apply binding
its 'this' to a response or any object that has a method write
*/
/**A typical initialization of of JSL as nodejs module*/
const jsl=require('jsl2.1.js');
jsl.init(jsl,global);
/**A typical way of applying a JSL transformation on a response stream of express,
using jsl.transform.call(response,....)*/
const app = express({simQuery:"/mypath/home?w=9em&h=5em"});
app.get('/mypath',function(request, response){
response.status(200);
jsl.transform.call(response,HTML({lang:"en-US",dir:"ltr"},
HEAD(
TITLE(request.user.name,' home page'),
META({name:"generator",content:"jsl2"}),
STYLE(//dynamic CSS from request
jsl.CSS({/*use the query to create a style sheet*/
'body':{
backgroundImage:"url("+request.user.pic+")",
},
'p':{//[collapsed]
color :request.user.colorA,
backgroundColor :request.user.colorB,
border :"2px solid "+request.user.colorA
},
'h1':{color:request.user.colorB},
'img':{height:request.query.h,width:request.query.w}
}),function(){//[collapsed] common CSS in comments
/*
html,body{
padding:0;margin:0;
}
body{
font-family:Segoe UI,Roboto,sans-serif;
font-size:7vh;
font-weight:lighter;
background-size:cover;
background-repeat:no-repeat;
background-attachment: fixed;
position: absolute;
width: 100%;
height: 100%;
}
p{
display:inline-block;
position:absolute;
bottom:1em;
}
h4{
display:block;
background-color:rgba(255,255,255,0.5);
position:absolute;
top:0;left:0;right:0;
margin:0;
}
*/}
)
),
BODY(//[collapsed]
H4('You are here:',request.url),
H1('Welcome back ',request.user.name),
P(
'This is your personal page. Your birthday is on ',
request.user.bday,BR())
)
));
response.end();
});//[server-side] nodejs server-side
if(((this==jslNs)||(!this))&&(!jslNs.target)){
return fragment;//i.e. this.transform(...)
}
var target=this == jslNs?jslNs.target:this;
if(typeof target == 'object'){
if(target.write){//i.e. jsl.transform.call(response,...);
target.write(fragment);
return true;
}else
if(this.exports){//this is a module
this.exports=fragment;
return;
}
}
throw new Error('I don\'t know what to do with this');/**An alternative way to make event handlers is by using the helper function jsl.listener
Calls to jsl.listener have the following format:
jsl.listener(buildTime1...,buildTime1N,function(event,runTime1,...runTime1N){
...
})
The parameters buildTime1...,buildTime1N are evaluated at build time and are
mapped to the event-handler as arguments runTime1...,runTime1N.
This allows to make event handlers aware of their environment with a very intuitive way.
Consider the following example and compare how the even figures versus the odd ones generate the onclick event handler.
Notice how the even figures generate the onclick event handler using jsl.listener as follows:
onclick: jsl.listener(item,function(event,person){
...
})
The argument item of jsl.listener is known at the time JSL produces the script.
jsl.listener maintains a reference to it, and passes it to the event-handler as param person
at the time the click event fires.
*/
jsl.transform(H4("Click to reveal the birthday"),DIV({class:'people'},
jsl.forEach(someDataWePulledEarlierFromTheServer,function(item,index){
switch(index % 2){
case 0://use jsl.listener to marshal the items to the event-handler
return FIGURE({
onclick:jsl.listener(item,function(event,person){
alert(
"The birthday of "+
person.name+"\nis on "+person.bday
);
})
},
IMG({src:item.href}),
FIGCAPTION(item.name)
);
case 1://use data-* attributes to access the item.* values
return FIGURE({
'data-bday':item.bday,
'data-name':item.name,
onclick:function(event){
alert(
"The birthday of "+
this.getAttribute("data-name")+
"\nis on "+this.getAttribute("data-bday")
);
}
},
IMG({src:item.href}),
FIGCAPTION(item.name)
);
};
})
)
);
/**
For a similar method suitable for generating javascript in <script> tags see also jsl.javascript
JSL is using jsl.listener internally when function properties of object arguments are processed.
*/function(/*param0...paramN-1,method*/){
var s='';
var a=new Array(arguments.length-1);
for(var i=0;i<a.length;i++){
s+=', ';
var v=arguments[i],t=typeof v;
if(v && //not null
(
(t=='object') || //objects(including arrays)
((t=='function')&&(!t.hasOwnProperty('prototype'))) //bound functions
)
){//need reference
if(clientSide){//client side doesn't need to make a persistent state of the object
var r=exports.o2r(v,v.name);
s+=jslNs.options.namespace+'['+r+']'+(jslNs.options.release?"":(v.name?'/*'+v.name+'*/':''));
}else{//server side must make a persistent state of the object. bound functions will not make it....
if(t=='function'){
console.log('server side bound function(',v.name,') that needs to be executed on the client!');
s+=v.toString();
}else{
s+=JSON.stringify(v);
}
}
}else
if(t=='string'){//strings
s+=JSON.stringify(v);
}else{//numbers, functions converted to string
s+=v;
};
}
var f=arguments[arguments.length-1];
if(f.name && (htmlNs[f.name]==f) && f.hasOwnProperty('prototype')){
f=f.name;
}else
if(f.hasOwnProperty('prototype') && clientSide && jslNs.options.optimize){
var r=exports.o2r(f,true);
f='(' + jslNs.options.namespace+'['+r+']'+(f.name?'/*'+f.name+'*/':'') + ')';
}else{
f='('+f.toString()+')';
}
return (f+'.call(this,event'+s+');').replace(/</g,'<');
}/**
fn:: a function object whose first multiline comments are to be converted to character data
jsl.cdata allows easy html cdata output without string escaping.
- If the first multiline comment starts with a single '*' the comment contents are treated as text. Thus the apearence of < is escaped with the sequence <
- If the first multiline comment starts with a double '**' the comment contents are treated as html.
jsl.cdata is internally invoked when tag-functions contain function arguments
*/
jsl.transform(
DIV(
H3("jsl.cdata is cool!"),
/** A call to cdata with single '*' */
P(jsl.cdata(function(){/*
<h3>jsl.cdata is cool!</h3>
<p><i>jsl.cdata</i> can be used to generate cdata
whithout having to use <i>strings</i> escaping</p>
In this example the comments of the function are
conveted to html text.
*/})
),
/** A similar call to cdata with double '**' */
DIV(jsl.cdata(function(){/**
<h3>jsl.rdata is cool!</h3>
<p><i>jsl.rdata</i> can be used to generate raw <b>html</b>,
whithout having to use <i>strings</i>...</p>
In this example the comments of the function are
converted to html markup.
*/})
)
)
);function(){
/*
cdata raw=false,auto=true
tdata raw=false,auto=false
rdata raw=true, auto=false
*/
var a=new Array(arguments.length);
for(var i=0;i<a.length;i++){
var s=(arguments[i]||'').toString();
if(typeof arguments[i] == 'function'){
var e=/(?:[^\/])+\/\*([^]+?)(?=\*\/)\*\//.exec(s);
if(!e){
console.warn('extraction failed!',s.slice(0,40));
}else{
s=(e?e[1]:s);
if(auto && (raw=(e=/^(\*+)/.exec(s))) ){
s=s.slice(e[1].length);
}
if(!raw){
s=s.replace(/</g,'<');//'<![CDATA['+ a + ']]>';;
}
}
};
a[i]=s;
}
return a;
}/** jsl.tdata a variation of jsl.cdata that always returns text content*/function(){
/*
cdata raw=false,auto=true
tdata raw=false,auto=false
rdata raw=true, auto=false
*/
var a=new Array(arguments.length);
for(var i=0;i<a.length;i++){
var s=(arguments[i]||'').toString();
if(typeof arguments[i] == 'function'){
var e=/(?:[^\/])+\/\*([^]+?)(?=\*\/)\*\//.exec(s);
if(!e){
console.warn('extraction failed!',s.slice(0,40));
}else{
s=(e?e[1]:s);
if(auto && (raw=(e=/^(\*+)/.exec(s))) ){
s=s.slice(e[1].length);
}
if(!raw){
s=s.replace(/</g,'<');//'<![CDATA['+ a + ']]>';;
}
}
};
a[i]=s;
}
return a;
}/** jsl.rdata a variation of jsl.cdata that always returns html content*/function(){
/*
cdata raw=false,auto=true
tdata raw=false,auto=false
rdata raw=true, auto=false
*/
var a=new Array(arguments.length);
for(var i=0;i<a.length;i++){
var s=(arguments[i]||'').toString();
if(typeof arguments[i] == 'function'){
var e=/(?:[^\/])+\/\*([^]+?)(?=\*\/)\*\//.exec(s);
if(!e){
console.warn('extraction failed!',s.slice(0,40));
}else{
s=(e?e[1]:s);
if(auto && (raw=(e=/^(\*+)/.exec(s))) ){
s=s.slice(e[1].length);
}
if(!raw){
s=s.replace(/</g,'<');//'<![CDATA['+ a + ']]>';;
}
}
};
a[i]=s;
}
return a;
}/**jsl.CSS is a helper function that converts its arguments to CSS style rules
- primitive arguments are expected to be valid CSS style rules.
- function arguments are evaluated repeativelly until they yield a non function
- object arguments, excluding arrays, are treated as if each property of the argument defines a CSS style rule.
- the property name defines the CSS selector
- the property value defines the CSS style declaration of the rule as in the case of style
- array arguments,
the array items are evaluated with the above rules and their results are merged
*/
var data=[];
jsl.transform(
STYLE(
jsl.CSS({//arg1 of the CSS is an object
/*a rule for body > nav */
'body > main > nav':{
backgroundColor:'#e5dccc'
},
/*a rule for body > main > nav li:only-child */
'body > main > nav li:last-of-type > nav > a':{
backgroundColor:'#f1c778'
}
})
)
)
/** A quite typical usecase for jsl.CSS is when you want to customize the style to the user preferences.
Check for example in the source of the server-side jsl.transform example.
*/function(){
var str="\n/*jsl.CSS*/\n";
for(var i=0;i<arguments.length;i++){
str+=valueToCascadingStyleSheet(arguments[i]);
}
return str+"\n";
}/**When we want to pass parameters inside a <script>...<script> we can use the helper function jsl.javascript .
fn:: is the function that should be invoked with arguments arg1...argN
*/
var foo={msg:"hello"};
jsl.transform(
SCRIPT(jsl.javascript(foo,function(obj){
alert(obj.msg);/*at run time, obj will refer to foo*/
}))
);
/*this flag will make the client use JSON like the server-side would*/
jsl.options.forceJSON=true;
jsl.transform(
SCRIPT(jsl.javascript(foo,function(obj){
alert(obj.msg);
}))
);
delete jsl.options.forceJSON;
/** For a similar method suitable for generating event handlers, see also jsl.listener */function(/*param0...paramN-1,method*/){
var s='';
var a=new Array(arguments.length-1);
for(var i=0;i<a.length;i++){
s+=', ';
var v=arguments[i],t=typeof v;
if(v && //not null
(
(t=='object') || //objects(including arrays)
((t=='function')&&(!t.hasOwnProperty('prototype'))) //bound functions
)
){//need reference
if(clientSide && !jsl.options.forceJSON){//client side doesn't need to make a persistent state of the object
var r=exports.o2r(v,v.name);
s+=jslNs.options.namespace+'['+r+']'+(jslNs.options.release?"":(v.name?'/*'+v.name+'*/':''));
}else{//server side must make a persistent state of the object. bound functions will not make it....
if(t=='function'){
throw new Error('server side bound function that needs to be executed on the client!');
}
s+=JSON.stringify(v);
}
}else
if(t=='string'){//strings
s+=JSON.stringify(v);
}else{//numbers, functions converted to string
s+=v;
};
}
var f=arguments[arguments.length-1];
if(f.name && (htmlNs[f.name]==f) && f.hasOwnProperty('prototype')){
f=f.name;
}else
if(f.hasOwnProperty('prototype') && clientSide && jslNs.options.optimize){
var r=exports.o2r(f,true);
f=jslNs.options.namespace+'['+r+']'+(f.name?'/*'+f.name+'*/':'');
}else{
f=f.toString();
}
var e=/\n(.*)\}$/g.exec(f);
return '\r\n'+(e?e[1]:'')+'('+f+').call(this'+s+');\r\n'
}/**jsl.choose is a helper function that can be called in two ways:
If the first argument(case) is a Number,Boolean,or a String value,
and its second argument an object(cases) then it returns the property of the object cases
with property name == case.
If no such property exists, and cases has a property named default then
jsl.choose will return the default property.
Notice that this behavior can be written in pure javascript quite efficiently with javascript expressions,
yet JSL provides it for aesthetic reasons.
If the arguments don't match the criteria above, jsl.choose returns its
first non void argument
Notice that in all cases the return values are evaluated whether there is a match or not.
This may have both performance implications, and lead to unintented errors.
So it is strongly recommended to return prefabricated functions/objects,
or even avoid jsl.choose.
*/
jsl.transform(
//using javascript expressions
jsl.forEach(['bold','italic','underline','normal'],(item)=>(
({
'bold' :B,
'italic' :I,
'underline' :U
}[item]||SPAN)('[',item,']')
)
),
HR(),
//using jsl.choose
jsl.forEach(['bold','italic','underline','normal'],(item)=>jsl.choose(item,{
'bold' :B,
'italic' :I,
'underline' :U,
default :SPAN
})('[',item,']')
),
HR(),
//the second mode of usage for jsl.choose:
jsl.forEach(['bOld','itaLic','underlime','normal'],(item)=>jsl.choose(
jsl.when(item.length==4, B),
jsl.when(/^i/.test(item), I),
jsl.when(item[0]=='u', U),
SPAN,
)('[',item,']')
),
)function(acase,cases){
if((typeIsPrimitive[typeof acase])&&(typeof cases == "object")){//treat cases as a hashmap of results
return (acase=cases[acase])||cases.default;
};
//return the first non void argument
for(var i=0;i<arguments.length;i++){
var result=arguments[i];
if(result){
return result;
}
}
}/** jsl.when(test,trueResult,falseResult) is a simple function that takes
three arguments, and returns the argument trueResult if the test is non void, or the argument falseResult otherwise.
Notice that this behavior can be written in pure javascript quite efficiently with javascript expressions,
using the test?trueResult:falseResult. yet JSL provides it for aesthetic reasons,
and because quite often the falseResult is omitted.
Please notice that in JavaScript all the arguments of a function are evaluated before the source of a function is executed.
Therefore, jsl.when(false,TAKE_THE_RED_PILL(),TAKE_THE_BLUE_PILL()), will
first call both functions and then return the result of the second.
see also jsl.choose for a relevant function.
*/function(test,etrue,efalse){
return test?etrue:efalse;
}/**jsl.defs containst the names of the tags with a corresponding JSL tag-function,
and a flag indicating whether they are singletons(1).
See below:
*/
jsl.transform(
H3('HTML tags:'),
P(
jsl.forEach(jsl.defs,function(value,name,defs,results,index){
return [index?', ':'',name,value.singleton&&I('(singleton)')];
})
)
)
/**(1)singleton tags, e.g. <br/> don't have a closing tag
*//**jsl.tags contains the jsl-functions corresponding to HTML elements.
See below:
*/
jsl.transform(
H3('HTML tags:'),
P(
jsl.forEach(jsl.tags,function(value,name,defs,results,index){
return [index?', ':'',value().replace(/</g,'&'+'lt;')];
})
),
)
/** Let us know if you are aware of a tag that should be in the list.*//**jsl.target can be used to define the target Node of jsl.transform.
It can be set to any valid css selector, or to a Node instance.
Alternativelly, jsl.transform can be called with call or apply
to define its target node.
For example:*/
jsl.target='footer .extra';//jsl.target set to a css selector
jsl.transform(P('jsl',SUP(jsl.version)));
jsl.target=document.querySelector('footer .extra');//jsl.target set to a DOM node
jsl.transform(P('jsl',SUP(jsl.version)));/** jsl.register(tagName,singleton,namespace)
registers a new tag tagName as a JSL tag-function
singleton is optional, by default false
namespace is an optional object where the new functions will be registered;
by default is the same namespace as with all the rest tag-functions
For example:
*/
/*
register the svg element svg, as a tag-function SVG(...)
*/
jsl.register('svg');
/*
make a namespace object for the rest of svg tag-functions,
to keep things tiddy
*/
var svg={};
/*
register the singleton svg element ellipse, as a tag-function svg.ELLIPSE(...)
*/
jsl.register('ellipse',true,svg);
/*
register the svg element text, as a tag-function svg.TEXT(...)
*/
jsl.register('text',false,svg);
/*
let's declare also a custom function that helps with svg transformations
*/
svg.translate=function(dx,dy){
return {transform:'translate('+dx+','+dy+')'};
}
/*
now it is JSL showtime as usual
*/
jsl.transform(
SVG({width:'100%',height:200,style:'border:1px solid black'},
jsl.forEach([0,50,100],function(vx,i){
return jsl.forEach([0,50,100],function(vy,j){
if((i*j)%2){
return;
}
return svg.ELLIPSE({cx:vx+'%',cy:vy+'%',rx:20,ry:15,
style:{
fill:'rgb(255,'+ vx +','+ vy +')'
}
},
svg.translate((0.5-vx/100)*48,(0.5-vy/100)*38)
)
})
}),
svg.TEXT({x:'50%',y:'50%','text-anchor':'middle'},
'This how jsl2svg.js could look like...'
)
)
);function(tagName,singleton,namespace){
namespace=namespace||tagnamespace;
tagName=tagName.toLowerCase();
var funcName=jslNs.options.capitalize?tagName.toUpperCase():tagName;
var f=namespace[funcName]=tagGen(tagName,singleton);
if(singleton)f.singleton=true;
return f;
}/**jsl.options define the performance and code-styling configuration of JSL.
The following script generates a list of the current configuration,
and the alternative settings:
*/
jsl.transform(TABLE(jsl.forEach(jsl.options,(value,name)=>name&&[
TR(
TD(I(name)),TD(B(value==!!value?value:'"'+value+'"')),TD(jsl.describe(name,value))
),
(value===!!value?
TR(TD({colspan:3},!value,':',jsl.describe(name,!value)))
:null)
])));
/**
The options can be set through the jsl.options variable,
and alternatively on the client through the calling script tag
For example the declaration used to load JSL in this page is:
<script src="jsl2.js?optimize=no&release=yes></ script>
*//**
JSL is pure javascript, its tag-functions are pure javascript functions, and accordingly
JSL templates are also pure javascript functions.
*/
var people=someDataWePulledEarlierFromTheServer;
//let's define a custom template
function PERSON(person,index){
return FIGURE({class:'person-figure'},
IMG({src:person.href}),jsl.when(this!=window,this),
FIGCAPTION(person.name,'#',index+1,SPAN(person.bday))
)
}
//let's try it out...
jsl.transform(
STYLE(jsl.CSS({
'.person-figure SPAN':{bottom:0,right:0}
})),
H4("Some imaginary people"),
DIV({class:'people'},
jsl.forEach(people.slice(0,2),PERSON)
)
)
//now let's put some extra styling in the template
var customStyle={style:'outline:4px solid',0:B({style:'bottom:0;'},'nice border')};
/**We will be binding the template PERSON to the object customStyle.
During execution, the PERSON's 'this' will refer to customStyle.
In its declaration, the PERSON passes its 'this' binding
as an argument to the FIGURE tag-function.
The rest is JSL magic
*/
jsl.transform(
STYLE(jsl.CSS({
'.person-figure SPAN':{bottom:0,right:0}
})),
H4("Some imaginary people"),
DIV({class:'people'},
jsl.forEach(people.slice(2,4),PERSON.bind(customStyle))
)
)
/**
Now let's define the same template using an arrow function.
*/
PERSON=(person,index)=>FIGURE({class:'person-figure'},
jsl.when(this!=window,this),
IMG({src:person.href}),
FIGCAPTION(person.name,'#',index+1,SPAN(person.bday))
);
/**
Notice! The binding to the customStyle will not work because of the arrow function in the declaration of PERSON.
Arrow functions inherit 'this' from their context of execution.
*/
jsl.transform(
STYLE(jsl.CSS({
'.person-figure SPAN':{bottom:0,right:0}
})),
H4("Some imaginary people"),
DIV({class:'people'},
jsl.forEach(people.slice(4,6),PERSON.bind(customStyle))
)
);/**
Download the documentation.js and try your own JSL template on it...
The content you see is generated using a JSL module on the server-side(nodejs).
The execution of the examples, and the DOM tree generation is performed on the browser.
Below is the template function that the server uses to build the navigation bar:
*/
BUILDMENU=function(fn,name=''){
var ref='#'+name.replace(/\s+/g,'_').replace(/\'|\"/g,'');
return NAV(
name&&A({href:ref},SPAN({class:"tick-mark"},'✓'),name),
OL(jsl.forEach(fn,(f,m)=>(m=='_ref')?null:LI(BUILDMENU(f,m))))
)
};
jsl.transform(SCRIPT({src:'/node_modules/documentation.js?ns=jsldoc',async:true,
onload:function(){jsl.transform(BUILDMENU(jsldoc));}
}));/**this is how the DOM Tree is created in all the examples*/
jsl.transform(
SCRIPT(
function doDomTreeClick(event){//[collapsed]
this.parentElement.classList.toggle('collapsed');
event.stopPropagation();
},
BUILDDOMTREE=((node)=>(DOMTREECASES[node.nodeType](node))),
()=>{
DOMTREECASES={
1: //ELEMENT_NODE
(node,name=node.nodeName.toLowerCase(),singleton=!node.hasChildNodes() && jsl.defs[name].singleton)=>
DIV({class:'ELEMENT'},
DIV({class:['tag-open',name]},singleton?{class:'tag-close'}:{onclick:doDomTreeClick},
'&','lt;',SPAN(name),
jsl.forEach(node.getAttributeNames(),(name) => DIV({class:'tag-attr'},' ',SPAN(name),
jsl.when(node.getAttribute(name),['=&','quot;',SPAN(node.getAttribute(name)),'&','quot;'])
)
),
(singleton?'/>':'>')
),
jsl.forEach(node.childNodes,BUILDDOMTREE),
jsl.when(!singleton,DIV({class:'tag-close'},'&','lt;/',SPAN(name),'>'))
),
3: //TEXT_NODE
(node)=>SPAN({class:'TEXT'},node.nodeValue.replace(/\</g,"<").replace(/([\r\n\s]+)/g," ")),
4: //CDATA_SECTION_NODE
(node)=>SPAN({class:'CDATA'},'<![CDATA['+node.nodeValue+']]>'),
7: //PROCESSING_INSTRUCTION_NODE:
(node)=>SPAN({class:'PROCESSING_INSTRUCTION'},'<?',node.nodeValue,'?>'),
8: //COMMENT_NODE
(node)=>SPAN({class:'COMMENT'},'<!--',node.nodeValue,'-->'),
9: //DOCUMENT_NODE
(node)=>DIV({class:'DOCUMENT'},SPAN('#document'),jsl.forEach(node.childNodes,BUILDDOMTREE)),
10: //DOCUMENT_TYPE_NODE
(node)=>DIV({class:'DOCUMENT_TYPE'},'<!doctype ',node.name,'>'),
11: //DOCUMENT_FRAGMENT_NODE
(node)=>DIV({class:'DOCUMENT_FRAGMENT'},SPAN('#document-fragment'),jsl.forEach(node.childNodes,BUILDDOMTREE)),
};
},
),
STYLE(()=>{//[collapsed]
/*
.ELEMENT{
font-family:monospace;
padding-left:2em;
color:gray;
border-left:1px dotted gray;
clear:both;
}
.ELEMENT.collapsed > *:not(.tag-open):not(.tag-close){
display:none;
}
.ELEMENT.collapsed>.tag-open::after{
content:'...';
}
.ELEMENT .tag-open,.ELEMENT .tag-close,.ELEMENT .tag-attr{
display:inline-block;
cursor:pointer;
}
.ELEMENT .tag-open>span,.ELEMENT .tag-close>span{
color:rgb(151,18,161);
}
.ELEMENT .tag-attr{
display:inline-block;
margin-left:0.5em;
}
.ELEMENT .tag-attr>span:nth-child(1){
color:rgb(153,69,0);
}
.ELEMENT .tag-attr>span:nth-child(2){
color:rgb(26,26,156);
}
.TEXT{
font-family:monospace;
color:black;
white-space:pre-line;
}
.DOCUMENT_TYPE{
font-family:monospace;
color:gray;
}
.COMMENT{
font-family:monospace;
color:darkgreen;
}
.DOCUMENT{
font-family:monospace;
color:gray;
}
.DOCUMENT_FRAGMENT{
font-family:monospace;
color:gray;
}
.ELEMENT .tag-open.style ~ *:not(.tag-close), .ELEMENT .tag-open.script ~ *:not(.tag-close){
white-space:pre;
}
*/}),
);
jsl.transform(
DIV(
H3('the DOM tree of the footer'),
DIV({style:'overflow-x:hidden'},
BUILDDOMTREE(document.querySelector('body footer'))
)
)
);/**
In this example we are using jsl.transform to populate the nodes of a tree from the linux distribution
as they are pulled from the server.
Notice that instead of XHR we are using the <script>...</script> element to fetch
the json data from the server asynchronously. This is a very common approach in JSL;-).
*/
var folder;
jsl.transform(
SCRIPT(
/**load the files of this node, or start from scratch*/
function loadFolder(event){
event && event.stopPropagation && event.stopPropagation();
jsl.transform(
(event?
/**generate an async scrypt that will load the files
of the current path*/
SCRIPT({src:this.id+"?folder",async:true,
/**jsl.listener will make sure that this DOMNode,
will be propagated as param fileItem
to 'doFilesLoaded' and 'doFilesError'.
*/
onload :jsl.listener(this,doFilesLoaded),
onerror:jsl.listener(this,doFilesError),
}):
/**start from scratch(event is undefined)*/
DIV("/sys",{class:"file-item pending",
id:"/sys",onclick:doFileClick
})
)
);
},
/**the subfolder list is loaded, event-handler for script.onload*/
function doFilesLoaded(event,fileItem){
event.stopPropagation();
/**
remove the <script>...</ script> tag from the document, we are done with it
*/
this.outerHTML='';
if(!fileItem.classList.contains('pending')){
return;
}
fileItem.classList.remove('pending');
/**now that we got the files of let's populate a <ul>...</ul> in fileItem*/
jsl.transform.call(fileItem,UL(
jsl.forEach(folder.files,(file,name)=>LI({
class:"file-item pending",id:file.path,
onclick:doFileClick
},SPAN(name)))
)
);
},
/**the subfolder list is failed to load, event-handler for script.onerror*/
function doFilesError(event,fileItem){
event.stopPropagation();
this.outerHTML='';
fileItem.classList.add('error');
},
/**a fileItem was clicked, event-handler for fileItem.onclick*/
function doFileClick(event){
if(this.classList.contains('pending')){
loadFolder.call(this,event);
}else
if(this.querySelector('ul')){
this.classList.toggle('collapsed');
};
event.stopPropagation();
},
function(){
loadFolder();
}
),
/** And some CSS for styling the file tree */
STYLE(function(){//[collapsed]
/*
.file-item{
list-style:none;
cursor:pointer;
}
.file-item > span:hover{
background-color:#e8e6e8;
}
.file-item::before{
content: '\025b4';
font-size:120%;
}
.file-item.pending::before{
content: '\025b8';
}
.file-item.collapsed::before{
content: '\025be';
}
.file-item.collapsed > ul{
display:none;
}
.file-item.error::before{
content:'\02a2f';
}
*/})
)