A first knockout custom binding to display twitter bootstrap alerts


Just had a go at my first knockout custom binding. I wanted to be able to bind an observableArray to a div and for that div to display as a twitter bootstrap alert with each array item on a separate line.

If you want to get TB working with knockout quickly then you may want to check out this library.
http://billpull.github.io/knockout-bootstrap/
I decided not to use it for my alerts because I wanted my errors to be in a single dismissable alert. It would be a pain for the user to have to dismiss all the alerts separately. Also, I’m trying to learn knockout so writing my own is all part of the fun. 🙂

So with that out of the way this is what I came up with.
First in my html I have this very simple markup.

<div data-bind="alertList: errors, priority: 'error'"></div>

I’m using my new binding called alertList to bind an array to this div. The priority option will allow me to choose an error, warning, info or success alert.

This is what the JSON looks like coming back from the server. The main things to note are that it’s an array of errors and each error has a message attribute.

[{"message":"Password may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"User.createUser.arg0.password","invalidValue":null},
{"message":"Language may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"User.createUser.arg0.supportedLanguage","invalidValue":null},
{"message":"Username may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"User.createUser.arg0.username","invalidValue":null}]

In the view model we’ll typically have an ajax request to get a rest resource. If that results in an error then we can populate the observable errors array with the response from the server. The custom binding will then magically render these in the div with the correct styling. This is a stripped down example just to give the idea.

define(["jquery", "knockout", "mapping"], function($, ko, mapping)
{
"use strict";
var FormVM = function FormVM()
{
  var self = this;
  self.errors = ko.observableArray();
  self.submit = function()
  {
    $.ajax( {
      url: "test",
      type: "post",
      error: function(data)
      {
        self.errors(data.responseJSON);
      }
    } );
  };
};
return FormVM;
});

Finally we just need the custom binding to glue it all together.

define(["jquery", "knockout"], function($, ko)
{
"use strict";
/**
* Binds an observable array to a dismissable alert box
*/
ko.bindingHandlers.alertList =
{
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
  var data = valueAccessor();
  var priority = allBindingsAccessor().priority;

  var alertClass = "alert-danger";
  if(priority === "info")
  {
    alertClass = "alert-info";
  }else if(priority === "warning")
  {
    alertClass = "alert-warning";
  }else if(priority === "success")
  {
    alertClass = "alert-success";
  }

  element.style.display = "none";
  element.innerHTML = "<button type=\"button\" data-hide=\"alert\" class=\"close\">x</button>" +
  "<h4 class=\"alert-heading\">Error</h4>";
  element.className = "alert " + alertClass + " alert-dismissable";

  var ul = document.createElement("ul");
  ul.className = "list-unstyled";
  ul.innerHTML = "<li><span data-bind='text: message'></span></li>";
  element.appendChild(ul);

  ko.applyBindingsToNode(ul, { foreach: data });
  return { controlsDescendantBindings: true };
},

update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
  var value = ko.utils.unwrapObservable(valueAccessor());
  if(value.length>0)
  {
    $(element).slideDown();
  } else {
    $(element).fadeOut("fast");
  }
}
};
});

The init function does all of the initial construction. We take the basic div and add in the bootstrap classes we need. We also add a ul element which we use to create a list of errors from the message property of the array items. We also check the priority option to determine the class used to display the alert (e.g. error or warning etc).

The update function just adds in some jQuery animations to make the alert slideDown when you give it some elements and then fade out if you remove the elements.

I also added the following bit of jQuery to make the dismiss button work. The default behavior for a bootstrap dismissable alert is to remove the element from the page but we want to only hide it so we can later display again if we add more errors to the observable. We add a click event to any element with the data-hide attribute to fade out when clicked.

$(document).on("click", "[data-hide]", function()
{
	$(this).closest("." + $(this).attr("data-hide")).fadeOut("fast");
});

Alert Binding

Source is also available as a gist.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s