JET Web Components

Oracle® JavaScript Extension Toolkit (JET)
15.1.0

F83698-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:
  • 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 any The current value of the subproperty that changed.
previousValue any 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.

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.
Bindings are applied to slot content in the application's context with the exception of template slots which are described below. Slot content are moved to their designated component slots after bindings are applied. Please note that only text and element nodes can be assigned to a slot. Comment nodes are not eligible, so oj-bind-* elements which resolve to comment nodes after bindings are applied should be wrapped in a span or other element node for slotting.

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.
Note that $current is always availble on the binding context regardless of whether any application provided aliases are set.