- KnockoutJS Essentials
- Jorge Ferrando
- 1110字
- 2021-07-23 20:16:18
Custom bindings
We know what a binding is, it is everything we write inside the data-bind
attribute. We have some built-in bindings. Click and value are two of them. But we can write our own custom bindings that extend the functionality of our application in a tidy way.
Writing a custom binding is very easy. It has a basic structure that we should always follow to begin with:
ko.bindingHandlers.yourBindingName = { init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { // This will be called when the binding is first applied to an element // Set up any initial state, event handlers, etc. here }, update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { // This will be called once when the binding is first applied to an element, // and again whenever any observables/computeds that are accessed change // Update the DOM element based on the supplied values here. } };
Knockout has an internal object called bindingHandlers
. We can extend this object with our custom binding. Our binding should have a name to refer to it inside the bindingHandlers
object. Our custom binding is an object that has two functions, init
and update
. Sometimes you should use just one of them, sometimes both.
Inside the init
method, we should initialize the state of our binding. Inside the update
method, we should set the code to update the binding when its model or value is updated. These methods give us some parameters to undertake this task:
element
: This is the DOM element involved in the binding.valueAccessor
: This is the value of the binding. It is usually a function or an observable. It is safer if you useko.unwrap
to get the value, such asvar value = ko.unwrap(valueAccessor());
.allBindings
: This is an object that you can use to access other bindings. You can get a binding usingallBindings.get('name')
, or ask if a binding exists usingallBindings.has('name');
.viewModel
: That is deprecated in Knockout 3.x. You should usebindingContext.$data
orbindigContext.$rawData
instead.bindingContext
: With the binding context, we can access familiar context objets such as$root
,$parents
,$parent
,$data
, or$index
to navigate through different contexts.
We can use custom bindings for many things. For example, we can format data automatically (currency or dates are clear examples) or increase the semantic meaning of other bindings. It's more descriptive to have a binding that is called toggle
than just set click
and visible
bindings to show and hide an element.

New folder structure with custom bindings and components
The toggle binding
To add new custom bindings to our application we are going to create a new folder called custom
inside our js
folder. Then we are going to create a file called koBindings.js
and we are going to link it inside our index.html
file, just below our template engine:
<script type="text/javascript" src="js/vendors/koExternalTemplateEngine_all.min.js"></script> <script type="text/javascript" src="js/custom/koBindings.js"></script>
Our first custom binding will be called toggle
. We will use this custom binding to change the value of a Boolean variable. With this behavior, we can show and hide elements, in our case, our cart. Just write this code at the beginning of the koBindings.js
file.
ko.bindingHandlers.toggle = { init: function (element, valueAccessor) { var value = valueAccessor(); ko.applyBindingsToNode(element, { click: function () { value(!value()); } }); } };
In this case, we don't need to use the update
method because we set all the behavior when we initialize the binding. We use the ko.applyBingidsToNode
method to link the click
function to the element. The applyBindingsToNode
method has the same behavior as applyBindings
but we set a context, a node from the DOM where the bindings are applied. We can say that applyBindings
is an alias of applyBindingsToNode($('body'), viewmodel)
.
Now we can use this binding in our application. Update the showCartDetails
button inside the views/header.html
template. Remove the following code:
<button class="btn btn-primary btn-sm" data-bind="click:showCartDetails, css:{disabled:cart().length < 1}">Show Cart Details </button>
Update the code for the following button:
<button class="btn btn-primary btn-sm" data-bind="toggle:visibleCart, css:{disabled:cart().length < 1}">
<span data-bind="text: visibleCart()?'Hide':'Show'">
</span> Cart Details
</button>
Now we don't need the showCartDetails
and hideCartDetails
methods any more and we can attack the visibleCart
variable directly with the toggle
binding.
With this simple binding, we have removed two methods of our code and we have created a reusable code that doesn't depend on our cart view-model. Because of that you can reuse the toggle binding in every project you want, as it doesn't have any external dependencies.
We should also update the cart.html
template:
<button type="button" class="close pull-right" data-bind="toggle:visibleCart"><span>×</span></button>
Once we have made this update, we realize that there is no need to use hideCartDetails
anymore. To remove it definitively, follow these steps:
- In the
finishOrder
function, remove the following line:hideCartDetails();
- Add the following line:
visibleCart(false);
There is no need to keep a function that manages just a single line of code.
The currency binding
The other useful utility that custom bindings offer is the option of formatting the data of the node they are applied to. For example, we can format the currency fields of our cart.
Add this binding just below the toggle binding:
ko.bindingHandlers.currency = { symbol: ko.observable('$'), update: function(element, valueAccessor, allBindingsAccessor){ return ko.bindingHandlers.text.update(element,function(){ var value = +(ko.unwrap(valueAccessor()) || 0), symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined? allBindingsAccessor().symbol: ko.bindingHandlers.currency.symbol); return symbol + value.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,"); }); } };
Here we are not going to initialize anything because the initial state and the update behavior is the same. As a must, when the init
and the update
method do the same thing, just use the update
method.
In this case, we are going to return the number with the format we want. First we use the built-in binding called text
to update the value of our element. This binding gets the element and a function that indicates how to update the text inside this element. In the local variable value, we are going to write the value that is inside valueAccessor
. Remember that valueAccessor
can be an observable; this is why we use the unwrap
method. We should do the same with the symbol
binding. The symbol
is another binding that we use to set the currency symbol. We don't need to define it because this binding does not have a behavior and is just a write/read binding. We can access it using allBindingsAccesor
. Finally, we return the value of joining the two variables and set a regular expression to convert the value in a formatted currency.
We can update the price binding in the catalog
and the cart
template:
<td data-bind="currency:price, symbol:'€'"></td>
We can set the symbol we want and the price will be formatted as: €100, or if we set the symbol $
or empty we will see $100
(if the price value is 100).

Currency custom binding
Notice how easy it is to add more and more useful bindings to increase the power of Knockout.

Debug the container with the $root context displayed
- Java程序設(shè)計(jì)與開(kāi)發(fā)
- C#程序設(shè)計(jì)教程
- 編譯系統(tǒng)透視:圖解編譯原理
- Python數(shù)據(jù)挖掘與機(jī)器學(xué)習(xí)實(shí)戰(zhàn)
- TradeStation交易應(yīng)用實(shí)踐:量化方法構(gòu)建贏家策略(原書第2版)
- Practical Game Design with Unity and Playmaker
- Internet of Things with ESP8266
- Building Slack Bots
- Qt 4開(kāi)發(fā)實(shí)踐
- Joomla!Search Engine Optimization
- Building Clouds with Windows Azure Pack
- Koa與Node.js開(kāi)發(fā)實(shí)戰(zhàn)
- Oracle SOA Suite 12c Administrator's Guide
- Java面試一戰(zhàn)到底(基礎(chǔ)卷)
- 第五空間戰(zhàn)略:大國(guó)間的網(wǎng)絡(luò)博弈