JaxServer Pages (JXP)
JXP Code Blocks
JaxServer Pages, or JXP, are a way to build dynamically generated pages in a JaxServer application. JXP's can be used as templates in JaxServer in the same way that JSP's are used in a Java Servlet.
JXP evalautes JavaScript code embedded in a page between the special tags <% and %>. Any text outside of a JXP block remains as untouched HTML.
<% /* evaluates as javascript */ %> <%= /* evalute and output to page */ %> <%~ /* evalute, escape HTML, and output to page */ %> <%@ import "..." %> <%-- Comment Block --%>
JXP Properties
These properties can be used to perform formatting operations within a template:
| String | __dirname | the directory the JXP is located |
| String | __filename | the complete path of of the jxp file |
| String | userAgent | equivalent to request.headers["User-Agent"] |
| Object | session | JXP reference to the session object |
| Object | location | server-side version of JavaScript location object |
| Object | request | NodeJS HTTP request object |
| Object | response | NodeJS HTTP response object |
| Object | this | reference to the controller which invoked the JXP |
JXP Functions
JXP functions are convenience functions that are scoped to the context of the route and controller that invoked it. These functions allow a JXP template to change what kind of response will be sent to the browser.
| void | clear() | clears all output |
| Object | cookie.get( String cookieName ) | retrieve an HTTP cookie |
| void | cookie.set( String cookieName, Object value ) | save an HTTP cookie, values accepted are string, number, boolean, null |
| void | cookie.del( String cookieName ) | Delete an HTTP cookie |
| String | charset() | gets the character set |
| void | charset( String charset ) | sets the character set "utf-8", or "ascii" |
| String | contentType() | gets the HTTP content-type |
| void | contentType( String type ) | sets the HTTP content-type, eg. text/plain, text/html |
| String | header( String property ) | gets a NodeJS HTTP header property |
| void | header( String property, Object value ) | sets a NodeJS HTTP header property |
| void | include( String path, Object input ) | calls another JXP with the specific input data |
| void | print( Object output ) | outputs content to the page, an alternative to using <%= %> |
| void | redirect( String url ) | clear output and send an HTTP redirect instead |
| Integer | statusCode( ) | returns the HTTP status code |
| void | statusCode( Integer statusCode ) | sets the HTTP status code |
JXP Views
Using JXP's as views allow website templates to be separated from the rest of the HTML, CSS, and image content. The controller can reuse a JXP as a template to generate many web pages.
JXP's are invoked using the jax.server.Controller API method sendView():
this.sendView ( HttpRequest request, HttpResponse response, String viewName, Object inputData );
Example:
function Helloworld() {
//...
// the url "/today" will execute the "today()" method
this.get('/today','today');
}
//..
Helloworld.prototype.today = function(request, response) {
var words = 'the quick brown fox jumped over the lazy dog'.split(' ');
// pick a random word as the word of the day
var randomword = words[Math.floor(Math.random()*words.length)];
// inputData properties will become local variables in the JXP
var inputData = {
date : new Date(),
wordofday : randomword
};
// sendView will process the today.jxp view and send it's result as the HTTP response
this.sendView(request, response, "today", inputData);
};
The today.jxp file needs to be saved in the application's views directory:
-
- - reusable JXP templates go here
- - the templae that will be processed when sendView(req,res,'today') is called
<html> <body> <p>Today's date is <%= date %></p> <p>The word of the day is : <b><%~ wordofday %></b></p> </body> </html>
- Results: http://localhost:8000/today
Routes and View Reuse
A more dynamic example can be created by allowing the user to pick any date. A regular expression route and parsing code will send a JavaScript Date object to the view. The existing today> route can be also be reused if no date was chosen.
function Helloworld() {
//...
this.get('/date','date'); // display "today"
this.get(/^\/date\/(\d{4})\/(\d{2})\/(\d{2})$/,'date'); // display a chosen date
}
//...
Helloworld.prototype.date = function(request, response) {
// request.matches returns the RegExp array that matched the route
// the url /date/2011/03/06 will return a string array ['/date/2011/03/06','2011','03','06']
if (request.matches) {
// convert to integers using parseInt(x, 10)
var year = parseInt(request.matches[1],10);
var month = parseInt(request.matches[2],10) - 1; // JavaScript Date() uses months starting at 0
var day = parseInt(request.matches[3],10);
var input = {
date : new Date(year,month,day) // convert year/month/date to date object
};
this.sendView(request, response, "date", input);
}
else {
// reuse the "today" route
this.today(request, response);
}
};
<html> <body> The date you chose is <%= date %> </body> </html>
- http://localhost:8000/date (today)
- http://localhost:8000/date/1885/09/02
- http://localhost:8000/date/1955/11/05
- http://localhost:8000/date/1985/10/26
Stand-Alone JXP's
JaxServer Pages can operate without an explicit route when placed in the application's /web/ directory. This is useful for pages that do not require any data from the controller and gives JXP's almost the same level of template flexibility as PHP and JSP have with one important limitation. Because of NodeJS's asynchronous nature, stand-alone JXP's cannot perform any I/O, they cannot read data from the file system, query a database, or perform any of NodeJS's "sync" commands.
The following is a stand-alone version of the date view using a date parameter to choose the date instead of a regular expression:
<%
var date;
if (typeof request.params.date=='string') {
var matches = request.params.date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (matches) {
var year = parseInt(matches[1],10);
var month = parseInt(matches[2],10) - 1;
var day = parseInt(matches[3],10);
// convert to date object
date = new Date(year,month,day);
}
else date = 'INVALID DATE';
}
else {
date = new Date();
}
%>
The date you chose is <%= date %>
- http://localhost:8000/date-standalone.jxp (today)
- http://localhost:8000/date-standalone.jxp?date=1885-09-02
- http://localhost:8000/date-standalone.jxp?date=1955-11-05
- http://localhost:8000/date-standalone.jxp?date=1985-10-26
If's, Loops, and Functions
Because JXP blocks get converted into conventional JavaScript, any combination of loops, functions, and any other valid JavaScript can be performed in the page:
<ul>
<%
function printNumber(number) {
%>
<li><%=number%></li>
<%
}
function even(number,end) {
do {
// only print even numbers
if (number%2==0) printNumber(number);
number++;
}
while (number<=end);
}
// print even numbers between 1 and 10
even(1,10);
%>
</ul>
JXP Redirect Views
HTTP redirection can be delegated to a JXP view. First, the routes are set up:
function Helloworld() {
//...
this.get('/three','three');
this.get('/four','four');
}
//...
Helloworld.prototype.three = function(request, response) {
this.sendView(request,response,'three');
};
Helloworld.prototype.four = function(request, response) {
jax.server.sendHTML(request, response, 'this is four');
};
Instead of outputting a web page, the three.jxp template can the JXP API function redirect() to send an HTTP redirect response :
<%
redirect('/four'); // sends to http://localhost:8000/four
%>
- http://localhost:8000/three
- redirects to: http://localhost:8000/four
HTML Includes
The JXP Function include() can be used to include the contents from another file. Take note: by default, in production mode included files are cached in memory for fast performance.
<%
include("include-message.html");
%>
<div style="color:red"> HTML include successful! </div>
JXP Includes
The include() function can be used to process other JXP templates. An input object can be sent to it, in order to relay variables to the template.
<%
include("include-message.jxp",{message:"include JXP was successful!"});
%>
<div style="color:red"> <%~message%> </div>
JavaScript Imports
Importing files operates differently than includes. Importing a file will copy its contents into the current JXP. This allows for sharing a library or copying data to many templates, with the disadvantage of increasing the amount of memory consumed per template.
<%@ import "import-message.js" %>
//...
<% doImport("import JS successful!"); %>
//...
function doImport(message) {
print('<span style="color:blue">'+String.escapeHTML(message)+'</span>');
}
JavaScript Libraries
An alternative, non-JXP way to call JavaScript libraries is to attach the library to the controller, and refer to the controller using the this context from within the JXP:
Helloworld.prototype.callMyLibrary = function(message) {
return '<span style="color:purple">'+String.escapeHTML(message)+'</span>';
}
<%= this.callMyLibrary("call to JS Library successful!") %>
JXP Imports
Importing a JXP allows a way to import more JXP that may be shared between different templates. Functions imported in this manner can contain further JXP processing which yeilds very flexible templating capabilities.
<%@ import "import-message.jxp" %>
//...
<%
doImport("import JXP successful!");
%>
//...
<%
function doImport(message) {
%><span style="color:blue"><%~message%></span><%
}
%>
Error Handling in JXP
From within a JXP template proper error messages can be generated by using clear() to eliminate any output, and statusCode() to change the HTTP status code sent to the browser, or redirect() to send an HTTP 302 redirect header. A return; statement within a JXP will exit the JXP and prevent any further output.
<%
var date;
if (typeof request.params.date=='string') {
var matches = request.params.date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (matches) {
var year = parseInt(matches[1],10);
var month = parseInt(matches[2],10) - 1;
var day = parseInt(matches[3],10);
// convert to date object
date = new Date(year,month,day);
}
else {
// custom error handling
clear();
statusCode(400);
%>
<b>INVALID DATE</b>
<%
return;
}
}
else {
// redirect to today
var today = new Date().format('%Y-%m-%d');
// redirect() will clear and set the statusCode to 302
redirect(location.pathname+'?date='+today);
return;
}
%>
The date you chose is <%= date %>
- none: http://localhost:8000/date-witherrors.jxp
- invalid: http://localhost:8000/date-witherrors.jxp?date=5-5-5
- success: http://localhost:8000/date-witherrors.jxp?date=2011-05-09
Multi-Format Handling
A single JXP can generate multiple types of responses by setting the contentType:
<%
var data = {
name : 'John Smith',
age : 30,
gender : 'male'
};
var format = 'json';
if (typeof request.params.format=='string' && request.params.format==='xml') format = 'xml';
if (format == 'json') {
contentType('text/javascript');
print(JSON.stringify(data,null,4));
}
else if (format == 'xml') {
contentType('text/xml');
// xml can't start with a newline so make sure there is none before the declaration
%><?xml version="1.0" encoding="utf-8"?>
<!-- generated by JXP -->
<data>
<%=jax.util.json2xml(data)%>
</data>
<%
}
Property Examples
The following example lists all the properties available to a JXP:
<% if (jax.config.env=='dev') { // only expose in development mode %>
this.appName = <%=this.appName%><br/>
this.appRoot = <%=this.appRoot%><br/>
this.webRoot = <%=this.webRoot%><br/>
this.jxpRoot = <%=this.jxpRoot%><br/>
<% } %>
__dirname = <%=__dirname%><br/>
__filename = <%=__filename%><br/>
userAgent = <%=userAgent%><br/>
<%
for (var i in location) {
%>location.<%=i%> = <%=location[i]%><br/><%
}
%>
request.url = <%=request.url%><br/>
request.method = <%=request.method%><br/>
<%
for (var i in request.headers) {
%>request.headers["<%=i%>"] = <%=request.headers[i]%><br/><%
}
%>
Cache-Control and Charset
To change the Cache-Control, Date, or Expires header for detailed cache handling the header() function can be used:
<%
charset('ascii');
header('cache-control','public');
header('date',new Date().toString());
header('expires',Date.days(30).toString());
%>
<html>
<body>
charset is <%=charset()%><br/>
cache-control is <%=header('cache-control')%><br/>
date is <%=header('date')%><br/>
expires is <%=header('expires')%>
</body>
</html>