Overview: JET Custom Elements

Oracle® JavaScript Extension Toolkit (JET)
5.0.0

E90577-01

Version:
  • 5.0.0
Module:
  • ojcustomelement

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 {*} 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:
  • external - By the application, using either the element's property setter, setAttribute, or external data binding.
  • internal - By the component, e.g. after user interaction with a text field or selection.
subproperty Object An object holding information about the subproperty that changed.
Name Type Description
path string The subproperty path that changed, starting from the top level property with subproperties delimited by '.'.
value * The current value of the subproperty that changed.
previousValue * The previous value of 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 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. 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.