A Functional CellSet Pivot Table Using AngularJS and jQuery

Saturday, April 06, 2013

I've take my previous Javascript experiment one step further; up to the point where it provides a functional CellSet pivot table and a hierarchy selector. I'm now learning AngularJS, and I like its MVC approach, so I'm using it for this experiment.

Sources, Live Demo and Maven Repository

You can find the sources for this experiment in my GitHub olap4j-js repository. Sources for olap4j-query are also in GitHub: olap4j-query; but I've set up a maven repository to host a olap4j-query 0.0.1 release version, used in this experiment. The required Maven snippet to use this repository is:

<repositories>
  ...
  <repository>
      <id>desarrollo-agil</id>
      <url>http://demo.desarrolloagil.es/nexus/content/repositories/releases/</url>
  </repository>
  </repositories>

You can experiment with the result at: http://demo.desarrolloagil.es/olap4j-js/

Solution Design

These are the components I've created for this project:

Client Side
  1. jQuery object to display a cellset, change the current cellset. Delegates drill/undrill to user-provided functions, responsible of transforming the query and updating the resulting cellset.
  2. AngularJS directive, uses the above control to display the cellset in a scope variable, watches that variable so that any change on that variable updates the table. Drill/undrill operations are delegated to equivalent operations provided in the cellset object.
  3. The AngularJS controller for the page executes the intitial query and the drill/undrill operations, updating the scope query variable (so triggering a cellset view update). It delegates server requests to a a service interface responsible for executing the REST requests to de server and hydrating the JSON query response into a full blown Javascript object graph.
Server side
  1. A JAX-RS (Jersey) resource providing query execution and transformation services. Stores the current query in the session scope and uses my olap4j-query library to implement the services.
  2. A JSON serializer for CellSet objects.

Implementation Details

You can find the details for the jQuery control in my previous post: An Experiment: Using Javascript to Display CellSet Data

Directive

Creates a jQuery cellset table control at compile time and, at link time, sets a watch on the query scope variable to call the controll's setData() method every time the query gets updated. The link function also sets the table handlers to call drill/undrill methods defined in the query. This the source for this directive

.directive('olapCellset', function () {
  var table;

  function link(scope, iElement, iAttrs) {

    function createHandler(opName) {
      return function (table, axis, position) {
               var query = scope.$eval(iAttrs.olapCellset);
               query[opName](axis, position);
             };
    }

    scope.$watch(iAttrs.olapCellset, function (newValue) {
      if (newValue) {
        table.setData(newValue);
      }
    });

    table.setDrillHandlers(createHandler('drill'), createHandler('undrill'));
  }

  return {
    restrict: 'A',
    compile: function (tElem) {
               table = new CellSetTable(tElem, CellSetRowsAxis, CellSetColsAxis);
               return link;
    }
  };
});
Controller

The controller executes the initial hierarchy list and query execution requests, provides the drill/undrill methods that will be ultimately called by the cellset table and provides operations to add/remove hierarchies.

All these server calls are wrapped with code that will set to true a queryInProgress scope variable if the call takes more than 500ms to finish.

View

Uses the CellSet directive to place the table and bind it to the proper controller scope variable. Draws the hierarchy list from the hierarchies scope property in the controller and sets the hierarchy list buttons to call the add/remove hierarchy methods in the controller.

Creates a busy spinner overlay and binds it visible CSS property to the queryInProgress scope property. The following HTML snippet shows the structure for the view

<body data-ng-controller="QueryCtrl">
<div data-olap-cellset='query'></div>

<div class="hierarchyList">
  <div>
    <h3>Row Hierarchies</h3>
    <ul>
      <li data-ng-repeat="hierarchy in query.axes[1].hierarchies">
        <div>
          <button type="button"
                  data-ng-click="addHierarchy(0, hierarchy.caption)">Cols</button>
          {{hierarchy.caption}}
          <button type="button" class="remove"
                  data-ng-click="removeHierarchy(1, hierarchy.caption)">×
          </button>
        </div>
      </li>
    </ul>
  </div>
  <div>
    <h3>Column Hierarchies</h3>
    ...
  </div>
  <div>
    <h3>Unused Hierarchies</h3>
    <ul>
      <li data-ng-repeat="hierarchy in hierarchies|filter:notUsedHierarchy">
        <div>
          ...
        </div>
      </li>
    </ul>
  </div>
</div>
<div class="modal-background" data-ng-show="queryInProgress">
</div>
</body>