What are components?
Some time ago Knockout.js team released new feature – components. This feature allows developer to build some custom components that will have it’s own view and logic. Registration of component looks almost like binding registration
[code language=”javascript” light=”true”]
ko.components.register(‘mywidget’, {
viewModel: function(params) {
//Define view model here
this.title = ko.observable("Hello from component!!!");
},
template: ‘<div data-bind="text: title"></div>’
});
[/code]
The example below looks very similar to definition of user control in technologies like WPF/Silverlight or even WinForms. We have template to define view of element and view-model to define logic.
Most interesting (for me personally) is usage of these components as custom elements – custom HTML tags. after registrations of my widget in previous examaple you can write following in your HTML code:
[code language=”html” light=”true”]
<mywidget></mywidget>
[/code]
Brief description (skip it if you already familiar with components)
And this HTML tag will be replaced with template of the component with applied component view model.
Template can be defined in following way:
- With existing element id. Template element can be any existing element
div
ortemplate
or anything else.
[code language=”javascript” light=”true”]
template: { element: ‘my-component-template’}
[/code]
Element content (only children of element) will be cloned to place where you apply your custom element
- With exiting element instance
[code language=”javascript” light=”true”]
template: { element: getElementById(‘…’) }
[/code]
- Directly with string of markup
[code language=”javascript” light=”true”]
template: ‘<div data-bind="text: title"></div>’
[/code]
- With array of element instances (elements will be add sequentially)
-
With AMD (Asynchronous Module Definition)
[code language=”javascript” light=”true”]
template: { require: ‘text!some-template.html’ }
[/code]
Require.js or any other AMD module loader can be used. See https://github.com/amdjs/amdjs-api/blob/master/AMD.md for details.
For view-model configuration you can use following:
- Constructor function
[code language=”javascript” light=”true”]
viewModel: function(params) {
//Define view model here
this.title = ko.observable("Hello from component!!!");
}
[/code]
- Existing instance of view model
[code language=”javascript” light=”true”]
viewModel: { instance: viewModelInstance }
[/code]
- Factory function
[code language=”javascript” light=”true”]
viewModel: {
createViewModel: function(params, componentInfo) {
return new ViewModel(params);
}
}
[/code]
Here we have additional parameter componentInfo
. This parameter allow us to get access to our custom element with componentInfo.element
. But unfortunately we can’t get access to this element before template is applied and analyze it as it was initially added to the document. I’ll describe why I’ve said unfortunately a little later
- Load view model through AMD
[code language=”javascript” light=”true”]
viewModel: { require: ‘some/module/name’ }
[/code]
- Specify whole the component as single AMD module
[code language=”javascript” light=”true”]
define([‘knockout’], function(ko) {
return {
viewModel: function(params) {
this.title = ko.observable("Hello from component!!!");
},
template: ‘<div data-bind="text: title"></div>’
};
});
[/code]
And register it with
[code language=”javascript” light=”true”]
ko.components.register(‘my-component’, { require: ‘some/module’ });
[/code]
What components are not?
Let’s assume we would like to build a component for bootstrap button with popover. And we would like to open this popover when button is clicked and when another button inside popover is clicked we would like to call some handler in view-model. Something like button with confirmation. And we would like to add custom confirmation template with elements bound to view model.
It would be nice to have component with following syntax
[code language=”html” light=”true”]
<popover text="Donate"
data-bind="command: makeDonation"
title="Enter amount of donation">
<input class=’form-control text-right’ type=’text’
data-bind=’value: donationAmount’ />
</popover>
[/code]
But unfortunately it’s not possible. There is no way to read HTML content of the component applied as custom HTML tag, because everything view-model factory, view-model constructor and all other functions are called when template is applied to component and template is required parameter.
Thus you can’t build custom controls with templates inside. Only one possible option is to specify template id as parameter of the your custom control.
[code language=”html” light=”true”]
<template id=’donate-template’>
<input class=’form-control text-right’ type=’text’
data-bind=’value: donationAmount’ />
</template>
<popover text="Donate"
title="Enter amount of donation"
data-bind="command: makeDonation"
template="donate-template"></popover>
[/code]
Or use usual binding instead of component to specify template control
[code language=”html” light=”true”]
<div class="btn btn-xs btn-primary">
<div data-bind="popover: {title:’Enter ammount’, command: makeDonation}"
class="hidden">
<input class=’form-control text-right’ type=’text’
data-bind=’value: donationAmount’ />
</div>
Donate
</div>
[/code]
More or less equivalent code but imagine how useful this “inline templateing” can be for controls like this http://grid.tesseris.com/Home/Documentation#!/General/general
Let’s hope for the future versions…