Last Updated Feb 2022 for JET 12
Introduction
In this article I’m going to drill down on all aspects of attributes and data in Custom JET Components. In the initial tutorial article we just referenced the tag attributes directly in the HTML template via the $properties object. In this article I’ll expand on that and look at a whole range of related topics including the following:
- How to access tag attribute values from within the component ViewModel
- Changing tag attribute values in an already rendered component
- Reacting to attribute value changes within the component
- Binding to data in an external viewModel
- The attribute definition metadata in detail
And more besides. For illustration throughout, I’ll be referring back to and extending the simple component developed in Article II in this series. If you want to follow along you might want to jump back and review that article if you’ve not already been through it.
Terminology – Properties v’s Attributes
Before I start, I want to clarify some nomenclature. When I refer to attribute I mean the HTML element attribute on the custom component tag that a consumer will use within their views to embed the component. This attribute will map directly to a component property which we have pre-defined in the component metadata (component.json file). So largely the two terms can be used interchangeably, however, as we’ll discuss the name of the attribute does not always match the name of the corresponding property. Additionally it is possible for a custom component to define internal properties which are not surfaced to the consumer as writable tag attributes.
Accessing Component Property Values
With any component, an obvious requirement is to be able to somehow pass data into the component and to do that in a structured way that defines the API of the component itself. The JSON metadata file associated with the component (see component.json in Article II in this series) defines the component API and as part of that declares the properties which will be exposed as attributes on the component tag. So how do we utilize these attribute values from within the component?
Directly in the Component View
As we saw in the basic tutorial outlined in Article II of the series, the simplest way to access the attributes passed to the component tag is via the $properties object. This can be used directly in the HTML of the view template used by the component, for example: <oj-bind-text value="[[$properties.propertyname]]"> or <oj-bind-text value="[[$properties['propertyname']]]"> The latter form being for when the actual name of the property as defined within the component metadata is not a valid identifier for use with dot notation, for example it contains a dash/hyphen.
So the use of $properties gives us a direct passthrough for the UI and because the $properties object internally is backed by observables, any changes made to it will be automatically be propagated.
Inside of the Component ViewModel
What if you need to access the value of an attribute/property from within the component viewModel? This is a pretty common case as well, so let’s examine that.
At this point we encounter the Custom JET Component lifecycle for the first time. I’ll be covering the entire lifecycle in a later article, and for now we’ll just look at the basic approach of getting access to the attribute values when the component has been provided with a component viewModel constructor during registration – This was the approach that I took in Article II.
When a component instance is instantiated, the framework will automatically call the viewModel constructor to create a viewModel for the instance. At this stage, go back to the viewModel.js file from that sample, and have a careful look. When the new instance of the component viewModel is created, this constructor is used, but it is also passed a context object which contains information that we will need in order to access the passed attributes as properties. (I’ve deleted code that we don’t need for clarity):
define(
['knockout',
'ojL10n!./resources/nls/ccdemo-name-badge-strings',
'ojs/ojcontext',
'ojs/ojknockout'],
function (ko, componentStrings, Context) {
function ExampleComponentModel(context) {
var self = this;
self.composite = context.element;
self.properties = context.properties;
};
return CCDemoNameBadgeComponentModel;
});
So the constructor function receives a context object. For the purposes of this article we only care about one of the attributes of this context object:
- properties – an object that provides a map of the attributes passed to the component
A common pattern, as I have done here using the line self.properties = context.properties, is to stash a reference to this properties map in the component viewModel instance because you’ll often need to be able to read and write those values during the lifecycle of the component.
To access a property value we can just use dot notation:
function ExampleComponentModel(context) {
var self = this;
self.composite = context.element;
self.properties = context.properties;
var badgeNameAttr = self.properties.badgeName;
};
...
});
Of course, in this example, I’m not doing anything useful with the badgeNameAttr value – I’ll do that next.
Component Data that is Not Mapped to Attributes / Properties
Whilst we’re considering component data in terms of values passed in through attributes, we should also acknowledge that in a lot of cases, data used within the component may be derived in some other manner. Often the data will be associated with some external service or simply generated within the component itself.
In this respect, Custom JET Components are just like a JET module. The viewModel for your component view is, of course, the same component ViewModel that we’ve used above. As such, any properties (e.g. observables) defined in the component model are available for reference inside of your template HTML. To illustrate this, let’s make a simple change to the ccdemo-name-badge component where we add a property within the component ViewModel that massages the supplied badge-name attribute to create an uppercase version of the first segment of the name. This results in an updated component that looks like this:

Doing this will require us to access the badgeName property as we did above and assign that to another variable which is private to the component model called uppercaseFirstName. This involves a minor change to the code we already have, I’ll just show the relevant part of the constructor for brevity1:
self.properties = context.properties;
//Extract the badge-name value
var badgeNameAttr = self.properties.badgeName;
//Do something with the badge name
self.upperFirstName = badgeNameAttr.split(' ')[0].toUpperCase();
Then the new upperFirstName value can be used in the usual way. Here’s the updated version of the view for the composite component where the upperFirstName value on the component model is used directly (no reference to $properties).
<div class="badge-face">
<img class="badge-image"
:src="[[$properties.badgeImage]]"
:alt="[[$properties.badgeName]]">
<h2>
<oj-bind-text value="[[upperFirstName]]"></oj-bind-text>
</h2>
<h3>
<oj-bind-text value="[[$properties.badgeName]]"></oj-bind-text>
</h3>
</div>
(Not) Traversing the Knockout Binding Hierarchy
A note of caution. If you are an advanced Knockout user you might be accustomed to walking up the binding hierarchy using data-bind expressions that refer to sources such as $root or $parents[n] and so forth.
Now these expressions might still have a place in your code when working in the context of say a for-each loop within the scope of the component. However, what you should not try is any case where you are reaching out from the component into the ViewModel hierarchy of the view that is consuming the component. By doing so, you would be making assumptions about the environment in which the component is being used. This would effectively introduce invisible dependencies on consuming applications adding to the burden of documentation and setup of the component. Plus there is the little fact that it won’t work (for your own good!)
Binding Data by Reference
In the example that we’ve been looking at so far, the attribute values being used by the component have been hardcoded into the tag in the consuming view as literal values. e.g.
<ccdemo-name-badge badge-name="Duke Mascot" badge-image="/images/duke.png">
What we might want to do, however, is to make it a two-way street and not only pass values into the component, but to allow the component to change that value and pass it back to the consuming view. Having this functionality makes a component much more useful as you can start to use it in editing environments such as data entry forms. The good news is that the underlying JET architecture supports this out of the box with only a small amount of configuration required in the definition of the component properties. Before we look at the metadata let’s look at the syntax from the perspective of the consuming view that is using the custom component. The consuming view can pass a reference to a variable within it’s viewModel by enclosing the variable name in double braces {{…}} for a read-write reference and square brackets [[…]] for a read only reference2. Seem familiar? Of course it is, is exactly the same syntax that the JET core components use! To see this in action, I’ll amend my sample to first of all add the name and image information that I need to the viewModel of the generated app which is the appController.js file:
define(['ojs/ojresponsiveutils',
'ojs/ojresponsiveknockoututils',
'knockout',
'ojs/ojknockout',
'ccdemo-name-badge/loader'],
function(ResponsiveUtils, ResponsiveKnockoutUtils, ko) {
function ControllerViewModel() {
var self = this;
...
self.personName = ko.observable('Duke Mascot');
self.personImageURL = ko.observable('/images/duke.png');
...
Now the view (index.html in this case) can use the reference syntax instead of hardcoding, I’ll pass the personName in as a writable value and the personImageURL as read-only using those two variations on the syntax:
<div>
<h2>Test Composite</h2>
<ccdemo-name-badge badge-name="{{personName}}"
badge-image="[[personImageURL]]">
</ccdemo-name-badge>
</div>
So now, without any change, the Custom Web Component is now being fed with dynamic, rather than hardcoded data.
One nice feature of this automatic binding is that the framework will handle the conversion of the data from and to the observable for you automatically. So a change of the observable value in the consuming viewModel will be automatically reflected into your component $properties values as well without you needing to write any code (and visa-versa).
Note, however, that changes to an externally supplied reference values like this, will not kill your existing component instance, create a new one and then re-run the component lifecycle anew. So, should you have your own internal mechanisms within the component that need to react in real time to the value change in an attribute value. When we look at the lifecycle I will cover how to do this.
Attribute Metadata in Detail
Next, let’s look at the Custom JET Component metadata that is used to specify the properties (and therefore tag attributes) of the component.
So far, we’ve seen a very simple definition where only the property name and the datatype was supplied. However, there are a few more options that are supported. Let’s go through the list of commonly used settings:
{
"properties": {
"your-property-name": {
"description" : "Some descriptive text",
"displayName":"My property",
"type": "string | boolean | number | Array | Object | function ",
"value" : some value as per the selected type,
"readOnly" : true | false,
"writeback" : true | false,
"enumValues" : array of valid string values
}
}
}
(The full list can be found in the JSDoc)
A Note on Property Name
Before diving into the attributes of the property definition itself, I just wanted to remind you about the effect that the name that you actually select for your property can have. This is covered in detail in Article III in this series (Conventions and Standards). The point to remember is that if you use camel-case in the name that you choose for your property in the metadata, this will be mapped to an attribute with a slightly transformed name to allow it to work in an HTML document. Thus a property defined in the metadata with the name myProperty would actually be used/set in the document tag as the tag attribute my-property
The description Property
The description, as the name suggests, provides you with a place to add some textual documentation about the purpose of the component property. This information is currently for documentation purposes only and has no impact at runtime. It is therefore not required although of course its use would be recommended. Design time tooling that exposes Custom JET Components will be able to use the description to improve the design time experience.
The displayName Property
Again displayName is primarily for design time tooling to use. Oracle Visual Builder will use this value to label the component in the Component Palette
The type Property
Type is of course the key property for the definition. If the user supplies a value in the tag attribute that does not match the declared type then the framework will log an error message in the form: Unable to parse value <supplied-value> for property <your-property-name> with type <defined-type>. In this case, the property will remain as un-set (i.e. undefined in the property map that is passed to the component componentModel instance). Additionally note that:
- If the offending property had a defined default value, this will not be set in this case.
- The other properties of the component will be processed normally
- Apart from the logged message on the console, the framework will take no remedial or “stopping” action. The lifecycle of the component will proceed as normal
Thus it is your responsibility to always check each component property that you use in an appropriate manner.
The common primitive types that you would use for your properties are:
- string
- number
- boolean
The framework will attempt to coerce any type supplied directly in the tag to these types for you. You can also specify non-primitive types for example:
{
"properties": {
"namesArray": {
"type": "Array",
}
}
}
Which could be used either with a binding to pass the array, or handily the framework will also resolve this well formed JSON string into a array of size 4 in the property value:
<my-tag names-array='["John","Paul","George","Ringo"]'>
Likewise you can define a general object type:
{
"properties": {
"personObject": {
"type": "Object",
}
}
}
And pass a JSON string:
<my-tag person-object='{"name":"Fred Flintstone","age":31}'>
Note that in both the array and object case we have to use double quotes within the attribute value to enclose any string values, so the tag attribute as a whole must use single quotes if values are being passed in-line in this fashion.
You can also refine your metadata from a documentation perspective by being more precise about the complex types that you expect. For example we could re-write the previous definition as:
{
"properties": {
"personObject": {
"type": "{name:string,age:number}"
}
}
}
Note this does not imply that the framework will attempt to validate the structure of any supplied object against this specification, rather it helps inform the user of the shape that they should supply.
When using these non-primitive types you would generally not be expecting the user to enter JSON strings directly into the tag attribute. In most cases the values will be passed in from the enclosing viewModel (e.g. using the {{…}} or [[…]] notation as discussed). However, the onus will be on you to validate the property values provided just in case the consumer has entered a JSON string manually and made a mistake.
Function Callbacks
The final type you can see in the metadata illustration above is function. As the name suggests this allows you to pass a function reference / callback from the consuming viewModel. Such callbacks will allow you to achieve a much smoother integration with the calling application. You might, for example, use this to carry out lazy loading or other integration tasks which need to take place in the context of the caller rather than the component. Function references are passed to the component via the tag attribute, using the {{…}} notation3 e.g.
<my-component scroll-next="{{moreRowsCallback}}" />
Obviously before using such callback functions you will want to be careful about documenting exactly what the shape of the callback function should be. You can in fact be more precise in your type definition about that shape of the function for documentation purposes. So for example you could define it thus
type="function(string, string, number):boolean"
This implies that a callback function with three arguments of type string, string and number respectively is required and that it returns a boolean.
The value Property
The value property of the property definition provides a default value for that property should the consumer not provide the value via the tag attribute. One slight catch to watch out for here concerns types. If the type of this default value does not match the declared type of the property as a whole it will still “win” if the attribute is not supplied via the tag markup. Thus in the case:
{
"properties": {
"doSomething": {
"type": "boolean",
"value" : "true"
}
}
}
If the do-something attribute is not supplied by the consumer then the property will be made available with the default value as part of the properties map, but as a string value not the expected boolean.
The readOnly Property
The readOnly property is a boolean attribute which, if set to true, prevents the property from being mapped from the tag attributes. This then provides a convenient way to create an internal property for the component that can still be manipulated from within the component itself, but which cannot be set directly by the consumer. If a default value is set for the property in question, it will be respected. If the consumer tries to supply the value as part of the component tag it will simply be ignored.
The writeback Property
Writeback is specifically used in cases where you wish to allow the custom component to be able to consume a binding via the {{…}} syntax and to make it a two way conversation. If writeback is set to true, the observable that was passed into the component will automatically be kept updated with any changes made to the associated property within the component. This means that you don’t have to write any plumbing code to keep the consuming page in step with the composite. If writeback is false (the default), then changes made within the component will not be propagated back.
The enumValues Property
The enumValues property will be ignored in versions of Jet prior to 3.0, although you can use it for documentation-only purposes before then. As the name suggests, when used with a string type of property, enumValues will allow you to restrict the valid values that can be supplied. Oracle Visual Builder will also use this metadata to generate you a selection list of the valid values in its property inspector.
Updating Properties
Finally for this article let’s look at updating properties. Although the properties are initially populated from the tag attributes they are not read-only. You are able to store the reference to the properties map as we have seen, this is what I have been doing in the code samples above by assigning to self.properties. Then you can just update the values as required. If the property that you change the value of is associated with a bound value from the consuming view, and the writeback property is set then the framework will automatically synchronize the consuming view attribute with the new property value that you have stored. This also true if you simply have an input bound to the attribute via the $properties reference e.g.
<oj-input-text value="{{$properties.inputValue}}">
An Important Note About Complex Objects
When a binding reference is passed to your component, any auto-propagation of changes between the component and source of the binding are based on object identity. When you bind a complex object type via {{…}} or [[…]] notation is it possible to update a child property of that object without effecting the top level object identity. The effect of this is that it is possible for a component to actually update a property of such a complex object even when writeback is set to false or the object was passed with [[…]] notation. In order to prevent this from happening if it is a problem for you, you will have to clone the incoming object when you parse your properties. Such a clone would be fully decoupled from the source object.
What’s Next?
Hopefully that’s a good primer on component properties (although there is more to learn). In the next article, I’ll be looking at component events.
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
