JET | Wednesday, February 1, 2017

JET Composite Components VII - The Lifecycle

By: Duncan Mills | Architect

Introduction

Once you get into any serious usage of Composite 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 Composite 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).

1 pending event raised
If the consuming view has registered a listener on the Composite Component for the pending event it will be received as the first indication that the Composite Component is loading

2

Component viewModel object constructed

If the bootstrap script for the Composite 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 Composite 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.

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

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

6
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

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

As well as these lifecycle methods and events, you can also receive property-changed events throughout the lifetime of the Composite 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 Composite 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 Composite 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 Composite 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 Composite Components 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.

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

(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 Composite 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 Composite Component Architecture - slotting.


CCA Series Index

  1. Introduction
  2. Your First Composite Component - A Tutorial
  3. Composite Conventions and Standards
  4. Attributes, Properties and Data
  5. Events
  6. Methods
  7. The Lifecycle
  8. Slotting Part 1
  9. Slotting Part 2
  10. Custom Property Parsing
  11. Metadata Extensibility
  12. Advanced Loader Scripts
  13. Deferred UI Loading
  14. Using ojModule in CCAs
  15. Language Support
  16. CCAs in Form Layouts
  17. Element IDs in CCA
  18. Labelled Form Fields
  19. Component Sub-Properties

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 Composite 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 Composite Component instance in play.

Join the discussion

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
 

Visit the Oracle Blog

 

Contact Us

Oracle

Integrated Cloud Applications & Platform Services