Investigating KnockoutJS in Oracle JET
An introduction to the best use cases for KnockoutJS, how it's differentiated from other JavaScript frameworks, and how it's used in Oracle JET.
Join the DZone community and get the full member experience.
Join For FreeIn continuing to learn about Oracle JET, this article shares what I've learned about Knockout and how it is used in Oracle JET. The previous article can be found here, and links to the complete set of articles can be found at the end of this article.
Knockout, also referred to KnockoutJS, or often abbreviated to KO, is one of a number of popular open source JavaScript frameworks included in Oracle JET. As mentioned in previous articles, Oracle has decided with Oracle JET not to reinvent the wheel, but harness existing popular JavaScript frameworks where possible.
Before writing this article I had some knowledge that Knockout is designed to simplify binding JavaScript data to UI components, rather than relying on just jQuery for the job. However it is reported to have many super powers so I am keen to understand it in more depth to see how it makes JavaScript developers' lives easier, with the idea being to share what I learned here.
Welcome to Knockout
The Knockout website provides a fine introduction to Knockout's capabilities. The opening graphic sums up its main features:
Declarative bindings allows developers to avoid low level jQuery DOM calls to update UI elements when data changes, or in other words Knockout takes care of updating the DOM if data changes on behalf of the developer. In defining the bindings the UI developer uses what looks to be an elegant and simple syntax which I'll describe more in a moment.
As declarative bindings assist with the automatic update of UI components, the 2nd main feature of automatic UI refresh appears to be related in my mind. Simply put if we update the data for a UI component in code, Knockout will automatically refresh the UI component's data for us.
Dependency tracking is where Knockout starts to show that though it appears to be simple to use, it is quite sophisticated behind the scenes. In code via Knockout we may setup computed items (or more correctly computed observables) where the item is dependent on other items. With Knockout's dependency tracking, if one or more of the other items are updated, then Knockout will take care of keeping the dependent computed item up to date, as well as updating any UI elements dependent on it too.
Finally templating is another area where Knockout shows its superpowers. Knockout templates allow us to create for example custom UI component templates, essentially defining our own combination of HTML markup and JavaScript code, to be reused elsewhere.
Considering all of Knockout's superpowers, this does of course beg the question does Knockout aim to replace jQuery?
The best answer I think in addressing this is taken from Knockout's own website:
KO doesn’t compete with jQuery or similar low-level DOM APIs. KO provides a complementary, high-level way to link a data model to a UI. KO itself doesn’t depend on jQuery, but you can certainly use jQuery at the same time, and indeed that’s often useful if you want things like animated transitions.
Knockout at Its Simplest
Alright, so beyond downloading and including Knockout in our application, how do we make use of it?
At its simplest we apply Knockout in 3 steps:
In our HTML components we add the data-bind HTML attribute to our UI component to map to our JavaScript's managed properties & their values
In our JavaScript we add a ko.observable() to manage the properties & their values
Finally we activate the bindings by calling ko.applyBindings() on 1 and 2
This is best demonstrated by examples. All kudos goes to Knockout for the following examples as they are pretty much taken from the Knockout website, with some minor modifications, main difference being my narrative.
The first HTML example is what we'll refer to as a 1 way binding which only uses steps 1 and 3 above. It's useful to introduce this to teach the basics. Consider the following code:
The person's name is <span id="name" data-bind="text: personName"></span>
<script>
var myViewModel = {
personName: 'Bob',
personAge: 23 };
ko.applyBindings(myViewModel, document.getElementIdName('name');
</script>
Of note:
The <span> data-bind attribute — this is Knockout's declarative binding syntax, containing 1 or more comma delimited bindings. In this example there is only one binding "text: personName".
"text" is a Knockout built-in binding name/type, one of several available to us. In this case "text" adds the binding value "personName" as text to the HTML <span> element, and is essentially a binding for outputting text and controlling the appearance of the component. Other binding types include controlling the visibility of the component, adding inline HTML, controlling styling, or more programmatic type bindings including control flow such as if and foreach, as well as events such as clicks, submits and more
"personName" is the binding value. This may be a literal, variable, or JavaScript expression. In this example, personName is binding to myViewModel.personName in our JavaScript block.
In terms of the JavaScript block we've defined our own object containing the attributes personName and personAge. The magic Knockout sauce is the call to ko.applyBindings(), which makes our object, myViewModel in this example, available to our HTML component "name" identified via the call to document.getElementIdName(). The last parameter is optional, and if omitted we may apply the bindings to the whole page if we so choose.
The previous example demonstrates the basic implementation, but it is limited in that if we update either of the properties in the JavaScript code, the changes to the data will not automatically be reflected in the UI component.
To solve this we introduce the concept of Knockout observables, which allow us to create 2-way bindings. In the previous example I've modified it below by adding two calls to ko.observable() within the myViewModel object for each property:
The person's name is <span id="name" data-bind="text: personName"></span>
<script>
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(23) };
ko.applyBindings(myViewModel, document.getElementIdName('name');
</script>
Observables are designed to notify subscribers, which includes our HTML components using the data-bind attributes, about data changes in our code so they can refresh themselves.
Now here is probably the hardest bit to remember about observables when first working with them, "observables are functions, not properties". As such if we want to update an observable's value, we must call the observable function with a new value, not assign a value as if it's a property. The following example demonstrates this:
<script>
var myViewModel = {
personName: ko.observable('Bob'),
personAge: ko.observable(23) };
myViewModel.personAge += 1; // Incorrect
myViewModel.personAge(myViewModel.personAge() + 1); // Correct
ko.applyBindings(myViewModel, document.getElementIdName('name');
</script>
Observables also support observable arrays over simple element types:
<div id="info">
Array length <span id="length" data-bind="text: myArray().length"></span>
1st person's age <span id="age1" data-bind="text: myArray()[0].personAge"></span>
</div>
<script>
var myArray = ko.observableArray([
{personName:'Bob', personAge:23},
{personName:'Sam', personAge:45},
{personName:'Joe', personAge:19}]);
myArray.push({personName:'Ela', personAge:35});
ko.applyBindings(myArray, document.getElementIdName('info');
</script>
As can be seen via the 1st and 2nd <span>, myArray is pretty much accessible like arrays in any language, including properties like length, and the ability to access elements and their properties in the zero indexed array by referencing their unique position with square brackets.
Within the JavaScript ko.observableArray() makes the array available to the bindings, and supports functions like push, pop, and remove, all well documented on the Knockout website.
Of importance, the ko.observableArray() does not make all the properties of the array observable, it just keeps track of which objects are in the array, and notifies subscribes when items are added or removed from the array. If we do want all the properties of the array to be observed, just like we saw earlier, each property needs to be wrapped in a ko.observable() call.
Relating to Knockout's dependency tracking capabilities, computed observables define observables that are dependent on other observables. The following example demonstrates creating a computer observable combining a person's first and last name to created a formatted full name called personsName:
Person's name is <span id="fullName" data-bind="text: personsName"></span>
<script>
function SimpleModel() {
this.firstName = ko.observable('Bob');
this.lastName = ko.observable('Smith');
this.personsName =
ko.computed(function() { return this.firstName() + ' ' + this.LastName; }, this);
}
ko.applyBindings(new SimpleModel(), document.getElementIdName('fullName');
</script>
Pretty much the sample stands by itself without explanation except maybe for the use of "this" passed as the 2nd parameter to ko.computed(). Essentially "this" is needed for the computed observable so it can access the properties this.firstName and this.LastName inside the function.
With a computed observable, if we were later to come along and update the SimpleModel observables firstName or lastName in code, these changes would automatically be reflected in the <span> via our computing obvservable personsName.
Knockout Binding Type Support
Returning to our earlier HTML markup:
The person's name is <span id="name" data-bind="text: personName"></span>
...we noted "text" within the data-bind attribute is a binding type supported by Knockout. Knockout supports a number of different bindings types including those for controlling:
Text and appearance bindings — visible, html, css, style, attr
Control flow bindings — foreach, if, if not, with, component
Rendering templates
Again, the Knockout documentation does a great job of demonstrating these, so I'll just rattle a few example to help ground us in what is possible beyond the simple text binding above:
The visible binding type takes a boolean type which we can use to hide or show a component:
<div data-bind="visible: showMessage">My message</div>
<script>
var viewModel = {
showMessage:ko.observable(true)
};
viewModel.showMessage(false); // Hide
viewModel.showMessage(true); // Show
ko.applyBindings(viewModel);
</script>
The html binding type allows us to insert inline HTML into an element:
<div data-bind="html: myHtml"></div>
<script>
var viewModel = {
myHtml:ko.observable()
};
viewModel.myHtml("<b>Please <a href='http://acme.com'>visit</a></b>");
ko.applyBindings(viewModel);
</script>
The css binding type supports adding CSS classes to an element. In the following example the class errorMsg is only added to the <div> if the observable errors property > 0:
<div data-bind="css: {errorMsg: errors()>0}">Error List</div>
<script>
var viewModel = {
errors: ko.observable(0);
};
viewModel.errors(7);
ko.applyBindings(viewModel);
</script>
In a similar fashion the style binding type adds the CSS style to an element, in this case changing the color to red or black dependent on if errors > 0:
<div data-bind="style: { color: errors() > 0 ? 'red' : 'black'}">Error List</div>
<script>
var viewModel = {
errors: ko.observable(0);
};
viewModel.errors(7);
ko.applyBindings(viewModel);
</script>
The attr binding type is I think fairly important addition, as it allows us to set any property of the UI component, not just properties of Knockout. In the following example within the data-bind property, we can see it is setting the UI component's href and target properties via the bindings:
<a data-bind="attr: { href:url, target:how}">Report</a>
<script>
var viewModel = {
url: ko.observable("acme.html"),
how: ko.observable("_target");
}
ko.applyBindings(viewModel);
</script>
Knockout also allows control flow statements to be added to the UI data-bind attribute too, such as foreach, if, ifnot and with.
The following demonstrates a simple if binding to show or hide a component:
<div data-bind="if: showMessage">Hello World</div>
<script>
var viewModel = {
showMessage:ko.observable(true) // Show
};
viewModel.showMessage(false); // Hide
ko.applyBindings(viewModel);
</script>
In the following example based on an array of people defined in our JavaScript, within a HTML table's <tbody> we've added the Knockout foreach binding to loop through the array people. The outcome is the <tbody>'s <tr>+<td> elements will be stamped out for every element in the people array.
<table>
<thead>
<tr><th>Name</th><th>Age</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
</tr>
</tbody>
</table>
<script>
ko.applyBindings({ people: [
{ name: 'Bert', age: 12 },
{ name: 'Charles', age: 22 },
{ name: 'Denise', age: 17 }]
});
</script>
I'll leave you to explore the other control flow examples on the Knockout website.
The foreach example above does lead nicely into the concept of Knockout templates.
Knockout Templates
As mentioned earlier, Knockout templating is an area where Knockout shows its super powers.
The foreach example above could be referred to as an anonymous template. With Knockout we can also create named templates:
Employees:
<div data-bind="template: { name: 'emp-template', data: emp1 }"></div>
<div data-bind="template: { name: 'emp-template', data: emp2 }"></div>
<script id="emp-template" type="text/html">
<p>Employee Number: <span data-bind="text: id"></span></p>
<p>Employee Name: <span data-bind="text: name"></span></p>
</script>
<script>
function MyModel() {
this.emp1 = {id:50, name:'Joe'};
this.emp2 = {id:51, name:'Sam'};
}
ko.applyBindings(new MyModel());
</script>
This example differs in that we're now including a named Knockout script "emp-template" of our own making. As we can see within the script it includes its own HTML components and bindings to name and id.
We can then re-use that named template elsewhere, in the example above each <div> tag makes use of the script by calling the template in the data-bind attribute. Of importance note not just the name of the template, but the data attribute to pass in object required by the template, effectively a function parameter, which needs to include the id and name properties. As we can see between the 2 <div> tags, we are passing in entirely two different objects emp1 and emp2 so each <div> can show different results.
Of course this example is trivial, but I can imagine setting up named templates for page headers containing customer information in a standard layout, or dialogs with defined structures and so on.
Knockout also allows us to mash scripts and flows together. In the following example the <div> tag will use emp-template to print out content for as many elements as defined in the foreach emps array:
Employees:
<div data-bind="template: { name: 'emp-template', foreach: emps }"></div>
<script id="emp-template" type="text/html">
<p>Employee Name: <span data-bind="text:name"></span></p>
<p>Employee Number: <span data-bind="text:id"></span></p>
</script>
<script>
function MyModel() {
this.emps = [
{id:50, name:'Joe'},
{id:51, name:'Sam'}];
}
ko.applyBindings(new MyModel());
</script>
Finally Knockout templates support the ability to create our own components too:
<div data-bind="component: 'my-component'"></div>
<div data-bind="component: {name: 'my-component', params: { name: 'Joe' }}"></div>
<script>
ko.components.register("my-component", {
viewModel: function(data) {
this.name = (data && data.name) || "none";
},
template: "<div data-bind=\"text: name\"></div>"
});
</script>
Via a call to ko.components.register() we are able to define our own component my-component, with its own viewModel for holding state and its own attributes, and it's own HTML template.
We can then make reuse of the component via our HTML markup by referring to the component in the data-bind attribute. In the two <div> examples above we first see the short hand notation where we can just simple refer to data-bind="component: 'my-component'", and in the 2nd <div> the long hand notation where we specify the name and named parameters to pass in too.
Knockout in Oracle JET
Overall Knockout looks to make working with data bound UI components a simple task.
Returning to Oracle JET, how is Knockout included? If we create an Oracle JET app from the Yeoman templates such as navbar what can we see?:
yo oraclejet MyWebProject --template=navbar
In investigating the src/js/main.js via the embedded bootstrap requirejs.config() call we can see Knockout loaded via the paths property on line 7, and a little further down on line 24 loaded as a core component of the application via the call to require():
Further down in the require() function we can see a nested function init(), and buried within that a couple levels down on line 34 a call to ko.applyBindings() applied to the page element globalBody:
In looking at the index.html page that the navbar includes, this includes the globalBody element the ko.applyBindings() refers to on line 56:
As such in all the examples we've seen to date in this article, we've added the ko.applyBindings() call ourselves. But as we can see via the navbar template, it takes care of the ko.applyBindings() for us, and loading Knockout too. This leaves us as developers to just bind the UI components to our observables.
Conclusion
Overall Knockout looks like a simple and elegant extension for Oracle JET applications to pick up and use. I'm sure there are other choices available in the JavaScript world to assist with binding UI elements to their data. But given the fact Knockout seems so easy to use and easy to learn, it does seem like a good inclusion to me.
Article Series Links
The complete series of articles published to date can be found here:
Opinions expressed by DZone contributors are their own.
Comments