Knockout.js does an excellent job of binding the UI to a view model. Sometimes, however, a value needs some view-specific formatting before we display it.
Displaying the position
Suppose we want to display the position of each item in an observable array as 01., 02., and so on. Knockout provides $index inside a foreach binding, but it is a zero-based observable. We therefore need to:
- Add one to the index (
$index() + 1). - Prefix positions below 10 with a zero.
- Append a period after each position.
This formatting depends on the binding context and is needed only in the current view. A binding value must be a JavaScript expression, though. If we put statements such as var and return directly in the binding, Knockout cannot parse the binding and throws an exception: Uncaught SyntaxError: Unable to parse bindings.
First attempt: the wrong way
My first implementation used the following code, but it did not work:
<span data-bind="text: {
var x = $index;
x += 1;
if(x < 10) x = '0' + x;
return x + '.';
} "></span>An IIFE to the rescue
An IIFE, short for Immediately Invoked Function Expression, is a function expression that runs immediately after it is defined. It lets us use statements such as if and return inside a function while the binding remains a single JavaScript expression.
The function accepts the current index as its argument. Because $index is an observable, we call $index() to read its current value.
<span data-bind="text:
(function(i) {
i += 1;
if(i < 10) i = '0' + i;
return i + '.';
})($index());
">
</span>Alternative: a global function
The IIFE is not the only solution. We can also move the same logic into a global function:
<script>
function formatPosition(i) {
i += 1;
if(i < 10) i = '0' + i;
return i + '.';
}
</script>
<span data-bind="text: formatPosition($index())"></span>This renders the same 01., 02., and so on. In a classic script, a top-level function declaration is globally available, so Knockout can call it by name. This does not apply to functions declared in a JavaScript module.
Trade-offs
The main advantage of an IIFE is locality: the formatting logic stays next to the binding that uses it. This works well for short, view-specific transformations.
The drawbacks become apparent as the logic grows. It makes the template harder to read, and the embedded function is difficult to reuse from another binding or view. When the same formatting is needed in multiple places, moving it into a shared function or the view model is usually clearer.
99% of the time I would use a custom binding for any of this sort of stuff (formatting for display). As a general rule I avoid globals and inline JS in my html templates.
The other 1% i would use a computed observable but you do raise interesting arguments against doing so.
Another neat thing with custom bindings is their re-use and portability for future knockout projects.
That’s a good rule to follow. This is a 0.1% case. Makes me realize we lack a formatting system. Something like .Net’s .ToString with patterns or something. Just for the easy stuff. A String.Format would be even cooler. Something to think about…