Exporting data from a filtered smart-table

After building a smart table in the a previous post, the next task was to make it so all the data on display was exportable to a CSV file so our customers could tinker with their own graphs, pivot tables, and so on. In this post I will introduce the angular plugin that made this possible and a directive that made it pretty.

This will be quick. Googled “angular export csv” and this github repowas the second result.It’s pretty straight forward. Add the script file, inject the module, and slap the directive on a button. Here’s what mine looks like:

Screen<em>Shot</em>2015-02-05<em>at</em>12<em>26</em>37_PM

Next, we want to flatten every object (in a long array of objects) so that every value, however deep, has its own column in the eventual CSV. Here is where I had to create a directive that takes the array, and creates a copy with flat objects, and serves it to the ng-csv button.

As you can see in the example below, ng-csv will accept an expression or function as long as it returns an array.

Screen Shot 2015-02-05 at 12.35.55 PMHere’s my directive, which only has to sit somewhere in the DOM above ng-csv in the hierarchy.

.directive('exportTable', ['$filter', function($filter) { return { scope: { exportTable: '=' }, templateUrl: 'exportTable.html', link: function (scope, element, attr, ctrl) { var flattenObject = function(ob) { var toReturn = {}; for (var i in ob) { if (!ob.hasOwnProperty(i)) continue; if ((typeof ob[i]) 'object') { var flatObject = flattenObject(ob[i]); for (var x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + '' + x] = flatObject[x]; } } else if (i.indexOf('type') -1) { toReturn[i] = ob[i]; } } return toReturn; }; scope.sync = function() { scope.prettified = []; var header = {}; angular.forEach(scope.exportTable, function(object) { var flat = flattenObject(object); angular.forEach(flat, function(value, key) { if (!header[key]) header[key] = key; }); }); var array = []; angular.forEach(header, function(object, key) { if (key.indexOf('.') -1 && key.indexOf('$') -1) array.push(key); }); array = $filter('orderBy')(array, 'toString()'); scope.prettified.push(array); angular.forEach(scope.exportTable, function(object) { var a = []; var flat = flattenObject(object); angular.forEach(array, function(value) { if (!flat[value]) a.push('N/A'); else a.push(flat[value]); }); scope.prettified.push(a); }); return scope.prettified; }; } }; }]);

It doesn’t feel optimized but it does a complicated set of tasks:

  1. In case not all objects are the same, we have to loop through them all to get every possible property.
  2. Inside that loop, we need to flatten every object so that nested properties hit the top. Such as, the property “firstname” inside the property “person” now becomes “person_firstname”.
  3. After getting all the possible top level keys, these are the column headers for our CSV. These we have to sort alphabetically into an array. Then, that array has to be the first in an array of rows.
  4. Then, we have to loop through all the flattened objects again, creating a row of data in the proper order. If the property does not exist for any object, then write“N/A” for the field.

Thenreturn the prettified array of rows. Nothing to it.

Here is a plunker to demonstrate it.