X

Technical Articles relating to Oracle Development Tools and Frameworks

  • JET
    January 25, 2017

JET Custom Components III - Conventions and Standards

Duncan Mills
Architect

Introduction

Now that we've covered the very basics of Custom JET Component creation, I wanted to step back and help you make sure that you get your standards set up correctly before you get any further and start to build your own.

Here I'll cover five fundamental topics:

  1. Component Scope
  2. Component Naming
  3. CSS Styles
  4. Code Organization
  5. Required Metadata

 

Component Scope

A key decision to make when thinking about your use of custom components is exactly what constitutes a good scope for a reusable component. What I mean by that, is the task of finding the correct balance for your application in terms of how many components you need to express the required functionality and how complex each is. With any reuse mechanism such as JET Components, there is an initial temptation to go overboard and define everything as a separate component. For example given the design goal of "We want to standardize how all our toolbars look"; should that be interpreted to mean I should have a single component to represent a standard toolbar? Well possibly, but does it also mean that I should have one component for an Insert toolbar icon, a second component for a "Delete" toolbar icon and so forth? Probably not... When considering how to effectively use custom components you'll have to balance the costs (both in development and runtime terms) verses the benefits. Each component has a cost, even just in terms of the contributing files that need to be loaded to create it. So, creating for example, a whole set of separate toolbar button specific components would really be less than wise in most scenarios.

Of course, in some cases, you will have an obvious re-use target in mind, for example, using this component as a plugin in a SaaS application, so that's really going to guide you well with respect to the scope of the thing. However, within the context of a normal application, you may have alternative mechanisms which may be more suitable as a vehicle for re-use. Examples of this may be the use of ojModule or even something as simple as Knockout templates if all you need is reusable UI.

When thinking about a candidate for a custom component you should always try and evaluate if this use case is a good one and that can generally be judged by positive answers to questions such as:

  • Will I re-use this thing in lots of places?
  • Will a componentized version of this be easier to use?
  • Will this component encapsulate behavior as well as attributes

The last of these is particularly important. If all you are doing is encapsulating some attributes and presenting them without events or custom methods then really a Knockout template will do the job just fine, you don't need the full power and cost of a custom component.

Given this set of criteria, my simple example given in Part II of this blog series fails to pass the last test. However, I'm sure that you'll forgive me for trying to keep things simple for illustrative purposes at the beginning.

Another good case for using a Custom JET Component is of course when it is a self contained component that takes care of managing particular set of data that is not directly sourced from the consuming application. An example of this might be something like a chat client that plugs into your app but which handles all of it's own server access and so forth. This is not a must-have feature though, there are many cases where a component will rely solely on data passed into it from the consuming application.

 

Naming and your Components

First of all let's think about the actual tag and attribute names. Here we have some guidance as part of the Web Components specification. The standard here is simple and designed to reduce the chance of future name collision. Your HTML tag should contain at least a dash/hyphen (-) character. Hence <acme-coyote-tracker> would be a good name for a Composite Component tag, however, <CoyoteTracker> would not. The same would be true of attributes as well e.g. character-name rather than say just name although it is not required by the standard. I'd also advise that you embed your organization name in some way into the name, for example as a prefix followed by a dash. In the world of WebComponents there is no governing body or fixed naming structure, and so this is a good precaution against name collisions with components from elsewhere.

A Note About Standard Element Attributes etc.

When a composite Component is created, it extends the base HTMLElement type, as a consequence, every component already inherits a defined set of attributes, methods and events including the id and title attributes.

Checklist of Naming Standards

I'd suggest that you follow the following guidelines:
  1. Your tag should include a dash/hyphen character as defined by the Web Component Standard.
  2. Name your tag in a way that assigns some kind of namespace to it. For example, if your project or company is called "Acme" then use that as as prefix (acme-...). The aim here is to reduce the chance of naming collisions with other Web Components that you do not control.
  3. For tags, case has to be ignored as users are free to use any case in their document when referencing a tag, so stick to lower case in your definition to prevent confusion.
  4. Properties in the JSON metadata that define tag attributes can either use camel-case (preferred) or the dash/hyphen character in the name of property to increase the expressiveness of the names whilst reducing the risk of collision with existing inherited attribute names. Note see Camel Case Properties below
  5. Be cautious about overloading the standard HTML attributes. For example you could use the value supplied by the standard title attribute as meaning something special to your component but that might end up confusing the user as it's not acting in the same way as other HTML elements. If in doubt, see rule 4.

 

Camel Case Properties

You may notice a slight problem with the naming rules comparing tags and the tag attributes outlined above. I've said that case is ignored by the browser for tags, but then stated that you can use camel case for the properties - surely the same rules apply? Well yes, and here's where things can get a little confusing if you're not paying attention. The tag name is supplied as the first argument to the oj.composite.register() API and that must certainly be lower case with hyphens as a matter of course. The tag attribute names, however, are not actually directly defined for the component. Instead they are derived from the properties object that you define within the component metadata. The core rule for the actual tag attributes that are derived from these property definitions is the same as for the tag, i.e. they should be case insensitive, so the framework will do some mapping for you when the property information is read. If you define your property as being all lowercase (with or without a dash/hyphen) then the property name will be used directly as the attribute name. If, however, your name your property in mixed case e.g.:

      {
        "properties":{
          "coyoteName" : {
              "type" : "string"
          }
        }
      }

Then the tag attribute that you would use to populate that when using the component would be called coyote-name. The attribute name is derived by breaking the string at the camel-case segments, inserting a dash/hyphen and converting all to lowercase. The framework will then manage the mapping between the hyphenated version and the defined property name that the Custom JET  Component uses internally. Thus, the component consumer may use component like this:

      <acme-coyote-tracker coyote-name="Wile E. Coyote"/>

But then internally in code, or in its HTML template the component can use the camel-case reference and get the supplied value. e.g.

      <h2 data-bind="text:$props.coyoteName"/>

The reason that you might want to use this approach is that if you defined the in-metadata property as "coyote-name" rather than "coyoteName" in the first place, then you would have to use the slightly more cumbersome property access syntax within your component:

      <h2 data-bind="text:$props['coyote-name']"/>

Although both approaches to naming work, the camel-case convention is preferred as this makes access to the properties much easier in your code.

File Naming

As we saw in Part II, we will generally have up to five standard files involved in each custom component:

  1. The bootstrap / loader JavaScript
  2. The metadata JSON
  3. The HTML view template file
  4. A CSS file
  5. The component viewModel JavaScript

The convention here is that the first of these is called loader.js. You should stick to this name to ensure maximum compatibility with any code that is trying to bootstrap your component at runtime.

Likewise, the metadata file should always be called component.json.

The remaining files for the custom component can follow one of two standard patterns:

Pattern A 

This is the pattern that I have used throughout these articles for clarity in the text. Files should share the same file name prefix as the associated tag, e.g. acme-coyote-tracker with the extension that represents their function:

  • acme-coyote-tracker-view.html
  • acme-coyote-tracker-styles.css
  • acme-coyote-tracker-viewModel.js

Pattern B 

Files should use a file name that reflects their purpose:

  • view.html
  • styles.css
  • viewModel.js

Pattern B means that if you copy an existing CCA to create a new one you have less re-naming to do!  That being said either pattern is acceptable. You may of course have additional scripts and resources as well which can be named as required or even organised into sub-folders.

Class Naming

Finally, for consistency you should think about how you refer to the Composite Component ViewModel throughout your code. This is in no way critical, but given that some standard is better than no-standard when it comes to helping others understand what your code is doing, it's a good idea to pick a naming convention and stick with it.

My preferred convention is to derive the name for the mode class from the name of the component, with the dashes stripped and converted to camel case, with ComponentModel appended to the end. Thus for our acme-coyote-tracker component we use AcmeCoyoteTrackerComponentModel:

      define(['ojs/ojcore','knockout','jquery'],
        function (oj, ko, $) {
        'use strict';
          function AcmeCoyoteTrackerComponentModel() {
          var self = this;
          ...
          };
        return AcmeCoyoteTrackerComponentModel;
      });

Styles

In the basic sample shown in Part II of the series I discussed how you should at least define a simple class selector that suppresses the visibility of your Composite Component until the framework has completed it's processing. You should always include this as a matter of course as your default CSS.

        acme-coyote-tracker:not(.oj-complete){
            visibility: hidden;
        }
        acme-coyote-tracker{
            display : ...;
        }

That aside, the core concern with any styles that your component requires will be the usual one of managing style conflicts. As such you should ensure the isolation of any classes that you define in this CSS by defining them in the context of the component:

acme-coyote-tracker .desert-background{
        background-image: url('images/paintedDesert.png');;
      }

CSS Resource Loading

In the example above, I'm using the url() directive to assign a background image to the desert-background styleclass. Note here that the location of these resources will be relative to the root directory for the component. Thus the paintedDesert.png will be, in this example, physically located in an images sub-directory which is a peer of the actual CSS file.

Code Organization

In terms of a single application where you put code for your components is not critical it just needs to be consistent and of course easy to map in your requireJS configuration. If you are using a standard JET project layout, as generated by the ojet command line  scaffolding, I'd recommend that you standardize on a location such as /js/jet-composites/, or similar, as the root for all your custom components, then you can place each individual custom component in its own subdirectory that shares the name of the component. Thereafter all the files that compose the component are referenced from this root.

  /js
    /jet-composites
      /acme-coyote-tracker
          loader.js
          component.json
          acme-coyote-tracker.html
          acme-coyote-tracker.css
          acme-coyote-tracker.js
          /images
             paintedDesert.png

This latter point is important because you always want to load the resources required for the component from a location relative to the bootstrap script (./*)1:

      define(
      ['ojs/ojcore', './acme-coyote-tracker', 
       'text!./acme-coyote-tracker.html',
       'text!./component.json', 
       'css!./acme-coyote-tracker', 
       'ojs/ojcomposite'],
      function (oj, viewModel, view, metadata, css) {
          'use strict';
          oj.Composite.register('acme-coyote-tracker',
              {
                  metadata: {inline: JSON.parse(metadata)},
                  viewModel: {inline: viewModel},
                  view: {inline: view},
                  css: {inline: css}
              });
         }
      );

You will note that in this case we're making the assumption that whatever JET application is consuming this component has the ojs path in its requireJS definition. This of course is a fairly safe bet, however, in general you are going to have to consider how to document a particular components dependencies particularly if non-JET shipped libraries are required.

Metadata

Over the course of the next few articles in this series I'll be discussing properties, events and so forth, along with the metadata that defines them within the component.json file. Much of this metadata is purely optional and not required for the component to actually run.  However, the metadata goes provide you with an opportunity to document your component for your consumers, and over time, may also be leveraged by component registries or design time tooling such as Visual Builder Cloud Service.  Therefore it's really a good idea to be as expressive with the metadata as you can. I'll cover the metadata specifics of each focus area in the relevant articles. However, concentrating on the component as a whole, there is some information that you should always provide at the root of the metadata .json:

 {
    "name" : "acme-coyote-tracker",
    "version" : "1.0.0",
    "jetVersion" : ">=2.2.0"
    "properties":{
    "coyoteName" : {
           "type" : "string"
       }
     }
  }

The name property documents the actual name of the tag that will surface this component. This of course should match the value that you define in the oj.composite.register() API call in your loader. The version property, of course, defines the version of your component. This becomes useful when coupled with the optional compositeDependencies property (not shown in this example) which allows you to declare inter-component dependencies. You should adopt Semantic Versioning for your component to ensure that the meaning of the version string ranges that you might use are totally clear. Finally the jetVersion property documents what version of Jet the component is compatible with. Note how these version references can specifiy a minimum version as opposed to a fixed version.

What's Next?

In the next article I'll be diving into Custom JET Components in more detail and specifically looking at properties. 

 


JET Custom Component Series

If you've just arrived at JET composite components and would like to learn more, then you can access the whole series of articles on the topic from the Custom JET Component Learning Path


1 In a future article I will cover the nuances of loading components from outside of your application.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha