X

Technical Articles relating to Oracle Development Tools and Frameworks

  • JET
    April 27, 2017

JET Custom Components XIX - Sub-Properties

Duncan Mills
Architect

Introduction

In the recent JET 3.0 release we made a couple of small changes to the Composite Component Architecture, some of which I've already talked about within this series of articles on the topic.  One new feature that is worth an article of it's own, however,  is the new support for nested properties, also referred to as  sub-properties. As the name suggests, this provides for a way in which a defined component property can be used to encapsulate a series of child properties. I'll look at how you define sub-properties in a moment, but first of all, let's consider why they might be useful...

It's All About The API

On the surface, being able to define a component property / tag attribute which has sub-properties does not really seem that exciting. Functionally it does not seem to offer any more than normal properties, and to an extent that is true.  However, what sub-properties to give you is the ability to define a much more organized and cleaner API for your component.  Let's illustrate that with a small example.

Here's a simple component that specifies a value property, and then three properties for translatable strings that the consumer can pass in. The idea being that they can override the default message strings baked into the component. Prior to JET 3.0 we would have exposed the translatable strings as separate properties:

<my-component value="{{value}}" 
             hint="The name of the widget you are ordering"
             required-message="You must enter a widget name" 
             required-detail="Without the name of the widget we can't place an order">

Or we might have defined the translatable strings as a complex object type allowing the user to use JSON inline to define the values:

<my-component value="{{value}}" 
             translations='{"hint":"The name of the widget you are ordering",
                            "required-message":"You must enter a widget name" 
                            "required-detail":"Without the name of the widget we can''t place an order"}'>

The main problem with this latter approach is that it's very simple for the consumer to make a mistake in the JSON string and hard to diagnose when that's the case.

Sub-properties allow us to define (for this example) a top level translations property with sub-properties for each of the strings that may be overridden. The consumer can then specify those values using a dot notation:

<my-component value="{{value}}" 
             translations.hint = "The name of the widget you are ordering"
             translations.required-message = "You must enter a widget name" 
             translations.required-detail = "Without the name of the widget we can't place an order"}'>

On the face of it, this is not really very different from the very first example where we simply had separate properties for each translatable resource. However, there are a several distinct benefits to using nested properties:

  1. The similar properties are now all grouped together into a sort of namespace, allowing you to maintain a better balance between the lengths of the property names and their descriptiveness to the user
  2. The fact that sub-properties are grouped helps the consumer to understand that they are all related, something which might not be obvious otherwise
  3. You can bind either the whole property group, or individual members, to observables in the consuming viewModel. e.g.
<my-component value="{{value}}" translations="[[translations]]">

There are some additional benefits for the CCA author which I'll come to shortly, but the cleaner API that can be presented is really the key advantage in my opinion.

Using Sub-Properties in your Components

So let's start off by reviewing how you, as a component author, can take advantage of sub-properties.

Metadata

To define sub-properties within your component.json, you literally define sub-properties! Here's the definition for the example component above

{
 "name": "my-component",
 "version": "1.0.0",
 "description": "Example Component to Illustrate Sub-Properties",
 "jetVersion": ">=3.0.0",
 "properties": {
   "value": {
     "description": "The value of the widget to edit (Required)",
     "type": "string",
     "writeback": true
   },
   "translations": {
     "description": "Options for specific error messages in the component (optional)",
     "writeback": false, 
     "properties": {
       "hint": {
         "description": "Hint text to show in the help bubble",
         "type" : string
       },
       "requiredMessage": {
         "description": "The error message to show if no value is entered",
         "type" : string
       },
       "requiredDetail": {
         "description": "The message detail to show if no value is entered",
         "type" : string
       }
    }
  }
}

Notes

  1. As you see we just define sub-properties by nesting a properties attribute under the parent property (translations in this case)
  2. There is no type attribute defined for the translations property as the sub-properties effectively define the shape of the object
  3. The sub-properties follow the same naming rules as top level properties, that is camel-case words are converted to hyphenated forms in the tag usage (e.g. requiredDetail → required-detail)
  4. Sub-properties can have nested sub-properties in turn. As you would expect, these are then specified in the tag using nested dot notation e.g. styling.ok-button.width="2em"
  5. Sub-properties do not directly support the definition of default values via the sub-property value attribute (see below)
  6. The user of the component will be able to set sub-properties one by one using dot notation, or they can set the value for all by assigning a value just to the parent property. However, mixing and matching will cause an error

Sub-Properties and Default Values

I mentioned above that sub-properties do not honour the setting of the value attribute to define a default value in case the user does not specify it on the tag. It is, however, still possible to define defaults for sub-properties, it's just that these have to be defined on the parent property itself. To do this, you specify the value of the parent property as a JSON string, defining the value of each sub-property that needs a default. For example we could update the translations property definition thus:

...
  "translations": {
    "description": "Options for specific error messages in the component (optional)",
    "writeback": false, 
    "value" : {"hint" : "Enter something",
               "requiredMessage" : "You need a value"},
    "properties": {
      "hint": {
        "description": "Hint text to show in the help bubble",
        "type" : string
      },
      "requiredMessage": {
        "description": "The error message to show if no value is entered",
        "type" : string
      },
      "requiredDetail": {
        "description": "The message detail to show if no value is entered",
        "type" : string
      }
   }
...

Note:

  1. The JSON string you define in the value can identify as many of the sub-properties as you need, it does not have to supply defaults for all of them
  2. Any explicit values defined for sub-properties are merged with the defaults defined here. In other words, explicitly specifying one sub-property within the tag will not prevent the others from defaulting if they are not explicitly supplied

Eventing

When using sub-properties, all of the changed events for a particular top level property and it's sub-properties are delivered via the normal parent property changed event. In order to support this, the detail attribute of the event object now contains an attribute called subproperty which in turn contains the following attributes:

  • path - a string containing the name of the sub-property that has been changed. This is formatted as path in dot notation starting from the root parent property name. Thus, if the translations hint sub-property was updated then the path would read translations.hint. Likewise if you have sub-properties within sub-properties then those names will all be chained together in the path. Example: styling.okButton.width
  • previousValue - the old value of the sub-property
  • value - the new value for the sub-property

In the same event, the overall detail.value and detail.previousValue attributes will reflect the consolidated object including the changed sub-property.

Just for illustration, here's a basic example of what such a change event handler might look like:

$(self.composite).on('stylingChanged',function(event){
  if (event.detail.updatedFrom === 'external'){
    if (event.detail.subproperty.path === 'styling.okButton.width'){
      $('#'+self.okButtonId).ojButton('widget').css('max-width',
                                 event.detail.subproperty.value);
    }
 }
});

Setting Properties from Outside of the Component

If your consumer wants to change the value of a sub-property after your component is loaded then they can do that in a couple of ways. Simplest of course, is to bind the tag attribute value to an observable and change the observable value. In this case the property will be automatically updated and the *Changed event fired. If, however, they want to explicitly set the value (maybe it was initially set to a literal in the page), then they need to use the setProperty() API on the element to do so. As before, you use the dot notation within the property name to specifically set the sub-property:

element.setProperty('styling.okButton.width', '20em');

Note that the new getProperty(«property name») and setProperty(«property name», «value») APIs on composite components are new in 3.0 and are the preferred way of programatically changing properties after the component has been instantiated.

Bound Properties and Write-Back

As I've already alluded, you can assign binding expressions ({{...}}, [[...]]) to sub-properties as well as the parent property, although you should not try and bind both the parent property and one of the sub-properties at the same time of course. However, apart from that, the bindings work in exactly the same way as with normal "top-level" properties.

Standards

I mentioned towards the beginning of this article that one of the great benefits of sub-properties is the opportunity that it affords to clean up otherwise complex tag APIs. As you start to build real-world Composite Components you may find yourself exposing sets of properties across multiple components that are functionally similar. Therefore it makes a lot of sense to standardize your property naming using sub-properties as a tool. You want your consumers to know, for example, that if you provide a way of overriding say an error message, they should expect a parent property root of translations with sub-properties for each of the messages.
The most common property roots might be something like:

These are only suggestions, but the idea would be to provide a degree of consistency across all your custom components to aid feature discovery. There will always be a debate, of course, as to if a particular property "deserves" a top level property of it's own, or if it should be buried in a sub-property. The rule here would be to consider how likely the user is to want to set it. You'd not, for example, want to hide field label properties under a translations root. Label is something that the user is likely to want to override very often so put it front and centre in a property of it's own.

  • translations - a group of properties with nested sub-properties for defining replaceable strings for messages and errors
  • options - a group of sub-properties used for advanced and rarely used options on a component
  • styling - a group of properties, maybe with nested sub-properties for defining style based configuration of the component, e.g. field widths and colors

JET Custom Component Series

If you've just arrived at Custom JET 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 ( 2 )
  • Nikhil Sabharwal Friday, September 29, 2017
    What are your suggestion for passing properties to sub component ?

    For ex - Assume we have a CCA-parent which internally consumes CCA-child .

    While consuming CCA-parent in an application , along with passing properties for CCA-parent the application need to pass properties to CCA-child too.

    Do we create cca-child-prop on the CCA-parent and let user pass in a JSON for it , and then CCA-parent breaks the JSON and internally form propertie list for CCA-child ?
  • Duncan Friday, September 29, 2017
    I assume that the child-component is within the view generated by the parent, as opposed to a slot from what you have said. That being the case then think of it in terms of the overall API presented to the consumer. The consumer should have no knowledge of the internal implementation of your parent component - you could change that at any time and as long as your published API does not alter, then consumers will not care.
    So the Properties and Sub-properties that you present in the component.json of the parent component will (from the users perspective) just be the API that they use. This is a long winded way of saying "no" you would not create a single cc-child-prop property and pass JSON, because that would introduce a tight coupling to a particular internal implementation of the parent.
    Contact me internally should you want to follow up.
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.