After a busy few weeks at work I’ve finally managed to spend some time on knockout development again, and today I found a nice solution to a problem with data-bound images.
In my example I had a list of contacts that were being displayed on the page, and each contact had a URL linking to their profile image.
{
Id: 1,
FirstName: "Steve",
LastName: "Greatrex",
ProfileImage: "/some/image.jpg"
}
The binding to display the image is initially pretty simple – I can use the attr
binding to set the src
property on the img
element using the vanilla knockout library.
<img data-bind="attr: { src: ProfileImage }" />
Simple enough, right?
Complications
Unfortunately some of my contacts don’t have a ProfileImage
property. Even worse, some of them do have a ProfileImage
but it points to a non-existant image.
If I use the attr
binding as above then I get an unpleasant looking “missing image” icon…
…when what I really want to do is use a placeholder image instead.
The img
binding
To resolve this problem I created a new custom binding named img
. This expects a parameter containing an observable src
property for the image URL, and either a hard-coded or an observable fallback
URL to be used in case of null URLs or errors.
<img
data-bind="img: { src: ProfileImage, fallback: '/images/generic-profile.png' }"
/>
The binding itself is nothing complicated. As with all custom bindings you can optionally specify an update
and an init
handler that are called when the value changes and when the binding is initialised respectively.
For this binding, the update handler needs to check the value of both the src
and fallback
properties, then set the src
attribute on the img
to whichever value is appropriate.
The only thing that the init
function needs to handle is the scenario where the image fails to load (using jQuery.error).
ko.bindingHandlers.img = {
update: function(element, valueAccessor) {
//grab the value of the parameters, making sure to unwrap anything that could be observable
var value = ko.utils.unwrapObservable(valueAccessor()),
src = ko.utils.unwrapObservable(value.src),
fallback = ko.utils.unwrapObservable(value.fallback),
$element = $(element);
//now set the src attribute to either the bound or the fallback value
if (src) {
$element.attr('src', src);
} else {
$element.attr('src', fallback);
}
},
init: function(element, valueAccessor) {
var $element = $(element);
//hook up error handling that will unwrap and set the fallback value
$element.error(function() {
var value = ko.utils.unwrapObservable(valueAccessor()),
fallback = ko.utils.unwrapObservable(value.fallback);
$element.attr('src', fallback);
});
}
};
That’s all there is to it - you can now specify a “primary” and a “fallback” binding for your images to get something like the effect below:
Another problem solved by the Swiss army knife that is knockout custom bindings.