Creating Composite Components

Use the Oracle JET command-line interface (CLI) to create a composite component template that you can populate with content. If you’re not using the tooling, you can add the composite component files and folders manually to your Oracle JET application.

The following image shows a simple Oracle JET composite component named demo-card that displays contact cards with the contact’s name and image if available. When the user selects the card, the content flips to show additional detail about the contact.

The procedure below lists the high level steps to create a composite component using this demo-card component as an example. Portions of the code are omitted for the sake of brevity, but you can find the complete example at Composite Component - Basic. You can also download the demo files by clicking the download button (download demo icon) in the cookbook.

To create a composite component:

  1. Determine a name for your composite component.
    The Web Component specification restricts custom element names as follows:
    • Names must contain a hyphen.

    • Names must start with a lowercase ASCII letter.

    • Names must not contain any uppercase ASCII letters.

    • Names should use a unique prefix to reduce the risk of a naming collision with other components.

      A good pattern is to use your organization’s name as the first segment of the component name, for example, org-component-name.

    • Names must not be any of the reserved names. For the complete list, see valid custom element name.

      Note:

      Oracle JET also reserves the oj namespace and prefixes.

    For example, use demo-card to duplicate the contact card example.

  2. Determine where to place your composite component, using one of the following options.
    • Add the composite component to an existing Oracle JET application that you created with the Oracle JET CLI as described in Create a Web Application Using the Oracle JET Command-Line Interface or Create a Hybrid Mobile Application.

      If you use this method, you’ll use the CLI to create a composite component template that contains the folders and files you’ll need to store the composite component’s content.

    • Use the Oracle JET CLI to scaffold a new composite component, based on a template, as well as a new Oracle JET application that you can use to view and test the composite component.

      If you use this method, you don’t need to create a new Oracle JET application. However, you’ll still need to install the Oracle JET CLI as described in Install the Prerequisite Packages. Also, the application contains an index.html file without UI, navigation, or layout, and you’ll have to add those features manually to your application.

    • Manually add the composite component to an existing Oracle JET application that doesn’t use the Oracle JET CLI.

      If you use this method, you’ll create the folders and files manually to store the composite component’s content.

  3. Depending upon the choice you made in the previous step, perform one of the following tasks to create the composite component.
    • If you used the Oracle JET CLI to create your application, in the application’s top level directory, enter the following command at a terminal prompt to generate the composite component template:

      ojet create component component-name

      For example, enter ojet create component demo-card to create a composite component named demo-card. The command will add jet-composites/demo-card to the application’s js folder and files containing stub content for the composite component.

    • If you installed the Oracle JET CLI and want to create a composite component and an Oracle JET application that you can use for testing, enter the following command at a terminal prompt in a folder of your choosing:

      ojet create component component-name

      For example, enter ojet create component demo-card to create an application and a composite component named demo-card.

      The command will add a demo-card folder containing the Oracle JET application files, jet-composites/demo-card to the new application’s js folder, and files containing stub content for the composite component.

    • If you’re not using the Oracle JET CLI, create a jet-composites folder in your application’s js folder, and add folders containing the name of each composite component you will create.

      For the demo-card example, create the jet-composites folder and add a demo-card folder to it. You’ll create the individual composite component files in the remaining steps.

  4. Determine the properties, methods, and events that your composite component will support and add them to the component.json file in the composite component’s root folder, creating the file if needed.

    Important:

    The name of the composite component properties, event listeners, and methods should avoid collision with the existing HTMLElement properties, event listeners, and methods. Additionally, the property name slot should not be used. Also, you must not re-define the global attributes and events listed in the following link: Global attributes.

    The demo-card example defines properties for the composite component and the contact’s full name, employee image, title, work number, and email address. The required properties are highlighted in bold.

     {
      "name": "demo-card",
      "description": "A card element that can display an avatar or initials on one side and employee information on the other.",
      "version": "1.0.2",
      "displayName": "Demo Card",
      "jetVersion": ">=3.0.0 <5.2.0",
       "properties": {
        "name": {
          "description": "The employee's full name.",
          "type": "string"
        },
        "avatar": {
          "description": "The url of the employee's image.",
          "type": "string"
        },
        "workTitle": {
          "description": "The employee's job title.",
          "type": "string"
        },
        "workNumber": {
          "description": "The employee's work number.",
          "type": "number"
        },
        "email": {
          "description": "The employee's email.",
          "type": "string"
        }
      }
    }
    

    This basic demo-card example only defines properties for the composite component. You can also add metadata that defines methods and events as shown in the cookbook’s Composite Component - Events example. The metadata lists the name of the method or event and supported parameters.

    {
      "properties": {
        ... contents omitted
      },
      "methods": {
         "flipCard" {
           "description": "Method to toggle flipping a card"
         },
         "enableFlip" {
           "description": "Enables or disables the ability to flip a card.",
           "params": [
             {
               "name": "bEnable",
               "description": "True to enable card flipping and false otherwise.",
               "type": "boolean"
              }
            ]
          },
        },
       "events": {
         "cardClick": {
           "description": "Triggered when a card is clicked and contains the value of the clicked card..",
           "bubbles": true,
           "detail": {
             "value": {
               "description": "The value of the card.",
               "type": "string"
                    }
                }
            } 
        }
    }    
  5. If your composite component contains a ViewModel, add its definition to composite-name-viewModel.js in the composite component’s root folder, creating the file if needed.

    The code sample below shows the ViewModel for the demo-card composite component. Comments describe the purpose, parameters, and return value of each function.

    define(['knockout', 'ojs/ojknockout'],
    	function(ko) {
    		function model(context) {
    			var self = this;
    			self.initials = null;
    			self.workFormatted = null;
    			var element = context.element;
    
    			/**
    			 * Formats a 10 digit number as a phone number.
    			 * @param  {number} number The number to format
    			 * @return {string}        The formatted phone number
    			 */
    			var formatPhoneNumber = function(number) {
    				return Number(number).toString().replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
    			}
    
    			if (context.properties.name) {
    				var initials = context.properties.name.match(/\b\w/g);
    				self.initials = (initials.shift() + initials.pop()).toUpperCase();
    			}
    			if (context.properties.workNumber)
    				self.workFormatted = formatPhoneNumber(context.properties.workNumber);
    
    			/**
    			 * Flips a card
    			 * @param  {MouseEvent} event The click event
    			 */
    			self.flipCard = function(event) {
    				if (event.type === 'click' || (event.type === 'keypress' && event.keyCode === 13)) {
    					// It's better to look for View elements using a selector 
    					// instead of by DOM node order which isn't gauranteed.
    					$(element).children('.demo-card-flip-container').toggleClass('demo-card-flipped');
    				}
    			};
    		}
    
    		return model;
    	}
    )
  6. In the composite component’s root folder, add the View definition to composite-name-view.html, creating the file if needed.

    The View for the demo-card composite component is shown below. Any property defined in the component’s metadata is accessed using the $properties property of the View binding context.

    
    <div tabindex="0" role="group" class="demo-card-flip-container"
      on-click="[[flipCard]]" on-keypress="[[flipCard]]" :aria-label="[[$properties.name + ' Press Enter for 
    more info.']]">
      <div class="demo-card-front-side">
        <oj-avatar class="demo-card-avatar" role="img" size="lg" initials="[[initials]]" 
    src="[[$properties.avatar]]" :aria-label="[['Avatar of ' + $properties.name]]">
        </oj-avatar>
        <h2>
          <oj-bind-text value="[[$properties.name]]"></oj-bind-text>
        </h2>
      </div>
    
      <div class="demo-card-back-side">
        <div class="demo-card-inner-back-side">
          <h2>
    			  <oj-bind-text value="[[$properties.name]]"></oj-bind-text>
          </h2>
          <h5>
            <oj-bind-text value="[[$properties.workTitle]]"></oj-bind-text>
          </h5>
          <oj-bind-if test="[[$properties.workNumber != null]]">
            <h5>Work</h5>
            <span class="demo-card-text"><oj-bind-text value="[[workFormatted]]"></oj-bind-text></span>
          </oj-bind-if>
          <oj-bind-if test="[[$properties.email != null]]">
            <h5>Email</h5>
            <span class="demo-card-text"><oj-bind-text value="[[$properties.email]]"></oj-bind-text></span>
          </oj-bind-if>
        </div>
      </div>
    </div>
    

    For accessibility, the View’s role is defined as group, with aria-label specified for the contact’s name. In general, follow the same accessibility guidelines for the composite View markup that you would anywhere else within the application.

  7. If you’re not using the Oracle JET CLI, create the loader.js RequireJS module and place it in the composite component’s root folder.

    The loader.js module defines the composite dependencies and registers the composite’s tagName, demo-card in this example.

    define(['ojs/ojcore', 'text!./demo-card-view.html', './demo-card-viewModel',
     'text!./component.json', 'css!./demo-card-styles', 'ojs/ojcomposite'],
      function(oj, view, viewModel, metadata) {
        oj.Composite.register('demo-card', {
          view: view, 
          viewModel: viewModel, 
          metadata: JSON.parse(metadata)
        });
      }
    );

    In this example, the CSS is loaded through a RequireJS plugin (css!./demo-card-styles), and you do not need to pass it explicitly in oj.Composite.register().

  8. Configure any custom styling that your composite component will use.
    • If you only have a few styles, add them to composite-name-styles.css file in the composite component’s root folder, creating the file if needed.

      For example, the demo-card composite component defines styles for the demo card’s display, width, height, margin, padding, and more. It also defines the classes that will be used when the user clicks a contact card. A portion of the CSS is shown below. For the complete code, see demo-card-styles.css in the Composite Component - Basic cookbook sample.

      /* This is to prevent the flash of unstyled content before the composite properties have been setup. */
      demo-card:not(.oj-complete) {
        visibility: hidden;
      }
      
      demo-card {
        display: block;
        width: 200px;
        height: 200px;
        perspective: 800px;
        margin: 10px;
        box-sizing: border-box;
        cursor: pointer;
      }
      
      demo-card h2,
      demo-card h5,
      demo-card a, 
      demo-card .demo-card-avatar  {
        color: #fff;
        padding: 0;
      }
       ... remaining contents omitted
      
      
    • If you used the Oracle JET tooling to create your application and want to use Sass to generate your CSS:
      1. If needed, at a terminal prompt in your application’s top level directory, type the following command to add node-sass to your application: ojet add sass.

      2. Create composite-name-styles.scss and place it in the composite component’s top level folder.

      3. Edit composite-name-styles.scss with any valid SCSS syntax and save the file.

        In this example, a variable defines the demo card size:
        $demo-card-size: 200px;
        
        /* This is to prevent the flash of unstyled content before the composite properties have been setup. */
        demo-card:not(.oj-complete) {
          visibility: hidden;
        }
        
        demo-card {
          display: block;
          width: $demo-card-size;
          height: $demo-card-size;
          perspective: 800px;
          margin: 10px;
          box-sizing: border-box;
          cursor: pointer;
        }
        
        demo-card h2,
        demo-card h5,
        demo-card a,
        demo-card .demo-card-avatar  {
          color: #fff;
          padding: 0;
        }
        ... remaining contents omitted
        
      4. To compile Sass, at a terminal prompt type ojet build or ojet serve with the --sass flag and application-specific options.

        ojet build|serve [options] --sass

        ojet build --sass will compile your application and generate composite-name-styles.css and composite-name-styles.css.map files in the default platform’s folder. For a web application, the command will place the CSS in web/js/js-composites/composite-name.

        ojet serve --sass will also compile your application but will display the web application in a running browser with livereload enabled. If you save a change to composite-name-styles.scss in the application’s src/js/jet-composites/composite-name folder, Oracle JET will compile Sass again and refresh the display.

        Tip:

        For help with ojet command syntax, type ojet help at a terminal prompt.
  9. If you want to add documentation for your composite component, add content to README.md in your composite component's root folder, creating the file if needed.

    Your README.md file should include an overview of your component with well-formatted examples. Include any additional information that you want to provide to your component’s consumers. The recommended standard for README file format is markdown. For help with markdown, refer to https://guides.github.com/features/mastering-markdown/.