X

Technical Articles relating to Oracle Development Tools and Frameworks

  • JET
    February 1, 2017

JET Custom Components VII - The Lifecycle

Duncan Mills
Architect

Introduction

Once you get into any serious usage of JET Custom Components it becomes useful to understand the lifecycle of any component in some detail, this is particularly true if, for example, your components UI needs to be dynamically adjusted at runtime. Fortunately the lifecycle is simple to understand and work with, so let's get on.

The Complete Lifecycle

In the following table I outline all of the events and lifecycle methods that can be called, within your component viewModel, as a Custom Component is rendered in a view. Note that all of the methods mentioned are optional, you don't need to create them for the component to work (with the exception of the basic Constructor for the object of course).

Note that this table has been updated as of JET 4.2.0 and deprecated events and lifecycle methods have a strikethough and an explanation as to what to use instead

1 pending event raised
If the consuming view has registered a listener on the JET Custom Component for the pending event it will be received as the first indication that the component is loading. This event should be  replaced with a check of the oj.busyContext.

2

Component viewModel object constructed

If the bootstrap script for the component was passed an object constructor as it's viewModel definition, an instance of the component viewModel will be physically created. The Constructor will be passed a context object (see below).
or
initialize lifecycle method executed
In the case where an already constructed object (as opposed to a constructor) is passed as the viewModel then the framework will call this initialize method, passing a context object.

3
activated lifecycle method executed
The activated lifecycle method is called and passed the standard context object. At the point in time that this method executes, the base component element will exist in the HTML DOM, however it will not yet have any child nodes1 so you cannot manipulate the UI at this stage.
You can return a promise from this method which will delay the rest of the component initialisation until it is complete

4
attached lifecycle method executed
The attached lifecycle method is called and passed the standard context object. At this phase, the view defined by the Composite Component bootstrap script will have been processed and the Composite Component element will now have child elements in the DOM that you can locate and manipulate if necessary. If you need to set up any viewModel values that will be used by knockout bindings in the generated view, this is your last chance to do so. Replaced by the connected method

5
connected lifecycle method executed
The connected lifecycle method is called and passed the standard context object. At this phase, the view defined by the Composite Component bootstrap script will have been processed and the Composite Component element will now have child elements in the DOM that you can locate and manipulate if necessary. If you need to set up any viewModel values that will be used by knockout bindings in the generated view, this is your last chance to do so. Unlike the deprecated attached method, this method is called every time that the element is connected to the DOM, not just the first time

6
bindingsApplied lifecycle method executed
The bindingsApplied lifecycle method is called and passed the standard context object. At this phase, the Knockout applyBindings has been called on the subtree and any data-bind or other Knockout directives will have been applied. Thus it would be too late to change a DOM element to add a data-bind at this stage.

7
ready event raised
If the consuming view has registered a listener on the Composite Component for the ready event it will be received as the indication that the Composite Component has completed its loading. This is deprecated and the oj.busyContext should be used instead

8
dispose lifecycle method executed
As a module containing this Composite Component is being replaced, this dispose lifecycle method is called with a reference to the Composite element object. This gives you the opportunity to do clean up if required. Note that dispose does not get called if the user resets the page with a manual browser refresh or explicit navigation that does not first replace the current module. Therefore, you cannot rely on this method as the only way of releasing resources. Replaced by the disconnected method.

9
disconnected lifecycle method executed
The disconnected lifecycle method is called with a reference to the Composite element object every time the component is detached from the browser DOM. This gives you the opportunity to do clean up if required (esp. any listeners that the component may have registered).

As well as these lifecycle methods and events, you can also receive property-changed events throughout the lifetime of the component should you have listeners registered to do so.

The context Object

As shown above, most of the lifecycle objects, as well as the viewModel constructor are passed a common context object. This object contains the following properties:

  1. element - the DOM element that represents the JET Custom Component itself. Useful if you want to attach listeners (such as property-change listeners) or to manipulate the DOM subtree for the component.
  2. props - A promise that will return the property map for the component. See Part IV in this series for information on it's usage. Generally I'd recommend storing the resolved property map reference on the component viewModel as soon as you can2. This will ease later access in event handlers, methods and so forth. However, for the purpose of accessing properties in one of the startup lifecycle phases, always use this pops promise that you obtain from the context i.e. using context.props.then(). That will ensure that you're not caught out by assuming that a stored reference has already been resolved during an earlier stage in the lifecycle.
  3. slotNodeCounts - Another promise which, when resolved, returns a list of the slots defined within the JET Custom Component and the number of nodes assigned to those slots. We cover the slotting feature in Parts VIII and IX of this series.
  4. unique - A string value generated by the framework which will return a unique "identity" for the Composite Component. This identity will take the form of _ojcomposite0 where the trailing number increments for each composite in the view. This value is also available for direct reference in your view using $unique. Note, although this composite identity will be unique within the parent view that embeds the composite component, the generation is not idempotent and the unique identity that is provided for a particular instance of a Composite Component might change from run to run of a view. As such the unique value is useful for ensuring unique ids within an instance of a CCA. For a more stable unique identity that will not change from run to run, you could consider assigning an id to the Composite Component instance in the consuming view. You can then access that id through the supplied element reference e.g. element.id.
  5. uniqueId - A varient of the unique attribute, uniqueid will either contain the specific element ID assigned by the user to the custom component element, or, if that has not been set, then the same value as the unique attribute

View-Model Constructor v's Object

You will notice that in step 2 of the lifecycle table, as illustrated above, there are two paths. This specifically relates to what exactly was passed as the viewModel property during the registration phase. Let's explore that.

If we start off with a conventional JET Custom Component viewModel definition, something like:

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

This defines a constructor function CCDemoNameBadgeComponentModel and then returns it.

Contrast this with the subtly different variant which is more like what you might use with a standard Knockout view / ojModule:

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

Note that there are two physical differences here, the first being the lack of a context argument to the constructor function and the second being the use of the new keyword in the return statement. The effect of this new statement, is that rather than just returning the constructor function, this second version returns a pre-created instance of the component viewModel.

The JET Custom Component registration process only takes place once for a particular composite, no matter how many instances are being used in the view. During that registration call, if the object passed to the viewModel property is a constructor function then, as each new instance of the physical component is created on the view, the framework will use this constructor to create a corresponding viewModel. As noted above, the constructor is passed a context object as part of this. This viewModel is therefore dedicated to that instance of the component. In this normal variant of the lifecycle the initialize lifecycle method is never called.

If the object passed to the viewModel is instead a instance, rather than a constructor, as in the second example, then all instances of the component created in that view will use this same pre-created viewModel object. In this case the alternate version of the lifecycle kicks in and the initialize function is called with a context that is appropriate for that instance of the Composite Component. It is, in fact, somewhat unusual to be using this second approach, if you do, it would generally be when you are leveraging a factory object that in turn would be creating a viewModel instance. You should rarely (if ever!) use this variant.

(Deprecated) Lifecycle Method Name Customization

In versions of JET prior to 3.0 it is possible to override the lifecycle function names used by the lifecycle callbacks (initialize, activate and so forth). This was accomplished by updating the defaults property of oj.Composite. This feature has been deprecated and should not be used, changing these values would make your CCAs incompatible with every other application that does not use the same names which defeats the whole object of a component standard. It goes without saying that you should not use this capability.

What I've Not Covered

This article is covers the basic custom component lifecycle. There are two related further topics that will be discussed in later articles in this series:

  1. Custom Property Parsing
  2. The use of promises during registration
 

These are both advanced topics that deserve independent coverage.

What's Next?

In this article we encountered the slotNodeCount property of the lifecycle context object. Therefore it makes sense to take a look next at very powerful feature of the WebComponents / Composite Component Architecture - slotting.


JET Custom Component Series

If you've just arrived at JET Custom 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 This is not strictly true in the case where you are using slotting, something I cover in the next couple of articles. However, that's not to say that you therefore have free reign to go playing with the DOM nodes at this stage in the lifecycle, you probably shouldn't!

2 This assumes you are defining a separate viewModel instance for each component instance. If you are using a shared viewModel for all instances then you would have to be careful to always use the props reference that you receive with each lifecycle method. This will ensure you are using the correct set of properties and other context information for the component instance in play.

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.