JET Custom Elements
Oracle® JavaScript Extension Toolkit (JET)
5.2.0
E97691-01
- Version:
- 5.2.0
- Since:
- 4.1.0
JET Custom Element Usage
JET components are implemented as
custom HTML elements
and programmatic access to these components is similar to interacting with regular HTML elements.
All JET custom elements have names starting with 'oj-'. A custom element will be recognized by the framework
only after its module is loaded by the application. Once the element is recognized, the framework will register
a busy state for the element and will begin the process of 'upgrading' the element. The element will not be
ready for interaction (e.g. retrieving properties or calling methods) until the upgrade process is complete with
the exception of property setters and the setProperty and setProperties methods. The application should
listen to either the page-level or an element-scoped BusyContext before attempting to interact with
any JET custom elements. See the BusyContext documentation
on how BusyContexts can be scoped.
Attributes
Basics
The next two sections will discuss how to work with custom element attributes and properties. Attributes
are declared in the custom element's HTML while properties are accessed using JavaScript on the element
instance. 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 need to be set using
expression syntax in the DOM or using the element's property setters or setProperty
and setProperties methods. Early property sets before the composite has been upgraded
will not result in [property]Changed events and will be passed to the component as part of its initial state.
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 custom elements 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 custom element'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. This limitation is due to the fact that we cannot determine whether value='2' for a property supporting any type should be parsed as a string or a number. The application should use data binding for all other value types.
Properties
Every attribute listed in the component API doc will have a corresponding property
on the HTML element and can be accessed once the component is fully upgraded.
See the property to attribute mapping
section below to see the syntax difference between setting attributes and properties.
Note that some properties are read only and updates to those properties using the property
setter will be ignored. 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.
When a property or attribute value changes, a [property]Changed CustomEvent
will be fired with the following properties in the event's detail property.
| Name | Type | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| value | * | The current value of the property that changed. | ||||||||||||
| previousValue | * | 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.
The application can listen to these [property]Changed events by adding a listener either declaratively:
<oj-some-element value="{{currentValue}}" on-value-changed="{{valueChangedListener}}"></oj-some-element>
or programmatically using the element property or the DOM addEventListener :
someElement.addEventListener("valueChanged", function(event) {...});
someElement.onValueChanged = function(event) {...};
Some JET components support complex properties. 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 correctly. 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.
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.
Data Binding and Expression Writeback
The upgrade of JET custom elements relies on any data binding resolving, the management of which is done by a binding provider. 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 thedata-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. When using the knockout binding
provider, applications should require the ojknockout module.
When a JET custom element is managed by a binding provider, its attributes may be set to a binding expression. Component attributes can be set directly using one-way [[...]] or two-way {{...}} data binding syntax whereas global attributes and attributes on any native HTML element may be one-way bound 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. In the case of component attributes, applications are recommended to bind the attribute names directly and avoid the use of the ":" prefix.
The class attribute binding supports a space delimited string of classes, an Array of classes, or an Object to toggle classes (e.g. :class='[[{errorClass: hasErrors}]]' where the hasErrors expression determines whether the errorClass class is present in the DOM). 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.
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). Since the style attribute supports Object types, it also supports dot notation for setting style subproperties directly (e.g. :style.font-weight='[[...]]').
Applications can control expression writeback in the custom element by using {{...}} syntax for two-way writable binding expressions or [[...]] for one-way only expressions. Most component properties do not writeback and those that do will indicate it in their API doc. Please note that there should be no spaces between the braces when using the data bind syntax (e.g. some-attribute='[ [...] ]').
Certain properties such as 'value' on editable components support updating the associated expression automatically whenever their value changes. This 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. 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. Please note that there should be no spaces between the braces when using the data bind syntax (e.g. some-attribute='[ [...] ]').
<oj-some-element value="[[currentValue]]" selection={{currentSelection}}></oj-some-element>
Methods
Methods can be accessed on the JET custom HTML element after the element is fully upgraded. See the specific method doc for sample usages.
Events and Listeners
By default, JET components will fire [property]Changed (e.g. valueChanged) CustomEvents
whenever a property is updated. These events, unlike other component events, are non bubbling.
See the properties section above
for details on the event payload.
In addition to supporting event listeners via the standard addEventListener
mechanism, both JET custom elements and native HTML elements 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.
Sample usages can be seen in the attribute API doc.
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 event listeners should be written with signatures of function(event, data, bindingContext).
Please note that there is a current limitation where event listeners specified using this syntax can only be set during component initialization. Subsequent setAttribute calls for the event listener attributes will be ignored.
Component events that are specifically documented in a JET custom element's API doc (including [property]Changed events) can also have listeners set via direct assignment of an on[EventName] property on the element (e.g. onClick or onOjExpand). Note however that when these events bubble up through the DOM, they can only be listened to via addEventListener calls or on-[event-name] attributes on parent elements. Event listeners that are specified via addEventListener or direct property assignment will not receive the additional parameters described above; they will be invoked with the single event parameter only.