Fallback Images with Knockout

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…

image

…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:

image

Another problem solved by the Swiss army knife that is knockout custom bindings.

Advertisements

‘Loading’ Placeholders using Knockout JS

Update: this feature is now available as part of the ko.plus library available on GitHub and NuGet!


Whenever you have an asynchronous call to the server from a web page it is good practice to have some form of loading indicator to let the user know that something is happening in the background.

Examples of loading images from around the web

(samples taken from Facebook, Amazon & Windows Azure Dashboard)

On a recent project I found myself constantly repeating code to hide areas of the UI whilst running an operation in the background and insert a ‘Loading…’ animation, so instead of repeating the same boilerplate I decided to write a re-usable custom binding to get the same effect.

Ideally, all I want to do is to specify a boolean property that indicates when the ‘Loading’ animation should be visible:

<div data-bind="loadingWhen: isLoading"></div>

The Custom Binding

Custom bindings in knockout are made up of two functions – init and update – that are called when the binding is first initialised and when its value is updated respectively.  Each function has access to both the element on which it has been set and the current value of the flag, so we have all the information we need.

The binding works by appending a new <div /> element to the container on which it is set when it is initialized.  The new div:

  1. Is absolutely positioned (centered) on the parent
  2. Has a spinner gif as a background image (I recommend the excellent ajaxload to generate something suitable)
  3. Is hidden by default

This div can then be shown/hidden along with the original content when the value of the isLoading flag changes.

Init

In the initialize step we want the create the ‘loader’ div and insert it into the parent element.  To do that we can use some basic jQuery to instantiate the div and set up the required CSS properties:

ko.bindingHandlers.loadingWhen = {
	init: function (element) {
		var
		//cache a reference to the element as we use it multiple times below
		$element = $(element),
		//get the current value of the css 'position' property
		currentPosition = $element.css("position")
		//create the new div with the 'loader' class and hide it
		$loader = $("<div></div>").addClass("loader").hide();

		//add the loader div to the original element
		$element.append($loader);

		//make sure that we can absolutely position the loader against the original element
		if (currentPosition == "auto" || currentPosition == "static")
			$element.css("position", "relative");

		//center the loader
		$loader.css({
			position: "absolute",
			top: "50%",
			left: "50%",
			"margin-left": -($loader.width() / 2) + "px",
			"margin-top": -($loader.height() / 2) + "px"
		});
	}
}

This is all fairly straightforward, though it is worth noting the setting of the CSS position on the original element.  This is because we need to use absolute positioning on the loader div so that it appears centered over the original content, and absolute positioning always works relative to the first ancestor element that does not have static positioning.  We want the loader position relative to the original element, so we need to set it’s position to make sure it is used.
Relative positioning means relative to the elements original position, so we should be able to safely replace either auto or static with relative without any offsets to have it remain in the original location.
The loader itself is given top and left margins of 50% (meaning 50% of the parent element), and then negative margins of half of the loader’s width and height so that it is properly centered.

Update

Next up we need to show and hide the content based on the value of the flag. For this we will add an update method to the binding that handles the hiding and showing using jQuery.

ko.bindingHandlers.loadingWhen = {
	init: function (element) {
		//unchanged
	},
	update: function (element, valueAccessor) {
		var
		//unwrap the value of the flag using knockout utilities
		isLoading = ko.utils.unwrapObservable(valueAccessor()),

		//get a reference to the parent element
		$element = $(element),

		//get a reference to the loader
		$loader = $element.find("div.loader")

		//get a reference to every *other* element
		$childrenToHide = $element.children(":not(div.loader)");

		//if we are currently loading...
		if (isLoading) {
			//...hide and disable the children...
			$childrenToHide.css("visibility", "hidden").attr("disabled", "disabled");
			//...and show the loader
			$loader.show();
		}
		else {
			//otherwise, fade out the loader
			$loader.fadeOut("fast");
			//and re-display and enable the children
			$childrenToHide.css("visibility", "visible").removeAttr("disabled");
		}
	}
};

Things to note here are:

  • We are using visibility: hidden instead of fadeOut as we don’t want to remove the element from the DOM; we just want to hide it.  If we removed it then the size of the parent element might change, which could cause the loader to jump about
  • We are disabling the original content to prevent it responding to any clicks etc whilst hidden

Done

And that’s all there is to it! Knockout handles the notification of changes to the isLoading flag so we should now be able to drop this into our website.

You can see a working example of this here.

Loading MVC PartialViews asynchronously using KnockoutJS

This question on StackOverflow asks (indirectly) how to use KnockoutJS to asynchronously load the result of a PartialView call into a div using jQuery.ajax.

The poster’s original attempt specified a getHtml functionon the view model, and then used the html binding to set the getHtml function. Unfortunately this cannot work as getHtml makes an asynchronous call to the server and so cannot return a value for the knockout binding to use.

One way (of many) that you could work around this would be to create a custom binding as demonstrated here.

This works by taking 2 parameters to the asyncHtml: a function to call that takes a success callback as its final parameter (plus any other parameters) and an array of the parameters that need to be passed to that function.

<div data-bind="asyncHtml: { source: getHtml, params: [123, 456] }"></div>

The custom binding then grabs these values, concats a custom callback onto the parameters that are passed to it, and calls the specified function:

ko.bindingHandlers.asyncHtml = {
    init: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var parameters = value.params.concat([function(data) {
           $(element).html(data); 
        }]);
        value.source.apply(null, parameters);
    }
};

Finally we can re-implement our view model HTML-retrieving method to make the POST call and invoke the new success handler:

var ViewModel = function() {
    this.getHtml = function(param1, param2, callback) {
        $.ajax(
            {
                type: "POST",
                url: "/echo/html/",
                data: {
                        html: "<p>server response " + param1 + " " + param2 + "</p>",
                        delay: 1
                    },
                success: callback,
                dataType: "text"
            });            
    };
};

This approach means we can flexibly call any method on our view model that wants to make an async call and then insert the result back into the view.