Data Sources

Dig deeper into data mining for your render layer, and learn about handling forms.

Datasources, as explored in the Builder's Guide, are HBS templated JSON files which articulate an adapter and its configuration. They have access to the same Render Context as the element. Datasources can be referenced by multiple elements.

All Datasources have a skeleton like so:

{
    "type"          :   "",
    "contextName"   :   "",
    "configuration" : { },
    "outputProcessors" : []
}  
Variable Description
type Type of adapter
contextName name of the container object exposed to the element layer, e.g. 'ArticleCollection'
configuration Adapter specific configuration
outputProcessors Array of bundlePaths to Serverside scripts functioning as data filters which are executed in a chain

Datasource Adapter Types

XprNullAdapter

{
    "type"          :   "XprNullAdapter",
    "contextName"   :   "EmptyObject",
    "configuration" : { },
    "outputProcessors" : ["Scripts/GenerateContent"]
}  

XprNullAdapters provide no data of their own. This adapter is useful as a binding point when you want to return all of your data from a Serverside script. In the above example, the contents of EmptyObject will be the results of function process(input) inside of /Scripts/GenerateContent

XprSimpleJsonAdapter

{
    "type"          :   "XprSimpleJsonAdapter",
    "contextName"   :   "JsonObject",
    "configuration" : { 
        "json" : {
            "String"  : "Hello",
            "Integer" : 42,
            "Array" : [],
            "SubObject" : {}
        }
    },
    "outputProcessors" : []
}
configuration option Description
json The json object to attach to contextName

XprSimpleJsonAdapters are containers for static JSON data. This adapter is effective for storing data you want to reference in multiple elements, but edit in one place.

XprApiAdapter

{
    "type"          :   "XprApiAdapter",
    "contextName"   :   "UserSearch",
    "configuration" : { 
        "uri" : "/users/",
        "params" : {
            "Id__eq" : "{{xpr.request.user.Id}}",
            "with" : "UserGroup",
        }
    },
    "outputProcessors" : []
}
configuration option Description
uri The XPR API uri to request
params Map of query params
collectionFormat defaults to "array" which performs embed unrolling. When "hal", will return a full HAL object including the total (unpaginated) collection count with the collection nested inside of {{contextName}}._embedded.{{ObjectType}}

XprApiAdapters are used to pull data from the XPR API.

XprWebAdapter

{
    "type"          :   "XprWebAdapter",
    "contextName"   :   "ProxySearch",
    "configuration" : { 
        "uri" : "external_webservice.com/api/umbrellas/",
        "params" : {

            "AccessToken" : "{{CurrentUserData._embedded.CustomFields.AccessToken}}",
            "UmbrellaSearch" : "{{xpr.request.urlParams.q}}"
        },
        "cache" : {
            "enabled" : false
        }
        "responseType" : "json",
    },
    "outputProcessors" : []
}  
configuration option Description
uri The www URI to request
params HTTP query params for the request
responseType "json" or "html"
{cache} optional caching configuration
cache.enabled true or false
cache.ttl ttl in seconds for cached response

XprWebAdapters are used to fetch data via HTTP GET from other services. They are useful for services such as public search APIs and feed services.

If more control is required over the web request, you may want to use an XprNullAdapter instead and handle the web requests via XprWeb inside of a Serverside Script (outputProcessor).

XprPostHandler

{
    "type"          : "XprPostHandler",  
    "contextName"   : "LoginForm",
    "configuration" : {
        "postHandler" : "userManagement/LoginHandler"
    }
}    
configuration option Description
postHandler A bundlePath to the postHandler to bind

XprPostHandlers are adapters which connect to a Post Handler object to supply the necessary render context to a View Template for constructing a form or AJAX endpoint which can handle an HTTP POST request and route to a script to handle the incoming payload.

context variable Description
fields an array of input field definitions
fields[*].name field name
fields[*].required whether the field has been marked as required
fields[*].type the data type accepted by the field
fields[*].options the optional inline option object in the Post Handler definition for this field
fields[*].error (On POST response) contains any validation or deserialization errors for a specific field
elementAjaxURL convenience URL field for POSTing against
postbackInput convenience hidden input which is required for the route to accept the type of post described by the Post Handler. If not posting via an HTML form, the formdata's __XPR_PostbackAction__ must be supplied and set to the same string as configuration.postHandler
result Data returned by the script identified by the Post Handler

Sample Usage:

{{#with LoginForm}}  

{{#if result}}
The result of your last post was: {{XprJson .}}
{{/if}}  

<form method="POST" action="{{{elementAjaxURL}}}">
    {{#each fields}}    
    {{name}}:
    <input name="{{name}}" type="{{type}}"/>  

    {{#if error}}  
         This field did not validate on your last POST due to: {{.}}
    {{/if}}  

    {{/each}}
    {{{postbackInput}}}
</form>
{{/with}}

Post Handlers
=============

A Post Handler is a JSON object which describes a structured HTTP post with validation and deserialization rules. A Post Handler also defines a script which, when the POST validates, handles the incoming data.

When bound to an XprPostHandler Datasource, the Post Handler definition drives the tags available in the render context, which can optionally be used to render a form. Validation errors, if present, or the response from the Post Handler's script are also imported into the render context for use in marking up either success or error states.

Note that the contents of each field definition gets serialized to the render context. This means that you can decorate each field with additional properties as you need to provide additional context for rendering markup if need be.

option Description
fields array of field definitions
script bundlePath of script to execute on success
trackSubmissions true or false, save submitted raw POST data to a log

Example :

{ 

    "fields" : [
        {
            "name" : "Name",
            "type" : "text",
            "required" : true,
            "validators" : [
                {
                    "type" : "length",
                    "options" : {
                        "min" : 3
                    }
                }
            ],
        },
        {
            "name" : "Email",
            "type" : "text",
            "required" : true,
            "validators" : [
                {
                    "type" : "email"

                }
            ]
        },
        {
            "name" : "MessageBody",
            "type" : "text",
            "required" : true
        },
    ],
    "script" : "UserManagement/ContactForm"
}

Field Definitions

option Description
name name of the POST field
type the type of field
required true or false
validators array of validator definitions

Field Types

Generally, any HTML5 input type is an acceptable value for type. There is one additional type, json which is useful when you want to communicate complex objects from client to server; for example, serializing the state of an object from an SPA which you would like to persist.

key Description
json JSON data, converted into a javascript object before reaching

Field Validators

The validators array on each field is used to add validation on input, which will cause the POST to be rejected. Some validators have additional options which are defined by the options object inline with the validator definition.

The following validators are available:

"type" : "length"

The length validator ensures input is within a specific range. Both min and max are optional, but one must be provided.

option key Description
min minimum length (in characters)
max maximum length

"type" : "email"

The input must be a validly formatted RFC 822 email address

"type" : "url"

The input must be a validly formatted URL

"type" : "integer"

The input must be a whole number

"type" : "float"

The input must be a valid floating point number

"type" : "regexp"

The input must match a specified regular expression

option key Description
regexp The regular expression to validate input against

Serverside Scripts
===

Much of the power of Expression comes from its scripting capabilities. Scripts are written in Javascript and have access to the XPR Objects Javascript API. They have the following entry points:

Entry Point Description
outputProcessors[] array of processors linked in a Datasource, triggered on Element execution
Post Handler Respond to POSTs against Elements
API Hooks Respond to API events
API Extension Module Requests API routes extended by ObjectViews with an Extension Module

Output Processors


An outputProcessor is a script with a function named process which takes a single argument containing data from the parent context. They are attached to Datasources and can be chained.

The value returned by process is fed into the next outputProcessor in the chain defined on the Datasource.

If the outputProcessor is the last in its chain, the data in the object identified by the contextName name variable on the Datasource config becomes the return value from process

Example :

function process(inputArticles) {  
    for(var key in inputArticles) {
        var article = inputArticles[key];
        article.Title = article.Title.toUpperCase();
    }
    return inputArticles;
}

Post Handlers

When an XprPostHandler is bound to an element, and that element is attached to a URL which receives an HTTP POST, the input is first processed by the Post Handler layer which validates the input and converts the formdata into a javascript object. The Post Handler configuration defines a script property which will be executed if the input data passes validation.

The data returned by process is stored in the result variable of the render context returned by the linked XprPostHandler Datasource.

Example :

var api = new XprApi();

function getDefaultUserGroup() {
    return { Id: 1 };
}

function usernameAvailable(username) {

   var users = api.call({ method:"GET", uri:"/users/", params: "Username__eq" : username});
   if(users.length>0)
        return false;
   return true;
}

function process(signupForm) {

    if(!usernameAvailable(signupForm.Username)) {
        return {
                user: null,
                message: "That Username is not available"
        };
    }

    else {
        var newUser = api.call({ 
            method: "POST", 
            uri:"/users/", 
            params:{}, 
            data:{
                Username: signupForm.Username,
                Password: signupForm.Password,
                _embedded: {
                    UserGroup :  getDefaultUserGroup()
                }
            }
         });

        return {
            user: newUser,
            message: "Succesfully created User Id:" + newUser.Id
        };
    }
} 

API Hooks


An API Hook is typically configured in the Global Settings Store in the XPR Backend. A hook definition consists of an array of bundlePaths which will execute in sequence when a particular API event occurs.

The hook receives as context the first level properties of the effected object.

The interface implemented in the script pointed to by the bundlePath is dependent on the particular hook:

hook description interface
preSave called before the object is saved function process(object)
postAdd called when a new object is created function process(object)
postEdit called when an object is changed function process(objectPrior, objectPost)
preDelete called before an object is deleted function process(object)

The argument names are arbitrary, typically for clarity you will want to name the argument after the object the hook affects, e.g. function process(Article) { ... }.

Modules


While Output Processors, API Hooks and Post Handlers can define functions other than process in their scripts, it is often desirable to split code out into Modules, especially if you want to share functionality accross multiple scripts.

Module scripts are simply javascript which assigns the module to the module.exports variable.

Example :

var MathModule = {
    add: (x,y) => { return x+y; },
    sub: (x,y) => { return x-y; },
    mul: (x,y) => { return x*y; },
    div: (x,y) => { return x/y; }
};

module.exports = MathModule;

Modules are included via the require function which takes a bundlePath and returns the module contents.

Example :

var MathModule = require("MyModules/MathModule");

MathModule.add(1,1);

Builtin Libraries


Expression includes by default underscore.js and moment.js

underscore.js

Underscore is a library which provides a wide range of useful functional programming constructs for use in your scripts.

moment.js

Moment is a library which is used to transform and format dates.

XPR Objects


XPR Objects are the standard library of Expression, providing facility beyond pure javascript and interaction with Expression's state.

XprApi

XprApi is used to execute synchronous requests against Expression's REST API. The terminology is consistent with an HTTP request, the request to the API happens internally without ever going out across the internet.

method description
call(options) execute an API call specified by options

Example :

var api = new XprApi();

var matchingArticles = api.call({
    method: "GET",
    uri: "/articles/",
    params: {  
        Title__like : "Umbrella%"
    },  
    data: {  
    },
    parseHAL: true
});  
option key description
method HTTP method to emulate (GET/POST etc.)
uri endpoint to request
params map of query params to values
data data for payload on POST/PATCH/PUT
parseHAL when true (default), convert HAL collections to arrays. When false, include full HAL response including metadata like Type and Total

API Errors

If the API is unable to handle the request, an ApiError will be thrown, with the following properties:

property description
status Emulated HTTP status code, e.g. 403
data metadata about the error
class a string identifying the category of error, i.e. ApiNotFoundError or ApiPermissionsError
type a subtype of the class of error, i.e. PermissionsReadForbidden
resource the specific resource to submit the error. Useful if the error occurs within an _embed as opposed to the resource directly referenced by uri

In some cases, the error may not have all of the above context necessary for reporting back, in which case the error will have a single data property with whatever information the system has collected about the error.

HTTP Requests API

The Requests library allows you to issue a synchronous HTTP request from your script.

Example :

function process(x) {

    var requests = require("XprObjects/Requests");
    var req = requests({
        "uri" : "http://vinceplayland2.xpr.ca/test.php",        
        "method" : "PUT",
        "data" : JSON.stringify( { "hello" : "world"}),
        "options" : {
            //"timeout" : [seconds]
            //"connect_timeout" :[seconds]
            //"useragent" : [string]
            //"follow_redirects : true,
            //"auth" : [ 'user', 'pass' ],
            //"proxy" : ???,
            //"max_bytes" : ???,
            //"data_format" : 'query' | 'body'  
        }
    });

    return req;

    //req.body (string)
    //req.raw  (string)
    //req.headers (array)
    //req.status_code (string)
    //req.protocol_version (string i.e. 1.0,1.1)
    //req.success (bool)
    //req.redirects (array)
    //req.url (string)
    //req.cookies (array)
}

XprDatasource

XprDatasource is available when the script was invoked as an outputProcessor. It contains the Datasource definition, post-variable replacement.

Example :

var callingDatasource = new XprDatasource();

function process(inputData) {  
    inputData.decorations = [
        "We are populating" : callingDatasource.contextName,
        "The adapter type was" : callingDatasource.adapter,
        "The configuration is" : JSON.stringify( callingDatasource.configuration ),
        "The list of bound output processors is" : JSON.stringify( callingDatasource.outputProcessors )
    ];
    return inputData;
}

XprRequest

XprRequest is the equivalent of the xpr.request object in the global render context.

Example :

var request = new XprRequest();  

function process(inputData) {  
    inputData.decorations = [
        "Logged in user is " + request.user.Username
    ];
    return inputData;
}  
request variable description
user authenticated frontend user API data
domain The current domain
section The current section API data, if routed as part of a page render and the router was able to resolved the landed section
article The current article, resolved the same way as section
urlParams map of HTTP query params to their specified values
debug global true or false value indicating if the "debug" option was selected for this XPR session
language active language for the request
client data about the remote HTTP client
store data about the ecommerce platform

XprBundle

XprBundle contains metadata about the bundle which contains the current executing script. This is primarily useful for getting the bundle's JSON configuration data which is used to contain shared variables between the element stack and the script.

Example :

var bundle = new XprBundle();

function process(inputData) {  
    inputData.decorations = [
        "This script lives inside of " + bundle.name,
        "The configuration data is" + JSON.stringify(bundle.config),
    ];
    return inputData;
} 

XprSettings

XprSettings is used to retrieve values from the Global Settings Store which have been enabled with the Export To Code option.

Example :

var settings = new XprSettings();  
var mySettingValue = settings["TestNamespace"]["TestSetting"];

XprHash

XprHash provides an MD5 function.

method description
md5(input) returns the MD5 hash of input

Example :

var hash = new XprHash();

function process(inputData) {  
    inputData.decorations = [
        "The hash of the input data for this Datasource is" : hash.md5(inputData)
    ];
    return inputData;
}  

XprFile

XprFile allows you to read files from the web server, typically this will be something like a text file uploaded via the MediaExplorer.

method description
read(path) reads the file path from the webserver
read_json(path) same as read but decodes JSON data

Example :

var file = new XprFile();

function process(inputData) {  
    var data = 
    inputData.decorations = [
        "The contents of /media/data.json are" : file.read("/media/data.json")
    ];
    return inputData;
} 

XprMail

XprMail allows you to send mail from the webserver.

method description
send(options) Send mail. Returns false on failure

Example :

var mail = new XprMail();

function process(inputData) {
    mail.send({
        to: "billg@microsoft.com",
        from: "sillygoat@crazyfarm.com",
        subject: "Important business idea",
        body: "..."
    });
}  
option key description
to target addresses
from address to send from
cc CC addresses
bcc BCC addresses
subject subject of email
body body of email

XprRedirect

XprRedirect tells the webserver to issue a 302 redirect.

Note that all elements and referenced scripts on the requested URL will execute as normal, i.e. the request to redirect does not halt other processing.

If another script is executed which also requests a redirect, the last requested redirect will be the one issued by the webserver.

method description
setNextLocation(options) requests a 302 redirect be issued

Example :

var redirect = new XprRedirect();

redirect.setNextLocation({
   uri: "www.google.com"
});
option key description
uri location for the 302 redirect

XprConsole

XprConsole is a global variable with one function, log which will emit its arguments to an Xpr Trace session, typically within the Bundle Editor.

Example :

XprConsole.log("Hello XPR Trace!", { hello: "world" });

XprApiExtensionRequest

XprApiExtensionRequest is issued by the API when hitting an ObjectView which has been extended with an API Extension Module.

method description
preventDefault() bypass standard API processing
setResponse(code,data) send data as an API response, with the specified HTTP status code (Supported termination codes are 200,201,400,401,403,404,500)

An XprApiExtensionRequest also has a state, in the form of attached properties:

property description
urlParams HTTP/Emulated HTTP query params
payload decoded JSON payload if present

Example :

getAll: function(request) {
    request.preventDefault();  
    request.setResponse(200,[1,2,3,4,5]);
}

Object Views ===

Note: Object Views are a bleeding edge feature. They have been used in production and are certainly useful but come with a warning: there may be dragons here. That said: please use them if they seem applicable and send us all your bug reports!

Object Views are wrappers for core APIs which can be used to create virtual api endpoints for use in Serverside Scripts and Datasources which supply validation and access semantics, including default filtering and serve as a binding point for API Extension Modules. They take the form of a JSON definition attached to a group of core settings.

This can be used, for example, to create an extension to the /users/ API which enforces a particular user group. The Object Views will show up in the XPR Backend as additional menu items. This makes it possible to create a menu and data explorer for a specific classification of object, e.g. "Customers" instead of the global "Users" view.

Object View Core Configuration


setting description
Resource The type of resource to wrap, selectable via dropdown e.g. Users\UserResource
uri The virtual endpoint to create, this will expand to /objectViews/{uri}/
Classifier Name used as an aid for filtering, typically the plural name of the resource being wrapped, e.g. "Users" or "Events"
FriendlyName The name which will appear in the menu
New Record Group Ownership Presently only in use for File resource, determined the user group to set ownership of for new objects created via POST
Use In Menus determines if the XPR Backend will attempt to consume the Object View when building the left hand administration menu

Object View JSON Configuration


configuration key description
View Qualifiers Definitions of rules which requests to / for collections will return and conditions under which to accept a POST or PATCH
View Qualifiers[*].field Name of field to validate. This must be a first level field of the wrapped API, or related object Id in the form {{Relation}}Id
View Qualifiers[*].rule == or in
View Qualifiers[*].value value to apply the check against. Will 403 reject any POSTS/PATCH which do not validate
ObjectTemplate Template object defintion which defaults
ExtensionModule bundlePath to a Serverside Script Module
ExtensionRoutes A list of additional routes beyond default CRUD your ExtensionModule supports
ExtensionRoutes[*].methods Array of emulated HTTP methods ["POST","GET"], etc a particular route supports
ExtensionRoutes[*].function Name of javascript function which responds to the API route
ExtensionRoutes[*].route The templated URI for the route. This will generate an internal URI for /objectViews/{uri}/{route} where route can feature tokens, e.g. :region which become input arguments for the named function that is the route target

Example 1 : Validation & Templating

{
    "ObjectViewDefinition" : {
        "ViewQualifiers" : [
            {
                "field" : "UserGroupId",
                "rule"  : "==",
                "value" : 4
            }
        ],
        "ObjectTemplate" : {
            "_embedded" : {
                "Parent" : {
                    "Id" : 4
                }
            }
        }
    }
}

Example 2 : Extension Module Binding

{  
    "ObjectViewDefinition" : {
        "ObjectTemplate" : {},
        "ExtensionModule" : "UserManagement/CustomUsers",
        "ExtensionRoutes" : [
            {
                "methods" : [ "GET" ],
                "function" : "generateReportData",
                "route" : "/report/:businessUnit"

            }
         ]
    }  
}  

API Extension Modules ===

An API Extension Module is a Serverside Javascript object which defines a set of functions extending the functionality of an API wrapped in an Object View.

A minimal API extension module requires the following structure:

var CustomUserAPI = {

    getExtensionEmbedData: function(User) {
        return {};
    },

    get: function(e,id) {
    },

    getAll: function(e) {
    },

    edit: function(e, id) {
    },

    add: function(e) {
    },

    delete: function(e,id) {
    },
}

module.exports = CustomUserAPI;

getExtensionEmbedData: function(object)

getExtensionEmbedData takes the API object (e.g. User, Article, etc.) that the Object View is wrapping as context, and allows you to return a javascript object which will included in the _embeds of the API response if hydrated with a query param with including key of ExtensionData

The extension data will be added to _embedded.{{YourInstallName}}

CRUD extensions

Each of the CRUD extension methods, (get/getAll/edit/add/delete) take an e argument, which is an XprApiExtensionRequest object.

The e object can be used to block further API processing, i.e.

get: function(e,id) {
    e.preventDefault(); 
    e.setResponse(200, {"message" : "We blocked API processing to return this custom object" });
}  

Route handlers

If the Object View has defined ExtensionRoutes, those routes are implemented in the same module as the CRUD handlers.

report: function(e, businessUnit) {
    var reportData = this.makeReport( businessUnit );
    e.setResponse(200, reportData );
}