X

Technical Articles relating to Oracle Development Tools and Frameworks

  • JET
    January 31, 2017

JET Custom Components VI - Methods

Duncan Mills
Architect

Introduction

In parts IV and V of this series I covered the JET Custom Component API in terms of properties and events. This part will complete the story by looking at component methods.

Methods, in this context, are functions that are exposed by the component that you are able to call on the JET Custom Component once it is running. You can use these as a way to pass information into the compnent after it has been started, or manipulate the component as a whole in some other way. 

A Note on Passing Data into A Running JET Custom Component

As a side note it's worth reminding you of alternative ways of passing data into the component once it is up and running. Although methods can indeed be used for this, they are not the only way. You can leverage the binding syntax ([[...]] or {{...}}) combined with property change listeners as discussed in the previous article to achieve this as well. Likewise you can set the properties directly from JQuery without using bindings e.g. $('#cc1').prop('badgeName','Fred Flintstone'); or even directly on the DOM element $('#cc1')[0]['badgeName'] = 'Fred Flintstone'; or document.getElementById("cc1")['badgeName'] = 'Fred Flintstone';. All of these will achieve the end of passing data changes in to the component when the relevant data endpoint for doing so is exposed as a writeable component property.  

The one advantage of using a JET Custom Component method though is, of course, that the method call can encapsulate a potentially complex data structure to pass and maybe you also need to update the internals of the component that are not exposed through published properties. 

Defining Custom Methods

Just like properties and events, JET Custom Component methods are declared in the component.json metadata. As an example, let's take our ongoing ccdemo-name-badge component and add a method called changeBackground that will update the background color of the badge. We'll have this take a single string parameter with the color name or code to assign. I'll also have it provide a return value to illustrate how that's done.

Not surprisingly we do this by adding a top level property to the metadata object called methods to the component.json file. This object then defines a sub-attribute for each method that the component needs to expose.

    {
      "name" : "ccdemo-name-badge",
      "version" : "1.0.0",
      "jetVersion" : "^4.2.0",      
      "properties": {
        "badgeName": {
          "description" : "Full name to display on the badge",
          "type": "string"
        },
        "badgeImage": {
          "description" : "URL for the avatar to use for this badge",
          "type": "string"
        }
      },
      "events" : {
        "badgeSelected" : {
          "description" : "The event that consuming views can use to recognize when this badge is selected",
          "bubbles" : true,
          "cancelable" : false,
          "detail" : {
            "nameOnBadge" : {"type" : "string"}
          }
        }
      },
      "methods" : {
        "changeBackground" : {
          "description" : "A function to update the background color of the badge",
          "internalName" : "_setBackgroundColor",
          "params" : [
            {
              "description":"Color name or hex color code",
              "name" : "colorToSet",
              "type": "string"
            }
          ],
          "return" : "boolean"
        }
      }
    }

Let's look at each of the attributes of the changeBackground method

The description

As in the other cases within this metadata, the description property is simply there to help your consumers and provide information for design time tooling to display. As such it's optional but recommended.

The internalName

This is an optional property which allows you to define a name for the implementing function inside of your JET Custom Component viewModel which is different from the public name that you provide as the API. In this example, the public name that consumers will use is called changeBackground but the implementing function in the component is called _setBackgroundColor. The framework automatically manages the mapping for you. You don't have to set an internalName, if you don't then the framework will just map directly to a function within the component that matches the public method name.

The params

As the name suggests, params is a place to declare any parameters that your public method may accept. The params property itself, if specified (it's optional), should define an array of sub-objects which are defined with a description, name and type property as shown here. The type definition matches the those you would use for defining properties although there is no type coercion in this case, the definition is purely informational, although design time tooling may take advantage of it.

The return

Should your method return a value you can declare this in the metadata. Again this is purely for informational purposes and no conversion or checking takes place.

Implementing a JET Custom Component Method

In order to implement your method, all you need to do is to define the appropriate function in the component View Model. The name of the function should match the name that your declared in the metadata, or the supplied internalName if you supplied that. So, here's our running example where I've implemented the custom method by creating a function with my allocated internalName - _setBackgroundColor.

define(
    ['ojs/ojcore','knockout','jquery'
    ], function (oj, ko, $) {
    'use strict';
    function CCDemoNameBadgeComponentModel(context) {
        var self = this;
        context.props.then(function(propertyMap){
            //Save the resolved properties for later access
            self.properties = propertyMap;
            //Extract the badge-name value
            var badgeNameAttr = propertyMap.badgeName;
            self._extractFirstName(badgeNameAttr);
        });
        self.composite = context.element;
        $(self.composite).on('badgeName-changed',function(event){
            if (event.detail.updatedFrom === 'external'){
              self._extractFirstName(event.detail.value);
            }
        });
        //Wire the custom event raise function into the click on
        //the composite
        $(self.composite).on('click keypress',function(event){
            self._raiseBadgeSelection(event);
        });
    };
    CCDemoNameBadgeComponentModel.prototype._extractFirstName = function (fullName) {
        if (this.upperFirstName === undefined){
            this.upperFirstName = ko.observable();
        }
        this.upperFirstName(fullName.split(' ')[0].toUpperCase());
    };
    //Generate and raise the custom event for Badge Selection
    CCDemoNameBadgeComponentModel.prototype._raiseBadgeSelection = function (sourceEvent) {
        if (sourceEvent.type === 'click' ||
            (sourceEvent.type === 'keypress' && sourceEvent.keycode === 13)){
            var eventParams = {
                'bubbles' : true,
                'cancelable' : false,
                'detail' : {
                    'nameOnBadge' : this.properties.badgeName
                }
            };
            //Raise the custom event
            this.composite.dispatchEvent(new CustomEvent('badgeSelected',eventParams));
        }
    };
    //Internal implementation of the changeBackground component method
    CCDemoNameBadgeComponentModel.prototype._setBackgroundColor = function (colorToSet) {
        var wasChanged = true;
        if (this.lastSetColor !== undefined && this.lastSetColor === colorToSet){
            wasChanged = false;
        }
        else {
            $(this.composite).children('.badge-face').css('background',colorToSet);
            this.lastSetColor = colorToSet;
        }
        return wasChanged;
    };
    return CCDemoNameBadgeComponentModel;
});

The implementation as you can see trivially updates the background-color of the main badge div and then returns a boolean value based on if the incoming value is the same as or different from the last one set.

Calling JET Custom Component Methods

Once the implementation function and the associated metadata is defined, the named custom method is just exposed on the custom component element. Thus we could call it using syntax such as:

var wasChanged = document.getElementById("cc1").changeBackground("red");

or

var wasChanged = $("#cc1")[0].changeBackground("red");

The important thing here is that other methods within the component viewModel, that are not exposed through the metadata, are kept private and cannot be accessed in this way.

What's Next

In the next article, I'll be taking a look at the lifecycle methods exposed by the underlying Composite Component Architecture and how that might be affected by your component definition.


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

Join the discussion

Comments ( 4 )
  • Hari Wednesday, July 18, 2018
    in the this usage
    var wasChanged = $("#cc1")[0].changeBackground("red");

    element id '#cc1' is used. How is this id associated to the component? There is no reference to this Id in any of the previous sections.
  • Duncan Wednesday, July 18, 2018
    cc1 is just the DOM id that has been assigned to the ccdemo-name-badge element in the page. e.g. ccdemo-name-badge id="cc1"
  • Frankie Wednesday, October 17, 2018
    Hi!
    In the code on the previous article (V - Events), it shows you using the CCDemoNameBadgeComponentModel.prototype.attached function to add event listeners for 'badgeNameChanged', 'click' and 'keypress' events. I noticed that in the updated code in this article, this has been converted into using jQuery and placed in the constructor. Is there any reason for this change?

    Also, on the previous article under the heading "Property Changed Events", you add a line of HTML that includes the function 'badgeNameChangeWatcher' - however this function is never defined. What should we do about this?

    Thanks,
    Frankie
  • Duncan Mills Thursday, October 18, 2018
    It does depend here what version of JET you are working with and although I've tried to keep all the articles in step with the changes some inconsistencies my have crept in - apologies for that.
    In general in anything > JET 5 you can just use the propertyChanged() lifecycle method of the component itself and not register any listeners as that propertyChanged method will be called automatically when there is any change made internally or externally to a property. If you are wanting to watch from outside of the component then you can catch the *Changed event in whatever way you prefer. It's just a normal DOM event.
    The badgeNameChangeWatcher function is just a whatever function you've created to react to the event that would be up to you to define in the case that you wanted to react to the property value changing in some way. If you don't have anything to specifically do when a property changes then there is no need to define one
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha