About Composite Components

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

Composite 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.

Composite Component Architecture

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

Composite components contain:

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

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

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

    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 composite 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 composite 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 composite 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 composite component’s required properties: name, version, and jetVersion. Metadata may also define optional properties, including description, displayName, compositeDependencies, icon, methods, events, and slots.

    Composite 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 composite 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 composite version change, e.g. 1.0.0 -> 1.0.1.",
      "jetVersion": "The semantic version of the supported JET version(s). Composite 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 composites with each major release and update the composite 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 composite component.

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

    The ViewModel is also where you define callbacks for various stages of the composite component’s lifecycle. Composite components support the following optional lifecyle methods: initialize (context), activated (context), attached (context), bindingsApplied (context), and detached (element).

  • CSS: Optional styling for the composite component.

    CSS is not scoped to composite components, and you must define styles appropriately.

  • SCSS: Optional files containing Sass variables to generate the composite 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 composite component’s folder and use the tooling framework to add node-sass to your application. See Step 6 — Creating Composite Components.

    Important:

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

Composite Component Files

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

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

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

  • my-composite-component.js: ViewModel

  • component.json: metadata

  • styles.css: CSS styling

  • styles.scss: Sass variables to generate CSS for composite components

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

Composite Component Slotting

Use slotting to allow the consumer of your composite component to specify child components (which can also be composites) that get slotted into specified locations within the composite's View markup. The following example contains a portion of the View markup for a composite component named demo-columns.

<div class="oj-flex oj-flex-item-pad">
  <div role="group"
       data-bind="attr: {'aria-label': $props.headers[0]}"
       class="oj-flex-item colA oj-flex oj-sm-flex-direction-column oj-sm-align-items-center">
    <h3 data-bind="text: $props.headers[0]"></h3>
    <oj-slot name="columnA">
    </oj-slot>
  </div>
  ... content omitted
</div>

In this example, the demo-columns composite component defines the oj-slot named columnA. As shown below, a developer can specify a child component with a slot named columnA when adding the demo-columns composite 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>

Composite Component Examples

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

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