10 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 10-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 10-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 10-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 VB Studio.

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 VB Studio 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.

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 10-4 Using Types in the Application Model

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

Example 10-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 Types

Four built-in types are available.

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.

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 the ServiceDataProvider and the endpoint it works with support. The capabilities object is defined by the JET DataProvider API.

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 2 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 VB Studio 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 custom 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 Objects REST API transforms shipped with VB Studio 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 Functions.

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.

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 calling 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 Functions 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.

  • 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.

    • 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.

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 RestAction) is called in the context of a data provider fetch call.

  • 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' of the response.

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 merged with those configured on the REST action, after the ones on the REST action have been processed, when building the Request.

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.

  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' can be configured.

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

"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 Functions

A page author can use the mergeTransformOptions property 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, fetchParameters } as a request servicing one fetch capability.

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

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

  • 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.

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
  • keyAttributes

  • itemsPath

  • uriParameters

  • filterCriterion

  • sortCriteria

  • pagingCriteria

  • responseType

  • capabilities

    • fetchByOffset

    • keys

    • ...

- A snapshot of the value of the ServiceDataProvider variable at the time the fetchByOffset() call was made.
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.
context
  • keyAttributes

  • itemsPath

  • uriParameters

  • filterCriterion

  • sortCriteria

  • pagingCriteria

  • responseType

  • capabilities

    • fetchFirst

    • keys

    • ...

- A snapshot of the value of the ServiceDataProvider variable at the time the fetchFirst() call was made.
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:

    • 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 }}"
      }
    }
    • 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 Functions

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;
};

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;
}
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>
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 10-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
      }
    }
  }
},
Multi-Service Data Provider

The vb/MultiServiceDataProvider builtin 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.

Design Time Assumptions

At design time, a service author can identify different endpoints that provide the fetchByKeys capability, in addition to the current fetch all (fetchFirst capability). It is common for the same REST endpoint to support multiple capabilities. 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'). Or the same endpoint can be used to fetch all customers, or to fetch customers by specific keys: GET /customers and GET /customers?ids=cus-101,cus-103.

Type Definition

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: 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:

  • 1 vb/MultiServiceDataProvider variable that references 2 ServiceDataProvider variables, one for each fetch capability

  • 2 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:
  • 1 vb/MultiServiceDataProvider variable

  • 2 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.

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.

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 run time 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, though it might be desirable to have one definition for the variable of type vb/ServiceDataProvider that multiplexes different fetch capabilities, the recommended approach is to have one ServiceDataProvider per fetch capability, then combine them using the vb/MultiServiceDataProvider. The latter will be used to bind to components.

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

1  "territoriesByCodesSDPVar": {
2    "type": "vb/ServiceDataProvider",
3    "defaultValue: {
4      "endpoint": "fa-crm-service/getTerritories",
5      "keyAttributes": "TerritoryCode",
6      "itemsPath": "items",
7      "uriParameters": {
8        "finder": "EnabledFlagFinder;BindEnabledFlag=Y"
9      },
10     "capabilities": {
11       "fetchByKeys": { "implementation": "lookup" }
12     },
13     "mergeTransformOptions": "{{ $functions.fixupTransformOptions }}"
14   }
15 }
  • Line 10: the 'capabilities' property is set to implementation "lookup".

  • Line 13: the 'mergeTransformOptions' property is bound to a method that can be used to fix up keys to map to filter options:

    • When the fetchByKeys() method is called with the 'keys' parameter, configuration parameters provided to the 'mergeTransformsOptions' function can be used to look up the key values to set on the 'filter' transform options.

    • The filter transform function can then generate the "q=" query param using the filter transform options. For example: &q=TerritoryCode IN ('AE', 'AD', 'US')

  • Note: A filter request transform mapping is not shown here.

1  /**
2   * @param configuration
3   * @param options
4   * @param transformsContext
5   *
6   * @returns {*}
7   */
8  PageModule.prototype.fixupTransformOptions =
9    function (configuration, transformOptions) {
10   var c = configuration;
11   if (c && c.capability === 'fetchByKeys') {
12     var params = c.fetchParameters;
13     if (params.keys) {
14       // If there are keys, update the filter transform options.
15       // The business object REST API filter transform function 
16       // will use the filter criterion to generate q=
17     }
18   } else {
19     // fetchFirst
20   }
21   return transformOptions;
22 };

This code is the JavaScript function referenced by the 'mergeTransformOptions' property of the ServiceDataProvider.

  • Line 8 implements a query transform for a business object REST API endpoint to handle the keys that might need to be appended as a query param.

Configuring the MultiServiceDataProvider Variable

A page author can configure a variable of type vb/MultiServiceDataProvider, with two dataProviders.

sample-page.html Description
1  "territoriesMultiSDP": {
2    "type": "vb/MultiServiceDataProvider",
3    "defaultValue": {
4      "dataProviders": {
5        "fetchFirst": "{{ $page.variables.territoriesSDPVar }}"
6        "fetchByKeys": "{{ $page.variables.territoriesByCodesSDPVar }}"
7      }   
8    }
9  }
  • Line 5: the 'fetchFirst' configuration has the 'variable' property set to the ServiceDataProvider variable territoriesSDPVar.

  • Line 6: the 'fetchByKeys' configuration has the 'variable' property set to the ServiceDataProvider variable territoriesByCodesSDPVar.

Configuring a JET Combo/Select in Design Time

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

  • Line 2: the options attribute is bound to the MultiServiceDataProvider variable.

1 <oj-combobox-many id="so11" value="{{ $variables.selectedTerritories }}" 
2              options="[[ $variables.territoriesMultiSDP ]]" 
3              options-keys.label='[[ "TerritoryShortName" ]]' 
4              options-keys.value='[[ "TerritoryCode" ]]' 
5 </oj-combobox-many>

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.

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": "19.4.3",
 
  "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.

  • 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

ArrayDataProvider2 supports the same capabilities as the legacy ArrayDataProvider:

  • {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 10-7 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 10-8 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 10-9 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 10-10 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 10-11 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 }

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 10-12 Object Variables

Object variables can also have default values:

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

Example 10-13 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 10-14 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 navigateToPage 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 10-15 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.

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 only to previously defined constants 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.

Example 10-16 Declaring a constant

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

Actions

A list of built-in actions available in VB Studio 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 Method 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 Module 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 Endpoint 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 Service Connections.

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 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 10-17 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>
  }
}

Fire Custom 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 VB Studio, 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 10-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 10-18 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 10-19 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 10-20 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 10-21 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 10-22 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 10-23 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/navigateToPageAction".

This action will navigate the user and also perform any parameter passing to activate that page. Parameters for this action are as follows:

Parameter Name Description
page The page path (as defined in the page model) of the page to navigate to (required)
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. Allowed values are 'replace', 'skip' or 'push'. If the value is 'replace', the current browser history entry is replaced, meaning that back button will not go back to it. If the value is 'skip', the URL is not modified. (optional and default is 'push')

Page parameters are variables that are marked input 'fromCaller' or 'fromUrl'. When calling this action it doesn't matter if the destination page's variables are passed internally or via the URL. The framework will arrange that the parameters end up in the correct place, and that they are passed to the destination page.

Example 10-24 Example

To navigate to another page:
"myActionChain": {
  "root": "myAction",
  "actions": {
    "myAction": {
      "module": "vb/action/builtin/navigateToPageAction",
      "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.

Navigating to the Same Page

Navigating to the same page but with different input parameters is a valid navigation. Since the current page is not changing, only the input variable value will change and the onValueChanged event will be triggered.

The navigation is pushed on the browser history, so pressing the browser back button will restore the input variables' previous values.

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 VB Studio 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 10-25 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.oracle.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 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 10-26 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 }}" }
      }
    }
  }
}

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 10-27 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 Module 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 10-28 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 10-29 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 VB Studio $current is JET/Knockout $data. In some JET contexts, like anitem.renderer script, you will also need to prefix VB Studio listeners with (Knockout) $parent in the HTML.

  • Within an itemTemplate slot, JET defines $current, and passes that, so VB Studio $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 VB Studio $current is "some string", and VB Studio $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 10-30 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 10-31 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 VB Studio 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 10-32 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 10-33 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 10-34 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 10-35 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 10-36 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 10-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 10-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 Endpoint Action
requestTransformationOptions (transformationOptionMap) transformationFunctionMap: map of request transform parameters REST helper See Call REST Endpoint Action
responseTransformationFunctions (transformationFunctionMap) transformationFunctionMap: map of functions. REST helper See Call REST Endpoint 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 10-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 10-5 Module function event helper methods

Method Description
fireCustomEvent(name, payload) See Fire Custom Event Action.
fireNotificationEvent(options) See Fire Notification Event Action.

Example 10-37 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 10-38 Declaration

"events": {
  "myPageEvent": {
    "payloadType": {
      "message": "string",
      "code": "number"
    }
  }
},

Example 10-39 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 10-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 10-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 VB Studio $current is JET/Knockout $data. In some JET contexts, like anitem.renderer script, you will also need to prefix VB Studio listeners with (Knockout) $parent in the HTML.

  • Within an itemTemplate slot, JET defines $current, and passes that, so VB Studio $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 VB Studio $current is "some string", and VB Studio $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 10-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.

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 10-41 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.