Routing
The Sapphire server does things other than just routing control to your application. It also serves static files, routes services, and manages cookies and sessions.
Application Routing
Applications can have sub-applications, for example, the application games could
have two sub-applications, mahjongg and solitaire, accessed through the urls
"/games/mahjongg" and "games/solitaire". All applications live under the apps
directory in the Sapphire root directory. "apps/games/mahjongg/mahjongg.js"
would be the entry point for the "games/mahjongg"application. All application
files export a single function called getApplication
which takes two
parameters, the request and the response. This function should return a promise
that will be resolved with an Application object.
Before the getApplication
function is called, a session object and a cookies
object are added both the the request and response parametera. Any changes to
the session object must be explicitly saved. Cookie changes will be updated when
the application is output to the browser. Although cookies can be added to the
cookies object under the request parameter, they should be added to the cookies
object in response.
Static File Routing
There are four types of static files:
- Global static files. These are the files available to all applications. They are in the /public/assets directory off the sapphire root.
- Local static files. These are files specific to an application or feature. They are located under the assets folder for the feature or application.
- Page files. These are the page template files; they will be served from pages directory under the application or feature directory.
- Dialog files. These are the dialog template files; they will be served from the dialogs directory under the application or feature directory.
Service Routing
Service URLs take the following form:
<app>/services/<service>/[...objects]/<method>
Parts | |
---|---|
app | The name of the application that implemented the service |
service | The name of the service being called |
objects | A nested list of objects, for example, building/resources |
method | The specific service method being called |
Example: /games/services/user/login
To find the service function, the service router looks for a directory named services in the application directory, and within that directory tries to load <service>.js
. Each service must export a function or object that corresponds to the first part of the service path. The router will then attempt to drill down into this service object to find the objects specified. For instance, if your service was named account, and you had an object named settings and a method named set, it would look for the presence of settings within the service object. The last part of the route is assumed to be the method name. The router will verify that this is a function, and then call it, passing the request, the response and the post data.
Application Class
Use the Application class to describe your application. Sapphire will use this description to construct the necessary HTML to send to the browser. The overall structure of the HTML document is defined in a file named master.html
. This file is a simple outline of the document with placeholders for where various parts of the application will be located. This is the entire default master html file, but for your application can specify a different one.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;UTF-8">
{favicon}
{title}
{metadata}
{masterCSS}
{headerJS}
</head>
<body class="{states}">
{body}
{masterJS}
{javaScript}
</body>
</html>
There are a number of keywords inside curly braces in this file, and these are used to mark where specific application pieces will be located. This pieces will be explained below.
Keywords | |
---|---|
favicon | The metadata tag to specify the favicon goes here |
title | The title tag goes here |
metadata | All the specified metadata tags go here |
masterCSS | The list of added CSS files go here |
headerJS | Once in a while it is necessary to specify a JavaScript file in the html header. These files will go here. |
states | States are CSS classes that define the initial state of the application |
body | The body is the HTML that specifies the overall chrome for the application. This is stuff outside of the pages. It goes here. |
masterJS | All the specified JavaScript files go here |
javaScript | The generated JavaScript goes here |
initialize
This is the constructor for the application object. It is automatically called when you create it.
initialize : function(ns, req)
Parameters | |
---|---|
ns | Namespace for the application variables |
req | the request object from the http server |
addState
Call this method to add a state class to the body tag of the HTML file. States are used to setup initial presentation states for the application. For example, a login state could configure the application header to display the login information, rather than a login button.
addState : function(state)
Parameters | |
---|---|
state | Name of the state |
addVariable
Call this function to add a variable to the application. Variables will be added to the name space specified when the Application was constructed.
addVariable : function(name, value)
Parameters | |
---|---|
name | Name of the variable |
value | Value of the variable this should be the native type, not a JSON string |
addConfig
Call this function to add a variable to the application that should be assigned before the JavaScript files are specified. Sometimes JavaScript files will need information such as the baseUrl for the application to properly initialize.
addConfig : function(name, value)
Parameters | |
---|---|
name | Name of the variable |
value | Value of the variable, this should be of the native type, not a JSON string |
addLink
Call this function to add a link meta tag to the application.
addLink : function(rel, href, type)
Parameters | |
---|---|
rel | |
href | The link. |
type | The mime type of the content of the link. |
addURL
Call this method to add a URL to the list of managed URLs. URLs will be
available in <namespace>.urls.<name>
This can be used to setup the urls for
service calls.
addUrl : function(name, value)
Parameters | |
---|---|
name | Name of the url |
value | Actual URL itself |
setBody
Call this method to set the body file for the application. The body defines the
overall chrome of the application. It should contain an element with the id
pages
which specifies where the in the document pages are placed. It should
also contain an element with the id dialogs
for the dialogs. If using the
dialogs feature, then an element with the id dialog-overlay
should appear
before the dialogs
element.
setBody : function(file)
Parameters | |
---|---|
file | Path to the file relative to the root of your sapphire directory, for example apps/games/templates/body/html |
setMaster
Call this method to set the master file for the application. The master file was described above. If this function is not called, then a default master file will be used.
setMaster : function(file)
Parameters | |
---|---|
file | Path to the file relative to the root of your sapphire directory, for example apps/games/templates/body/html |
addTemplates
Call this method to add templates to the HTML file. The HTML file specified will be added to the output immediately after the body. Templates will be described later in this document.
addTemplates : function(file)
Parameters | |
---|---|
file | Path to the file – relative to the root of your sapphire directory. |
addFileReplacement
Call this method to add a file replacement. In addition to the curly braced
keywords in the master.html
file, user defined replacements can exist in
either this file or in the body file. This function will replace the replacement
with the passed name, with the contents of the file specified. This is a bit
like an include file
addFileReplacement : function(name, file)
Parameters | |
---|---|
name | Name of the replacement |
file | Path to the file – relative to the root of your sapphire directory. |
addStringReplacement
Call this method to add a string replacement. In addition to the curly braced
keywords in the master.html
file, user defined replacements can exist in
either this file or in the body file. This function will replace the
replacement with the passed name, with the passed string which should contain
valid HTML.
addStringReplacement : function(name, value)
Parameters | |
---|---|
name | Name of the replacement |
value | Replacement string |
addJS
Call this method to add JavaScript files to the application.
addJS : function(files, raw)
Parameters | |
---|---|
files | Array of JavaScript files these should be relative to either the apps root, or /public for global files. |
raw | Set this value to true if the link should remain unmodified, otherwise it will be cache busted. This would be the case if the linked file existed on a different domain. |
addAsyncJS
Call this method to add JavaScript files to the application that will loaded asynchronously.
addAsyncJS : function(files)
Parameters | |
---|---|
files | Array of JavaScript files. These should be relative to either the apps root, or /public for global files. |
addCSS
Call this method to add CSS files to the application.
addCSS : function(files)
Parameters | |
---|---|
files | The array of CSS files. These should be relative to either the apps root, or /public for global files. |
addAsyncCSS
Call this method to add CSS files to the application that will be loaded asynchronously.
addAsyncCSS : function(files)
Parameters | |
---|---|
files | The array of CSS files. These should be relative to either the apps root, or /public for global files. |
addPage
Call this method to add a page to the application. Pages will be loaded on demand.
addPage : function(spec)
Parameters | |
---|---|
spec | Specification for the page |
The spec is an object with a number of fields specifying the details for this page.
Fields | |
---|---|
name | Name of the page as it will be referenced in the client |
url | Path to the page template file, an HTML file |
javascript | Array of JavaScript files that will be loaded the first time a page is shown |
css | Array of css files that will be loaded the first time a page is shown |
dontPrune | Set this to true if the page should not be pruned. When a page is not pruned it will remain in the DOM even when it is not shown |
clone | Set this to true if a new instance of a page should be created every time it is shown |
addDialog
Call this method to add a dialog to the application. Dialogs are conceptually very similar to pages, but function differently. A dialog will be presented in a modal state and must be dismised before continuing. Dialogs will be loaded on demand.
addDialog : function(spec)
Parameters | |
---|---|
spec | Specification for the page |
The spec is an object with a number of fields specifying the details for this dialog.
Fields | |
---|---|
name | Name of the dialog as it will be referenced in the client |
url | Path to the dialog template file, an HTML file |
javascript | Array of JavaScript files that will be loaded the first time a dialog is shown |
css | Array of css files that will be loaded the first time a dialog is shown |
clone | Set this to true if a new instance of a dialog should be created every time it is shown |
addPanel
Call this method to add a loadable panel to the application. Panels are sub-parts of an application that are not pages or dialogs, but managed separately. For instance, a page may need many sub-parts, each one standing alone. Panels are specified using a data structure with the following members
addPanel : function(setName, spec)
Parameters | |
---|---|
setName | Name of a panel set where this panel will be used, must be a valid JavaScript identifier |
spec | Specification for the page |
The spec is an object with a number of fields specifying the details for this panel.
Fields | |
---|---|
name | Name of the panel as it will be referenced in the client |
url | Path to the panel template file, an HTML file |
javascript | Array of JavaScript files that will be loaded the first time a panel is shown |
css | Array of css files that will be loaded the first time a panel is shown |
setTitle
Call this method to set the title for the HTML document.
setTitle : function(title)
Parameters | |
---|---|
title | Title for the given page |
addMetadata
Call this method to add metadata tags to the output HTML.
addMetadata : function(name, content)
Parameters | |
---|---|
name | name of the metadata |
content | content of the metadata |
setFavicon
Call this method to set a favicon metadata tag to the HTML.
setFavicon : function(url)
Parameters | |
---|---|
url | url of the favicon |
Example
Here is example code for an application called sparcade. This application uses
two built in features animated
and dialogs
. It also includes a dialog named
mj
.
var Q = require('q');
var sapphire = require('sapphire-express');
var mj = require('./pages/mj/mj.js');
function main(req, res, app)
{
app.addCSS([
'/sparcade/assets/css/sparcade.css',
]);
app.addJS([
'/assets/js/lib/translate.js',
'/assets/js/lib/templates.js',
'/assets/js/lib/sets.js',
'/sparcade/assets/js/random.js',
'/sparcade/assets/js/Views/Sparcade.js',
'/sparcade/assets/js/Controllers/Sparcade.js',
]);
return Q(app)
}
exports.getApplication = function(req, res)
{
var session = req.session.get();
var app = new sapphire.Application('SPARCADE', req, res);
app.setTitle('SPArcade');
app.setBody('apps/sparcade/templates/body.html');
app.setMaster('apps/sparcade/templates/master.html');
return sapphire.features.animator(req, res, app)
.then(sapphire.features.dialogs.bind(sapphire.features.dialogs, req, res))
.then(main.bind(this, req, res))
.then(mj.bind(mj, req, res))
.then(function(app)
{
return Q(app);
})
}
Feature Class
A feature is a description part of an application. Features can be reused across multiple applications. All the instructions to build this part of the application use paths relative to the path of the feature's javascript file. Use the Feature class to create a feature.
The methods of the Feature class mirror methods in the Application class, but any relative paths specified in any of these methods will be modified to point to the feature directory.
The Feature class is implemented as a convenience, so that paths do not need to be duplicated multiple places, and so that the feature can be more easily relocated.
Pages and dialogs are implemented as features.
initialize
This is the constructor for the feature.
initialize : function(app, path)
The path should be absolute rooted off the app directory, e.g.
var admin = new Feature(app, '/sparcade/features/header/');
Parameters | |
---|---|
app | Application object that this feature is a subset of |
path | Path to the feature root |
Example
var Q = require('q');
var Feature = require('sapphire-express').Feature;
module.exports = function(req, res, app)
{
var mj = new Feature(app, '/sparcade/pages/mj/');
mj.addPage({
name: 'mj',
url: 'assets/pages/mj.html',
javascript: ['assets/js/Controllers/MahJongg.js', 'assets/js/Views/MahJongg.js', 'assets/js/Models/MahJongg.js'],
css: ['assets/css/mj.css']
});
return Q(app);
}
Notice that all the files local to the feature are specified with relative paths, while those that are outside of the feature, such as shared JavaScript and CSS files are specified with an absolute path.
This example is implemented using promises, which allows the main application to include this feature as part of a promise chain.
var promise = account(req, res, app)
.then(header.bind(this, req, res))
.then(admin.bind(this, req, res))
.then(function(app)
{
app.getHTML(callback);
}).done();
Services
Service functions must return a Q promise that will be fulfilled when the service function is complete. This makes it easier for the service router to capture errors and return a properly formatted response. Server responses are assumed to be in JSON.
Sapphire has a Service class that can be used to protect against cross site request forgery. This service class restricts service functions to be one level deep, but is typically good enough.
Here is an example services using the Services class, this would be called by posting to the url: /sample/services/user/login
var Service = require('sapphire-express').Service;
var User = require('../models/User');
var Q = require('q');
UserService = new Class({
Implements : [Service],
initialize : function()
{
this.export('login', module);
},
verify : function(req, res)
{
return true;
},
login : function(req, res)
{
var user = new User();
var session = req.session.get();
var email = req.body.email;
var password = req.body.password;
return user.login(email, password)
.then(function(user)
{
if (user === false)
return {success: false, message : 'invalid login'};
else
{
session.user = user.user;
return req.session.save()
.then(function()
{
return {success: true, result: user.getIdentity(), identity: user.getIdentity()};
}.bind(this));
}
}.bind(this));
}
});
new UserService();
Standard Responses
Sapphire applications are written with a standard response format in mind. Responses are in JSON, with the top level items being:
Repsonse | |
---|---|
success | Will be true or false if the function succeeded. Results that return an empty set should be considered successful. Only problems with the request like missing data, or database errors, should be considered failures |
result | This is the result of the service call. It can be any data type |
In addition to these two standard members, others can be added to the top level of the response. These can be intercepted in the client to look for global level changes, like the user identity changing.
Directory Organization
The directory structure for sapphire is organized like this:
Each application also has a directory structure:
- assets/ static assets for your application:
- js
- css
- images
- **/ sub application directories
- features/ features go here
- pages/ page features go here
- dialogs/ dialog features go here
- services/ services go here
- node_modules/ needed packages go here
- templates/ html template files
The directory structure for features and dialogs are similar to applications:
- assets/ static assets for your application
- templates/ html template files