PaJes - Simple JavaScript Templating Library for generating HTML
PaJes is a simple JavaScript templating library for generating well-formed HTML. It can be used to generate innerHTML segments in browser in a safe and clean way without having to resolve to the error prone string concatenation and manual escaping of HTML entities. It can also be used on server side along with server side JavaScript frameworks like node.js as a full blown view templating solution. In fact, PaJes is available as a node.js compatible CommonJS module using file PaJes-CommonJS.js.
Why yet another JS library to generate HTML?
PaJes's approach to HTML generation is similar in philosophy to that of JAML's which in turn is inspired by Ruby's HAML. So why yet another library? It is certainly not meant to be either an exercise in vanity or an attempt to add yet another name to the already delicious alphabet soup :) I wrote it to provide following particular features I personally missed while using other libraries:
-
Better handling of loops and if-then-else branches during output generation. I wanted embedding of loops and if-then-else logic in the template to be as simple as in PHP or JSP. Though there is some support for handling indirect looping of collections through the use of reusable templates in other solutions; being forced to break out part of code in separate template every time you need a loop is less than ideal, IMHO. Also, I couldn't figure out an easy way to conditionally include a part of an output using if-then-else branches. PaJes makes handling both loops and conditional logic trivial as shown in examples 3,4 and 6 below.
-
PaJes provides even better, functional programming like support for loops and conditionals through built in functions "forEach()" and "checkIf()" to enable more terse coding
-
Better support to include JavaScript function code in the output without the need of complicated escape sequences as shown in example 5 below.
Example 1: Simplest use case: static HTML generation
PaJes code
DIV(
{id:"demo-div", width: "60%"},
OL(
{style: "list-style-type: upper-roman"},
LI(
"First Student",
A(
{href:"/student-details?id=1"},
"details"
)
),
LI(
"Second Student",
A(
{href:"/student-details?id=2"},
"details"
)
)
)
).display(out);
out.flush();
output
<DIV id="demo-div" width="60%">
<OL style="list-style-type: upper-roman">
<LI>
First Student
<A href="/student-details?id=1">
details
</A>
</LI>
<LI>
Second Student
<A href="/student-details?id=2">
details
</A>
</LI>
</OL>
</DIV>
PaJes follows convention of using HTML elements' all capitalized tag names as function names to generate those respective tags in the output. These tag generating functions can be nested arbitrarily deep to generate corresponding hierarchical markup. Tag's attributes, if any, are specified as a first argument to the function using object literal notation. If the tag has no attributes you can omit the first object literal argument. That's mostly all you need to know to start working with PaJes!
Example 2: Value substitution (Merging template with model)
PaJes code
var model = [
{name: "Lisa Simpson", id: 1},
{name: "Bart Simpson", id: 666}
]
DIV(
{id:"demo-div", width: "60%"},
OL(
{style: "list-style-type: upper-roman"},
LI(
model[0].name,
A(
{href:"/student-details?id="+model[0].id},
"details 1"
)
),
LI(
model[1].name,
A(
{href:"/student-details?id="+model[1].id},
"details 2"
)
)
)
).display(out);
out.flush();
output
<DIV id="demo-div" width="60%">
<OL style="list-style-type: upper-roman">
<LI>
Lisa Simpson
<A href="/student-details?id=1">
details 1
</A>
</LI>
<LI>
Bart Simpson
<A href="/student-details?id=666">
details 2
</A>
</LI>
</OL>
</DIV>
Example 3: Collections and Loops
All that hard coded model indices in the example 2 above leaves something to be desired. What we need is a generic way to loop through any collection of items and spit out same templatized markup for each item. "forEach()"to the rescue.
PaJes code
var model = [
{name: "Lisa Simpson", id: 1},
{name: "Bart Simpson", id: 666}
]
DIV(
{id:"demo-div", width: "60%"},
OL(
{style: "list-style-type: upper-roman"},
forEach(model, studentDetails)
)
).display(out);
out.flush();
function studentDetails(index, value) {
return LI(
value.name,
A(
{href:"/student-details?id="+value.id},
"details "+(index+1)
)
);
}
output
<DIV id="demo-div" width="60%">
<OL style="list-style-type: upper-roman">
<LI>
Lisa Simpson
<A href="/student-details?id=1">
details 1
</A>
</LI>
<LI>
Bart Simpson
<A href="/student-details?id=666">
details 2
</A>
</LI>
</OL>
</DIV>
Above code generates exactly the same output as the earlier example but it is much more concise and clean. forEach takes a collection as its first argument and a function that generates markup for an individual item of the collection as its second argument. It internally calls the markup generating function once for each item from the collection passing the index of the item and the item itself as arguments to each call. Finally, it combines all individual markups returned by these function calls and inserts it in the enclosing parent tag.
forEach may be called with an array or an object as a collection. When called with an array it will call the markup generating function with array index and item at that index for each item in the array. When called with an object it will call the markup generating function with property name and the corresponding property value for each of the object's property.
Note that in the example above you could further reduce code size somewhat by using anonymous function instead of the function studentDetails().
PaJes code
DIV(
{id:"demo-div", width: "60%"},
OL(
{style: "list-style-type: upper-roman"},
forEach(
model,
function(index, value) {
return LI(
value.name,
A(
{href:"/student-details?id="+value.id},
"details "+(index+1)
)
);
}
)
)
)
output
<DIV id="demo-div" width="60%">
<OL style="list-style-type: upper-roman">
<LI>
Lisa Simpson
<A href="/student-details?id=1">
details 1
</A>
</LI>
<LI>
Bart Simpson
<A href="/student-details?id=666">
details 2
</A>
</LI>
</OL>
</DIV>
Example 4: Generating output conditionally
Use checkIf() to generate output conditionally.
PaJes code
DIV(
{id:"demo-div", width: "60%"},
checkIf(
/* check condition */
(grade === 'A'),
SPAN("Ivy League"),
SPAN("Community College")
)
)
output case I: grade = "A"
<DIV id="demo-div" width="60%">
<SPAN>
Ivy League
</SPAN>
</DIV>
output case II: grade = "C"
<DIV id="demo-div" width="60%">
<SPAN>
Community College
</SPAN>
</DIV>
You can leave out the third argument of checkIf(). In that case checIf() will act like simple "if(){ }" instead of "if(){ }/else{ }" checkIf() calls can also be nested, just like normal if/else. A slightly contrived example:
PaJes code
DIV(
{id:"demo-div", width: "60%"},
checkIf(
(flag > 0),
checkIf(
(flag === 1),
SPAN('flag set to 1'),
SPAN('flag greater than 0 but not equal to 1')
),
checkIf(
(flag === 0),
SPAN('flag set to 0'),
SPAN('flag is negative')
)
)
)
Finally, if you are really concerned about your memory utilization or performance and have a really gnarly case of deep nested checkIf()s with each if generating huge mark up you can lessen your load by wrapping second and third arguments of checkIf() in anonymous function calls. If you look really closely at the checkIf() call you would realize its both "if" and "else" clauses are always evaluated (that is markup is generated for both clauses) and then one of the markup is thrown away depending upon the check condition. It generally poses no problem for 99% of cases but if you are convinced that your case falls in the remaining 1% you can use following idiom:
PaJes code
DIV(
{id:"demo-div", width: "60%"},
checkIf(
(grade === 'A'),
function() {
return SPAN("Ivy League");
},
function() {
return SPAN("Community College");
}
)
)
In this case only the matching clause's markup will be generated. Remember the time proven dictum though: "Premature optimization is root of all evil".
Example 6: Adding new children to old tags
Typically children of a HTML tag are specified as nested, inline PaJes function calls. For example, DIV(OL(LI())). This will generate HTML tree with DIV tag at its root with a single child OL which in turn has one child LI. While this is the most convenient and succinct way of constructing HTML what if you need to customize parts of your HTML tree depending upon runtime values? Say you want to add/remove some HTML elements conditionally based on the values being passed in the model. checkIf() from example 4 above might be useful as long as the inclusion/exclusion logic is fairly simple. But as soon as the logic becomes complex, multi-level nesting of checkIf() calls may become unmanageable. Or sometimes you may decide it's just easier to build your markup using a straightforward procedural code for that one peculiar use case. PaJes can support that too. Key learning here is that the value returned by any PaJes HTML tag building call like DIV() and OL() etc. are actually functions that retain their arguments from earlier (and all successive) calls through closure. This means you can store a reference returned by any particular tag building PaJes call and call it later multiple times with new arguments (new child tags) to in effect "append" new children to the tag. This all sounds much more complicated than it really is. Following example shows how straightforward it is to use this in practice.
PaJes code
var model = {loggedIn: true, name: "John Doe"};
/* store the reference to the DIV */
var topDiv = DIV({id: "greetingbox"});
if (model.loggedIn) {
topDiv(SPAN("Welcome "+model.name));
}
else {
topDiv(STRONG("Please log in"));
}
topDiv.display(out);
out.flush();
loggedIn: true
<DIV id="greetingbox">
<SPAN>
Welcome John Doe
</SPAN>
</DIV>
loggedIn: false
<DIV id="greetingbox">
<STRONG>
Please log in
</STRONG>
</DIV>
Ability to store a reference to particular tag's building function (topDiv in the example above) and then call it later - multiple times if desired - with more children/markup can dramatically simplify markup generating code in some cases. All subsequent calls to the same function effectively append the passed in children to the tag.
Example 7: Something like JSP's custom tags or Freemarker's macros
JSP's custom tags and Freemarker's macros are invaluable tools in promoting use of reusable presentation components. Creating similar reusable components using PaJes is even simpler. There are no extraneous TLD files to define or even a need for a new syntax/keyword like "macro"! Just use normal JavaScript function to package your component. JavaScript rocks!
For example following function defines a reusable "breadcrumb" component that generates line like "Car Electronics > Car Audio and Video > Car Stereos" on sites like Amazon
PaJes code
var model = ["Car Electronics",
"Car Audio and Video",
"Car Stereos"];
function breadcrumb(model) {
return UL(
{"class": "brdcrmb"},
forEach(
model,
function(index, item) {
if (index === (model.length-1)) {
return LI(""", item, """);
}
else {
return LI(""", item, """, ">");
}
}
)
);
}
DIV(
"Category:",
breadcrumb(model)
)
output
<DIV>
Category:
<UL class="brdcrmb">
<LI>
"Car Electronics"
>
</LI>
<LI>
"Car Audio and Video"
>
</LI>
<LI>
"Car Stereos"
</LI>
</UL>
</DIV>
Note how easy it was to define reusable presentation component "breadcrumb" and use it once it was defined.