About Web Components

Oracle JET Web Components are packaged as standalone modules that your application can load using RequireJS. The framework supplies APIs that you can use to register Web Components. Knockout currently provides one and two way data binding, template markup, and Web Component activation.

Web Components follow much of the W3C Web Components - Custom Elements specification which describes a mechanism for enabling authors to define and use custom DOM elements. If you are new to Web Components and would like to learn more, visit this Oracle Blogs page for a series of articles that will introduce you to important concepts: https://blogs.oracle.com/groundside/cca.

Web Component Architecture

The following image shows a high-level view of the JET Web Component architecture. In this example, an Oracle JET application is consuming the Web Component, but you can add Web Components to other JavaScript or Oracle applications where supported.

Web Components contain:

  • A custom DOM element: Named element that functions as a HTML element.

    <my-web-component attribute1="value1" attribute2="value2" ...>
    </my-web-component> 
  • Web Component binding definition: Knockout expression for setting Web Component attributes.

    <my-web-component attribute1="value1" attribute2="[[value2]]" attribute3="{{value3}}">
    </my-web-component> 

    attribute1’s value can be a primitive JavaScript type (boolean, number, string) or a JSON object, attribute2’s value uses one way data binding, and attribute3 ‘s value uses a two way binding. One way bindings on Web Components specify that the expression will not update the application’s ViewModel if the value changes. In two way bindings, the expression will update and the value written back to the application’s ViewModel.

    In the following code sample, the Web Component is declared with three attributes: type, data, and axis-labels.

    <my-chart type=bubble data="[[salesData]]" axis-labels={{showAxisLabels}} ... </my-chart>

    Because the salesData expression is declared using one way binding ([[salesData]]), it will not be written back to if the data property is updated by the Web Component's ViewModel. The data property will contain the current value, but the salesData expression will not be updated. Alternatively, if the axisLabels property is updated by the ViewModel, both the axisLabel property and the {{showAxisLabels}} expression will contain the updated value.

  • Metadata: Data provided in JSON format which defines the Web Component’s required properties: name, version, and jetVersion. Metadata may also define optional properties, including description, displayName, dependencies, icon, methods, events, and slots.

    Web Components support both runtime and design time metadata. Design time metadata isn’t required at runtime and is useful for design time tools and property editors. Design time tools can define tools-specific metadata extensions to the Web Component’s metadata. For any tool-specific metadata extensions, refer to the documentation for that specific tool. For additional information about metadata properties, see oj.Composite in the API documentation.

    Note:

    The following sample shows some of the available metadata fields with descriptions of their content and whether they are not used at run time. Required metadata are highlighted in bold.
    {
      "name": "The component tag name",
      "version": "The component version. Note that changes to the metadata even for minor updates like updating the jetVersion should result in at least a minor Web Component version change, e.g. 1.0.0 -> 1.0.1.",
      "jetVersion": "The semantic version of the supported JET version(s). Web Component authors should not specify a semantic version range that includes unreleased JET major versions as major releases may contain non backwards compatible changes. Authors should instead recertify Web Components with each major release and update the component metadata or release a new version that is compatible with the new release changes.",
      "description": "A high-level description for the component. Not used at run time.",
      "displayName": "A user friendly, translatable name of the component. Not used at run time.",
    
      "properties": {
        "property1": {       
          "description": "A description for the property. Not used at run time.",
          "displayName": "A user friendly, translatable name of the property. Not used at run time.",
          "readOnly": "Boolean that determines whether a property can be updated outside of the ViewModel. False by default.",
          "type": "The type of the property, following Google's Closure Compiler syntax.",
          "value": "Object containing an optional default value for a property.",
          "writeback": "Boolean that determines whether an expression bound to this property should be written back to. False by default.",
          "enumValues": "An optional array of valid enum values for a string property. An error is thrown if a property value does not match one of the provided enumValues.",
          "properties": "A nested properties object for complex properties. Subproperties exposed using nested properties objects in the metadata can be set using dot notation in the attribute. See the Subproperties section for more details on working with subproperties."
          },
    
        "property2": {
             ... contents omitted
          }
      },
    
      "methods": {
        "method1": {   
          "description": "A description for the method. Not used at run time.",
          "displayName": "A user friendly, translatable name of the method. Not used at run time.",
          "internalName": "An optional ViewModel method name that is different from, but maps to this method.",
          "params": "An array of objects describing the method parameter . Not used at run time.",
          "return": "The return type of the method, following Closure Compiler syntax. Not used at run time."
         },
        "method2": {
         ... contents omitted
         }
      },
    
      "events": {
        "event1": {
          "bubbles": "Boolean that indicates whether the event bubbles up through the DOM or not. Defaults to false. Not used at run time.",
          "cancelable": "Boolean that Indicates whether the event is cancelable or not. Defaults to false. Not used at run time.",
          "description": "A description for the event. Not used at run time.",
          "displayName": "A user friendly, translatable name of the method. Not used at run time.",
          "detail": {
            "field name": "Describes the properties available on the event's detail property which contains data passed when initializing the event. Not used at run time."
           }
         }, 
        "event2": {
         ... contents omitted
         } 
       },
    
      "slots": {
        "slot1": {
          "description": "A description for the slot. Not used at run time.",
          "displayName": "A user friendly, translatable name of the method. Not used at run time."
         }
       }
    }
    

    HTML markup: (Required) Contains the View definition which describes how to render the Web Component.

  • JavaScript: Optional script for defining the ViewModel and custom events.

    The ViewModel is also where you define callbacks for various stages of the Web Component’s lifecycle. Web Components support the following optional lifecycle methods: activated (context), connected (context), bindingsApplied (context), propertyChanged (context), and disconnected (element). For more information on lifecycle methods, see oj.Composite - Lifecycle.

  • CSS: Optional styling for the Web Component.

    CSS is not scoped to Web Components, and you must define styles appropriately.

  • SCSS: Optional files containing Sass variables to generate the Web Component’s CSS.

    If you’re defining only a few styles for your component, then adding the CSS manually may be sufficient. However, there may be use cases where you want to use Sass variables to generate the CSS. In those cases, create and place the SCSS files in the Web Component’s folder and use the tooling to add node-sass to your application. See Step 8 - Creating Web Components.

    Important:

    You must add the Sass files manually to the Web Component’s folder. The tooling will compile any Sass files if they exist, but it will not create them for you.

Web Component Files

Place Web Component files in a folder with the same name as the Web Component tag. Typically, you place the folder within your application in a jet-composites folder: application-path/jet-composites/my-web-component/.

You can also place your Web Component in a different file location or reference a Web Component on a different server using RequireJS path mapping. For examples, see oj.Composite - Packaging and Registration.

Each Web Component file should use the following naming convention to match the purpose:
  • my-web-component—view.html: view template

  • my-web-component—viewModel.js: ViewModel

  • component.json: metadata

  • my-web-component-styles.css: CSS styling

  • my-web-component-styles.scss: Sass variables to generate CSS for Web Components

  • loader.js: RequireJS module defining the dependencies for its metadata, View, ViewModel, and CSS This file should also include the Web Component registration.

Web Component Slotting

Use slotting to add child components (which can also be Web Components) that get slotted into specified locations within the Web Component's View markup. The following example contains a portion of the View markup for a Web Component named demo-columns.

<div class="oj-flex oj-flex-item-pad">
  <div role="group" :aria-label="[[$properties.headers[0]]]"
   class="oj-flex-item demo-columns-col-a oj-flex oj-sm-flex-direction-column oj-sm-align-items-center">
    <h3>
			<oj-bind-text value="[[$properties.headers[0]]]"></oj-bind-text>
		</h3>
		<oj-bind-slot name="columnA">
		</oj-bind-slot>
 	</div>
  ... content omitted
</div>

In this example, the demo-columns Web Component defines an oj-bind-slot named columnA. As shown below, a developer can specify a child component with a slot named columnA when adding the demo-columns Web Component to the page.

<demo-columns id="composite-container" headers='["Sales", "Human Resources", "Support"]'>
  <!-- ko foreach: sales -->
    <demo-card slot="columnA" name="[[name]]" work-title="[[title]]"></demo-card>
  <!-- /ko -->
  ... contents omitted
</demo-columns>

Web Component Template Slots

You can define placeholders in your template using template slots that can be filled with any markup fragment you want when the template element is used within a markup of your component. When you need to reuse a stamped template with varying data, you can use a template slot to expose the additional data from the component's binding context.

Template slots for Web Components are used to define additional binding contexts for a slotted content within an application. To declaratively define a template slot, use the oj-bind-template-slot element in the Web Component's View markup for the slot that contains a stamped template DOM. The oj-bind-template-slot element is similar to the oj-bind-slot element, but its slotted content should be wrapped inside a template element within the application DOM.

In the below example, the demo-list Web Component defines an oj-bind-template-slot named item. This template slot provides the data attribute that exposes additional properties to the template DOM and an as attribute that is used as an alias for the $current variable. Note that the as attribute for oj-bind-template-slot element can be referenced only inside a default template.

<table>
  <thead>
    <tr>
      <th>
        <oj-bind-text value="[[$properties.header]]"></oj-bind-text>
      </th>
    </tr>
  </thead>
  <tbody>
    <oj-bind-for-each data="{{$properties.data}}">
      <template>
        <tr>
          <td>
            <!-- Template slot for list items with default template and an optional alias -->
            <oj-bind-template-slot name="item" data="{{ {'value': $current.data} }}" as="listItem">
              <!-- Default template -->
              <template>
                <span>
                 <oj-bind-text value='[[listItem.value]]'</oj-bind-text>
                </span>
              </template>
            </oj-bind-template-slot>
          </td>
        </tr>
      </template>
    </oj-bind-for-each>
  </tbody>
  ... contents omitted
</table>

The oj-bind-template-slot children are resolved when the Web Component View bindings are applied and are then resolved in the application's binding context extended with additional properties provided by the Web Component. These additional properties are available on the $current variable in the application provided template slot. The application can use an optional data-oj-as attribute as an alias in the template instead of the $current variable. The following example contains a portion of the application’s markup named demo-list.

<demo-list data="{{groceryList}}" header="Groceries">
 <template slot="item" data-oj-as="groceryItem">
   <oj-checkboxset>
     <oj-option value="bought"><oj-bind-text value='[[groceryItem.value]]'></oj-bind-text></oj-option>
   </oj-checkboxset>
 </template>
... contents omitted
</demo-list>

The Oracle JET Cookbook at Web Component - Template Slots includes complete examples for using template slots. oj-bind-template-slot API documentation describes the attributes and other template slot properties.

Web Component Examples

The Oracle JET Cookbook contains complete examples for creating basic and advanced Web Components. You can also find examples that use slotting and data binding. For details, see Web Component - Basic.

For additional information about Web Component fields and methods, see oj.Composite in the API documentation.