JET Web Components
Oracle® JavaScript Extension Toolkit (JET)
17.1.0
G12196-01
Overview
JET components and custom components, collectively referred to as JET Web Components, are implemented as custom HTML elements and extend the HTMLElement interface. This means that JET custom elements automatically inherit global attributes and programmatic access to these components is similar to interacting with native HTML elements. All JET components live in the "oj" namespace and have HTML element names starting with "oj-". We will use the term "JET component" to refer to both native JET custom elements and custom elements implemented using the Composite component APIs after this point.
Upgrading a Custom Element
The upgrade process will begin for current JET custom elements in the DOM when the component module is loaded,
registering a class constructor with its tag name using the
CustomElementRegistry
define()
API. Existing elements matching the registered tag name will be updated to inherit the new
class definition and all of the component properties and methods will be available on the custom element after
this process completes. Additionally, JET components will resolve any data bindings during the upgrade process.
The application is responsible for calling their binding provider to apply bindings or for adding a
data-oj-binding-provider="none"
attribute in their page to indicate that no data bindings exist.
If an application is rendered using Preact, data-oj-binding-provider="preact"
should be set instead.
Note that the JET custom element upgrade process will not complete until data bindings
are resolved or no binding provider is indicated using the data-oj-binding-provider
attribute.
Also, due to JET components' data binding support, all JET component upgrades will occur asynchronously regardless
of whether a binding provider is used.
Please see the data binding section for more details on binding providers and data binding.
The application should not interact with the JET custom element except to programmatically set properties until the
custom element upgrade is complete. The recommended way to wait on the asynchronous upgrade process is to use an
element-scoped or page-level BusyContext.
Using a JET Custom Element
Custom elements can be used declaratively in HTML by using the component tag name and attributes. They are not self closing
elements and applications should include a closing tag. To interact with them programmatically, DOM APIs can be used to
retrieve the element and then access properties and methods directly on the element instance. JET custom elements can also fire
CustomEvents
for which the application can attach event listeners both declaratively and programmatically.
The rest of this document discusses these features in more detail.
Attributes
Attribute values set as string literals will be parsed and coerced to the property type. JET currently
only supports the following string literal type coercions: boolean, number, string, Object and Array, where
Object and Array types must use JSON notation with double quoted strings. A special "any" type is also supported
and is described below. All other types should to be set using
expression syntax in the DOM or using the element's property setters or setProperty
and setProperties
methods programmatically. Unless updates are done via the DOM element.setAttribute(),
the DOM's attribute value will not reflect changes like those done via the property setters or the setProperty and
setProperties methods. Attribute removals are treated as unsetting of a property where the component default value will be used if one exists.
As described below, JET uses [[...]] and {{...}} syntax to represent data bound expressions. JET does not currently provide any escaping syntax for "[[" or "{{" appearing at the beginning of the attribute value. You will need to add a space character to avoid having the string literal value interpreted as a binding expression (e.g. <oj-some-element some-attribute='[ ["arrayValue1", "arrayValue2", ... ] ]'></oj-some-element>).
Boolean Attributes
JET components treat boolean attributes differently than HTML5. Since a common application use case is to toggle a data bound boolean attribute, JET will coerce the string literal "false" to the boolean false for boolean attributes. The absence of a boolean attribute in the DOM will also be interpreted as false. JET will coerce the following string literal values to the boolean true and throw an Error for all other invalid values.
- No value assignment (e.g. <oj-some-element boolean-attribute></oj-some-element>)
- Empty string (e.g. <oj-some-element boolean-attribute=""></oj-some-element>)
- The "true" string literal (e.g. <oj-some-element boolean-attribute="true"></oj-some-element>)
- The case-insensitive attribute name (e.g. <oj-some-element boolean-attribute="boolean-attribute"></oj-some-element>)
Object-Typed Attributes
Attributes that support Object type can be declaratively set using dot notation. Note that applications should not set overlapping attributes as these will cause an error to be thrown.
<!-- person is an Object typed attribute with a firstName subproperty -->
<oj-some-element person.first-name="{{name}}"></oj-some-element>
<!-- overlapping attributes will throw an error -->
<oj-some-element person="{{personInfo}}" person.first-name="{{name}}"></oj-some-element>
If applications need to programmatically set subproperties, they can call the JET components's setProperty
method with dot notation using the camelCased property syntax (e.g. element.setProperty("person.firstName", "Sally")).
Any-Typed Attributes
Attributes that support any type are documented with type {any} in the API doc and will be coerced as
Objects, Arrays, or strings when set in HTML as a string literal. Numeric types are not supported due
to the fact that we cannot determine whether value="2" on a property supporting any type should be
coerced to a string or a number. The application should use data binding for all other value types and ensure
that when linking any-typed attributes across multiple components, that the resolved types will match, e.g.
do not data bind an value
attribute to a numeric value and use a string literal
number for its child value
attributes since those would evaluate to strings.
Data Binding
Applications can use the JET data binding syntax in order to use expressions or a non coercible attribute type (e.g. a type other than boolean, number, string, Object or Array) declaratively in HTML. This syntax can be used on both JET custom elements and native HTML elements. The application is responsible for applying bindings using a supported binding provider which then notifies JET framework code that the bindings have been resolved and to finish the custom element upgrade process.
Binding Providers
The binding provider is responsible for setting and updating attribute
expressions and any custom elements within its managed subtree will not finish upgrading until it
applies bindings on that subtree. By default, there is a single binding provider for a page,
but subtree specific binding providers can be added by using the data-oj-binding-provider
attribute with values of "none" and "knockout". The default binding provider is knockout, but if a
page or DOM subtree does not use any expression syntax or knockout, the application can set
data-oj-binding-provider="none"
on that element so its dependent JET custom elements
do not need to wait for bindings to be applied to finish upgrading. Note that regardless of whether a
binding provider is used, the custom element upgrade process will be asynchronous. When using the
knockout binding provider, applications should require the ojknockout module.
Data Binding Syntax for JET Components
Data binding syntax can be used directly on component attributes. See the specific component API doc for the complete list of component attributes. Global HTML attributes inherited from HTMLElement can also be data bound, but require special syntax described below. JET detects data bound attributes by looking for values wrapped with {{...}} or [[...]]. Please note that there should be no spaces between the braces when using the data bind syntax (e.g. some-attribute="[ [...] ]"). The {{...}} wrapped expression indicates that the application is allowing the component to update the expression which can be a knockout observable. Attributes bound using [[...]] will not be updated or "written back" to by the component. Unless the component attribute documents that it supports "writeback", we recommend that the [[...]] syntax be used, e.g. selection-mode="[[currentSelectionMode]]".
Writeback
Certain properties such as "value" on editable components support updating the associated expression automatically whenever their value changes. This usually occurs after user interaction such as with selection or typing into an input field. This expression update functionality is also known as "writeback". Applications can control expression writeback by using the {{...}} syntax for two-way writable binding expressions or [[...]] for one-way only expressions. The one-way expressions should be used when the application needs expressions strictly for "downstream-only" purposes, e.g. only for updating a component property. Note that if a writeback attribute is bound using the "downstream-only" syntax, the application and component states can become out of sync. This is different from the read-only properties, which are "upstream-only", e.g. they are used only to monitor component state. Thus an expression associated with a read-only property should always use the {{}} syntax. Most component properties do not writeback and those that do will indicate it in their API doc.
<oj-some-element value="[[currentValue]]" selection={{currentSelection}}></oj-some-element>
Data Binding Syntax for Native HTML Elements and Global Attributes
JET's data binding syntax can also be used on native HTML elements and global attributes to create one-way bindings by prefixing the attribute name with ":" (e.g. :id="[[idVar]]"). The attribute binding syntax results in the attribute being set in the DOM with the evaluated expression. Since global HTML attributes are always string typed, expressions using the ":" prefixing should resolve to strings with the exception of the style and class attributes which support additional types and are described in more detail below. In the case of component attributes, applications are recommended to bind the attribute names directly and avoid the use of the ":" prefix.
:Class Attribute
The class attribute binding supports a space delimited string of classes, an Array of classes, or an Object whose keys are individual style classes and whose values are booleans to determine whether those style classes should be present in the DOM (e.g. :class="[[{errorClass: hasErrors}]]"). Note that the Array and string types will override existing values in the class attribute when updates occur, whereas the Object type will only add and remove the classes specified. Since JET custom elements add their own classes, we recommend using the Object type when using the class attribute binding on JET custom elements.
:Style Attribute
When using the style attribute binding with an Object type, the style Object names should be the JavaScript names for that style (e.g. "fontWeight" instead of "font-weight" style='{"fontWeight": "..."}'). Since the style attribute supports Object types, it also supports dot notation for setting style subproperties directly (e.g. :style.font-weight="[[...]]").
Properties
In addition to properties inherited from the HTMLElement prototype, attributes listed in the component API doc will also be exposed as properties on the JET custom element. See the property to attribute mapping section below to see the syntax difference between setting attributes and properties. These properties can be set at any time, but can only be retrieved once the HTML element is fully upgraded. Early property sets before the component has been upgraded will not result in [property]Changed events and will be passed to the component as part of its initial state.
Read-only and Writeback Properties
Some properties are specially marked as read-only or supporting writeback in the component API doc. Read-only properties can only be read and not set by the application and generally support writeback. Writeback properties support automatic updates if they are bound using two way data binding syntax to an expression, e.g. value="{{valueObservable}}". Applications can bind an expression to a read-only attribute in HTML by using the {{..}} to ensure that updates will be reflected in the observable, but should not use this syntax to try and push a value to the read-only attribute which will result in an error state. Similarly, property sets using the setProperty, setProperties, or the element property setters should also be avoided for a read-only property.
Subproperties
Some JET components support complex properties where the top level property is of type Object and it
contains additional subproperties. If the application needs to set a single subproperty
instead of the entire complex property, the setProperty
method should be used
instead to ensure that [property]Changed events will be fired with the subproperty changes.
Note that directly updating the subproperty via dot notation (e.g. element.topProp.subProp = newValue)
will not result in a [property]Changed event being fired.
Unsetting of a Property
The undefined value is treated as unsetting of a property when passed to the
property setter and will result in the component using the default value if one exists. Unsetting of
subproperties using the element's setProperty
is not supported. Subproperties can only
only be unset when the top level property is unset.
Property sets will not result in DOM attribute updates and after the custom
element is upgraded, the application should use the custom element properties, not attributes to check
the current value.
[property]Changed Events
When a property or attribute value changes, a non-bubbling [property]Changed CustomEvent
will be fired with the following properties in the event's detail property.
Name | Type | Description | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
value | any | The current value of the property that changed. | ||||||||||||
previousValue | any | The previous value of the property that changed. | ||||||||||||
updatedFrom | string |
Where the property was updated from. Supported values are:
|
||||||||||||
subproperty | Object | An object holding information about the subproperty that changed.
|
Please note that in order for components to be notified of a property change for Array properties, the value should be data bound and updated using an expression, setting the property to an updated copy by calling slice(), or by refreshing the component after an in place Array mutation.
See Events and Listeners for additional information on how to listen for these events.
Note as well that in cases where a component property that supports writeback is bound to a writable expression, the relative order of expression writing and invocation of property change listeners is not guaranteed. For this reason, it is not recommended to write logic that depends both on component/event state and application view model state.
Property-to-Attribute Mapping
The following rules apply when mapping property to attribute names:
- Attribute names are case insensitive. CamelCased properties are mapped to kebab-cased attribute names by inserting a dash before the uppercase letter and converting that letter to lower case (e.g. a "chartType" property will be mapped to a "chart-type" attribute).
- The reverse occurs when mapping a property name from an attribute name.
Methods
Methods can be accessed on the JET component after the element is fully upgraded. See the component API doc for specifics.
Events and Listeners
JET Web Components, like other custom HTML elements, may fire CustomEvents
. These
events will be described in component documentation, including whether they bubble, are cancelable and any
event detail payloads. In addition, JET components
fire non-bubbling, non-cancelable [property]Changed (e.g. valueChanged) CustomEvents
whenever a property is updated. See the properties section above
for details on the event payload.
JET CustomEvents
can be listened to using the standard addEventListener mechanism:
someElement.addEventListener("eventName", function(event) {...});
Additionally, JET custom elements and native HTML elements within JET pages support declarative specification of event
listeners via on-[event-name]
attributes (e.g. on-click
,
on-value-changed
or on-oj-expand
). The attributes ultimately delegate to the standard
addEventListener
mechanism and only support data bound expressions
that evaluate to functions; arbitrary JavaScript will not be accepted.
Please note that event listeners specified using this syntax can only be set during component initialization.
Subsequent setAttribute calls for the event listener attributes will be ignored.
There is no associated on[EventName]
property on the JET custom element for the equivalent
on-[event-name]
attribute.
In addition to the event parameter, event listeners specified via on-[event-name]
attributes will receive two additional parameters when they are invoked: data
and bindingContext
.
The bindingContext
parameter provides the listener with the entire data binding context that
was applied to the element while the data parameter provides convenient access to relevant data.
When in an iteration context (e.g. inside an oj-bind-for-each
), the data
parameter
is equal to bindingContext["$current"]
; otherwise, it is equal to bindingContext["$data"]
.
These declarative event listeners should take the form:
<oj-some-element on-event-name="[[eventListener]]"></oj-some-element>
function eventListener(event, data, bindingContext) {
...
}
Slots
Some JET components allow application provided child content. This child content will be moved by the JET component to a designated "slot" and is referred to as slot content. Slot content can have one of two characteristics:
- Named slot content - Any direct child element with a slot attribute will be moved into that named slot, e.g. <span slot='startIcon'>... </span>. All supported named slots are described in the API Doc. Child elements with unsupported named slots will be removed from the DOM.
- Default slot content - Any direct child element lacking a slot attribute will be moved to the default slot, also known as a regular child.
Template Slots
Some components support template slots which allow the application to pass a template element with a DOM fragment that will be stamped out by the component. Bindings are not applied to template slot content until they are stamped out by the component. All template slot children will have access to the following variables:
- $current - Default variable that contains component exposed subproperties as documented in the component's API doc.
- component-level template alias - Set by the application if the component has provided a component-level alias attribute as part of its API. Provides a template alias available to all template slot binding contexts and has the same subproperties as the $current variable.
- template-level alias - Set by the application on the template element via the 'data-oj-as' attribute. Provides an alias for a specific template instance and has the same subproperties as the $current variable.