Knockout.js is a very powerful library to build interactive web applications in. The creators of knockout tried really hard to keep it as light-weight as possible (and for good reason). That being said, if you're using knockout to do a lot, it often isn't long before your markup is filled with not-so-pretty-looking data-bind
attribute selectors.
One of the most criminally unused features of knockout tends to be custom binding handlers. I've talked at length about them before, but I thought it would be nice to simply enumerate a couple of really simple binding handlers which I have found invaluable... and if not, at least aesthetically helpful!
href and src attributes
It's pretty common to bind <img />
elements or anchor tags programmatically... but how annoying is it to have to use the attr
binding handler? You can quickly create "shortcut" binding handlers that give you a cleaner syntax.
ko.bindingHandlers.href ={
update:function(element, valueAccessor){
ko.bindingHandlers.attr.update(element,function(){return{ href: valueAccessor()}});}};
ko.bindingHandlers.src ={
update:function(element, valueAccessor){
ko.bindingHandlers.attr.update(element,function(){return{ src: valueAccessor()}});}};
Thus we now have:
<imgdata-bind="src: imgSrc"/><adata-bind="href: myUrl">Click Me</a><!-- instead of --><imgdata-bind="attr: { src: imgSrc }"/><adata-bind="attr: { href: myUrl }">Click Me</a>
Hidden
It's easy to quickly get annoyed by the asymmetry of some of the bindings which consume booleans. This is because knockout's binding parser allows you to simply reference an observable by name... ie, text: myTextObservable
which is equivalent totext: myTextObservable()
, however, to do anything more complicated than that, the parser expects a valid expression, in which case you then need to evaluate the observable and do it the second way.
A common example I run into is the visible
binding (one of the top 3 most commonly used bindings in my experience). However, sometimes what you really want is a hidden
binding. No problem!
ko.bindingHandlers.hidden ={
update:function(element, valueAccessor){var value = ko.utils.unwrapObservable(valueAccessor());
ko.bindingHandlers.visible.update(element,function(){return!value;});}};
And thus, we have:
<formdata-bind="hidden: hideForm">
...
</form><!-- instead of --><formdata-bind="visible: !hideForm()">
...
</form>
Instant Value
Of course any interactive web application ought to have user inputs, and thus we find ourselves using the value
binding handler.
Well, part of the fun of using a fancy javascript library such as knockout.js is to give our users immediate feedback. As a result, I found myself using the value
binding with it's binding option valueUpdate: 'afterkeydown'
more often than not! If you're like me, writing that darn option every time is a sight for sore eyes... let's get rid of it:
ko.bindingHandlers.instantValue ={
init:function(element, valueAccessor, allBindings){var newAllBindings =function(){// for backwards compatibility w/ knockout < 3.0return ko.utils.extend(allBindings(),{ valueUpdate:'afterkeydown'});};
newAllBindings.get=function(a){return a ==='valueupdate'?'afterkeydown': allBindings.get(a);};
newAllBindings.has =function(a){return a ==='valueupdate'|| allBindings.has(a);};
ko.bindingHandlers.value.init(element, valueAccessor, newAllBindings);},
update: ko.bindingHandlers.value.update
};
And thus, we can now use:
<inputdata-bind="instantValue: val"/><!-- instead of --><inputdata-bind="value: val, valueUpdate: 'afterkeydown'"/>
Note: If you want, you can even replace the original value
binding handler. I don't recommend this, since you are overriding some default behavior of knockout, but if you like to live on the edge, go for it:
(function(original)){var extend = ko.utils.extend,
unwrap = ko.utils.unwrapObservable;
ko.bindingHandlers.value ={
init:function(element, valueAccessor, allBindingsAccessor){var origBindings = allBindingsAccessor(),
origValueUpdate = origBindings.valueUpdate,
newAllBindings =function(){return origValueUpdate ===undefined? extend(origBindings,{valueUpdate:'afterkeydown'}: origBindings;};return original.init(element, valueAccessor, newAllBindings);},
update: original.update
};}(ko.bindingHandlers.value));
Toggle
This little gem can really help reduce some clutter in your viewmodels. Often times you need to bind a click handler that simply toggles (or negates) a boolean observable. voila:
ko.bindingHandlers.toggle ={
init:function(element, valueAccessor){var value = valueAccessor();
ko.applyBindingsToNode(element,{
click:function(){
value(!value());}});}};
Usage:
<buttondata-bind="toggle: isHidden">Show / Hide</button><!-- instead of --><buttondata-bind="click: function(){isHidden(!isHidden());}">Show / Hide</button>
Time Ago
Not every application needs this, but I've certainly used it plenty. Dates are a tough thing to get right in javascript. Especially when your users could be from anywhere in the world, in any time zone. There are certainly more sophisticated approaches, but one approach is just to ditch the exact time and show a relative time.
While you can certainly replace my hacky relative-time function with something more complex, like moment.js, but this did the trick for me since I just needed "time ago" dates (and only dates in the past).
function toTimeAgo (dt){var secs =(((newDate()).getTime()- dt.getTime())/1000),
days =Math.floor(secs /86400);return days ===0&&(
secs <60&&"just now"||
secs <120&&"a minute ago"||
secs <3600&&Math.floor(secs /60)+" minutes ago"||
secs <7200&&"an hour ago"||
secs <86400&&Math.floor(secs /3600)+" hours ago")||
days ===1&&"yesterday"||
days <31&& days +" days ago"||
days <60&&"one month ago"||
days <365&&Math.ceil(days /30)+" months ago"||
days <730&&"one year ago"||Math.ceil(days /365)+" years ago";};
ko.bindingHandlers.timeAgo ={
update:function(element, valueAccessor){var val = unwrap(valueAccessor()),
date =newDate(val),// WARNING: this is not compatibile with IE8
timeAgo = toTimeAgo(date);return ko.bindingHandlers.html.update(element,function(){return'<time datetime="'+ encodeURIComponent(val)+'">'+ timeAgo +'</time>';});}};
With this you can use:
<divdata-bind="timeAgo: dateCreated"></div>
Which will result in the following machine-and-human-friendly HTML being created:
<divdata-bind="timeAgo: dateCreated"><timedatetime="2014-03-04T03:19:06.627">3 hours ago</time></div>
Currency
It seems more often than not, the need to format currency arises. Why fight it? Although it might be overly simplistic for some use cases, the following binding has done the job reasonably well for me.
ko.bindingHandlers.currency ={
symbol: ko.observable('$'),
update:function(element, valueAccessor, allBindingsAccessor){return ko.bindingHandlers.text.update(element,function(){var value =+(ko.utils.unwrapObservable(valueAccessor())||0),
symbol = ko.utils.unwrapObservable(allBindingsAccessor().symbol ===undefined? allBindingsAccessor().symbol
: ko.bindingHandlers.currency.symbol);return symbol + value.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g,"$1,");});}};
This allows you to globally change the "symbol" being used as currency, as well as replace it only locally.
For example:
<spandata-bind="currency: price"></span><!-- Use a different dollar sign --><spandata-bind="currency: price, symbol: '€'"></span>
Which, with price = 1853.251;
will produce standard formatted currency:
<spandata-bind="currency: price">$1,853.25</span><!-- Use a different dollar sign --><spandata-bind="currency: price, symbol: '€'">€1,853.25</span>
Markdown
Although this requires an external dependency, it's worth simply pointing out how simple it is to integrate an external library into knockout. Look how easy it is to pull in a markdown compiler:
var converter =Markdown.getConverter();
ko.bindingHandlers.markdown ={
update:function(element, valueAccessor){return ko.bindingHandlers.html.update(element,function(){return converter.makeHtml(ko.utils.unwrapObservable(valueAccessor()));});}};
And then it's as easy as:
<divdata-bind="markdown: markdownSource"></div>
Stop Binding
I can't take credit for this incredibly simple but useful binding. Ryan Neimeyer originally introduced me to this concept.
For large knockout applications, it is inevitable that you will want to bind specific subsections of HTML with a viewmodel that's isolated from everything else. It is important to make sure that that HTML subtree stays completely unbinded from anything else. Well here is a simple way to do it:
ko.bindingHandlers.stopBinding ={
init:function(){return{ controlsDescendantBindings:true};}};
ko.virtualElements.allowedBindings.stopBinding =true;
Usage:
<!-- ko stopBinding: true --><divid="widget">
...
</div><!-- /ko -->
toJSON
There may be a use for this in an actual app, but I have mostly found it useful as a debugging tool.
Knockout comes with a useful utility function, ko.toJSON
. Although it has many other uses, you can use it to quickly bind entire viewmodels (which may have a bunch of observable properties) and quickly see what sort of changes are going on in your viewmodel. So simple:
ko.bindingHandlers.toJSON ={
update:function(element, valueAccessor){return ko.bindingHandlers.text.update(element,function(){return ko.toJSON(valueAccessor(),null,2);});}};
Usage:
<h3>Debug Info:</h3><predata-bind="toJSON: $root"></pre>
That's all I got for today. Have any useful binding handlers that you've built for knockout? Share them below!
相关推荐
在这个“knockout示例”中,我们将探讨如何将Knockout与knockout.mapping和knockout.validation这两个扩展库结合使用,以增强数据处理和验证功能。 首先,我们来看**knockout.mapping**。这个库是Knockout的一个...
Knockout.js 的中文文档不仅包含框架的基本用法,还会深入讲解各种高级特性和最佳实践,是学习和掌握Knockout.js 的宝贵资源。 在压缩包中,"mvvm" 文件可能包含了关于MVVM模式的资料,以及Knockout.js 的相关示例...
2. **绑定数据**:接着,我们创建一个FlexGrid实例,并使用`bindingSource`属性将其与Knockout的数据模型关联起来。通过设置`itemsSource`为我们的可观察数组,FlexGrid将自动显示数据。 3. **自定义列**:FlexGrid...
Civicsource 淘汰赛绑定================================== 用于敲除绑定的 yoeman 生成器###安装npm install -g generator-civicsource-knockout-binding ###用法如果尚未安装,请安装 Yeoman: npm install...
9. **文件名称列表中的"Knockout"**:这可能是一个文件夹或者文件,包含与Knockout.js相关的示例代码或库文件,用于演示如何在ASP.NET MVC 4项目中集成和使用Knockout进行无刷新操作。 综上所述,这个资源包很可能...
**Knockout.js** 是一个轻量级的MVVM(Model-View-ViewModel)JavaScript库,用于构建富交互的Web应用程序。它通过数据绑定、依赖跟踪和可观察对象的概念,使得开发者可以更轻松地实现视图与模型之间的双向数据同步...
了解并熟练掌握这些概念和用法,将使你在使用Knockout构建动态用户界面时游刃有余。通过结合依赖跟踪、声明式绑定以及灵活的扩展性,开发者可以构建出高效且易于维护的前端应用。无论是简单的数据展示还是复杂的交互...
查看该,以快速了解其工作原理和使用方法。 安装 剔除pickadate绑定在bower存储库中可用。 要将其安装在启用Bower的项目中,只需执行以下操作: bower install knockout-pickadate-binding 用法 在页面中包含指向...
cap3Options-binding是一个下拉列表,可以轻松地为不同的浏览器(IE,Firefox,Chrome)设置样式。 绑定符合原始敲除选项绑定的API,以支持轻松迁移。 当前版本 0.2.2 安装凉亭 凉亭安装敲除选择 #要求# 淘汰赛v...
5. **Knockout.js的API**:介绍Knockout.js提供的各种函数和方法,如`ko.applyBindings`、`ko.utils`等,以及如何使用它们来扩展和定制功能。 6. **实践示例**:提供实际的代码示例,演示如何在HTML和JavaScript之间...
在这个名为"playknock"的应用程序中,我们有机会深入实践和探索`Knockout.js` 的各种功能和用法。该应用基于两本权威书籍:`KnockoutJS Web 开发 [packt]` 和 `KnockoutJS [O'Reilly]`,这两本书都是学习`Knockout....
7. **转换器(Converters)和绑定处理程序(Binding Handlers)**:Durandal继承了Knockout的功能,支持自定义转换器和绑定处理程序,允许开发者扩展数据绑定的能力,实现更复杂的业务逻辑。 8. **模块启动器...
通过这个项目,开发者不仅可以掌握Knockout JS的基础用法,还能深入理解MVVM模式如何提升代码的可维护性和可扩展性。无论你是初学者还是经验丰富的开发者,"小猫克利克"都是一个很好的实践平台,它将理论知识与实际...
使用如`knockout.js`或`AngularJS`这样的MVVM框架,可以实现数据和视图的双向绑定,使得Combobox的输入和选项自动响应数据模型的变化。 10. **响应式设计(Responsive Design)**: 考虑到不同设备的屏幕尺寸,...