Oracle Visual Builder Page Model Reference
Oracle Visual Builder Page Model Reference
Release 25.01
G17369-01
October 2024
Oracle Cloud Oracle Visual Builder Page Model Reference, Release 25.01
G17369-01
This software and related documentation are provided under a license agreement containing restrictions on use and
disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or
allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit,
perform, publish, or display any part, in any form, or by any means. Reverse engineering, disassembly, or decompilation
of this software, unless required by law for interoperability, is prohibited.
The information contained herein is subject to change without notice and is not warranted to be error-free. If you find
any errors, please report them to us in writing.
If this is software, software documentation, data (as defined in the Federal Acquisition Regulation), or related
documentation that is delivered to the U.S. Government or anyone licensing it on behalf of the U.S. Government, then
the following notice is applicable:
U.S. GOVERNMENT END USERS: Oracle programs (including any operating system, integrated software, any
programs embedded, installed, or activated on delivered hardware, and modifications of such programs) and Oracle
computer documentation or other Oracle data delivered to or accessed by U.S. Government end users are "commercial
computer software," "commercial computer software documentation," or "limited rights data" pursuant to the applicable
Federal Acquisition Regulation and agency-specific supplemental regulations. As such, the use, reproduction,
duplication, release, display, disclosure, modification, preparation of derivative works, and/or adaptation of i) Oracle
programs (including any operating system, integrated software, any programs embedded, installed, or activated on
delivered hardware, and modifications of such programs), ii) Oracle computer documentation and/or iii) other Oracle
data, is subject to the rights and limitations specified in the license contained in the applicable contract. The terms
governing the U.S. Government's use of Oracle cloud services are defined by the applicable contract for such services.
No other rights are granted to the U.S. Government.
This software or hardware is developed for general use in a variety of information management applications. It is not
developed or intended for use in any inherently dangerous applications, including applications that may create a risk of
personal injury. If you use this software or hardware in dangerous applications, then you shall be responsible to take all
appropriate fail-safe, backup, redundancy, and other measures to ensure its safe use. Oracle Corporation and its
affiliates disclaim any liability for any damages caused by use of this software or hardware in dangerous applications.
Oracle®, Java, MySQL, and NetSuite are registered trademarks of Oracle and/or its affiliates. Other names may be
trademarks of their respective owners.
Intel and Intel Inside are trademarks or registered trademarks of Intel Corporation. All SPARC trademarks are used
under license and are trademarks or registered trademarks of SPARC International, Inc. AMD, Epyc, and the AMD logo
are trademarks or registered trademarks of Advanced Micro Devices. UNIX is a registered trademark of The Open
Group.
This software or hardware and documentation may provide access to or information about content, products, and
services from third parties. Oracle Corporation and its affiliates are not responsible for and expressly disclaim all
warranties of any kind with respect to third-party content, products, and services unless otherwise set forth in an
applicable agreement between you and Oracle. Oracle Corporation and its affiliates will not be responsible for any loss,
costs, or damages incurred due to your access to or use of third-party content, products, or services, except as set forth
in an applicable agreement between you and Oracle.
Contents
Preface
Audience vii
Documentation Accessibility vii
Diversity and Inclusion vii
Related Resources vii
Conventions viii
iii
Call Action Chain 1-115
Call Component 1-116
Call Function 1-116
Call REST 1-117
Call Variable 1-123
Code 1-124
Fire Data Provider Event 1-124
Fire Event 1-129
Fire Notification 1-130
For Each 1-131
Get Dirty Data Status 1-132
Get Location 1-133
If 1-134
Login 1-134
Logout 1-135
Navigate Back 1-135
Navigate To Application 1-135
Navigate To Flow 1-137
Navigate To Page 1-137
Open URL 1-138
Reset Dirty Data Status 1-139
Reset Variables 1-139
Return 1-140
Run in Parallel 1-140
Scan Barcode 1-142
Share 1-143
Switch 1-143
Try-Catch-Finally 1-144
JSON Action Chains 1-144
JSON Actions 1-144
Assign Variables Action 1-144
Call Action Chain Action 1-147
Call Component Action 1-148
Call Function Action 1-149
Call REST Action 1-149
Call Variable Method Action 1-155
EditorUrl Action 1-156
Fire Event Action 1-157
Fire Data Provider Event Action 1-158
Fire Notification Event Action 1-162
ForEach Action 1-162
Get Location Action 1-164
iv
If Action 1-166
Login Action 1-166
Logout Action 1-167
Navigate Action 1-167
Navigate Back Action 1-173
Open URL Action 1-173
Reset Variables Action 1-175
Return Action 1-175
Run in Parallel / Fork Action 1-176
Scan Barcode Action 1-177
Share Action 1-178
Switch Action 1-179
Take Photo Action 1-180
Transform Chart Data Action (Deprecated) 1-182
Web Share Action 1-185
Action Chain Properties 1-186
Variable References in Action Chains 1-186
Action Chain Variables 1-188
Action Results 1-188
Flow 1-189
Flow Properties 1-190
Using Flows to Create Single-Page Applications 1-190
Represent the Flow State in the URL 1-191
Navigating Between Flows and Pages 1-192
Flow Lifecycle 1-192
Load Flow Resources 1-192
Use Flows Not in the Flows Folder 1-192
Shell Flow 1-193
Fragments 1-194
Define a Fragment Component 1-194
Fragment Scopes and Namespaces 1-196
Define Fragment Input Parameters 1-197
Write Back a Fragment Variable Value to the Parent Container 1-199
Deferred Rendering of a Fragment 1-200
Fragment Events 1-200
Referencing Fragments in Extensions 1-206
Extending a Fragment 1-210
Fragment Patterns 1-211
Components 1-214
HTML Source 1-214
VB Switcher Component 1-214
VB Switcher Navigation 1-215
v
VB Switcher Usage and Properties 1-215
VB Switcher Methods 1-216
VB Switcher Events 1-217
VB Switcher Examples 1-217
Imports 1-219
Import Custom Components 1-219
Import Custom Modules 1-219
Import Modules Using requireJS Path Mapping 1-219
Import Modules Using a Global Functions Resource Path 1-221
Import Custom CSS 1-232
Security 1-234
Security Configuration 1-234
Security Provider 1-235
User Information 1-236
Error Handling 1-236
Helper Utilities 1-237
REST Helper 1-237
Module Function Event Builder 1-240
Security Helper 1-241
Events 1-241
Declared Events 1-243
Lifecycle (Page and Flow) Events 1-244
Component Events 1-247
Fragment Events 1-249
Custom Events 1-249
System Events 1-250
Event Behavior 1-251
Variable ‘onValueChanged’ Events 1-252
2 Related Topics
Declarative RequireJS Path Mapping 2-1
Service Resolution 2-1
Service Transforms 2-6
Metadata Transforms 2-10
Translations 2-12
vi
Preface
Oracle Visual Builder Page Model Reference describes the structure and components used in
the Oracle Visual Builder page model.
Topics:
• Audience
• Documentation Accessibility
• Diversity and Inclusion
• Related Resources
• Conventions
Audience
Oracle Visual Builder Page Model Reference is intended for users who want to understand the
structure and components used in visual application pages and application extensions.
Documentation Accessibility
For information about Oracle's commitment to accessibility, visit the Oracle Accessibility
Program website at https://www.oracle.com/corporate/accessibility/.
Related Resources
For more information, see these Oracle resources:
vii
Preface
Conventions
The following text conventions are used in this document.
Convention Meaning
boldface Boldface type indicates graphical user interface elements associated with an
action, or terms defined in text or the glossary.
italic Italic type indicates book titles, emphasis, or placeholder variables for which
you supply particular values.
monospace Monospace type indicates commands within a paragraph, URLs, code in
examples, text that appears on the screen, or text that you enter.
viii
1
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.
Applications built using Visual Builder typically have multiple flows, each containing multiple
pages. Every application has a default flow, and every flow has a default page, and pages
are what users see and interact with.
A Visual Builder page is backed by a model. This guide describes how to work with the
metadata in the model's JSON file. Other containers like, application, flow, page, fragment
and layout, also each use a similar model.
Note:
While most metadata defined in the page model applies to pages and other container
types (for example, variables are supported in every container), the level of support
for specific metadata may differ between container types. For details on the
differences, refer to the specific container types.
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
}
1-1
Chapter 1
Variables
"nameOfVariable": {
"type": {
"foo": "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 1-2 An Array Represented by a Variable
A JavaScript array
let myArray = [
{
foo: "someString",
bar: 10
},
{
foo: "someOtherString",
bar: 11
}
]
1-2
Chapter 1
Variables
]
}
Metadata Variables
Metadata variables are variables intended to represent metadata in specific cases. They are
declared in a different "metadata" namespace (regular variables are in the "variables"
namespace/declaration), and have slightly different behavior than regular variables. Metadata
variables:
• do not have a "persisted" property
• do not have an "input" property (and cannot be used on a URL for navigation input, for
example).
• are initialized after "variables" variables, and as such, "variables" declarations cannot have
expressions dependent on their values.
• only specific types are supported; these are unique to "metadata" variables.
For a description of the "metadata" declarations used to provide metadata to JET Dynamic UI
Components, see JET Dynamic UI Variable Types.
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.
1-3
Chapter 1
Variables
current App UI
The current following App UI variables are available on the global object when using App UIs.
Name Description
$global.currentAppUi.id The id of the App UI
$global.currentAppUi.urlId The id of the App UI as shown in the URL
$global.currentAppUi.displayName The display name for the App UI
$global.currentAppUi.description The description of the App UI
$global.currentAppUi.defaultPage The default page of the App UI (if there is one)
$global.currentAppUi.defaultFlow The default flow of the App UI (if there is one)
$global.currentAppUi.applicationStripe The stripe of the custom App UI
$global.currentAppUi.pillarTheme The pillar theme to use for the App UI
$global.currentAppUi.pillarThemeMode The pillar theme mode to use for the App UI
$global.currentAppUi.icon The icon of the custom App UI
$global.currentAppUi.usage (This variable is reserved for Oracle Cloud
Applications)
$global.currentAppUi.menuDisplayName The name of the custom App UI
$global.currentAppUi.extensible (Boolean) If this App UI can be extended
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 Deprecated. The variable is always set to web.
$application.deployment.pwa = 'enabled' || Used to indicate if application is configured as a progressive
'disabled' web applications (PWA).
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.
$extension.path The path needed to retrieve a resource in the current
extension.
1-4
Chapter 1
Variables
user
The user variable is used to access information about the current user. It is based on the User
Info returned by the Security Provider. It is possible to modify the set of user information by
changing the implementation of the Security Provider. See Security.
Name Description
$application.user.userId The user id <string>.
$application.user.fullName The user full name <string>.
$application.user.email The user email <string>.
$application.user.username The user name <string>.
$application.user.roles The user roles (array of strings).
$application.user.roles.roleName Returns true if roleName is a role of this user.
$application.user.permissions User permissions (array of strings).
$application.user.permissions.permName Returns true if permName is a permission of this user.
$application.user.isAuthenticated Returns true if this user is authenticated.
translations
This is not a variable, but an API available for getting localized strings
using $<container>.translations.<bundlename>.key,
or $container.<translations>.format(<bundlename>,<key>,args...).
This API exists for $application, $flow, and $page, but is only useful if you have defined
translation bundles. If translation values are needed in JavaScript function modules, they must
be passed as arguments to the function.
responsive
This is not directly a variable, but contains several Knockout Observables that represent JET
Media Queries. The following are available, and are accessible
via $application.responsive.XXX (for example, $application.responsive.smUp): smUp,
mdUp, lgUp, xlUp, smOnly, mdOnly, lgOnly.
info
Some information from the application and page descriptor can be retrieved using the info
keyword.
Name Description
$application.info.id The application id defined in app-flow.json
$application.info.description The application description defined in app-flow.json
$flow.info.id The flow id defined in flow-id-flow.json
$flow.info.description The flow description defined in flow-id-flow.json
$page.info.title The page title defined in page-id-page.json
$page.info.description The page description defined in page-id-page.json
$fragment.info.id This is the id set on the oj-vb-fragment component or the
system generated stable id (if an id is not set on the
component).
The fragment id defined in fragment-id-
fragment.json
1-5
Chapter 1
Variables
Name Description
$fragment.info.title The fragment title defined in fragment-id-
fragment.json.
$fragment.info.description The fragment description defined in fragment-id-
fragment.json.
$layout.info.id The layout id.
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') Use document.querySelector, which returns a JET
(deprecated) 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 1-4 Using Types in the Application Model
types: {
"myType": {
"foo": "string",
"bar": "number"
}
}
1-6
Chapter 1
Variables
Page
A page can access a type defined in itself, or the parent flow, or the application.
Definition Result
Uses the type named myType defined in the page.
"nameOfVariable": {
"type": "myType"
}
Flow
A flow can access a type defined in itself, or the application.
Definition Result
Uses the type named myType defined in the flow.
"nameOfVariable": {
"type": "myType"
}
1-7
Chapter 1
Variables
Application
An application can access a type defined in itself.
Definition Result
Uses the type named myType defined in the application.
"nameOfVariable": {
"type": "myType"
}
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
}
}
1-8
Chapter 1
Variables
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 Service Data Provider 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.
1-9
Chapter 1
Variables
Alternatively, if a fetchChainId is not specified, headers are passed through to the internal
REST calling mechanism by the ServiceDataProvider.
idAttribute
Supports composite keys, using multiple properties. It is a string or array that is the field or
fields in the response data for each row, that represents the 'id', or key, field. Deprecated; use
keyAttributes instead.
keyAttributes
A string or array, that is the field or fields in the response data for each row, that represent(s)
the 'id' (or key) field. Can be:
• A property name - the key, in various contexts, will also be a string.
• An array of property names - the key will also be an array, of values.
• @value, use all properties - the key will also be an array, of values.
• @index, use the index as the key - the key will be an integer.
itemsPath
A string that is the path to the root of the actual collection in the response. Example 'result' if
the response returned from the endpoint looks like {count: 10, result: [...]}
capabilities
An object that defines the capabilities supported by the ServiceDataProvider and the endpoint
it uses. The capabilities object is defined by the JET DataProvider API.
This property serves as a hint for UI components bound to an SDP variable, to know about the
capabilities the endpoint supports and use the correct fetch / sort / filter behaviors.
A variable of type vb/ServiceDataProvider generally defaults to a 'fetchFirst' capability if no
capability is specified. This means that the endpoint associated to the SDP is assumed to
support a fetchFirst behavior. The same endpoint can support other 'fetch' capabilities as
well.
For example, with business object REST API GETAll endpoints, the same endpoint can
provide fetchFirst / fetchByKeys ('lookup') and fetchByOffset ('randomAccess') behaviors.
With third-party services it's important for authors to carefully consider the behaviors their
endpoint supports before configuring the SDP property. For example if the third-party service
endpoint provides optimal 'lookup' based fetchByKeys, and a 'randomAccess' based
fetchByOffset, it's important that the author implements the appropriate transforms functions
to support these capabilities. Refer to the section on Request Transformation Functions,
particularly the 'paginate' and 'fetchByKeys' types for details.
If the same endpoint cannot be used to provide the other fetch behaviors then it might be
required to use a Multi-Service Data Provider. In all other cases SDP will fallback to using the
fetchFirst behavior to provide sub-optimal implementations of fetchByKeys and
fetchByOffset behavior.
1-10
Chapter 1
Variables
1-11
Chapter 1
Variables
1-12
Chapter 1
Variables
responseType
The type of the response that is returned by the ServiceDataProvider. This can be an object or
array. When provided it is used for two purposes:
1. To determine the fields to fetch (aka select fields) from the endpoint. A transforms author
will be provided these fields via the 'select' transforms function, if they wish to edit it, but
ultimately the final response is shaped by the ServiceDataProvider based on the
responseType set on it (see point 2 below).
a. When using an Oracle Cloud Application-based endpoint with ServiceDataProvider,
the built-in business object REST API transforms are loaded automatically (vb/
1-13
Chapter 1
Variables
Note:
Auto-shaping of response data is based on rules determined by the Visual
Builder type system. If authors do not want the automatic shaping of data
performed by ServiceDataProvider to introduce unexpected behavior, they
must either ensure that the response data is 'complete', or they need to wrap
binding expressions to guard against missing data. Response data can be
made 'complete' either on the server-side, or the client can use a 'body'
response transforms function to fix up incomplete data based on business
rules.
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.
1-14
Chapter 1
Variables
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
1-15
Chapter 1
Variables
could be numerous fetch requests hitting the endpoint. If the service or endpoint would like
to limit this, it's important to set this value. This also gets used with the optimized
fetchByOffset capability for its optimized iteration based implementation.
Page authors need to understand how the above properties are used by the
ServiceDataProvider during a fetch call:
1. Generally, the page size used by a fetch can be defaulted using the pagingCriteria.size.
This is only used when a component does not explicitly provide a size. The same is true for
an offset.
2. When the size is provided by the caller (for example, components), this overrides the
default pagingCriteria.size set on the ServiceDataProvider.
Note:
When components do ask for a specific number of rows, and the
ServiceDataProvider returns more rows than were explicitly requested, some
components can get in an indeterminate state. In such cases, to control the
fetchSize, it's better to set this property on the component. Specifically, oj-list-
view has a scrollPolicyOptions.fetchSize.
3. Some components do not support a fetchSize property. If this is the case, you can force
the fetch to be a different value from what the component requested by writing a paginate
transform function where the size can be tweaked. But you might then encounter the
indeterminate state described in #2.
4. It is generally not recommended that you set endpoint-specific size and offset parameters
using the uriParameters property directly (for example, the business object REST API
supports 'limit' and 'offset' query parameters that are the equivalent of
the pagingCriteria.size and offset). If you do, you are on your own to write a business
object REST API transform that can merge/use the value set both in the uriParameters and
pagingCriteria properties. And you are also likely run into the caveats explained in #3.
filterCriterion
An object representing a single attribute filter criterion with the properties { op, attribute,
value }, where 'op' is one of the supported JET attribute operators, and 'attribute' and 'value
are the name and value of the attribute respectively. It may also represent a compound filter
criterion {op, criteria}, where 'op' is a compound operator, and ‘criteria’ is an array of
attributes or compound criterion.
Most complex filter expressions can be expressed using the JET filterCriterion structure.
Sometimes you may need to externalize fetches to build your filter criteria for the REST action.
Note:
The business object REST API transforms shipped with Visual Builder support all
attribute operators except $regex. They can transform a simple attribute filter or a
compound filter that is an array of attribute filter criterion.
// attribute criterion
{
"op": "$eq",
"attribute": "empName",
1-16
Chapter 1
Variables
"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"
}
1-17
Chapter 1
Variables
]
}
]
}
// 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'>"
}]
When using multiple attributes for the sortCriteria, you specify them separated by commas:
[
{
"attribute": "col2",
"direction": "ascending"
},
{
"attribute": "col3",
"direction": "ascending"
}
]
mergeTransformOptions
This property allows a page author to set a callback to fix up or merge the final transforms
options that are passed to the transform functions configured on the ServiceDataProvider. Let's
say a sample endpoint, GET /customers, supports an 'ids' query parameter that can used to
query customers by specific keys. For example:
/customers?ids=cus-101,cus-103
A component like oj-select-many might call the ServiceDataProvider requesting the customer
data for specific keys by calling fetchByKeys() with these keys: ['cus-101', 'cus-103'].
The ServiceDataProvider does not support a declarative way to automatically map these keys
programmatically to the 'ids' query parameter on the URL. Therefore, it might be necessary for
the page author to use this property to set a function callback that will fix up the query
transforms option. For details on writing this function, see Merge Transform Options Function.
transformsContext
A context object passed to the transform functions for both request and response. For
fetchFirst calls, the context will be available for all iterations using the same iterator. Authors
1-18
Chapter 1
Variables
can manage this object as they wish. If this property is not set, an empty Object is provided by
default to all transform functions. When a fetchMetadata property is provided as part of a
fetch*() call, then this property is automatically set on the transformsContext Object and made
available to transform functions.
• fetchMetadata
For Elastic searches where the query can be arbitrarily complex, callers can send extra
search metadata via the fetch call. This parameter can be used to tweak the body that is
used as POST-body in the query.
Note:
This is a Preview API and subject to change.
• textFilterAttributes
See Filter Transform for details on this property.
totalSize
See getTotalSize
transforms
An object that has two properties for specifying 'request' and 'response' transform functions
(callbacks).
Request transformation (or transform) functions are generally specified on the service (or
endpoint) definition as it applies to all usages of the service. The transform functions specified
here are only applicable for the current usage of the service or endpoint.
Request transform functions are primarily used to transform the URL or Request configuration
before a request is sent to the endpoint.
Response functions can be used to process the response and return any additional state along
with the response. Additional state is saved as internal state on the data source variable.
At design time, the page author will need to know whether the endpoint supports paging,
sorting, filtering (or QBE), and the format/syntax for specifying these. Using the transform
functions, the page author can tweak the Request to build a URL containing the paging,
sorting, filtering params, and additional endpoint specific query params.
• request: An object whose properties refer to the type of the request transform functions,
and the value the actual function. The following types are supported. See Request
Transformation 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.
1-19
Chapter 1
Variables
– body: a body transform function that allows page authors to tweak the body if needed
before the fetch call is made.
– fetchByKeys: transforms function that allows a page author to take a key or Set of
keys passed in via the options, and update the request to fetch requested keys.
• response: An object whose properties also refer to the type of the response transform
function. See Response Transformation Functions for details.
– paginate: This transform function is called immediately after the REST layer receives
a response. It is called with the response so this function can process it and return an
object with a group of properties set. The returned object is the primary way
ServiceDataProvider obtains information about the paging state of the request:
* totalSize: <optional> used to inform SDP what the totalSize of the result is.
* hasMore: <generally requiredc> A boolean that indicates whether there are more
records to fetch. Example in business object REST API usecases this would map
to the hasMore boolean property commonly returned in the response. See
explanation below for behavior of SDP when hasMore is not set.
* pagingState: <optional> This can be used to store any paging state specific to the
paging capability supported by the endpoint. In 1.0.0, this property can be used in
the response paginate transform function, to set additional paging state. Which will
then be passed 'as is' to the request paginate transform function, for the next fetch
call.
– body: This transform function is called immediately after the REST layer receives a
response. It is a hook for authors to transform the response body, and is not
guaranteed to be called in any specific order.
The way this works is an iterating component will get the AsyncIterator from the
dataProvider (like ServiceDataProvider) and keep iterating until there is no more data to
fetch, or until the component viewPort is filled, or until its current scrollPosition is reached
(this might be needed when a selected row is several pages down), whichever comes first.
So it's extremely important for SDP to have the above information, to know when to stop
iterating.
Missing 'hasMore' property in the paginate
In the event that service implementors may not have configured a paginate transform, we
provide the following fallback behavior. If the first fetch request from by the SDP's
AsyncIterator, has no 'hasMore' through the paginate response, SDP assumes there are no
more records to fetch and iterator is marked as done. This behavior at least allows
components to render some data without causing repetitive fetches. Of course this means
scrolling through component will not fetch next set, if the endpoint did indeed have more rows
to fetch.
"incidentListDataProviderImplicit": {
"type": "vb/ServiceDataProvider",
"description": "configuration for implicit fetches",
1-20
Chapter 1
Variables
"input": "none",
"defaultValue": {
"endpoint": "ifixitfast-service/getIncidents",
"headers": {},
"keyAttributes": "id",
"itemsPath": "result",
"uriParameters": {
"technician": "{{ $application.user.userId }}"
}
}
}
It is important to note that a ServiceDataProvider variable does not cache its data, just its
configuration. The data is also not persisted to history, session or localStorage.
Since the data can be arbitrarily large data sets, it is recommended that page authors use
other means to cache data on the client, such as the JET offline toolkit cache. This applies to
externalized fetches as well.
Externalized Fetch via an Action Chain
When a 'fetchChainId' property is present, the ServiceDataProvider delegates the fetch to the
action chain. A typical configuration for a ServiceDataProvider variable (supporting a fetchFirst
capability) that externalizes REST will look like the code below. These are the only properties
that are allowed to be configured (or that are relevant):
• capabilities: when this property isn't set, the 'fetchFirst' fetch capability is assumed.
• fetchChainId
• idAttribute (deprecated) or keyAttributes
• itemsPath
• mergeTransformOptions: this property is defined on the ServiceDataProvider variable,
because merging transform options only applies when an action chain (with a REST
action) is called in the context of a data provider fetch call.
• transformsContext: Unlike most transforms-related properties, this property can only be
defined on the SDP configuration. Most transforms-related properties can be defined on
the REST action (requestTransformationOptions, requestTransformFunctions,
responseTransformationFunctions).
• responseType
"variables": {
"incidentListTableSource": {
"type": "vb/ServiceDataProvider",
"input": "none",
"persisted": "session",
"defaultValue": {
"fetchChainId": "fetchIncidentListChain",
"keyAttributes": "id",
"itemsPath": "result",
"responseType": "application:incidentsResponse"
}
}
},
"chains": {
"fetchIncidentListChain": {
1-21
Chapter 1
Variables
...
},
}
"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
1-22
Chapter 1
Variables
ServiceDataProvider, they will be ignored, and those configured on the REST action will be
used when building the request. It's important to note that sortCriteria / filterCriterion passed in
by the component / caller will always get used and (attempted to be) merged with the ones
configured on RestAction. See Merge Transform Options Function property.
In the example below, the action chain 'fetchIncidentListChain' defined in
the fetchChainId property of the ServiceDataProvider variable above has a typical chain
configuration, one of which is a RestAction.
1. The 'hookHandler' property under configuration chain variable will be automatically
generated at design time and is always set to vb/RestHookHandler. SDP implements a
custom hookHandler that extends from this class.
2. If the REST response returns a structure that is exactly what the ServiceDataProvider
expects, this can be returned directly (as in the example below). But if the REST response
is different from the expected responseType, then an action that maps the REST response
to the structure defined by 'responseType' on the SDP needs to be configured.
3. The last action in the chain will always be a ReturnAction whose payload resembles the
REST response whose body resembles 'responseType'. The incidentsResponse response
variable in the chain is provided for clarity but is not used by the chain.
4. If more fields are returned than what the responseType has, SDP will attempt to auto-map
the result to the response type.
5. It's important to not set the 'returnType' property when a ReturnAction is already present in
the chain for SDP, because this additionally coerces the response returned to the caller.
"chains": {
"fetchIncidentListChain": {
"variables": {
"configuration": {
"type": {
"hookHandler": "vb/RestHookHandler"
},
"description": "the configuration for the rest action",
"input": "fromCaller",
"required": true
},
"response": {
"type": "application:incidentsResponse"
}
},
"root": "fetchIncidentList",
"actions": {
"fetchIncidentList": {
"module": "vb/action/builtin/restAction",
"parameters": {
"endpoint": "ifixitfast-service/getIncidents",
"uriParams": {
"technician": "{{ $application.user.userId }}"
},
"hookHandler": "{{ $variables.configuration.hookHandler }}",
"requestTransformOptions": {
"sort": "{{ $page.variables.sortExpression }}",
"filter": "{{ $page.variables.filterAtomicExpression }}"
},
"requestTransformFunctions": {
1-23
Chapter 1
Variables
The configuration object will contain one set of { capability, context, externalContext,
fetchParameters } set as a request is servicing one fetch capability.
For the configuration object, this table describes configuration parameters for the
fetchByKeys capability.
1-24
Chapter 1
Variables
1-25
Chapter 1
Variables
Property Description
• query These are the properties when the ServiceDataProvider is
• filter configured for implicit fetch.
• paginate When the ServiceDataProvider is configured to use an
• sort external fetch chain, the options configured on the
• select RestAction 'requestTransformOptions' property will be
made available here.
A sample endpoint, GET /customers, supports an 'ids' query parameter that can used to query
customers by specific keys. For example: customers?ids=cus-101,cus-103.
For this to work, there is currently no easy way at design time to map the keys provided by the
component programmatically to the 'ids' query parameter on the URL. It might be necessary for
page authors to use this property to wire up a function that will merge the transforms option.
This should be configured as follows:
1. Configuring 'mergeTransformOptions' property
• The ServiceDataProvider variable below defines a fetchByKeys capability.
• The 'mergeTransformOptions' property is configured to point to a page function.
"customerSingleSDP_External": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "demo-data-service/getCustomers",
"keyAttributes": "id",
"itemsPath": "result",
"capabilities": {
"fetchByKeys": {
"implementation": "lookup"
}
1-26
Chapter 1
Variables
"mergeTransformOptions":
"{{ $page.functions.processOptionsForGetCustomers }}"
}
}
/**
* 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;
1-27
Chapter 1
Variables
// fetchByKeys call
to.query.ids = keysToFetchStr;
}
return to;
};
3. • 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.
Each request transformation function has the following signature (except in the case of
vbPrepare and fetchByKeys transform):
1-28
Chapter 1
Variables
– initConfig: Map of another configuration passed into the request. The 'initConfig'
exactly matches the 'init' parameter of the request.
– parameters: Path and query parameters. These are not writable.
– url: Full URL 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.
• transformsContext: A context object, set by the author (ServiceDataProvider, RestHelper,
Call Rest action), that is then 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.
vbPrepare Transform
In order to fetch the data required by the application, clients are expected to use the VB
RestHelper directly or, for example, via a RestAction or ServiceDataProvider. Regardless of
the invocation mechanism, there are two main pieces of information that are usually provided
for the request to happen: the identifier of the endpoint and, if relevant, the values of the
endpoint "parameters" (such as server variables, path parameters, query parameters, and
header parameters).
The vbPrepare request transform provides a hook that clients can use to programmatically
modify the parameters, which can effectively change the URL of the request issued by Visual
Builder.
Signature
The vbPrepare transform can be declared as follows:
This transform is invoked before any other transform. Its arguments are also slightly different
from the other transforms:
• The configuration parameter provides the information about the endpoint being fetched,
including the endpoint identifier, the OpenAPI path for the endpoint, and the server details
(including URL templates and server variables).
• The options parameter has a property parameters that exposes the object holding the
parameters passed to the RestHelper. The value of parameters is a "live" object: in other
words, changing the properties of options.parameters actually modifies the values used
by the RestHelper.
• The transformsContext is an object that is set at the RestHelper, which is then passed to
all transforms.
vbPrepare Request Transform Examples
The examples below illustrate the arguments passed to the vbPrepare transform, as well as
the effect its code has on the fetch performed by the RestHelper.
The 'store' service
1-29
Chapter 1
Variables
• The service is located on an extension extA, and is exposed to extensions that depend on
extA.
• The server of the service refers to the backend storeapi, and has a server variable
storeId:
"servers": [
{
"url": "vb-catalog://backends/extA:storeapi/{storeId}",
"variables": {
"storeId": {
"default": "001"
}
}
}
],
• The backend storeapi is defined in the catalog.json artifact of extA, and also has a server
variable:
"backends" {
"storeapi": {
"extensionAccess": true,
"transforms": {
"path": "./storeapi.js"
},
"servers": [{
"url": "https://www.mystore.com/{version}",
"variables": {
"version": {
"default": "1.0"
}
}
}]
}
}
• The service has an operation listProduct with both a "path" and a "query" parameter:
"/products/{productId}": {
"get": {
"operationId": "listProduct",
"description": "List a product",
"parameters": [
{
"name": "productId",
"in": "path",
"description": "The ID of product.",
"required": true,
"schema": {
"type": "string"
}
},
{
1-30
Chapter 1
Variables
"name": "manufactureModel",
"in": "query",
"description": "Whether or not to use the manufacture's model.",
"required": false,
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
...
}
}
}
Transform Script
As indicated above, the transform script storeapi.js is provided by the storeapi backend, so
that's the artifact that must contain the vbPrepare transform method.
If the service store itself had a transform script, the method for vbPrepare should be declared
there.
Example 1-6 RestAction
Assume that the following RestAction is defined in an action chain.
"getProduct": {
"module": "vb/action/builtin/restAction",
"parameters": {
"endpoint": "extA:store/listProduct",
"uriParams": {
"productId": "[[ $chain.variables.productId ]]"
}
},
The getProduct action provides only the value for the productId path parameter, which is
required. The data fetch fails if the path is not specified.
The vbPrepare transform could be implemented in storeapi.js like this (this example uses the
traditional, "function-prototype" design for transforms):
define([], function () {
var Request = function () {};
1-31
Chapter 1
Variables
/*
* options = {
* // The resolved uriParams specified by the RestAction.
* parameters: {
* productId: 'tv001',
* },
* }
*/
// Changing both the 'productId' path parameter and the 'version' server
variable.
options.parameters.productId = 'notebook003';
options.parameters['server:version'] = '2.1';
return {
request: Request,
response: Response
};
});
With the transform above, the request URL that is fetched is https://
www.mystore.com/2.1/001/products/notebook003?internalSKU=true.
1-32
Chapter 1
Variables
restHelper.parameters({
'server:storeId': 'To-001',
manufactureModel: true,
productId: 'tv001',
});
restHelper.transformsContext({
myValue: 123,
});
return restHelper.fetch;
Also, assume that the web application defines the following value on the index.html artifact:
<script type="text/javascript">
window.vbInitParams = {
'services.catalog.common.version': 'untested',
};
</script>
The vbPrepare transform could be implemented in storeapi.js like this (this examples uses an
alternative, simpler design for transforms):
'use strict';
define([], () => ({
request: {
vbPrepare: (configuration, options, transformsContext) => {
/*
* configuration = {
* endpointId: 'extA:store/listProduct',
* endpointPath: '/products/{productId}',
* serverUrlTemplates: [
* {
* // The url of the server of the store service.
* template: 'vb-catalog://backends/extA:storeapi/{storeId}',
*
* // The value that VB would use for the 'storeId' server
variable,
* // which in this case is provided via the
'RestHelper.parameter'
* variables: {
* storeId: 'To-001',
* }
* },
* {
* // The url of the server of the storeapi backend.
1-33
Chapter 1
Variables
* template: 'https://www.mystore.com/{version}',
*
* // The value that VB would use for the 'version' server
variable,
* // which in this case is provided via the 'vbInitParams' from
index.html.
* variables: {
* version: 'untested',
* }
* },
* ],
* }
*/
/*
* options = {
* // The parameters set via 'RestHelper.parameters'.
* parameters: {
* 'server:storeId': 'To-001',
* manufactureModel: true,
* productId: 'tv001',
* },
* }
*/
/*
* // The value set via 'RestHelper.transformsContext'
* transformsContext = {
* myValue: 123,
* }
*/
With the transform above, the request URL that is fetched is https://www.mystore.com/
untested/To-001/products/tv001.
FetchByKeys Transform
A fetchByKeys transforms function allows the page author to take a key or Set of keys passed
in via the options and tweak the URL, to fetch the data for the requested keys.
When the consumer of the SDP calls the fetchByKeys() method, if the transforms author has
provided a 'fetchByKeys' transforms implementation, it is called over the other transforms. If no
fetchByKeys transforms function is provided then the default transforms are called.
The built-in business object REST API transforms already provides a fetchByKeys transforms
function implementation that appends the keys to the URL. This should suffice for most
common cases and should result in at most one fetch request to the server. For third-party
REST endpoints, the author can provide a custom fetchByKeys transforms implementation.
1-34
Chapter 1
Variables
Signature
The fetchByKeys transform function can be declared like this:
var c = configuration;
// use the keys provided to update the c.url
return c;
}
Examples
The examples below illustrate the arguments passed to the fetchByKeys transform, as well as
the effect its code has on the fetch performed by the RestHelper.
Example 1-8 FixItFast service
The examples below use a service fixitfast that has a GET endpoint to retrieve the list of
customers.
ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the GET /customers endpoint.
{
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "fixitfast-service/getCustomers",
"keyAttributes": "id",
"itemsPath": "result",
"responseType": {
"result": "customerResponse[]"
}
}
},
"customersSDP2": {
"type": "vb/ServiceDataProvider2",
"constructorParams": [
{
"endpoint": "fixitfast-service/getCustomers",
1-35
Chapter 1
Variables
"keyAttributes": "id",
"itemsPath": "result",
"responseType": {
"result": "customerResponse[]"
}
}
]
}
}
}
The above SDP variable customersSDP, an extended type variable, is bound to an endpoint
that returns all customers. Another variable, customersSDP2 is an instance factory type - vb/
ServiceDataProvider2 that is bound to the same endpoint.
Note:
In the former case, the fetchByKeys capability may need to be set explicitly to values
other than the defaults for optimal behavior. This is because the extended type vb/
ServiceDataProvider does not automatically fetch the capabilities from the service
metadata transforms, whereas this is not the case for the extended type. Example,
{
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
// ...
"capabilities": {
"fetchByKeys": {
"implementation": "lookup",
"multiKeyLookup": "single"
}
}
}
}
}
In both cases, when a component bound to the SDP variable initiates a fetchByKeys calls it
passes the Set of keys whose data needs to be fetched. In both cases, a RestHelper instance
is created and the fetchByKeys request transforms alone is run prior to the fetch. The
fetchByKeys request transforms function uses the Set of keys passed in to build a query param
on the configuration url.
The fetchByKeys transform is implemented in the default service transforms as follows:
define([], function () {
class Request {
1-36
Chapter 1
Variables
if (fetchByKeysCap) {
const fetchKeys = options;
if (fetchKeys && fetchKeys instanceof Set && fetchKeys.size > 0) {
const keyAttribute = fetchConfig.context.keyAttributes ||
fetchConfig.context.idAttribute;
const keysArr = Array.from(keys);
return c;
}
}
Filter Transform
The filter request transform allows authors to take a filter criterion and turn into a filter query
that is generally appended to the configuration.url.
The filter criterion provided via the options parameter is an object that has a single attribute
filter criterion with properties { op, attribute, value }, or more complex criterion as defined by the
JET Data Provider docs. Additionally, text filtering is also supported (similar to the JET
TextFilterDef).
While the JET Data Provider Filter API defines many complex structures for representing a
filter criterion only a subset of these definitions and capabilities are implemented by the
Business Object based service transforms implementation.
The Business Objects transforms supports transforming filter criterion that use a simple form
(similar to JET AttributeExprFilterDef - refer to the JET docs for details), or a compound filter
that is an array of simple attribute filter criterion. Nested criterion structures as shown below
are also supported. See examples shown below
When using an SDP also refer to the docs for the same on how filterCriterion property is
configured.
Examples of Criterion Transforms
1-37
Chapter 1
Variables
{
"op": "$eq",
"attribute": "empName",
"value": "Lucy"
}
{
"op": "$or",
"criteria": [
{
"op": "$gt",
"attribute": "hireDate",
"value": "2015-01-01"
},
{
"op": "$le",
"attribute": "hireDate",
"value": "2018-01-01"
}
]
}
This example is a nested compound criterion that transforms to the following query parameter
"q=((foo LIKE 'foo%') and ((bar >= 'bar1') or (bar <= 'bar2')))":
{
"op": "$or",
"criteria": [
{
"op": "$and",
"criteria": [
{
"attribute": "price",
"op": "$gt",
"value": 30
},
{
"attribute": "price",
"op": "$lt",
"value": 40
}
]
}
]
}
This is an example of text filtering that transforms to q=(PartyName LIKE 'Megha%'). The term
'PartyName' is picked up by automatically looking up a special property on the
1-38
Chapter 1
Variables
{
"text": "Megha"
}
This is an example of text filtering that is combined with compound criterion that transforms to
q=((PartyName LIKE 'Megha%') and (Foo = 'bar1'))',":
{
"op": "$and",
"criteria": [
{
"text": "Megha"
},
{
"op": "$or",
"criteria": [
{
"op": "$eq",
"attribute": "Foo",
"value": "bar1"
}
]
}
]
}
For 3rd party services the author can provide a custom filter transforms implementation.
Signature
The filter transform function can be declared like this:
var c = configuration;
// use the filter criterion provided on 'options' parameter to generate the
filter query
// update c.url as needed
return c;
}
1-39
Chapter 1
Variables
• options the filter criterion to transform. Business Objects transforms supports the following
compound and attribute operators in the filter criterion:
Usages
The examples below illustrate the arguments passed to the filter transform as well as the effect
its code has on the fetch performed by the RestHelper.
The examples below use a service fixitfast that has a GET endpoint to retrieve the list of
customers.
Example 1-9 ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the above GET /customers endpoint. The SDP variable includes a default filter
criterion and also sets a property called transformsContext with a key vb-
textFilterAttributes that is set to ['lastName']. This is a hint to the filter request transform
to use this attribute when building a text query.
{
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "fixitfast-service/getCustomers",
"keyAttributes": "id",
"itemsPath": "result",
"responseType": {
"result": "customerResponse[]"
},
"filterCriterion": {
"op": "$eq",
"attribute": "region",
"value": "{{ $variables.customer.region || \"US\" }}"
},
"transformsContext": {
"vb-textFilterAttributes": ["lastName"]
1-40
Chapter 1
Variables
}
}
}
}
}
When a caller such as a component bound to the above SDP initiates a fetch call, it can also
provide additional filter criterion along with a text value to search against if applicable (example
oj-select-single provides a text filter in the form { text: '<some-text-to-search>' }). These
are combined with the configured filter criterion above and the merged filter criterion is
provided to the filter transforms function. Refer to the Service Data Provider docs for details.
The filter request transforms function uses the filter criterion passed in via the options
parameter to build a query param on the configuration url.
The filter transform is implemented in the service transforms as follows:
define([], function () {
class Request {
return c;
}
}
1-41
Chapter 1
Variables
When a fetch is initiated by a component bound to the SDP, the filter criterion is automatically
passed in to the filter transform function associated to the service, along with the ones
provided by caller, via the options parameter. Refer to the Call Rest action docs for details.
{
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"fetchChainId": "fetchCustomersChain",
"keyAttributes": "id",
"itemsPath": "result"
}
}
},
"chains": {
"fetchCustomersChain": {
"variables": {
"configuration": {
"type": {
"hookHandler": "vb/RestHookHandler"
},
"description": "the configuration for the rest action",
"input": "fromCaller",
"required": true
}
},
"root": "fetchCustomers",
"actions": {
"fetchCustomers": {
"module": "vb/action/builtin/restAction",
"parameters": {
"endpoint": "fixitfast-service/getCustomers",
"hookHandler": "{{ $variables.configuration.hookHandler }}",
"responseType": "customersComputedResponse",
"requestTransformOptions": {
"filter": {
"op": "$eq",
"attribute": "region",
"value": "{{ $page.variables.customer.region || \"US\" }}"
}
}
},
"outcomes": {
"success": "returnCustomersResponse",
"failure": "returnFailureResponse"
}
}
}
}
}
}
1-42
Chapter 1
Variables
"transformsContext": {
"vb-textFilterAttributes": ["lastName"]
}
For the above configuration example, if a user enters text 'foo' in select-single, the SDP
generates q=lastName LIKE 'foo%'.
By default, the operator used is 'startsWith' as this is considered to be more optimized for
db queries than 'contains'.
• Option 2: If Option 1 doesn't meet your needs, then you can write a custom filter transform
that massages the text filter and turns it into a regular filterCriterion.
If you use option 2, you could do something similar to the following example. In this example,
resourcesListSDP uses the getall_resources endpoint. The (request) filter transforms property
is a callback that is defined in the PageModule.
"resourcesListSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "crmRestApi11_12_0_0/getall_resources",
"keyAttributes": "PartyNumber",
"itemsPath": "items",
"responseType": "page:getallResourcesResponse",
"transformsContext": {
"vb-textFilterAttributes": ["PartyName"]
},
"transforms": {
"request": {
"filter": "{{ $functions.processFilter }}"
}
}
}
}
It's important to note that the transformsContext object is an argument to every transforms
function, so transforms authors can read the attributes and build the query that way.
The transforms function below takes the text value provided by the component and turns into
an attribute filter criterion using the attributes passed in:
define(['vb/BusinessObjectsTransforms'], function(BOTransforms) {
'use strict';
1-43
Chapter 1
Variables
/**
* The filter transform parses the text filter that may be part of the
options and replaces
* it with an appropriate attribute filter criterion using the
textFilterAttrs.
*
* Note: select-single provides a text filter in the form { text:
'someTextToSearch' }.
*
* The processing of the resulting filterCriterion is delegated to the
Business Object REST API
* transforms module, which takes the filterCriterion and turns it into the
'q' param.
* @param textFilterAttrs
* @return a transforms func that is called later with the options
*/
PageModule.prototype.processFilter = function(config, options,
transformsContext) {
const c = configuration;
let o = options;
let textValue;
let isCompound;
const tc = transformsContext;
const textFilterAttributes = tc && tc['vb-textFilterAttributes];
Note:
Page authors are discouraged from configuring the SDP with the 'q' parameter
directly, for example by setting a 'q' parameter in the uriParameters property. It is
recommended that authors always use filterCriterion property to define 'q' criteria.
This is especially important when using text filtering because the components always
provide a filterCriterion which is appended to any configured filterCriterion on the
SDP It becomes especially difficult for VB to reconcile the 'q' defined in uriParameters
with the filterCriterion and authors are on their own to merge the two.
It's also important to note that select-single calls fetchByKeys very often to locate the
record(s) pertaining to the select keys. For this reason, a new fetchByKeys
transforms function has been added. Refer to the fetchByKeys transforms function for
details.
1-44
Chapter 1
Variables
Paginate Transform
The paginate request transform allows authors to take a paging criteria and generate paging
related query param that is then appended to the configuration.url.
The paging criteria is provided via the options parameter with properties { size, offset }.
{
"size": 5,
"offset": 10
}
Signature
The paginate transform function can be declared like this:
var c = configuration;
// use the paging criteria provided on 'options' parameter to generate the
appropriate query param
// update c.url as needed
return c;
}
Usages
Example 1-11 When size of -1 is provided
1-45
Chapter 1
Variables
var c = configuration;
var os = (options.size === -1 || options.size > 100) ? 100 : options.size;
if (options) {
c.url = appendToUrl(c.url, 'limit', os);
c.url = appendToUrl(c.url, 'offset', options.offset);
}
return c;
}
Query Transform
The query request transform allows authors to take the uri parameters and modify or add new
query parameters to the configuration.url.
Normally uriParameters configured on the Service Data Provider or the Call Rest action are
appended to the URL automatically but there may be cases where user would want to tweak
the query parameters some more.
Let's say the endpoint GET /incidents, supports a query parameter called "search", which does
a semantic aka contextual search. If there is a special param that needs to always be
appended before calling the endpoint, then the transform function could be used for that.
Signature
The paginate transform function can be declared like this:
var c = configuration;
// use the query criteria provided on 'options' parameter to generate the
query param
// update c.url as needed
return c;
}
Examples
The examples below illustrate the arguments passed to the query transform as well as the
effect its code has on the fetch performed by the RestHelper.
1-46
Chapter 1
Variables
The example below uses a service fixitfast that has a GET endpoint to retrieve the list of
customers.
Example 1-12 ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the GET /incidents endpoint.
The SDP variable includes the uriParameters property as shown below.
{
"variables": {
"incidentListTableSource": {
"type": "vb/ServiceDataProvider",
"input": "none",
"defaultValue": {
"endpoint": "fixitfast-service/getIncidents",
"keyAttributes": "id",
"uriParameters": {
"technician": "hcr",
"searchIn": "{{ $page.variables.searchType }}"
},
"transforms": {
"request": {
"query": "{{ $page.functions.query }}"
}
}
}
}
}
}
The query request transforms function uses the criteria passed in via the options parameter to
build a query param on the configuration url.
The query transform is implemented in the page module JavaScript as follows:
define([], function () {
class PageModule {
if (o && !o.searchIn) {
let newUrl = c.url;
newUrl = `${newUrl}&search=faq`; // appends faq search qp
c.url = newUrl;
}
return c;
}
}
return PageModule;
});
1-47
Chapter 1
Variables
Select Transform
The select request transform allows authors to construct the query param, to include fields
whose values need to be included, in the response. Example, the built-in Business Objects
based transforms creates a 'fields' query parameter.
The select criteria provided via the options parameter is an Object with properties { attributes,
type }. The type is the response type structure typically specified on the ServiceDataProvider
and Call Rest action configurations. The attributes are provided by the caller, such as the
component, through a fetch call. See JET Data Provider docs for details on the fetch methods
and the parameters. particularly 'attributes' (JET FetchAttribute). When attributes and type
are present, how these are combined is left to the discretion of the transforms' implementation.
Examples
select criteria with type transforms to
"fields=PartyId,PartyStatus,PartyType;PrimaryAddress:AddressId,FormattedAddress"
{
"attributes": null,
"type": [
{
"PartyId": "number",
"PartyStatus": "string",
"PartyType": "string",
"PrimaryAddress": [
{
"AddressId": "number",
"FormattedAddress": "string"
}
]
}
]
}
{
"attributes": [
"a",
{
"name": "b",
"attributes": [
"b1",
"b2"
]
},
{
"name": "c",
"attributes": [
{
"name": "c1",
"attributes": [
"c1a",
"c1b"
1-48
Chapter 1
Variables
]
},
"c2"
]
}
],
"type": null
}
{
"attributes": [
"PartyId",
{
"name": "PrimaryAddress",
"attributes": [
"AddressId",
{
"name": "FormattedAddress",
"attributes": [
"c"
]
},
"b"
]
},
"a"
],
"type": {
"items": [
{
"PartyId": "number",
"PartyStatus": "string",
"PartyType": "string",
"PrimaryAddress": [
{
"AddressId": "number",
"FormattedAddress": "string"
}
]
}
]
}
}
For 3rd party services the author can provide a custom select transforms implementation.
Signature
1-49
Chapter 1
Variables
var c = configuration;
// use the select criteria provided on 'options' parameter to generate the
query param
// update c.url as needed
return c;
}
Usage
The examples below illustrate the arguments passed to the select transform, as well as the
effect its code has on the fetch performed by the RestHelper.
The example below uses a service fixitfast that has a GET endpoint to retrieve the list of
customers.
Example 1-13 ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the above GET /customers endpoint. The SDP variable includes a response type that
it expects the response to be in, set via the property responseType.
{
"types": {
"customerResponse": {
"PartyId": "number",
"PartyStatus": "string",
"PartyType": "string",
"PrimaryAddress": [
{
"AddressId": "number",
"FormattedAddress": "string"
}
]
}
1-50
Chapter 1
Variables
},
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "fixitfast-service/getCustomers",
"keyAttributes": "PartyId",
"itemsPath": "result",
"responseType": {
"items": "customerResponse"
}
}
}
}
}
When a caller such as a component bound to the above SDP initiates a fetch call, it can also
provide additional attributes. These are combined with the configured responseType above and
the combined select criteria is provided to the select transforms function.
The select request transforms function uses the criteria passed in via the options parameter
to build a query param on the configuration url. The select transform is implemented in the
service transforms as follows:
define([], function () {
class Request {
// process options to build the query param for the fields requested in
the response
if (selectOpts && (selectOpts.type || selectOpts.attributes)) {
// ...
}
1-51
Chapter 1
Variables
Sort Transform
The sort request transform allows authors to transform a sort criteria into a sort query param
that is then appended to the configuration.url.
The sort criteria provided via the options parameter is an array that has one or more criteria
with properties { attribute, direction }. See JET SortCriterion for details.
Examples of Sort criteria
This example shows a single sort criterion that transforms to "orderBy=firstName:asc"
[
{
"attribute": "firstName",
"direction": "ascending"
}
]
[
{
"attribute": "firstName",
"direction": "ascending"
},
{
"attribute": "age",
"direction": "descending"
}
]
For 3rd party services the author can provide a custom sort transforms implementation.
Signature
The sort transform function can be declared like this:
var c = configuration;
// use the sort criteria provided on 'options' parameter to generate the
query param
// update c.url as needed
return c;
}
1-52
Chapter 1
Variables
Usage
The examples below illustrate the arguments passed to the sort transform, as well as the effect
its code has on the fetch performed by the RestHelper.
The example below uses a service fixitfast that has a GET endpoint to retrieve the list of
customers.
Example 1-14 ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the above GET /customers endpoint. The SDP variable includes a default sort criteria
set via the property sortCriteria.
{
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "fixitfast-service/getCustomers",
"keyAttributes": "id",
"itemsPath": "result",
"responseType": {
"result": "customerResponse[]"
},
"sortCriteria": [
{
"attribute": "lastName",
"direction": "ascending"
}
]
}
}
}
}
When a caller such as a component bound to the above SDP initiates a fetch call, it can also
provide additional sort criteria. These are combined with the configured cort criteria above and
the merged sort criteria is provided to the sort transforms function.
The
sort
options
1-53
Chapter 1
Variables
sort
define([], function () {
class Request {
1-54
Chapter 1
Variables
When a fetch is initiated by a component bound to the SDP, the sort criteria is automatically
passed in to the sort transform function associated to the service, via the options parameter.
Refer to the Call Rest action docs for details.
{
"variables": {
"customersSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"fetchChainId": "fetchCustomersChain",
"keyAttributes": "id",
"itemsPath": "result"
}
}
},
"chains": {
"fetchCustomersChain": {
"variables": {
"configuration": {
"type": {
"hookHandler": "vb/RestHookHandler"
},
"description": "the configuration for the rest action",
"input": "fromCaller",
"required": true
}
},
"root": "fetchCustomers",
"actions": {
"fetchCustomers": {
"module": "vb/action/builtin/restAction",
"parameters": {
"endpoint": "fixitfast-service/getCustomers",
"hookHandler": "{{ $variables.configuration.hookHandler }}",
"responseType": "customersComputedResponse",
"requestTransformOptions": {
"sort": [
{
"attribute": "lastName",
"direction": "desc"
}
]
}
},
"outcomes": {
"success": "returnCustomersResponse",
"failure": "returnFailureResponse"
}
}
}
}
}
}
1-55
Chapter 1
Variables
Body Transform
(Required) <Enter a short description here.>
The body request transform allows the author to modify the body payload for the fetch request.
Some getAll endpoints in any type of service, be it Business Object / BOSS / ElasticSearch,
may use a POST operation with a body (example, where the search criteria is set on the 'body'
of the request payload) in order to retrieve search results. The body of the payload is generally
provided by the caller of the request, which can be modified using this request transform
function.
The body transform function is called after all other transforms are run. This is to allow
additional contextual information to be added by the previous transforms (to the
transformsContext parameter) that need to be included in the body.
Signature
The body transform function can be declared like this:
Usage
The examples below illustrate the arguments passed to the body transform, as well as the
effect its code has on the fetch performed by the RestHelper.
The example below uses a service fixitfast that has a POST endpoint to retrieve the list of
incidents currently open in the system.
• The service has an operation postToGetIncidents similar to what is shown below. Please
refer to the VB Docs on creating a Service Connection on how to add/configure POST
endpoints and also set up a custom transforms for the same.
{
"/incidents": {
"post": {
"operationId": "postToGetIncidents",
"responses": {
"default": {
"description": "Default response"
}
1-56
Chapter 1
Variables
},
"x-vb": {
"transforms": {
"path": "./fixitfast-post-transforms.js",
"disabled": {
"request": [
"sort",
"filter",
"query"
]
}
},
"headers": {
"Content-type": "application/json",
"Accept": "application/json"
}
}
}
}
}
Transform Script
As indicated above, the transform script fixitfast-post-transforms.js is specified in the
fixitfast service catalog, along with the endpoint definition. This artifact contains the body
transform method.
If the service fixitfast had a transform script, the method for body could be declared there as
long as the transforms code applies to this POST endpoint.
Example 1-16 ServiceDataProvider variable
Assume that the following variable of type vb/ServiceDataProvider is defined in a page that
refers to the above POST endpoint.
{
"variables": {
"userFilter": {
"type": "object",
"defaultValue": {
"technician": "hcr",
"role": "tech"
}
},
"searchCriteria": {
"type": "object",
"defaultValue": {
"searchLevel": "allReports"
}
},
"incidentsList": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "fixitfast-service/postToGetIncidents",
"keyAttributes": "id",
"itemsPath": "result",
"body": {
1-57
Chapter 1
Variables
The above SDP variable incidentsList is bound to listview component that initiates a fetch to
retrieve all incidents. This creates a RestHelper instance and subsequently the request
transforms to be run. The body request transforms function is called, and the body is updated
as needed, on the configuration object, before the POST request is made.
The body transform is implemented in fixitfast-post-transforms.js as follows:
define([], function () {
class Request {
static body(configuration, options, transformsContext) {
const c = configuration;
/*
* options = {
* userFilter: {
* technician: 'hcr',
* role: 'tech'
* },
* searchCriteria: {
* searchLevel: 'allReports'
* }
* }
*/
return c;
};
}
1-58
Chapter 1
Variables
function(configuration, transformsContext) {
// process the contents and return the result appropriate for the
response transform
return result;
}
Generally these functions are implemented by a service author and associated to the service,
but the individual functions can also be overridden on the Service Data Provider variable or the
Call Rest action.
The parameters to the function are:
• configuration: An object that has the following properties:
– headers: The response headers.
– body: The body returned in the response.
– fetchConfiguration: the configuration pertaining to this fetch call. If fetch was initiated
by ServiceDataProvider this includes the following properties:
* capability: The fetch capability, like fetchByKeys, fetchFirst, fetchByOffset
* context: a snapshot of the ServiceDataProvider variable state at the time the fetch
call was made.
* externalContext: if the fetch was externalized to a chain, then the context setup
on the RestAction in that chain
* fetchParameters: a snapshot of the original fetch parameters provided by the
initiator of the fetch (such as a component). The parameters passed to the fetch
call are defined by the JET Data Provider fetch API.
* transformsOptions: these are full set of transforms options that are passed to
each transform function. These are computed using the parameters configured on
the Service Data Provider, the RestAction (if applicable), and the input parameters
provided by initiator (such as the component).
• transformsContext: a context object that is passed to every transform function to store/
retrieve any contextual information for the current request lifecycle.
The function returns a configuration object appropriate for the response transform type. See
the following common response transform types (paginate transform, body transform) below
for details on the returned responses.
1-59
Chapter 1
Variables
Paginate Transform
This transformation function is called immediately after the fetch returns with a response. The
paginate response transform function can process the response and return an object with the
following properties set.
The returned Object is the primary way in which callers like Service Data Provider know about
the paging state.
• totalSize: A number tracking for the (canonical) total size of the result is. See JET Data
Provider Docs for details.
• hasMore: generally required. A boolean that indicates whether there are more records to
fetch. Example in Business Objects based services, this would map to the hasMore
boolean property commonly returned, in the response. Iterating components can use this
information to keep iterating until there is no more data to fetch, or until certain UI
conditions are met (this might be needed when a selected row is several pages down).
• pagingState: This can be used to store any paging state specific to the paging capability
supported by the endpoint. This additional paging state will then be passed 'as is' to the
request paginate transform function, for the next iteration.
Body Transform
This transform function is called immediately after the REST call returns with a response, and
is a hook for authors to transform the response body, if needed. This function is not guaranteed
to be called in any specific order.
Example
A ServiceDataProvider variable that is configured with a custom body response transform is
shown below. While it overrides the body response transforms whereas, the paginate
response transforms function used to process the response returned by fetch call, is the
default transforms associated to the service.
{
"variables": {
"incidentsList": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "ifixitfast-openapi3/getCustomers",
"keyAttributes": "id",
"itemsPath": "items",
"responseType": {
"items": "customerResponse[]",
"extraResult": "extraResponse"
},
"transforms": {
"response": {
"body": " {{ $page.functions.bodyResponse }}"
}
}
}
}
}
}
1-60
Chapter 1
Variables
The default paginate response transforms functions for a Business Object based service
returns an object with the properties { totalSize, hasMore } as shown below:
define([], function () {
class Response {
/**
* Called after response returns from a fetch call, this is a good place
to process
* response, to provide pagination info such as totalSize and hasMore.
*
* @param configuration - a Map containing the following properties
* - headers: response header
* - body: body of the response
* - fetchConfiguration: the configuration that triggered this fetch call.
*
* @param transformsContext transforms context
*
* @returns {{}}
*/
static paginateResponse(configuration, transformsContext) {
const ps = {};
const tr = {};
if (configuration.body) {
const rb = configuration.body;
if (rb.totalCount) {
tr.totalSize = rb.totalCount;
}
if (rb.totalCount > 0) {
tr.hasMore = !!rb.hasMore;
} else {
tr.hasMore = false;
}
}
return tr;
};
}
1-61
Chapter 1
Variables
The custom body response transforms function configured in the SDP variable is defined in the
PageModule JS. It appends extra results to the return value.
define([], function () {
class PageModule {
/**
* Fix up response data and extract other info and return a transformed
result body.
* The object returned must have the body that the caller is configured
for
*
* @param configuration - a Map containing the following properties
* - headers: response header
* - body: body of the response
* - fetchConfiguration: the configuration that triggered this fetch call.
*
* @param transformsContext transforms context
*
* @returns {*}
*/
static bodyResponse = (configuration, transformsContext) => {
const tr = {};
const c = configuation;
if (c.body) {
// fix up result.body from REST if needed and set the new body in tr
tr.items = c.body.items;
return tr;
};
};
return PageModule;
});
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.
1-62
Chapter 1
Variables
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": {
"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
1-63
Chapter 1
Variables
• 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 1-17 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:
1-64
Chapter 1
Variables
1-65
Chapter 1
Variables
}
}
"fireDataProviderMutationEventActionChain": {
"variables": {
"payload": {
"type": "application:dataProviderMutationEventDetail",
"input": "fromCaller"
}
},
"root": "fireEventOnDataProvider",
"actions": {
"fireEventOnDataProvider": {
"module": "vb/action/builtin/fireDataProviderEventAction",
"parameters": {
"target": "{{ $page.variables.incidentListDataProvider }}", // SDP
variable
// on which the event is
fired
"add": "{{ $variables.payload.add }}",
"remove": "{{ $variables.payload.remove }}",
"update": "{{ $variables.payload.update }}" // has the updated record
details
}
}
}
},
ServiceDataProviderFactory
Some times it's desirable to create a standalone VB type instance programmatically by passing
an initial state. Here the instance is not backed by a variable, that is, its state is not stored in
redux. Instead the instance and/or the caller manages its state essentially.For such cases VB
publishes a contract for a TypeFactory that any type author can implement. See Custom
Extended Types.
The TypeFactory contract is provided in the vb/types/factories/typeFactory.js. VB
provides TypeFactory implementations for creating a ServiceDataProvider instance. Refer to
the ServiceDataProviderFactory for details. (vb/types/factories/
serviceDataProviderFactory.js)
Methods
createInstance
Returns an instance of the ServiceDataProvider. Refer to the JSDocs for the parameters
supported on this method. The instance returned supports all methods from the DataProvider
contract.
• options, object used to instantiate the ServiceDataProvider with, usually contains these
properties
– dataProviderOptions, its initial or 'default' state.
* state properties are same as what a regular ServiceDataProvider variable takes
1-66
Chapter 1
Variables
1-67
Chapter 1
Variables
Properties
A variable of the built-in type vb/MultiServiceDataProvider can be configured with the
dataProviders property using the following sub-properties.
"defaultValue": {
"dataProviders":
{
"fetchFirst":
"{{ $variables.li
stSDP }}"
}
}
}
}
}
1-68
Chapter 1
Variables
"activitiesMultiS
DP": {
"type":
"vb/
MultiServiceDataP
rovider",
"defaultValue": {
"dataProviders":
{
"fetchFirst":
"{{ $variables.li
stSDP }}"
"fetchByKeys":
"{{ $variables.de
tailSDP }}"
}
}
}
}
}
1-69
Chapter 1
Variables
"activitiesMultiS
DP": {
"type":
"vb/
MultiServiceDataP
rovider",
"defaultValue": {
"dataProviders":
{
"fetchFirst":
"{{ $variables.li
stSDP }}"
"fetchByOffset":
"{{ $variables.li
stSDP }}"
}
}
}
}
}
Behavior
• A variable of type vb/MultiServiceDataProvider must have at least one fetch capability
defined. Otherwise an error is flagged.
• When a fetchFirst capability is not defined, a no-op fetchFirst capability is used. The JET
DataProvider contract requires a fetchFirst implementation to be provided.
• All fetch capabilities must point to a variable of type vb/ServiceDataProvider.
• A MultiServiceDataProvider cannot reference another MultiServiceDataProvider variable.
Usage
Here are some of the common ways service endpoints might provide their fetch capabilities.
Usage: When a service provides unique endpoints for different fetch capabilities
When a service has unique endpoints for each fetch capability, we will require one variable of
type 'vb/ServiceDataProvider' per fetch API, and a variable of type 'vb/
MultiServiceDataProvider' variable that combines the individual ServiceDataProvider
variables together. The list-of-values component will then bind to a variable of type vb/
MultiServiceDataProvider.
1-70
Chapter 1
Variables
Let's consider this third-party REST API that is used to get information about countries.
• fetchFirst capability: to get a list of all countries and their info, where the alpha3Code is the
primary key
– service/endpoint: rest-service/getAllCountries
– GET https://restcountries.eu/rest/v2/all
• fetchByKeys capability (with multi key lookup): to get a list of countries by their three-letter
alpha code
– service/endpoint: rest-service/getCountriesByCodes
– GET https://restcountries.eu/rest/v2/alpha?codes=usa;mex
In order for the list-of-values component to use the above endpoints, the design time will need
to create three variables:
• One vb/MultiServiceDataProvider variable that references two ServiceDataProvider
variables, one for each fetch capability
• Two vb/ServiceDataProvider variables
vb/MultiServiceDataProvider Configuration
At design time, a variable using this type will be created that looks like this:
1 {
2 "variables": {
3 "countriesMultiSDP": {
4 "type": "vb/MultiServiceDataProvider",
5 "defaultValue": {
6 "dataProviders": {
7 "fetchFirst": "{{ $page.variables.allCountriesSDP }}
8 "fetchByKeys": "{{ $page.variables.countriesByCodesSDP }}"
9 }
10 }
11 }
12 }
13 }
1-71
Chapter 1
Variables
Configuration Description
Line 3: defines the ServiceDataProvider variable
1 { with a fetchFirst capability.
2 "variables": { • When a capabilities property is not specified,
3 "allCountriesSDP": { it's assumed that the ServiceDataProvider
4 "type": "vb/ supports a fetchFirst capability.
ServiceDataProvider", • When a capabilities property is present but no
5 "defaultValue": { fetch capability is defined (that is, only the filter
6 "endpoint": "rest-service/ and sort capabilities are defined), fetchFirst is
assumed.
getAllCountries",
Line 6: defines the endpoint to use the
7
getAllCountries operation to fetch all countries.
"keyAttributes": "alpha3Code"
8 }
9 },
10 "countriesByCodesSDP": {...}
11 }
12 }
1-72
Chapter 1
Variables
Configuration Description
Line 4: defines the ServiceDataProvider variable
1 { that supports a fetchByKeys capability.
2 "variables": { Line 7: uses the getCountriesByCodes operation to
3 "allCountriesSDP": { fetch a list of countries by their codes.
4 "countriesByCodesSDP": { Line 9: a 'capabilities' property is added to
5 "type": "vb/ ServiceDataProvider that has
ServiceDataProvider", a 'fetchByKeys' property object. See next section
6 "defaultValue": { for details.
7 "endpoint": "rest-service/ • 'implementation' property is set to "lookup"
getCountriesByCodes", • 'multiKeyLookup' property set to "no"
8 Line 15: the 'mergeTransformOptions' property
"keyAttributes": "alpha3Code" is set to a page function.
, • this is needed so page author can map the
9 "capabilities": { keys set programmatically to be turned into the
10 "fetchByKeys": { query parameters '?codes='
11
"implementation": "lookup
",
12 Note:
"multiKeyLookup" : 'no' Normally fetchByKeys() is called by
13 } a JET component programmatically
14 }, with one or more keys.
15
"mergeTransformOptions": "{{ • When keys are provided programmatically,
$functions.fixupTransformOptions }}" ServiceDataProvider will use a best-guess
heuristic to map keys to the appropriate
16 }
transform options. But when this is not easily
17 }
decipherable by ServiceDataProvider, page
18 } 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.
1-73
Chapter 1
Variables
Configuration Description
Line 17: function that fixes up the transform options
1 /** that will be sent to the transform functions.
2 * fix up the query transform Line 33: set a new 'codes' query parameter, whose
options. value is a ';' separated list of country alpha codes.
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.fixupTransformOp
tions =
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
1-74
Chapter 1
Variables
Configuration Description
Usage: When a service provides unique endpoints for different fetch capabilities, but
the fetchByKeys endpoint only supports a single-key-based lookup
In this use case, the service supports a fetchFirst capability that fetches all rows, and a
fetchByKeys capability that returns a single row by its key. There is no endpoint that can return
rows by multiple keys.
To understand this usecase further let's take the example of the sample ifixitfast service - and
the incidents endpoints that is used to get information about incidents.
• fetchFirst capability: to get a list of all incidents for the selected technician,
– service/endpoint: fixitfast-service/getIncidents
– GET https://.../ifixitfaster/api/incidents?technician=hcr
1-75
Chapter 1
Variables
• fetchByKeys capability (with single key lookup): to get a single incident it its 'id'
– service/endpoint: fixitfast-service/getIncident
– GET https://.../ifixitfaster/api/incidents/inc-101
In order for the list-of-values component to use the above endpoints, the design time will need
to create three variables:
• One vb/MultiServiceDataProvider variable that references two ServiceDataProvider
variables, one for each fetch capability
• Two vb/ServiceDataProvider variables
vb/MultiServiceDataProvider Variable Configuration
The configuration for the vb/MultiServiceDataProvider variable is similar to the previous
examples.
1 {
2 "variables": {
3 "countriesMultiSDP": {
4 "type": "vb/MultiServiceDataProvider",
5 "defaultValue": {
6 "dataProviders": {
7 "fetchFirst": "{{ $page.variables.allIncidentsSDP }}"
8 "fetchByKeys": "{{ $page.variables.incidentBySingleKeySDP }}"
9 }
10 }
11 }
12 }
13 }
1-76
Chapter 1
Variables
Configuration Description
some-page.json • Line 3: defines the ServiceDataProvider
variable with the fetchFirst capability.
1 { • Line 6: defines the endpoint that uses the
2 "variables": { getAllIncidents operation to fetch all incidents.
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.u
serId }}"
11 }
12 }
13 },
14 "incidentBySingleKeySDP":
{...}
15 }
16 }
1-77
Chapter 1
Variables
Configuration Description
Line 4: defines the ServiceDataProvider variable
1 { with the fetchByKeys capability. The
2 "variables": { ServiceDataProvider variable is configured for an
3 "allIncidentsSDP": {...}, implicit fetch.
4 "incidentBySingleKeySDP": { Line 7: uses the getIncident operation to fetch a
5 "type": "vb/ single incident by its id.
ServiceDataProvider", Line 9: maps the 'id' key in the 'uriParameters'.
6 "defaultValue": { • At runtime the 'id' key value is substituted in
7 "endpoint": "fixitfast- the path parameter of the URL.
service/getIncident", • For example, if the 'id' value is "inc-101", the
8 "keyAttributes": "id", request URL goes from https://.../
9 "uriParameters": { incidents/{id} → http://.../
10 "id": incidents/inc-101
"{{ $variables.incidentId }}" Line 12: a new 'capabilities' property is added
11 } to ServiceDataProvider that has
12 "capabilities": { a 'fetchByKeys' key object.
13 "fetchByKeys": { • The 'implementation' property is set
14 "implementation": to "lookup".
"lookup", • The 'multiKeyLookup' property is set to "no",
15 "multiKeyLookup" : as the endpoint only supports lookup using a
'no' single key at a time.
16 } Notice that a 'mergeTransformOptions' property
17 } is not set.
18 } • This is because Service Data Provider uses a
19 } simple heuristic to map the 'keys' provided
20 } 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-78
Chapter 1
Variables
Configuration Description
Line 4: defines the ServiceDataProvider variable
1 { with a fetchByKeys capability.
2 "variables": { • The Service Data Provider variable uses an
3 "allIncidentsSDP": {...}, action chain to fetch data. See the next section
4 for the action chain configuration.
"incidentBySingleKeySDP_External": { Line 9: sets a mergeTransformOptions function.
5 "type": "vb/ • This function is used by the page author to fix
ServiceDataProvider", up the 'query' transform options to use the key
6 "defaultValue": { passed in via the fetch call.
7 "fetchChainId":
"fetchSingleIncidentChain",
8 "keyAttributes": "id",
9 "mergeTransformOptions":
"{{ $page.functions.fixupTransformOpt
ions }}",
10 "capabilities": {
11 "fetchByKeys": {
12 "implementation":
"lookup",
13 "multiKeyLookup": "no"
14 }
15 }
16 }
17 },
18 "chains": {}
19 }
1-79
Chapter 1
Variables
Configuration Description
mergeTransformOptions function
/**
* 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.fixupTransformOp
tions = 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;
1-80
Chapter 1
Variables
Configuration Description
}
}
return to;
};
1-81
Chapter 1
Variables
Configuration Description
The external fetch action chain is configured as
1 { follows.
2 "variables": {}, Line 4: the action chain used by the
3 "chains": { ServiceDataProvider.
4 "fetchSingleIncidentChain": { Line 23: the RestAction, the chain calls to fetch a
5 "variables": { single incident by id.
6 "configuration": { Line 28: the 'uriParams' property of the RestAction
7 "type": { is set to the page variable "incidentId".
8 "hookHandler": "vb/ • The value of the "incidentId" variable might be
RestHookHandler" different from what the caller passes in.
9 }, • The mergeTransformOptions function above
10 "description": "the builds the query options containing the final id
configuration for the rest action", value.
11 "input": "fromCaller", Line 31: the requestTransformFunction.query maps
12 "required": true to a query transform function that substitutes the
13 }, endpoint URL with the final id value.
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.hookHand
ler }}",
28 "uriParams":
"{{ $variables.uriParameters }}",
29 "responseType":
"flow:incident",
30
"requestTransformFunctions": {
31 "query":
"{{ $page.functions.queryIncidentById
}}"
32 }
33 },
34 "outcomes": {
35 "success":
"returnIncidentResponse",
36 "failure":
1-82
Chapter 1
Variables
Configuration Description
"returnFailureResponse"
37 }
38 },
39 }
40 }
41 }
42 }
1-83
Chapter 1
Variables
Note:
It is recommended that service authors ensure that the service is configured to use
the default business object REST API transforms.
{
"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
A finder query parameter is applied to all queries
"territoriesSDPVar": { going against the endpoint.
"type": "vb/ServiceDataProvider", When no capabilities are set, the
"defaultValue: { ServiceDataProvider variable is assumed to
"endpoint": "fa-crm-service/ support a fetchFirst capability
getTerritories",
"keyAttributes": "TerritoryCode",
"itemsPath": "items",
"uriParameters": {
"finder":
"EnabledFlagFinder;BindEnabledFlag=Y"
}
}
}
1-84
Chapter 1
Variables
MultiServiceDataProviderFactory
Some times it's desirable to create a standalone VB type instance programmatically by passing
an initial state. In this case, the instance is not backed by a variable, that is, its state is not
stored in redux. Instead the instance and/or the caller manages its state essentially. For such
cases VB publishes a contract for a TypeFactory that any type author can use. See Custom
Extended Types.
The TypeFactory contract is provided in the vb/types/factories/typeFactory.js. VB
provides TypeFactory implementations for creating a ServiceDataProvider instance. Refer to
the MultiServiceDataProviderFactory for details. (vb/types/factories/
multiServiceDataProviderFactory.js)
Methods
createInstance
Returns an instance of the MultiServiceDataProvider. Refer to the JSDocs for the parameters
supported on this method. The instance returned supports all methods from the DataProvider
contract.
• options. The object used to instantiate the ServiceDataProvider usually contains these
properties:
– dataProviderOptions. This is its initial or 'default' state.
* state properties are similar to the properties of a regular MultiServiceDataProvider
variable
• serviceOptions. This optional configuration is needed by the RestHelper to locate the
endpoint details.
• – vbContext. This optional configuration is needed by the RestHelpers to locate the
service of an endpoint. Typically this object should be obtained from a Visual Builder
API or via a callback mechanism.
If not available, clients should pass in an object with a string property 'extensionId'. The
property's value is the id of the extension executing this code (for example, the id of
the extension that contains the action chain using the MultiServiceDataProvider).
Here is an example of how a caller can create an instance
1-85
Chapter 1
Variables
// create SDP
ServiceDataProviderFactory.createInstance({ dataProviderOptions: { endpoint:
"foo/getBars", responseType: "barType[]", keyAttributes: "id"} })
.then((sdpInstance) => {
// use SDP to create MDP instance
MultiDataProviderFactory.createInstance({ dataProviderOptions:
{ dataProviders: { fetchFirst: sdpInstance } } })
.then((mdpInstance) => {
const iter = mdpInstance.fetchFirst();
iter.next().then((results) => {
// process results
});
});
});
"variables": {
"productListADPA": {
"type": "vb/ArrayDataProvider2",
"defaultValue": {
"itemType": "application:productSummary",
"keyAttributes": "id"
1-86
Chapter 1
Variables
}
}
}
...
{
"attribute": "<name of the field>",
"direction": "<'ascending' (default) or 'descending'>"
}
itemType
The type of each item in the data array. This is usually a string that points to an application type
or to a definition.
sortComparators
An optional object with a 'comparators' property that is either an array of arrays where each
inner array has 2 items - name of the attribute that the sortCriteria applies to, and a comparator
function callback that is used by ADP to sort the attribute (column), or is a Map of attribute to
comparator function. This API is similar to the JET SortComparator API.
Here are some examples of configuration for array or arrays.
"sortComparators": {
"comparators": [
[
"Category", "{{ $page.functions.alphaSort }}"
],
[
"Product", "{{ $page.functions.alphaSort }}"
]
1-87
Chapter 1
Variables
]
}
Using a Map:
sortComparators: {
comparators: "{{ new Map([['name', $page.functions.alphaSort]]) }}",
}
textFilterAttributes
An array of attributes to filter on. See the JET documentation for ArrayDataProvider
textFilterAttributes.
"customerListADP": {
"type": "vb/ArrayDataProvider2",
"defaultValue": {
"keyAttributes": "id",
"itemType": "flow:customer",
"textFilterAttributes": [
"lastName", "firstName"
]
}
}
1-88
Chapter 1
Variables
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.
Note:
ADP data in a JSON file needs to be assigned a valid JSON value. ADP data that is
assigned a value from the result of a previous action (for example, a call module
action or REST action), must also be valid JSON. When a non-JSON value (such as
JavaScript values like NaN or Infinity) is provided, you should choose the correct
JSON value that should be used and then replace it. For example, the JavaScript
value "NaN" can be replace by "0", which is an accepted JSON value.
"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
}]
}
}
}
1-89
Chapter 1
Variables
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.findInde
x(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",
1-90
Chapter 1
Variables
14 "parameters": {
15
"$page.variables.productsADPB.data[$page.variables.productsADPB.data.length]":
{
16 "source": "{{ $chain.results.generateNewProduct }}"
17 }
18 }
19 }
"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 }
1-91
Chapter 1
Variables
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 },
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.)
1-92
Chapter 1
Variables
A variable of this type is generally defined on the page, using the built-in type vb/
ArrayDataProvider.
"variables": {
"productListADPD": {
"type": "vb/ArrayDataProvider",
"defaultValue": {
"itemType": "application:productSummary"
}
}
}
...
{
"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.
1-93
Chapter 1
Variables
"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"
1-94
Chapter 1
Variables
}
},
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.generateNewProduc
t]) }}",
27 "reset": "empty"
28 }
29 }
30 }
31 }
32 }
"variables": {
"products": {
"type": "ProductType[]",
1-95
Chapter 1
Variables
"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 }
1-96
Chapter 1
Variables
25 }
26 }
27 }
28}
"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 }
1-97
Chapter 1
Variables
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.findInde
x(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 }
1-98
Chapter 1
Variables
An example:
"myVariable": {
"type": "my/ComicStripType",
"defaultValue": {}
}
Reserved Properties
value
The state of an extended type is generally referred to as its value and its default value can be
specified using the 'defaultValue' property of a variable. For example, the comicStripType
specifies its default value, an Object, by providing defaults for 'name', 'publicationType' etc.
Also note that charactersADP is a reference to a variable of type vb/ArrayDataProvider2.
The type of the value is defined via the 'getTypeDefinition' function (see below). In this
example, this would be the properties in the defaultValue object: name, publicationType,
publications, etc.
"comicStripVar": {
"type": "vb/sample/types/comicStripType",
"defaultValue": {
"name": "flowPage-Calvin & Hobbes",
"publicationType": "flowPagePublicationType",
"publications": [
{
"publication": "Universal Press Syndicate",
"volumes": 24,
"author": "Bill Watterson",
"title": "The Doghouse",
"year": 1987,
"launchDate": "1985-11-18T08:00:00.000Z"
},
{
"publication": "United Feature Syndicate",
"volumes": 250,
"author": "Bill Watterson",
"title": "Calvin and Hobbes",
"year": 1990,
"launchDate": "1990-06-01T08:00:00.000Z"
}
],
"charactersADP": "{{ $variables.flow1SecondComicCharactersAdpVar }}"
}
}
internalState
1-99
Chapter 1
Variables
In addition to 'value', extended type instances are provided an 'internalState' property. Custom
types can externalize their internal state so that it can be captured in redux by using this
'internalState' property. More specifically they can use property accessors to read (see
getInternalState() method) and write (see setInternalState() method) the internal state
are provided.
Methods
getTypeDefinition
As stated before, the type definition for the value of an extended type must be provided via the
'getTypeDefinition' function. This method is called at the time the type instance is created.
The example below returns the type definition of the state (value) of comicStripType. name,
publicationType, publications and charactersADP represent its state.
hoistValueObjectProperties
As a convenience, if the type of this variable as defined in 'getTypeDefinition' is 'object', all root
properties of the values will be hoisted to the root variable type instance. This allows these
properties to be accessible via expressions like '$scope.variables.theInstance.property'. If
this is not desired, return false from 'hoistValueObjectProperties'.
• activate
The 'activate' method is called when this and other variables in the current scope have
been created and its initial (default) values determined. This method is called right before
the 'vbEnter' event and the value of the variable, and can be a good time for types to do
other setup using the resolved value. It is important to note that at the time 'activate' is
1-100
Chapter 1
Variables
called, any value assigned, to the extended type variable or the variables it depends on, in
the vbEnter action chains will not be available.
• dispose
The 'dispose' method is called when the current scope is being torn down and all variables,
including this variable is being disposed. This would be a good time to cleanup state for the
extended type. It is important to note that any outstanding async tasks that are pending,
would be the responsibility of the extended type to wind down gracefully.
handlePropertyValueChanged
When the value of an extended type variable changes (say via assignVariablesAction) it will be
notified of the change via this method.
invokeEvent
Additionally, custom type implementations have the ability to fire a custom event using
'invokeEvent', providing a name, payload. For example, 'comicStripUpdate' is an event fired by
the ComicStripType in the sample provided below.
getType
Custom extended types can retrieve the exploded type structure given a type definition, using
the 'getType' method.
'use strict';
define(['vb/types/extendedType', 'vb/types/typeUtils'], (ExtendedType,
TypeUtils) => {
class ComicStripType extends ExtendedType {
getTypeDefinition(variableDef, scopeResolver) {
let publicationsDef = 'any';
if (variableDef.defaultValue &&
variableDef.defaultValue.publicationType) {
const { publicationType } = variableDef.defaultValue;
if (typeof publicationType === 'string') {
publicationsDef = `${publicationType}[]`;
}
}
return {
type: {
name: 'string',
publicationType: 'string',
publications: TypeUtils.getType(`${this.getId()}:$
{publicationsDef}`,
{ type: publicationsDef }, scopeResolver),
charactersADP: 'vb/ArrayDataProvider2',
},
resolved: true, // because we are pre-resolving type references
};
}
activate() {
console.log('activate called on variable', this.id);
const value = this.getValue();
const { name } = value;
const { publicationType } = value;
const { publications } = value;
1-101
Chapter 1
Variables
const initialValue = {
name, publications, publicationType, charactersADPVValue,
};
this.setInternalState('opStatus', 'not-started');
console.log('initial evaluated value for variable', this.id, 'is',
finalValue);
}
handlePropertyVariableChangeEvent(e) {
if (e.name.endsWith('value')) {
if (e.diff) {
if (e.diff.publications) {
// process value change here
}
}
}
}
/**
* a sample method provided by this type that fakes a async op and
updates the internalState
* @returns {Promise<T>}
*/
callAsyncMethod() {
this.setInternalState('opStatus', 'started');
return Promise.resolve().then(() => {
// call some other async method; set some internalState and fire an
event
callAnotherAsyncMethod().then((res) => {
const result = res;
this.setInternalState('opStatus', 'completed');
this.invokeEvent('comicStripUpdate', { status: 'success', result });
});
});
}
}
return ComicStripType;
});
InstanceFactory Types
vb/InstanceFactory
With an InstanceFactory type, authors can declaratively plug in any JET type or a custom type,
and use it with a special Visual Builder variable (instance factory variable). The
InstanceFactory type:
• Supports creating immutable, or re-creatable (type) classes.
1-102
Chapter 1
Variables
• Many constructs in JET are immutable classes that are then assigned to component
properties. As a framework, Visual Builder facilitates the (re)creation of these classes, and
reassignment when the configuration of these classes change .
• The vb/InstanceFactory variable takes in the JS (type) class, as well as the parameters
to the constructor. When bound, this variable provides an 'instance' of the class (along with
the 'constructorParams').
• When the constructor parameters change, the InstanceFactory variable will automatically
create a new instance of the class.
• Like regular variables, a VB 'valueChanged' event is fired when an InstanceFactory
variable changes. The event payload will have the old and new values containing the two
properties constructorParams and instance.
• The 'constructorParams' of the variable alone will be serialized and persisted, not the
instance. If the constructorParams includes a property that references another
InstanceFactory variable, then that variable needs to be marked 'persisted', if author wants
to persist the full tree. It's preferable that authors always update the 'constructorParams',
so the instance is created automatically. If the instance is updated separately from
constructorParams, the persisted state may not accurately reflect the correct state.
Code Description
• Line 3: use the JET array data provider type
1 "variables": { ojs/ojarraydataprovider. This module is
2 "customersADP": { automatically loaded when the variable is
3 "type" : "ojs/ created, because a require mapping for ojs
already exists.
ojarraydataprovider",
– you must use a '/' in its name
4 "constructorParams": []
• Line 8: types declaration for ojs/
5 }
ojarraydataprovider
6 },
• Line 10: indicates that instance of JET ADP is
7 "types": { created using a vb/InstanceFactory.
8 "ojs/ojarraydataprovider": { – An author can use the short convention, in
9 ... which case the type name is assumed to
10 "constructorType": "vb/ be the require JS module, Or use the
InstanceFactory" longer convention "vb/
11 } InstanceFactory<ojs/
12 } ojarraydataprovider>", if the
typename is different than the actual
require path.
1-103
Chapter 1
Variables
Code Description
• Line 4: customersData is the data array
1 "customersADP": { • Line 5: options object
2 "type" : "ojs/
ojarraydataprovider",
3 "constructorParams": [
4
"{{ $page.variables.customersData }}"
,
5 {
6 "keyAttributes": "id",
7 "textFilterAttributes": [
8 "lastName",
9 "firstName"
10 ]
11 }
12 ]
13 }
<oj-select-single id="ss11"
value="{{ $variables.customerId }}"
constructorParams
• the array of params passed to the constructor of the type
• the constructorParams can be used in EL expressions as well for readonly expressions
• $variables.customersADP.constructorParams
1-104
Chapter 1
Variables
Note:
The properties defined on the instance can be mutated directly, and will be reflected
on the instance stored in redux.
The methods available on the instance can be called directly.
The properties and methods supported on the instance are assumed to be declared
by the type author using typescript or at design-time. This information is not relevant
for runtime purposes.
"incidentsList": {
"type": "vb/ServiceDataProvider2",
"constructorParams": [
{
"endpoint": "demo-data-service/getIncidents",
"keyAttributes": "id",
"itemsPath": "result",
"uriParameters": "{{ $variables[\"technicianURIParams\"] }}"
}
]
},
"incidentsListView": {
"type": "ojs/ojlistdataproviderview",
"constructorParams": [
"{{ $page.variables.incidentsList.instance }}", // SDP2
{
"sortCriteria": [
{
"attribute": "priority",
"direction": "ascending"
}
]
}
]
}
"setFilterCriterion": {
"module": "vb/action/builtin/assignVariablesAction",
"parameters": {
"$page.variables.incidentsListView.constructorParams[1]": {
"source": {
1-105
Chapter 1
Variables
"op": "$eq",
"attribute": "status",
"value": "accepted"
},
"mapping": {
"$target.filterCriterion": {
"source": "$source",
"reset": "empty"
}
},
"reset": "none"
}
}
}
"resetVariables": {
"module": "vb/action/builtin/resetVariablesAction",
"parameters": {
"variables": [
"$page.variables.incidentsListView"
]
}
}
Note:
This option is not supported.
"callGetCapabilityChain": {
"root": "getCapabilityOnLDPV",
"actions": {
"getCapabilityOnLDPV": {
"module": "vb/action/builtin/callVariableMethodAction",
"parameters": {
"variable": "$page.variables.incidentsListView",
"method": "getCapability",
"params": [
"sort"
]
}
1-106
Chapter 1
Variables
}
}
}
1 "assignInstanceAndCPToListViewVar": {
2 "module": "vb/action/builtin/assignVariablesAction",
3 "description": "update variable instance and constructorParams
declaratively",
4 "parameters": {
5 "$page.variables.incidentsListView": {
6 "module": "{{ $application.builtinUtils.assignmentUtils }}",
7 "functionName": "assignValue",
8 "params": [
9 {
10 "instance":
"{{ $chain.results.setFilterCriterion_priorityLow.instance }}",
11 "constructorParams":
"{{ $chain.results.setFilterCriterion_priorityLow.constructorParams }}"
12 }
13 ]
14 }
15 }
16 }
Note:
The (requireJS) prefix 'oj-dynamic' must be mapped to the root of the components/
providers. Typically, this would be done using the declarative "requirejs" syntax in
app-flow.json.
Binding Syntax
The binding for these variables is different than typical Visual Builder variables; each of these
variables expose the JET metadata provider as a 'provider' property of the variable.
1-107
Chapter 1
Variables
vb/DynamicLayoutMetadataProviderDescriptor
The following parameters are mutually exclusive:
Parameter Description
endpoint A standard Visual Builder endpoint ID, in the form
of <service ID> / <operationID>, in an OpenAPI3
document with appropriate JSON Schema type
information.
path A path to a JSON file, which contains a (JET-
defined) JSON descriptor for the data.
"metadata": {
"employee": {
"type": "vb/DynamicLayoutMetadataProviderDescriptor",
"defaultValue": {
"endpoint": "sales/getAllSales"
}
},
"department": {
"type": "vb/DynamicLayoutMetadataProviderDescriptor",
"defaultValue": {
"path": "dynamicLayouts/some/path",
"operationId": "get_Chickens",
}
}
}
vb/ContainerMetadataProviderDescriptor
There is no defaultValue.
"metadata": {
"myContainerLayoutVar": {
"type": "vb/ContainerMetadataProviderDescriptor"
},
vb/HeterogeneousMetadataProviderDescriptor
Parameter Description
discriminator The field in the data that contains the options that
can be used to determine which metadata provider
to use for each new provider.
"metadata": {
"incidentsProvider": {
1-108
Chapter 1
Variables
"type": "vb/HeterogeneousMetadataProviderDescriptor",
"defaultValue": {
"discriminator": "discriminatorField"
}
}
},
vb/ServiceMetadataProviderDescriptor
Parameter Description
endpoint A standard VB endpoint ID, in the form of <service
ID> / <operationID>, in an OpenAPI3 document
with appropriate JSON Schema type information.
"metadata": {
"employee": {
"type": "vb/ServiceMetadataProviderDescriptor",
"defaultValue": {
"endpoint": "sales/getAllSales"
}
}
}
vb/JsonMetadataProviderDescriptor
Requires that 'oj-dynamic' prefix be (requireJS) mapped to the root of the Dynamic UI
Components.
The following parameters are mutually exclusive:
• path - path to a JSON file
• data - a (JS) object
"metadata": {
"employee": {
"type": "vb/JsonMetadataProviderDescriptor",
"defaultValue": {
"path": "path/to/some.json"
}
}
}
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"
1-109
Chapter 1
Variables
},
"yetAnotherVariable": {
"type": "number",
"defaultValue": 10
}
The following table shows how a variable is initialized, based on its type, when no default value
is provided.
1-110
Chapter 1
Variables
Expressions must be wrapped in expression syntax :{{ expr }}. and the expression must be
the entire value. Expressions can also call external functions via the page function module.
To reference another variable in a default value, you can do the following:
"nameOfVariable": {
"type": "application:myType",
"defaultValue": {
"foo": "{{ $application.variables.someOtherVariable }}"
}
}
Since these are expressions, you can also add simple Javascript code to the values:
"myOtherVariable": {
"type": {
"someBoolProperty": "boolean"
},
"defaultValue": {
"someBoolProperty": {{ $application.variables.someOtherVariable === true }}"
}
}
Input Variables
Variables can also be inputs to the page.
There are two types of input. The first consists of inputs that come from the URL. The second
type consists of inputs that are passed internally by the framework. To mark a variable as an
input, you can use the following properties:
"nameOfVariable": {
"type": "string",
"input" "fromCaller/fromUrl"
"required": true
}
Here the input is either "fromCaller" or "fromUrl". If it is "fromCaller", it will be passed internally
using the params property of the navigate action. If it is "fromURL", it will be passed via the
URL request parameter of the same name, like ?myVar=someValue. If the "required" property is
true, the variable value will be required to be passed during a navigation or page load.
The implicit object $parameters is used to retrieve the input parameter values inside
the vbBeforeEnter event handler. Input variables do not exist until the vbEnter event.
1-111
Chapter 1
Variables
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 1-27 Using a Persisted Variable
"variables": {
"sessionToken": {
"type": "string",
"persisted": "session"
}
}
1-112
Chapter 1
Constants
Specify the rateLimit property, with a timeout property in milliseconds, to limit how often the
onValueChanged event is fired on that variable. For example:
"pageVar": {
"type": "string",
"onValueChanged": {...},
"rateLimit": {
"timeout": 1000 // in milliseconds
}
}
The default is to wait for the timeout to expire after all changes stop before firing the change
event.
Constants
Constants are scoped like variables, but their values can't be changed through assignment.
Constants have the following properties and restrictions:
• The scope of a constant can be page, flow, application, or action chain. The value of a
constant is defined declaratively in the descriptor using the constants property.
• The value of a constant can be an expression. The expression can refer to previously-
defined constants and variables in the current scope or application/flow.
• Constants are evaluated first, so expressions in variables can refer to constants.
• The name of a constant cannot be used by a variable in the same scope.
• Constants can be used in action chains.
• A constant can be an input parameter to a page or action chain.
• A constant cannot be of a built-in type.
• A constant holds a value that is immutable (contrary to JavaScript). For instance, in the
case where the content is an object, this means the object's contents (for example, its
properties) cannot be altered.
• Constants do not dispatch change events, since their values never change.
"constants": {
"myConstant": {
"type": "string",
"description": "A useful constant",
"defaultValue": "This string"
}
}
Type
Constant type is the same as for variable except it cannot be a built-in type.
Default Value
Static Default Value. Constants hold a value that is immutable (unlike 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. The value of a constant can be overridden in an
1-113
Chapter 1
Constants
extension during initialization, but once the value is set, it cannot be changed. (discussed
below).
Dynamic Default Value. A constant's default value can be an expression that contains
variables. In this this case, the constant will change when the variable value changes. That
change triggers a valueChange event that can be listened to using the onValueChanged
property:
"constants": {
"fullName": {
"defaultValue": "{{ $variables.firstName + ' ' + $variables.lastName }}",
"onValueChanged": {
"chains": [
{
"chainId": "fullNameChanged"
}
]
}
}
}
Input
Constant input is the same as for variable.
Extension
Like variables, constants can be accessed by downstream or dependent extensions if they are
defined in the interface section of the base container.
"interface": {
"constants": {
"extendableConstant": {
"type": "string",
"description": "A constant visible to extensions",
"defaultValue": "A string"
}
}
}
Additionally, when extending a container with an interface constant, the (base) value of the
constant can be changed on the extending container, using the defaultValue property, in the
extensions section:
"extensions": {
"constants": {
"extendableConstant": {
"defaultValue": "Value from the extension"
}
}
}
Note that the onValueChanged can also be overwritten. In that case, the chain(s) defined in the
extension will be invoked instead the one(s) in the base object.
1-114
Chapter 1
JavaScript Action Chains
JavaScript Actions
This section lists the built-in JavaScript actions that are available in Visual Builder for creating
JavaScript action chains.
Assign Variable
This action is used to assign a value to a local, page, flow, or application variable. It can also
be used to create a local variable.
When using code to reference a variable, the scope must be specified, unless it's defined in
the current scope. If the scope isn't specified, it means it's the current scope. For example, on
a page, the page's variables would be referenced as $variables.myVar instead
of $page.variables.myvar.
Note:
You can call a JSON action chain from a JavaScript action chain using this action;
however, you can't call a JavaScript action chain from a JSON action chain.
1-115
Chapter 1
JavaScript Action Chains
},
});
Return Values
The call returns a result if the called action chain returns a result.
Call Component
A Call Component action provides a declarative way to call methods on JET components.
Here are details about this action's parameters:
Call Function
This action is used to call a function defined for the current flow, page, or application (web
app). For extensions, it's used to call a function defined for the current flow, page, App UI, or
extension. These functions are referred to as module functions, and they're created and edited
using the JavaScript editor for a particular scope.
Using code, the syntax for specifying a module function that isn't a page function is:
$<scope>.functions.someFunction();
$functions.someFunction();
Example
Suppose this function is defined for a page:
sum(num1, num2) {
return num1 + num2;};
};
1-116
Chapter 1
JavaScript Action Chains
You call the function and assign its result like this:
const sumResult
= $functions.sum($variables.firstNum_pv, $variables.secondNum_pv);
For extensions, you can create global functions, which would be available to all App UIs in the
extension, and referenced using this syntax, <artifact-scope>.modules.<module-
id>.<function-name>. For example:
$application.modules.calculator.add
For more about using global functions in extensions, see Add Global Functions.
Return Values
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.
Call REST
The call REST action is used to make a REST call in conjunction with the service definitions.
Internally, this action uses the REST Helper, which is a public utility. Its parameters are as
follows.
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.
1-117
Chapter 1
JavaScript Action Chains
1-118
Chapter 1
JavaScript Action Chains
• GET:
– uriParams parameter is used to provide any required input parameters, such as an ID
input parameter to get a single record.
Here's an example of a GET endpoint call to get a single record. The empIDToGet_ip
variable is an input parameter that passes the record's ID to the action chain that
contains this Call REST call:
• DELETE:
– uriParams parameter is used to provide the ID of the record to delete.
Here's an example of a DELETE endpoint call to delete a record. The
empIDToDelete_ip variable is an input parameter that passes the record's ID to the
action chain that contains this Call REST call:
• PATCH:
– body parameter is set to the variable containing the record with the updated data.
– uriParams parameter is used to provide the ID of the record to update.
Here's an example PATCH endpoint call:
1-119
Chapter 1
JavaScript Action Chains
Service Definitions
If your service connection details are static, the details, such as the server, path, and schema
of the request and response, are stored in the openapi3.json file for the service connection.
To view or edit a service's definition, select the service connection in the Services pane, then
open the Source tab. The editor uses the OpenAPI3 specification and JSON format.
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.
1-120
Chapter 1
JavaScript Action Chains
mytransform(configuration,
options, context)
1-121
Chapter 1
JavaScript Action Chains
Here are the transform functions defined in the page module, located on the page’s JavaScript
tab:
class PageModule {
transformCityNames(result) {
let tr = {};
if (result.body) {
tr = result.body.items;
for (let i = 0; i < tr.length; i++) {
tr[i].cityName = tr[i].cityName + " (city)";
}
}
}
sort2(configuration, options) {
configuration.url = configuration.url + options;
return configuration;
}
}
1-122
Chapter 1
JavaScript Action Chains
For details about working with business objects, refer to Accessing Business Objects Using
REST APIs.
Call Variable
This action is used to call a method of an InstanceFactory variable that has been defined in the
current scope (flow, page, or application). Using this action with any other type results in an
error.
You can call any method on the current instance associated with the InstanceFactory variable,
including asynchronous ones. However, since actions are by design synchronous, this action
will wait for the asynchronous call to resolve before proceeding to the next action in the chain.
Here's an example of a call to an InstanceFactory variable's method:
1-123
Chapter 1
JavaScript Action Chains
Return Values
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.
Code
In the Design editor, the Code action is used to add JavaScript code to an action chain. To do
so, add the Code action from the Action pallet to the action chain and enter the code in the
Properties pane.
In the Code editor, you can use this action to create a local function.
Note:
This action is not necessary for a VB Array Data Provider variable, since the data
array of an ADP variable, exposed via the data property, can be updated directly
using the Assign Variable action. Assigning the data array is automatically detected
by Visual Builder, and all listeners are notified of this change. Users will be warned of
this when the fireDataProviderEvent is used with an ADP, 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.
This table provides details about the parameters for the Fire Data Provider Event action. For
further details, see DataProviderOperationEventDetail in Oracle JET API Reference.
1-124
Chapter 1
JavaScript Action Chains
await Actions.fireDataProviderEvent(context, {
target: $variables.employeeListSDP,
refresh: null,
});
1-125
Chapter 1
JavaScript Action Chains
The SDP's itemsPath property specifies where the added records are in the
response payload, relative to the root of the response. Here are three different
structures for an add operation's response and the corresponding itemsPath
specification:
1. Added records (just one in this example) are provided as an array, at the
root of the response:
[
{
"id": 149,
"firstName": "Qinqin",
"lastName": "Han"
}
]
1-126
Chapter 1
JavaScript Action Chains
2. Added records (just one in this example) are provided in an array, which is
in an object's property, such as this object's items property:
{
"items": [
{
"id": 149,
"firstName": "Qinqin",
"lastName": "Han"
}
],
"count": 1,
"hasMore": false,
"offset": 0
}
1-127
Chapter 1
JavaScript Action Chains
{
"foo" : {
"bar" : [
{
"id": 149,
"firstName": "Qinqin",
"lastName": "Han"
}
]
}
}
keys: [callRestCreateEmployeeResult.body.id],
• metadata: Array.<ItemMetadata.<KeyValue>>; required for optimal
performance. Passes the key values of the added records. Here's an example
value for this parameter:
1-128
Chapter 1
JavaScript Action Chains
Fire Event
This action allows you to fire a custom event that has been defined in your application, flow,
page or fragment, using the Events tab. A custom event can carry a payload that you define
when you create the event, and the payload is passed to the event using the Fire Event action.
Here's a quick overview of how a custom event and the Fire Event action are used:
1. Create a custom event, defining parameters if required.
2. Create an event listener, which can start more than one action chain:
a. Assign it the custom event
b. Create a new action chain for the event, which is launched when the event is triggered.
Create the action chain through the Event Listener tab, because if the listener's custom
event has input parameters, the action chain is created with an event input parameter.
This event object will contain the custom event's input parameters (example:
1-129
Chapter 1
JavaScript Action Chains
Fire Notification
This action is used to fire a "vbNotification" event to display a message to the user in the
browser.
There are four types of notifications: Info, Error, Warning, and Confirmation. They display a
summary and a message underneath:
1-130
Chapter 1
JavaScript Action Chains
"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.
Here are details about this action's parameters:
await Actions.fireNotificationEvent(context, {
message: $variables.message,
summary: $variables.summary,
displayMode: 'persist',
type: 'error',
});
For Each
This action lets you execute one or more actions for each item in an array.
Here are details about this action's parameters:
The "mode" parameter allows for serial or parallel action. The default is serial, for which each
"actionId" call is only made for an item 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 For Each 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:
1-131
Chapter 1
JavaScript Action Chains
Return Values
On success, an array is returned with each element containing the return value from the last
action in the loop, from each iteration. For instance, if the loop contains two actions that return
results, actionA → actionB, and the loop iterates 5 times, the returned array will have 5
elements, each corresponding to an iteration and containing actionB's result from that iteration.
To reset the scope's Dirty Data status back to 'notDirty', use the Rest Dirty Status action.
Here's a sample action chain that uses this action, which is started by a vbBeforeExit event
listener for the page:
async run(context) {
const { $page, $flow, $application, $constants, $variables } = context;
1-132
Chapter 1
JavaScript Action Chains
Get Location
The Get Location 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, so as to associate the permission prompt with the
action they just initiated.
Here are details about this action's parameters:
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.
If geolocation is not supported by the browser, or a parameter with a wrong type is detected,
an error is returned by results.getCurrentLocation.error. If a PositionError occurs when
1-133
Chapter 1
JavaScript Action Chains
if (getLocationResult.getCurrentLocation.error != null) {
await Actions.assignVariable(context, {
variable: '$variables.coords_pv',
value: getLocationResult.coords,
});
} else {
await Actions.fireNotificationEvent(context, {
message: getLocationResult.getCurrentLocation.error.message,
summary: 'Error',
});
}
If
The If action is used to add conditions.
Login
This action launches the login process as defined in the Security Provider implementation.
It invokes the handleLogin function on the Security Provider with the returnPath argument.
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.
1-134
Chapter 1
JavaScript Action Chains
await Actions.login(context, {
returnPath: '/loginpage',
});
Logout
This action launches the logout process as defined in the Security Provider implementation.
It invokes the handleLogout function on the Security Provider with the logoutUrl argument.
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, use the logout URL of the Security Provider
configuration.
• After the user is logged out, the application continues to the default page of the application.
Here's an example of a call to the Logout action:
await Actions.logout(context, {
logoutUrl: $variables.logoutURL_pv,
});
Navigate Back
The Navigate Back action is used to return to the previous page in a browser's history.
This table describes the parameters for the Navigate Back action:
Here's an example of a call to the Navigate Back action, in which two parameters are passed:
Navigate To Application
The Navigate To Application action is used to navigate to a navigable page or flow in a
specified App UI, and if required, to pass parameters to the page or flow. For a page or flow to
1-135
Chapter 1
JavaScript Action Chains
be navigable, meaning you can navigate to it from a different App UI, that page or flow must be
set as navigable, as will be explained for this action's flow and page parameter.
This table describes the parameters for the Navigate To Application action:
page The page within the selected flow to navigate to. Only pages that have their "Let
other App UIs navigate to this page" setting enabled on their Settings tab can be
navigated to
params An object with the parameters to pass to the application, if required.
Here's an example of a call to the Navigate To Application action, in which an input parameter
is passed to the application:
The navigation is pushed to the browser history, so pressing the browser's Back button
restores the previous values of the input parameters.
1-136
Chapter 1
JavaScript Action Chains
Navigate To Flow
For base apps (web apps), this action is used to navigate to a flow in the current application,
and if necessary, to pass parameters to the flow.
For App UIs, this action is used to navigate to a flow in the current App UI, and if necessary, to
pass parameters to the flow. To navigate to a flow in a different App UI, use the Navigate to
Application action.
This table describes the parameters for the Navigate To Flow action:
Here's an example of a call to the Navigate To Flow action, in which two input parameters are
passed to the flow:
Navigate To Page
For base apps (web apps), this action is used to navigate to a page in the current application,
and if necessary, to pass parameters to the page.
For App UIs, this action is used to navigate to a page in the current App UI, and if necessary, to
pass parameters to the page. To navigate to a page in a different App UI, use the Navigate to
Application action..
This table describes the parameters for the Navigate To Page action:
1-137
Chapter 1
JavaScript Action Chains
Here's an example of a call to the Navigate To Page action, in which an input parameter is
passed to the page:
The navigation is pushed to the browser history, so pressing the browser's Back button
restores the previous values of the input parameters.
Open URL
The Open URL action is used to navigate to an external URL. 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 can open local file attachments as well as remote resources.
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:
• .pdf
• .doc
• .txt
• .text
• .ppt
• .rtf
• .xls
• .mp3
• .mp4
• .csv
The very first time, the user gets 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 fails with the
appropriate error. After a file is first opened, it will always be opened with the same application
across all Visual Builder installed apps on the device.
If the specified file is not local or if the file extension is not recognized, this action will use
Cordova's plugin cordova-plugin-inappbrowser to open the specified URL.
This table describes the parameters for the Open URL action:
1-138
Chapter 1
JavaScript Action Chains
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. The page
input parameters will be remembered, even if their type is 'fromCaller'.
Here's an example of a call to the Open URL action, in which one parameter is passed:
await Actions.openUrl(context, {
url: $variables.urlToOpen_pv,
params: {
inParam: $variables.itemID,
},
hash: $variables.hashPart_pv,
history: 'push',
windowName: '_self',
});
Reset Variables
The Reset Variables action is used to reset variables to their default values, as defined in their
variable definitions.
This table describes the parameters for the Reset Variables action:
1-139
Chapter 1
JavaScript Action Chains
Note:
If a single variable expression is provided instead of
an array, it is implicitly treated as an array of one
variable.
Each expression in the array has to resolve to a variable or variable property, and
variables must be prefixed with their scope:
• $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)
Here's an example of a call to the Reset Variables action, in which two variables are to be
reset:
await Actions.resetVariables(context, {
variables: [
'$page.variables.firstNum_pv',
'$page.variables.secondNum_pv',
],
}, { id: 'resetFirstAndSecondNum' });
Return
The Return action is used to return a payload for an action chain and to return control back to
where the action chain was called. For instance, action chain A can call action chain B, which
returns a value, then action chain A can use that returned value for further processing.
The Return action can also be used to exit an action chain early due to an exception, such as
an invalid value, or some other condition. If no value is returned by the Return action, the value
of undefined is returned by default.
For the Run In Parallel action, which uses aysc() functions to run blocks of code in parallel,
the Return action can be used to return a value for a block of code. For further details, see Run
in Parallel.
Run in Parallel
The Run in Parallel action is used to run multiple action chains in parallel, and it can also be
used to wait for their results to produce a combined result.
1-140
Chapter 1
JavaScript Action Chains
The actions to run for each sequence are placed within an asyn() method, and the value
returned by the asyn() method is put into the array returned by the Run in Parallel action. The
first element of the returned array contains the result from the first asyn() method, the second
element contains the result from the second asyn() method, and so on.
Here's an example of the Run in Parallel action, which returns its results in an array named
empInfo. In parallel, the action makes REST calls to get an employee's office location,
department, and team. The employee's information is then displayed:
return callRestGetOfficesResult.body.location;
},
async () => {
return callRestGetDepartmentResult.body.name;
},
async () => {
const callRestBusinessObjectsGetTeamResult = await
Actions.callRest(context, {
endpoint: 'businessObjects/get_Team',
uriParams: {
'Team_Id': team_ip,
},
});
return callRestBusinessObjectsGetTeamResult.body.name;
},
].map(sequence => sequence()));
await Actions.fireNotificationEvent(context, {
summary: 'Employee Info',
message: 'LOCATION: ' + empInfo[0] + ' DEPARTMENT: ' + empInfo[1] + '
TEAM: '+ empInfo[2],
});
}
1-141
Chapter 1
JavaScript Action Chains
Return Values
This action returns an array (empInfo) with the first element (index 0) containing the value
returned from the first asyn() method, the second element containing the value from the
second asyn() method, and the third element containing the value from the third asyn()
method.
Scan Barcode
Use the Scan Barcode action in your mobile application to scan QR codes and barcodes for
details such as URLs, Wi-Fi connections, and contact information.
The parameters for this action are:
Here's an example of a call to the Scan Barcode action, in which a bitmap returned by a
module function is used for the Image parameter:
Return Values
On success, a DetectedBarcode object is returned using the auto-generated variable shown by
the Store Results In parameter. If the browser does not support the Shape Detection API or if
a specified format is not supported, an exception is thrown.
1-142
Chapter 1
JavaScript Action Chains
Share
The Share action is used to invoke the native sharing capabilities of the host platform in order
to share content with other applications, such as Facebook, Twitter, Slack, SMS and so on.
Invoke this action following a user gesture, such as a button click. Also, we recommend that
the Share action's UI only be shown if navigator.share is supported by the given browser, as
in this HTML code:
<oj-button disabled="[[!navigator.share]]">Share</oj-button>
await Actions.webShare(context, {
title: document.querySelector('h1').textContent,
text: 'Check out this cool new app!',
url: document.querySelector('link[rel=canonical]') &&
document.querySelector('link[rel=canonical]').href || window.location.href,
});
Switch
Use the Switch action to select the actions to execute for a specific case value. If a case value
is not matched, the "default" case is executed.
Here's an example of a Switch code block that returns a language's three letter code:
switch (language) {
case 'English':
return 'eng';
break;
case 'Chinese':
return 'chn';
break;
case 'Spanish':
return 'spn';
break;
default:
return 'error';
break;
}
1-143
Chapter 1
JSON Action Chains
Try-Catch-Finally
This action is used to add Try, Catch, and Finally blocks in order to gracefully handle errors
and avoid program crashes.
JSON Actions
A list of built-in actions, JSON based, available in Visual Builder for applications
Note:
Action definitions minimally have a "module" property that specifies the action
implementation. Actions can also have an optional "label" property, which is user-
friendly.
1-144
Chapter 1
JSON Action Chains
• $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.
1-145
Chapter 1
JSON Action Chains
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.
Example
"$page.variables.target": {
"source": "{{ $page.variables.source }}",
"mapping": {
"$target.a": "$source.b",
"$target.b.c": "$source.c.b"
}
}
Example
"$page.variables.target": {
"source": "{{ $page.variables.source }}",
"mapping": {
"$target.a": "$source.b",
"$target.b": {
"source": "$source.c"
"mapping": {
"$target.c": "$source.b"
}
}
}
}
1-146
Chapter 1
JSON Action Chains
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;
}
Note:
You can call a JSON action chain from a JavaScript action chain using this action;
however, you can't call a JavaScript action chain from a JSON action chain.
1-147
Chapter 1
JSON Action Chains
The outcome and result will be the outcome and result of the last action executed in the called
action chain.
$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.
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" }]
}
}
}
}
1-148
Chapter 1
JSON Action Chains
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;
}
1-149
Chapter 1
JSON Action Chains
Note:
Note that this is
deprecated. Instead,
use 'contentType' and
'fileContentType'.
1-150
Chapter 1
JSON Action Chains
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,
1-151
Chapter 1
JSON Action Chains
"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 }}"
}
}
}
}
}
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.
1-152
Chapter 1
JSON Action Chains
mytransform(configuration,
options, context)
1-153
Chapter 1
JSON Action Chains
1-154
Chapter 1
JSON Action Chains
return configuration;
}
Outcomes
The Call REST action has the following outcomes.
"callGetCapabilityChain": {
"root": "getCapabilityOnLDPV",
"actions": {
"getCapabilityOnLDPV": {
"module": "vb/action/builtin/callVariableMethodAction",
"parameters": {
"variable": "$page.variables.incidentsListView",
"method": "getCapability",
"params": [
"sort"
]
}
1-155
Chapter 1
JSON Action Chains
}
}
}
"incidentsListLDPV": {
"type": "ojs/ojlistdataproviderview",
"constructorParams": [
"{{ $page.variables.incidentsList.instance }}",
{
"sortCriteria": [
{
"attribute": "priority",
"direction": "ascending"
}
]
}
],
"persisted": "session"
}
The outcome is either 'success' if the function call was successful, or a 'failure' outcome. An
error is thrown for configuration errors.
The result payload is equivalent to whatever the function returns (which may be undefined if
there is no return). If the function returns a promise, the result payload will be whatever is
resolved in the promise.
EditorUrl Action
This action is used to build the URL of the Visual Builder editor from an application at runtime.
It gathers multiple pieces of information and returns a URL with request parameters
representing various contextual info needed by the editor.
The action module for this action is vb/action/builtin/editorUrlAction.
Note:
This action should not be used for mobile applications.
The base URL pointing to the editor location is either passed as an argument to the action or
has to be defined in the EDITOR_URL property of the vbInitConfig global object. If this value
is not available, the action will abort with an error. Depending if the dynamicLayout request
1-156
Chapter 1
JSON Action Chains
parameter is defined, the editor will either edit the current page or the ruleset of a specific
dynamic component.
Here is an example of editorUrlAction usage:
"openEditor": {
"variables": {
"componentId": {
"type": "string",
"input": "fromCaller",
"required": true
}
},
"root": "editorUrl",
"actions": {
"editorUrl": {
"module": "vb/action/builtin/editorUrlAction",
"parameters": {
"componentId": "{{ $variables.componentId }}"
},
"outcomes": {
"success": "openEditor"
}
},
"openEditor": {
"module": "vb/action/builtin/openUrlAction",
"parameters": {
"url": "{{ $chain.results.editorUrl }}",
"windowName": "VB_EDITOR"
}
}
}
}
Parameter Description
editorUrl URL of the VB Extension editor (optional). If not
defined, use the value of
vbInitConfig.EDITOR_URL.
componentId The id of the component to use to retrieve the
dynamic layout. (optional)
1-157
Chapter 1
JSON Action Chains
Note:
This action can be used with a vb/ArrayDataProvider2. It does not need to be used
with a legacy vb/ArrayDataProvider because the 'data' is already exposed as a
property on the variable. This allows page authors to directly mutate the data array
using the assignVariables action. This assignment is automatically detected by Visual
Builder, and all listeners of this change are notified, removing the need to use a
fireDataProviderEventAction. Users will be warned when the
fireDataProviderEventAction is used with a legacy ArrayDataProvider, prior to
mutating the 'data' property directly.
A mutation event can include multiple mutation operations (add, update, remove) as long as
the id values between operations do not intersect. This behavior is enforced by JET
components. For example, you cannot add a record and remove it in the same event, because
the order of operations cannot be guaranteed.
The action can return either success or failure. Success returns null, while failure returns the
error string.
1-158
Chapter 1
JSON Action Chains
1-159
Chapter 1
JSON Action Chains
"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)
}
}
}
},
"variables": {
"productListSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"keyAttributes": "id",
1-160
Chapter 1
JSON Action Chains
"responseType": "application:productSummary[]"
}
},
}
"chains": {
"deleteProductChain": { // (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)
}
}
}
1-161
Chapter 1
JSON Action Chains
}
},
"actions": {
"fireNotification": {
"module": "vb/action/builtin/fireNotificationEventAction",
"parameters": {
"summary": "[[ $page.variables.summary ]]",
"message": "[[ $page.variables.message ]]",
"displayMode": "persist",
"type": "info"
}
}
}
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:
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:
1-162
Chapter 1
JSON Action Chains
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 1-32 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 }}' }"
}
}
}
}
1-163
Chapter 1
JSON Action Chains
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.
1-164
Chapter 1
JSON Action Chains
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 ]].
1-165
Chapter 1
JSON Action Chains
"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.
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.
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.
1-166
Chapter 1
JSON Action Chains
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.
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 1-35 Example
An example of a chain using the logoutAction:
"logoutChain": {
"root": "logout",
"actions": {
"logout": {
"module": "vb/action/builtin/logoutAction"
}
}
}
Navigate Action
The action module for this action is "vb/action/builtin/navigateAction".
This action will navigate the user to a page and also pass any parameters to activate that
page. Parameters for this action are:
1-167
Chapter 1
JSON Action Chains
Page input parameters are page variables with the Input Parameter enabled. You can use the
Navigate action to set the value for these input parameters. But if a page parameter was a path
to a deeply nested page, like /shell/main/other, you'll see a list of all input parameters
from each page/flow in the path (that is, input parameters for the shell page, the main flow, as
well as other pages). Name collisions across flows/pages are not accounted for—something
you'll need to keep in mind when defining input parameters.
Here's an example of the navigate action:
"myActionChain": {
"root": "navigate",
"actions": {
"navigate": {
"module": "vb/action/builtin/navigateAction",
"parameters": {
"page": "myOtherPage",
"params": {
"id": "{{ $page.variables.myId }}"
}
}
}
}
}
This returns the outcome 'success' if there was no error during navigation. If navigation
completed successfully, returns the action result true, otherwise false. Returns the outcome
fail with the error in the payload if there was an error.
1-168
Chapter 1
JSON Action Chains
page is done by marking it with the navigation fromExternal property set to 'enabled' in the
page descriptor and the parent flow descriptor.
If you expose a page or flow to navigation, it becomes part of your extension API. That means
that you can no longer rename, delete, or change the required input parameters for this page
or flow, as extensions may depend on them.
When navigating from a page in one App UI to a page in another App UI, and the page in the
second App UI is not exposed, will throw the following error, and navigation will be canceled:
Navigation from page main/main-start to appUI2/main/main-other is not enabled
Property Description
fromExternal Defines if navigation to this page or flow is allowed
from another App UI or from a page extension. For
navigation to be allowed to a page, the entire
hierarchy of containers (parent flow, application)
need to have this property set to 'enabled'.This
property is not required for the default page of an
application.
The default for this property is 'disabled'.
embeddable Defines if this page or flow can be embedded in an
oj-vb-switcher component. For the page to be
allowed to be embedded in a switcher, the entire
hierarchy of containers (parent flow, application)
need to have this property set to 'enabled'.
The default for this property is 'disabled'.
Example 1-36 Page or flow descriptor with navigation fromExternal property set to
enabled
"navigation": {
"fromExternal": "enabled"
}
The 'page' parameter is the ID of a sibling page or a path starting with a sibling page's ID (like
pageId/flowId/...). It cannot be or start with a flow ID.
"parameters": {
"page": "other"
}
1-169
Chapter 1
JSON Action Chains
Example 1-38 Navigate to a sibling page and change content of the nested flow
To navigate to flow main, which is defined under the sibling page other:
"parameters": {
"page": "other/main"
}
"parameters": {
"page": "/"
}
"parameters": {
"page": ""
}
Example 1-41 Navigate to a deeply nested page relative to the application root
To navigate to a deeply nested page relative to the root of the application:
"parameters": {
"page": "/shell/main/other"
}
The 'flow' parameter can only be the ID of a flow defined below the current page or an empty
string.
Example 1-42 Navigate to a specific flow
To change the content of the flow displayed in the current page to the flow main:
"parameters": {
"flow": "main"
}
"parameters": {
"flow": "main",
"page": "other"
}
1-170
Chapter 1
JSON Action Chains
"parameters": {
"flow": ""
}
"parameters": {
"target": "parent",
"flow": "main"
}
"parameters": {
"target": "parent",
"flow": ""
}
"parameters": {
"target": "parent",
"flow": "main",
"page": "other"
}
The 'application' parameter can only be the ID of an App UI defined in the current host
application or an empty string.
When working with App UIs, the flow to be used is defined by the defaultFlow property in
app.JSON.
• When the flow is defined, it replaces the App UI's default flow.
• When both flow and page are defined, the flow is always applied first; page navigation is
relative to the flow.
• When only the page is defined, page navigation is relative to the default flow.
• When the page is a path, it's considered as being relative to the flow.
• When page starts with a backslash (/), it is ignored.
1-171
Chapter 1
JSON Action Chains
"parameters": {
"application": "appUi2"
}
"parameters": {
"application": "appUi2",
"page": "other"
}
"parameters": {
"application": "appUi2",
"page": "other/main/next"
}
where other is a page within the default flow of appUi2, main is a flow, and next is a page.
"parameters": {
"application": "appUi2",
"flow": "main"
}
When main is a sibling of the default flow, the default flow is replaced with the main flow.
"parameters": {
"application": "appUi2",
"flow": "main",
"page": "other"
}
1-172
Chapter 1
JSON Action Chains
"parameters": {
"application": ''
}
Example 1-54 Navigate to a deeply nested page relative to the current App UI's default
flow
To navigate to a deeply nested page next relative to the current App UI's default flow:
"parameters": {
"application": '',
"page": "other/main/next"
}
where other is a page in current App UI's default flow, main is a flow, and next is 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.
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
1-173
Chapter 1
JSON Action Chains
• .mp3
• .mp4
• .csv
The very first time, the user will get an option to select which application to use for opening a
given file type. If no application is available to open such a file, this action will fail with the
appropriate error. Once the given file has been opened once, it will always be opened with the
same application across all Visual Builder installed apps on the device.
If the specified file is not local or if the file extension is not recognized, this action will use
Cordova's plugin cordova-plugin-inappbrowser to open the specified URL.
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 1-55 Open a new window in the browser with the given URL
To open a URL:
"myActionChain": {
"root": "myAction",
"actions": {
"myAction": {
"module": "vb/action/builtin/openUrlAction",
"parameters": {
"url": "http://www.example.com",
"params": {
"id": "{{ $page.variables.myId }}"
},
"windowName": "myOtherWindow"
}
}
}
}
1-174
Chapter 1
JSON Action Chains
Note:
If a single variable
expression is
provided instead of
an array, it will be
implicitly treated as
an array of one
variable.
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:
1-175
Chapter 1
JSON Action Chains
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'.
1-176
Chapter 1
JSON Action Chains
"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 }}" }
}
}
}
}
1-177
Chapter 1
JSON Action Chains
Here's an example of the barcodeAction's metadata used to read QR code from an HTML
image element:
"fromImage": {
"module": "vb/action/builtin/barcodeAction",
"parameters": {
"image": "[ document.querySelector('#qrcode') ]",
"formats": "[[ [ 'qr_code' ] ]]"
},
"outcomes": {
"failure": "showError",
"success": "openUrl"
}
}
Here's another example, using the barcodeAction to process the outcome of the Take Photo
action as a QR code:
"qrCodeFromFile": {
"module": "vb/action/builtin/barcodeAction",
"parameters": {
"image": "[[ $chain.results.takePhoto.file ]]",
"formats": "[[ [ 'qr_code' ] ]]",
"convertBlob": true
},
"outcomes": {
"failure": "showError",
"success": "openUrl"
}
}
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>
1-178
Chapter 1
JSON Action Chains
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.
Example:
"myActionChain": {
"root": "myAction",
"actions": {
"myAction": {
"module": "vb/action/builtin/switchAction",
"parameters": {
"caseValue": "{{ $chain.variables.myCase }}",
"possibleValues": ["case1", "case2"]
1-179
Chapter 1
JSON Action Chains
},
"outcomes": {
"case1": "...",
"case2": "...",
"default": "..."
}
}
}
}
1-180
Chapter 1
JSON Action Chains
URL.revokeObjectURL(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 }}"
]
}
},
1-181
Chapter 1
JSON Action Chains
1-182
Chapter 1
JSON Action Chains
The example below shows a chain called "fetchTechnicianStatsChain" with four actions
chained together to take a REST response and turn the JSON response into a form that can
be used by a Chart UI component. The four actions are:
1. Use a Call REST endpoint action to fetch technician stats.
2. Use an Assign Variables action to map the response from (1) to a form that the Transform
Chart Data action expects. If the REST response is so deeply nested that a simple
transformation of source to target using an Assign Variables action is not possible, page
authors can use a page function (using a Call Function action) to transform the data into a
form that the Transform Chart Data action expects.
3. Use a Transform Chart Data action to take the response from (2) and turn it into a form that
a Chart component can consume.
4. Use an Assign Variables action to store the return value from (3) in a page variable.
"actions": {
"fetchTechnicianStatsChain": {
"variables": {
"flattenedArray": {
1-183
Chapter 1
JSON Action Chains
"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"
}
}
}
}
1-184
Chapter 1
JSON Action Chains
}
}
Note:
Web apps require the web browser running the app to support the Web Share action.
Currently, not all browsers support this native feature.
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>
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.
1-185
Chapter 1
JSON Action Chains
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"
}
}
}
1-186
Chapter 1
JSON Action Chains
"myActionChain": {
"root": "myAction",
"actions": {
"myAction": {
"label": "some action",
"module": "vb/action/builtin/someAction",
"parameters": {
"key": "{{ $page.variables.myVariable }}"
}
}
}
}
1-187
Chapter 1
JSON 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 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>.
1-188
Chapter 1
Flow
}
}
}
}
Flow
A flow is a way to organize your application in independent and shareable units of work that
are building blocks for a single-page application.
The structure of a flow is the same as the structure of an application. A flow has a descriptor
(<name>-flow.json) and a functions module file (<name>-flow.js), and contains pages and
possibly other flows. The id of a flow is the name of the folder that contain the flow structure.
The following example shows an application that contains two flows: main and other.
app-flow.[json|js]
pages/
app-page.[json|js|html]
flows/
main/
main-flow.[json|js]
pages/
start-page.[json|js|html]
flows/
...
other/
other-flow.[json|js]
pages/
start-page.[json|js|html]
1-189
Chapter 1
Flow
Flow Properties
A flow can define a default page, variables, chains, functions, listeners and types.
Here is an example of the descriptor for the flow other:
{
"flowModelVersion": "18.1.5",
"id": "other",
"description": "Flow other",
"defaultPage": "start",
"types": {}
"variables": {},
"chains": {},
"eventListeners": {}
}
All pages of a flow can access variables, chains, functions, listeners and types defined in the
flow. Defining these elements in the flow allows you to share definition and objects that are
used across multiple pages in the flow.
Property Description
defaultPage The defaultPage property is used to define which
page should be the current page of a flow, when
the default page is not specified by the navigation.
In the example application above with the flows
main and other, the path app/other will navigate
to the flow other, and display the flow's default
page.
In the descriptor for the flow other above, the
defaultPage property defines start as the flow's
default page.
types Pages can address a flow type using the
"flow:typeName" syntax, where typeName is a
type defined in the flow.
variables Flow variables can be addressed in any expression
in the page using $flow.
chains In a callChainAction, pages can address a flow
chain using the "flow:chainId" syntax.
<oj-vb-content config="[[vbRouterFlow]]"></oj-vb-content>
The content of the current page of the current flow is displayed in the page at the location of
this component tag in the view (HTML). The currentFlow and currentPage are managed by
1-190
Chapter 1
Flow
Visual Builder using a hierarchy of routers. When navigating in the application, the router
changes the value of the currentPage of a flow, or the currentFlow of a page, and this
determines the content of the oj-vb-content element. The router also manages the URL to
reflect the currentFlow and the currentPage.
For example, when navigating using the path app/flow-a, the current flow for page app is
flow-a, and the content of the default page of flow-a is inserted at the location of the oj-vb-
content tag.
Nesting content at a specific locations in the page allows you to build page templates or shells.
Note:
A flow should only be nested in page. Nesting a flow in a dialog will not work properly.
{
"pageModelVersion": "18.1.5",
"description": "Application Page",
"routerFlow": "main",
"variables": {
...
}
• query (default): the current page path is stored in the URL using a query parameter like
this:
http://myApp/?page=app&app=main
• path: the current page path is stored in the URL using a path segment like this:
http://myApp/vp/app/main
Notice the marker vp added to the URL. It is needed in order for Visual Builder to recognize
where the path to the current page starts.
To change the strategy, use the routerStrategy property in app-flow.json:
{
"applicationModelVersion": "18.1.5",
"id": "flowDemo",
1-191
Chapter 1
Flow
...
"routerStrategy": "path"
}
When using the path router strategy, the server where the application is deployed needs to be
able to handle these URLs in a special way to ensure that browser refreshes and bookmarks
work properly.
Flow Lifecycle
While navigating between flows, for example from flow-a to flow-b, the current flow for a page
changes from flow-a to flow-b. When this change happens, two events notifying the change
are dispatched: 1) flow-b is entered, then 2) flow-a is exited.
Name Description
vbEnter Dispatched when entering a flow after all the flow
scoped variables have been added and initialized.
vbExit Dispatched when exiting a flow before disposing of
flow resources.
Each variable is used to build a path relative to the location of the flow:
<!-- Display an image located in the resource folder in this application -->
<img alt="photo" :src="[[$application.path + 'resources/images/tools.png']]"/>
<!-- Display an image located in the resource folder in this flow -->
<img alt="photo" :src="[[$flow.path + 'resources/images/tools.png']]"/>
app-flow.json
{
1-192
Chapter 1
Flow
The path is relative to the current flow location. If the path starts with /, the path is absolute,
meaning that it is relative to the application directory (the directory where app-flow.json is
located). You can also use a URL for the path. When the path is a URL, the flow will be loaded
from the URL.
Shell Flow
A shell flow is a special flow with only one page. The purpose of a shell flow is to define the
shell page of an application, or of another flow. To make an application use a shell flow, you
enter the id of the shell flow for the defaultPage property of the application. When you do this,
the application will use the default page of the shell flow as the default page of the application:
{
"applicationModelVersion": "18.2.3",
"id": "flowDemo",
"description": "An Application to demonstrate the use of flow",
"defaultPage": "shellFlowId",
...
}
When using a flow for the default page, the flow id is not included in the URL. The flow id is
hidden from the URL and from the path used for navigation.
Note:
Shell flows have the following limitations:
• Only one page can be defined in a shell flow.
• The page cannot make reference to artifacts such as variables or types defined
in the shell flow metadata.
{
"applicationModelVersion": "18.2.3",
"id": "flowDemo",
"description": "An Application to demonstrate the use of flow",
"defaultPage": "shellFlow/crmFlow",
1-193
Chapter 1
Fragments
...
}
In the example above, the flow with the id "crmFlow" will be used as the default flow of the
shell page.
Fragments
Fragments encapsulate a reusable piece of UI, model and code (HTML, JSON and JavaScript)
that can be shared across pages in an application.
Fragments can be added and reused in pages and other fragments in applications, extensions
and app UIs. A fragment can also be used multiple times in the same page, for example,
providing different sets of input parameters to the same fragment, as shown here:
A fragment can also be 'nested' in another fragment. However, when looking at the structure of
applications and extensions, every fragment in a page, no matter how deeply it's 'nested', is an
independent unit that encapsulates its state and 'logic, and is not 'aware' of its container.
When the component above is rendered, it starts loading the fragment identified by the 'name'.
The fragment instance created for the page is identified by the 'id'. The component can have
the following properties:
1-194
Chapter 1
Fragments
Note:
The id property need not be set on the oj-vb-fragment
component. When an id is not provided a system
generated unique id will be used. Though unique for
the container consuming the fragment, this id is not
considered "stable" and cannot be used for persisting
variable values. If you want to persist variable values
in a fragment, you'll need to provide an id, ensuring
that it is both unique and stable, particularly when
fragments are used inside of stamping components.
name Required.
A <string> name of fragment to load. The component loads the physical fragment
artifacts using the 'name' property. This needs to be statically defined and cannot
be an expression.
bridge Required within a VDOM. Also works within a component.
This property allows the fragment to discover the current context and establish a
bridge between the component and Visual Builder eco-system. It's value is always
"vbBridge".
The example below shows the 'bridge' property configured on an oj-vb-fragment.
The same configuration can be used with oj-dynamic-form component as well.
- oj-vb-fragment- For each input parameter a fragment defines via the 'input' property on a variable,
param (sub- this sub-component can be used to provide the values for the input parameters.
component) Parameters marked as "required" in the fragment must be provided.
• name: string, name of param
• value: any, value of the input param
A fragment model (the descriptor JSON) can tag its variables with these properties
to declare the input parameters (see Define Fragment Input Parameters below):
• input ("fromCaller"),
• required, that can be true (if the caller has to pass a value) or false, and
• writeback, that can be set to true to allow the fragment variable value to be
automatically written back to the input parameter variable.
1-195
Chapter 1
Fragments
incidentsShell-page.html Notes
Line 10, 13, 16: <oj-vb-
1 <oj-tab-bar fragment> uses the 'name'
selection="{{ $variables.incidentsLayout }}"> property to specify a static
2 <ul> fragment to load. The 'id'
3 <li id="list">List</li> property is expected to be
4 <li id="map">Map</li> unique to the current page.
5 <li id="schedule">Schedule/li>
Lines 13, 16: component is
6 </ul>
wrapped in an oj-defer (see
7 </oj-tab-bar>
Deferred Rendering of a
8 <oj-switcher
Fragment).
value="[[ $variables.incidentsLayout ]]">
9 <div slot="list">
10 <oj-vb-fragment id="incLL"
name="incidentsListLayout"></oj-vb-fragment>
11 </div>
12 <oj-defer slot="map">
13 <oj-vb-fragment id="incML"
name="incidentsMapLayout"></oj-vb-fragment>
14 </oj-defer>
15 <oj-defer slot="schedule">
16 <oj-vb-fragment id="incSL"
name="incidentsScheduleLayout"></oj-vb-fragment>
17 </oj-defer>
18 </oj-switcher>
Within a fragment, there is a new local $fragment scope can be used within the fragment (this
can be particularly useful when writing expressions):
• $variables / $fragment.variables can be used to refer to the local variable in a
fragment. $fragment.variables is needed for action chains.
Namespaces
Namespaces are used when referencing types in other scopes. The namespaces are similar to
the scopes: global: / application: (for base apps), and fragment: for local scope.
1-196
Chapter 1
Fragments
Property Description
"input": "fromCaller" Identifies the variable or constant as a fragment
input parameter, which the caller of a fragment can
provide.
"required": true Identifies that the variable must be provided by the
caller.
"writeback": true Identifies that the variable's value will be
automatically written back to the input parameter
variable.
Example 1-61 Fragment where the userId and fragFilterCriterion variables are set as
required and fromCaller.
The incidentsSDP variable can use the input param values to initialize its state.
A page that loads a fragment can provide the parameters like this:
incidentShell-page
<oj-vb-fragment-param name="userId"
value="[[ $page.variables.technician.id ]]"></oj-vb-fragment-param>
<oj-vb-fragment-param name="fragFilterCriterion"
value="[[ $page.variables.filterCriterion ]]"></oj-vb-fragment-param>
</oj-vb-fragment>
1-197
Chapter 1
Fragments
incidentsShell-page
"technician": {
"type": "object",
"defaultValue": {
"id": "rosie",
"name": "Rosie Riveter"
}
},
"filterCriterion": {
"type": "object",
"defaultValue": {
"op": "$ne",
"attribute": "status",
"value": "closed"
}
}
In the 'incidentShell-page', the page variables userId and filterCriterion are passed in as
parameters to the fragment ("incidentsListLayout-fragment"). In this example, when the
filterCriterion page variable changes, it updates the fragment parameters, which in this
example is the fragFilterCriterion fragment variable. As a local SDP variable on the
fragment references fragFilterCriterion, a re-fetch is triggered by the SDP using the new
criteria.
incidentsListLayout-fragment
"userId": {
"type": "string",
"input": "fromCaller",
"required": true
},
"fragFilterCriterion": {
"type": "object",
"input": "fromCaller",
"required": true
},
"incidentsSDP": {
"type": "vb/ServiceDataProvider",
"defaultValue": {
"endpoint": "ifixitfast-service/getIncidents",
"keyAttributes": "id",
"uriParameters": {
"technician": "{{ $variables.userId }}"
},
"filterCriterion": "{{ $variables.fragFilterCriterion }}"
}
}
1-198
Chapter 1
Fragments
Note:
Though this is unusual, the caller can pass a reference to the SDP variable defined
by the outer page to a fragment. The reference the fragment variable holds can be
used only for the purposes of rendering. Using an action that mutates the state of the
variable (such as assignVariables or resetVariables) is not allowed, and will throw
errors. Use with extreme caution.
incidentsListLayout-fragment
{
"variables": {
"incidentsSDP": {
"type": "vb/ServiceDataProvider",
"input": "fromCaller",
"required": true
}
}
}
The page that loads the fragment above will provide the input parameters using the oj-vb-
fragment-param sub-component:
incidentsShell-page
<oj-vb-fragment-param name="incidentsSDP"
value="[[ $page.variables.incidentsSDP ]]">
</oj-vb-fragment-param>
</oj-vb-fragment>
"incidentId": {
"type": "string",
"description": "extensions can update the value",
"input": "fromCaller",
1-199
Chapter 1
Fragments
"writeback": true
}
When the fragment variable value changes, the value is automatically written back into the
outer variable selectedIncidentId.
Note:
The expression is wrapped in {{ }}. This is required for the web component
framework to enable writeback.
As an alternative to the configuration above, the other recommended way for a page to be
notified of updates to a fragment variable is for the fragment to fire a custom event (with the
propagationBehavior property set to "container" ) that 'emits' the event to the page, which has
a listener to handle the event. See Custom Fragment Events for details.
For examples of using <oj-defer> with <oj-vb-fragment>, see Fragment Patterns below.
Fragment Events
Fragments support several lifecycle events defined by the system. In addition, fragments also
support custom events that can be handled by listeners defined in the fragment, and further
propagated to the listener bound on the fragment container.
Lifecycle Events
When the lifecycle event is raised, the framework calls the event listener with the name of the
event. Fragments can fire these events when the fragment artifacts load, when the fragment
state is activated, or when the fragment is disposed. Other lifecycle events are currently not
supported by fragments.
1-200
Chapter 1
Fragments
Framework Events
vbNotificationEvent is an example of a framework event that raises a notification for further
processing by a parent container and to display the notification message. This is a special
event that is automatically bubbled up to the parent container(s) without any need for binding
the event on the fragment component. Other specialized types of notification events, such as
SDP vbDataProviderNotification events, also have the same behavior.
Component Events
The behavior and usage of component events in fragments is similar to that in other
components. See Component Events.
Custom Events
Custom events can be declared in fragments under the "events" property. There are two types
of custom events in fragments:
1-201
Chapter 1
Fragments
Property Description
propagationBehavior When this property is set to container, the
fragment component (oj-vb-fragment) can listen
to the fragment event, but fragment listeners cannot
listen to the event.
When this property is not set, the default value is
"self", implying the event can only be handled by
the fragment listeners.
Note:
This property is only
supported by
fragment events.
{
"description": "An incident form fragment",
"title": "Incidents Form Fragment",
"events": {
"saveincident": {
"description": "fired when an incident has to be saved. The mutated
incident data provided in payload",
"propagationBehavior": "container",
"payloadType": {
"data": {
"id": "string",
"problem": "string",
"priority": "string",
"status": "string",
"customer": {
"id": "string"
}
}
}
}
}
...
}
This allows the oj-vb-fragment component that loads the fragment to bind an event listener to
the same event, as shown below:
<oj-vb-fragment name="incident-form"
id="[[ $page.functions.fragmentUniqueId ]]" bridge="[[ vbBridge ]]"
on-saveincident="[[ $page.listeners.saveIncident ]]">
1-202
Chapter 1
Fragments
<oj-vb-fragment-param name="currentIncident"
value="[[ $page.variables.currentIncident ]]"></oj-vb-fragment-param>
</oj-vb-fragment>
WARNING:
Note the 'on-saveincident' attribute. It is important that the event name be lowercase
or camelCase with no hyphens as defined by Web Component DOM event naming
conventions.
{
"description": "A incident form fragment",
"title": "Incidents Form Fragment",
"interface": {
"events": {
"saveincident": {
"description": "fired when an incident has to be saved. The mutated
incident data provided in payload",
"autoWire": "performSaveIncident",
"propagationBehavior": "container",
"payloadType": {
"data": {
1-203
Chapter 1
Fragments
"id": "string",
"problem": "string",
"priority": "string",
"status": "string",
"customer": {
"id": "string"
}
}
}
}
}
},
"chains": {
"fireSaveIncidentChain": {
"variables": {
"incidentPayload": {
"type": "fragment:incidentEventPayload",
"description": "the payload of the incident data to send with
event",
"input": "fromCaller",
"required": true
}
},
"root": "fireCustomSaveIncidentEvent",
"actions": {
"fireCustomSaveIncidentEvent": {
"module": "vb/action/builtin/fireCustomEventAction",
"parameters": {
"name": "saveincident",
"payload": {
"data": "{{ $variables.incidentPayload }}"
}
}
}
}
}
},
"eventListeners": {
"fireSaveIncident": {
"chains": [
{
"chainId": "fireSaveIncidentChain",
"parameters": {
"incidentPayload": {
"id": "{{ $variables.currentIncident.id }}",
"problem": "{{ $variables.currentIncident.problem }}",
"priority": "{{ $variables.currentIncident.priority }}",
"status": "{{ $variables.currentIncident.status }}",
"customer": {
"id": "{{ $variables.currentIncident.customer.id }}"
}
}
}
}
]
}
1-204
Chapter 1
Fragments
},
...
}
Here's the code for page-x, an extended parent container which uses the above fragment:
<oj-vb-fragment name="incident-
form" :id="[[ $page.functions.fragmentUniqueId ]]">
<oj-vb-fragment-param name="currentIncident"
value="[[ $page.variables.currentIncident ]]"></oj-vb-fragment-param>
</oj-vb-fragment>
Here's the code for page, the base parent container of the above fragment that defines the
auto-wired listener:
...
"eventListeners": {
"performSaveIncident": {
"autoWire": "full",
"chains": [
{
"chainId": "performSaveIncidentChain",
"parameters": {...}
}
]
}
}
...
In this example, when the fragment fires the saveincident auto-wired event, the
performSaveIncident auto-wired event listener from the base page container is invoked
because its autoWire property is set to full.
Let's say the base page container defined the auto-wired listener with autoWire set to
selfOnly as shown here:
...
"eventListeners": {
"performSaveIncident": {
"autoWire": "selfOnly",
"chains": [
{
"chainId": "performSaveIncidentChain",
"parameters": {...}
}
]
}
}
...
In this case, no auto-wired listener is invoked because the fragment's immediate parent is the
extended page container that does not have an auto-wired listener defined, while the base
1-205
Chapter 1
Fragments
{
"description": "A product list fragment",
"title": "Product List Fragment",
"referenceable": "extension",
...
}
1-206
Chapter 1
Fragments
To bind an event fired by the fragment onto a listener in the calling page of the extension, the
event in the fragment must be part of the interface. It must also have the
'propagationBehavior' set to 'container'. For details, see Custom Fragment Events.
This example shows the 'saveproduct' event declared by the fragment 'products-list':
"interface": {
"events": {
"saveproduct": {
"description": "fired when a product has been created. The mutated
product is fixed up in a local array and returned",
"propagationBehavior": "container",
"payloadType": {
"data": [
{
"id": "string",
"name": "string",
"unitPrice": "number",
"productCategory": "string"
}
],
"message": "string"
}
}
}
}
A page in the extension that references the above fragment from the dependent extension can
bind the event to a listener on the page using the on-<eventname> attribute:
</oj-vb-fragment>
WARNING:
It is important that the event name be lowercase or camelCase with no hyphens as
defined by Web Component DOM event naming conventions.
1-207
Chapter 1
Fragments
It's important to note that the fragment 'incidents-list' must be marked as 'referenceable' so that
a dependent extension can use it.
{
"description": "A incidents list layout fragment",
"title": "Incidents List Fragment",
"referenceable": "extension",
...
}
To bind an event fired by a referenced fragment to a listener in the calling page template, the
event in the fragment must be part of the interface. It must also have the
'propagationBehavior' set to 'container'. For details, see Custom Fragment Events.
"interface": {
"events": {
"updatedincidentmessage": {
"description": "fired when an incident has been updated. The mutated
incident data is provided in payload",
"propagationBehavior": "container",
"payloadType": {
"data": {
"id": "string",
"problem": "string",
"priority": "string",
"status": "string",
"customer": {
"id": "string"
}
}
}
}
}
}
}
The template in the extension (that references the fragment) can bind the event to a listener
using the "on-<eventname>" attribute.
1-208
Chapter 1
Fragments
WARNING:
It is important that the event name be lowercase or camelCase with no hyphens as
defined by Web Component DOM event naming conventions.
In the following example of a form template, the form is rendered using the fragment dynamic-
form-template-employee:
<template id="formTemplateSimple">
<oj-vb-fragment id="formTemplateSimple_EmpFrag" name="dynamic-form-template-
employee" bridge="[[ vbBridge ]]">
<oj-vb-fragment-param name="$dynamicLayoutContext"
value="[[ $dynamicLayoutContext ]]"></oj-vb-
fragment-param>
</oj-vb-fragment>
</template>
$dynamicLayoutContext
The $dynamicLayoutContext context property needs to be configured when a fragment is used
in form or field templates in layout components. The $dynamicLayoutContext allows:
1-209
Chapter 1
Fragments
Before fragments were used in layout component templates, authors would have used any
number of the context properties (like $fields, $value etc.) that the parent layout
exposed, and bind those to the components they use in the templates. But after adding
support for fragments inside templates, a new container boundary is introduced, so these
context properties are now no longer available/bindable directly by the components inside
the fragment. In order to expose these context properties to fragment components, this
top-level context property was introduced.
Extending a Fragment
When extending a fragment, an extension can override the fragment's metadata (JSON) and
JavaScript. For example, to extend the fragment my-example-fragment, the fragment artifacts
in the extension would be myexample-fragment-x.json and my-example-fragment-x.js.
When you extend a fragment, the fragment overrides are picked up automatically.
For example, an extension extA might define a fragment dynamic-form-employee using the
following HTML and model (omitting the JavaScript for this example):
dynamic-form-employee-fragment.html
dynamic-form-employee-fragment.json
{
"fragmentModelVersion": "22.01.0",
"description": "Fragment that loads a dynamic form",
"title": "Fragment Dynamic Form Employee",
"referenceable": "extension",
"types": {
"getEmployeeByIdResponse": "object"
},
"interface": {
"constants": {
"layoutName": {
"type": "string",
"mode": "readWrite",
"defaultValue": ""
}
}
},
"metadata": {
"employeeByIdMetadata": {
"type": "vb/DynamicLayoutMetadataProviderDescriptor",
"defaultValue": {
"endpoint": "employees/getEmployeeById"
}
}
},
1-210
Chapter 1
Fragments
"variables": {
"getEmployeeById": {
"type": "fragment:getEmployeeByIdResponse"
},
"getEmployeeByIdDetailFormLoadingStatus": {
"type": "string",
"defaultValue": "pending"
},
"getEmployeeByIdDetailFormRenderedFields": {
"type": "any[]"
}
}
...
}
A downstream extension (extB) could extend the fragment in extA above, for example, by
overriding the constant layoutName in order to load a different layout template from the
extension layout. The fragment artifacts in extB might look like the following (in this example,
the JavaScript code is not included because there are no meaningful changes). The
layoutName constant in the extension redefines the layout to be one defined in its extended
layout (extB/formlayout_extended).
dynamic-form-employee-fragment-x.json
{
"fragmentModelVersion": "22.01.0",
"title": "Dynamic form employee fragment extension",
"description": "A fragment extension for dynamic-form-employee-fragment",
"extensions": {
"constants": {
"layoutName": {
"description": "layout name override; layout provider loaded in base
fragment",
"defaultValue": "extB/formlayout_extended"
}
}
},
"variables": {},
"chains": {},
"eventListeners": {},
"imports": {}
}
Fragment Patterns
Example 1-69 Tab Bar containing three tabs, and all tabs except the first one are
hidden
In this example, when the page loads, only the 'list' tab item fragment is loaded and rendered.
The 'map' and 'schedule' tab items are hidden, and the fragment and associated artifacts are
not loaded, and the components inside those fragments are not rendered.
1. The <oj-vb-fragment> component is used to isolate the content of each tab item
1-211
Chapter 1
Fragments
2. In the switcher associated with the tab bar, the <oj-defer> slot is used to hide tabs. The
fragments are loaded and rendered when their tabs become visible.
3. For details on configuring the component, see Deferred Rendering in the JET Developer
Cookbook.
<oj-defer slot="map">
<oj-vb-fragment id="incML" name="incidentsMapLayout"></oj-vb-fragment>
</oj-defer>
<oj-defer slot="schedule">
<oj-vb-fragment id="incSL" name="incidentsScheduleLayout"></oj-vb-
fragment>
</oj-defer>
</oj-switcher>
Example 1-70 Content inside a dialog is hidden initially, and loaded when the user
opens the dialog
1. The <oj-vb-fragment> component is used to isolate the content of the dialog.
2. In the dialog 'body' slot, <oj-defer> is used to wrap the oj-vb-fragment. When the dialog is
opened, the input parameters are passed to the fragment component, and the fragment is
loaded and rendered.
3. If the fragment fires an event, binding the event to a listener on the page enables the page
to listen to it. The "saveproduct" event has the "propagationBehavior": "container"
property, so the fragment component on the page can listen to it, and then call the
'onSaveProduct' listener on the page.
4. For details on configuring the component see Deferred Rendering in the JET Developer
Cookbook.
Note:
It's best to have all the content of the dialog within the fragment and the 'body' slot,
rather than splitting it, for example, having the buttons in the footer and having the
content within the <oj-defer>.
1-212
Chapter 1
Fragments
<oj-defer>
<oj-vb-fragment id="createProd1" name="create-product"
on-saveproduct="[[ $page.listeners.onSaveProduct ]]"
on-
cancelproduct="[[ $page.listeners.onCancelProduct ]]">
<oj-vb-fragment-param name="products"
"saveproduct": {
"description": "fired when a product has been created. The mutated product
is returned in
payload",
"behavior": "notify",
"payloadType": {
"data": [
{
"id": "string",
"name": "string",
"unitPrice": "number",
"inventory": "number",
"productCategory": "string"
}
]
},
"propagationBehavior": "container"
}
In the example below, the 'edit-product' fragment is used by two components, and each
fragment has a unique id. The parameters and event configurations are also different.
<oj-vb-fragment-param name="products"
1-213
Chapter 1
Components
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.
VB Switcher Component
The VB switcher web component that is used to display the content of one of many VB flows in
a VB page, and to quickly switch which one is displayed.
An API is provided to select which flow to render and to add or remove flows from an array of
available flows.
The following features are supported:
• the view and viewModel is persisted when switching flows
• navigation within a switcher element is allowed
• record the transition in the browser history
DOM and viewModel caching
In order to provide a quick switching between flows, support pages with iframe and to preserve
the selection and scrolling position, the content of the flow is preserved when switching to an
other flow. This is done by by showing and hiding the DOM nodes. The resources taken by a
switcher element are only released when the element is removed from the ArrayDataProvider.
Note:
Memory usage
Be aware that having a large amount of flows open in the switcher can result in a
large memory usage in the browser.
1-214
Chapter 1
Components
VB Switcher Navigation
Page navigation inside a switcher element or when switching elements does not update the
URL but the change is recorded in the browser history. As a result, the bookmarked page will
not restore the current state of the switcher.
Navigation within a switcher element
It is possible to navigate to a different page inside a switcher element. When navigation occur,
the URL is not updated but the navigation is recorded in the browser history. Using the
browser's back button restores the previous page of the current switcher element. Navigation
should be done using the navigateAction.
A switcher element can navigate to a different flow in the current App UI, open a different App
UI, or navigate to an App UI.
Switching between elements
When switching between elements, the transition is recorded in the browser history. Using the
back button restores the previously displayed element. This behavior can be altered using
vbBeforePopState.
When switching between elements, the page lifecycle events are not dispatched because the
page does not enter or exit. vbBeforeExit and vbExit are dispatched only after an element of
the switcher is deleted.
1-215
Chapter 1
Components
VB Switcher Methods
closeItem
Dispatch the vbBeforeExit event to all the containers of a switcher element the same way it is
displayed when navigating. Returns a promise with the result of the event. This allows a page
to veto the closing of a switcher element, for example, when dirty data is detected.
navigate
Navigate the content of the current switcher element from a page containing the switcher
component.
The parameters and the return value are the same as the navigate action.
1-216
Chapter 1
Components
VB Switcher Events
vbBeforePopState
This event is dispatched when the browser history changes (Back and Forward buttons), and it
is used for two purposes:
1. To be notified when a change to the switcher current item will be made due to a browser
Back or Forward button.
2. Cancel the default handling by setting preventDefault to "true".
Properties
All of the event payloads listed below can be found under event.detail.
VB Switcher Examples
Example 1-72 Switcher elements ADP declaration using JET
ojmutablearraydataprovider
"switcherArray": {
"type": "object[]",
"defaultValue": [
{
"flow": "aaa",
"name": "Flow aaa",
"id": "a"
}
]
},
"switcherMutableArrayDP": {
"type": "ojs/ojmutablearraydataprovider",
"constructorParams": [
"{{ $variables.switcherArray }}",
{
1-217
Chapter 1
Components
"keyAttributes": "id"
}
]
},
"switcherArray": {
"type": "object[]",
"defaultValue": [
{
"flow": "aaa",
"name": "Flow aaa",
"id": "a"
}
]
},
"switcherADP": {
"type": "vb/ArrayDataProvider2",
"defaultValue" : {
"keyAttributes": "id",
"data": "{{ $variables.switcherArray }}",
"itemType": "object"
}
}
{
"title": "Start Page",
"description": "Landing page of the flow",
...
"navigation": {
"embeddable": "enabled"
}
}
<oj-vb-switcher
data="[[ $variables.switcherADP ]]"
current-item="{{ $variables.selectedItem }}"
bridge="[[ vbBridge ]]"
on-vb-before-pop-state="[[ $listeners.beforePopstate ]]">
</oj-vb-switcher>
Example 1-76 Entry in imports section of the page definition to load the component
"imports": {
...
"components": {
"oj-vb-switcher": {
"path": "vb/components/oj-vb-switcher/loader"
1-218
Chapter 1
Imports
}
}
}
Imports
The sections below discuss how to import components, CSS, and modules.
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 1-77 Example:
"imports": {
"components": {
"demo-card": {
"path": "resources/components/democard/loader"
}
}
}
{
"imports": {
"modules" : {
"converterUtils": {
"path": "ojs/ojconverterutils-i18n"
},
"arrayUtils": {
"path": "faCommon/arrayUtils"
}
}
}
}
1-219
Chapter 1
Imports
• "converterUtils" specifies a path to a JET module using the implicit requireJS mapping
('ojs') that is set up for JET modules in VB.
• "arrayUtils", on the other hand, uses a requireJS path 'faCommon' that is a requireJS
path mapping defined in the application metadata. See Declarative RequireJS Path
Mapping.
Each module defined in the section is available through an un-scoped "$imports" built-in
variable.
The built-in "$imports" context property is un-scoped and limited to the current container to
avoid performance issues and module conflicts at different context (for
example, $page, $flow, $application).
<div>
<oj-bind-text
value="[['Last Updated on - '
+ $imports.converterUtils.IntlConverterUtils.dateToLocalIso(new Date())]]">
</oj-bind-text>
</div>
In a page.json action chain, the assignVariablesAction uses the external module imported as
"arrayUtils", to call a filter method, as shown here:
{
"removeTab": {
"module": "vb/action/builtin/assignVariablesAction",
"parameters": {
"$page.variables.switcherArray": {
"module": "{{ $imports.arrayUtils }}",
"functionName": "filter",
"params": [
{
"array": "{{ $page.variables.switcherArray }}",
"callback": "{{ function (p) { return p.id !
== $variables.itemToRemove } }}"
}
]
}
}
}
}
class ArrayUtils {
/**
* Returns a new array with all elements that pass the test implemented by
the provided function.
* @param helper assignment helper
* @param defaultValue the default value of the variable
* @param {Object} params properties are
* @param {Array} params.array - the array being filtered
* @params {Function} params.callback - function used as callback to filter
1-220
Chapter 1
Imports
values.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Global_Objects/Array/filter
* @return {Array} filtered array or the array1 if args invalid
*/
static filter(helper, defaultValue, params) {
const array1 = params.array1;
const callback = params.callback;
if (Array.isArray(array1) && callback && typeof callback === 'function') {
return array1.filter(callback);
}
Note:
The term 'global function' refers to its usage as a globally available module, and must
not be confused with a context, such as $global.
Note:
Global functions can only be defined for an extension. They cannot be defined under
an App UI or other resources folders, including in a unified app.
1-221
Chapter 1
Imports
For example, ext-layout, is an extension that defines two JavaScript files that are shared by all
containers, like page, fragment or dynamic layout artifacts, in the current extension. The files -
dateUtils.js and standardUtils.js are located under ext-layout/ui/self/resources/
functions.
The JavaScript files are meant to be utilities style classes so that when the module is loaded
(using requireJS) it returns a map of function names to function callbacks.
Example 1-78 Global function JS module
The resources/functions folder contains a functions.json, a configuration file that defines
the list of JavaScript modules (in the 'files' section).
In this example, the standardUtils.js, defined in the location above, defines several static
methods, and returns the methods that are allowed for general use:
'use strict';
define([], () => {
class StandardFunctions {
static join(arr = []) {
if (arr.length === 0) {
return '';
}
const newArr = (arr.slice(1, arr.length).map((o) => (${o})));
return [arr[0]].concat(newArr).join(' ');
}
/**
* Returns true if the field provided as parameter #1 contains the
characters provided as parameter #2, else
* returns false.
* @param {string} field
* @param {string} characters
* @returns {boolean}
*/
static contains(field = '', characters) {
return field.indexOf(characters) >= 0;
}
/**
* Returns a string converted from a decimal.
* @param {number} field
* @returns {string}
*/
static convertNumberToString(field) {
return field.toString();
}
/**
* Convert string to number
* @param {string} field
* @return {number}
*/
static convertStringToNumber(field) {
return parseInt(field, 10);
}
1-222
Chapter 1
Imports
/**
* Returns the number of characters in a string.
* @param {string} field
* @return {number}
*/
static lengthOfString(field = '') {
return field.length;
}
}
return {
contains: StandardFunctions.contains,
convertNumberToString: StandardFunctions.convertNumberToString,
convertStringToNumber: StandardFunctions.convertStringToNumber,
join: StandardFunctions.join,
lengthOfString: StandardFunctions.lengthOfString,
};
});
{
"files": {
"utils": {
"path": "standardUtils",
"label": "Standard Utility Functions",
"referenceable": "extension",
"functions": {
"contains": {
"params": {
"field": {
"label": "field",
"description": "",
"type": "string"
},
"characters": {
"label": "characters",
"description": "",
"type": "string"
}
},
"return": "boolean"
},
"convertNumberToString": {
"referenceable": "self"
},
"convertStringToNumber": {
"referenceable": "self"
1-223
Chapter 1
Imports
},
"join": {},
"lengthOfString": {}
}
},
"dateLocalUtils": {
"path": "date/dateUtils",
"label": "Date Utility Functions",
"referenceable": "self",
"functions": {
"dateToIsoString": {
"referenceable": "extension"
}
}
}
}
}
Note:
The metadata in the sample JSON above is edited to show the relevant details. It is
not a complete configuration.
The "files" section includes one or more JavaScript files. "utils" is an alias to the
JavaScript file "standardUtils.js", defined under the "path" property (the .js extension can
be dropped because it is a requireJS module).
The "referenceable": "extensible" declares that the file is accessible to downstream
dependent extensions. The file alias "dateLocalUtils", with the path "date/dateUtils", is
set to "referenceable": "self", which means it is only accessible to artifacts in the current
extension.
The "functions" section can be used to list functions that are available to callers. The function
name can be used in expressions, if present (see below). Additionally, function metadata can
define whether it can be referenced from the current extension or a dependent downstream
extension. While a function can be less permissive about its access, it cannot supersede the
access set on the file.
For example, the file "utils" allows access to all dependent extensions (it is set to
"referenceable": "extension"), whereas the method "convertNumberToString" within
"utils" only allows access to the current extension (it is set to "referenceable": "self") .
This is allowed because a function can be less permissive. This means a dependent extension
that imports this module, will not be able to call the "convertNumberToString" function (it will
result in a log error).
Another example, is where the file "dateLocalUtils" that defines a function
"dateToIsoString", which expands its access beyond what the file allows. This is not allowed
and ignored. The function can only be called by artifacts in the current extension.
When a function does not define "referenceable", access is set on the file. The default
access for a file is "self".
1-224
Chapter 1
Imports
{
"imports": {
"modules": {
"utils": {
"path": "self:$functions/utils"
},
"dateUtils": {
"path": "self:$functions/dateLocalUtils"
},
"commonUtils": {
"path": "ext-common:$functions/utils"
}
}
}
}
The path property uses a scheme for locating the JavaScript resource, particularly functions,
using a convention with three elements:
{extId}:{resourceType}/{resourceAlias}
The path resolves to the actual require path to the JavaScript module loader.
Element Description
{extId} Refers to the extension the resource belongs to. 'self' means the current
extension. Any other extension will be identified by its id.
• 'self: - a reserved word, is required to refer to extension level resources.
– Example: "self:$functions/dateLocalUtils" refers to the current
extension functions. The namespace 'self:' is required to refer to
extension resources.
• 'ext-common' - refers to the name of an upstream extension that the current
extension depends on. 'utils' is the resource alias defined there
{resourceType} Uses a special keyword for the /functions resources (for example,
"$functions").
• self:$functions resolves to an actual resource path ('ext-layout/ui/
self/resources/functions')
• ext-common:$functions resolves to an actual resource path ('ext-
common/ui/self/resources/functions')
1-225
Chapter 1
Imports
Element Description
{resourceAlias} For global functions, resourceAlias is the alias of the JavaScript defined in
functions.json.
• self:$functions/dateLocalUtils' resolves to ext-layout/ui/self/
resources/functions/date/dateUtils.js', where 'dateLocalUtils' is
the alias defined in functions.json.
• ext-common:$functions/commonUtils resolves to ext-common/ui/
self/resources/functions/utils/common.js, where 'commonUtils' is
the alias for the resource located under 'utils/common.js', that is defined in
functions.json.
{
"commonUtils": {
"path": "utils/common",
Note:
For "ext-layout" to import global functions from "ext-common", it must have a
dependency on the "ext-common" (see below).
Note:
$modules is the shortened form for use in the current container.
1-226
Chapter 1
Imports
Note:
Any container that imports modules can expect $modules to be available (for
example, $page.modules, $fragment. modules).
await Actions.assignVariable(context,
{
variable: '$layout.variables.someValue',
value: result / 2,
});
}
}
extA/
ui/
self/
resources/
functions/
extAUtils.js
functions.json // { extAUtils: { referenceable: "extension" } }
applications/
appUIA/
.../
some-page.json
1-227
Chapter 1
Imports
manifest.json // { dependencies: [] }
In the example above, some-page.json can have an imports section like this:
{
"imports": {
"modules": {
"AUtils": {
"path": "self:$functions/extAUtils"
}
}
}
}
The imported module in the example above can be accessed using the expression
({{ $page.modules.AUtils.someMethod() }}).
Example 1-80 extA1 depends on extA and defines functions (both private and public)
• extA1 has a dependency on extA
• extA1 defines a private a1PrivateUtils.js that can be imported only by extA1 containers.
– For example, a1-form-fragment.json can import the private module.
• It also defines a public extA1Utils.js that can be imported by containers in the current
and downstream extensions.
extA1/
ui/
self/
resources/
functions/
private/
a1PrivateUtils.js
public/
extA1Utils.js
functions.json
fragments/
a1-form/
a1-form-fragment.json
a1-form-fragment.js
a1-form-fragment.html
In the example above, a1-form-fragment.json can import the private module and have an
imports section. If a module is not declared here, it's not automatically available. For example,
'extA1Utils' is not defined here, so it cannot be used.
{
"imports": {
1-228
Chapter 1
Imports
"modules": {
"A1PrivateUtils": {
"path": "self:$functions/a1PrivateUtils"
},
"extAUtils": {
"path": "extA:$functions/extAUtils"
}
}
}
}
Some fragment markup can call a method exposed on the private utils:
<oj-bind-text
text="{{ $modules.A1PrivateUtils.titleCase($variables.name)) }}"/>
Because extA1 depends on extA, it can also refer to public functions from extA.
Example 1-81 extB depends on extA and extA1 and defines a functions JS extBUtils.js
• extB defines a layout 'buttons'
• extB defines its own global functions (extBUtils)
• extB also extends the fragment 'a1-form-fragment' from extA1
extB/
dynamicLayouts/
self/
buttons/
layout.json
ui/
self/
resources/
functions/
extBUtils.js
functions.json
js/
ext-A1/
fragments/
a1-form/
a1-form-fragment-x.json
a1-form-fragment-x.js
1-229
Chapter 1
Imports
buttons/layout.json references the fragment from extA1. It can call the public extAUtils and
extA1Utils, in addition to the global functions extBUtils. This could be defined in layout.json:
{
"imports": {
"modules": {
"extA1Utils": {
"path": "extA1:$functions/extA1Utils"
},
"BUtils": {
"path": "self:$functions/extBUtils"
}
}
}
}
The layout.html could define a template that uses a fragment from extA1. The fragment,
because it's defined by extA1, can internally call a 'functions' module that is private to extA1.
<template id="formTemplateSimple">
<oj-vb-fragment name="extA1:a1-form"
bridge="[[vbBridge ]]">
<oj-vb-fragment-param name="dynamicLayoutContext"
value="[[ $dynamicLayoutContext ]]"></oj-vb-
fragment-param>
<oj-vb-fragment-param name="foo"
value="[[ $modules.BUtils.titleCase($layout.variables.lucy) ]]">
</oj-vb-fragment-param>
</oj-vb-fragment>
</template>
extB also extends the fragment from extA1. This means the fragment-x will need to explicitly
define all imports it needs from the current extension and any of its upstream extensions. The
use of $base is not recommended for accessing imports of the base container, because the
exact list of imports that are accessible by the extended artifact cannot be easily determined,
nor can it be complete. For example, the a1-form-fragment.json from extA1 did not import
extA1Utils. Likewise, it imports local resources that should be hidden for the current extension.
It is recommended that authors explicitly import the modules they need. A sample imports on
a1-form-fragment-x.json might look like this:
{
"imports": {
"modules": {
"extA1Utils": {
"path": "extA1:$functions/extA1Utils"
}
}
}
}
1-230
Chapter 1
Imports
Example 1-82 extZ extends layout from extB and also depends on extA1
• extZ extends the 'buttons' layout from extB and also defines its own 'zippers' layout.
• extZ also overrides some pages from the unified app.
• extZ also defines its own functions, in addition to having a 'functions' folder at the App UI
level (resources/functions) folder under appUiZ (see the example below).
– While there appears to be a functions.json defined at the App UI resources level,
these cannot be imported into the App UI using the $functions scheme. These files
can be imported into the App UI pages using the current schemes for importing such
files.
extZ/
dynamicLayouts/
extB/
buttons/
layout-x.json
self/
zippers/
layout.json
ui/
self/
applications/
appUiZ/
app.json
pages/
shell-page.html
shell-page.json
resources/
functions/
appUiZtils.js
functions.json
resources/
functions/
extZUtils.js
functions.json
base/
pages/
root/
first-page-x.json
app-flow-x.json
The following sections detail the level of access for functions and the $modules usage in the
various Visual Builder containers.
Functions Access and $modules Usage in extZ/dynamicLayouts/extB/buttons/layout-x
When layout-x is an extension of a layout from extB, it should be possible to import extB's
functions, as well as the dependency extension (extA1).
1-231
Chapter 1
Imports
extA is not specified in the dependencies list, so its functions cannot be imported.
Note:
An extension must explicitly import the resources it needs, and not use $base to
access the imports set up by the artifact it extends.
extZ defines its own functions, so layout-x can import any of these.
{
"imports": {
"modules": {
"extBUtils": {
"path": "extB:$functions/extBUtils"
},
"extA1Utils": {
"path": "extA1:$functions/extA1Utils"
},
"ZUtils": {
"path": "self:$functions/extZUtils"
}
}
}
}
1-232
Chapter 1
Imports
In the following application structure, ext-A depends on ext-B, and extends a layout "incidents"
defined in ext-B. extA defines its own CSS files, under ui/self/resources. Additionally, it also
defines an App UI (appUi-A) with its own CSS resource.
ext-A/
dynamicLayouts/
self/
orders/
layout.json
ext-B/
incidents/
layout-x.json
ui/
self/
applications/
appUi-A/
app.json
pages/
shell-page.json
resources/
css/
shell-2.css
resources/
css/
app.css
shell.css
resources/
css/
ext.css
base/
pages/
root/
first-page-x.json
app-flow-x.json
{
"imports": {
"css" : [
"self:/resources/css/ext.css", // starts from the extension
"/resources/css/shell.css", // starts from the App UI
"resources/css/shell-2.css", // not supported, will throw an error
"https://static.oracle.com/cdn/fnd/gallery/2007.0.0/some.css" // same
]
}
}
1-233
Chapter 1
Security
• If the path starts with self:/, the path starts at the root of the current extension (for
example, ext-A/ui/self/resources).
• If the path is absolute, the path starts at the root of the current App UI (appUi-A),
equivalent to the path starting with extA/ui/self/applications/appUi-A/resources.
• If the path is relative, throw an error because a relative path is not supported.
• If the path is a URL, use that URL.
Example 1-84 Import CSS in app.json
{
"imports": {
"css" : [
"self:/resources/css/ext.css",
"/resources/css/app.css"
]
}
}
The app.json has access to both the extension level resources (self:/) and the App UI ones
(starting with /resources).
{
"imports": {
"css" : [
"self:/resources/css/ext.css"
]
}
}
Extension artifacts can only access resources defined at the current extension level.
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.json, name-flow.json, name-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.
1-234
Chapter 1
Security
"security": {
"access": {
"requiresAuthentication": true/false,
"roles": ["role1", "role2"],
"permissions": ["perm1", "perm2"]
}
}
When an anonymous user navigates to an artifact (page, flow or application) and the artifact is
secure, the user is prompted to login, and is then redirected to the artifact. This functionality is
provided by the default implementation of the Security Provider.
Security Provider
Security for an application is enabled using a pluggable mechanism called Security
Providers.
In the application model, app-flow.json, you can specify a "userConfig" element. The
userConfig element selects which Security Provider to use and how to configure it:
Example of an entry in app-flow.json to specify the Security Provider
"userConfig": {
"type": "vb/DefaultSecurityProvider",
"configuration": {
"url": "url to some security api"
}
}
A Security Provider takes a configuration object with a url. The url property should point to a
REST API. It must be possible to retrieve the current Security Provider configuration via this
REST API. The configuration contains user information and configuration information such
as loginUrl and logoutUrl.
A Security Provider performs the following functions.
Function Description
fetchCurrentUser(config) Fetch the configuration from the url and initialize
the userInfo property as well as the
loginUrl and logoutUrl properties.
static getUserInfoType() Return an object describing the type of the user info.
isAccessAllowed(type, path, accessInfo Check if the current user can access a resource with the
given access info. If the user is not authenticated, this
method returns false. Otherwise, if the user role is one of the
roles in accessInfo, or if the user permission is one of the
permissions in accessInfo, then the method returns true.
handleLoadError(error, returnPath) This function is called by the client when an error occurs
while loading a page. It attempts to handle the load error for
a Visual Builder artifact, and returns true if it does.
1-235
Chapter 1
Security
Function Description
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 1-86 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-236
Chapter 1
Helper Utilities
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 1-87 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;
});
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.
1-237
Chapter 1
Helper Utilities
Here's a snippet showing how to use the REST helper with an extension, with the second
parameter defining the scope:
If the header has hyphens, create the initConfig object like this:
1-238
Chapter 1
Helper Utilities
responseBodyFormat(for format: one of: text, json, REST helper Overrides the default
mat) blob, arrayBuffer, behavior, which looks at
base64, or base64Url. the "content-type"
The response body type header to determine how
is the same as the to read (and parse) the
corresponding method response.
for Response (except
base64, which returns
just the encoded portion
of the base64 URL).
fetch() - Promise Performs the configured
fetch() call
toUrl() - Promise Utility methods for
toRelativeUrl() 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.
1-239
Chapter 1
Helper Utilities
The REST helper fetch() call returns a Promise that resolves with an object that contains the
following properties:
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.
Method Description
fireCustomEvent(name, payload) See Fire Custom Event Action.
fireNotificationEvent(options) See Fire Notification Event Action.
'use strict';
define(function () {
function MainPageModule(context) {
this.eventHelper = context.getEventHelper();
}
return MainPageModule;
});
1-240
Chapter 1
Events
Security Helper
The SecurityHelper utility provides methods to retrieve security-related data.
getServiceAccessToken() Method
The SecurityHelper.getServiceAccessToken() method returns the JWT token for a
configured service connection that uses a JWT token based authentication method and that
doesn’t use the VB Proxy. The method returns an error if the connection type is proxy based
(for example, if the connection type is “Always use proxy, irrespective of CORS support"
or ”Dynamic, the service does not support CORS”). This method is supported for these
authentication types:
• Oracle Cloud Account
• OAuth 2.0 User Assertion
• OAuth 2.0 Client Credentials
• OAuth 2.0 Resource Owner Password Credentials
This table provides further details about this method:
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.
1-241
Chapter 1
Events
"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 }}"
}
1-242
Chapter 1
Events
}
],
}
Event Prefix
An event prefix is a way for event listeners to define which custom event they are listening to.
Two aspects of the event listener are represented in the reference: the extension where the
event is defined, and the scope. The syntax for the scope is the same in the base and in the
extension. The extension reference is placed before the scope, and is separated with a slash
(/).
Inside base (Local)
The syntax is myScope:myEventName where myScope can be omitted if it refers to an event
defined in this object.
Reference Description
page:eventName Refer to an event defined in current page
flow:eventName Refer to an event defined in the flow containing this page
application:eventName Refer to an event defined in the App UI
global:eventName Refer to an event defined in the Unified App
Declared Events
Declared events are events that are explicitly defined in the application model, to define a
specific contract and type for the event.
1-243
Chapter 1
Events
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 1-89 Declaration
"events": {
"myPageEvent": {
"payloadType": {
"message": "string",
"code": "number"
}
}
},
"eventListeners": {
"page:myPageEvent": {
"chains": [
{
"chainId": "handleEvent",
"parameters": {
"payload": "{{ $event }}"
}
}
]
},
1-244
Chapter 1
Events
1-245
Chapter 1
Events
{
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
},
{
error: {
detail: 'string',
},
}
1-246
Chapter 1
Events
Component Events
Component events (also known as DOM events) are similar to page events, except that they
are fired by components on a page (or other container).
A component event listener can have any name, and is generally associated to a component
event property via the binding expression on the component markup. Component event
listeners are defined in the Page (or container) module under the eventListeners property,
much like other Visual Builder events. For example, an event listener for the selectionChange
event for the <oj-tab-bar> component can be defined within the eventListeners section as:
"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:
1-247
Chapter 1
Events
These variables do not exist outside the listener context. In other words, you can reference
these in the listener declaration, but you cannot reference them in the called action chain; any
values needed in these variables must be passed explicitly to the action chain as arguments
(chain variables).
These three variables represent the arguments passed to the listener, and are not directly tied
to specific JET values. Their meaning could be different depending on the context.
For example, if using an event listener within an <oj-list-item> item, the value of $current
could be different whether you are using the item.renderer attribute or the itemTemplate slot
to display the item.
• Within an item.renderer script, JET does not define $current, so instead passes $data as
the second argument, so the Visual Builder $current is JET/Knockout $data. In some JET
contexts, like anitem.renderer script, you will also need to prefix Visual Builder listeners
with (Knockout) $parent in the HTML.
• Within an itemTemplate slot, JET defines $current, and passes that, so Visual
Builder $current is JET $current.
To determine whether JET $current exists for your use case., refer to the JET documentation
for the component to which you are adding a listener.
Additionally, the developer could decide to pass their own custom object for the parameters. In
the example below, the listener is wrapped, so Visual Builder $current is "some string", and
Visual Builder $bindingContext is undefined.
"eventListeners": {
"customEventTwo": {
"chains": [
{
"actionsId": "handleEventInMod2PageChain",
"parameters": {
"eventPayload": "{{ $event }}"
},
}
],
"preventDefault": "{{ $event.type === 'info' }}"
}
1-248
Chapter 1
Events
asynchronously, if needed. The Promise provided by Visual Builder event listeners can also be
resolved or rejected within Visual Builder based on the action chain's behavior.
To opt in to the async behavior for a component event, the eventListeners property
asyncBehavior must be set to "enabled". The default value for this property is "disabled".
Before implementing action chain logic, refer to the component docs to make sure you
understand the implications of enabling async behavior.
Here's an example of enabling async behavior for a table component's ojBeforeRowEditEnd
event, with the asyncBehavior property set to "enabled" within the eventListeners property:
{
"eventListeners": {
"tableBeforeRowEdit": {
"asyncBehavior": "enabled",
"chains": [
{
"chainId": "beforeRowEditChain",
"parameters": {
"rowIndex": "{{$event.detail.rowContext.status.rowIndex}}"
}
}
]
}
}
}
The table component bound to the ojBeforeRowEditEnd event in the preceding example can
be configured as:
<oj-table scroll-policy="loadMoreOnScroll"
id="oj-table-1"
class="oj-flex-item oj-sm-12 oj-md-12"
edit-mode="rowEdit"
selection-mode='{"row": "single"}'
data="{{ $page.variables.productsADP }}"
scroll-policy-options.fetch-size="3"
columns="{{ $page.functions.columnsArray }}"
on-oj-before-row-edit="[[$listeners.table1BeforeRowEdit]]">
...
</oj-table>
Fragment Events
See Fragment Events.
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.
1-249
Chapter 1
Events
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 1-91 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.
1-250
Chapter 1
Events
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 1-92 stopPropagation Example
"eventListeners": {
"customEventTwo": {
"stopPropagation": "{{ $event.type === 'info' }}"
"chains": [
{
"actionsId": "handleEventInMod2PageChain",
"parameters": {
"eventPayload": "{{ $event }}"
}
}
],
}...
Event Behavior
Event behavior refers to how the listeners are called in relation to each other, whether the
result for the listeners is available, and what form the result would take.
Event behavior is meaningful for base applications, as well as extensions, and is not specific to
events defined in the "interface".
1-251
Chapter 1
Events
1-252
Chapter 1
Events
"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 JSON
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.
1-253
2
Related Topics
{
"applicationModelVersion": "19.3.1",
"id": "myApp",
"description": "Big Box FixitFast Technician App",
"defaultPage": "shell",
"requirejs": {
"paths": {
"myPathPrefix": "some/other/path/prefix",
"expPrefix": "{{ $initParams.myPrefix + '/somepath' }}"
}
},
Service Resolution
This section provides an overview of how the Visual Builder runtime (RT) resolves a service
name into the actual service definition that describes how to make a REST request. The
behavior is applicable to applications and extensions/app UIs.
Note:
In this section, the term "module" refers to both the base app (base) and to
extensions.
2-1
Chapter 2
Service Resolution
2-2
Chapter 2
Service Resolution
• A static service is contributed by providing the actual metadata for the service as an artifact
of the application.
– The default path for a static service in the base is <app>/services/<service name>/
openapi3.json.
– The default path for a static service in an extension is <extension>/services/self/
<service name>/openapi3.json.
– Example of static service:
2-3
Chapter 2
Service Resolution
A module may be able to use services and backends from another module
• A service and a backend can be only used by a different module if that "object" is marked
as accessible and if the module using the "object" depends on the module defining the
"object".
– An accessible service or backend defined in base can be used by any module.
* Conversely, base cannot use services and backends defined by other modules.
– An accessible service defined in extension extB can only be used by extB itself and by
extensions that depend on extB (directly or indirectly).
* The same applies to a backend
• Services that are not marked as accessible are private and can only be used by the
module that defines them.
2-4
Chapter 2
Service Resolution
2-5
Chapter 2
Service Transforms
Endpoint Id
• An endpoint id is a string that identifies a service and an operation within that service.
– For example, petstore/getAllPets refers to the operation from the service petstore,
and which is identified by the id getAllPets.
• With the introduction of extensions, an endpoint id is expected to be "namespaced" to
precisely indicate the module that contains the service.
– Examples: base:petstore/getAllPets, extA:storage/createItem
* The DT warns the user if the endpoint id is not fully qualified (if it does not have the
extension id).
* Adding a namespace to an endpoint id does not circumvent the visibility rules for
services and backends (see Using services and backends from another module
above).
* In other words, using the endpoint id extB:myservice/getAll on a module
that depends on the extension extB fails to resolve if extB is not exposing a
service named myservice.
Note:
The RT can handle endpoint ids without namespaces, however, this is not
recommended because it may lead to "app level" programming errors that
are hard to debug.
Service Transforms
In order to fetch data required by the application (from a backend service), callers can use the
VB RestHelper utility directly or, use a Call Rest action or ServiceDataProvider.
2-6
Chapter 2
Service Transforms
Regardless of the mechanism used, for the request to happen the identifier of the endpoint
along with the values of the endpoint "parameters" may need to be processed and transformed
into a form that is expected by the target service / endpoint scheme. Likewise, the response
received may need to be processed into to a form that is expected by the caller. In order to
facilitate this every service configuration must register a transforms module that implements
metadata, request and response transform functions.
Collectively, all three APIs are referred to as the Transforms API. The specific implementation
of the Transforms API for a particular service is referred to by its name (for example, Business
Objects Transforms). Refer to the JSDocs for serviceTransforms.js for details.
Standard Transforms File
A sample custom transforms implementation must return an object with three properties
(metadata, request and response), each containing methods with specific signatures. Refer to
the documentation for each type to understand how to implement them.
'use strict';
define([], () => {
/**
* Request class implements all the request tranforms functions needed to
transform the input parameters to the
* Rest call. Each transforms function takes the parameters passed to it
2-7
Chapter 2
Service Transforms
/**
* Response class implements all the response tranforms functions needed to
transform the response from the Rest
* call. Each transforms function takes the parameters passed to it and
transforms the result to a form that the
* caller expects. Often this involves updating configuration.body, which
*/
class Response {
static paginateResponse(configuration, transformsContext) {}
someOtherMethod(config) {}
somePrivateMethod() {}
}
class Metadata {
static capabilities(configuration) {}
}
2-8
Chapter 2
Service Transforms
// Note: If the above classes implement other instance methods its best to
return
// just the methods a transforms user will need. For example, the response
property
// below returns just the exported methods
return {
metadata: Metadata,
request: Request,
response: { paginate: Response.paginateResponse, body:
Response.bodyResponse }
};
});
Usage
Generally, all implementations for the various request, response and metadata transforms
functions pertaining to a particular service, are included in a transforms file that is then
associated to the service configuration.
In the example below, vb/BusinessObjectsTransforms is the pre-defined transforms
implementation used with Business Objects based services.
In a catalog.json for a Business Objects based service, the transforms file is set in the
backends (or services) section (see the example below). Refer to the Service Connection docs
for exact details on how this is configured, and where it is defined.
This file contains the default implementations for the most common transforms functions, that
are applied for the majority of service endpoints. However, specific endpoints can override the
defaults, and/or add custom transforms implementations.
{
"backends": {
"crmBO": {
"headers": {},
"servers": [
{
"variables": {
"faVersionVar": {
"default": "11.13.18.05"
}
},
"url": "vb-catalog://backends/fa/crmRestApi/resources/
{faVersionVar}"
}
],
"transforms": {
"path": "vb/BusinessObjectsTransforms"
}
}
},
"services": {
"journeys": {
"info": {
"x-vb": {
"transforms": {
"path": "./finderOperationTransform.js"
2-9
Chapter 2
Service Transforms
}
}
},
"servers": [
{
"x-vb": {
"headers": {
"Accept": "application/vnd.oracle.openapi3+json"
}
},
"url": "vb-catalog://backends/hcmBO/journeys/describe"
}
]
}
}
}
Additionally, the default service transforms can also be overridden at the Service Data
Provider, Call Rest action and the Rest Helper levels, using specific properties on each. Refer
to the docs for the same for details.
Note:
In VB applications, services are generally BusinessObjects. VB Runtime provides a
default implementation for a Business Objects based service.
If your application uses a third party service, you may need to implement a custom
transforms module that includes the appropriate metadata, request, response
transforms functions specific to the capabilities afforded by the service.
Metadata Transforms
Service authors can implement the Metadata Transforms API in their transforms code, which
returns a Map of capabilities (with keys such as filter, sort, fetchByKeys) supported by a
particular service endpoint.
The default transforms implementations for Business Objects services processes the endpoint
metadata to determine the level of support and returns a Map of capabilities for that endpoint,
that can then be understood by Data Provider implementations. The capabilities can be
retrieved using the Metadata.capabilities().
The following is a sample structure of the capabilities for a Business Objects based service.
The list of capabilities generally applies to all endpoints that the service includes, but authors
can provide custom capabilities for different endpoints.
{
"fetchByKeys": {
"implementation": "lookup",
"multiKeyLookup": "yes"
},
"fetchFirst": {
"implementation": "iteration"
},
2-10
Chapter 2
Service Transforms
"fetchByOffset": {
"implementation": "randomAccess"
},
"sort": {
"attributes": "single"
},
"filter": {
"operators": [
"$eq",
"$ne",
"$co",
"..."
],
"textFilter": true
}
}
The Service Data Provider determines its own capabilities based on the information above.
Signature
The metadata transforms capabilities function has the following signature:
function capabilities(configuration) {
const caps = {};
// process the endpoint configuration and build the capabilities for this
endpoint
return caps;
}
2-11
Chapter 2
Translations
it's determined it remains the same. It's uncommon for page authors to want to override the
default capabilities, but it can be set on the Service Data Provider variable, if needed.
define([], function () {
/**
* transforms pertaining to a service or endpoint and not tied to a request.
*
* @type {{capabilities: (function(*): {})}}
*/
class MetadataTransforms {
/**
* Returns the capabilities as defined by a DataProvider
* @param configuration
* @return {Object}
* @private
*/
// eslint-disable-next-line no-underscore-dangle
static getCapabilities(configuration) {
const caps = {};
const c = configuration;
const epDef = c.endpointDefinition;
const paramsDef = epDef && epDef.parameters;
if (paramsDef) {
const queryParamsDef = paramsDef.query || {};
if (queryParamsDef) {
// using the endpoint definition build the capabilities
}
}
return caps;
}
}
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.
2-12
Chapter 2
Translations
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. See the note about Using "merge" below.
• 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.
Caution:
Using "merge"
When using "merge", take care to use requireJS mapped references consistently. A
common failure is when the "merge" property does not use a requireJS mapping, but
the defining path to the bundle does use a mapping. For example,when a CCA is
loaded using a requireJS path ("mapped/foo/loader") and it references the bundle
using a relative path ("./resources/nls/strings"), the app flow MUST also use the
mapping: ("merge": "mapped/foo/resources/nls/strings").
When a dot (".") is used as a prefix in the bundle paths, be aware that "merge" will
not work. Internally, Visual Builder 'normalizes' bundle paths, so the actual paths
used to define the bundle do not have a "dot" prefix.
For example, the declaration below defines a bundle, and then overrides it; note the
use of the "dot" prefix everywhere except the "merge". If "merge" is used in a a
declaration in app-flow.json, which is typical, the "dot" prefix on the "path"
properties are optional.
"translations": {
"translations" : {
"app" : {
"path" : "./resources/strings/app/nls/app-strings"
},
"appoverride" : {
"merge": "resources/strings/app/nls/app-strings",
"path" : "./resources/strings/override/nls/override-strings"
}
},
2-13
Chapter 2
Translations
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/>
Expression Language
Similar to variable references and other references, the objectcan be prefixed with the
container (for example, application in the example below), or you can omit the container, in
which case the current container is assumed.
<oj-bind-text
value="[[$translations.format('myPageBundle', 'info.instructions',
{ pageName: 'index.html' })
]]">
</oj-bind-text>
<!-- or -->
<oj-bind-text
value="[[$application.translations.format('myPageBundle',
'info.instructions', { pageName:
'index.html' }) ]]">
</oj-bind-text>
2-14
Chapter 2
Translations
In the example above, the format() function allows both named and positional replacement.
<oj-bind-text
value="[[ $page.translations.shell.shell_header_title ]]">
</oj-bind-text>
2-15