User experience, that’s the name of the game these days. Green screens and console applications are (hopefully) becoming a thing of the past. In the web world, this means a more fluid and asynchronous experience. In my last post, I gave a quick introduction to using jQuery and MVC to perform asynchronous requests to the browser. In this post, I will expand on that topic and show how to build a favorite image icon.
The concept is pretty simple. For example, in the Twitter web UI, an empty star image appears on the right side of a tweet if you mouse over it.If the star is clicked, the icon changes to a flashing dot, and then to a yellow star indicating that the tweet has been favorited. It’s a neat, asynchronous effect, and it’s fairly simple to achieve.
First, we need three images. Here are the images that Twitter uses:
Next, a CSS style is defined for the off, on, and loading states. The purpose of each style is to allow style switching to not only change the picture, but also to allow the state to be determined through the style class assigned.
.favimgoff, .favimgon, .imgload { width: 16px; height: 16px; border: 0px; background-repeat: no-repeat; }
.favimgoff { background-image: url(images/icon_star_empty.gif); }
.favimgon { background-image: url(images/icon_star_full.gif); }
.imgload { background-image: url(images/icon_throbber.gif); }
On the MVC side, an action is needed to handle the server processing.
public JsonResult SetOrRemoveUserFavorite(int favoriteId)
{
JsonResult result = new JsonResult();
// do work here, and set SetOn to the proper state
result.Data = new { Success = true, SetOn = true };
return result;
}
Next, on the client side, add an image tag with the class attribute and id set. The image is set to a one pixel transparent gif otherwise certain browsers may not render the background image.
<img class="favimgoff" src="<%= Url.Content("~/Content/images/spacer.gif")%>" id="1" />
Finally, jQuery on the client side takes care of the tying it all together:
$('.favimgoff, .favimgon').click(function() {
var $favimg = $(this);
if ($favimg.attr('class') != 'imgload') {
$favimg.removeClass().addClass('imgload');
$.getJSON('<%= Url.Action("SetOrRemoveUserFavorite", "Services") %>', {
favoriteId: $favimg.attr('id')
}, function(json) {
if (json.Success) {
if (json.SetOn) {
$favimg.removeClass().addClass('favimgon');
} else {
$favimg.removeClass().addClass('favimgoff');
}
}
else {
alert('Favorite could not be set. Please refresh the page and try again.');
}
});
}
});
Let’s step through the jQuery script. First, define the click event handler for both classes, and get a reference to the image clicked:
$('.favimgoff, .favimgon').click(function() {
var $favimg = $(this);
Next, to avoid any duplicate action taken while the process is run, make sure the image isn’t in the loading state. This is necessary because jQuery will still have an event reference to the original image even when the class changes from the original two classes of on or off.
if ($favimg.attr('class') != 'imgload') {
Next, remove any existing class and add the loading class so the user gets feedback that their action is being processed:
$favimg.removeClass().addClass('imgload');
Then, make a server call to the action defined, and pass the id of the item as a parameter to the action:
$.getJSON('<%= Url.Action("SetOrRemoveUserFavorite", "Services") %>', {
favoriteId: $favimg.attr('id')
}, function(json) {
if (json.Success) {
Finally, based on the SetOn value returned from the server call, remove the loading class and set the appropriate class:
if (json.SetOn) {
$favimg.removeClass().addClass('favimgon');
} else {
$favimg.removeClass().addClass('favimgoff');
}
If by chance the server call fails and Success is false, alert the user:
alert('Favorite could not be set. Please refresh the page and try again.');
That’s it. Utilizing the baked-in goodness of jQuery and ASP.NET MVC, providing a rich, asynchronous user experience doesn’t have to be complex.