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

Initialising DataTables with Twitter Bootstrap and Spring MVC Ajax

In the previous post I introduced handling Ajax requests from DataTables using Spring MVC.
Spring MVC with restful DataTables

This is only one side of the story though as I left out how I was doing the initialisation on the client side. I’m using DataTables with twitter bootstrap so my initialisation is doing two things, one setting up communication with the back end and two adding twitter bootstrap styling.

Continuing with the customer grid example this is my grid.

<table class="table table-striped table-bordered" id="customers">
  <thead>
    <tr> <th>ID</th> <th>Name</th> </tr>
  </thead>
  <tbody></tbody>
</table>
<script type="text/javascript">
	$(document).ready(function() {
	    $('#customers').dataTable( {
	    	"sAjaxSource": "/Root/customers/get",
	    	"bProcessing": true,
	    	"bServerSide": true,
	    	"sPaginationType": "bootstrap",
	    	"oLanguage": {"sLengthMenu": "_MENU_ records per page",
	    				  "sInfo": "Displaying _START_ to _END_ of _TOTAL_ records"},
	        "aoColumns":[
				{"mDataProp":"id"},
				{"mDataProp":"name"}
				]
	    } );
	} );	
</script>

The first three properties set up the Ajax processing, pagination type gives us bootstrap style paging buttons and oLanguage allows us to change the standard labels. When it came to mapping the columns I had to use the aoColumns property and setting the mDataProp value. In most of the examples online you’ll see a PHP script returning the JSON as an array but since we’re using Jackson we’re returning an object. The difference is this.

Array

{
  "aaData": [
    [
      "1",
      "Ben Thurley"
    ],
    [
      "2",
      "Another Person"
    ]
  ]
}

Object

{
  "aaData": [
    {
      "id" : "1",
      "name" : "Ben Thurley"
    },
    {
      "id" : "2",
      "name" : "Another Person"
    }
  ]
}

If you’re returning an array then you give DataTables the index of the property that matches the column, as we’re returning objects we have to give the name of the property that matches the column. We do this by setting the mDataProp property.

As an added bonus DataTables sends back these mDataProp values on the Ajax request which allows us to convert the sort column index into a meaningful column name.

The rest of the twitter bootstrap styling steps can be found on the DataTables site.
http://datatables.net/blog/Twitter_Bootstrap_2

It’s somewhat work in progress but I have also worked this into a JSP tag file so I don’t need to keep writing the same stuff in my JSP’s. It should in theory make it easy to change the tale plugin if required at a later date. I just added the following two tag files into WebRoot/WEB-INF/tags/table.

table.tag

<%@ tag language="java" %>
<%@ attribute name="id" required="true" %>
<%@ attribute name="datasource" required="true" rtexprvalue="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<c:set var="org_languagetool_tags_table_outputmode" value="TABLE" scope="request" />
	<table class="table table-striped table-bordered" id="${id}">
	  <thead>
	    <tr><jsp:doBody /></tr>
	  </thead>
	  <tbody></tbody>
	</table>
<c:set var="org_languagetool_tags_table_outputmode" value="SCRIPT" scope="request" />
<c:set var="org_languagetool_tags_table_firstcolumn" value="TRUE" scope="request" />
	<script type="text/javascript">
		$(document).ready(function() {
		    $('#${id}').dataTable( {
		    	"sAjaxSource": "<c:url value="${datasource}" />",
		    	"bProcessing": true,
		    	"bServerSide": true,
		    	"sPaginationType": "bootstrap",
		    	"oLanguage": {"sLengthMenu": "<spring:message code="table.records.count" />",
		    				  "sInfo": "<spring:message code="table.showing.records" />"},
		        "aoColumns":[
		                   	<jsp:doBody />
		                   	]
		    } );
		} );	
	</script>	

column.tag

<%@ tag language="java" %>
<%@ attribute name="field" required="true" %>
<%@ attribute name="label" required="true" rtexprvalue="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<c:choose>
  <c:when test="${org_languagetool_tags_table_outputmode == 'TABLE'}">
    <th><spring:message code="${label}" /></th>
  </c:when>
  <c:when test="${org_languagetool_tags_table_outputmode == 'SCRIPT'}">
    <c:choose>
      <c:when test="${org_languagetool_tags_table_firstcolumn == 'TRUE' }">
        <c:set var="org_languagetool_tags_table_firstcolumn" value="FALSE" scope="request" />
      </c:when>
      <c:otherwise>,</c:otherwise>
    </c:choose>
    {"mDataProp":"${field}"}
  </c:when>
</c:choose>

It’s not required unless you’re putting these in a jar but I also wrote out a tag library descriptor.

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
  <tlib-version>1.0</tlib-version>
  <short-name>Table</short-name>
  <uri>http://bthurley.org/tags/table</uri>
  
  <tag-file>
    <name>table</name>
    <path>/WEB-INF/tags/table/table.tag</path>
  </tag-file>  
  
  <tag-file>
    <name>column</name>
    <path>/WEB-INF/tags/table/column.tag</path>
  </tag-file>
 
</taglib>

By using a tld I’ve been able to specify a uri for importing and using the tag. Otherwise you can give the path to import the tag.

<%@ taglib prefix="tab" uri="http://bthurley.org/tags/table" %>
<tab:table id="customers" datasource="/customers/get" >
  <tab:column field="id" label="customers.field.id" />
  <tab:column field="name" label="customers.field.name" />
</tab:table>

I guess I’ll be adding to these tags as time goes on and I think of new things.

Spring MVC with restful DataTables

Up until recently I’ve been using jqGrid for the app I’m working on but I wanted to re-work the UI to make use of twitter bootstrap. I’m not a graphic designer and TB certainly gives you a polished looking app with minimal effort. After some research it seemed that jQuerys DataTables plugin had better UI support for TB than jqGrid. jqGrid is reliant on jQuery UI for its look and feel which as of yet doesn’t have a complete TB theme (though there is work in progress).

If you want to know how to get DataTables displaying in a TB style then the main website has a good walkthrough. It’s really not too difficult. http://datatables.net/blog/Twitter_Bootstrap_2

What was slightly trickier was processing the AJAX requests using Spring MVC. Most of the examples on the site are with PHP where it is quite normal to manually retrieve request parameters. In contrast with Spring MVC the aim is to have a POJO controller and map each parameter on the request to a method parameter. Unfortunately when it comes to sort columns DataTables passes a number to tell you how many columns to sort on and then each column is on the request with an index as part of the parameter name.

You end up needing this kind of thing.

$sOrder = "";
if ( isset( $_GET['iSortCol_0'] ) )
{
  $sOrder = "ORDER BY ";
  for ( $i=0 ; $i<intval( $_GET['iSortingCols'] ) ; $i++ )
  {
    if ( $_GET[ 'bSortable_'.intval($_GET['iSortCol_'.$i]) ] == "true" )
    {
      $sOrder .= "`".$aColumns[ intval( $_GET['iSortCol_'.$i] ) ]."` ". mysql_real_escape_string($_GET['sSortDir_'.$i] ) .", ";
    }
  }
  $sOrder = substr_replace( $sOrder, "", -2 );
  if ( $sOrder == "ORDER BY" )
  {
    $sOrder = "";
  }
}

Now you could do something similar by passing the request object into the method and let your method retrieve the parameters manually but this defeats the object a bit and makes unit testing harder. Instead I decided to create a class to model the DataTables parameters and get Spring to populate it automatically. It’s then possible to pass this single object down through the layers to the place that needs to do the DB query. This makes the parameter lists much shorter and easier to maintain since you can simply add an attribute to the containing object rather than having to visit every method signature.

My object to hold the basic paging criteria is a simple POJO. I’ve made it immutable because you shouldn’t be able to override the inputs that the user has set.

public final class PagingCriteria
{
  private final Integer displayStart;
  private final Integer displaySize;
  private final List<SortField> sortFields;
  private final Integer pageNumber;

  public PagingCriteria(Integer displayStart, Integer displaySize, Integer pageNumber, List<SortField> sortFields)
  {
    this.displayStart = displayStart;
    this.displaySize = displaySize;
    this.pageNumber = pageNumber;
    this.sortFields = sortFields;
  }

  public Integer getDisplayStart() {
    return displayStart;
  }

  public Integer getDisplaySize() {
    return displaySize;
  }

  public List<SortField> getSortFields()
  {
    return Collections.unmodifiableList(sortFields);
  }

  public Integer getPageNumber()
  {
    return pageNumber;
  }

}

I’ve also extracted the concept of a sort field and direction into a further class with an enum representing sort directions.

public final class SortField 
{
  private final String field;
  private final SortDirection direction;
	
  public SortField(String field, SortDirection direction)
  {
    this.field = field;
    this.direction = direction;
  }
	
  public SortField(String field, String direction)
  {
    this.field = field;
    this.direction = SortDirection.valueOfCaseInsensitive(direction);
  }

  public String getField() {
    return field;
  }

  public SortDirection getDirection() {
    return direction;
  }

}
public enum SortDirection 
{
  ASC("asc"), DESC("desc");
	
  private String direction;
  private static final Logger logger = LoggerFactory.getLogger(SortDirection.class);
	
  private SortDirection(String direction)
  {
    this.direction = direction;
  }
	
  public String getDirection()
  {
    return this.direction;
  }
	
  public static SortDirection valueOfCaseInsensitive(String value)
  {
    String valueUpper = value.toUpperCase();
    SortDirection sd = SortDirection.valueOf(valueUpper);
    logger.debug("SortDirection converted "+value+" to "+sd);
    return sd;
  }
}

The PagingCriteria object is passed into my Spring MVC controller something like this.

@Controller
@RequestMapping(value="customers")
public class CustomerController
{

  @Autowired
  private CustomerService customerService;

  @RequestMapping(value="/get", method=RequestMethod.GET)
  public @ResponseBody WebResultSet<Customer> getCustomers(@TableParam PagingCriteria criteria)
  {
    ResultSet<Customer> customers = this.customerService.getCustomers(criteria);
    return ControllerUtils.getWebResultSet(criteria, customers);
  }
...

Ignore the return type for the moment, we’ll come back to that. This is mapping all GET requests to /customers/get to the getCustomers method. It takes in a single parameter for the paging criteria which has been annotated with @TableParam. This is a custom annotation created for populating PagingCriteria with parameters on the request. The annotation class file itself is pretty straightforward.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableParam
{

}

So it’s just a simple marker. We’re using it to annotate method PARAMETER’s and we want to retain this annotation at RUNTIME. Next we need a class that can resolve this annotation and return our PagingCriteria object.

public class TableParamArgumentResolver implements WebArgumentResolver
{

  private static final String S_ECHO = "sEcho";
  private static final String I_DISPLAY_START = "iDisplayStart";
  private static final String I_DISPLAY_LENGTH = "iDisplayLength";
  private static final String I_SORTING_COLS = "iSortingCols";

  private static final String I_SORT_COLS = "iSortCol_";
  private static final String S_SORT_DIR = "sSortDir_";
  private static final String S_DATA_PROP = "mDataProp_";

  public Object resolveArgument(MethodParameter param, NativeWebRequest request) throws Exception
  {
    TableParam tableParamAnnotation = param.getParameterAnnotation(TableParam.class);

    if (tableParamAnnotation!=null)
    {
      HttpServletRequest httpRequest = (HttpServletRequest) request.getNativeRequest();

      String sEcho = httpRequest.getParameter(S_ECHO);
      String sDisplayStart = httpRequest.getParameter(I_DISPLAY_START);
      String sDisplayLength = httpRequest.getParameter(I_DISPLAY_LENGTH);
      String sSortingCols = httpRequest.getParameter(I_SORTING_COLS);

      Integer iEcho = Integer.parseInt(sEcho);
      Integer iDisplayStart = Integer.parseInt(sDisplayStart);
      Integer iDisplayLength = Integer.parseInt(sDisplayLength);
      Integer iSortingCols = Integer.parseInt(sSortingCols);

      List sortFields = new ArrayList();
      for(int colCount=0; colCount<iSortingCols; colCount++)
      {
        String sSortCol = httpRequest.getParameter(I_SORT_COLS+colCount);
        String sSortDir = httpRequest.getParameter(S_SORT_DIR+colCount);
        String sColName = httpRequest.getParameter(S_DATA_PROP+sSortCol);
        sortFields.add(new SortField(sColName, sSortDir));
      }

      PagingCriteria pc = new PagingCriteria(iDisplayStart, iDisplayLength, iEcho, sortFields);

      return pc;
    }

    return WebArgumentResolver.UNRESOLVED;
  }
}

All we do is implement WebArgumentResolver which has a single method resolveArgument. One of the parameters to this method is the request which allows us to retrieve the named request parameters we need. DataTables just passes a column index for sorting but we can use this index to lookup the column name too if mDataProp is used to initialise DataTables.

It’s very important to check first that the annotation being processed is our TableParam and if not to return WebArgumentResolver.UNRESOLVED. The first time through Spring calls each resolver in turn until it finds one that handles that annotation. This is cached and from then on it will always call that resolver for that annotation.

All that’s left now is to register our resolver with spring in the servlet context. This is much easier in Spring 3.1 because of the extra parameters you can now pass to mvc:annotation-driven.

  <mvc:annotation-driven>
    <mvc:argument-resolvers>
      <bean class="org.languagetool.web.utils.TableParamArgumentResolver" />
    </mvc:argument-resolvers>
  </mvc:annotation-driven>

That’s pretty much it. Now when a request comes in to getCustomers(), Spring  sees the @TableParam annotation and calls our custom resolver. This wings around the request picking up all the DataTables parameters and puts them in a PagingCriteria which it then returns. The great thing is once you’ve set this up you can easily reuse it throughout your controllers.

But what about the return type on the controller? If we take another look at the controller method.

@RequestMapping(value="/get", method=RequestMethod.GET)
public @ResponseBody WebResultSet<Customer> getCustomers(@TableParam PagingCriteria criteria)
{
  ResultSet<Customer> customers = this.customerService.getCustomers(criteria);
  return ControllerUtils.getWebResultSet(criteria, customers);
}

There are a couple of things here. The service call is returning a ResultSet object but the controller returns a WebResultSet. This has been annotated with @ResponseBody so if the request object has a request type of JSON then this object will be automatically parsed and returned as JSON. Likewise a content type of XML would return XML assuming the correct dependencies are on the classpath (Jackson and JAXB respectively).

My services are returning a ResultSet object which is similar to PagingCriteria in that it simply wraps the returning data. It’s basically a collection but with extra bits needed to display in the grid such as a record count. The ResultSet holds a collection of DomainObject’s, this is my layer super type for JPA entities which I will be posting about at a later date.

public final class ResultSet<T extends DomainObject>
{
  private final List<T> rows;
  private final Long totalDisplayRecords;
  private final Long totalRecords;

  public ResultSet(List<T> rows, Long totalRecords, Long totalDisplayRecords)
  {
    this.rows = rows;
    this.totalRecords = totalRecords;
    this.totalDisplayRecords = totalDisplayRecords;
  }

  public List<T> getRows()
  {
    return Collections.unmodifiableList(rows);
  }

  public Long getTotalDisplayRecords()
  {
    return totalDisplayRecords;
  }

  public Long getTotalRecords()
  {
    return totalRecords;
  }
}

DataTables itself though will be looking for these parameters to have specific names. It also must have the sEcho parameter from the request sent back. This is a “draw count” which datatables increments each time it makes a request. (Thanks to Sean in the comments for explaining this) Luckily we retrieved sEcho behind the scenes into our PagingCriteria so we use the static ControllerUtils.getWebResultSet() method to build us a DataTables friendly resultset.

public static <T extends DomainObject> WebResultSet<T> getWebResultSet(PagingCriteria pc, ResultSet<T> rs)
{
  return new DataTablesResultSet<T>(pc, rs);
}
public class DataTablesResultSet<T extends DomainObject>  implements WebResultSet<T>
{
  private final Integer sEcho;
  private final Long iTotalRecords;
  private final Long iTotalDisplayRecords;
  private final List<T> aaData;

  public DataTablesResultSet(PagingCriteria pc, ResultSet<T> rs)
  {
    this.sEcho = pc.getPageNumber();
    this.aaData = rs.getRows();
    this.iTotalRecords = rs.getTotalRecords();
    this.iTotalDisplayRecords = rs.getTotalRecords();
  }

  public Integer getsEcho() {
    return sEcho;
  }

  public Long getiTotalRecords() {
    return iTotalRecords;
  }

  public Long getiTotalDisplayRecords() {
    return iTotalDisplayRecords;
  }

  public List<T> getAaData() {
    return Collections.unmodifiableList(aaData);
  }
}

So WebResultSet is simply a marker interface that’s used as a return type on the controllers. We then determine in ControllerUtils.getWebResultSet that it’s a DataTablesResultSet we want. In theory we can now tweak both the inputs and outputs from DataTables or any other grid we choose without having to change the controllers or services.  We’ve essentially removed the controllers dependency on the JavaScript grid implementation.

The next step is to wrap the view layer in custom JSP tags