Search code examples
javascriptknockout.jsbindingcustom-binding

Knockout string format pattern


In WPF there is a binding that I use a lot;

<GridViewColumn DisplayMemberBinding="{Binding Path=Price, StringFormat=Now {0:c}!}"/>

Is there a good way to achieve a similar binding in knockout? I'm using a sprintf library which would be great to utilize.

I guess I can create a custom binding for this, but this should be a rather common request so I thought I'd check here before trying to re-invent the wheel.

One use case is to build a href attribute of an atag dynamically, e.g. to produce something like this:

<a href="#products/1/product/2">Foo</a>

Where 1 is the product group and 2 is the id of a product


Solution

  • Because you want to format a value for display (ui) and to follow its changes, a custom binding that calls your sprintf library is the most suitable solution, which offers reusability also, for example:

    ko.bindingHandlers.sprintf = {
        update: function(element, valueAccessor) {
            var options = valueAccessor();
            var formatted = sprintf(ko.unwrap(options.format), 
                                    ko.unwrap(options.args).map(function(arg) {
                                        return ko.unwrap(arg);
                                    });
            if(options.attr) {
                element.setAttribute(options.attr, formatted);
            } else {
                element.textContent = formatted;
            }
        }
    }
    

    where this custom binding require a config object having the following properties:

    • attr: the attribute to update (null if the text content of the element is to update)
    • format: string format
    • args: format args

    Usage:

    <a data-bind="sprintf: { attr:'href', 
                             format:'#products/%0/product/%1', 
                             args: [val1, val2] }">
        click
    </a>
    // val1 & val2 are ko observables
    

    But you can also use sprintf directly within the data-bind:

    <a data-bind="attr: { href: sprintf('#products/%0/product/%1', 
                                        [val1(), val2()]) 
                        }">
        click
    </a>
    

    Demo: JSFiddle