-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
Knockout-component is a try to solve the problem of creating reusable code for KnockoutJS. Knockout-component idea is in combining view model and corresponding template in one reusable piece - component.
Let's try to create a piece of dynamic JavaScript UI and see how one may be wraped in a component and used later. For example it will be a labeled value with inplace edit functionality. So it will be a label:
<label> Caption: </label>
with displaying value:
<label> Caption: </label><span>Value</span>
Let's create Viewmodel and add bindings to markup:
var vm = {
value: ko.observable()
};
<label> Caption: </label><span data-bind='text:value'>Value</span>
It looks now like KnockoutJS basic tutorial. So let's add editing functionality. Next to value will be a link switching a <span>
into text input, so we can edit value. We'll also need buttons to confirm our edit operation or cancel it switching UI back into display mode.
<label> Caption: </label>
<!-- ko ifnot: editing -->
<span data-bind='text: value'></span>
<a href='void:' data-bind='click:doEdit'>edit</a>
<!-- /ko -->
<!-- ko if: editing -->
<input type='text' data-bind='value: _value'>
<button data-bind='click:doSave'>Save</button>
<button data-bind='click:doCancel'>Cancel</button>
<!-- /ko -->
var
vm = {
value: ko.observable('Value'),
_value: ko.observable(),
editing: ko.observable(false)
};
vm.doSave = function() {
vm.value(vm._value());
vm.editing(false);
}
vm.doCancel = function() {
vm.editing(false);
}
vm.doEdit = function() {
vm._value(vm.value());
vm.editing(true);
}
So we have added editing
observable indicating that we are in edit state, and _value
observable to store our value while we are in edit mode. You may play with that piece of code in this fiddle.
This code has good functionality to reuse and is worth of creating a component. But we need to make it a bit more general. As we would need different captions for different values in future. Let's add a binding for caption:
...
<label> <span data-bind='text:caption'></span>: </label>
...
and corresponding observable to view model.
...
vm.caption = ko.observable('Caption');
...
<label> <span data-bind='text:caption'></span>: </label>
<!-- ko ifnot: editing -->
<span data-bind='text: value'></span>
<a href='void:' data-bind='click:doEdit'>edit</a>
<!-- /ko -->
<!-- ko if: editing -->
<input type='text' data-bind='value: _value'>
<button data-bind='click:doSave'>Save</button>
<button data-bind='click:doCancel'>Cancel</button>
<!-- /ko -->
var
vm = {
caption: ko.observable('Caption'),
value: ko.observable('Value'),
_value: ko.observable(),
editing: ko.observable(false)
};
vm.doSave = function() {
vm.value(vm._value());
vm.editing(false);
}
vm.doCancel = function() {
vm.editing(false);
}
vm.doEdit = function() {
vm._value(vm.value());
vm.editing(true);
}
This still looks like basic KnockoutJS tutorial...
Now it's time for knockout-componet to come on scene. Create a template from your markup. This is pretty simple - just wrap in script tags.
<script id='labeledValueTemplate' type='text/html'>
... Your markup here.
</script>
Then wrap your viewmodel in a function returning your viemodel:
function createVM(){
var vm = ...
return vm;
}
Next define your component as:
kc.component({
name: 'labeledValue',
template: 'labeledValueTemplate',
create: createVM
});
We have created the component.
<script id='labeledValueTemplate' type='text/html'>
<label> <span data-bind='text:caption'></span>: </label>
<!-- ko ifnot: editing -->
<span data-bind='text: value'></span>
<a href='void:' data-bind='click:doEdit'>edit</a>
<!-- /ko -->
<!-- ko if: editing -->
<input type='text' data-bind='value: _value'>
<button data-bind='click:doSave'>Save</button>
<button data-bind='click:doCancel'>Cancel</button>
<!-- /ko -->
</script>
function createVM(){
var vm = {
caption: ko.observable('Caption'),
value: ko.observable('Value'),
_value: ko.observable(),
editing: ko.observable(false)
};
vm.doSave = function() {
vm.value(vm._value());
vm.editing(false);
};
vm.doCancel = function() {
vm.editing(false);
}
vm.doEdit = function() {
vm._value(vm.value());
vm.editing(true);
}
return vm;
}
kc.component({
name: 'labeledValue',
template: 'labeledValueTemplate',
create: createVM
});
#How to use the component
kc.component
function creates custom binding for your component. In order to keep component bindings distinct from other bindings they are created in kc
namespace. So markup to use our labeledValue component will look like:
<!-- ko kc.labeledValue: true --!>
<!-- /ko --!>
of course you may use it with div tag, so component will be created in div:
<div data-bind="kc.labeledValue: true"></div>
Component's internal viemodel is accessed through object literal you pass in binding. In that object you define which properties of your outer viewmodel(bindingContext) are connected with internal viewmodel properties of component. For our labeledValued we may define a constant caption, and connect its value property to outer Veiwmodel property.
<div data-bind="kc.labeledValue: {caption:'Value #1', value: value1 }"></div>
You do not have to connect all your internal properties. They may have default values. It is obvious that you will need to connect at least one property otherwise you would better use plain template. Let's create simple outer viewmodel with 2 observables to demonstrate use of our labeledValue component:
var outerVM = {
value1: ko.observable('First Value'),
value2: ko.observable('Second Value')
}
Markup:
<div data-bind="kc.labeledValue: {caption:'Value #1', value: value1 }"></div>
<div data-bind="kc.labeledValue: {caption:'Value #2', value: value2 }"></div>
Now you may see how it is simple to reuse code with knockout-component.
All code:
<div data-bind="kc.labeledValue: {caption:'Value #1', value: value1 }"></div>
<div data-bind="kc.labeledValue: {caption:'Value #2', value: value2 }"></div>
<script id='labeledValueTemplate' type='text/html'>
<label> <span data-bind='text:caption'></span>: </label>
<!-- ko ifnot: editing -->
<span data-bind='text: value'></span>
<a href='void:' data-bind='click:doEdit'>edit</a>
<!-- /ko -->
<!-- ko if: editing -->
<input type='text' data-bind='value: _value'>
<button data-bind='click:doSave'>Save</button>
<button data-bind='click:doCancel'>Cancel</button>
<!-- /ko -->
</script>
var outerVM = {
value1: ko.observable('First Value'),
value2: ko.observable('Second Value')
}
createLabeledValueComponent();
ko.applyBindings(outerVM);
function createLabeledValueComponent() {
function createVM() {
var vm = {
caption:ko.observable('Caption'),
value: ko.observable('Value'),
_value: ko.observable(),
editing: ko.observable(false)
};
vm.doSave = function() {
vm.value(vm._value());
vm.editing(false);
};
vm.doCancel = function() {
vm.editing(false);
};
vm.doEdit = function() {
vm._value(vm.value());
vm.editing(true);
};
return vm;
}
kc.component({
name: 'labeledValue',
template: 'labeledValueTemplate',
create: createVM
});
}
And JSFiddle to play with.
This is all as for Getting Started. But Knockout-component allows more complex use scenarios:
- Loading template from url with jQuery AJAX call
- Defining custom template loading function
- Assigning internal viewmodel to outer viewmodel property
- componentOptions binding to pass into internal viewmodel creation function
- templateless components
- nesting simpler components in more complex componentns
- AMD loading of library with requireJS
to be described in other Wiki pages.