Monday, May 20, 2013

Arbitrary cell formatting in YUI3 DataTable

YUI is an underrated JavaScript framework, in my opinion. I suspect it isn't very popular because it had dependencies on Flash as late as 2011, and because there were major API changes between YUI2 and YUI3, resulting in a split community and a paucity of documentation and forum help for YUI3. But YUI3 is a rich and free collection of rich web front-end components.

An example of weak documentation and examples is custom formatting of DataTable cells. What was the "formatter" property in YUI2 has been split in YUI3 into "formatter" for changing the just cell text and "nodeFormatter" for changing (actually, supplying) the cell text and the cell property. Since when using a nodeFormatter, one must explicitly transfer the contents of the cell data to the cell display, I decided: why not just embed the formatting right into the cell data? Normally, it is expected that you would maintain a separate external array containing the formatting (e.g. an array of Booleans indicating which cells should be highlighted in red), but with the technique below, the formatting information is embedded right with the cell data in the form of a JavaScript/JSON object for each cell.


<!DOCTYPE html>
<head>
 <script src="yui/build/yui/yui-min.js"></script>
 <script>
  var fmt = function(o) {
      if (o.value) {
          if (o.value.value)
              o.cell.set('text', o.value.value);
          if (o.value.classname)
              o.td.setAttribute("class", o.value.classname);
      }
      return false;
  }

  var columns = [{"key":"Part #", "nodeFormatter":fmt},
                 {"key":"Part Name", "nodeFormatter":fmt}];
  var data = [{"Part #":{"value":1234},
               "Part Name":{"value":"Capacitor"}},
              {"Part #":{"value":5678},
               "Part Name":{"value":"Resistor", "classname":"redbackground"}}];

  YUI().use('datatable', function(Y) {
   new Y.DataTable({columns: columns, data: data, render: "#table"});
  });
 </script>
 <style>
  .redbackground { background: red; }
 </style>
</head>
<body>
 <div id="table" class="yui3-skin-sam"></div>
</body>
</html>

UPDATE 2013-05-23: Thanks to Luke Smith's post at the YUI forum, it turns out it is possible to use formatter, which is more performant than nodeFormatter. The trick, though, is using formatter requires a more specific CSS selector. Full code below.

<!DOCTYPE html>
<head>
 <script src="yui/build/yui/yui-min.js"></script>
 <script>
  var fmt = function(o) {
      if (o.value.classname)
          o.className = o.value.classname;
      return o.value.value;
  }

  var columns = [{"key":"Part #", "formatter":fmt},
                 {"key":"Part Name", "formatter":fmt}];
  var data = [{"Part #":{"value":1234},
               "Part Name":{"value":"Capacitor"}},
              {"Part #":{"value":5678},
               "Part Name":{"value":"Resistor", "classname":"redbackground"}}];

  YUI().use('datatable', function(Y) {
   new Y.DataTable({columns: columns, data: data, render: "#table"});
  });
 </script>
 <style>
  #table .redbackground { background: red; }
 </style>
</head>
<body>
 <div id="table" class="yui3-skin-sam"></div>
</body>
</html>

2 comments:

Unknown said...

As noted in the YUI forums, formatters (not nodeFormatters) support an o.className property which allow you to add classes to the TDs conditionally.

The reason you want to avoid nodeFormatter is noted in the user guide, but the short form is this: nodeFormatters cause a Node instance to be created for each cell in that column, which slows down the table rendering significantly. For smaller tables, it probably won't be noticeable, but for bigger tables, it can make a real difference in perceived performance.

Michael Malak said...

I've tried just now, but I'm not seeing the effect on the two browsers I've tried just now, Safari and Chrome, using the code below:

<!DOCTYPE html>
<head>
<script src="yui/build/yui/yui-min.js"></script>
<script>
var fmt = function(o) {
if (o.value.classname)
o.className = o.value.classname;
return o.value.value;
}

var columns = [{"key":"Part #", "formatter":fmt},
{"key":"Part Name", "formatter":fmt}];
var data = [{"Part #":{"value":1234},
"Part Name":{"value":"Capacitor"}},
{"Part #":{"value":5678},
"Part Name":{"value":"Resistor", "classname":"redbackground"}}];

YUI().use('datatable', function(Y) {
new Y.DataTable({columns: columns, data: data, render: "#table"});
});
</script>
<style>
.redbackground { background: red; }
</style>
</head>
<body>
<div id="table" class="yui3-skin-sam"></div>
</body>
</html>