Accessing Payload Data
There are a few different methods for accessing and manipulating data from workflow payloads and device data queries. Depending on the use case, you may run across a point while building your app where you’re asked to supply a data reference in one of the following formats.
{
"data" : {
"aNumberProp": 42,
"aStringProp": "Hello world",
"aBooleanProp": true,
"aFalsyProp" : 0,
"aDecimal": 123.456789,
"anUndefinedProp": undefined,
"aNullProp": null,
"aJavascriptDateObject": Wed Sep 28 2016 15:40:25 GMT-0400 (EDT),
"anArray": [
44,
"Goodbye world",
false,
{
"anObjectPropInAnArray": "I'm deep!"
}
],
"anObject": {
"aNestedArray": [
"World Star!",
20,
{ }
],
"aNestedNumber": 56,
"aNestedString": "Wally World"
},
"anHtmlString": "<span>Wall-E</span>"
}
}
Payload Paths
Many workflow nodes, such as the HTTP Node, the Get and Store Value nodes and the Math Node, require users to define a payload path at which either data will be accessed for evaluation, or at which the returned result of an operation will be stored. In these cases we use payload paths.
Payload paths are dot-separated references to object properties, such as foo.bar
. If any property in the chain returns undefined
, the value of the payload path will also return undefined.
Square brackets wrapped around a property name can be used in a handful of cases:
- Accessing an array value by index. e.g.
data.anArray.[0]
returns the first value in the array at that path. - Accessing a specific character of a string by index. e.g.
data.aString.[1]
returns the second letter of the string at that path. - Escaping payload properties that have spaces or other special characters. e.g.
data.[a silly property name]
.
Examples
Given our example object, here is how we would reference many of the properties using payload paths:
data.aNumberProp // 42
data.aBooleanProp // false
data.aNullProp // null
data.aJavascriptDateObject // Wed Sep 28 2016 15:40:25 GMT-0400 (EDT)
. // (returns the entire example object)
data.anArray.[1] // "Goodbye world"
data.anArray.1 // (invalid syntax)
data.anArray.[1].[2] // o (The character at index 2 of the array item at index 1)
data.anArray.[3] // { "anObjectPropInAnArray": "I'm deep!" }
data.anArray.[3].anObjectPropInAnArray // "I'm deep!"
data.anArray.[8] // (undefined. This index does not exist on the array.)
data.anObject.aNestedNumber // 56
data.anObject.aNestedArray // ["World Star!", 20, { }]
data.anObject.aNestedArray.[1] // 20
data.aNonexistentObject.aMissingProp // (undefined. "aNonExistentObject" is not a property of "data")
aNumberProp // (undefined. This is not a top-level property of our object.)
Payload paths do not accept variables within the path definition; the paths must be static references.
String Templates
String templates are Handlebars templates – that is, they are constructed using a payload path that is wrapped in double curly brackets, such as {{foo.bar}}
. The value at the specified path should resolve to a printable property; specifically, the value should be of the type “string”, “number”, “boolean” or “undefined”. Objects referenced in this way will return [object Object]
, and arrays will return as comma-concatenated strings of all array values, such as foo,bar,42,false
.
Areas within the app that call for a string template do not necessarily have to include a variable; it is perfectly acceptable to put a static string value in place of a template.
String templates appear in a number of use places within the WEGnology Platform; in all cases, they serve one of two purposes:
- Referencing payload values, such as for setting state in the Device State Node.
- Formatting values for output, such as in the GPS History block’s tooltip or the SMS Node’s message.
Escaping HTML
By default, HTML within a Handlebars helper is escaped to prevent malformed HTML from breaking the page layout. If you wish to override this feature, put your string templates inside of triple curly brackets, e.g. {{{aValueWithHtmlTags}}}
. You may, for example, wish to include HTML tags in the message body of the Email Node.
Conditional Block Helpers
It is possible to wrap string templates in a block helper, such as {{#if foo}}{{foo}}{{else}}Not set!{{/if}}
. Values in block helpers can themselves be wrapped in subexpressions, such as {{#eq (upper foo) 'BAR'}}They're equal!{{/eq}}
.
In addition to the built-in Handlebars helpers, WEGnology provides a number of other helpers for use in string templates. If you have an idea for a new helper, please let us know about it in our forums.
{{#eq p1 p2}}
: Returns true ifp1
andp2
are equal by shallow comparison.{{#ne p1 p2}}
: Returns true ifp1
andp2
are not equal.{{#gt p1 p2}}
: Returns true ifp1
is greater thanp2
.{{#gte p1 p2}}
: Returns true ifp1
is greater than or equal top2
.{{#lt p1 p2}}
: Returns true ifp1
is less thanp2
.{{#lte p1 p2}}
: Returns true ifp1
is less than or equal top2
.{{#match str regExpStr}}
: Returns true ifstr
matches the regular expressionregExpStr
.{{#includes collection value}}
: Returns true ifvalue
is in the arraycollection
.
Format Helpers
Handlebars helpers can also mutate a given value in place and print the result. WEGnology provides the following formatting helpers:
{{add val1 val2}}
: Casts the two values as numbers and adds them.{{currentDateTime formatStr}}
: Returns the current date/time formatted by the provided format string. The format string follows Moment.js format syntax, and the default is ‘L LTS’.{{divide val1 val2}}
: Casts the two values as numbers and divides them.{{dashboardUrl id queryParamKey1='queryParamValue1' ...}}
: Creates a link to the dashboard at the given ID (defaults to the current dashboard) and adds the optional query parameters. To pass a nested object of parameters, such as for dashboard context, include the parameter asctx=(obj key1=valueFromField key2="staticValue")
.{{decodeBase64 val}}
: Treatsval
as Base64-encoded and returns a decoded string (UTF-8 character set).{{decodeURI str}}
: Returnsstr
as a decoded URI.{{decodeURIComponent str}}
: Returnsstr
as a decoded URI component.{{defaultTo val default}}
: Returns the value ofdefault
if the value ofval
isnull
,undefined
, or an empty string. Otherwise returns the value ofval
.{{encodeBase64 val}}
: Treatsval
as a string, and returns the Base64-encoded version of that string (UTF-8 character set).{{encodeURI str}}
: Returnsstr
as an encoded URI.{{encodeURIComponent str}}
: Returnsstr
as an encoded URI component.{{evalExpression str ctx}}
: Evaluatesstr
as an expression, usingctx
as the context for variable lookups. Ifctx
is not provided, the overall payload is used. Cannot be used recursively, or combined withtemplate
.-
{{format val formatStr}}
:- If
val
is a number, returns the number in the D3 format matching theformatStr
parameter (default ‘,.6’). When used in a dashboard, this will use the current browser locale when formatting. - If
val
is a JavaScript Date object, returns the date in the Moment.js format matching theformatStr
parameter (default ‘L LTS’). When used in a Dashboard, this will use the current browser locale when formatting. - If
val
is an object, returns the stringified object and ignores theformatStr
parameter. - For all other formats,
val
is returned as a string without mutation.
- If
{{formatDate val formatStr}}
: Casts the given value as a date, and then formats it using theformatStr
parameter (default ‘L LTS’). When used in a dashboard, this will use the current browser locale when formatting.{{formatDateRelative date relativeTo}}
: Outputs a relative date value (such as “5 minutes ago”). Date is calculated relative to therelativeTo
parameter, which defaults to the current time when not set. When used in a dashboard, this will use the current browser locale when formatting.{{formatGps gpsStr formatStr precision}}
: Takes the given GPS coordinate, in any of the supported formats, and reformats to the desired format.formatStr
can bedecimal
(the default),degrees
,sexagesimal
, orgga
.precision
will round the coordinates to the given number of decimal places (default 6).{{gpsDistance gpsStr1 gpsStr2}}
: Takes two GPS points and returns the distance between them (in meters).{{join array separator}}
: Joins the values in the given array as a single string, separated by the given separator. If no separator is given, defaults to,
.{{jsonEncode val spacerStr}}
: Returnsval
stringified. If no spacer string is given (for adding whitespace for readability to the output), defaults to adding no whitespace.-
{{length val}}
:- If
val
is an array, returns the number of items in the array. - If
val
is an object, returns the number of keys on the object. - If
val
is a string, returns the length of the string. - For everything else, returns
undefined
.
- If
{{lower str}}
: Returnsstr
converted to all lowercase characters.{{max val1 val2}}
: Returns the larger of the two given values.{{min val1 val2}}
: Returns the smaller of the two given values.{{multiply val1 val2}}
: Casts the two values as numbers and multiplies them.{{queryStringEncode object}}
: Takes the given object and converts it to a URL query string.{{scaleLinear fromLow fromHigh toLow toHigh value}}
: Scales the givenvalue
from the domain defined byfromLow
andfromHigh
to the range defined bytoLow
andtoHigh
. Similar to the Arduino Map function.{{subtract val1 val2}}
: Casts the two values as numbers and subtracts them.{{template str ctx}}
: Evaluatesstr
as string template, usingctx
as the context for variable lookups. Ifctx
is not provided, the overall payload is used. Cannot be used recursively, or combined withevalExpression
.{{toHtml object}}
: Takes the given object and converts it to an HTML/XML document string. The object must be of the same format that the HTML Parser Node outputs in JSON mode.{{trim str}}
: Returnsstr
with all whitespace trimmed from the start and end.-
{{typeof val}}
: Returns the following string based on the type ofval
:- If
val
is a number, returnsnumber
. - If
val
is a boolean, returnsboolean
. - If
val
is a string, returnsstring
. - If
val
is null, returnsnull
. - If
val
is undefined, returnsundefined
. - If
val
is an array, returnsarray
. - If
val
is a date object, returnsdate
. - If
val
is an object, returnsobject
.
- If
{{upper str}}
: Returnsstr
converted to all uppercase characters.{{valueByKey objArray key keyField valueField}}
: Returns the value ofvalueField
on the object in theobjArray
whose value forkeyField
matches the value ofkey
. The argumentskeyField
andvalueField
are optional, defaulting to the field nameskey
andvalue
. If there is no match found, a blank result is returned.
Iterating Over Arrays and Objects
One very useful feature of string templates is the ability to iterate over arrays and objects using the {{#each}}
block helper. When doing so, a few variables become available to the user in the context of the loop:
- For arrays,
{{@index}}
returns a number corresponding to the index of the current item in the loop. One possible use of this value is to know how many times the loop has executed, and to stop executing additional code within the loop based on this value. - For objects,
{{@key}}
returns the name of the current key. You can use this to execute a different string template based on the current item in the loop. - For both arrays and objects,
{{this}}
represents the current item in the loop; or, put another way, the value at{{anArray.[@index]}}
or{{anObject.[@key]}}
.
String Template Examples
Using our example object from above, let’s see what each of these string templates would print …
<span>My New Post</span>
=> <span>Wall-E</span>
=> 44,Goodbye world,false,[object Object]
(all items in the array concatenated by commas)
=> G
(the character at index 0 of the item at index 1 of the array)
=> (invalid syntax)
(0 is not a property of the array; should be [0] to access by index)
=> (prints nothing)
(index does not exist on the array.)
The number is
=> The number is 42
The number is
=> (prints nothing. The condition fails.)
Truth! Fiction!
=> Fiction! (0 is falsy so the condition falls to the else statement.)
: ;
=> 0: 44; 1: Goodbye world; 2: false; 3: [object Object];
: ;
=> aNestedArray: World Star!,20,[object Object]; aNestedNumber: 56; aNestedString: Wally World;
: -- No object!
=> No object!
First number is greater
=> First number is greater
Second number is greater
=> (prints nothing)
Equal! Not equal
=> Not equal (cases don't match)
Equal! Not equal
=> Equal! (cases match after running through the "lower" subexpression)
=> (the stringified object)
=> 1,234.6
=> Hello%20world
Ba de ya! Play something else
=> Ba de ya!
=> 42
=> Hello world
=> true
=> (prints nothing)
=> (prints nothing)
=> Goodbye world
=> [object Object]
=>
Expressions
Sometimes it is necessary to evaluate our data and determine which of two (or many) paths to follow as a result of the evaluation, such as in the dashboard Indicator block’s expressions and the workflow Conditional Node. In these cases we use expressions.
An expression is a combination of string templates and JavaScript operators that forms a “JavaScript-like” expression after parsing through the Handlebars engine. The expression returns a value; depending on the use case, that value may be evaluated as truthy or falsy to determine which branch a workflow follows (as in the Conditional Node), or the value may be added to a payload (as in the Math Node).
Any operators or functions applied to a string template within an expression should be placed outside of the string template; for example, {{foo}} > {{bar}}
or !{{bat}}
.
String templates used within expressions may not use block helpers; they may, however, use format helpers.
Note that, when doing string comparisons in expressions, it is not necessary to wrap any string template in quotes to “stringify” the output. The expression will be evaluated with the output of the string template serving as a variable; for example {{foo}} === 'bar'
or {{bat}} !== {{baz}}
.
Below are lists of operators, keywords and functions supported in expressions. If you have use cases that are not handled by these, please let us know in our forums.
Supported Operators
Expressions support the following operators:
+
(addition)-
(subtraction)*
(multiplication)/
(division)%
(modulus)^
(exponentiation)==
(loose equality)===
(strict equality)!=
(loose inequality)!==
(strict inequality)>
(greater than)<
(less than)>=
(greater than or equal)<=
(less than or equal)&&
(boolean ‘and’)||
(boolean ‘or’)!
(boolean ‘not’)
Supported Keywords
Expressions have support for strings and numbers, as well as the following special keywords:
true
(boolean true)false
(boolean false)null
(JSON null)undefined
(JSON undefined)E
(e, approximately 2.718)PI
(π, approximately 3.14159)
Supported Functions
Expressions also support the following functions:
sin
(computes the sine of a number)cos
(computes the cosine of a number)tan
(computes the tangent of a number)asin
(computes the arcsine of a number)acos
(computes the arccosine of a number)atan
(computes the arctangent of a number)sinh
(computes the hyperbolic sine of a number)cosh
(computes the hyperbolic cosine of a number)tanh
(computes the hyperbolic tangent of a number)asinh
(computes the hyperbolic arcsine of a number)acosh
(computes the hyperbolic arccosine of a number)atanh
(computes the hyperbolic arctangent of a number)atan2
(computes the arctangent of the two variables y and x)sqrt
(computes the square root of a number)log
(computes the natural logarithm of a number)log10
(computes the base 10 logarithm of a number)abs
(computes the absolute value of a number)ceil
(computes the smallest integer greater than or equal to a number)floor
(computes the largest integer less than or equal to a number)round
(rounds a number to the nearest integer)trunc
(truncates any fractional digits of a number)exp
(computes e raised to the power of the given number)pow
(raises a number to the power of a second number)length
(computes the length of an array, object, or string)max
(computes the maximum of the two arguments)min
(computes the minimim of the two arguments)
Expression Examples
With the provided functions, keywords and operators, it is possible to build very complex expressions, such as …
sin({{ data.distance }}) >= 0.25 * PI && ({{ data.open }} !== undefined || !{{ data.closed }})
Given our example object, let’s see how these simpler expressions evaluate.
{{data.aBooleanProp}} // true
{{data.aBooleanProp}} === false // false
{{data.aNumberProp}} === 'hello' // false
{{data.aFalsyProp}} == false // true
{{data.aFalsyProp}} === false // false
!!{{data.anUndefinedProp}} // false
{{data.aNumberProp}} > 20 // true
{{data.anObject.aNestedNumber}} - {{data.aNumberProp}} // 14
{{data}} && {{data.aBooleanProp}} // true
{{data.aBooleanProp}} || {{data.aFalsyProp}} // true
{{data.aBooleanProp}} && {{data.aFalsyProp}} // false
{{data.aNumberProp}} > {{data.anObject.aNestedArray.[1]}} // true
{{data.aNumberProp}} > {{data.aStringProp}} // false
{{data.aNumberProp}} < {{data.aStringProp}} // false
{{format data.aJavascriptDateObject 'MMMM'}} === 'September' // true
{{lower data.aStringProp}} === 'Hello world' // false
{{data.anObject.aNestedArray.[1]}} / 5 <= PI // false
pow({{data.aNumberProp}}, {{data.aFalsyProp}}) === 1 // true
JSON Templates
In a few places within the WEGnology Platform – such as the Input Controls block and WEGnology API Node, it is necessary to construct JSON objects from other data. In these instances we use JSON templates.
JSON templates can take any of the following formats (or a combination thereof):
- Valid, static JSON. e.g.
{"foo": "bar"}
,[1,2,3]
ornull
- JSON containing block helpers around keys, values or both. e.g.
{ {{#if foo}}"bar": "bat"{{else}}"baz": "bub"{{/if}} }
- JSON containing string helpers for keys or values. e.g.
{"month": "{{format date 'MMMM'}}" }
- Standard handlebars payload references, e.g.
{{data.nestedObject.nestedArray.[1]}}
Your entire input will run through Handlebars and the WEGnology-provided helpers. After evaluation, the result must be valid JSON. Because of this, there are a few things to keep in mind when using string templates within your JSON templates:
- Any string value being output by a string template should be wrapped in double quotes, like so:
{ "foo": "{{data.aStringProp}}" }
. - Any boolean or number value being output by a string template should not be wrapped in quotes, unless you want the value passed as a string:
{ "foo": {{data.aNumberProp}} }
. - Any objects or arrays being referenced by a string helper should always use the
jsonEncode
Handlebars helper to ensure that all string values are wrapped in quotes and all objects print as valid JSON, and should be wrapped in triple curly braces to prevent escaping of characters:{ "anObject": {{{jsonEncode data.anObjectProp}}} }
or{ "anArray": {{{jsonEncode data.anArrayProp}}} }
.
JSON Template Examples
Again, given our example object above, these JSON templates will evaluate as follows …
{ "foo": "bar", "staticNumber" : 66 }
// { "foo": "bar", "staticNumber" : 66 }
{ "myArray":
}
// { "myArray": [44, "Goodbye world", false, {"anObjectPropInAnArray": "I'm deep!"} }
{ "timestamp": , "dayOfWeek": " ", "capitalString": " " }
// { "timestamp": 1475091625000, "dayOfWeek": "Wednesday", "capitalString": "HELLO WORLD"}
{ "myString": }
// FAILS since the string is not wrapped in double quotes
// [44, "Goodbye world", false, {"anObjectPropInAnArray": "I'm deep!"}
// 20
// FAILS because the referenced value is a string, which is not valid JSON
{ "highNumber" "lowNumber" : }
// { "highNumber": 42 }