十个有用的knockout binding用法

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 hiddenbinding. 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!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用Binding方法实现的WebView微型浏览器的示例代码。 在 XAML 文件中,我们需要添加一个 WebView 控件和一个 TextBox 控件用于输入网址: ```xaml <Grid> <WebView x:Name="webView" /> <TextBox x:Name="urlTextBox" TextChanged="UrlTextBox_TextChanged" /> </Grid> ``` 在 C# 代码中,我们需要创建一个名为 BrowserViewModel 的类,并在其中添加一个名为 NavigateCommand 的命令。这个命令将在用户输入网址并按下 Enter 键时被触发。 ```csharp using System.Windows.Input; using Windows.UI.Xaml.Controls; namespace WebViewMiniBrowser { public class BrowserViewModel { public ICommand NavigateCommand { get; } public BrowserViewModel() { NavigateCommand = new RelayCommand<string>(Navigate); } private void Navigate(string url) { webView.Navigate(new Uri(url)); } private WebView webView; public void SetWebView(WebView webView) { this.webView = webView; } } } ``` 在这个类中,我们创建了一个 RelayCommand 泛型类,它将在命令被触发时执行 Navigate 方法。我们还添加了一个 SetWebView 方法,用于在视图中设置 WebView 控件的引用。 现在,我们需要在视图的代码中创建一个 BrowserViewModel 的实例,并将其与视图绑定起来。我们还需要将视图中的 WebView 控件传递给 BrowserViewModel。 ```csharp public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var viewModel = new BrowserViewModel(); viewModel.SetWebView(webView); DataContext = viewModel; } private void UrlTextBox_TextChanged(object sender, TextChangedEventArgs e) { var viewModel = DataContext as BrowserViewModel; viewModel.NavigateCommand.Execute(urlTextBox.Text); } } ``` 在这个代码中,我们在 MainPage 构造函数中创建了 BrowserViewModel 的实例,并将其与视图进行绑定。我们还在 SetWebView 方法中将视图中的 WebView 控件传递给 BrowserViewModel。 最后,我们在 TextChanged 事件处理程序中获取 BrowserViewModel 的实例,并执行 NavigateCommand 命令来导航到用户输入的网址。 这样,我们就完成了一个使用 Binding 方法实现的 WebView 微型浏览器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值