{ result.Title }
{ result.Description }
{ result.Description }
Cannot find anything related to '{ keyword }'. Try a different search or contact us
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 |
{
"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
{
"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.
{
"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.
{
"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).
{
"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"
}
option | Description |
---|---|
name | name of the POST field |
type | the type of field |
required | true or false |
validators | array of validator definitions |
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 |
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;
}
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 is a library which provides a wide range of useful functional programming constructs for use in your scripts.
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 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.
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 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 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 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 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 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 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 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 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 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 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 |
{
"ObjectViewDefinition" : {
"ViewQualifiers" : [
{
"field" : "UserGroupId",
"rule" : "==",
"value" : 4
}
],
"ObjectTemplate" : {
"_embedded" : {
"Parent" : {
"Id" : 4
}
}
}
}
}
{
"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 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}}
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" });
}
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 );
}