Index Next: Logging and Debugging

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__dirnamethe directory the JXP is located
String__filenamethe complete path of of the jxp file
StringuserAgentequivalent to request.headers["User-Agent"]
ObjectsessionJXP reference to the session object
Objectlocationserver-side version of JavaScript location object
ObjectrequestNodeJS HTTP request object
ObjectresponseNodeJS HTTP response object
Objectthisreference 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.

voidclear()clears all output
Objectcookie.get( String cookieName )retrieve an HTTP cookie
voidcookie.set( String cookieName, Object value )save an HTTP cookie, values accepted are string, number, boolean, null
voidcookie.del( String cookieName )Delete an HTTP cookie
Stringcharset()gets the character set
voidcharset( String charset )sets the character set "utf-8", or "ascii"
StringcontentType()gets the HTTP content-type
voidcontentType( String type )sets the HTTP content-type, eg. text/plain, text/html
Stringheader( String property )gets a NodeJS HTTP header property
voidheader( String property, Object value )sets a NodeJS HTTP header property
voidinclude( String path, Object input )calls another JXP with the specific input data
voidprint( Object output )outputs content to the page, an alternative to using <%= %>
voidredirect( String url  )clear output and send an HTTP redirect instead
IntegerstatusCode( )returns the HTTP status code
voidstatusCode( 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:

Source of ~/jaxserver/app/helloworld/views/today.jxp:
<html>
<body>
 
<p>Today's date is <%= date %></p>

<p>The word of the day is : <b><%~ wordofday %></b></p>

</body>
</html>

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);
	}
};
Source of ~/jaxserver/app/helloworld/views/date.jxp:
<html>
<body>

The date you chose is <%= date %>
 
</body>
</html>

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:

Source of ~/jaxserver/app/helloworld/web/date-standalone.jxp:
<%
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 %>

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:

Source of ~/jaxserver/app/helloworld/web/loopsfuncs.jxp:
<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 :

Source of ~/jaxserver/app/helloworld/views/three.jxp:
<%
redirect('/four'); // sends 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.

Source of ~/jaxserver/app/helloworld/views/includehtml.jxp:
<%
include("include-message.html");
%>
Source of ~/jaxserver/app/helloworld/views/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.

Source of ~/jaxserver/app/helloworld/views/includejxp.jxp:
<%
include("include-message.jxp",{message:"include JXP was successful!"});
%>
Source of ~/jaxserver/app/helloworld/views/include-message.jxp:
<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.

Source of ~/jaxserver/app/helloworld/views/importjs.jxp:
<%@ import "import-message.js" %>
//...
<% doImport("import JS successful!"); %>
//...
Source of ~/jaxserver/app/helloworld/views/import-message.js:
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.

Source of ~/jaxserver/app/helloworld/views/importjxp.jxp:
<%@ import "import-message.jxp" %>
//...
<%
doImport("import JXP successful!");
%>
//...
Source of ~/jaxserver/app/helloworld/views/import-message.jxp:
<%
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.

Source of ~/jaxserver/app/helloworld/views/date-witherrors.jxp:
<%
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 %>

Multi-Format Handling

A single JXP can generate multiple types of responses by setting the contentType:

Source of ~/jaxserver/app/helloworld/web/jsonorxml.jxp:
<%
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:

Source of ~/jaxserver/app/helloworld/web/jsonorxml.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:

Source of ~/jaxserver/app/helloworld/web/cachecontrol.jxp:
<%
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>
Next: Logging and Debugging