Conventions Client API Server API Architecture Getting Started

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:


Service Routing

Service URLs take the following form:

<app>/services/<service>/[...objects]/<method>
Parts
appThe name of the application that implemented the service
serviceThe name of the service being called
objectsA nested list of objects, for example, building/resources
methodThe 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
faviconThe metadata tag to specify the favicon goes here
titleThe title tag goes here
metadataAll the specified metadata tags go here
masterCSSThe list of added CSS files go here
headerJSOnce in a while it is necessary to specify a JavaScript file in the html header. These files will go here.
statesStates are CSS classes that define the initial state of the application
bodyThe body is the HTML that specifies the overall chrome for the application. This is stuff outside of the pages. It goes here.
masterJSAll the specified JavaScript files go here
javaScriptThe 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
nsNamespace for the application variables
reqthe 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
stateName 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
nameName of the variable
valueValue 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
nameName of the variable
valueValue 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
hrefThe link.
typeThe 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
nameName of the url
valueActual 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
filePath 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
filePath 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
filePath 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
nameName of the replacement
filePath 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
nameName of the replacement
valueReplacement string

addJS

Call this method to add JavaScript files to the application.

addJS : function(files, raw)
Parameters
filesArray of JavaScript files these should be relative to either the apps root, or /public for global files.
rawSet 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
filesArray 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
filesThe 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
filesThe 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
specSpecification for the page

The spec is an object with a number of fields specifying the details for this page.

Fields
nameName of the page as it will be referenced in the client
urlPath to the page template file, an HTML file
javascriptArray of JavaScript files that will be loaded the first time a page is shown
cssArray of css files that will be loaded the first time a page is shown
dontPruneSet 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
cloneSet 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
specSpecification for the page

The spec is an object with a number of fields specifying the details for this dialog.

Fields
nameName of the dialog as it will be referenced in the client
urlPath to the dialog template file, an HTML file
javascriptArray of JavaScript files that will be loaded the first time a dialog is shown
cssArray of css files that will be loaded the first time a dialog is shown
cloneSet 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
setNameName of a panel set where this panel will be used, must be a valid JavaScript identifier
specSpecification for the page

The spec is an object with a number of fields specifying the details for this panel.

Fields
nameName of the panel as it will be referenced in the client
urlPath to the panel template file, an HTML file
javascriptArray of JavaScript files that will be loaded the first time a panel is shown
cssArray 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
titleTitle for the given page

addMetadata

Call this method to add metadata tags to the output HTML.

addMetadata : function(name, content)
Parameters
namename of the metadata
contentcontent of the metadata

setFavicon

Call this method to set a favicon metadata tag to the HTML.

setFavicon : function(url)
Parameters
urlurl 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
appApplication object that this feature is a subset of
pathPath 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
successWill 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
resultThis 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:

The directory structure for features and dialogs are similar to applications: