11 Understand the Page Model

The page model consists of a JSON file. To work with the page model by hand, you should understand the structure and components of this JSON.

Variables

Variables are the basic blocks of state management. Components and expressions in applications are bound to variables, and the variables and their structure must be defined to ensure that the design time and runtime work properly.

A variable must have a name and a type. Variables are in the variables namespace.

A variable can send an event when it changes. To add an event handler to a value change event, specify it in the 'onValueChanged' property of the variable. For details, see Variable ‘onValueChanged’ Events. See rateLimit Variable Property for information on setting a timeout value for the 'onValueChanged' property.

Object Variables

Variables may also be objects that contain properties.

In this case, the type of the variable should be an object that defines what properties are allowed in that object.

The following variable in JavaScript:

let nameOfVariable = {
  foo: "someString",
  bar: 10
}

could be defined like this:

"nameOfVariable": {
  "type": {
    "foo": "string",
    "bar": "number"
  }
}

Example 11-1 An Object Containing Another Object

This JavaScript object

let otherObject = {
  foo: {
    name: "myName"
  },
  bar: 10
}

can be described by the following structure:

"otherObject": {
  "type": {
    "foo": {
      "name": "string",
    },
    "bar": "number"
  }
}

Array Variables

Variables can represent arrays.

Arrays are defined the same way as objects. However, in this case, the object type is inside an array.

Arrays can have nested objects or arrays as well, and object types can also contain nested arrays.

Example 11-2 An Array Represented by a Variable

A JavaScript array

let myArray = [
  {
    foo: "someString",
    bar: 10
  },
  {
    foo: "someOtherString",
    bar: 11
  }
]

can be represented like this:

"nameOfVariable": {
  "type": [
    {
      "foo": "string",
      "bar": "number"
    }
  ]
}

Example 11-3 An Array of Strings

"nameOfVariable": {
  "type": "string[]"
}

Built-in Variables

There are several built-in variables available.

currentPage

To access some of the current page's metadata, such as ID and title, there is a built-in variable named currentPage on the application object. The currentPage variable automatically updates as the current page changes during navigation. This can be used to update a navigation component with the currently selected page.

Name Description
$application.currentPage.id The path of the current page. The path describes the location of the page in the flow hierarchy.
$application.currentPage.path The path of the current page for the application. The path describes the location of the page in the flow hierarchy.
$application.currentPage.title The title of the current page. The title is formed by prepending all the titles of the shells in the flow hierarchy to the current page.
$flow.currentPage The id of the current page for this flow.

currentFlow

If there is a routerFlow in the page, the $page.currentFlow variable can be used to retrieve the id of this flow.

Name Description
$page.currentFlow The id of the current flow.

deployment

Use the deployment variable to distinguish between web, mobile, and progressive web applications that have been deployed from Visual Builder.

Name Description
$application.deployment.appType = 'web' || 'mobile' Distinguish between web applications and mobile applications that you have deployed. For example, use $application.deployment.appType = 'web' to indicate a web application.
$application.deployment.pwa = 'enabled' || 'disabled'

Distinguish between mobile applications that have been configured as progressive web applications (PWA) and those mobile applications that have not. For example, use $application.deployment.pwa = 'enabled' to indicate that the mobile application is configured as PWA.

Both values ('enabled' || 'disabled') reflect the deployed state of the application, so the behaviour of the mobile application in the Designer varies depending on the value you use.

For a web application, $application.deployment.appType is always set to 'web' and $application.deployment.pwa is always 'disabled', regardless of whether the web application is running in the Designer or deployed.

For a mobile application, the value of $application.deployment depends on whether the application runs inside the Designer, or is installed on a mobile device. For example, $application.deployment.appType will always be set to 'web' in the Designer, since the application is not running on a mobile device. When a mobile application that is not configured to run as a PWA is installed on a mobile device, $application.deployment.appType is 'mobile' and $application.deployment.pwa is set to 'disabled'.

For a PWA, $application.deployment.appType is always set to 'web'. The value of $application.deployment.pwa is set to 'disabled' when the application is in the Visual Builder Designer and $application.deployment.pwa is set to 'enabled' when the application is deployed.

path

The path variable is used to build the path to a resource, such as an image located in a folder in the application or in a flow. 

Name Description
$application.path The path needed to retrieve a resource located in the application folder.
$flow.path The path needed to retrieve a resource in the flow folder.

user

The user variable is used to access information about the current user. It is based on the User Info returned by the Security Provider. It is possible to modify the set of user information by changing the implementation of the Security Provider. See Security.

Name Description
$application.user.userId The user id <string>.
$application.user.fullName The user full name <string>.
$application.user.email The user email <string>.
$application.user.username The user name <string>.
$application.user.roles The user roles (array of strings).
$application.user.roles.roleName Returns true if roleName is a role of this user.
$application.user.permissions User permissions (array of strings).
$application.user.permissions.permName Returns true if permName is a permission of this user.
$application.user.isAuthenticated Returns true if this user is authenticated.

translations

This is not a variable, but an API available for getting localized strings using $<container>.translations.<bundlename>.key, or $container.<translations>.format(<bundlename>,<key>,args...).

This API exists for $application, $flow, and $page, but is only useful if you have defined translation bundles. If translation values are needed in JavaScript function modules, they must be passed as arguments to the function.

responsive

This is not directly a variable, but contains several Knockout Observables that represent JET Media Queries. The following are available, and are accessible via $application.responsive.XXX (for example, $application.responsive.smUp): smUp, mdUp, lgUp, xlUp, smOnly, mdOnly, lgOnly.

info

Some information from the application and page descriptor can be retrieved using the info keyword.

Name Description
$application.info.id The application id defined in app-flow.json
$application.info.description The application description defined in app-flow.json
$flow.info.id The flow id defined in flow-id-flow.json
$flow.info.description The flow description defined in flow-id-flow.json
$page.info.title The page title defined in page-id-page.json
$page.info.description The page description defined in page-id-page.json

components

This is not a variable, but contains utility methods for finding JET components on a page. These methods return an element that is a JET component. If no element is found, or if the element is not part of a JET component, these methods will return null.

Note:

These methods are not for finding general elements To find elements on the page, use methods such as document.getElementById and document.querySelector.
Name Description
$page.components.byId('myCard') (deprecated) Use document.getElementById, which returns a JET Component or null.
$page.components.bySelector('#myCompId') (deprecated) Use document.querySelector, which returns a JET Component or null.

Types

Types define structure in much the same way as variables.

Types can be defined at the application, flow, and page level, and can be referenced by variables.

Types can be defined once at the application level in the application model. This can help you to avoid using the same structure repeatedly in different variables.

Example 11-4 Using Types in the Application Model

types: {
  "myType": {
    "foo": "string",
    "bar": "number"
  }
}

Example 11-5 Referencing Types in a Variable

To reference types in a variable, prefix the type with 'application:', for example:

"nameOfVariable": {
  "type": "application:myType"
}

Page

A page can access a type defined in itself, or the parent flow, or the application.

Definition Result
"nameOfVariable": {
  "type": "myType"
}
Uses the type named myType defined in the page.
"nameOfVariable": {
  "type": "page:myType"
}
Uses the type named myType defined in the page (same as no prefix).
"nameOfVariable": {
  "type": "flow:myType"
}
Uses the type named myType defined in the flow containing this page.
"nameOfVariable": {
  "type": "application:myType"
}
Uses the type named myType defined in the application.

Flow

A flow can access a type defined in itself, or the application.

Definition Result
"nameOfVariable": {
  "type": "myType"
}
Uses the type named myType defined in the flow.
"nameOfVariable": {
  "type": "flow:myType"
}
Uses the type named myType defined in the flow (same as no prefix).
"nameOfVariable": {
  "type": "application:myType"
}
Uses the type named myType defined in the application.

Application

An application can access a type defined in itself.

Definition Result
"nameOfVariable": {
  "type": "myType"
}
Uses the type named myType defined in the application.
"nameOfVariable": {
  "type": "application:myType"
}
Uses the type named myType defined in the application (same as no prefix).

Type References

An existing type can be used inside a type definition.

"types": {
  "region": {
    "facility": {
      "id": "string",
      "name": "string",
      "detail": "string"
    },
  "address": "flow:address",  <-- Use address defined in the parent flow
  "facilities": "facility[]"  <-- Use facility defined above
  }
}

Built-in Extended Types

VB provides a few built-in 'extended' types that extend from some base types provided by JET (for example, JET ArrayDataProvider) or implement an interface (JET DataProvider), and, most importantly, that use the VB Extended Type mechanism so that these types are VB aware. These VB types are generally used with a VB variable.

Authors can also use the same Extended Type mechanism to write Custom Extended Types.

VB provides these built-in extended types:

  • Service Data Provider

    This built-in extended type represents a data provider that fetches data from a service endpoint and that can be bound to listView, table and other collection components that can bind to a DataProvider implementation. It encapsulates various capabilities such as filtering, sorting, pagination, and fetch and allows externalizing fetches to an actionChain.

  • Multi-Service Data Provider

    JET components that bind to data providers like oj-combobox-one / oj-select-one (or the -many variants), often use different 'fetch' capabilities. Example a oj-select-single component calls fetchFirst() (on the DataProvider implementation) to populate its options, in addition to fetchByKeys() to fetch data for selected value and fetchByOffset. This built-in extended type is a dataProvider implementation that combines multiple ServiceDataProvider variables, each providing a unique fetch capability.

  • Array Data Provider 2

    This extended builtin type is a data provider implementation where the data is available as an array. Generally with vb/ArrayDataProvider2 (similar to vb/ArrayDataProvider) all the data is set once, the data itself can fetched from a backend service (say a list of countries) as it is assumed that array once created is static, i.e. data changes infrequently or has limited/infrequent adds/updates and removes done to it.

  • Array Data Provider (Legacy)

    This extended type uses the JET oj.ArrayDataProvider implementation, which is based on the DataProvider interface, and whose data is a plain array. The properties on the variable of type vb/ArrayDataProvider generally mirror the JET ADP's properties.

Service Data Provider

ServiceDataProvider represents a data provider that provides data by fetching it from a service or endpoint and that can be bound to components. It also allows externalizing fetches through an action chain.

ServiceDataProvider can be used to fetch collections of data either implicitly using a configured endpoint, or externally by delegating to an action chain. Additionally, when ServiceDataProvider uses an Oracle Cloud Applications service, the built-in business object REST API transforms associated with the service automatically enable capabilities such as sorting, filtering, and pagination of the data. When used with endpoints not part of an Oracle Cloud Applications service, it's important for service authors to provide a custom transforms implementation that supports these capabilities. For details refer to the description of the transforms property in Service Data Provider Properties and to its associated sections on request and response transforms.

A variable that uses this built-in type can be bound to collection components like listView, table, combobox/select, chart, and other JET components that accept a data provider.

When the properties of the ServiceDataProvider variable change, it listens to the variable onValueChanged event, and notifies all its subscribers (such as components) to refresh (by raising a data provider event). Currently, UI components are the only listeners of this event.

Service Data Provider Properties

ServiceDataProvider (SDP) exposes properties that a variable of this type can use to configure. All properties are directly accessible through the variable. Expressions like {{ $page.variables.incidentListTableSource.filterCriterion }} can be used where expressions are supported, including component (markup) attributes.

endpoint

A string that is the REST endpoint in the format 'serviceName/endpointName'. The endpoint is typically a GET endpoint that returns a collection, and is defined in the service model.

fetchChainId

A string that is the 'id' of the actionChain to use to fetch the results. See Implicit and Externalized Fetches for more information.

headers

An object of the names of one or more header properties, and the corresponding values. Any headers specified here are also set on the externalized REST action by the design time. Alternatively, if a fetchChainId is not specified, headers are passed through to the internal REST calling mechanism by the ServiceDataProvider.

idAttribute

Supports composite keys, using multiple properties. It is a string or array that is the field or fields in the response data for each row, that represents the 'id', or key, field. Deprecated; use keyAttributes instead.

keyAttributes

A string or array, that is the field or fields in the response data for each row, that represent(s) the 'id' (or key) field. Can be:
  • A property name - the key, in various contexts, will also be a string.

  • An array of property names - the key will also be an array, of values.

  • @value, use all properties - the key will also be an array, of values.

  • @index, use the index as the key - the key will be an integer.

itemsPath

A string that is the path to the root of the actual collection in the response. Example 'result' if the response returned from the endpoint looks like {count: 10, result: [...]}

capabilities 

An object that defines the capabilities supported by the ServiceDataProvider and the endpoint it uses. The capabilities object is defined by the JET DataProvider API.

"This property serves as a hint for UI components bound to an SDP variable, to know about the capabilities the endpoint supports and use the correct fetch / sort / filter behaviors.

A variable of type vb/ServiceDataProvider generally defaults to a 'fetchFirst' capability if no capability is specified. This means that the endpoint associated to the SDP is assumed to support a fetchFirst behavior. The same endpoint can support other 'fetch' capabilities as well.

For example, with business object REST API GETAll endpoints, the same endpoint can provide fetchFirst / fetchByKeys ('lookup') and fetchByOffset ('randomAccess') behaviors.

With third-party services it's important for authors to carefully consider the behaviors their endpoint supports before configuring the SDP property. For example if the third-party service endpoint provides optimal 'lookup' based fetchByKeys, and a 'randomAccess' based fetchByOffset, it's important that the author implements the appropriate transforms functions to support these capabilities. Refer to the section on Request Transformation Function, particularly the 'paginate' and 'fetchByKeys' types for details.

If the same endpoint cannot be used to provide the other fetch behaviors then it might be required to use a Multi-Service Data Provider. In all other cases SDP will fallback to using the fetchFirst behavior to provide sub-optimal implementations of fetchByKeys and fetchByOffset behavior.

Key / Type sub-key Values Example Description
fetchFirst (optional) / object implementation "iteration"  

fetchFirst is not a capability supported by the JET DataProvider contract but is a new capability that SDP introduces.

Why this is needed?

SDP variables created prior to this enhancement will always assume the 'fetchFirst' capability, for backwards compatibility. New SDP variables created in DT 'may' choose to set this property to correctly reflect the capability the endpoint supports.

fetchByKeys (optional) / object implementation

"lookup"

"iteration"

getCustomers endpoint supports a lookup based fetchByKeys.

"customersSDP": {
  "type": "vb/ServiceDataProvider",
  "defaultValue": {
    "endpoint": "demo-data-service/getCustomers",
    "keyAttributes": "id",
    "itemsPath": "result",
    "capabilities": {
      "fetchByKeys": {
        "implementation": "lookup"
      }
    }
  }
},
(see JET DataProvider API)

the "lookup" based implementation indicates the endpoint supports fetching key(s) data using a single request.

  • For business object REST API services, most GETAll endpoints that provide a fetchFirst capability also support querying for a key. So the default business object REST API transforms uses the fetchFirst endpoint to query for the key(s) and this property need not be set. In rare cases an entirely different endpoint might be required to fetch key data, in which case a Multi Service Data Provider might be needed.
  • For all other types of services, authors must ensure a lookup based 'fetchByKeys' transforms function is provided. If not an "iteration" based implementation is used.

when a "lookup" based implementation is not provided, SDP falls back to an "iteration" based implementation. This is non-performant because it uses fetchFirst / iteration to iterate over rows until the requested key(s) are located, before returning the requested key data in the form - FetchByKeysResults.

multiKeyLookup

"yes"

"no"

"capabilities" : {
  "fetchByKeys": {
    "implementation": "lookup",
    "multiKeyLookup": "no"
  }
}
Tells SDP whether endpoint can fetch multiple or a single key at a time.

defaults to 'yes'. only available when implementation is 'lookup'. This is automatically supported for business object REST API services that use the provided business object REST API transforms.

  • when fetchByKeys() is called with more than one key and the capability only supports lookup by a single key, then as an optimization SDP makes multiple fetch calls against the endpoint, one per key and assembles the results
fetchByOffset (optional) / object implementation

"iteration"

"randomAccess"

getIncidents endpoint supports a lookup based fetchByOffset.
"incidentsSDP": {
  "type": "vb/ServiceDataProvider",
  "defaultValue": {
    "endpoint": "demo-data-service/getIncidents",
    "keyAttributes": "id",
    "itemsPath": "result",
    "capabilities": {
      "fetchByOffset": {
        "implementation": "randomAccess"
      }
    }
  }
}

the "randomAccess" based implementation requires an endpoint that supports random access of requested page from an offset.

  • For business object REST API services, most GETAll endpoints support querying from a specified offset, and so the default business object REST API transforms uses this implementation automatically.
  • For all other types of services, authors must ensure a "randomAccess" based (paginate) transforms function is provided. If not an "iteration" based implementation is used.

when a "randomAccess" based implementation is not provided, SDP falls back to an "iteration" based implementation. This is non-performant because it uses fetchFirst / iteration to iterate over pages until the desired offset is reached.

filter / object operators array of supported operators
"capabilities" : {
  "filter": {
    "operators": ["$eq", "$or"]
  }
}
a map of supported filter operators.

Note: VB does not support Set types so use Array for operators

This doc does not go into the details of wiring up the 'filter' and 'sort' capabilities, but when these are set the getCapability() method on the DataProvider will use the information defined here.

For more on filter operators, see the JET documentation: oj.FilterCapability.html#operators

It's a combination of attribute and compound operators.

  • For list of attribute operators - oj.AttributeFilterDef.html#op
  • For list of component operators - oj.CompoundFilterDef.html#op
textFilter any value
"capabilities" : {
  "filter": {
    "textFilter": true
  }
}

any truthy value can be set for textFilter. By default SDP sets this to true.

This value tells the consumer of the SDP that text filtering is enabled.

For business object REST API endpoints text filtering works by default with some minimal configuration, but the service author is expected to write a filter transforms function for any complex text filtering. See the section below titled 'Transforms Function for Text Filtering' for further details.

For 3rd party endpoints the service author must write a filter transforms function that handles the text filter, or they can turn off the capability entirely.

sort / object
"capabilities" : {
  "sort": {
    "attributes": "single"
  }
}
array of supported sort operators.

For more on sort capabilities, see oj.SortCapability in the JET documentation.

responseType

The type of the response that is returned by the ServiceDataProvider. This can be an object or array. When provided it is used for two purposes:

  1. To determine the fields to fetch (aka select fields) from the endpoint. A transforms author will be provided these fields via the 'select' transforms function, if they wish to edit it, but ultimately the final response is shaped by the ServiceDataProvider based on the responseType set on it (see point 2 below).
    1. When using an Oracle Cloud Application-based endpoint with ServiceDataProvider, the built-in business object REST API transforms are loaded automatically (vb/BusinessObjectsTransform for Business Objects or business object REST API services), and the select transforms function creates a 'fields' query parameter with the desired fields, both scalar and objects (and recursively includes the object's fields, as well). This will both include and expand fields.
  2. To automatically shape the response (from the fetch call) to match the responseType. Shaping a response to match the responseType usually means that missing data is 'fixed up'. This is done to ensure that binding expressions used in components work without issues.
    1. For example, an expression like {{ $current.objectVar.propA }} will fail if objectVar is missing.

      Note:

      Auto-shaping of response data is based on rules determined by the Visual Builder type system. If authors do not want the automatic shaping of data performed by ServiceDataProvider to introduce unexpected behavior, they must either ensure that the response data is 'complete', or they need to wrap binding expressions to guard against missing data. Response data can be made 'complete' either on the server-side, or the client can use a 'body' response transforms function to fix up incomplete data based on business rules.

Some additional things to consider:

When ServiceDataProvider externalizes data fetch

When author chooses to externalize the ServiceDataProvider fetch, the design-time often configures a chain with a RestAction, with most properties from the ServiceDataProvider on the action (RestAction and ServiceDataProvider configuration share similar properties). It also adds a 'hookHandler' property. There are certain properties that are best set on the ServiceDataProvider and not on the RestAction. Refer to the Externalized Fetch section for a list of properties that must be configured on the ServiceDataProvider variable.

It is recommended that 'responseType' always be configured on the ServiceDataProvider so that the 'select fields' are requested with the fetch call, and auto shaping of the response does not yield unexpected results (see note). The former is always determined by ServiceDataProvider.

Note:

For external fetches, if the RESTAction also has 'responseType' set, then it gets applied first to the response. Not only is this redundant and not performant, it's also problematic if the responseType on RestAction were to auto-shape the response to have fewer attributes than what the 'select fields' requested.

When ServiceDataProvider is used with dynamic components

Another reason for recommending that 'responseType' always be configured on the ServiceDataProvider is to address dynamic UI cases, where the responseType is not known at design-time, and 'select fields' are only provided at runtime (see note). In fact the responseType is often set to a wildcard type ('any' / 'any[]').

Note:

Dynamic collection components determine the list of attributes to fetch only at runtime. And this is provided via a fetchFirst() call to ServiceDataProvider (using the 'attributes' parameter) and not configured using the 'responseType' property (see JET FetchListParameters). When 'attributes' are provided, 'responseType' is ignored. There is also no default auto-shaping done when attributes are provided.

body

An object that represents the body for a fetch request, where the request is made to a POST based endpoint. Another example is where ElasticSearch based endpoints use a POST method to fetch search results, where the search criteria are set using the body.

uriParameters

An object that defines one or more properties that are parameters on the endpoint URL. For example, the FixitFast service has an endpoint to retrieve all incidents for a technician using the URL http://.../incidents?technician={technician}. Here 'technician' is a query parameter that will be defined under uriParameters like this:

"uriParameters": {
  "technician": "{{ $page.variables.appUser.name }}"
},

The uriParameters are used to perform a simple string replacement if the URL includes parameters that must be substituted before it's resolved. Otherwise the parameters are appended to the URL. The uriParameters are also passed to the query transform function (details below), so page authors can use the value of the above property to tweak the URI further if needed. 

pagingCriteria

An object that defines the paging defaults if needed. Generally a paging component (like listView or table) will provide the data provider with size or offset or both. If the component does not provide either size or offset, the ServiceDataProvider will use the values set on this property as defaults. The pagingCriteria are then passed to the paginate transform function (see below). Supports the following properties.

  • size: number of rows to fetch by default, when no size is provided by caller.

  • offset: the offset to start the fetch from. Defaults to 0.

  • maxSize: the default maximum number of rows to fetch when the caller (usually a component) requests that all rows be fetched. Some JET components, like oj-chart, often request all rows by setting { size: -1 }. This property can be used to control the maximum number of rows to fetch, when it may not be performant to ask the service endpoint to return all rows. If this property is not set, then the size: -1 property is passed through to the paginate transforms, and it may be necessary for transforms authors to handle -1 as the size.

  • iterationLimit: the upper limit of the number of rows that can be fetched during iteration cycles. This is only used when size isn't provided and continuous iteration of rows is required. An example is when a list of values component tries to fetch labels for selected keys and the underlying multiServiceDataProvider is not configured with a 'lookup' based fetchByKeys capability. So the ServiceDataProvider reverts to using an optimized 'iteration' based implementation that is based on the fetchFirst capability. When this happens, there could be numerous fetch requests hitting the endpoint. If the service or endpoint would like to limit this, it's important to set this value. This also gets used with the optimized fetchByOffset capability for its optimized iteration based implementation.

Page authors need to understand how the above properties are used by the ServiceDataProvider during a fetch call:

  1.  Generally, the page size used by a fetch can be defaulted using the pagingCriteria.size. This is only used when a component does not explicitly provide a size. The same is true for an offset.

  2.  When the size is provided by the caller (for example, components), this overrides the default pagingCriteria.size set on the ServiceDataProvider. 

    Note:

    When components do ask for a specific number of rows, and the ServiceDataProvider returns more rows than were explicitly requested, some components can get in an indeterminate state. In such cases, to control the fetchSize, it's better to set this property on the component. Specifically, oj-list-view has a scrollPolicyOptions.fetchSize.
  3.  Some components do not support a fetchSize property. If this is the case, you can force the fetch to be a different value from what the component requested by writing a paginate transform function where the size can be tweaked. But you might then encounter the indeterminate state described in #2.

  4.  It is generally not recommended that you set endpoint-specific size and offset parameters using the uriParameters property directly (for example, the business object REST API supports 'limit' and 'offset' query parameters that are the equivalent of the pagingCriteria.size and offset). If you do, you are on your own to write a business object REST API transform that can merge/use the value set both in the uriParameters and pagingCriteria properties. And you are also likely run into the caveats explained in #3.

filterCriterion 

An object representing a single attribute filter criterion with the properties { op, attribute, value }, where 'op' is one of the supported JET attribute operators, and 'attribute' and 'value are the name and value of the attribute respectively. It may also represent a compound filter criterion {op, criteria}, where 'op' is a compound operator, and ‘criteria’ is an array of attributes or compound criterion.

Most complex filter expressions can be expressed using the JET filterCriterion structure. Sometimes you may need to externalize fetches to build your filter criteria for the REST action.

Note:

The business object REST API transforms shipped with Visual Builder support all attribute operators except $regex. They can transform a simple attribute filter or a compound filter that is an array of attribute filter criterion.
// attribute criterion
{
  "op": "$eq",
  "attribute": "empName",
  "value": "Lucy"
}
  
// In the business object REST API, the above criterion will become the following query parameter:
//   "q=empName = 'Lucy'"
// compound criterion
{
  "op": "$or",
  "criteria": [
    {
      "op": "$gt",
      "attribute": "hireDate",
      "value": "2015-01-01"
    },
    {
      "op": "$le",
      "attribute": "hireDate",
      "value": "2018-01-01"
    }
  ]
}
  
// In the business object REST API, the above criterion will become the following query parameter:
//   "q=hireDate > '2015-01-01' or hireDate <= '2018-01-01'"

Complex grouped criteria can be expressed in JSON using the filterCriterion API, but a transform function that can handle such grouped (or nested) criteria will need to be written by page authors for the business object REST API or for other external REST services, in order to build the appropriate query parameter.

{
  "op": "$and",
  "criteria": [
    {
      "op": "$sw",
      "attribute": "project",
      "value": "BUFF"
    },
    {
      "op": "$or",
      "criteria: [
        {
          "op": "$ge",
          "attribute": "label",
          "value": "foo"
        },
        {
          "op": "$le",
          "attribute": "label",
          "value": "bar"
        }
      ]
    }
  ]
}

// In the business object REST API, the above criterion will become the following query parameter:
// "q=((project LIKE 'BUFF%') and ((label >=  'foo) or (label <= 'bar')))"

sortCriteria

An array of objects, where each object is an atomic sort expression of the form shown here.  If you have more complex structures for representing sortCriteria, you can use the externalized fetch option to build sort criteria and provide it to the REST action. See Implicit and Externalized Fetches for details.

[{
  "attribute": "<name of the field>", 
  "direction": "<'ascending' (default) or 'descending'>" 
}]

mergeTransformOptions 

This property allows a page author to set a callback to fix up or merge the final transforms options that are passed to the transform functions configured on the ServiceDataProvider. Let's say a sample endpoint, GET /customers, supports an 'ids' query parameter that can used to query customers by specific keys. For example:

/customers?ids=cus-101,cus-103

A component like oj-select-many might call the ServiceDataProvider requesting the customer data for specific keys by calling fetchByKeys() with these keys: ['cus-101', 'cus-103'].

The ServiceDataProvider does not support a declarative way to automatically map these keys programmatically to the 'ids' query parameter on the URL. Therefore, it might be necessary for the page author to use this property to set a function callback that will fix up the query transforms option. For details on writing this function, see Merge Transform Options Function.

transformsContext

A context object passed to the transform functions for both request and response. For fetchFirst calls, the context will be available for all iterations using the same iterator. Authors can manage this object as they wish. If this property is not set, an empty Object is provided by default to all transform functions. When a fetchMetadata property is provided as part of a fetch*() call, then this property is automatically set on the transformsContext Object and made available to transform functions.

  • fetchMetadata
    For Elastic searches where the query can be arbitrarily complex, callers can send extra search metadata via the fetch call. This parameter can be used to tweak the body that is used as POST-body in the query.

    Note:

    This is a Preview API and subject to change.
  • textFilterAttributes

    See Write a Filter Transforms Function for Text Filtering for details on this property.

totalSize

See getTotalSize

transforms

An object that has two properties for specifying 'request' and 'response' transform functions (callbacks).

Request transformation (or transform) functions are generally specified on the service (or endpoint) definition as it applies to all usages of the service. The transform functions specified here are only applicable for the current usage of the service or endpoint.

Request transform functions are primarily used to transform the URL or Request configuration before a request is sent to the endpoint.

Response functions can be used to process the response and return any additional state along with the response. Additional state is saved as internal state on the data source variable.

At design time, the page author will need to know whether the endpoint supports paging, sorting, filtering (or QBE), and the format/syntax for specifying these. Using the transform functions, the page author can tweak the Request to build a URL containing the paging, sorting, filtering params, and additional endpoint specific query params. 

  • request: An object whose properties refer to the type of the request transform functions, and the value the actual function. The following types are supported. See Request Transformation Function for details.

    • paginate: a paginate function that implements code to transform the request for pagination (or iterating through record sets) specific to the endpoint.

    • sort: a sort function that implements code to transform the request for sorting, specific to the endpoint.

    • filter: a filter function. Note: Refer to the next section for details on how to use the transform functions. 

    • query: a query function, to pre-process query parameters available through the uriParameters property.

    • select: a select (fields) function used to build the list of fields to fetch, if the endpoint supports it.

    • body: a body transform function that allows page authors to tweak the body if needed before the fetch call is made.

    • fetchByKeys: transforms function that allows a page author to take a key or Set of keys passed in via the options, and update the request to fetch requested keys.
  • response: An object whose properties also refer to the type of the response transform function.  See Response Transformation Functions for details.

    • paginate: This transform function is called immediately after the REST layer receives a response. It is called with the response so this function can process it and return an object with a group of properties set. The returned object is the primary way ServiceDataProvider obtains information about the paging state of the request:

      • totalSize: <optional> used to inform SDP what the totalSize of the result is.
      • hasMore: <generally requiredc> A boolean that indicates whether there are more records to fetch. Example in business object REST API usecases this would map to the hasMore boolean property commonly returned in the response. See explanation below for behavior of SDP when hasMore is not set.
      • pagingState: <optional> This can be used to store any paging state specific to the paging capability supported by the endpoint. In 1.0.0, this property can be used in the response paginate transform function, to set additional paging state. Which will then be passed 'as is' to the request paginate transform function, for the next fetch call.
    • body: This transform function is called immediately after the REST layer receives a response. It is a hook for authors to transform the response body, and is not guaranteed to be called in any specific order.

    The way this works is an iterating component will get the AsyncIterator from the dataProvider (like ServiceDataProvider) and keep iterating until there is no more data to fetch, or until the component viewPort is filled, or until its current scrollPosition is reached (this might be needed when a selected row is several pages down), whichever comes first. So it's extremely important for SDP to have the above information, to know when to stop iterating.

Missing 'hasMore' property in the paginate

In the event that service implementors may not have configured a paginate transform, we provide the following fallback behavior. If the first fetch request from by the SDP's AsyncIterator, has no 'hasMore' through the paginate response, SDP assumes there are no more records to fetch and iterator is marked as done. This behavior at least allows components to render some data without causing repetitive fetches. Of course this means scrolling through component will not fetch next set, if the endpoint did indeed have more rows to fetch.

Implicit and Externalized Fetches

When a ServiceDataProvider is configured with properties described in the Service Data Provider Properties section, it will, for the most part, manage fetching data and notifying components implicitly. The exception is the 'fetchChainId'.

Implicit Fetch

A typical configuration for an implicitly fetching ServiceDataProvider would look like this:

"incidentListDataProviderImplicit": {
  "type": "vb/ServiceDataProvider",
  "description": "configuration for implicit fetches",
  "input": "none",
  "defaultValue": {
    "endpoint": "ifixitfast-service/getIncidents",
    "headers": {},
    "keyAttributes": "id",
    "itemsPath": "result",
    "uriParameters": {
      "technician": "{{ $application.user.userId }}"
    }
  }
}

It is important to note that a ServiceDataProvider variable does not cache its data, just its configuration. The data is also not persisted to history, session or localStorage.

Since the data can be arbitrarily large data sets, it is recommended that page authors use other means to cache data on the client, such as the JET offline toolkit cache. This applies to externalized fetches as well.

Externalized Fetch via an Action Chain

When a 'fetchChainId' property is present, the ServiceDataProvider delegates the fetch to the action chain. A typical configuration for a ServiceDataProvider variable (supporting a fetchFirst capability) that externalizes REST will look like the code below. These are the only properties that are allowed to be configured (or that are relevant):

  • capabilities: when this property isn't set, the 'fetchFirst' fetch capability is assumed.

  • fetchChainId

  • idAttribute (deprecated) or keyAttributes

  • itemsPath

  • mergeTransformOptions: this property is defined on the ServiceDataProvider variable, because merging transform options only applies when an action chain (with a REST action) is called in the context of a data provider fetch call.

  • transformsContext: Unlike most transforms-related properties, this property can only be defined on the SDP configuration. Most transforms-related properties can be defined on the REST action (requestTransformationOptions, requestTransformFunctions, responseTransformationFunctions).

  • responseType

"variables": {
  "incidentListTableSource": {
    "type": "vb/ServiceDataProvider",
    "input": "none",
    "persisted": "session",
    "defaultValue": {
      "fetchChainId": "fetchIncidentListChain",
      "keyAttributes": "id",
      "itemsPath": "result",
      "responseType": "application:incidentsResponse"
    }
  }
},
"chains": {
  "fetchIncidentListChain": {
    ...
  },
}

The type definition of "application:incidentsResponse" used by the 'responseType' property can be seen in this example. This structure is similar to the one returned from a REST response. Note that itemsPath is always located within the 'body' property of the response that is returned.

For example, the app-flow.json file for the ServiceDataProvider configuration shown above could look like this:

"incidentsResponse": {
  "type": {
    "status": "string",
    "headers": "object",
    "body": {
      "result": "application:incidentSummary[]"
    }
  }
},
"incidentSummary": {
  "type": {
    "id": "string",
    "problem": "string",
    "priority": "string",
    "status": "string",
    "customer": "application:customer"
  }
},

A sample return value from the action chain would look like this:

{
  "status": "200",
  "headers": {},
  "body": {
    "result": [
      {
        "id": "incident_1",
        "problem": "heater broken",
        "priority": "high",
        "status": "open",
        "customer": {}
      }
    ]
  }
}

Generally, users externalize fetches to ensure full control over how the request and response are processed.

For example, users can connect custom sort and filter query parameters either in the service endpoint or in the REST action. This is the preferred configuration approach. If, however, properties like sortCriteria, filterCriterion, transforms, and so on, are defined on the ServiceDataProvider, they will be ignored, and those configured on the REST action will be used when building the request. It's important to note that sortCriteria / filterCriterion passed in by the component / caller will always get used and (attempted to be) merged with the ones configured on RestAction. See Merge Transform Options Function property.

In the example below, the action chain 'fetchIncidentListChain' defined in the fetchChainId property of the ServiceDataProvider variable above has a typical chain configuration, one of which is a RestAction.

  1. The 'hookHandler' property under configuration chain variable will be automatically generated at design time and is always set to vb/RestHookHandler. SDP implements a custom hookHandler that extends from this class.

  2. If the REST response returns a structure that is exactly what the ServiceDataProvider expects, this can be returned directly (as in the example below). But if the REST response is different from the expected responseType, then an action that maps the REST response to the structure defined by 'responseType' on the SDP needs to be configured.

  3. The last action in the chain will always be a ReturnAction whose payload resembles the REST response whose body resembles 'responseType'. The incidentsResponse response variable in the chain is provided for clarity but is not used by the chain.

  4. If more fields are returned than what the responseType has, SDP will attempt to auto-map the result to the response type.
  5. It's important to not set the 'returnType' property when a ReturnAction is already present in the chain for SDP, because this additionally coerces the response returned to the caller.
"chains": {
   "fetchIncidentListChain": {
    "variables": {
      "configuration": {
        "type": {
          "hookHandler": "vb/RestHookHandler"
        },
        "description": "the configuration for the rest action",
        "input": "fromCaller",
        "required": true
      },
      "response": {
        "type": "application:incidentsResponse"
      }
    },
    "root": "fetchIncidentList",
    "actions": {
      "fetchIncidentList": {
        "module": "vb/action/builtin/restAction",
        "parameters": {
          "endpoint": "ifixitfast-service/getIncidents",
          "uriParams": {
            "technician": "{{ $application.user.userId }}"
          },
          "hookHandler": "{{ $variables.configuration.hookHandler }}",
          "requestTransformOptions": {
            "sort": "{{ $page.variables.sortExpression }}",
            "filter": "{{ $page.variables.filterAtomicExpression }}"
          },
          "requestTransformFunctions": {
            "paginate": "{{ $page.functions.paginate }}",
            "query": "{{ $page.functions.query }}",
            "filter": "{{ $page.functions.filter }}",
            "sort": "{{ $page.functions.sort }}"
          },
          "responseTransformFunctions": {
            "paginate": "{{ $page.functions.paginateResponse }}"
          }
        },
        "outcomes": {
          "success": "returnSuccessResponse",
          "failure": "returnFailureResponse"
        }
      },
      "returnSuccessResponse": {
        "module": "vb/action/builtin/returnAction",
        "parameters": {
          "outcome": "success",
          "payload": "{{ $chain.results.fetchIncidentList }}"
        }
      },
      "returnFailureResponse": {
        "module": "vb/action/builtin/returnAction",
        "parameters": {
          "outcome": "failure",
          "payload": "{{ $chain.results.fetchIncidentList }}"
        }
      }
    }
  }
}
Merge Transform Options Function

mergeTransformOptions function signature:

A page author can use the mergeTransformOptions function callback on the ServiceDataProvider fetch to fix up the transforms options that will be passed to the transform functions, if and when needed. The function will be passed two parameters: 'configuration' and 'transformOptions'. 

The configuration object will contain one set of { capability, context, externalContext, fetchParameters } set as a request is servicing one fetch capability.

For the configuration object, this table describes configuration parameters for the fetchByKeys capability.

Sub-property Sub-property Value Description
capability - fetchByKeys A hint that supplies the author the fetch capability.
context
  • idAttribute

  • itemsPath

  • uriParameters

  • filterCriterion

  • sortCriteria

  • pagingCriteria

  • responseType

  • capabilities

    • fetchByKeys

    • keys

    • ...

-

Provides a snapshot of the ServiceDataProvider variable at the time the fetchByKeys() call is made.

For external chains, the state may not include all properties listed here.

externalContext     If the fetch was externalized then the context setup on the RestAction
fetchParameters keys -

The original parameters passed in via the fetchByKeys call.

This table describes configuration parameters for the fetchByOffset capability.

Property Sub-property Value Description
capability - fetchByOffset A hint telling the author the fetch capability for the current request.
context
  • idAttribute

  • itemsPath

  • uriParameters

  • filterCriterion

  • sortCriteria

  • pagingCriteria

  • responseType

  • capabilities

    • fetchByKeys

    • keys

    • ...

  A snapshot of the value of the ServiceDataProvider variable at the time the fetchByOffset() call was made.
externalContext     If the fetch was externalized then the context setup on the RestAction
fetchParameters
  • filterCriterion

  • size

  • offset

  • sortCriteria

- The original parameters passed in via the fetchByOffset call.

This table describes configuration parameters for the fetchFirst capability.

Property Sub-property Value Description
capability - fetchFirst A hint telling that the request is a fetchFirst capability.
value
  • idAttribute

  • itemsPath

  • uriParameters

  • filterCriterion

  • sortCriteria

  • pagingCriteria

  • responseType

  • capabilities

    • fetchByKeys

    • keys

    • ...

- A snapshot of the value of the ServiceDataProvider variable at the time the fetchFirst() call was made.
externalContext     If the fetch was externalized then the context setup on the RestAction
fetchParameters
  • filterCriterion

  • size

  • sortCriteria

- -

This table describes the properties for the transformOptions parameter.

Property Description
  • query

  • filter

  • paginate

  • sort

  • select

These are the properties when the ServiceDataProvider is configured for implicit fetch.

When the ServiceDataProvider is configured to use an external fetch chain, the options configured on the RestAction 'requestTransformOptions' property will be made available here.

A sample endpoint, GET /customers, supports an 'ids' query parameter that can used to query customers by specific keys. For example: customers?ids=cus-101,cus-103

For this to work, there is currently no easy way at design time to map the keys provided by the component programmatically to the 'ids' query parameter on the URL. It might be necessary for page authors to use this property to wire up a function that will merge the transforms option.

This should be configured as follows:

  1. Configuring 'mergeTransformOptions' property
    • The ServiceDataProvider variable below defines a fetchByKeys capability.

    • The 'mergeTransformOptions' property is configured to point to a page function.

    "customerSingleSDP_External": {
      "type": "vb/ServiceDataProvider",
      "defaultValue": {
        "endpoint": "demo-data-service/getCustomers",
        "keyAttributes": "id",
        "itemsPath": "result",
        "capabilities": {
          "fetchByKeys": {
            "implementation": "lookup"
          }
        }
     
        "mergeTransformOptions":
          "{{ $page.functions.processOptionsForGetCustomers }}"
      }
    }
  2. Implementing the function
    • The page author uses the function to fix up the 'query' transform options that will be passed to the query transform function.

    • The page function "{{ $page.functions.processOptionsForGetCustomers}}" will look like the following:

    /**
     * fix up the query transform options.
     * When the fetchByKeys capability is set, the 'keys' provided via the fetch call
     * can be be looked up via the configuration.fetchParameters. This can be 
     * set/merged onto the 'query' transform options (1). This allows the transform 
     * function to then use the keys to build the final 'ids=' query param on the url.
     * See queryCustomersByIds method.
     *
     * Note: (1) this is needed because there is no way through DT configuration 
     * to define a mapping of 'keys' that are provided via a fetch call, to the 'ids' 
     * query parameter.
     *
     * @param configuration a map of 3 key values. The keys are
     * - fetchParameters: parameters passed to a fetch call
     * - capability: 'fetchByKeys' | 'fetchFirst' | 'fetchByOffset'
     * - context: the context of the SDP when the fetch was initiated.
     *
     * @param transformOptions a map of key values, where the keys are the names of
     *  the transform functions.
     * @returns {*}
     */
    PageModule.prototype.processOptionsForGetCustomers =
        function (configuration, transformOptions) {
      var c = configuration;
      var to = transformOptions;
      var fbkCap = !!(c && c.capability === 'fetchByKeys');
      var keysToFetch = fbkCap ? (c && c.fetchParameters && c.fetchParameters.keys) : null;
     
      if (fbkCap && keysToFetch && keysToFetch.length > 0) {
        // join keys
        var keysToFetchStr = keysToFetch.join(',');
        to = to || {};
        to.query = to.query || {};
        // ignore ids set on the query options and instead use ones passed in by
        //   fetchByKeys call
        to.query.ids = keysToFetchStr;
      }
     
      return to;
    };
    • A query transform function is not needed in the above example because the query parameters are automatically appended to the final request URL if no additional transformation of the query options to the query parameter is needed.

    • A query transform function might be needed in more complex use cases.

Request Transformation Function

A request transformation (or transform) function is generally specified on the service endpoint. It can also be specified on the ServiceDataProvider variable, which overrides the endpoint one.

A request transform function is called right before a request is made to the server/endpoint. It provides a chance for page authors to transform the options (paginate, filter, sort, and so on) and build the final (request) configuration. The ServiceDataProvider supports a predefined list of request transform function types, described in this section. Note that there are no guarantees of the order in which transform functions are called.

A request transformation function has the following signature: function (configuration, options) { return configuration }. The parameters to the function are:

  • configuration: an object that has the following properties:

    • url: Full URL of the request.

    • readOnlyParameters: Path and query parameters. These are not writable.

    • initConfig: Map of another configuration passed into the request. The 'initConfig' exactly matches the 'init' parameter of the request.

  • options: An object that is relevant to the type of transformation function. For a filter function, for example, this would be the filterCriterion.

  • context: A context object that is passed to every transform function to store or retrieve any contextual information for the current request lifecycle.

If transformations are needed for a specific data provider instance, these functions can be defined on the ServiceDataProvider variable under the 'transforms' property. For externalized fetch cases, the RestAction properties can be used for configuring transformations. 

Types of Request Transform Functions

paginate

The  'pagingCriteria' is passed in as the 'options' parameter to the paginate function. The pagingCriteria is often based on the current paging/scrolled state of the component. 

  • For implicit fetches, the pagingCriteria provided to the 'paginate' transform function can be used to construct a URL with the right paging query.

  • For externalized fetches, the pagingCriteria is always set on the REST instance through the hook handler. This means that if the RestAction has a responseTransformFunctions.paginate transform function property configured, then it can expect the pagingCriteria to be provided to it.

For offset based paging:

  •  size: Specifies how many items should be returned.

  • offset: Specifies which item the response should begin from.

  • The default value for the pagingCriteria can be set on the configuration, but will generally a component that is bound to the ServiceDataProvider variable will provide the values, where offset and size will be based on the configuration set in the component.

    // Variable Configuration
    "incidentListTableSource": {
      "type": "vb/ServiceDataProvider",     // variable of type vb/ServiceDataProvider
      "input": "none",
      "defaultValue": {
        "pagingCriteria": {                 // default size
          "size": 10
        },
     
        "transforms": {                     // transform function for paginate
          "request": {
            "paginate": "{{ $page.functions.paginate }}"
          }
        }
    }
    // paginate Transform Function
    // Transform function appends limit and offset parameters to the URL
    PageModule.prototype.paginate = function (configuration, options, context) { 
      const c = configuration;
      let newUrl = c.url;
      newUrl = `${newUrl}&limit=${options.size}&offset=${options.offset}`;
      c.url = newUrl;
      return c;
    };

filter

For this transform function, the 'filterCriterion' property is passed in as the 'options' parameter. The filterCriterion JSON property is an object representing a attribute criterion or a compound criterion. This example defines a simple structure for filter criteria that is a single criterion: 

// Variable Configuration
"incidentListTableSource": {
  "type": "vb/ServiceDataProvider",
  "input": "none",
  "defaultValue": {
    "filterCriterion": {                     // filterCriterion property defaultValue
      "attribute": "",
      "op": "eq",
      "value": ""
    },
    "transforms": {
      "request": {
        "filter": "{{ $page.functions.filter }}"  // transform function for filter
      }
    }
  }
}

Here's a sample filter transform function that converts the filterCriterion property to a query parameter appropriate to the endpoint: 

/**
 * Filter Transform Function Implementation
 * @param configuration
 * @param options the JSON payload that defines the filterCriterion
 * @param context an object to store/retrieve any contextual information for the 
 *  current request lifecycle
 * @returns {object} configuration object. the url looks like ?filter=foo eq 'bar'
 */
 
PageModule.prototype.filter = function (configuration, options, context) {
  const c = configuration;
  const filterCriterion = options;
 
  function jetFilterOpToScim(fop) {
    switch (fop) {
      case '$eq':
        return 'eq';
      case '$ne':
        return 'ne';
      case '$co':
        return 'co';
      default:
        console.warn('unable to interpret the op ' + fop);
        return null;
    }
  }
 
  function isEmpty(val) {
    return (val === undefined || val === null || val === '');
  }
 
  if (typeof filterCriterion === 'object' && Object.keys(filterCriterion).length > 0) {
    if (filterCriterion.op && filterCriterion.attribute && 
          !isEmpty(filterCriterion.value)) {
      const atomicExpr = {};
      atomicExpr.op = jetFilterOpToScim(filterCriterion.op);
      atomicExpr.attribute = filterCriterion.attribute;
      atomicExpr.value = filterCriterion.value;
 
      if (atomicExpr.op && atomicExpr.attribute) {
        c.url = URI(c.url).addQuery({
          filter: `${atomicExpr.attribute} ${atomicExpr.op} ${atomicExpr.value}`,
        }).toString();
      }
    }
  }
 
  return c;
};

Write a Filter Transforms Function for Text Filtering

If your SDP binds to a business object REST API endpoint, you have the following options to get text filtering to work.

  • Option 1: configure the SDP to include a vb-textFilterAttributes property where the attributes to apply the text filter is specified. The built-in business object REST API transforms provided by Visual Builder looks for this property and automatically builds a filter criterion using the text and turns it into a 'q' param.
    • "transformsContext": {
          "vb-textFilterAttributes": ["lastName"]
      }
    • Example: if a user enters text 'foo' in select-single, for the configuration above SDP generates q=lastName LIKE 'foo%'
    • By default the operator used is 'startsWith' as this is considered to be more optimized for db queries that 'contains'
  • Option 2: if the above doesn't meet your needs, then you can write a custom filter transform that massages the text filter and turns it into a regular filterCriterion.

If you use option 2 above, you could do something similar to the following example. In this example, resourcesListSDP uses the getall_resources endpoint. The (request) filter transforms property is a callback that is defined in the PageModule.

"resourcesListSDP": {
  "type": "vb/ServiceDataProvider",
  "defaultValue": {
    "endpoint": "crmRestApi11_12_0_0/getall_resources",
    "keyAttributes": "PartyNumber",
    "itemsPath": "items",
    "responseType": "page:getallResourcesResponse",
    "transformsContext": {
      "vb-textFilterAttributes": ["PartyName"]
    },
    "transforms": {
      "request": {
        "filter": "{{ $functions.processFilter }}"
      }
    }
  }
}

It's important to note that the transformsContext object is an argument to every transforms function, so transforms authors can read the attributes and build the query that way.

The transforms function below takes the text value provided by the component and turns into an attribute filter criterion using the attributes passed in.

define(['vb/BusinessObjectsTransforms'], function(BOTransforms) {
  'use strict';

  var PageModule = function PageModule() {};

   /**
   * The filter transform parses the text filter that may be part of the options and replaces
   * it with an appropriate attribute filter criterion using the textFilterAttrs.
   *
   * Note: select-single provides a text filter in the form { text: 'someTextToSearch' }.
   *
   * The processing of the resulting filterCriterion is delegated to the Business Object REST API
   * transforms module, which takes the filterCriterion and turns it into the 'q' param.
   * @param textFilterAttrs
   * @return a transforms func that is called later with the options
   */
  PageModule.prototype.processFilter = function(config, options, transformsContext) {
    const c = configuration;
    let o = options;
    let textValue;
    let isCompound;
    const tc = transformsContext;
    const textFilterAttributes = tc && tc['vb-textFilterAttributes];
 
    textValue = o && o.text;
 
    // build your regular filtercriterion and delegate to VB BO REST API filter transforms
 
    return BOTransforms.request.filter(configuration, o);
  }
  return PageModule;
});

Note:

Page authors are discouraged from configuring the SDP with the 'q' parameter directly, for example by setting a 'q' parameter in the uriParameters property. It is recommended that authors always use filterCriterion property instead to define their 'q' criteria. This is especially important when using text filtering because the components always provide a filterCriterion which is appended to any configured filterCriterion on the SDP It becomes especially difficult for VB to reconcile the 'q' defined in uriParameters with the filterCriterion and authors are on their own to merge the two.

It's also important to note that select-single calls fetchByKeys very often to locate the record(s) pertaining to the select keys. For this reason a new fetchByKeys transforms function has been added. Refer to the fetchByKeys transforms function for details.

sort

For this transform function, the 'sortCriteria' is passed in as the 'options' parameter. If page authors have complex sort expressions that cannot be expressed as a simple array, they can externalize the fetch to configure their own sort criteria and build a request using that.

// Variable Configuration
"incidentListTableSource": {
  "type": "vb/ServiceDataProvider",
  "input": "none",
  "defaultValue": {
    "sortCriteria": [                           // sortCriteria property default value
      {
        "direction": "ascending"
      }
    ],
     
    "transforms": {
      "request": {
        "sort": "{{ $page.functions.sort }}"    // transform function for sort
      }
    }
  }
}
/**
 * Sort Transform Function Implementation
 * @param configuration
 * @param options the JSON payload that defines the sortCriteria
 * @param context an object to store/retrieve any contextual information for the
 *  current request lifecycle.
 * @returns {object} configuration object. the url looks like ?orderBy=foo:asc
 */
PageModule.prototype.sort = function (configuration, options, context) {
  const c = configuration;
 
  if (options && Array.isArray(options) && options.length > 0) { 
    const firstItem = options[0]; 
    if (firstItem.name) { 
      const dir = firstItem.direction === 'descending' ? 'desc' : 'asc' 
      let newUrl = c.url; 
      newUrl = `${newUrl}&orderBy=${firstItem.attribute}:${dir}`; 
      c.url = newUrl; 
    } 
  } 
  return c; 
};

query

For this transform function, the 'uriParameters' property is passed in as options. Normally uriParameters are appended to the URL automatically, but there may be cases where the user would want to adjust the query parameters. For example, suppose the endpoint GET /incidents supports a query parameter called "search", which does a semantic search. If a specific transform needs to happen before the endpoint us cakked, then the transform function could be used for that. 

// Variable Configuration
"incidentListTableSource": {
  "type": "vb/ServiceDataProvider",
  "input": "none",
  "defaultValue": {
    "uriParameters":
      {
        "technician": "hcr",
        "search": "{{ $page.variables.searchBoxValue }}"// search query parameter 
                                                        // bound to some UI field
      }
    ],
     
    "transforms": {
      "request": {
        "query": "{{ $page.functions.query }}"       // transform function for query
      }
    }
  }
}
/**
 * query Transform Function Implementation
 */
PageModule.prototype.query = function (configuration, options, context) {
  const c = configuration;
  if (options && options.search) {
    let newUrl = c.url;
      newUrl = `${newUrl}&search=${options.search} faq`; // appends 'faq' to the 
                                                         // search term 
      c.url = newUrl; 
	}
	return c;
  // configuration, options}; 
};

select

This transform typically uses the 'responseType' to construct a query parameter to select and expand the fields returned from the service. The built-in vb/BusinessObjectsTransforms creates a 'fields' query parameter, such that the response will include all fields in the responseType structure, including expanded fields. For example:

/**
 * select transform function.
 * Example:
 *
 * Employee
 * - firstName
 * - lastName
 * - department
 *   - items[]
 *     - departmentName
 *     - location
 *        - items[]
 *          - locationName
 *
 * would result in this 'fields' query parameter:
 *
 * fields=firstName,lastName;department:departmentName;department.location:locationName
 *
 * @param configuration
 * @param options
 * @param context a transforms context object that can be used by authors of transform
 *  functions to store contextual information for the duration of the request.
 */
PageModule.prototype.select = function(configuration, options, context) {
  // the options should contain a 'type' object, to override
  var c = configuration;

  // do nothing if it's not a GET
  if (c.endpointDefinition && c.endpointDefinition.method !== 'GET') {
    return c;
  }

  // do nothing if there's already a '?fields='
  if(queryParamExists(c.url, 'fields')) {
    return c;
  }

  // if there's an 'items', use its type; otherwise, use the whole type
  vartypeToInspect = (options && options.type && (options.type.items || options.type));
  if(typeToInspect && typeoftypeToInspect === 'object') {
    var fields; // just an example; query parameter construction is left to the 
                // developer
 
    if(fields) {
      c.url = appendToUrl(c.url, 'fields', fields);
    }
  }
  return c;
}

function appendToUrl(url, name, value) {
  // skip undefined and null
  if (value !== undefined && value !== null) {
    var sep = url.indexOf('?') >= 0 ? '&' : '?';
    return url + sep + name + '=' + value;
  }
  return url;
}
 
function queryParamExists(url, name) {
  const q = url.indexOf('?');
  if (q >= 0) {
    return (url.indexOf(`?${name}`) === q) || (url.indexOf(`&${name}`) > q);
  }
  return false;
}

body

This transform is used to build or tweak the body for the fetch request. With some endpoints, especially those involving ElasticSearch, the search is made with a complex search criteria set on the body that can be tweaked here.

This transform function is the only function that is guaranteed to be called after all other request transform functions, (filter, sort, paginate, and so on). The reason is that any of the other transform functions can set info into the 'transformsContext' parameter, as a way to update the body. It's entirely left to the discretion of the transforms author how to use the 'transformsContext' property and the 'fetchMetadata' parameter provided via the fetch call. Currently, the built-in business object REST API transforms implementation does not use either.

/**
 * If a body is specified then we look for 'search' and 'technician' in the post body.
 * All other keys are ignored.
 * @param configuration
 * @param options
 * @param context transforms context
 */
function bodyRequest(configuration, options, transformsContext) {
  const c = configuration;
  if (options && typeof options === 'object' && Object.keys(options).length > 0) {
    c.initConfig.body = c.initConfig.body || {};
    // update body
  }
  return c;
}

fetchByKeys

A fetchByKeys transforms function allows the page author to take a key or Set of keys passed in via the options and tweak the URL, to fetch the data for the requested keys.

When the consumer of the SDP calls the fetchByKeys() method, if the transforms author has provided a 'fetchByKeys' transforms implementation then it gets called over the other transforms. If no fetchByKeys transforms function is provided then the default transforms will get called.

The built-in business object REST API transforms already provides a fetchByKeys transforms function implementation that appends the keys to the URL. This should suffice for most common cases and should result in at most one fetch request to the server. For third-party REST endpoints, the author can provide a custom fetchByKeys transforms implementation.

Example: for a sample third-party endpoint, the key is appended to the URL as a query param 'id=<key>'

define(['ojs/ojcore', 'urijs/URI'], function (oj, URI) {

  PageModule.prototype.fetchByKeysTransformsFunc = function (configuration, transformOptions){
    var c = configuration;
    var to = transformOptions || {};
    var fetchByKeys = !!(c && c.capability === 'fetchByKeys'); // this tells us that the current fetch call is a fetchByKeys

    if (fetchByKeys) {
      var keysArr = Array.from(c.fetchParameters.keys);
      var key = keysArr[0]; // grab the key provided by caller
      if (key) {
        c.url = URI(c.url).addQuery({
          id: key,
        }).toString();
      }
    }
    return c;
  };
});
Response Transformation Functions

Response transformation (transform) functions are called right after a request returns successfully. They provide a hook for page authors to transform the response further for the consumption of the ServiceDataProvider or user interface.

The ServiceDataProvider supports a predefined list of response transformation function types, described in this section. Note that there are no guarantees of the order in which transform functions are called.

A response transformation function has the following signature: function (result). It can be defined on the service endpoint, but can also be overridden on the variable. The parameter to this function is:

  • result: an object that has the following properties:

    • response: the response object, an implementation of the Response interface of the Fetch web API.

    • body: The (response) body that corresponds to the requested content type.

Types of Response Transform Functions

paginate

The paginate response transform function is called with the response so this function can process it and return an object with the following properties set. The returned object is the primary way ServiceDataProvider obtains information about the paging state of the request:

  • totalSize: Optional. Used to inform ServiceDataProvider what the totalSize of the result is (the total count of the records in the backend service/endpoint).

  • hasMore: Usually required, because with the JET DataProvider API, the paginate response transform function is relied upon to inform the ServiceDataProvider when to stop requesting to fetch more data. It is a boolean that indicates whether there are more records to fetch. For example, in business object REST API use cases, this would map to the hasMore boolean property commonly returned in the response.

    An iterating component such as ServiceDataProvider requires this information in order to know when to stop iterating when fetching data. The reason for this requirement is that an iterating component will get the AsyncIterator from the dataProvider and continue iterating until there is no more data to fetch, until the component viewPort is filled, or until its current scrollPosition is reached, whichever comes first.

  • pagingState: Optional. This can be used to store any paging state specific to the paging capability supported by the endpoint. This property can be used in the response paginate transform function to set an additional paging state. This will then be passed as is to the request paginate transform function for the next fetch call.

// Variable Configuration
"transforms": {
  "response": {
    "paginate": " {{ $page.functions.paginateResponse }}"
  }
}
// paginate() Response Transform Function
PageModule.prototype.paginateResponse = function (result, context) {
  const ps = {}; const tr = {};
 
  if (result.body) {
    const rb = result.body;
    if (rb.totalCount) {
      tr.totalSize = rb.totalCount;
    }
    if (rb.totalCount > 0) {
      tr.hasMore = !!rb.hasMore;
    } else {
      tr.hasMore = false;
    }
  }
  return tr;
};

body

This transform function is called last, after all the other response transforms have been called. It is a hook for authors to transform the response body or build an entirely new one that the ServiceDataProvider or component expects.

Methods

ServiceDataProvider implements most methods from oj.DataProvider, except for the isEmpty method.

Most ServiceDataProvider methods, such as fetchFirst, fetchByKeys, fetchByOffset, containsKeys, and getCapabilities, are called by the component that interfaces with the DataProvider implementation and will rarely need to be used directly. The getTotalSize method is an exception to this general rule.

getTotalSize method

The getTotalSize method returns a Promise that resolves to the total size of data available on the service endpoint. If a positive number is not set in the response transforms, a size of -1 is returned. Generally the returned value is the canonical size of the (endpoint) fetch when no search criteria is applied. In other words, this value is meant to be the same every time a fetch is called against the endpoint.

Because page authors often want the convenience of binding the totalSize on the page, vb/ServiceDataProvider supports a totalSize property that is a number. This can be used instead of the getTotalSize method, which is used by JavaScript callers.

For example, a page author can use the totalSize property of the ServiceDataProvider in markup as follows:

<oj-bind-text id="totalIncRows"
  value="[[ $variables.incidentListDataProvider.totalSize ]]"></oj-bind-text>

Features and Capabilities

Page authors generally need not be concerned with this, but it's generally useful to understand the features and capabilities that SDP supports. For details refer to JET DataProvider#getCapability.

At design time, a page author may need to know what features and capabilities the endpoint supports, and they may need to configure the correct properties and transforms.

Events

At design time, a page author may need to know what features and capabilities the endpoint supports, and they may need to configure the correct properties and transforms.

Events

The events raised by the data provider are defined by contract for oj.DataProvider. These events are fired at appropriate times to notify UI components. Page authors may need to force the variable to fire some of the DataProvider events, including 'add', 'remove', 'refresh', and 'update'.

vbDataProviderNotification Event Listener

Page authors can register an event listener of this type in order to be notified of catastrophic errors that may occur when something goes wrong during an implicit fetch. For an externalized fetch, where the fetch is externalized to a action chain, the current mechanism of handling failure outcomes can continue to be used.

For example, on the page, the listeners property can have this definition:

"vbDataProviderNotification": {
  "chains": [
    {
      "chainId": "someChainX"
    }
  ]
}

The event payload available to the listener is an object that has the following properties:

  • severity: a string
  • detail: any details of the error, such as REST failure details
  • capability: an object with the capabilities configured on the ServiceDataProvider
  • fetchParameters: an object with the parameters passed to the fetch
  • context: an object representing the state of the ServiceDataProvider at the time the fetch was initiated
  • id: uniqueId, a string, the id of the ServiceDataProvider instance
  • key: since the event can be fired multiple times, this identifies the event instance

Page authors can use this to display an error message.

Example 11-6 Firing a DataProvider event by using a fireDataProviderEvent action

A page is configured to have a master list and detail form showing the details of the current selected row on the list. Suppose that the form is wired to PATCH to a different endpoint than the one configured on the list. When the user updates the form data, it's desirable for the same actionChain to also raise the 'update' event on the ServiceDataProvider so it can show the changes to the current row. To configure the page:

<!-- list view bound to page variable incidentListTableSource --> 
<oj-list-view id="listview" 
              data="{{$variables.incidentListTableSource}}" 
... 
</oj-list-view> 

<!-- form UI fields bound to page variable currentIncident --> 
<div class="oj-form-layout" 
  <div class="oj-form" 
    <div class="oj-flex" 
      <div class="oj-flex-item" 
        <oj-label for="problem"Problem</oj-label> 
      </div> 
      <div class="oj-flex-item" 
        <oj-input-text id="problem" 
                       value="{{$variables.currentIncident.problem}}" 
                       required=true</oj-input-text> 
      </div>
    </div> 
...

<!-- Save button bound to componentEvent handler 'saveIncident' -->
<oj-button href="#" id='saveButton'
           label='Save' 
           on-dom-click='[[$componentEvents.saveIncident]]'</oj-button>
// saveIncident calls the actionChain 'saveIncidentChain', which
// (1) defines 2 variables - incidentId and incidentPayload
// (2) then calls a REST action to put/patch payload
// (3) then it takes the result from (2) and assigns to incidentsResponse chain 
//     variable,
// (4) calls an actionChain to fire a data provider event to refresh the SDP page
//     variable
// (5) an update event payload passed to the action chain
"saveIncidentChain": {
  "variables": {                                                // (1)
    "incidentId": {
      "type": "string",
      "description": "the ID of the incident to update",
      "input": "fromCaller",
      "required": true
    },
    "incidentPayload": {
      "type": "object",
      "description": "the payload of the incident data",
      "input": "fromCaller",
      "required": true
    },
    "incidentsResponse": {
      "type": "application:incidentsResponse"
    }
  },
  "root": {
    "id": "saveIncidentToRest",                                 // (2)
    "module": "vb/action/builtin/restAction",
    "parameters": {
      "endpoint": "ifixitfast-service/putIncident",
      "uriParams": {
        "id": "{{ $variables.incidentId }}"
      },
      "body": "{{ $variables.incidentPayload }}"
    },
    "outcomes": {
      "success": "assignVariables_incidentsResponse"
    }
  },
  "assignVariables_incidentsResponse": {
    "module": "vb/action/builtin/assignVariablesAction",
    "parameters": {
      "$variables.incidentsResponse.result": {
        "source": "{{ $chain.results.saveIncidentToRest.body }}" // (3)
      }
    },
    "outcomes": {
      "success": "updateIncidentList"
    }
  },
  "updateIncidentList": {
    "module": "vb/action/builtin/callChainAction",
    "parameters": {
      "id": "fireDataProviderMutationEventActionChain",         // (4)
      "params": {
        "payload": {
          "update": {                                           // (5)
            "data": "{{ $variables.incidentsResponse }}"
          }
        }
      }
    }
  }
}
"fireDataProviderMutationEventActionChain": {
  "variables": {
    "payload": {
      "type": "application:dataProviderMutationEventDetail",
      "input": "fromCaller"
    }
  },
  "root": "fireEventOnDataProvider",
  "actions": {
    "fireEventOnDataProvider": {
      "module": "vb/action/builtin/fireDataProviderEventAction",
      "parameters": {
        "target": "{{ $page.variables.incidentListDataProvider }}", // SDP variable 
                                                     // on which the event is fired
        "add": "{{ $variables.payload.add }}",
        "remove": "{{ $variables.payload.remove }}",
        "update": "{{ $variables.payload.update }}" // has the updated record details
      }
    }
  }
},
ServiceDataProviderFactory

Some times it's desirable to create a standalone VB type instance programmatically by passing an initial state. Here the instance is not backed by a variable, that is, its state is not stored in redux. Instead the instance and/or the caller manages its state essentially.For such cases VB publishes a contract for a TypeFactory that any type author can implement. See Custom Extended Types.

The TypeFactory contract is provided in the vb/types/factories/typeFactory.js. VB provides TypeFactory implementations for creating a ServiceDataProvider instance. Refer to the ServiceDataProviderFactory for details. (vb/types/factories/serviceDataProviderFactory.js)

Methods

createInstance

Returns an instance of the ServiceDataProvider. Refer to the JSDocs for the parameters supported on this method. The instance returned supports all methods from the DataProvider contract.

  • options, object used to instantiate the ServiceDataProvider with, usually contains these properties
    • dataProviderOptions, its initial or 'default' state.
      • state properties are same as what a regular ServiceDataProvider variable takes
    • serviceOptions, optional configuration needed by the RestHelper to locate the endpoint details. This can be skipped if the dataProviderOptions includes an 'endpoint' property
      • properties:
        • url <string>
        • operationRef <string>

caller can create an instance as follows:

ServiceDataProviderFactory.createInstance({ dataProviderOptions: { endpoint: "foo/getBars", responseType: "barType[]", keyAttributes: "id"} })
  .then((sdpInstance) => {
    const iter = sdpInstance.fetchFirst();
    iter.next().then((results) => {
      // process results
    });
  });
Multi-Service Data Provider

The vb/MultiServiceDataProvider built-in type is a data provider implementation that combines multiple vb/ServiceDataProvider variables, each providing a unique fetch capability.

Often components that bind to data providers, like oj-combobox-one and oj-select-single (or the -many variants), require or use different 'fetch' capabilities on the data provider implementation.

For example, an oj-select-single component might call fetchFirst() (on the DataProvider implementation) to populate its options, and then call fetchByKeys() to fetch data for selected value, and fetchByOffset() to fetch items from an offset. Often the endpoint configured on a ServiceDataProvider may provide multiple capabilities - for example, most GETAll endpoints for business object REST API services also allow fetching data for specific keys, and from an offset, on the same endpoint. However, on rare occasions authors might require different endpoints to support different fetch capabilities. A MultiServiceDataProvider can be used for this purpose.

Design Time Assumptions

At design time, a service author can identify different endpoints that provide the fetchByKeys and fetchByOffset capabilities, in addition to the current fetch all (fetchFirst capability). When there are different endpoints a page author must pick different endpoints for each (fetch) capability when configuring a variable of a type vb/MultiServiceDataProvider. It is common for the same REST endpoint to support multiple capabilities.

  • for fetchByKeys
    • For example, the same endpoint can fetch all territories and a set of territories that match a set of territory codes (fetchByKeys): GET /fndTerritories? and /fndTerritories?q=TerritoryCode in ('US', 'AE')
    • the same endpoint can be used to fetch all customers, or to fetch customers by specific keys using the same endpoint but different query parameters: GET /customers and GET /customers?ids=cus-101,cus-103.
  • for fetchByOffest
    • an Oracle Cloud application endpoint can fetch all territories, and territories at a given offset - GET /fndTerritories and /fndTerritories?offset=50&size=10

Properties

A variable of the built-in type vb/MultiServiceDataProvider can be configured with the dataProviders property using the following sub-properties.

dataProviders Sub-property Type Example Description
fetchFirst "vb/ServiceDataProvider"
{
  "variables": {
    "activitiesMultiSDP": {
      "type": "vb/MultiServiceDataProvider",
      "defaultValue": {
        "dataProviders": {
          "fetchFirst": "{{ $variables.listSDP }}"
        }
      }
    }
  }
}
A MultiServiceDataProvider is needed only when more than one fetch capability needs to be configured.
fetchByKeys "vb/ServiceDataProvider"
{
  "variables": {
    "activitiesMultiSDP": {
      "type": "vb/MultiServiceDataProvider",
      "defaultValue": {
        "dataProviders": {
          "fetchFirst": "{{ $variables.listSDP }}"
          "fetchByKeys": "{{ $variables.detailSDP }}"       
        }
      }
    }
  }
}
A reference to the vb/ServiceDataProvider variable.
fetchByOffset "vb/ServiceDataProvider"
{
  "variables": {
    "activitiesMultiSDP": {
      "type": "vb/MultiServiceDataProvider",
      "defaultValue": {
        "dataProviders": {
          "fetchFirst": "{{ $variables.listSDP }}"
          "fetchByOffset": "{{ $variables.listSDP }}"       
        }
      }
    }
  }
}
A reference to the vb/ServiceDataProvider variable.

Behavior

  • A variable of type vb/MultiServiceDataProvider must have at least one fetch capability defined. Otherwise an error is flagged.
  • When a fetchFirst capability is not defined, a no-op fetchFirst capability is used. The JET DataProvider contract requires a fetchFirst implementation to be provided.
  • All fetch capabilities must point to a variable of type vb/ServiceDataProvider. 
  • A MultiServiceDataProvider cannot reference another MultiServiceDataProvider variable.

Usage

Here are some of the common ways service endpoints might provide their fetch capabilities.

Usage: When a service provides unique endpoints for different fetch capabilities

When a service has unique endpoints for each fetch capability, we will require one variable of type 'vb/ServiceDataProvider' per fetch API, and a variable of type 'vb/MultiServiceDataProvider' variable that combines the individual ServiceDataProvider variables together. The list-of-values component will then bind to a variable of type vb/MultiServiceDataProvider.

Let's consider this third-party REST API that is used to get information about countries.

  • fetchFirst capability: to get a list of all countries and their info, where the alpha3Code is the primary key

    • service/endpoint: rest-service/getAllCountries

    • GET https://restcountries.eu/rest/v2/all

  • fetchByKeys capability (with multi key lookup): to get a list of countries by their three-letter alpha code
    • service/endpoint: rest-service/getCountriesByCodes

    • GET https://restcountries.eu/rest/v2/alpha?codes=usa;mex

In order for the list-of-values component to use the above endpoints, the design time will need to create three variables:

  • One vb/MultiServiceDataProvider variable that references two ServiceDataProvider variables, one for each fetch capability

  • Two vb/ServiceDataProvider variables

vb/MultiServiceDataProvider Configuration

At design time, a variable using this type will be created that looks like this:

1  {
2    "variables": {
3      "countriesMultiSDP": {
4        "type": "vb/MultiServiceDataProvider",
5        "defaultValue": {
6          "dataProviders": {
7            "fetchFirst": "{{ $page.variables.allCountriesSDP }}
8            "fetchByKeys": "{{ $page.variables.countriesByCodesSDP }}"
9          }
10       }
11     }
12   }
13 }
  • Line 3: countriesMultiSDP is a variable of type vb/MultiServiceDataProvider. This defines two properties: 'fetchFirst' and 'fetchByKeys'.

  • Line 7: The fetchFirst property allows the MultiServiceDataProvider to call fetchFirst() on the referenced ServiceDataProvider variable.

  • Line 8: The fetchByKeys property allows the MultiServiceDataProvider to call fetchByKeys() on the referenced ServiceDataProvider variable.

vb/ServiceDataProvider Variables Configuration

For the above use case, the referenced ServiceDataProvider variables will be configured as follows:

Configuration Description
1  { 
2    "variables": { 
3      "allCountriesSDP": { 
4        "type": "vb/ServiceDataProvider",
5        "defaultValue": {
6          "endpoint": "rest-service/getAllCountries",
7          "keyAttributes": "alpha3Code"
8        }
9      },
10     "countriesByCodesSDP": {...}
11   }
12 }

Line 3: defines the ServiceDataProvider variable with a fetchFirst capability.

  • When a capabilities property is not specified, it's assumed that the ServiceDataProvider supports a fetchFirst capability.

  • When a capabilities property is present but no fetch capability is defined (that is, only the filter and sort capabilities are defined), fetchFirst is assumed.

Line 6: defines the endpoint to use the getAllCountries operation to fetch all countries.

1  { 
2    "variables": { 
3      "allCountriesSDP": { 
4     "countriesByCodesSDP": { 
5        "type": "vb/ServiceDataProvider",
6        "defaultValue": {
7          "endpoint": "rest-service/getCountriesByCodes", 
8          "keyAttributes": "alpha3Code",
9          "capabilities": {
10            "fetchByKeys": {
11             "implementation": "lookup",
12             "multiKeyLookup" : 'no'
13           }  
14         },
15         "mergeTransformOptions": "{{ $functions.fixupTransformOptions }}"
16       }
17     }
18   }

Line 4: defines the ServiceDataProvider variable that supports a fetchByKeys capability.

Line 7: uses the getCountriesByCodes operation to fetch a list of countries by their codes.

Line 9: a 'capabilities' property is added to ServiceDataProvider that has a 'fetchByKeys' property object. See next section for details.

  • 'implementation' property is set to "lookup"

  • 'multiKeyLookup' property set to "no"

Line 15: the 'mergeTransformOptions' property is set to a page function.

  • this is needed so page author can map the keys set programmatically to be turned into the query parameters '?codes='

    Note:

     Normally fetchByKeys() is called by a JET component programmatically with one or more keys.
  • When keys are provided programmatically, ServiceDataProvider will use a best-guess heuristic to map keys to the appropriate transform options. But when this is not easily decipherable by ServiceDataProvider, page authors can use a 'mergeTransformOptions' property that maps to a function, to fix up the list of the 'query' options. This function will be passed in all the info it needs to merge the final transform options.

    Note:

     In this example the keys need to map to the codes uriParameters, and such a mapping cannot be represented in the page model using an expression.
  • When no keys are provided, ServiceDataProvider will throw an error.

1  /**
2   * fix up the query transform options.
3   * When the fetchByKeys capability is set, the 'keys' provided via the fetch call
4   * can be be looked up via configuration.fetchParameters.
5   * This can be used to set a 'codes' property on the 'query' transform options
6   * whose value is the keys provided via a fetch call.
7   *
8   * @param configuration a map of 3 key values, The keys are	
9   *   - fetchParameters: parameters passed to a fetch call
10  *   - capability: 'fetchByKeys' | 'fetchFirst' | 'fetchByOffset'
11  *   - context: the context of the SDP when the fetch was initiated.
12  *
13  * @param transformOptions a map of key values, where the keys are the
14  *        names of the transform functions.
15  * @returns {*}
16  */
17 PageModule.prototype.fixupTransformOptions =
18     function (configuration, transformOptions) {
19   var c = configuration;
20   var to = transformOptions;
21   var fbkCap = !!(c && c.capability === 'fetchByKeys');
22   var keysToFetch = fbkCap ?
23                      (c && c.fetchParameters && c.fetchParameters.keys) : null
24 
25   if (fbkCap && keysToFetch && keysToFetch.length > 0) {
26     // join keys
27     var keysToFetchStr = keysToFetch.join(';');
28     to = to || {};
29     to.query = to.query || {};
30 
31     // ignore codes set on the query options and instead use ones passed in
32     // by fetchByKeys call
33     to.query.codes = keysToFetchStr;
34   }
35 
36   return to;
37 };

Line 17: function that fixes up the transform options that will be sent to the transform functions.

Line 33: set a new 'codes' query parameter, whose value is a ';' separated list of country alpha codes.

Configuring a JET Combo/Select at Design Time

To configure a list-of-values field that uses the above, the design time needs to create three variables:
  • One vb/MultiServiceDataProvider variable

  • Two vb/ServiceDataProvider variables

The MultiServiceDataProvider variables are bound to the combo/select components as follows.
  • Line 2 points to a variable of type vb/MultiServiceDataProvider.

1  <oj-combobox-one id="so11" value="{{ $variables.selectedActivities }}"
2                         options="[[ $variables.countriesMultiSDP ]]"
3                          options-keys.label='[[ "name" ]]'
4                         options-keys.value='[[ "alpha3Code" ]]'
5  </oj-combobox-one>

A distinct vb/ServiceDataProvider variable is needed for each unique service/endpoint. Often authors want to provide different default filterCriterion, sortCriteria or uriParams, or even write different transforms for each capability. Isolating each capability to a unique ServiceDataProvider variable allows for this separation.

Any individual vb/ServiceDataProvider variables might externalize its fetch, or allow an actionChain to assign values to its properties directly via expressions. They can also allow a fireDataProviderEventAction to reference the Service Data Provider variable directly. First class variables are the easiest way to give page authors access.

Usage: When a service provides unique endpoints for different fetch capabilities, but the fetchByKeys endpoint only supports a single-key-based lookup

In this use case, the service supports a fetchFirst capability that fetches all rows, and a fetchByKeys capability that returns a single row by its key. There is no endpoint that can return rows by multiple keys.

To understand this usecase further let's take the example of the sample ifixitfast service - and the incidents endpoints that is used to get information about incidents.

  • fetchFirst capability: to get a list of all incidents for the selected technician,
    • service/endpoint: fixitfast-service/getIncidents
    • GET https://.../ifixitfaster/api/incidents?technician=hcr
  • fetchByKeys capability (with single key lookup): to get a single incident it its 'id'
    • service/endpoint: fixitfast-service/getIncident
    • GET https://.../ifixitfaster/api/incidents/inc-101

In order for the list-of-values component to use the above endpoints, the design time will need to create three variables:

  • One vb/MultiServiceDataProvider variable that references two ServiceDataProvider variables, one for each fetch capability
  • Two vb/ServiceDataProvider variables

vb/MultiServiceDataProvider Variable Configuration

The configuration for the vb/MultiServiceDataProvider variable is similar to the previous examples.

1  {
2    "variables": {
3       "countriesMultiSDP": {
4         "type": "vb/MultiServiceDataProvider",
5         "defaultValue": {
6          "dataProviders": {
7             "fetchFirst": "{{ $page.variables.allIncidentsSDP }}"
8              "fetchByKeys": "{{ $page.variables.incidentBySingleKeySDP }}"
9          }
10       }
11      }
12    }
13  }

vb/ServiceDataProvider Variables Configuration

For the previous use case, the referenced ServiceDataProvider variables will be configured as follows.

Configuration Description

some-page.json

1   {
2       "variables": {
3        "allIncidentsSDP": {
4           "type": "vb/ServiceDataProvider",
5           "defaultValue": {
6              "endpoint": "fixitfast-service/getAllIncidents",
7              "keyAttributes": "id",
8              "itemsPath": "result",
9              "uriParameters": {
10                "technician": "{{ $application.user.userId }}"
11             }
12          }
13       },
14       "incidentBySingleKeySDP": {...}
15    }
16  }
  • Line 3: defines the ServiceDataProvider variable with the fetchFirst capability.

  • Line 6: defines the endpoint that uses the getAllIncidents operation to fetch all incidents.

1  {
2    "variables": {
3      "allIncidentsSDP": {...},
4      "incidentBySingleKeySDP": {
5        "type": "vb/ServiceDataProvider",
6        "defaultValue": {
7          "endpoint": "fixitfast-service/getIncident",
8          "keyAttributes": "id",
9          "uriParameters": {
10           "id": "{{ $variables.incidentId }}"
11         }
12         "capabilities": {
13           "fetchByKeys": {
14             "implementation": "lookup",
15             "multiKeyLookup" : 'no'
16           }         
17         }
18       }
19     }
20   }

Line 4: defines the ServiceDataProvider variable with the fetchByKeys capability. The ServiceDataProvider variable is configured for an implicit fetch.

Line 7: uses the getIncident operation to fetch a single incident by its id.

Line 9: maps the 'id' key in the 'uriParameters'.

  • At runtime the 'id' key value is substituted in the path parameter of the URL.

  • For example, if the 'id' value is "inc-101", the request URL goes from https://.../incidents/{id} → http://.../incidents/inc-101

Line 12: a new 'capabilities' property is added to ServiceDataProvider that has a 'fetchByKeys' key object.

  • The 'implementation' property is set to "lookup".

  • The 'multiKeyLookup' property is set to "no", as the endpoint only supports lookup using a single key at a time.

Notice that a 'mergeTransformOptions' property is not set.

  • This is because Service Data Provider uses a simple heuristic to map the 'keys' provided programmatically to the 'id' sub-property of the 'uriParameters'.

    • It can do this because ServiceDataProvider sees that the keyAttributes value "id" is the same attribute key set on 'uriParameters'.

    • Also, this is only possible when ServiceDataProvider is configured to use implicit fetch (that is, it does not use an external action chain to do a fetch).

  • In some cases the ServiceDataProvider cannot easily decipher the mapping (as seen in the previous example), and this is when page authors can use a 'mergeTransformOptions' property to map the keys to the right transform options.

  • When multiple keys are provided by the caller, ServiceDataProvider as an optimization calls the single endpoint a single key at a time, assembles the result, and returns this to caller.

1  {
2    "variables": {
3      "allIncidentsSDP": {...},
4      "incidentBySingleKeySDP_External": {
5        "type": "vb/ServiceDataProvider",
6        "defaultValue": {
7          "fetchChainId": "fetchSingleIncidentChain",
8          "keyAttributes": "id",
9          "mergeTransformOptions": "{{ $page.functions.fixupTransformOptions }}",
10         "capabilities": {
11           "fetchByKeys": {
12           "implementation": "lookup",
13           "multiKeyLookup": "no"
14         }
15       }
16     }
17   },
18   "chains": {}
19 }

Line 4: defines the ServiceDataProvider variable with a fetchByKeys capability.

  • The Service Data Provider variable uses an action chain to fetch data. See the next section for the action chain configuration.

Line 9: sets a mergeTransformOptions function.

  • This function is used by the page author to fix up the 'query' transform options to use the key passed in via the fetch call.

/**
 * Process the transform options.
 * When ServiceDataProvider uses external fetch chain, it doesn't 
 * have all the information to build the final transform options
 * to use with the transform functions. In such cases the page 
 * author can use this method to build the final list of options. 
 * Replaces id set via configuration with the value passed in by caller.
 *
 * @param configuration an Object with the following properties
 *   - capability: 'fetchByKeys' | 'fetchFirst' | 'fetchByOffset'
 *   - fetchParameters: parameters passed to the fetch call
 *   - context: the context of the Service Data Provider variable at 
 *              the time the fetch call was made
 *
 * @param transformOptions a map of key values, where the keys are the
 *   names of the transform functions.
 *
 * @returns {*} the transformOptions either the same one passed in or 
 *   the final fixed up transform options
 */
PageModule.prototype.fixupTransformOptions = function (configuration, transformOptions) {
  var c = configuration;
  var to = transformOptions || {};
  var fetchByKeys = !!(c && c.capability === 'fetchByKeys');
 
  if (fetchByKeys) {
    var key = c.fetchParameters.keys[0];
    if (key &&
        (!to.query || (to.query && to.query.id !== c.fetchParameters.keys[0]))) {
      to.query = to.query || {};
      to.query.id = key;
    }
  }
  return to;
};

mergeTransformOptions function

1  {
2    "variables": {},
3    "chains": {
4      "fetchSingleIncidentChain": {
5        "variables": {
6          "configuration": {
7            "type": {
8              "hookHandler": "vb/RestHookHandler"
9            },
10           "description": "the configuration for the rest action",
11           "input": "fromCaller",
12           "required": true
13         },
14         "uriParameters": {
15           "type": "object",
16           "defaultValue": {
17             "id": "{{ $page.variables.incidentId }}"
18           }
19         }
20       },
21       "root": "fetchSingleIncidentAction",
22       "actions": {
23         "fetchSingleIncidentAction": {
24           "module": "vb/action/builtin/restAction",
25           "parameters": {
26             "endpoint": "fixitfast-service/getIncident",
27             "hookHandler": "{{ $variables.configuration.hookHandler }}",
28             "uriParams": "{{ $variables.uriParameters }}",
29             "responseType": "flow:incident",
30             "requestTransformFunctions": {
31               "query": "{{ $page.functions.queryIncidentById }}"
32             }
33           },
34           "outcomes": {
35             "success": "returnIncidentResponse",
36             "failure": "returnFailureResponse"
37           }
38         },
39       }
40     }
41   }
42 }

The external fetch action chain is configured as follows.

Line 4: the action chain used by the ServiceDataProvider.

Line 23: the RestAction, the chain calls to fetch a single incident by id.

Line 28: the 'uriParams' property of the RestAction is set to the page variable "incidentId".

  • The value of the "incidentId" variable might be different from what the caller passes in.

  • The mergeTransformOptions function above builds the query options containing the final id value.

Line 31: the requestTransformFunction.query maps to a query transform function that substitutes the endpoint URL with the final id value.

/**
 * query transform function that takes the id provided in the options 
 *   and expands the URL.
 * @param configuration
 * @param options
 * @returns {*}
 */
PageModule.prototype.queryIncidentById = function (configuration, options) {
  const c = configuration;
  if (options && options.id) {
    var result = URI.expand(c.endpointDefinition.url, { id: options.id });
    var newUrl = result.toString();
    if (newUrl !== c.url) {
      console.log(`typesDemo sample: replacing ${c.url} with ${newUrl}`);
    }
    c.url = newUrl;
  }
  return c;
};
Query transform function

Usage: When the same endpoint supports multiple fetch capabilities

Most list-of-value objects fall into this category. For example, to fetch both a list of territories and to fetch a subset of territories by their ids, the same endpoint is used:

  • fetchFirst capability: 

    • service/endpoint: fa-crm-service/getTerritories

    • GET /fndTerritories?finder=EnabledFlagFinder;BindEnabledFlag=Y

  • fetchByKeys capability: 

    • GET /fndTerritories?finder=EnabledFlagFinder;BindEnabledFlag=Y&q=TerritoryCode IN ('AE', 'AD', 'US')

In this case, a single ServiceDataProvider variable of type vb/ServiceDataProvider that multiplexes different fetch capabilities is the recommended approach. The ServiceDataProvider variable can then be used to bind to the list-of-values component.

Note:

It is recommended that service authors ensure that the service is configured to use the default business object REST API transforms.

vb/ServiceDataProvider Variables Configuration

The data returned by the service endpoint will look something like this:

{
  "items": [
    {
      "TerritoryCode": "AE",
      "AlternateTerritoryCode": "ar-AE",
      "TerritoryShortName": "United Arab Emirates",
      "CurrencyCode": "AED"
    },
    ...
  ],
  "count": 25,
  "hasMore": false,
  "limit": 25,
  "offset": 0,
}

The ServiceDataProvider variables for the fetchFirst and fetchByKeys capabilities will be configured as follows

sample-page.html Description
"territoriesSDPVar": {
  "type": "vb/ServiceDataProvider",
  "defaultValue: {
    "endpoint": "fa-crm-service/getTerritories",
    "keyAttributes": "TerritoryCode",
    "itemsPath": "items",
    "uriParameters": {
      "finder": "EnabledFlagFinder;BindEnabledFlag=Y"
    }
  }
}

A finder query parameter is applied to all queries going against the endpoint.

When no capabilities are set, the ServiceDataProvider variable is assumed to support a fetchFirst capability

Configuring a JET Select-Single in Design Time

  • Line 1: the value is bound to a variable that is an array of selected TerritoryCode keys.

  • Line 2: the data attribute is bound to the ServiceDataProvider variable.

1 <oj-select-single id="so11" value="{{ $variables.selectedTerritories }}" 
2              data="[[ $variables.territoriesSDPVar ]]" 
3              item-text='[[ "TerritoryShortName" ]]' 
4 </oj-select-single>

Usage: When a service provides a fetchByKeys capability, and DataProvider.containsKeys is called

The containsKeys() method can be called by components bound to a ServiceDataProvider variable that supports the 'fetchByKeys' capability. The default implementation of containsKeys() will call fetchByKeys() and return a oj.ContainsKeysResult object, as defined by the JET DataProvider contract. This implementation addresses the most common usecase.

MultiServiceDataProviderFactory

Some times it's desirable to create a standalone VB type instance programmatically by passing an initial state. Here the instance is not backed by a variable, that is, its state is not stored in redux. Instead the instance and/or the caller manages its state essentially. For such cases VB publishes a contract for a TypeFactory that any type author can use. See Custom Extended Types.

The TypeFactory contract is provided in the vb/types/factories/typeFactory.js. VB provides TypeFactory implementations for creating a ServiceDataProvider instance. Refer to the MultiServiceDataProviderFactory for details. (vb/types/factories/multiServiceDataProviderFactory.js)

Methods

createInstance

Returns an instance of the MultiServiceDataProvider. Refer to the JSDocs for the parameters supported on this method. The instance returned supports all methods from the DataProvider contract.

  • options, object used to instantiate the ServiceDataProvider, usually contains these properties:
    • dataProviderOptions, its initial or 'default' state.
      • state properties are similar to the properties a regular MultiServiceDataProvider variable
  • serviceOptions, optional configuration needed by the RestHelper to locate the endpoint details.

Here is an example of how a caller can create an instance

Example 11-7 Create SDP

// create SDP
ServiceDataProviderFactory.createInstance({ dataProviderOptions: { endpoint: "foo/getBars", responseType: "barType[]", keyAttributes: "id"} })
  .then((sdpInstance) => {
    // use SDP to create MDP instance
    MultiDataProviderFactory.createInstance({ dataProviderOptions: { dataProviders: { fetchFirst: sdpInstance  } } })
    .then((mdpInstance) => {
      const iter = mdpInstance.fetchFirst();
      iter.next().then((results) => {
        // process results
      });
    });
  });
Array Data Provider 2

Like the legacy Array Data Provider, the built-in Array Data Provider 2 can be bound to collection components.

Like ArrayDataProvider, this built-in type is a data provider implementation where the data is available as an array. All the data is set once, and the data itself can fetched from a backend service (say a list of countries), but it is assumed that array once created is static, that is, data changes infrequently or has limited and infrequent adds, updates and removes done to it.

The vb/ArrayDataProvider2 can be bound to collection components such as listView and table components. Operations on the data, such as sorts, adds, removes, and updates, are managed by the vb/ArrayDataProvider2 itself. This is different from the vb/ServiceDataProvider, where all operations generally are processed in the back end via REST calls.

ArrayDataProvider2 behaves differently from the legacy ArrayDataProvider in the following ways:

  • Writes to individual properties of the ArrayDataProvider2.data are NOT allowed, and users will see an error when this occurs. Usually this happens when components use writable binding expressions that write directly to properties within individual data (array) items.

  • ArrayDataProvider2 SUPPORTS using the fireDataProviderEventAction to mutate data, in addition to the assignVariablesAction.

  • ArrayDataProvider2 tracks mutations to data made using fireDataProviderEventAction and notifies listeners (that is, components) of just the changes. This has the benefit of only updating the necessary parts of the UI.

A variable of this type is generally defined on the page, using the built-in type vb/ArrayDataProvider2.

{
  "pageModelVersion": "20.10.0",
 
  "variables": {
    "productListADPA": {
      "type": "vb/ArrayDataProvider2",
      "defaultValue": {
        "itemType": "application:productSummary",
        "keyAttributes": "id"
      }
    }
  }
  ...

ArrayDataProvider2 has several properties available.

data

The static array of data that the ArrayDataProvider2 wraps. The data property is set once when the page loads. The implicitSort criteria that the data is pre-sorted with is also set once the page loads.

keyAttributes

A string or array of string field names that represent the primary key for each row. Can be one of:

  • a field name - the key value is a primitive or whatever the field value represents.

  • an array of field names - the key will also be an array of values. For example, for keyAttributes: ['id'], when data is [{id: 'ie', name: "IE"}, {id: 'chrome', name: "Chrome"}], the corresponding keys will be [['ie'], ['chrome']]

  • @value, use all properties - the key will also be an array of all values.

  • @index, use the index as the key - the key will be an integer.

implicitSort

The implicit sort criteria by which the data is pre-sorted. This is an array of objects, where each object is an atomic sort expression of the form:

{
  "attribute": "<name of the field>", 
  "direction": "<'ascending' (default) or 'descending'>" 
}

itemType

The type of each item in the data array. This is usually a string that points to an application type or to a definition.

sortComparators

An optional object with a 'comparators' property that is either an array of arrays where each inner array has 2 items - name of the attribute that the sortCriteria applies to, and a comparator function callback that is used by ADP to sort the attribute (column), or is a Map of attribute to comparator function. This API is similar to the JET SortComparator API.

Here are some examples of configuration for array or arrays.

"sortComparators": {
  "comparators": [
    [
      "Category", "{{ $page.functions.alphaSort }}"
    ],
    [
      "Product", "{{ $page.functions.alphaSort }}"
    ]
  ]
}

Using a Map:

sortComparators: {
  comparators: "{{ new Map([['name', $page.functions.alphaSort]]) }}",
}

The comparator function will look like this:

var alphaSort = function (a, b) {
  return a.localeCompare(b);
}

textFilterAttributes

An array of attributes to filter on. See the JET documentation for ArrayDataProvider textFilterAttributes.

"customerListADP": {
  "type": "vb/ArrayDataProvider2",
  "defaultValue": {
    "keyAttributes": "id",
    "itemType": "flow:customer",
    "textFilterAttributes": [
      "lastName", "firstName"
    ]
  }
}

Features and Capabilities

ArrayDataProvider2 supports the same capabilities as the legacy ArrayDataProvider:

sort

  • {capabilityName: 'full', attributes: 'multiple} means the endpoint has support for sorting results by one or more fields.

  • null means the endpoint has no support for sorting.

Data Mutation and Refresh Events

vb/ArrayDataProvider2 notifies components when the underlying data mutates or is changed in a way that requires a refresh. The events currently supported by any iterating data providers are the 'mutate' ('add', 'remove' and 'update') event and 'refresh'. See Assigning Data for details.

Variable Events

All variables including vb/ArrayDataProvider2 raise the variable onValueChanged event when any of its properties change. ArrayDataProvider2 in particular will detect which of its data has changed, and will automatically notify subscribers of just the change (these are typically components that are bound to the ArrayDataProvider2 variable and have registered a listener).

Assigning Data

The data property of the vb/ArrayDataProvider2 variable is set once, when the page or component loads. The implicitSort criteria that the data is pre-sorted with is also set once the page or component loads.

After the initial load, a page author can mutate the data either by directly manipulating the data array using the 'assignVariablesAction' action or by using the 'fireDataProviderEventAction'.

Using a fireDataProviderEventAction, authors can mutate data property, and also notify components in one shot. When the mutation events 'add', 'remove' and 'update' are called the vb/ArrayDataProvider2 implementation will automatically mutate the underlying data, so users are not required to mutate the ArrayDataProvider2.data prior to raising this event, say, using an assignVariablesAction. This is a convenience offered only by the vb/ArrayDataProvider2 implementation, not by vb/ArrayDataProvider. See Fire Data Provider Event Action for details.

Often the mutation to the data is triggered by the UI or some other app logic, which might require the use of assignVariablesAction. This is another way to update the ArrayDataProvider2.data, in which case It's not required to use the fireDataProviderEventAction. See Assign Variables Action for details.

Example 11-8 Where the data is literally inlined

In this example, the ArrayDataProvider2 variable productsADPB has its initial data inlined.

"variables": {
  "productsADPB": {
    "type": "vb/ArrayDataProvider2",
    "description": "mutations are done on 'data' property using assignVariables",
    "defaultValue": {
      "itemType": "ProductType",
      "keyAttributes": "id",
      "data": [{
        "Amount": 30,
        "CurrencyCode": "USD",
        "Quantity": 3,
        "RegisteredPrice": 30,
        "Type": "Literal",
        "Product": "Product-Literal",
        "id": 30
      }]
    }
  }
}

To remove an item from the above ArrayDataProvider2 data you can use an assignVariablesAction.

  • Line 16: filters the data array of productsADPB by removing the item with the matching key

1  "removeProductsADPB": {
2    "root": "removeFromProductsADPB",
3    "description": "",
4    "variables": {
5      "key": {
6        "type": "number",
7        "required": true,
8        "input": "fromCaller"
9      }
10   },
11   "actions": {
12     "removeFromProductsADPB": {
13       "module": "vb/action/builtin/assignVariablesAction",
14       "description": "splice returns the removed item, so filter is used instead, which mutates and returns the original array",
15       "parameters": {
16         "$page.variables.productsADPB.data": {
17           "source": "{{ $page.variables.productsADPB.data.filter((p) => p.id !== $chain.variables.key) }}",
18           "reset": "empty",
19           "auto": "always"
20         }
21       }
22     }
23   }
24 }

When the data is inlined or is assigned from a vbEnter action chain, you can add or update items to the array using the assignVariablesAction.

  • Line 1: shows an example action where the product is updated directly

  • Line 12: shows an example action where the new product is added to the tail end of the data array

1  "updateProductsADPB": {
2    "module": "vb/action/builtin/assignVariablesAction",
3    "description": "directly updating ADP2.data item is possible when data has no expression",
4    "parameters": {
5      "$page.variables.productsADPB.data[$page.variables.productsADPB.data.findIndex(p => p.id === $chain.variables.key)]": {
6        "source": "{{ $chain.variables.product }}",
7        "auto": "always",
8        "reset": "empty"
9      }
10   }
11 }
12 "addToProductsADPBTail": {
13   "module": "vb/action/builtin/assignVariablesAction",
14   "parameters": {
15   "$page.variables.productsADPB.data[$page.variables.productsADPB.data.length]": {
16       "source": "{{ $chain.results.generateNewProduct }}"
17     }
18   }
19 }

Example 11-9 Where the productsADPC is updated via a fireDataProviderEventAction

In this example, productsADPC has its data coming from another variable.

"productsADPC": {
  "type": "vb/ArrayDataProvider2",
  "description": "mutations on data can be done on the referenced 'products' or on "
    + "the 'data' property directly. The latter will disconnect the reference",
  "defaultValue": {
    "data": "{{ $page.variables.products }}",
    "itemType": "ProductType",
    "keyAttributes": "id"
  }
}

To update a specific product, you can use the fireDataProviderEventAction to set the target, data and keys properties.

  • Line 28: set the event payload using the fireDataProviderEventAction

1  "updateProductsADPC": {
2    "root": "updateProduct",
3    "description": "updates productsADPC using data provider mutation event",
4    "variables": {
5      "product": {
6        "type": "page:ProductType",
7        "required": false,
8        "input": "fromCaller"
9      }
10   },
11   "actions": {
12     "updateProduct": {
13       "module": "vb/action/builtin/assignVariablesAction",
14       "parameters": {
15         "$chain.variables.product": {
16           "source": {
17             "Amount": "{{ $chain.variables.product.Amount * (1+Math.floor(Math.random() * Math.floor(5))) }}",
18             "Quantity": "{{ $chain.variables.product.Quantity * (1+Math.floor(Math.random() * Math.floor(5))) }}"
19           },
20           "reset": "none",
21           "auto": "always"
22         }
23       },
24       "outcomes": {
25         "success": "fireEventProductsADPC"
26       }
27     },
28     "fireEventProductsADPC": {
29       "module": "vb/action/builtin/fireDataProviderEventAction",
30       "parameters": {
31         "target": "{{ $page.variables.productsADPC }}",
32         "update": {
33           "keys": "{{ [ $chain.variables.product.id ] }}",
34           "data": "{{ [ $chain.variables.product ] }}"
35         }
36       }
37     }
38   }
39 },
Array Data Provider (Legacy)

The built-in legacy array data provider could be bound to collection components in previous versions. It should not be used in new applications.

This legacy built-in type is a data provider implementation based on the JET oj.ArrayDataProvider implementation, where the data is static. A static source of data can be fetched from a backend service, but it is assumed that it does not change frequently and only allows infrequent adds/updates and removes. This data provider can be bound to collection components such as listView and table components. Operations on the data, such as sorts, adds, removes, or updates are managed by the vb/ArrayDataProvider itself. This is different from the vb/ServiceDataProvider, where all operations generally are processed in the back end via REST calls.

New applications should use vb/ArrayDataProvider2.

The ArrayDataProvider behaves as follows:

  • Writes to individual properties of the ArrayDataProvider.data are allowed. Usually this happens when components use writable binding expressions that write directly to properties within individual data (array) items.

    Note:

    It's important to remember that when you use a writable binding expression, the component writes the new value to the bound ADP.data property. This causes the ADP variable to change and the table or listview component bound to the ADP variable to refresh. If this behavior is not desired, use vb/ArrayDataProvider2 and the proper editable table / list-view patterns. (The recommended patterns are documented in the Oracle blogs.)
  • ArrayDataProvider does not support using the fireDataProviderEventAction to mutate data. Instead, use the assignVariablesAction.

A variable of this type is generally defined on the page, using the built-in type vb/ArrayDataProvider.

{
  "pageModelVersion": "0.9.9",
 
  "variables": {
    "productListADPD": {
      "type": "vb/ArrayDataProvider",
      "defaultValue": {
        "itemType": "application:productSummary"
      }
    }
  }
  ...

The ArrayDataProvider has several properties available.

data

The static array of data that the ArrayData Provider wraps. The data property is set once when the page or component loads. The implicitSort criteria that the data is pre-sorted with is also set once the page or component loads.

idAttribute

A string or array of string field names that represent the primary key for each row. Deprecated: use keyAttributes instead.

keyAttributes

A string or array of string field names that represent the primary key for each row.

  • a field name - the key value is a primitive or whatever the field value represents.

  • an array of field names - the key will also be an array of values.

  • @value, use all properties - the key will also be an array of all values.

  • @index, use the index as the key - the key will be an integer.

implicitSort

The implicit sort criteria by which the data is pre-sorted. This is an array of objects, where each object is an atomic sort expression of the form:

{
  "attribute": "<name of the field>", 
  "direction": "<'ascending' (default) or 'descending'>" 
}

itemType

The type of each item in the data array. This is usually a string that points to an application type or to a definition.

Features and Capabilities

The ArrayDataProvider provides a sort feature:

  • {capabilityName: 'full', attributes: 'multiple} means the endpoint has support for sorting results by one or more fields.

  • null means the endpoint has no support for sorting.

Data Mutation and Refresh Events

vb/ArrayDataProvider notifies components when the underlying data mutates or is changed in a way that requires a refresh. The only way to mutate ArrayDataProvider data is via the 'assignVariablesAction' event. The 'fireDataProviderEventAction' is a no-op when it comes to updating the data property but can be used to notify just the listeners of the ArrayDataProvider (components) of the change. But the latter is not needed when assignVariablesAction is used, because it does both.

Variable Events

All variables including vb/ArrayDataProvider raise the variable onValueChanged event when any of its properties change. ArrayDataProvider in particular will detect which of its data has changed, and will automatically notify subscribers of just the change (these are typically components that are bound to the ArrayDataProvider variable and have registered a listener).

Assigning Data

The data property of the vb/ArrayDataProvider variable is set once, when the page or component loads. The implicitSort criteria that the data is pre-sorted with is also set once the page or component loads.

After the initial load, a page author can mutate the data by directly manipulating the data array using the assignVariablesAction action. Typically, the mutation to the data is triggered by the UI or some other application logic. In either circumstance, the ArrayDataProvider data needs to be manually updated. When the data property mutates, ArrayDataProvider automatically detects the change and notifies all listeners/components of the change, so that they can re-render. If the data is mutated directly, it's not required to use the fireDataProviderEvent action with the ArrayDataProvider.

Example 11-10 Where the data refers to a constant

Here the ArrayDataProvider variable productADPE gets its initial data from a constant, productsConstant. The ArrayDataProvider data array is initialized with one item.

"constants": {
  "productsConstant": {
    "type": "ProductType[]",
    "defaultValue": [{
      "Amount": 10,
      "CurrencyCode": "USD",
      "Quantity": 1,
      "RegisteredPrice": 10,
      "Type": "Constant",
      "Product": "Product-C1",
      "id": 10
    }]
  }
},
"productsADPE": {
  "type": "vb/ArrayDataProvider",
  "description": "mutations on data have to be done directly to the 'data' property",
  "defaultValue": {
    "data": "{{ $page.constants.productsConstant }}",
    "itemType": "ProductType",
    "keyAttributes": "id"
  }
},

In order to add a new item to the above ArrayDataProvider data you can use an assignVariablesAction:

  • Line 12: action that generates a new product item
  • Line 22: assigns a new array with the new item appended to the existing data

It is currently not possible to add to a specific index of the array using assignVariablesAction, when the array references a constants expression.

 1 "addProductsADPE": {
 2  "description": "adds the generated product to the end",
 3  "variables": {
 4    "detail": {
 5      "required": true,
 6      "type": "any",
 7      "input": "fromCaller"
 8    }
 9  },
10  "root": "generateNewProduct",
11  "actions": {
12    "generateNewProduct": {
13      "module": "vb/action/builtin/callModuleFunctionAction",
14      "parameters": {
15        "module": "{{ $page.functions }}",
16        "functionName": "generateNewProduct"
17      },
18      "outcomes": {
19        "success": "assignToADPData"
20      }
21    },
22    "assignToADPData": {
23      "module": "vb/action/builtin/assignVariablesAction",
24      "parameters": {
25        "$page.variables.productsADPE.data": {
26          "source": "{{ $page.variables.productsADPE.data.concat([$chain.results.generateNewProduct]) }}",
27          "reset": "empty"
28        }
29      }
30    }
31  }
32 }

Example 11-11 Where the data refers to another variable

In this example the ArrayDataProvider variable productADPF gets its initial data from the variable products. The ArrayDataProvider data array is initialized with one item.

"variables": {
  "products": {
    "type": "ProductType[]",
    "defaultValue": [{
      "Amount": 20,
      "CurrencyCode": "USD",
      "Quantity": 2,
      "RegisteredPrice": 20,
      "Type": "Variable",
      "Product": "Product-V1",
      "id": 20
    }]
  },
  "productsADPF": {
    "type": "vb/ArrayDataProvider",
    "description": "mutations on data can be done on the referenced 'products' or "
     + "on the 'data' property directly. The latter will disconnect the reference",
    "defaultValue": {
      "data": "{{ $page.variables.products }}",
      "itemType": "ProductType",
      "keyAttributes": "id"
    }
  },

In order to update an item of the above ArrayDataProvider data, you can use an assignVariablesAction:

  • Line 5: the action chain gets the updated product item
  • Line 22: assign a new array to productsADPF with the updated product
 1 "updateProductsADPF": {
 2  "root": "assignToADPData",
 3  "description": "",
 4  "variables": {
 5    "updatedProduct": {
 6      "type": "page:ProductType",
 7      "required": true,
 8      "input": "fromCaller"
 9    },
10    "key": {
11      "type": "number",
12      "required": true,
13      "input": "fromCaller"
14    }
15  },
16  "actions": {
17    "assignToADPData": {
18      "module": "vb/action/builtin/assignVariablesAction",
19      "description": "assigning to specific item in ADP.data is not possible, so we replace entire array",
20      "parameters": {
21        "$page.variables.productsADPF.data": {
22          "source": "{{ $page.variables.productsADPF.data.map(p => (p.id === $chain.variables.key ? $chain.variables.updatedProduct : p)) }}",
23          "reset": "empty"
24        }
25      }
26    }
27  }
28}

Example 11-12 Where the data is literally inlined

In this example the ArrayDataProvider variable productADPG has its initial data inlined.

"variables": {
  "productsADPG": {
    "type": "vb/ArrayDataProvider",
    "description": "any mutations are done on 'data' property directly",
    "defaultValue": {
      "itemType": "ProductType",
      "keyAttributes": "id",
      "data": [{
        "Amount": 30,
        "CurrencyCode": "USD",
        "Quantity": 3,
        "RegisteredPrice": 30,
        "Type": "Literal",
        "Product": "Product-Literal",
        "id": 30
      }]
    }
  }
}

In order to remove an item from the above ArrayDataProvider data you can use an assignVariablesAction. Line 16 filters the data array of productsADPG by removing the item with the matching key.

 1 "removeProductsADPG": {
 2   "root": "removeFromProductsADPG",
 3   "description": "",
 4   "variables": {
 5     "key": {
 6       "type": "number",
 7       "required": true,
 8       "input": "fromCaller"
 9     }
10   },
11   "actions": {
12     "removeFromProductsADPG": {
13       "module": "vb/action/builtin/assignVariablesAction",
14       "description": "splice returns the removed item, so filter is used instead, which mutates and returns the original array",
15       "parameters": {
16         "$page.variables.productsADPG.data": {
17           "source": "{{ $page.variables.productsADPG.data.filter((p) => p.id !== $chain.variables.key) }}",
18           "reset": "empty",
19           "auto": "always"
20         }
21       }
22     }
23   }
24 }

When the data property is a literal value, to add or update items to the array it is possible to assign to a specific item of the array:

  • Line 1: shows an example action where the product is updated directly
  • Line 12: shows an example action where the new product is added to the tail end of the data array
 1 "updateProductsADPG": {
 2   "module": "vb/action/builtin/assignVariablesAction",
 3   "description": "directly updating ADP.data item is possible when data has no expression",
 4   "parameters": {
 5     "$page.variables.productsADPG.data[$page.variables.productsADP3.data.findIndex(p => p.id === $chain.variables.key)]": {
 6       "source": "{{ $chain.variables.product }}",
 7       "auto": "always",
 8       "reset": "empty"
 9     }
10   }
11 }
12 "addToProductsADPGTail": {
13   "module": "vb/action/builtin/assignVariablesAction",
14   "parameters": {
15   "$page.variables.productsADPG.data[$page.variables.productsADPG.data.length]": {
16       "source": "{{ $chain.results.generateNewProduct }}"
17     }
18   }
19 }

Custom Extended Types

Page authors can implement a Visual Builder type class using either the Extended Type mechanism (that extends from the vb/types/extendedType class module) or use the Instance Factory mechanism. The latter is much simpler to use since authors can simply plug their type into a Visual Builder variable without writing any extra JavaScript code (which was needed with the Extended Type system).

At runtime the instance of the custom type class can automatically make use of the redux framework to store its 'value' (state). Visual Builder variables generally have a type that points to a class or a type definition or can be a JavaScript primitive or an object. The Visual Builder runtime discovers built-in types and custom types by detecting a forward slash in the type name (for example, my/ComicStripType). The type is assumed to be a require path to a type module and loads it.

An example:

"myVariable": {
  "type": "my/ComicStripType",
  "defaultValue": {}
}

Reserved Properties

value

The state of an extended type is generally referred to as its value and its default value can be specified using the 'defaultValue' property of a variable. For example, the comicStripType specifies its default value, an Object, by providing defaults for 'name', 'publicationType' etc. Also note that charactersADP is a reference to a variable of type vb/ArrayDataProvider2.

The type of the value is defined via the 'getTypeDefinition' function (see below). In this example, this would be the properties in the defaultValue object: name, publicationType, publications, etc.

In order to make the value accessible in expressions via '<$scope>.variables.comicStripVar.value' where $scope is $page/$flow etc., and 'comicStripVar' is the type instance of the custom type that is created, 'value' is a special property defined on the extended type instance and for this reason, will overlay any local 'value' property defined in your implementation. For this reason, take care not to use this property internally! Property accessors to read (see getValue() method) and write (see setValue() method) the value are provided.

"comicStripVar": {
  "type": "vb/sample/types/comicStripType",
  "defaultValue": {
    "name": "flowPage-Calvin & Hobbes",
    "publicationType": "flowPagePublicationType",
    "publications": [
      {
        "publication": "Universal Press Syndicate",
        "volumes": 24,
        "author": "Bill Watterson",
        "title": "The Doghouse",
        "year": 1987,
        "launchDate": "1985-11-18T08:00:00.000Z"
      },
      {
        "publication": "United Feature Syndicate",
        "volumes": 250,
        "author": "Bill Watterson",
        "title": "Calvin and Hobbes",
        "year": 1990,
        "launchDate": "1990-06-01T08:00:00.000Z"
      }
    ],
    "charactersADP": "{{ $variables.flow1SecondComicCharactersAdpVar }}"
  }
}

internalState

In addition to 'value', extended type instances are provided an 'internalState' property. Custom types can externalize their internal state so that it can be captured in redux by using this 'internalState' property. More specifically they can use property accessors to read (see getInternalState() method) and write (see setInternalState() method) the internal state are provided.

Methods

getTypeDefinition

As stated before, the type definition for the value of an extended type must be provided via the 'getTypeDefinition' function. This method is called at the time the type instance is created. The example below returns the type definition of the state (value) of comicStripType. name, publicationType, publications and charactersADP represent its state.

class ComicStripExtendedType extends ExtendedType {
  getTypeDefinition(variableDef, scopeResolver) {
    let publicationsDef = 'any';
    if (variableDef.defaultValue && variableDef.defaultValue.publicationType) {
      // responseType is specified in the defaultValue
      const { publicationType } = variableDef.defaultValue;

       if (typeof publicationType === 'string') {
        publicationsDef = `${publicationType}[]`;
      }
    }
    return {
      type: {
        name: 'string',
        publicationType: 'string',
        publications: TypeUtils.getType(`${this.getId()}:${publicationsDef}`,
          { type: publicationsDef }, scopeResolver),
        charactersADP: 'vb/ArrayDataProvider2',
      },
      resolved: true, // because we are pre-resolving type references
    };
  }
}
hoistValueObjectProperties

As a convenience, if the type of this variable as defined in 'getTypeDefinition' is 'object', all root properties of the values will be hoisted to the root variable type instance. This allows these properties to be accessible via expressions like '$scope.variables.theInstance.property'. If this is not desired, return false from 'hoistValueObjectProperties'.

init / activate / dispose (lifecycle methods)

A Visual Builder variable goes through various lifecycle stages. Extended type instances will be notified of these stages via the init, activate and dispose methods.

  • activate

    The 'activate' method is called when this and other variables in the current scope have been created and its initial (default) values determined. This method is called right before the 'vbEnter' event and the value of the variable, and can be a good time for types to do other setup using the resolved value. It is important to note that at the time 'activate' is called, any value assigned, to the extended type variable or the variables it depends on, in the vbEnter action chains will not be available.

  • dispose

    The 'dispose' method is called when the current scope is being torn down and all variables, including this variable is being disposed. This would be a good time to cleanup state for the extended type. It is important to note that any outstanding async tasks that are pending, would be the responsibility of the extended type to wind down gracefully.

handlePropertyValueChanged

When the value of an extended type variable changes (say via assignVariablesAction) it will be notified of the change via this method.

invokeEvent

Additionally, custom type implementations have the ability to fire a custom event using 'invokeEvent', providing a name, payload. For example, 'comicStripUpdate' is an event fired by the ComicStripType in the sample provided below.

getType

Custom extended types can retrieve the exploded type structure given a type definition, using the 'getType' method.

Sample Extended Type - ComicStripType

Implementation

'use strict';
  define(['vb/types/extendedType', 'vb/types/typeUtils'], (ExtendedType, TypeUtils) => {
  class ComicStripType extends ExtendedType {
      getTypeDefinition(variableDef, scopeResolver) {
      let publicationsDef = 'any';
      if (variableDef.defaultValue && variableDef.defaultValue.publicationType) {
        const { publicationType } = variableDef.defaultValue;
          if (typeof publicationType === 'string') {
          publicationsDef = `${publicationType}[]`;
        }
      }
      return {
        type: {
          name: 'string',
          publicationType: 'string',
          publications: TypeUtils.getType(`${this.getId()}:${publicationsDef}`,
            { type: publicationsDef }, scopeResolver),
          charactersADP: 'vb/ArrayDataProvider2',
        },
        resolved: true, // because we are pre-resolving type references
      };
    }
    
    activate() {
      console.log('activate called on variable', this.id);
        const value = this.getValue();
      const { name } = value;
      const { publicationType } = value;
      const { publications } = value;
      const { charactersADP } = value;
      let charactersADPVValue;
      if (charactersADP) {
        charactersADPVValue = charactersADP.getValue();
      }
 
      const initialValue = {
        name, publications, publicationType, charactersADPVValue,
      };
      
      this.setInternalState('opStatus', 'not-started');
      console.log('initial evaluated value for variable', this.id, 'is', finalValue);
    }
    
    handlePropertyVariableChangeEvent(e) {
      if (e.name.endsWith('value')) {
        if (e.diff) {
          if (e.diff.publications) {
            // process value change here
          }
        }
      }
    }
  
    /**
     * a sample method provided by this type that fakes a async op and updates the internalState
     * @returns {Promise<T>}
     */
    callAsyncMethod() {
      this.setInternalState('opStatus', 'started');
        return Promise.resolve().then(() => {
        // call some other async method; set some internalState and fire an event
        callAnotherAsyncMethod().then((res) => {
          const result = res;
          this.setInternalState('opStatus', 'completed');
          this.invokeEvent('comicStripUpdate', { status: 'success', result });
        });
      });
    }
  }
  
  return ComicStripType;
});

InstanceFactory Types

vb/InstanceFactory

With an InstanceFactory type, authors can declaratively plug in any JET type or a custom type, and use it with a special Visual Builder variable (instance factory variable). The InstanceFactory type:

  • Supports creating immutable, or re-creatable (type) classes.
  • Many constructs in JET are immutable classes that are then assigned to component properties. As a framework, Visual Builder facilitates the (re)creation of these classes, and reassignment when the configuration of these classes change .
  • The vb/InstanceFactory variable takes in the JS (type) class, as well as the parameters to the constructor. When bound, this variable provides an 'instance' of the class (along with the 'constructorParams').
  • When the constructor parameters change, the InstanceFactory variable will automatically create a new instance of the class.
  • Like regular variables, a VB 'valueChanged' event is fired when an InstanceFactory variable changes. The event payload will have the old and new values containing the two properties constructorParams and instance.
  • The 'constructorParams' of the variable alone will be serialized and persisted, not the instance. If the constructorParams includes a property that references another InstanceFactory variable, then that variable needs to be marked 'persisted', if author wants to persist the full tree. It's preferable that authors always update the 'constructorParams', so the instance is created automatically. If the instance is updated separately from constructorParams, the persisted state may not accurately reflect the correct state.

Associate a type with a variable to create an instance of that type

This example shows how you can do this.

Code Description
1  "variables": {
2    "customersADP": {
3      "type" : "ojs/ojarraydataprovider",
4    "constructorParams": []
5    }
6  },
7  "types": {
8    "ojs/ojarraydataprovider": {
9      "typedef": "http://jet-cdn/path/to/typescript/file/
              for/ojs/ojarraydataprovider/index.d.ts"
10     "constructorType": "vb/InstanceFactory"
11   }
12  }
  • Line 3: use the JET array data provider type ojs/ojarraydataprovider. This module is automatically loaded when the variable is created, because a require mapping for ojs already exists.
    • you must use a '/' in its name
  • Line 8: types declaration for ojs/ojarraydataprovider
  • Line 9: path to the typescript definition for JET ADP
    • Custom type authors are required to provide a typescript declaration file defining the interface for that type. At design time the file will be loaded, the constructor params parsed so the design-time can instantiate vars of these types, and the method signatures parsed to aid in calling functions on the variable instance.
  • Line 10: indicates that instance of JET ADP is created using a vb/InstanceFactory.
    • An author can use the short convention, in which case the type name is assumed to be the require JS module, Or use the longer convention "vb/InstanceFactory<ojs/ojarraydataprovider>", if the typename is different than the actual require path.

Specify an array of params using the 'constructorParams' property

In this example, a JET ADP takes a data array as its first param and an options Object as its second param.

Code Description
1  "customersADP": {
2    "type" : "ojs/ojarraydataprovider",
3    "constructorParams": [
4      "{{ $page.variables.customersData }}",
5      {
6        "keyAttributes": "id",
7        "textFilterAttributes": [
8          "lastName",
9          "firstName"
10       ]
11     }
12   ]
13 }
  • Line 4: customersData is the data array
  • Line 5: options object

Create an instance of the type, when the variable 'customersADP' is created

The variable has two properties that are stored in redux.

instance

This holds the constructed ADP instance.

A component that wants an ADP instance can use it this way.

<oj-select-single id="ss11"
 value="{{ $variables.customerId }}"
 
 data="[[ $variables.customersADP.instance ]]"
 
 item-text='[[ $page.functions.getItemText ]]'>
</oj-select-single>

constructorParams

  • the array of params passed to the constructor of the type
  • the constructorParams can be used in EL expressions as well for readonly expressions
  • $variables.customersADP.constructorParams

Note:

The properties defined on the instance can be mutated directly, and will be reflected on the instance stored in redux.

The methods available on the instance can be called directly.

The properties and methods supported on the instance are assumed to be declared by the type author using typescript or at design-time. This information is not relevant for runtime purposes.

To change the 'constructorParams'

Variable properties can be changed in several ways.

Using assignVariables action

For an InstanceFactory variable that is defined like this:

"incidentsList": {
  "type": "vb/ServiceDataProvider2",
  "constructorParams": [
    {
      "endpoint": "demo-data-service/getIncidents",
      "keyAttributes": "id",
      "itemsPath": "result",
      "uriParameters": "{{ $variables[\"technicianURIParams\"] }}"
    }
  ]
},
"incidentsListView": {
  "type": "ojs/ojlistdataproviderview",
  "constructorParams": [
    "{{ $page.variables.incidentsList.instance }}", // SDP2
    {
      "sortCriteria": [
        {
          "attribute": "priority",
          "direction": "ascending"
        }
      ]
    }
  ]
}

The assignVariablesAction below adds a filterCriterion property on the constructorParams of the JET ListDataProviderView variable. This assignment will cause the variable to create a new instance based on the new values.

"setFilterCriterion": {
  "module": "vb/action/builtin/assignVariablesAction",
  "parameters": {
    "$page.variables.incidentsListView.constructorParams[1]": {
      "source": {
        "op": "$eq",
        "attribute": "status",
        "value": "accepted"
      },
      "mapping": {
        "$target.filterCriterion": {
          "source": "$source",
          "reset": "empty"
        }
      },
      "reset": "none"
    }
  }
}

Using resetVariables action

For an InstanceFactory variables that is defined like above, the resetVariablesAction looks like below to reset the view variable.

"resetVariables": {
  "module": "vb/action/builtin/resetVariablesAction",
  "parameters": {
    "variables": [
      "$page.variables.incidentsListView"
    ]
  }
}

Using component writeback via EL bindings

Note:

This option is not supported.

Call methods on the instance using an action

You can use 'callVariableMethodAction' to call any method, including async methods. It's important to remember that because actions in a chain are intrinsically synchronous, a method that returns a Promise waits for the Promise to resolve before executing the next action.

"callGetCapabilityChain": {
  "root": "getCapabilityOnLDPV",
  "actions": {
    "getCapabilityOnLDPV": {
      "module": "vb/action/builtin/callVariableMethodAction",
      "parameters": {
        "variable": "$page.variables.incidentsListView",
        "method": "getCapability",
        "params": [
          "sort"
        ]
      }
    }
  }
}

Update the instance and constructorParams together

You can use the assignVariablesAction and a built-in function to update the instance and constructorParams together.

In the following example, Line 6 uses a built-in utils called 'assignmentUtils' that provides an assignValue method. This allows authors to provide both the updated instance, and the associated constructorParams.

1  "assignInstanceAndCPToListViewVar": {
2    "module": "vb/action/builtin/assignVariablesAction",
3    "description": "update variable instance and constructorParams declaratively",
4    "parameters": {
5      "$page.variables.incidentsListView": {
6        "module": "{{ $application.builtinUtils.assignmentUtils }}",
7        "functionName": "assignValue",
8        "params": [
9          {
10           "instance": "{{ $chain.results.setFilterCriterion_priorityLow.instance }}",
11           "constructorParams": "{{ $chain.results.setFilterCriterion_priorityLow.constructorParams }}"
12         }
13       ]
14     }
15   }
16 }

Default Values

Variables (but not types) may have default values. 

To specify a default value:

"nameOfVariable": {
  "type": "string",
  "defaultValue": "someString"
},
"someOtherVariable": {
  "type": "boolean",
  "defaultValue": true"
},
"yetAnotherVariable": {
  "type": "number",
  "defaultValue": 10
}

Example 11-13 Object Variables

Object variables can also have default values:

"nameOfVariable": {
  "type": {
    "foo": "string",
    "bar": "number"
  },
  "defaultValue": {
    foo: "myDefaultFoo"
  }
}

Example 11-14 Object Variables That Reference An Application Type

Object variables that reference an application type can also have a default value for their properties:

"nameOfVariable": {
  "type": "application:myType",
  "defaultValue": {
    "foo": "myDefaultValue"
  }
}

Example 11-15 Arrays

Arrays can also have a default value for their properties:

"nameOfVariable": {
  "type": "application:myArrType",
  "defaultValue": [
    {
      "foo": "myDefaultValue"
    }
  ]
}

The following table shows how a variable is initialized, based on its type, when no default value is provided.

Type Initial Value
String Undefined
Number Undefined
Boolean Undefined
Any Undefined
Object { }
Array [ ]
Custom type An empty object with all properties initialized according to this table

Expressions in Default Values

Default values may contain expressions.

When a default value contains an expression, note that expressions can also use other variables. You can reference a variable with the following syntax:

Scope Variable Syntax
Application $application.variables.<variableName>
Page $page.variables.<variableName>
Action Chain $chain.variables.<variableName>

Expressions must be wrapped in expression syntax :{{ expr }}. and the expression must be the entire value. Expressions can also call external functions via the page function module.

To reference another variable in a default value, you can do the following:

"nameOfVariable": {
  "type": "application:myType",
  "defaultValue":  {
    "foo": "{{ $application.variables.someOtherVariable }}"
  }
}

Since these are expressions, you can also add simple Javascript code to the values:

"myOtherVariable": {
  "type": {
    "someBoolProperty": "boolean"
  },
  "defaultValue": {
    "someBoolProperty": {{ $application.variables.someOtherVariable === true }}"
  }
}

Input Variables

Variables can also be inputs to the page.

There are two types of input. The first consists of inputs that come from the URL. The second type consists of inputs that are passed internally by the framework. To mark a variable as an input, you can use the following properties:

"nameOfVariable": {
  "type": "string",
  "input" "fromCaller/fromUrl"
  "required": true
}

Here the input is either "fromCaller" or "fromUrl". If it is "fromCaller", it will be passed internally using the params property of the navigate action. If it is "fromURL", it will be passed via the URL request parameter of the same name, like ?myVar=someValue. If the "required" property is true, the variable value will be required to be passed during a navigation or page load.

The implicit object $parameters is used to retrieve the input parameter values inside the vbBeforeEnter event handler. Input variables do not exist until the vbEnter event.

In this example, the input regionName is retrieved using $parameters.regionName in the vbBeforeEnter handler and using $page.variables.regionName in the vbEnter handler.

"eventListeners": {
  "vbBeforeEnter": {
    "chains": [
      {
        "chainId": "checkForRegionName",
        "parameters": {
          "regionName": "{{ $parameters.regionName }}"
        }
      }
    ],
  },
  "vbEnter": {
    "chains": [
      {
        "chainId": "initializeVariables",
        "parameters": {
          "regionName": "{{ $page.variables.regionName }}",
          "facilityId": "{{ $page.variables.facilityId }}"
        }
      }
    ]
  }
},

Persisted Variables

The value of a variable can be persisted on the history, for the current session or across sessions.

If you set "persisted" to "history", the variable value is stored in the browser history. When navigating back to a page in the browser history using the browser back button or when refreshing the page, the value of the variable is restored to its value at the time the application navigated away from this page.

If you set "persisted" to "session", the variable is stored in the browser session storage as long as the browser is open. To store a variable across sessions, use "device" instead of "session".

If you set "persisted" to "device", the variable is stored in the browser local storage, so it is persisted on the device where the application is running even if the browser is closed.

To remove a variable from storage, set its value to null.

Example 11-16 Using a Persisted Variable

"variables": {
  "sessionToken": {
    "type": "string",
    "persisted": "session"
  }
}

rateLimit Variable Property

A variable can set a rateLimit property that limits how often the onValueChanged event is fired.

Specify the rateLimit property, with a timeout property in milliseconds, to limit how often the onValueChanged event is fired on that variable. For example:

"pageVar": {
  "type": "string",
  "onValueChanged": {...},
  "rateLimit": {
    "timeout": 1000 // in milliseconds
  }
}

The default is to wait for the timeout to expire after all changes stop before firing the change event.

Constants

Constants are scoped like variables, but their values can't be changed through assignment.

Constants have the following properties and restrictions:

  • The scope of a constant can be page, flow, application, or action chain. The value of a constant is defined declaratively in the descriptor using the constants property.
  • The value of a constant can be an expression. The expression can refer to previously-defined constants and variables in the current scope or application/flow.
  • Constants are evaluated first, so expressions in variables can refer to constants.
  • The name of a constant cannot be used by a variable in the same scope.
  • Constants can be used in action chains.
  • A constant can be an input parameter to a page or action chain.
  • A constant cannot be of a built-in type.
  • A constant holds a value that is immutable (contrary to JavaScript). For instance, in the case where the content is an object, this means the object's contents (for example, its properties) cannot be altered.
  • Constants do not dispatch change events, since their values never change.

Type

Constant type is the same as for variable except it cannot be a built-in type.

Default Value

Constants value can be an expression. Expression can only refer to previously defined constants in the current scope or application/flow. Constants are evaluated first so expression in variables can refer to constants.

Input

Constant input is the same as for variable.

Example 11-17 Declaring a constant

"constants": {
  "myConstant": {
    "type": "string",
    "description": "A useful constant",
    "defaultValue": "This string"
  }
}

Actions

A list of built-in actions available in Visual Builder for applications

Note:

Action definitions minimally have a "module" property that specifies the action implementation. Actions can also have an optional "label" property, which is user-friendly.

Assign Variables Action

This action is used to assign values to a set of variables.

This action has two forms. The first is metadata-driven, where you can specify how assignment should be performed by using metadata. The second supports calling out to a custom assign variable function. This custom assign variable function can perform a transformation on the source value before assignment.

"myActionChain": {
  "root": "myAssignVariableAction",
  "actions": {
    "myAssignVariableAction": {
      "module": "vb/action/builtin/assignVariablesAction",
      "parameters: {
        "$page.variables.target1": { "source": "{{ $page.variables.source1 }}" },
        "$page.variables.target2": { "source": "{{ $page.variables.source2 }}" }
      }
    }
  }
}
Metadata-Driven Variable Assignment

This action is used to assign values to a set of variables using metadata.

Metadata-driven variable assignment lets you use metadata to specify how assignment should be performed.

This form takes a map of target expression and assignment metadata pairs. For example, if the target expression is a structure, it has to resolve to a variable or to a variable's property. The target expression has to be prefixed with one of the following:

  • $application.variables
  • $page.variables
  • $chain.variables
  • $variables

This should be followed by a variable name or a path to a variable property, such as the following:

  • $application.variables.a
  • $page.variables.a.b
  • $variables.a.b.c

Note that $variables.a.b.c is a shortened form of $chain.variables.a.b.c.

The expression can be arbitrarily complex as long as it is a valid JavaScript expression and satisfies the above constraints.

The assignment metadata has the following format:

{
  "source": "some expression",
  "reset": "none", // default to " toDefault"
  "auto": "always", // default to "ifNoMappings"
  "mapping": { ... }
}

The "source" expression can be an arbitrary expression that evaluates to a primitive value, an object or an array.

The "reset" option can be one of the following:

  • "toDefault" - reset the target to its default value before assignment. This is the default.

  • "empty" - clear the target before assignment. If the target has an object type, the target will be reset to an empty object of that type. If the target is an array, the target will be reset to an empty array.

  • "none" - overwrite the existing target value

The "auto" option controls whether to auto-assign all properties from the source to the corresponding properties of the target. It can be set to one of the following:

  • "always" - auto-assignment will always be performed first before any mapping is applied.

  • "ifNoMapping": auto-assignment will only be performed if no mapping is provided. This is the default.

The "mapping" is a piece of metadata used to provide fine-grained control over what gets assigned from the source to the target. When no "mapping" is used to control the assignment, there are two possible schemes for assignment depending on the target type, auto and direct.

Auto Assign Source to Target 

If the target has a concrete type, the assign action will auto-assign the source to the target. If the target type is an object type, auto-assignment will recursively assign each property in the source object to the corresponding property in the target object based on the target type. If the target is an array, the source will be treated as an array if it is not one already. For each item of the source array, an empty item will be created using the target's array item type and appended to the target array. The source item is then auto-assigned to the target item. 

If the target property is an object and the source property is a primitive or vice versa, no assignment will be made. For primitive types, the source value will be coerced into the target type before assignment. For boolean type, the coercion will be based on whether the source value is truthy except for "false" (case-insensitive) and "0" which will be coerced to false.

Direct Assign Source to Target 

If the target has a wildcard type, e.g., any, any[], object or object[], direct assignment will be performed. The behavior may differ depending on the wildcard type:

  • any - the source value is directly assigned to the target

  • any[] - the source value is turned into an array if not one already and then directly assigned to the target

  • object - same as any except the source value has to be an object. Otherwise, no assignment is performed.

  • object[] - same as any[] except the items in the source array have to be objects. Otherwise, no assignment is performed.

Example: Metadata-driven assignment takes a map of target expression and assignment metadata pairs.

"myActionChain": {
  "root": "myAssignVariableAction",
  "actions": {
    "myAssignVariableAction": {
      "module": "vb/action/builtin/assignVariablesAction",
      "parameters: {
        "$page.variables.target1": { "source": "{{ $page.variables.source1 }}" },
        "$page.variables.target2": { "source": "{{ $page.variables.source2 }}" }
      }
    }
  }
}
Example: Mapping metadata is used to assign specific properties from source to target:
"$page.variables.target": {
  "source": "{{ $page.variables.source }}",
  "mapping": {
    "$target.a": "$source.b",
    "$target.b.c": "$source.c.b"
  }
} 
Example: Mapping can also be nested:
"$page.variables.target": {
  "source": "{{ $page.variables.source }}",
  "mapping": {
    "$target.a": "$source.b",
    "$target.b": {
      "source": "$source.c"
      "mapping": {
        "$target.c": "$source.b"
      }
    }
  }
}
Assign Variables With a Custom Function

This action uses a custom function to assign values to a set of variables.

A custom assign variable function can perform a transformation on the source value before assignment.

The AssignVariablesAction will first look up the function referenced by "functionName" from the page's functions module and call it with the current available scopes. It will then assign the return value of the function call to the target variable. The custom function should have the following signature:

PageModule.prototype.myAssignVariableFunction = function (helper, targetDefaultValue)

The "targetDefaultValue" is the default value for the target which can be used to emulate the "toDefault" reset option.

The "helper" is an utility object that can be used to retrieve values for variables within the current scope and perform auto-assignment. It has the following interface:

class AssignmentHelper {
  /**
   * Gets a variable from a scope by its string representation, e.g.,
   * helper.get("$page.variables.myVar")
   */
  get(expr);
  
  /**
   * Assigns properties from one or more sources to the target if and
   * only if the property already exists on the target. The sources
   * are processed in the order they are defined.
   *
   * If target is null, any empty target value will be created based
   * on the target's type. If the target is not null, it will be cloned
   * and the sources will be assigned into the clone. In either case,
   * this value will be returned as the result.
   */
   pick(target, ...sources) {
}

Example: an assign variable function that resets the target value to its default value and auto-assign the source to the target:

PageModule.prototype.myAssignVariableFunction = function (helper, targetDefaultValue) {
  var source = helper.get("$page.variables.source");
  var result = helper.pick(targetDefaultValue, source);
  return result;
}

Call Action Chain Action

The action module for this action is "vb/action/builtin/callChainAction".

To call an action chain, you need to pass the following parameters:

Parameter Name Description
id The ID of the action chain to call. Action chains need to be prefixed with application: for an application chain and flow: for a flow chain.
params An expression that maps to an array of parameters.

The outcome and result will be the outcome and result of the last action executed in the called action chain.

Call Component Action

The action module for this action is "vb/action/builtin/callComponentMethodAction". This provides a declarative way to call methods on JET components.

Parameters

Parameter Name Description
component The component on the page. Use the DOM method document.getElementById to locate a JET element/component.

The following deprecated utility methods are provided in the $page scope to get JET components, but will be removed in a future release:

$page.components.byId('myCard')
$page.components.bySelector('#myCompId')

Note:

These two methods will return null if no element is found, or if the element is not part of a JET component.
method The name of the component method to call.
params Array of parameters to pass to the method, if it takes arguments. Primitives, objects, and array parameters are passed by value and not by reference. Instances are still sent as references.

For this sample composite component, the 'flipCard' method takes two parameters: 'model', which is unused (null below), and 'event', which we construct with a 'type' property:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "flipCardMethodCall": {
      "label": "Flip the Card",
      "module": "vb/action/builtin/callComponentMethodAction",
      "parameters": {
        "component": "{{ document.getElementById('myCard') }}",
        "method": "flipCard",
        "params": ["{{ null }}", { "type": "click" }]
       }
     }
   }
}

Call Function Action

The action module for this action is "vb/action/builtin/callModuleFunctionAction".

To call a module function, you need to pass the following parameters.

Parameter Name Description
module The module to call the function on.This could be "$page.functions", "$application.functions", or "$flow.functions".
functionName The name of the function to call.
params An array of parameters. Note that a single parameter must still be passed as a single item array.

The outcome is either 'success' if the function call was successful, or 'error' otherwise. The result payload is equivalent to whatever the function returns (which may be undefined if there is no return). If the function returns a promise, the result payload will be whatever is resolved in the promise.

Suppose there is a function defined in the page functions module as follows:

PageModule.prototype.sum = function(one, two) {
  return one + two;
}

You can call that function with the following action:

"myActionChain": {
  "root": "mySumAction",
  "actions": {
    "myAction": {
      "label": "call my sum function",
      "module": "vb/action/builtin/callModuleFunctionAction",
      "parameters": {
        "module": "{{$page.functions}}",
        "functionName": "sum",
        "params": ["3", "4"]
      }
    }
  }
}

After this action call, $chain.results.mySumAction should be set to 7.

Call REST Action

The action module for this action is "vb/action/builtin/restAction".

The call REST action is used to make a REST call in conjunction with the service definitions. For details on services, see Work with Services.

Internally, this action uses the Rest Helper, which is a public utility. Its parameters are as follows.

Parameter Name Description
endpoint The endpoint ID as defined in the service configuration.
uriParams A key/value pair map that will be used to override path and query parameters as defined in the service endpoint.
body A structured object that will be sent as the body.
requestType The content-type of the request, either 'json', 'form', or 'url'.

Note:

Note that this is deprecated. Instead, use 'contentType' and 'fileContentType'.
headers An object; each property name is a header name and value that will be sent with the request.
contentType An optional string value with an actual MIME type, which will be used for the "content-type" header. When used with "fileContentType", this is also used as the type for the File blob.
responseType If set, the specified type is used to do two things at run-time:
  • Generate a fields parameter for the REST URI to limit the attributes fetched;
  • Automatically map the fetched response to the response type (when used with the built-in vb/BusinessObjectsTransform). This applies to standard action chains.

See the definition for "responseType" in Service Data Provider Properties for details on how the assigned type is used in that context.

filePath An optional path to a file to send with the request. If "contentType" is set, that is used as the type for the File contents. If “contentType” is not set, a lookup of common file extensions will be used.
filePartName Optional, used with filePath to allow override of the default name ("file") for the FormData part.
fileContentType An optional string, used in combination with "contentType", "multipart/form-data", and "filePath".
hookHandler Used primarily by vb/ServiceDataProvider when externalizing data fetches. See Service Data ProviderServiceDataProvider for details.
requestTransformOptions A map of values to pass to the corresponding transform, as the "options" parameter.
requestTransformFunctions A map of named transform functions, called before making the request, where the function is: fn(configuration, options)
responseTransformFunctions A map of named transform functions, called before making the response, where the function is: fn(configuration, options)
responseBodyFormat  A string that allows an override of the standard Rest behavior, which normally looks for a “content-type” header to determine how to read and parse the response. Possible values are "text", "json", "blob", "arrayBuffer", "base64", "base64Url", and "formData".
responseFields This is an "advanced" field, for use specifically with JET Dynamic Forms. The value would typically be a variable that is bound to the <oj-dynamic-form> "rendered-fields" attribute. This is how a calculated layout can tell the Rest Action call which fields to fetch.

Note: the vb/BusinessObjectsTransform transform is necessary to create a query from this value.

Note: When "responseFields" is provided, "responseType" is ignored.

Using multipart/form Data

If you have set "contentType" to "multipart/form-data", the Action will interpret your request "body" object as the form parts. Each property of the body object will be a form part. If "filePath" is also set, it will be added as an additional part using the lookup of common file extension types.

If "filePath" is also set, it will be added as an additional part using the sample simple file extension type association. The name of this part will be "file", or can be specified using "filePartName".

You may optionally override the file type by using "fileContentType" for the file part.

Defining Services

In order to use a REST API, it should be first defined.

In this example, the following endpoint is registered for the 'foo' service:

{
  "openapi": "3.0",
  "info": {
    "version": "1.1",
    "title": "ifixitfast",
    "description": "FIF",
  },
  "host": "exampledomain.com",
  "basePath": "/services/root",
  "schemes": [
    "http"
  ],
  "paths": {
    "/foo/{id}": {
      "get": {
        "summary": "get a specific Foo object",
        "operationId": "getBar",
        "parameters": [
           {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string"
           }
        ],
        "responses": {
          "200": {
            "description": "",
            "schema": {}
          }
        }
      }
    }
  }
}

You can invoke that endpoint with the following, passing in a value for the 'id' path parameter from a page parameter:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/restAction",
      "parameters": {
        "endpoint": "foo/getBar",
        "uriParams": { 
          "id": "{{ $page.variables.myId }}"
        }
      }
    }
  }
}

Declaring Services in the Application

Service definitions are referenced in declarations in the application or in flows. The service name and path are defined by a "services" section in an app-flow.json or xxx-flow.json model. Service declarations support two syntaxes: a string (path), or an object with "path" and "headers":

"services": {
  "fooService": "./demo-data-service.json",
  "barService": {
     "path":  "./service-def.json",
     "headers": {
        "Accept": "application/vnd.oracle.openapi3+json"
     }
  }
}

Transforms

The requestTransformOptions, requestTransformFunctions, and responseTransformFunctions can be used to modify the request and response. Some built-in service endpoints have built-in transform functions for 'sort', 'filter', 'paginate', and 'select', so options for these transform functions can be defined using the same name via the requestTransformOptions property. For third party services, the options set are based on the type of transform functions supported.

When using the Rest Action the transform names have no semantic meaning, and all request and response transforms are called.

Request and response transform functions have the following signatures.

Transform Type Parameters Return Value
Request
/**
 * configuration: {
 *  url:
 *   initConfig: {
 *     method: // string with http method
 *     body: // request body, if any
 *     credentials: // string see (fetch) Request
 *     headers: // object, map of strings
 *   }
 * },
 *
 * options: provided by the application
 *
 * context: an empty object, which exists for the
 *   lifetime of one REST call, a set of 
 *   transforms share this.
 **/
 
mytransform(configuration, options, context)

Configuration object; see "Parameters".

Typically, returns the same object passed in, or a modified one.

Response
/**
 * response: { body, headers }
 *
 * context: an empty object, see "Request transforms"
 *
 */
myresponsetransform(response, context);

The return value is application-defined. The value is returned as the 'transformResults' of the REST call result:

/**
 * {
 *  response: The (fetch) Response object. Note that the body has already
 *     been read, so the functions (ex. json()) cannot be called.
 *
 *  body: the result of the json()/text()/etc.
 * 
 *  transformResults: a map of return values from Response Transforms
 * }
 */

Example 11-18 A Simple Transform Function

One request transform function and one response transform function for a third party service or endpoint might look like this example. Here, the transform functions are defined in the page module and are configured on the RestAction directly. More commonly, transform functions are defined in the service definition and do not need to be mapped on the RestAction.

"fetchIncidentList": {
  "module": "vb/action/builtin/restAction",
  "parameters": {
    "endpoint": "ifixitfast-service/getIncidents",
    "requestTransformOptions": {
      "sort": "{{ $page.variables.sortExpression }}",
    },
    "requestTransformFunctions": {
      "sort": "{{ $page.functions.sort }}"
    },
    "responseTransformFunctions": {
      "paginate": "{{ $page.functions.paginateResponse }}"
    }
  },
  "outcomes": {
    "success": "returnSuccessResponse",
    "failure": "returnFailureResponse"
  }
},
The corresponding module functions would be:
PageModule.prototype.sort = function (configuration, options) {
   /// some code here to modify 'configuration'
   return configuration;
}

PageModule.prototype.paginateResponse = function (configuration) {
   /// some code here to modify 'configuration'
   return configuration;
}

Outcomes

The Call REST action has the following outcomes.

Outcome Description Result Payload
success

If the response code is within the 200 range (or 'ok' in fetch API terms).

  • status: number
  • headers: Headers object
  • body: the result of the call (scalar, obejct, array, etc).
{
  status: <responseCode>,
  headers: <responseHeaders>,
  body: <result body>
}
failure

If the response code is outside of the 200 range (an error response).

  • message

    • summary: string
  • error: Error object, or null
  • payload
    • status: number
    • headers: Headers object
    • body: the result of the call (scalar, obejct, array, etc).
{
  message: {
    summary: <rt message>
  },
  error: <Error, or null>,
  payload: {
    status: <responseCode>,
    headers: <responseHeaders>,
    body: <result body>
  }
}

Call Variable Method Action

The action module for this action is vb/action/builtin/callVariableMethodAction. This action is used to call a method on a variable of InstanceFactory type only. Using it with any other variable will report an error.

Here is an example.

"callGetCapabilityChain": {
  "root": "getCapabilityOnLDPV",
  "actions": {
      "getCapabilityOnLDPV": {
          "module": "vb/action/builtin/callVariableMethodAction",
          "parameters": {
            "variable": "$page.variables.incidentsListView",
            "method": "getCapability",
            "params": [
              "sort"
            ]
          }
      }
  }
}

Where incidentsListView is an InstanceFactory variable defined like this:

"incidentsListLDPV": {
    "type": "ojs/ojlistdataproviderview",
    "constructorParams": [
        "{{ $page.variables.incidentsList.instance }}",
        {
          "sortCriteria": [
            {
              "attribute": "priority",
              "direction": "ascending"
            }
          ]
        }
    ],
    "persisted": "session"
}

To call a variable method, we need to pass the following parameters:

Parameter Name Description
variable The variable path
method The name of the method to call
params (optional) An array of parameters. Note that a single parameter must still be passed as a single item array.

The outcome is either 'success' if the function call was successful, or a 'failure' outcome. An error is thrown for configuration errors.

The result payload is equivalent to whatever the function returns (which may be undefined if there is no return). If the function returns a promise, the result payload will be whatever is resolved in the promise.

Fire Event Action

The action module for this action is "vb/action/builtin/fireCustomEventAction".

This action allows you to fire application-defined events.

"actions": {
  "fireEvent": {
    "module": "vb/action/builtin/fireCustomEventAction",
    "parameters": {
      "name": "{{ $variables.name }}",
      "payload": "{{ $variables.payload }}"
    }
  }
}

Fire Data Provider Event Action

The action module for this action is "vb/action/builtin/fireDataProviderEventAction".

This causes the DataProvider specified via the 'target' parameter to dispatch an oj.DataProvider event as a way to notify all listeners registered on that DataProvider to react to changes to the underlying data. For example, a component using a particular ServiceDataProvider may need to render new data because new data has been added to the endpoint used by the ServiceDataProvider.

The action can be called either with a mutation event or a refresh but not both. Generally a mutation event is raised when items have been added, updated, or removed from the data that the ServiceDataProvider represents.

Note:

This action can be used with a vb/ArrayDataProvider2. It does not need to be used with a legacy vb/ArrayDataProvider because the 'data' is already exposed as a property on the variable. This allows page authors to directly mutate the data array using the assignVariables action. This assignment is automatically detected by Visual Builder, and all listeners of this change are notified, removing the need to use a fireDataProviderEventAction. Users will be warned when the fireDataProviderEventAction is used with a legacy ArrayDataProvider, prior to mutating the 'data' property directly.

A mutation event can include multiple mutation operations (add, update, remove) as long as the id values between operations do not intersect. This behavior is enforced by JET components. For example, you cannot add a record and remove it in the same event, because the order of operations cannot be guaranteed.

The action can return either success or failure. Success returns null, while failure returns the error string.

Table 11-1 Parameters

Name Type Description Example
target string Target of the event, usually a variable of type vb/SDP or vb/ADP.
target: "{{ $page.variables.incidentList }}"
refresh null Indicates a data provider refresh event needs to be dispatched to the data provider identified by the target. A null value is specified because the refresh event does not require a payload.
refresh: null
add object The following properties may be present in the payload:
  •  data: Array<Object>; the results of the 'add' operation. Note there can be more than one rows added. If data alone is present in the payload, and the target has a keyAttributes property specified, then the 'keys' are built for you. The structure of the data returned must be similar to the responseType specified on the target variable of type vb/ServiceDataProvider (respecting the "itemsPath", if any), or the itemType specified on the vb/ArrayDataProvider

  • keys: optional Set<*>. the keys for the rows that were added. If a ServiceDataProvider variable is configured with a keyAttributes property, this can be determined by the ServiceDataProvider itself from the data, if data is present. 

  •  metadata: optional Array<ItemMetadata<Object>>. Since the ServiceDataProvider variable is configured with 'keyAttributes', this can be determined by the ServiceDataProvider itself.

  • addBeforeKeys: Optional Array of keys for items located after the items involved in the operation. They are relative to the data array, after the operation was completed and not the original array. If null and index are not specified, then insert at the end.
  • afterKeys: Deprecated: use addBeforeKeys instead. Optional Set<*>; a Set that is the keys of items located after the items involved in the operation. If null and index not specified then insert at the end.

  • indexes: optional Array<number>, identifying insertion point.

"add": {
   "data": "{{ $chain.results.saveProduct.body }}",    "indexes": [0] 
}

An example with ServiceDataProvider, where "itemsPath": "items":

"updateList": {
  "module": "vb/action/builtin/fireDataProviderEventAction",
  "parameters": {
    "target": "{{ $page.variables.personList }}",
    "add": {
      "data": {
        "items": "{{ [$chain.results.createPersonPost.body] }}"
      }
    }
  }
}
remove   The payload for the remove event is similar to add above except 'afterKeys'/'addBeforeKeys' are not present.
"remove": {
  "keys": "{{ [ $page.variables.productId ] }}"
}
update    Same as remove.
"update": {
  "data": "{{ $page.variables.currentIncidentResponse }}"
}

The action can return two outcomes:

  • The name of the outcome can be 'success' or 'failure'.
  • The result of a failure outcome is the error string, and the result of a success outcome is null.

Example 11-19 Example 1

Configuring a refresh event to be dispatched to a ServiceDataProvider:

(1) activityListDataProvider is the name of the
page variable that is of type vb/ServiceDataProvider
(2) refresh has a null value
  
"fireDataProviderRefreshEventActionChain": {
  "variables": {
    "payload": {
      "type": {
        "target": "activityListDataProvider"                            (1)
      }
    }
  },
  "root": "fireEventOnDataProvider",
  "actions": {
    "fireEventOnDataProvider": {
      "module": "vb/action/builtin/fireDataProviderEventAction",
      "parameters": {
        "target": "{{ $page.variables[$variables.payload.target] }}",
        "refresh": null                                                 (2)
      }
    }
  }
},

Example 11-20 Example 2

Configuring a remove event to be a dispatched to a ServiceDataProvider:

(1) deleteProductChain deletes a product and ends up calling
another chain that fires a remove event on the ServiceDataProvider
(2) deletes the product from the backend service via a RestAction
(3) calls fireDataProviderEventAction
(4) on a variable of type vb/ServiceDataProvider
(5) with a remove payload
  
"variables": {
  "productListSDP": {
    "type": "vb/ServiceDataProvider",
    "defaultValue": {
      "keyAttributes": "id",
      "responseType": "application:productSummary[]"
    }
  },
}
"chains": {
  "deleteProductChain": { // (1)                                                           // (1)
    "variables": {
      "productId": {
        "type": "string",
        "description": "delete a single product",
        "input": "fromCaller",
        "required": true
      }
    },
    "root": "deleteProduct",
    "actions": {
      "deleteProduct": { // (2)
        "module": "vb/action/builtin/restAction",
        "parameters": {
          "endpoint": "ifixitfast-service/deleteProduct",
          "uriParams": {
            "productId": "{{ $page.variables.productId }}"
          }
        },
        "outcomes": {
          "success": "refreshProductList"
        }
      },
      "refreshProductList": {
        "module": "vb/action/builtin/callChainAction",
        "parameters": {
          "id": "fireDataProviderMutationEventActionChain",
          "params": {
            "payload": {
              "remove": {
                "keys": "{{ [ $page.variables.productId ] }}"
              }
            }
          }
        }
      }
    }
  },
  "fireDataProviderMutationEventActionChain": {
    "variables": {
      "payload": {
        "type": "application:dataProviderMutationEventDetail",
        "input": "fromCaller"
      }
    },
    "root": "fireEventOnDataProvider",
    "actions": {
      "fireEventOnDataProvider": {
        "module": "vb/action/builtin/fireDataProviderEventAction", // (3)                  // (2)
        "parameters": {
          "target": "{{ $page.variables.productListSDP }}",    // (4)
          "remove": "{{ $variables.payload.remove }}" // (5)
        }
      }
    }
  }
},

Fire Notification Event Action

The action module for this action is "vb/action/builtin/fireNotificationEventAction". This action is used to fire "vbNotification" events.

"vbNotification" events are just like custom events, except that they have a defined name and a suggested use. Notifications are generally intended to help implement a flexible message display, but the specific use can be defined by the application. See Custom Events for details.

"actions": {
  "fireNotification": {
    "module": "vb/action/builtin/fireNotificationEventAction",
    "parameters": {
      "summary": "{{ $variables.summary }}",
      "message": "{{ $variables.message }}",
      "type": "{{ $variables.type }}",
      "displayMode": "{{ $variables.displayMode }}"
    }
  }
}

ForEach Action

This action lets you execute another action for each item in an array. 

The ForEach action takes an 'items' and 'actionId', and adds a $current context variable for the called action, or  'Callee', in order to access the current item. The parameters are as follows:

Parameter Name Description
as An optional alias for $current. Used to name the context so that it can be referenced in nested Callees.
actionId An ID in the current action chain.
items An expression that evaluates to an array.
mode "serial" (default) or "parallel".

The "mode" parameter allows for serial or parallel action. Prior to this parameter, the behavior was "serial"; each "actionId" call was made for an item only when any previous item's "actionId" call finished (meaning, any Promise returned from the last action resolves). Using "parallel" means that each "actionId" call does not wait for the previous call to finish (useful for Rest Action calls, etc).Using either mode, the ForEach action does not finish until all Promises returned from the "actionId" chain resolve (if no Promise is returned, it is considered resolved on return).

The following table describes additional properties injected into the available contexts that the called action ('callee') can reference in its parameter expressions:

Parameter Name Description
$current.data The current array item.
$current.index The current array index.
alias.data An alternate syntax for $current.data, which allows a reference to $current from nested contexts.
alias.index An alternate syntax for $current.index, which allows a reference to $current from nested contexts.

The outcome of the action is either:

  • "success", with an array containing the return value of the last action's results; in other words, an array of the return of the "sub-chain" ("chainlet"?) called for each item in the loop,
  • or "failure" if there is some exception/error.

Note: Except for the return value for the last action, the results of each Action are not accessible outside of the sub-chain; for example, if the sub-chain is "actionA" → "actionB", the result of the ForEach will contain an array of "actionB" return values, and not "actionA"'s.

ForEach "as" Alias

By default, the ForEach Action ID in the declaration will be used for the alias to $current.  

Note that if an action has an "as" alias, then the value will be used as the alias instead. For example, for as="foo", you can also create expressions that reference "foo.data" and "foo.index".

Example 11-21 Example 1

In this example, $current.data and forEachCurrent.data are equivalent.

actions: {
   "forEach": {
     "module": "vb/action/builtin/forEachAction",
        "parameters": {
           "items": "{{ $variables.testArray }}",
           "actionId": "someAction",
           "as": "forEachCurrent",
      },
    },
    "someAction": {
       "module": "someRandomAction",
         "parameters": {
             "outcome": "{{ $current.data.foo }}",
             "payload": {
               "text": "{{ forEachCurrent.data.bar }}",
               "index": "{{ $current.index }}' }"
          }
       }
    }
}

Example 11-22 Example 2

This example demonstrates the use of “as”.

"actions": {
  "forEachOuter": {
    "label: 'the outer-most action, a ForEach',
    "module": "vb/action/builtin/forEachAction",
    "parameters": {
      "items": ["a", "b"],
      "actionId": "forEachInner"
    }
  },
  "forEachInner": {
    "label": "the inner-most action, a ForEach, called by a ForEach",
    "module": "vb/action/builtin/forEachAction",
    "as": "inner",
    "parameters": {
      "items": [1, 2],
      "actionId": "someAction",
    }
  },
  "someAction": {
    "label": "a custom action",
    "module": "countToTwoAction",
    "parameters": {
      "someParam": "{{ forEachOuter.data }}",
      "anotherParam": "{{ inner.data }}"
    }
  }
}

Get Location Action

The action module for this action is "vb/action/builtin/geolocationAction".

This action provides a declarative access to geographical location information associated with the hosting device. This action requires the user's consent. As a best practice, it should only be fired on a user gesture. Doing so will allow users to more easily associate the system permission prompt for access with the action they just initiated.

Parameter Name Description
maximumAge A positive long value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to 0, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set to Infinity, the device must return a cached position regardless of its age.
timeout A positive long value representing the maximum length of time, in milliseconds, that the device is allowed to take in order to return a position. The default value is Infinity, meaning that getCurrentPosition() won't return until the position is available.
enableHighAccuracy A boolean that indicates the application would like to receive the best possible results. If true, and if the device is able to provide a more accurate position, it will do so. This can result in slower response times or increased power consumption. If false (the default value), the device can save resources by responding more quickly or using less power. On mobile devices, enableHighAccuracy should be set to true in order to use GPS sensors.

If the geolocation API is supported in the browser, geolocationAction returns a JSON Position object that represents the position of the device at a given time.

Return Type Description Example
Object The Position interface represents the position of the concerned device at a given time. The position, represented by a Coordinates object, comprehends the 2D position of the device, on a spheroid representing the Earth, but also its altitude and its speed.
  • Position.coords returns a Coordinates object defining the current location.
  • Position.timestamp returns a DOM timestamp representing the time at which the location was retrieved.
Latitude and longitude can be accessed from the Position's coordinates as follows:
[[$chain.results.getCurrentLocation.coords.latitude ]]
[[$chain.results.getCurrentLocation.coords.longitude ]]

where getCurrentLocation is a geolocationAction.

If geolocation is not supported by the browser, or a parameter with a wrong type is detected, a failure outcome is returned. If a PositionError occurs when obtaining geolocation, a failure outcome with a PositionError.code payload is returned. Possible PositionError.code values are:
  • PositionError.PERMISSION_DENIED
  • PositionError.POSITION_UNAVAILABLE
  • PositionError.TIMEOUT

For every failure, a descriptive error message can be obtained from the action chain, such as [[ $chain.results.getCurrentLocation.error.message ]].

An example of using the geolocation action:

"chains": {
  "getCurrentLocation": {
    "root": "geolocation1",
    "description": "",
    "actions": {
      "geolocation1": {
        "module": "vb/action/builtin/geolocationAction",
        "parameters": {
          "timeout": 50000,
          "maximumAge": "{{Infinity}}"
        },
        "outcomes": {
          "failure": "fireNotification1",
          "success": "assignVariables1"
        }
      },
      "fireNotification1": {
        "module": "vb/action/builtin/fireNotificationEventAction",
        "parameters": {
          "summary": "[[ $chain.results.geolocation1.error.message ]]",
          "type": "error",
          "displayMode": "persist"
        }
      },
      "assignVariables1": {
        "module": "vb/action/builtin/assignVariablesAction",
        "parameters": {
          "$page.variables.coords": {
            "source": "{{ $chain.results.geolocation1.coords }}",
            "auto": "always"
          }
        }
      }
    }
  }
},

If Action

The action module for this action is "vb/action/builtin/ifAction".

This action will evaluate an expression and return a 'true' outcome if the expression evaluates to true, and a 'false' outcome otherwise.

Parameter Name Description
condition The expression to evaluate.

For example:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/ifAction",
      "parameters": {
        "condition": "{{ $chain.results.myRestAction.code === 404 }}"
      },
      "outcomes": {
        "true": "...",
        "false": "..."
      }
    }
  }
}

Login Action

This action launches the login process as defined in the Security Provider implementation.

The action module for this action is "vb/action/builtin/loginAction". It invokes the handleLogin function on the Security Provider with the returnPath argument.

Parameter Name Description
returnPath The path of the page or flow to go to when login is successful.
The behavior of the default implementation of the Security Provider handleLogin function is:
  • Navigate to the login URL specified by the Security Provider configuration.

  • If returnPath is not defined, use the default page of the application.

  • Convert the page returnPath to a URL path and add it to the login URL.

Example 11-23 Example

An example of a chain using the loginAction:

"signInChain": {
  "root": "signInAction",
  "actions": {
    "signInAction": {
      "module": "vb/action/builtin/loginAction"
    }
  }
}

Logout Action

This action launches the logout process as defined in the Security Provider implementation.

The action module for this action is "vb/action/builtin/logoutAction". It invokes the handleLogout function on the Security Provider with the logoutUrl argument.

Parameter Name Description
logoutUrl The URL to navigate to in order to log out.
The behavior of the default implementation of the Security Provider handleLogout function is:
  • Navigate to the URL defined by the logoutURL parameter.

  • If the logoutUrl parameter is not defined, uses the logout Url of the Security Provider configuration.

  • After the user is logged out, the application continues to the default page of the application.

Example 11-24 Example

An example of a chain using the logoutAction:

"logoutChain": {
  "root": "logout",
  "actions": {
    "logout": {
      "module": "vb/action/builtin/logoutAction"
    }
  }
}

Navigate Action

The action module for this action is "vb/action/builtin/navigateAction".

This action will navigate the user to a page and also pass any parameters to activate that page. Parameters for this action are:
Parameter Name Description
page The path to the destination page. The path can be a single page ID, or a path starting with a page ID. It can be an absolute path starting at the application or relative to the current page.
flow ID of the destination flow, used to change the content of the flow displayed in the current page. When used with 'page', navigates to the page in that flow.
target Target of the destination flow, used to change the content of the parent flow instead of the nested flow. Values are 'parent' or 'self' (default).
params A key/value pair map that will be used to pass parameters to a page (optional)
history Defines the effect on the browser history. Values are 'replace', 'skip' or 'push'. If the value is 'replace', the current browser history entry is replaced, meaning that the back button will not go back to it. If the value is 'skip', the URL is not modified. (optional and default is 'push')

Page input parameters are page variables with the Input Parameter enabled. You can use the Navigate action to set the value for these input parameters. But if a page parameter was a path to a deeply nested page, like /shell/main/other, you'll see a list of all input parameters from each page/flow in the path (that is, input parameters for the shell page, the main flow, as well as other pages). Name collisions across flows/pages are not accounted for—something you'll need to keep in mind when defining input parameters.

Here's an example of the navigate action:
"myActionChain": {
  "root": "navigate",
  "actions": {    
    "navigate": {
      "module": "vb/action/builtin/navigateAction",
      "parameters": {
        "page": "myOtherPage",
          "params": {
            "id": "{{ $page.variables.myId }}"
         }
       }
     }
   }
 }

This returns the outcome 'success' if there was no error during navigation. If navigation completed successfully, returns the action result true, otherwise false. Returns the outcome fail with the error in the payload if there was an error.

Navigation with the page parameter

The 'page' parameter is the ID of a sibling page or a path starting with a sibling page's ID (like pageId/flowId/...). It cannot be or start with a flow ID.

Example 11-25 Navigate to a sibling of the current page

To navigate to page other, a sibling of the current page:
navigate({
  page: 'other'
});

Example 11-26 Navigate to a sibling page and change content of the nested flow

To navigate to page other and change its content to be the flow main:
navigate({
  page: 'other/main'
});

Example 11-27 Navigate to the root application

To navigate to the root of the application:
navigate({
  page: '/'
});

Example 11-28 Navigate to the current flow's default page

To navigate to the current flow's default page:
navigate({
  page: ''
});

Example 11-29 Navigate to a deeply nested page relative to the application root

To navigate to a deeply nested page relative to the root of the application:
navigate({
  page: '/shell/main/other'
});

Navigation with the flow parameter

The 'flow' parameter can only be the ID of a flow defined below the current page or an empty string.

Example 11-30 Navigate to a specific flow

To change the content of the flow displayed in the current page to the flow main:
navigate({
  flow: 'main',
});

Example 11-31 Navigate to a page in a specific flow

To change the content of the flow displayed in the current page to the flow main and navigate to the page other or the flow main:
navigate({
  flow: 'main',
  page: 'other',
});

Example 11-32 Navigate to the current page's default flow

To navigate to the current page's default flow:
navigate({
  flow: '',
});

Example 11-33 Navigate a parent to a specific flow

To change the parent flow to the flow main:
navigate({
  flow: 'main',
  target: 'parent',
});

Example 11-34 Navigate a parent to the default flow

To change the parent flow to the default flow:
navigate({
  flow: '',
  target: 'parent',
});

Navigate Back Action

The action module for this action is "vb/action/builtin/navigateBackAction".

This action will go back one step in browser history. It has a single 'success' outcome and can return a payload by specifying values for the input parameters.

Parameter Name Description
params An optional key/value pair map that will be used to pass parameters to a page.

When a parameter is not specified, the original value of the input parameter on the destination page is used. When a parameter is specified, it has precedence over fromUrl parameters.

Open URL Action

The action module for this action is "vb/action/builtin/openUrlAction".

In a web app, this action opens the specified URL in the current window or in a new window using the window.open() API.

In a native mobile app, this action supports opening local file attachments as well as remote resources. Allowed file types for the url parameter are as follows:

  • .pdf
  • .doc
  • .txt
  • .text
  • .ppt
  • .rtf
  • .xls
  • .mp3
  • .mp4
  • .csv

The very first time, the user will get an option to select which application to use for opening a given file type. If no application is available to open such a file, this action will fail with the appropriate error. Once the given file has been opened once, it will always be opened with the same application across all Visual Builder installed apps on the device.

If the specified file is not local or if the file extension is not recognized, this action will use Cordova's plugin cordova-plugin-inappbrowser to open the specified URL.

Parameter Name Description
url The url to navigate to (required)
params A key/value pair map that will be used as query parameters to the url (optional)
hash The hash entry to append to the URL. (optional)
history Defines the effect on the browser history. Allowed values are 'replace' or 'push'. If the value is 'replace', the current browser history entry is replaced, meaning that the back button will not go back to it. (optional, and default is 'push')
windowName A name identifying the window as defined in the window.open() API (optional). If not defined, the URL opens in the current window. Otherwise, refer to the window.open() API documentation. In a mobile app, there are 3 possible values: _self, _blank, or _system. The default is _self. Refer to the documentation for cordova-plugin-inappbrowser. For local file types, this parameter is ignored.

Once on the URL location, the browser back button will re-enter the last page if you specified a value for the windowName parameter that opens the URL in the current window and the page input parameters will be remembered, even if their type is 'fromCaller'.

Example 11-35 Open a new window in the browser with the given URL

To open a URL:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/openUrlAction",
      "parameters": {
        "url": "http://www.example.com",
        "params": {
          "id": "{{ $page.variables.myId }}"
        },
        "windowName": "myOtherWindow"
      }
    }
  }
}

Reset Variables Action

Use this action to reset variables to their default values defined in their variable definitions.

The action module for this action is vb/action/builtin/resetVariablesAction.

Parameter Name Description
variables

An array of variables. Here is an example.

["$page.variables.var1", "$page.variables.var2"]

Note:

If a single variable expression is provided instead of an array, it will be implicitly treated as an array of one variable.
Each expression in the array has to resolve to a variable or variable property. It has to be prefixed with one of the following:
  • $application.variables
  • $page.variables
  • $chain.variables

Each expression should be followed by a variable name or a path to a variable property. For example:
  • $application.variables.a
  • $page.variables.a.b
  • $variables.a.b.c (which is shorthand for $chain.variables.a.b.c)

Return Action

The action module for this action is "vb/action/builtin/returnAction".

This action (which should be the terminal action of a chain) allows you to control the outcome and payload of that chain when necessary. Parameters for this action are as follows:

Parameter Name Description
payload The payload to return from this action. Useful in a 'callChainAction" to control the resulting payload from calling that action chain. This can be an expression.
outcome The outcome to return from this action. Useful in a 'callChainAction" to control the resulting outcome from calling that action chain. This can be an expression.

An example that uses the return action on a chain that makes a REST call, but returns a simpler value:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "someRestCall": {
      "module": "vb/action/builtin/callRestAction",
      "parameters": {...},
      "outcomes": {
        "success": "myReturnAction"
      }
    }
    "myReturnAction": {
      "module": "vb/action/builtin/returnAction",
      "parameters": {
        "outcome": "success",
        "payload": "{{ $chain.results.someRestCall.body.somewhere.inthe.payload.isa.string }}"
      }
    }
  }
}

This will return a simple string on a successful REST call if this action chain was called via the 'callChainAction'.

Run in Parallel / Fork Action

The action module for this action is "vb/action/builtin/forkAction".

This action allows multiple action chain paths to run in parallel, then wait for their responses and produce a combined result. Normally, if you do not care what your action chains return, you can chain multiple action chains on the event handler. If you want to wait for the result, and take action once everything is complete, you can use this action instead. 

A fork action has an arbitrary set of actions whose action sub-chains will run in parallel. A special outcome, 'join', will be followed once all the sub-chains complete processing. The outcome of the fork action is always 'join', and the result is a mapping from the outcome id's of the sub-chains to their outcome/result payload.

This action takes one parameter, "actions", which is a map of an action alias, to an Action ID in the chain. The alias is the property name used in the results of the Fork action results (an alias allows the same Action to be called multiple times in the same Fork Action).

Example 11-36 Example

To make two REST calls, then do some assignments only after they both complete:

"myActionChains": {
  "root": "myAction",
  "actions": {
  "myForkAction": {
    "module": "vb/action/builtin/forkAction",
    "parameters": {
      "orcl": "orcl",
      "crm": "crm",
    },
    "outcomes": {
      "join": "join"
    },
  "orcl": {
    "module": "vb/action/builtin/restAction",
    "parameters": {
      "endpoint": "stock/get-stock-quote",
      "uriParams": { "stock": "ORCL" }
    }
  }
  "crm": {
    "module": "vb/action/builtin/restAction",
    "parameters": {
      "endpoint": "stock/get-stock-quote",
      "uriParams": { "stock": "CRM" }
     }
   },
   "join": {
      "module": "vb/action/builtin/assignVariablesAction",
      "parameters": {
        "$page.variables.orcl": { "source": "{{ '' + $chain.results.getAllStockQuotes.orcl.result.body }}" },
        "$page.variables.crm": { "source": "{{ '' + $chain.results.getAllStockQuotes.crm.result.body }}" }
      }
    }
  }
}

Scan Barcode Action

Use this action in your mobile application to scan QR codes and barcodes for details such as URLs, Wi-Fi connections, and contact information.

The action module for this action is vb/action/builtin/barcodeAction. Parameters for this action are:
Parameter Name Description
image An image object, which can be a CanvasImageSource, Blob, ImageData, or an <img> element
formats Optional: A series of barcode formats to search for, for example, one or more of the following:

['aztec', 'code_128', 'code_39', 'code_93', 'codabar', 'data_matrix', 'ean_13', 'ean_8', itf', 'pdf417', 'qr_code', 'upc_a', 'upc_e']

Note that all formats may not be supported on all platforms.

If formats is not specified, the browser will search all supported formats, so limiting the search to a particular subset of supported formats may provide better performance.

Here's an example of the barcodeAction's metadata used to read QR code from an HTML image element:
"fromImage": {
          "module": "vb/action/builtin/barcodeAction",
          "parameters": {
             "image":  "[ document.querySelector('#qrcode') ]",
             "formats": "[[ [ 'qr_code' ] ]]"
           },
           "outcomes": {
             "failure": "showError",
             "success": "openUrl"
           }
         }

A success outcome will include the DetectedBarcode object as a result. DetectedBarcode (https://wicg.github.io/shape-detection-api/#detectedbarcode) has a rawValue property that corresponds to the decoded string. A failure outcome will be returned if the browser does not support Shape Detection API, or if a specified format is not supported.

Share Action

Use this action in mobile applications to invoke the native sharing capabilities of the host platform and share content with other applications, such as Facebook, Twitter, Slack, SMS and so on.

The action module for this action is "vb/action/builtin/webShareAction".

Invoke this action following a user gesture, such as a button click. Also, we recommend that the share UI should only be shown if navigator.share is supported in the given browser, as in this HTML code:
<oj-button disabled="[[!navigator.share]]">Share</oj-button>
Parameter Name Description
title Optional. Represents the title of the document being shared. This value may be ignored by the target.
text Optional. Text that forms the body of the message being shared. Can be specified with or without a URL.
url Optional. URL string that refers to the resource being shared. Any URL can be shared, not just URLs under website's current scope.

Although all parameters are individually optional, you must specify at least one parameter.

Example:

"share": {
  "module": "vb/action/builtin/webShareAction",
  "parameters": {
    "text": "Check out this cool new app!",
    "title": "[[document.querySelector('h1').textContent]]",
    "url": "[[ document.querySelector('link[rel=canonical]') && document.querySelector('link[rel=canonical]').href || window.location.href]]",
  },
  "outcomes": {
    "failure": "handleShareError"
  }
}

A success outcome is returned when the user completes a share action. A failure outcome is returned when the browser does not support the Web Share API or a parameter error is detected.

Switch Action

The action module for this action is "vb/action/builtin/switchAction".

This action will evaluate an expression and create an outcome with that value as the outcome name. An outcome of "default" is used when the expression does not evaluate to a usable string.

Parameter Name Description
caseValue This value is used as the outcome value. If null or undefined, the outcome is "default".
possibleValues Optional. Array of strings, representing the allowed outcomes. If caseValue evaluates to something not in this array, the outcome is "default".

Example:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/switchAction",
      "parameters": {
        "caseValue": "{{ $chain.variables.myCase }}",
        "possibleValues": ["case1", "case2"]
      },
      "outcomes": {
        "case1": "...",
        "case2": "...",
        "default": "..."
      }
    }
  }
}

Take Photo Action

Use this action in your application to take photos or choose images from the system's image library.

The action module for this action is vb/action/builtin/takePhotoAction.

The behavior of this action depends on the type of application that you use it in:

  • Web application: Opens a File selection dialog to select a file
  • iOS application: Prompts user with multiple options, such as Camera, Browse, or Like
  • Android application: Prompts user with options, such as Camera, Browse, or Cancel
  • Progressive web apps on Android and iOS: Prompts user with multiple options, such as Camera, Browse, or Like
Parameter Name Description
mediaType

Set to image by default. The video type is also supported.

Clear the image input value from the Media Type dropdown list if you want your mobile application to use the deprecated Take Photo action implementation from pre-19.1.3 releases. The pre-19.1.3 Take Photo action can only be used in Android and iOS applications.

If mediaType is set to video:

  • In Web apps, the open dialog will filter out any non-video files.
  • For iOS Native apps, options to record video using the Camera or to select video files will be provided.
  • For Android Native apps, only file selection is allowed. Recording using the Camera is not supported.
  • For PWA apps on iOS and Android, options to record video using the Camera or to select video files will be provided.

Example 11-37 Example

The outcome of this action is a binary data object (blob) duck-typed as File. The outcome name is file.


// To use the outcome file in images, use the URL.createObjectURL and URL.revokeObjectURL 
// methods, as in the following example
const blobURL = URL.createObjectURL(fileBlob);

// Release the BLOB after it loads.
document.getElementById("img-712450837-1").onload = function () {
    URL.revokeObjectURL(blobURL);
};

// Set the image source to the BLOB URL
document.getElementById("img-712450837-1").src = blobURL;


// To upload the selected/captured image or video, use restAction and set the body of 
// restAction to the outcome file of takePhotoAction.
"takePhoto1": {
    "module": "vb/action/builtin/takePhotoAction",
    "parameters": {
        "mediaType": "image"
    },
    "outcomes": {
        "success": "callTakePhotoSuccess",
        "failure": "callTakePhotoFailed"
    }
},
"callRestEndpoint1": {
    "module": "vb/action/builtin/restAction",
    "parameters": {
        "endpoint": "OracleCom/postUpload",
        "body": "{{ $chain.results.takePhoto1.file }}", // <- File is set as body of restAction
        "contentType": "image/jpeg"
    },
    "outcomes": {
        "success": "callUploadSuccess",
        "failure": "callUploadFailed"
    }
},
"callUploadFailed": {
    "module": "vb/action/builtin/callModuleFunctionAction",
    "parameters": {
        "module": "{{$page.functions}}",
        "functionName": "uploadFailed",
        "params": [
            "{{ $chain.results.callRestEndpoint1.body }}"
        ]
    }
},
"callUploadSuccess": {
    "module": "vb/action/builtin/callModuleFunctionAction",
    "parameters": {
        "module": "{{$page.functions}}",
        "functionName": "uploadSuccess",
        "params": [
            "{{ $chain.results.callRestEndpoint1.body }}"
        ]
    }
},

Transform Chart Data Action

The action module for this action is "vb/action/builtin/transformChartDataAction".

Transforms a JSON array with a particular structure into a JSON object containing (array) properties that JET chart component expects.

Page Authors can use this action to take the response from a REST action, turn into a format that this action expects, and use the result returned by this action to assign to a variable bound to the chart component. 

The action supports the following parameter.

Parameter Name Type Description Example
source Array<Object> An array of objects, or data points, where each data point has one of the two structures below. The first is used with charts that show groups of data for one or more series, such as bar and pie. The second is used with charts that show three dimensions of data, such as bubble.
// Structure 1
{
  group: '<group-name>',
  series: '<series-name>',
  value: '<value-number>'
}
// Structure 2
{
  group: '<group-name>',
  series: '<series-name>',
  valueX: '<valueX-number>',
  valueY: '<valueY-number>',
  valueZ: '<valueZ-number>'
}
// JSON for Structure 1
[{
  group: 'bob',
  series: 'Feb',
  value: 5
}, {
  group: 'joe',
  series: 'Feb',
  value: 2
}]
// JSON for Structure 2
[{
  group: 'bob',
  series: 'Feb',
  valueX: 5,
  valueY: 1,
  valueZ: 3
}, {
  group: 'joe',
  series: 'Feb',
  valueX: 6,
  valueY: 2,
  valueZ: 4
}]

The action returns a JSON object with the following properties.

Return Type Description Example
Object The Object has two properties. The properties differ based on the structure that's passed in.
  • groups: {Array} of one or more group names
  • series: {Array} of objects where each object has 2 properties: name and items
    • name: {String} name of the series
    • items:
      • {Array} of numbers when the input resembles the Structure 1 above; or
      • {Array} of objects, when the input resembles the second structure above, with each object containing the following properties:
        • x: {Number}
        • y: {Number}
        • z: {Number}
// Return Value for Structure 1
{
  groups: ['bob', 'joe'],
  series: [{
    name: 'Feb',
    items: [5, 2]
  }]
}
// Return Value for Structure 2
{
  groups: ['bob', 'joe'],
  series: [{
    name: 'Feb',
    items: [{
      x: 5,
      y: 1,
      z: 3
    }, {
      x: 6,
      y: 2,
      z: 4
    }]
  }]




}

The example below shows a chain called "fetchTechnicianStatsChain" with four actions chained together to take a REST response and turn the JSON response into a form that can be used by a Chart UI component. The four actions are:

  1. Use a Call REST endpoint action to fetch technician stats.

  2. Use an Assign Variables action to map the response from (1) to a form that the Transform Chart Data action expects. If the REST response is so deeply nested that a simple transformation of source to target using an Assign Variables action is not possible, page authors can use a page function (using a Call Function action) to transform the data into a form that the Transform Chart Data action expects.

  3. Use a Transform Chart Data action to take the response from (2) and turn it into a form that a Chart component can consume.

  4. Use an Assign Variables action to store the return value from (3) in a page variable.

"actions": {
    "fetchTechnicianStatsChain": {
        "variables": {
            "flattenedArray": {
                "type": [
                    {
                        "group": "string",
                        "series": "string",
                        "value": "string"
                    }
                ],
                "description": "array of data points",
                "input": "none"
            }
        },
        "root": "fetchTechnicianStats",
        "actions": {
            "fetchTechnicianStats": {                                       // (1)
                "module": "vb/action/builtin/restAction",
                "parameters": {
                    "endpoint": "ifixitfast-service/getTechnicianStats",
                    "uriParams": {
                        "technician": "{{ $page.variables.technician }}"
                    }
                },
                "outcomes": {
                    "success": "flattenDataForBar"
                }
            },
            "flattenDataForBar": {                                          // (2)
                "module": "vb/action/builtin/assignVariablesAction",
                "parameters": {
                    "$chain.variables.flattenedArray": {
                        "source": "{{ $chain.results.fetchTechnicianStats.body.metrics }}",
                        "reset": "toDefault",
                        "mapping": {
                            "$target.group": "$source.technician",
                            "$target.series": "$source.month",
                            "$target.value": "$source.incidentCount"
                        }
                    }
                },
                "outcomes": {
                    "success": "transformToBarChartData"
                }
            },
            "transformToBarChartData": {                                    // (3)
                "module": "vb/action/builtin/transformChartDataAction",
                "parameters": {
                    "source": "{{ $chain.variables.flattenedArray }}"
                },
                "outcomes": {
                    "success": "assignToPageVariable"
                }
            },
            "assignToPageVariable": {                                      // (4)
                "module": "vb/action/builtin/assignVariablesAction",
                "parameters": {
                    "$page.variables.incidentChartDS": {
                        "source": "{{ $chain.results.transformToBarChartData }}",
                        "reset": "toDefault"
                    }
                }
            }
        }
    }
}

Web Share Action

The action module for this action is "vb/action/builtin/webShareAction".

Web Share action allows mobile applications to invoke the native sharing capabilities of the host platform and share content with other applications, such as Facebook, Twitter, Slack, SMS, etc. This action should only be invoked following a user gesture (such as a button click). It is a good idea to only enable share UI based of feature detection:

<oj-button disabled="[[!navigator.share]]">Share</oj-button>

Web Share action parameters correspond to Web Share API options:

The action supports the following parameters.

Parameter Name Description
title Title of the document being shared. May be ignored by the handler/target.
text An arbitrary text that forms the body of the message being shared.
url A URL string referring to a resource being shared.

All parameters are individually optional, but at least one parameter has to be specified. Any url can be shared, not just urls under website's current scope. Text can be shared with or without a url.

The example below illustrates an action's parameters one would specify to share the current page's title and url:

"share": {
  "module": "vb/action/builtin/webShareAction",
  "parameters": {
    "text": "Check out this cool new app!",
    "title": "[[document.querySelector('h1').textContent]]",
    "url": "[[ document.querySelector('link[rel=canonical]') && document.querySelector('link[rel=canonical]').href || window.location.href]]",  },
  "outcomes": {
    "failure": "handleShareError"
  }
}

A success outcome is returned once user has completed a share action. A failure outcome is returned when browser does not support Web Share API or a parameter error is detected.

Action Chains

Action chains are defined under the 'chains' property of the page model.

Action Chain Properties

An action chain has two properties: the set of variables it can use, and the root action.

Action chains are defined under the 'chains' property of the page model. An action chain always has a root action. This root action will always be called when the action chain is invoked.

This action chain will call the 'myAction' action:

"chains": {
  "myActionChain": {
    "root": "myAction",
    "actions": {
      "myAction": {
        "label": "My action!",
        "module": "vb/action/builtin/someAction",
        "parameters": {
          "key": "value"
        }
      }
    }
  }
}

Each action has an outcome. Usually, an action supports the "success" or "error" outcomes. Some actions may also support other outcomes. Actions can be chained by connecting an additional action to a previous action's outcome.

To perform another action if the previous action succeeds, and handle error cases if it does not succeed, you could do the following:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "key": "value"
      },
      "outcomes": {
        "success": "mySuccessAction",
        "error": "myErrorAction"
      }
    },
    "mySuccessAction": {
      "module": "vb/action/builtin/someAction"
    },
    "myErrorAction": {
      "module": "vb/action/builtin/someAction"
    }
  }
}

Variable References in Action Chains

Variables can be referenced for the parameter values of an action.

The runtime will automatically evaluate parameter values as expressions. Similar to the default value syntax of variables, variables can be referenced directly into an action parameter's value:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "label": "some action",
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "key": "{{ $page.variables.myVariable }}"
      }
    }
  }
}

Simple JavaScript code can be added to the values:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "label": "some action",
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "key": "{{ $page.variables.myVariable === 'yellow' }}"
      }
    }
  }
}

Non-expressions are entered in JSON:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "myString": "somestaticvalue",
        "myNumber": 1
        "myBoolean": true
      }
    }
  }
}

Map and array values are also expressed in JSON:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "key": { 
          "key1": "static value", 
          "key2": "{{ $page.variables.something }}"
        }
      }
    }
  }
}

Action Chain Variables

An action chain can also have variables. These are defined and used in the same way as page parameters.

Unlike page parameters, input variables only support the 'fromCaller' or 'none' type. Input variables must be specified by event handlers calling into action chains. 

"myActionChain": {
  "variables": {
    "id": {
      "type": "string",
      "description": "the ID of something to update",
      "input": "fromCaller",
      "required": true
    }
  },
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction"
    }
  }
}

Action chain variables can be assigned to or read from using the syntax $chain.variables.varName and are only accessible within an action chain. They can also be referenced by the shorthand $variables.varName within the chain.

Action Results

Actions in an action chain can return a result that can be used by subsequent actions in the chain.

After an action implementation is run, it may return a result.  The type of these results are specific to an implementation of an action. This result will be stored in a special variable, $chain.results. The results of a previous action are contained within $chain.results.<actionId>.

Example 11-38 Accessing a Previous Action`s Results

To access a previous action's results:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction",
      "outcomes": {
        "success": "someOtherAction"
      }
    },
    "someOtherAction": {
      "module": "vb/action/builtin/someAction",
      "parameters": {
        "myKey": "{{ $chain.results.myAction }}"
      }
    }
  }
}

Example 11-39 Action Chain Return Type and Outcomes

You can specify a return type and an array of outcomes. If a return type is specified, the result of the final outcome will be auto-mapped into the return type. If "outcomes" is specified, the name of the final outcome must match one of the possible outcomes. Otherwise, the action chain will fail. Here is an example:

"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/someAction"
    }
  },
  "returnType": "application:someType",
  "outcomes": ["success", "failure"]
}

Action "failure" Outcomes

Actions return a standard object shape when returning a "failure" outcome. The result will be an object with the following properties:

  • "message": may contain one optional "summary" string property

  • "error": may contain an Error object

  • "payload": may contain any Action-specific additional information about the failure

Components

Components are written as an HTML file, using standard HTML syntax.

HTML Source

Components are written as standard HTML files.

The HTML file for a page is located as a peer to the page model, as name-page.html. This HTML source can be edited as a normal JET page.

There are currently two kinds of expressions, write-back and no write-back. This can be seen in the component properties.


<oj-input-text maxlength='30' placeholder="[[$variables.searchText]]"
               value="{{$variables.searchVariable}}"</oj-input-text>

Component Events

Also known as DOM events, component events are similar to page and flow events, except that they are fired by components on a page.

An event listener can have any name, but must be bound to a component event. Component event listeners are defined in the Page module under the 'eventListeners' property. Custom events also propagate up the page's container hierarchy, executing any handlers found in parent containers or their shells. Event propagation can be stopped.

Component event listeners are defined within the eventListeners section of the page model, along with Page lifecycle events:

"eventListeners": {
  "onSelectionChange": {
    "chains": [
      {
        "chainId": "respondToChange",
        "parameters": {
          "text": "{{ $event.detail.value }}"
        }
      }
    ]
  }
}

Component event listeners are called in the same way as page lifecycle event listeners. There can be more than one listener. When there is more than one, they run in parallel. 

To reference an event listener from a component, you can use the $listeners.eventListenerName implicit object. For example:

<oj-select-single ... on-selection-change="[[$listeners.onSelectionChange]]"

Component Event Objects

Within the context of component event listeners, there are three implicit objects.

  • $event: The event payload sent by the component.

  • $current: This represents the second parameter passed to the handler, if any. For JET, this can be either the "$current" binding variable, or the "$data" variable if $current does not exist in the component context.

  • $bindingContext: represents the third parameter passed, if any. For JET, this is the (Knockout) view model, and it will therefore contain the $current or $data variable as a property.

These variables do not exist outside the listener context. In other words, you can reference these in the listener declaration, but you cannot reference them in the called action chain; any values needed in these variables must be passed explicitly to the action chain as arguments (chain variables).

These three variables represent the arguments passed to the listener, and are not directly tied to specific JET values. Their meaning could be different depending on the context.

For example, if using an event listener within an <oj-list-item> item, the value of $current could be different whether you are using the item.renderer attribute or the itemTemplate slot to display the item.

  • Within an item.renderer script, JET does not define $current, so instead passes $data as the second argument, so the Visual Builder $current is JET/Knockout $data. In some JET contexts, like anitem.renderer script, you will also need to prefix Visual Builder listeners with (Knockout) $parent in the HTML.

  • Within an itemTemplate slot, JET defines $current, and passes that, so Visual Builder $current is JET $current.

To determine whether JET $current exists for your use case., refer to the JET documentation for the component to which you are adding a listener.

Additionally, the developer could decide to pass their own custom object for the parameters. In the example below, the listener is wrapped, so Visual Builder $current is "some string", and Visual Builder $bindingContext is undefined.

<oj-button on-click="{{ function(event, current, bindingContext) { $page.listeners.someListener(event, "some string") } }}">
  Click Me!
</oj-button>

Component Event Listener "preventDefault" Property

Component event listeners have an additional "preventDefault" property, which can be used to prevent the normal DOM event handling from being executed.

This example uses an expression to check the payload of the event to stop propagation:

"eventListeners": {
  "customEventTwo": {
      "chains": [
        {
          "actionsId": "handleEventInMod2PageChain",
          "parameters": {
            "eventPayload": "{{ $event }}"
          },
        }
      ],
      "preventDefault": "{{ $event.type === 'info' }}"
  }

Custom Events

Custom events are similar to page events, except that they are not limited to lifecycles. Their event listeners can be defined in a page, flow, or application.

An event name is defined by the user, and is explicitly fired by the application, using the event Actions provided, in the context of a page.

Custom event listeners are defined in the page or flow under the eventListeners property.

One difference between custom events and page events is that they 'bubble' up the containment hierarchy. Any event listeners in a given flow or page for the event are executed before looking for listeners in the container's parent. The order of container processing is:
  • The page from where the event is fired.

  • The flow containing the page.

  • The page containing the flow.

  • Recursively up the containment, ending with the application.

Custom and system event behavior can be modified using the stopPropagation property, which prevents the event from bubbling to this event listener's container's parents.

Example 11-40 stopPropagation Example

"eventListeners": {
  "customEventTwo": {
    "stopPropagation": "{{ $event.type === 'info' }}"
    "chains": [
      {
        "actionsId": "handleEventInMod2PageChain",
        "parameters": {
          "eventPayload": "{{ $event }}"
        }
      }
    ],
  }... 

vbNotification Events

The vbNotification event is a built-in custom event, rather than a page, flow, or application event, as it is an event only explicitly fired by the application using the action 'vb/action/builtin/fireNotificationEventAction' (see Fire Notification Event Action)

The payload is an object with these properties:

  • "summary": a short summary, subject, or title

  • "message": any text meaningful to the application

  • "displayMode": "persist" or "transient"

  • "type": "error", "warning", "info", or "confirmation"

  • "key": an optional GUID, which may be useful for the UI. If not provided, one is generated and provided in the payload.

Import Custom Components

JET Custom Components can be loaded using the "imports" section in a shell or page.

The 'components' section contains a map of component IDs to objects which contain a (requireJS) path to the JET Custom Components loader javascript. The ID should match the component tag.

Example 11-41 Example:

"imports": {
  "components": {
    "demo-card": {
      "path": "resources/components/democard/loader"
    }
  }
}

Security

The security entry provides certain access limits.

The security entry provides a way to limit access to UI level artifacts, such as pages, flows, or applications. These artifacts can require either a specific role or a specific permission in order to enter and display the resource. If the user does not have the correct role or permission, the runtime will refuse entry into that UI artifact. Currently the application, flows, and individual pages can be protected in this manner.

Security Configuration

The security configuration is managed in several resources.

The configuration for security resides in the model for each of these resources: app-flow.jsonname-flow.jsonname-page.json. If requiresAuthentication is false, specifying roles or permissions results in an error. By default an artifact inherits the requiresAuthentication property from its parent. If this is not present in the application configuration, it defaults to true. This means that if no security section is defined in any of the artifacts, the application will require authentication when starting.

The configuration follows the format seen in this example:

"security": {
  "access": {
    "requiresAuthentication": true/false,
    "roles": ["role1", "role2"],
    "permissions": ["perm1", "perm2"]
  }
}

When an anonymous user navigates to an artifact (page, flow or application) and the artifact is secure, the user is prompted to login, and is then redirected to the artifact. This functionality is provided by the default implementation of the Security Provider.

Security Provider

Security for an application is enabled using a pluggable mechanism called Security Providers.

In the application model, app-flow.json, you can specify a "userConfig" element. The userConfig element selects which Security Provider to use and how to configure it:

Example of an entry in app-flow.json to specify the Security Provider

"userConfig": {
  "type": "vb/DefaultSecurityProvider",
  "configuration": {
    "url": "url to some security api"
  }
}

A Security Provider takes a configuration object with a url. The url property should point to a REST API. It must be possible to retrieve the current Security Provider configuration via this REST API. The configuration contains user information and configuration information such as loginUrl and logoutUrl.

A Security Provider performs the following functions.

Function Description
fetchCurrentUser(config) Fetch the configuration from the url and initialize the userInfo property as well as the loginUrl and logoutUrl properties.
static getUserInfoType() Return an object describing the type of the user info.
isAccessAllowed(type, path, accessInfo Check if the current user can access a resource with the given access info. If the user is not authenticated, this method returns false. Otherwise, if the user role is one of the roles in accessInfo, or if the user permission is one of the permissions in accessInfo, then the method returns true.
handleLoadError(error, returnPath) This function is called by the client when an error occurs while loading a page. It attempts to handle the load error for a Visual Builder artifact, and returns true if it does.
handleLogin(returnPath) Handle the user login process. Redirects to the login page using the login URL given by the security provider configuration. If defined, the returnPath is added to the login URL using the query parameter name. This is defined in the 'returnPathQueryParam' property of the SecurityProvider class.
handleLogout(logoutUrl) Handle the user logout process. The default implementation navigates to the URL defined by the logoutUrl argument. If the logoutUrl argument is not defined, it uses the logoutUrl of the SecurityProvider configuration.

User Information

The userInfo contains the user information fetched by the Security Provider. 

For the default implementation, the userInfo has the following type:
{
  "userId": "string",
  "fullName": "string",
  "email": "string",
  "roles": "string[]",
  "permissions": "string[]",
  "isAuthenticated": "boolean"
}

The userInfo is made available to the application with the help of the $application.user built in variable. This allows content in the page to be rendered conditionally.

Example 11-42 Example of conditional content rendering

<!-- Render 'I am a manager' if manager is a role of the current user -->
<oj-bind-if test='[[!$application.user.roles.manager]]'>
    I am a manager
</oj-bind-if>


<!-- Render the 'Sign In' button if the current user is not authenticated -->
<oj-bind-if test='[[!$application.user.isAuthenticated]]'>
  <oj-button id='signIn' on-oj-action='[[$listeners.onSignIn]]'Sign In</oj-button>
</oj-bind-if>

Error Handling

Support for unauthorized error handling is provided by several functions.

When loading an artifact returns an error, the function handleLoadError is called with an error object that has a statusCode property. If the artifact is secure and the roles and permissions of the current user do not match the ones required by the artifact, the error statusCode is 403. The default implementation of the handleLoadError will check if the user is authenticated, and if not, will call the handleLogin function. This redirects to the loginUrl provided by the Security Provider configuration.

The default implementation of the Security Provider handles status 401 and 403 errors. Other security schemes will need to implement their own security provider and specify it in the UserConfig section of the application descriptor. To implement your own security provider:

  1. Create your own class extending vb/types/securityProvider and override any method necessary.

  2. If the user information is different, make sure to match the content of the userInfo property and the type information returned by getUserInfoType(), since this determines what information is exposed in the $application.user variable.

  3. Enter your new type in the "type" section of the userConfig in app-flow.json as well as the URL to retrieve the Security Provider configuration.

Example 11-43 Example of a custom Security Provider

define(['vb/types/securityProvider'],
(SecurityProvider) => {
  class TestSecurityProvider extends SecurityProvider {
    handleLogin(returnPath) {
      // implement your own login mechanism here
    }
  }
 
  return TestSecurityProvider;
});

Translations

The Translations API makes it possible to get localized strings using $container.translations.

Translation bundles may now be defined declaratively in Application, Flow, or Page containers. The properties of the "translations" object are the names of the bundle, and the value must contain a "path" property that is the path to the bundle.

When you declare a bundle at the Application level, an optional "merge" property allows you to specify an existing bundle path, which this bundle should merge with and override. This allows overriding existing bundles in JET, or JET CCs, with Application-level bundles. Expressions for "merge" are supported, but they cannot reference Application artifacts, as this evaluation happens before the creation of the Application.

The following paths are supported for "path":

  • container relative: a path fragment prefixed by "./" (dot-slash) will be appended to the declaring container's (flow, page) path. Note that flows and pages are not allowed to reach outside of its container (the path cannot reference parent folders). This means that "../" is not allowed anywhere in the path.

  • application relative: a path fragment without a "./" prefix will be relative to the application root. This is discouraged for Flows or Pages, except where a RequireJS path mapping is being used.

  • absolute: paths that contain a host are used as-is.

The bundle must be in a folder named nls : the path can be any depth, but the last folder in the path must be nls, such that the root bundle is in the nls/ folder.

Translation bundles have the standard JET bundle format. String resolution uses the JET oj.Config.getLocale() to get the current locale for the context.

Example 11-44 Bundles

Two bundles, translations.js and moreTranslations.js, are defined in a Page model JSON, named "app" and "anotherBundle":

"translations": {
  "app": {
    "path": "./resources/nls/translations"
  },
  "anotherBundle": {
    "path": "./resources/nls/moreTranslations"
  }
},

The corresponding expression syntax would be as follows, with one expression per bundle:

<h4><oj-bind-text value="[[$page.translations.anotherBundle.description]]"</oj-bind-text></h4>
<span> 
  <oj-bind-text value="[[$page.translations.format('app', 'info.instructions', { pageName: 'index.html' }) ]]"</oj-bind-text> 
</span> 
<br/>

Example 11-45 Overriding both JET strings and a component's strings

{
  "applicationModelVersion": "19.4.3",
  "id": "demoCardDemo",
  "description": "Custom Component, Demo Card, with methods",
  "defaultPage": "shell",
  "translations": {
    "main": {
      "path": "resources/nls/translations",
      "merge": "ojtranslations/nls/ojtranslations"
    },
    "dcoverride": {
      "path": "resources/nls/demo-card-overrides",
      "merge": "resources/components/democard/resources/nls/demo-card-translations"
    }
  },

Existing Applications That Use Translations

Applications that used translations prior to 18.2.3 must manually migrate their translations. Translations previously used the JET configuration, and therefore had one bundle for the entire app. You have several options:

  • Declare the bundle. You can choose to break the bundle up logically, but the simplest migration would be to use the exact example above in app-flow.json, which uses the path for the existing bundle provided for new apps.

  • Change the expression syntax to the new syntax. Assuming you declared your single bundle in the same manner as the Bundles example, named "app":

    • For just the translated string, change $application.translations.get(key) to $application.translations.app.key

    • For Strings that require replacement, change $application.translations.get(key, arguments) to $application.translations.format('app', key, arguments)

Specifying the Locale

By default, VB defers to JET to determine the current locale for the client. This is typically done by first looking at the <html> tag 'lang' attribute, and then falling back to some browser settings.

There is a "localization" declaration section in the Application model (app-flow.json) that contains a "locale" property, which allows the developer to specify an alternate locale. This configures the JET ojL10n plugin to use this locale.

Expressions may be used, but the application is not created at this point, and therefore no application functions or variables are available. Instead, the developer must provide the necessary JavaScript. The developer should also set the 'lang' attribute on the <html> tag, so that JET, and anything that uses JET, will also use this locale.

Example 11-46 Locale Example

{
  "applicationModelVersion": "19.4.3",
  "id": "demoCardDemo",
  "description": "Custom Component, Demo Card, with methods",
  "defaultPage": "shell",
  "services": {},
  "translations": {
    "main": {
      "path": "resources/nls/translations",
    },
  },
  "localization": {
    "locale": "{{ determineLocale() }}"
  },
  "types": {}
}

Helper Utilities

The run time provides public JavaScript helpers to help with implementing some features in JavaScript when a lower level of control is desired or needed.

These can be imported in your Javascript module functions.

REST Helper

The REST helper utility allows calling REST endpoints, which are defined in the service definitions.

The Visual Builder runtime uses this helper internally.

The REST helper looks at the content-type header, if available, to try to determine how to read and parse the response. If no content-type is available, text is assumed.

Table 11-2 REST helper content types

Content type Response method
contains "json" Response.json()
starts with "image/" Response.blob()
application/octet-stream Response.blob()

This behavior can be overridden using the responseBodyFormat() method.

The following example shows the use of the REST helper.

define(['vb/helpers/rest'], (Rest) => { 
...
var rest = Rest.get('myservice/myendpoint').parameters(myparameters);
var promise = rest.fetch();

Table 11-3 REST helper methods

Method Parameters Return Value Description
static get(endpointId) endpointId: serverID/operationID, same as RestAction, ServiceDataProvider Instance of REST object Factory method
initConfiguration(initConfig) initConfig: the initConfig of the fetch() Request object REST helper, to allow chaining of method calls See the Request Web API
parameters(parametersMap) parametersMap: object of key/value pairs, same as RestAction 'uriParams' REST helper Set the parameter for the call. Parameters defined as path parameters for the endpoint will be inserted in the URL as appropriate; the rest will be appended as query parameters.
requestTransformationFunctions (transformationFunctionMap) transformationFunctionMap: map of functions. REST helper See Call REST Action
requestTransformationOptions (transformationOptionMap) transformationFunctionMap: map of request transform parameters REST helper See Call REST Action
responseTransformationFunctions (transformationFunctionMap) transformationFunctionMap: map of functions. REST helper See Call REST Action
body(body) body: actual payload to send with the request REST helper -
hookHandler(handler) handler: should extend RestHookHandler, and may override the following:
handlePreFetchHook(rest)
handleRequestHook(request)
             -returns request
handleResponseHook(response)
             -returns response
handlePostFetchHook(result)
handlePostFetchErrorHook(result)
REST helper Allows installation of callbacks for various phases of the REST call, which may configure the REST helpers, modify the request and response, or do special processing based on the result or result error.
define(['vb/helpers/rest', 'vb/helpers/rest'],
(Rest, RestHoookHandler) => {
    class MyHandler extends RestHookHandler {
responseBodyFormat(format) format: one of: text, json, blob, arrayBuffer, base64, or base64Url. The response body type is the same as the corresponding method for Response (except base64, which returns just the encoded portion of the base64 URL). REST helper Overrides the default behavior, which looks at the "content-type" header to determine how to read (and parse) the response.
fetch() - Promise Performs the configured fetch() call

toUrl()

toRelativeUrl()

- Promise Utility methods for building requests and responses that require the endpoint path. Resolves with the full (or relative) path of the endpoint, or empty string if the endpoint is not found.
The REST helper fetch() call returns a Promise that resolves with an object that contains the following properties:

Table 11-4 fetch() call return value

Property Description
response The Response object from the native fetch() call, or the return from a HookHandler's handleResponseHook, if one is being used.
body The body of the response object; the helper will attempt to call the appropriate Response method (json(), blob(), arrayBuffer(), etc) based on responseBodyFormat() and Content-Type.

Module Function Event Builder

Within the context of module functions including main-page.js and app-flow.js, there is an event helper available to allow raising custom events, similar to the Fire Custom Event Action.

The helper is made available to the module function through a context passed to the Module classes constructor, and has two methods available.

Table 11-5 Module function event helper methods

Method Description
fireCustomEvent(name, payload) See Fire Custom Event Action.
fireNotificationEvent(options) See Fire Notification Event Action.

Example 11-47 Usage in a module function

'use strict';

define(function () {
  function MainPageModule(context) {
    this.eventHelper = context.getEventHelper();
  }

  MainPageModule.prototype.fireCustom = function (name, payload) {
    return this.eventHelper.fireCustomEvent(name, payload);
  }

  MainPageModule.prototype.fireNotification = function (subject, message) {
    return this.eventHelper.fireNotificationEvent({ subject, message, type: 'info' });
  }

  return MainPageModule;
});

Events

There are several types of events, all of which the application can react to, using the event listener syntax.

There are several types of events in the runtime: page events, flow events, system events, custom or developer-defined system events, component (DOM) events, and variable events. Event types are all handled by executing action chains.

The application reacts to events through event listeners, which declaratively specify action chains to execute when the event occurs.

Event Listener Syntax

An event listener is an object with the following properties:

  • "chains": an array of action chains to execute; includes "chainId" and optional "parameters".

  • "stopPropagation": optional, used only by custom and component events. An expression that is evaluated immediately; if true, the event will not propagate to the current hander's container's parent.

  • "preventDefault": optional, used only by component events. Like "stopPropagation", it is evaluated immediately. If true, The default (DOM) handling is not executed.

The "chainId" refers to an action chain to trigger when this variable changes. Optional parameters can be sent to the action chain in response to the event (see the next section for more details). To gain access to the old or new values, these are exposed in the $event implicit object, where $event.value is the new value and $event.oldValue is the old value.

The following example defines three event listeners; one for the vbNotification built-in event, a custom event listener, and a component listener. The syntax for all three is the same, though how they are invoked is different:

  • The built-in vbNotification event is called when that event is fired by the system. No explicit wiring of the listener is required. The name identifies which action should invoke this listener.

  • The custom myCustomEventOne, is called when the application explicitly fires that event. As with vbNotification, no explicit wiring of the listener is required.

  • onButtonClicked is a component event, and is explicitly bound to a component action.

"eventListeners": {
  "vbNotification":
    "chains": [
        {
          "chainId": "application:logEventPayloadChain",
          "parameters": {
            "message": "{{ $event.message }}"
            "type": "{{ $event.type }}"
          }
        }
    ]
  },
  "myCustomEventOne": {
    "stopPropagation": "{{ $event.type === 'error' }}",
    "chains": [
      {
        "chainId": "application:fireEventChain",
        "parameters": {
          "name": "customEventOne",
          "payload": {
            "value1": "some value",
            "value2": 3
          }
        }
      }
    ]
  },
  "onButtonClicked": {
    "chains": [
        {
          "chainId": "application:logEventPayloadChain",
          "parameters": {
            "eventPayload": "{{ $event }}"
          }
        }
      ],
   }

The following HTML example shows explicit component event binding:

<oj-button href="#" id='myButton'
           disabled="[[true]]"
           chroming='half'
           on-click='[[$listeners.onButtonClicked]]'>My Button!!!</oj-button>

Declared Events

Declared events are events that are explicitly defined in the application model, to define a specific contract and type for the event.

Events can be declared at the Application, Flow, or Page level. References to events use prefixes, just like variables and chains.

Events may also be declared in Layouts; when used within the Layout, they behave like other Visual Builder events. But to be able to listen to a Layout event outside of the Layout, you must use the the "dynamicComponent" behavior (below).

Events have a "payloadType" which declares the type of the event payload. This type is limited to simple scalar types, or objects and arrays composed of scalar types; you cannot define a "payloadType" that references other type definitions.

Example 11-48 Declaration

"events": {
  "myPageEvent": {
    "payloadType": {
      "message": "string",
      "code": "number"
    }
  }
},

Example 11-49 Event Listener

The "page:" prefix is required only when listening outside the page, but is always recommended for clarity).

"eventListeners": {
  "page:myPageEvent": {
    "chains": [
      {
        "chainId": "handleEvent",
        "parameters": {
          "payload": "{{ $event }}"
        }
      }
    ]
  },

Page and Flow Events

Page and flow events are lifecycle events defined by the system.

Event listeners are defined in a page or flow, and share the name of the event. Page events have a 'vb' prefix. When a page event is raised, the framework calls the event listener with the name of the event, as defined in the page.

Event listeners are defined in the page module under the “eventListeners“ property. Like all event types, a single event can have multiple event listeners. Event listeners call action chains and can pass parameters and return a payload.

"eventListeners": {
  "vbEnter": {
    "chains": [
      {
        "chainId": "handleEnterOne",
        "parameters": {
          "id": "1"
        }
      },
      {
        "chainId": "handleEnterTwo"
      }
    ]
  }
},

The parameters are expressions, and are evaluated just like action parameters. The parameters must of course match the action chain's input variables. If multiple event listeners are defined, they will be called in parallel to each other.

The order of execution during navigation from page source to page target is:
  1. vbBeforeExit is dispatched to the source page.
  2. vbBeforeEnter is dispatched to the target page.
  3. vbExit is dispatched to the source page.
  4. vbEnter is dispatched to the target page.

Table 11-6 Page Event Parameters

Name Description Returns
vbBeforeEnter

Dispatched to a page before navigating to it. Three variable scopes are available:

  • $application: All application variables

  • $flow: All parent flow variables

  • $parameters: All page input parameters from the URL

Page authors are able to cancel navigation to this page by returning an object with the property cancelled set to true. This is useful if the user does not have permission to this page or to redirect to another page.

{cancelled: boolean}
vbEnter

Dispatched after all the page scoped variables have been added and initialized to their default values, values from URL or persisted values. Variable scopes available:

  • $application: All application variables

  • $flow: All parent flow variables

  • $page: All page variables

Use this to trigger data fetches. These may occur concurrently.

None
vbBeforeExit Dispatched to a page before exiting it. This event is can be cancelled, giving the opportunity to the page author to stop navigation away from the page. Cancellation is done by returning an object with the property cancelled set to true to the listener chain. This is useful when the page is dirty and leaving the page should not be allowed before saving. {cancelled: boolean}
vbExit Dispatched when exiting the page. This event can be used to clean up resources before leaving the page. None
vbBeforeAppInstallPrompt Dispatched when a PWA receives BeforeInstallPromptEvent from the browser. The event will be dispatched after vbBeforeEnter, but there is no guarantee that it will be dispatched after vbEnter. The vbBeforeAppInstallPrompt event can be used to display a native application install prompt by calling event.getInstallPromptEvent().prompt(). Currently, this is only supported in Chrome. For PWAs, the event will be handled automatically by the root page. Applies to application scope as well as page and flow. { getInstallPromptEvent() }
vbPause

Dispatched as a response to Apache Cordova's pause event. The event fires when the native platform puts the native mobile application into the background, typically when the user switches to a different native mobile application. Applies to application scope as well as page and flow.

For more information, including differences in behavior between the Android and iOS platforms, please read Apache Cordova's documentation.

None
vbResume

Dispatched as a response to Apache Cordova's resume event. The event fires when the native mobile platform returns the native mobile application from the background. Applies to application scope as well as page and flow.

For more information, including differences in behavior between the Android and iOS platforms, please read Apache Cordova's documentation.

None
vbAfterNavigate

Dispatched from the current page after navigation to this page is complete. The payload is an object with 2 properties:

  • currentPage: the path of the current page

  • previousPage: the path of the previous page

None
vbDataProviderNotification

Dispatched when a Data Provider's implicit fetch fails with an error.

The event has the following payload:

{
 severity: 'string', // severity level
 detail: 'any', // details of the error, this could have the Rest failure details
 capability: 'object', // object with the capabilities configured on the SDP
 fetchParameters: 'object', // object with the parameters passed to the fetch
 context: 'object', // object representing the state of the SDP at the time fetch was initiated
 id: 'string', // uniqueId of the SDP instance
 key: 'string', //  since the event can be fired multiple times, this identifies the event instance
},
 
vbResourceChanged

Dispatched when an application has been updated. This event allows the application to notify the user that they need to refresh to view the updated application.

A default handler resourceChangedHandler is added in the application template.

{
   error: {
      detail: 'string',
   },
}
 

Table 11-7 Flow Event Parameters

Name Description Returns
vbEnter

Dispatched after all the flow scoped variables have been added and initialized to their default values, values from URL or persisted values. Variable scopes available:

  • $application: All application variables

  • $flow: All parent flow variables

  • $page: All page variables

Use this to trigger data fetches. These may occur concurrently.

None
vbExit Dispatched when exiting the page. This event can be used to clean up resources before leaving the page. None
vbBeforeAppInstallPrompt Dispatched when a PWA receives BeforeInstallPromptEvent from the browser. The event will be dispatched after vbBeforeEnter, but there is no guarantee that it will be dispatched after vbEnter. The vbBeforeAppInstallPrompt event can be used to display a native application install prompt by calling event.getInstallPromptEvent().prompt(). Currently, this is only supported in Chrome. For PWAs, the event will be handled automatically by the root page. Applies to application scope as well as page and flow. { getInstallPromptEvent() }
vbPause

Dispatched as a response to Apache Cordova's pause event. The event fires when the native platform puts the native mobile application into the background, typically when the user switches to a different native mobile application. Applies to application scope as well as page and flow.

For more information, including differences in behavior between the Android and iOS platforms, please read Apache Cordova's documentation.

None
vbResume

Dispatched as a response to Apache Cordova's resume event. The event fires when the native mobile platform returns the native mobile application from the background. Applies to application scope as well as page and flow.

For more information, including differences in behavior between the Android and iOS platforms, please read Apache Cordova's documentation.

None

Component Events

Also known as DOM events, component events are similar to page and flow events, except that they are fired by components on a page.

An event listener can have any name, but must be bound to a component event. Component event listeners are defined in the Page module under the 'eventListeners' property. Custom events also propagate up the page's container hierarchy, executing any handlers found in parent containers or their shells. Event propagation can be stopped.

Component event listeners are defined within the eventListeners section of the page model, along with Page lifecycle events:

"eventListeners": {
  "onSelectionChange": {
    "chains": [
      {
        "chainId": "respondToChange",
        "parameters": {
          "text": "{{ $event.detail.value }}"
        }
      }
    ]
  }
}

Component event listeners are called in the same way as page lifecycle event listeners. There can be more than one listener. When there is more than one, they run in parallel. 

To reference an event listener from a component, you can use the $listeners.eventListenerName implicit object. For example:

<oj-select-single ... on-selection-change="[[$listeners.onSelectionChange]]"

Component Event Objects

Within the context of component event listeners, there are three implicit objects.

  • $event: The event payload sent by the component.

  • $current: This represents the second parameter passed to the handler, if any. For JET, this can be either the "$current" binding variable, or the "$data" variable if $current does not exist in the component context.

  • $bindingContext: represents the third parameter passed, if any. For JET, this is the (Knockout) view model, and it will therefore contain the $current or $data variable as a property.

These variables do not exist outside the listener context. In other words, you can reference these in the listener declaration, but you cannot reference them in the called action chain; any values needed in these variables must be passed explicitly to the action chain as arguments (chain variables).

These three variables represent the arguments passed to the listener, and are not directly tied to specific JET values. Their meaning could be different depending on the context.

For example, if using an event listener within an <oj-list-item> item, the value of $current could be different whether you are using the item.renderer attribute or the itemTemplate slot to display the item.

  • Within an item.renderer script, JET does not define $current, so instead passes $data as the second argument, so the Visual Builder $current is JET/Knockout $data. In some JET contexts, like anitem.renderer script, you will also need to prefix Visual Builder listeners with (Knockout) $parent in the HTML.

  • Within an itemTemplate slot, JET defines $current, and passes that, so Visual Builder $current is JET $current.

To determine whether JET $current exists for your use case., refer to the JET documentation for the component to which you are adding a listener.

Additionally, the developer could decide to pass their own custom object for the parameters. In the example below, the listener is wrapped, so Visual Builder $current is "some string", and Visual Builder $bindingContext is undefined.

<oj-button on-click="{{ function(event, current, bindingContext) { $page.listeners.someListener(event, "some string") } }}">
  Click Me!
</oj-button>

Component Event Listener "preventDefault" Property

Component event listeners have an additional "preventDefault" property, which can be used to prevent the normal DOM event handling from being executed.

This example uses an expression to check the payload of the event to stop propagation:

"eventListeners": {
  "customEventTwo": {
      "chains": [
        {
          "actionsId": "handleEventInMod2PageChain",
          "parameters": {
            "eventPayload": "{{ $event }}"
          },
        }
      ],
      "preventDefault": "{{ $event.type === 'info' }}"
  }

Custom Events

Custom events are similar to page events, except that they are not limited to lifecycles. Their event listeners can be defined in a page, flow, or application.

An event name is defined by the user, and is explicitly fired by the application, using the event Actions provided, in the context of a page.

Custom event listeners are defined in the page or flow under the eventListeners property.

One difference between custom events and page events is that they 'bubble' up the containment hierarchy. Any event listeners in a given flow or page for the event are executed before looking for listeners in the container's parent. The order of container processing is:
  • The page from where the event is fired.

  • The flow containing the page.

  • The page containing the flow.

  • Recursively up the containment, ending with the application.

Custom and system event behavior can be modified using the stopPropagation property, which prevents the event from bubbling to this event listener's container's parents.

Example 11-50 stopPropagation Example

"eventListeners": {
  "customEventTwo": {
    "stopPropagation": "{{ $event.type === 'info' }}"
    "chains": [
      {
        "actionsId": "handleEventInMod2PageChain",
        "parameters": {
          "eventPayload": "{{ $event }}"
        }
      }
    ],
  }... 

vbNotification Events

The vbNotification event is a built-in custom event, rather than a page, flow, or application event, as it is an event only explicitly fired by the application using the action 'vb/action/builtin/fireNotificationEventAction' (see Fire Notification Event Action)

The payload is an object with these properties:

  • "summary": a short summary, subject, or title

  • "message": any text meaningful to the application

  • "displayMode": "persist" or "transient"

  • "type": "error", "warning", "info", or "confirmation"

  • "key": an optional GUID, which may be useful for the UI. If not provided, one is generated and provided in the payload.

System Events

System events are identical to custom and page events, except that the framework defines the event.

An event name is defined by the user, and is explicitly fired by the application, using the event Actions provided, in the context of a page.

System event listeners are defined in the page, shell, or flow under the eventListeners property.

System events also propagate or bubble up the page's container hierarchy, executing any listeners.  Event bubbling can be stopped.

One difference between system events and page events is that they 'bubble' up the containment hierarchy. Any event listeners in a given flow or page for the event are executed before looking for listeners in the container's parent. The order of container processing is:
  • The page from where the event is fired.

  • The flow containing the page.

  • The page containing the flow.

  • Recursively up the containment, ending with the application.

Custom and system event behavior can be modified using the stopPropagation property, which prevents the event from bubbling to this event listener's container's parents.

Example 11-51 stopPropagation Example

"eventListeners": {
  "customEventTwo": {
    "stopPropagation": "{{ $event.type === 'info' }}"
    "chains": [
      {
        "actionsId": "handleEventInMod2PageChain",
        "parameters": {
          "eventPayload": "{{ $event }}"
        }
      }
    ],
  }... 

Variable ‘onValueChanged’ Events

Specific to variables, the 'onValueChanged' event is raised by the framework when a variable’s value changes.

To add an event listener to an event, specify it in the 'onValueChanged' property of the variable. Event listeners can only be added to the root variable, not to any sub-objects of the variable structure. It uses the same syntax as other event listeners.

"variables" : {
  "incidentId": {
    "type": "string",
    "input": "fromCaller",
    "required": true,
    "onValueChanged": {
      "chains": [
        {
          "chainId": "fetchIncidentChain",
          "parameters": {
            "incidentId": "{{ $event.value }}"
          }
        }
      ]
    }
  }
},
Old and new variable values are available in the $event implicit object.
  •  $event.oldValue provides the variable’s old value.

  • $event.value provides the variable’s new value.

  • $event.diff can be used for complex types, where it is necessary to know the properties within the variable that changed.

See the Variables section for details on variables.

Optional parameters can be sent to the action chain in response to the event. See the Action Chains section for more information.

Multiple event listeners can be added for the same event (note that 'chains' is an array property). In this case, the event listeners will be run in parallel with respect to each other.