<!DOCTYPE html>
<html ng-app="myApp">

  <head>
    <meta charset="utf-8" />
    <!-- Demo styling -->
    <link rel="stylesheet" href="style.css" />

    <!-- Clusterize.js -->
    <link data-require="Clusterize.js@0.16.1" data-semver="0.16.1" rel="stylesheet" href="https://cdn.jsdelivr.net/clusterize.js/0.16.1/clusterize.css" />
    <script data-require="Clusterize.js@0.16.1" data-semver="0.16.1" src="https://cdn.jsdelivr.net/clusterize.js/0.16.1/clusterize.min.js"></script>

    <!-- Angualr.js -->
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.4.9/angular.js" data-semver="1.4.9"></script>

    <!-- **************************************************-->
    <!-- Angular Clusterize Directive-->
    <script src="angularClusterize.js"></script>
    <!-- **************************************************-->

    <script src="service.js"></script>
    <!-- Demo app and controller-->
    <script src="app.js"></script>
  </head>

  <body ng-controller="myApp.Controller as vm">
    <div class="ctrl-wrapper">
      <angular-clusterize on-last-row="vm.fetchPage()" col-names="vm.headers" rows="vm.rows"></angular-clusterize>
    </div>
  </body>
</html>
.clusterize-header table,
.clusterize-scroll table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
}

.clusterize-header th,
.clusterize-scroll td {
  border: 1px solid #aaa;
  padding: 0 2px;
}

.clusterize-header th {
  background-color: #ddd;
}

.clusterize-header {
  overflow-y: hidden;
}

.clusterize-header-hidescroll {
  overflow-y: hidden;
  overflow-x: scroll;
  margin-bottom: -15px;
}

th,
td {
  padding: 0 0.2rem;
  position: relative;
  vertical-align: middle;
}

td {
  border-bottom: 1px solid #ddd;
  border-top: 1px solid #ddd;
}

td:not(:last-child) {
  border-right: 1px solid #ddd;
}

th {
  background-color: #ddd;
  cursor: default;
  height: 1.8rem;
  text-align: center;
}

th:not(:last-child) {
  border-right: 0.1rem solid #aaa;
}

.resize-grip {
  bottom: 0;
  cursor: col-resize;
  position: absolute;
  right: 0;
  top: 0;
  width: 5px;
  z-index: 500;
}

.truncate {
  display: block;
	max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Just some style to make the Clusterize.js table to standout from Plunker */
.ctrl-wrapper {
  border: 1px solid #ccc;
  margin: 1rem;
  max-width:600px;
}
(function () {
  'use strict';

  // App declaration
  angular.module('myApp', ['angularClusterize', 'myService'])
    .controller('myApp.Controller', ['tvshowService', _myAppCtrl]);

  /**
   * Example controller.
   *
   * @param  {Object} tvshowService TV show Service
   */
  function _myAppCtrl(tvshowService) {
    var vm = this;
    var page = 1;

    vm.rows = [];
    vm.headers = ['Title', 'Genre'];

    /**
     * Fetch page from service.
     *
     */
    vm.fetchPage = function () {
      tvshowService.list(page).then(function (res) {
        vm.rows = vm.rows.concat(res.data);

        // Advance page
        page += 1;
      });
    };

    // Fetch first page
    vm.fetchPage();
  }
}());
(function () {
  'use strict';

  // Service declaration
  angular.module('myService', [])
    .service('tvshowService', ['$http', _tvshowService]);

  /**
   * TV show Service
   * @param  {Object} $http Angular $http service
   * @return {Object}       TV show service
   */
  function _tvshowService($http) {
    var _service = {};

    _service.list = function (page) {
      return $http.get('http://api.tvmaze.com/shows?page=' + page);
    };

    return _service;
  }
}());
/* global Clusterize */

/**
 * @preserve
 * Clusterize.js Angular.js directive example
 *
 * https://github.com/Thrilleratplay/Angular-Clusterize-Examples
 *
 * Copyright 2016 Tom Hiller
 * Released under the GPLv3 license:
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */
(function () {
  'use strict';

  // Directive declaration
  angular.module('angularClusterize', [])
    .directive('angularClusterize', ['$compile', '$timeout', '$document', _angularClusterize]);

  /**
   * Angular Clusterize directive.
   *
   * @param   {Object} $compile  AngularJs $compile service.
   * @param   {Object} $timeout  AngularJs $timeout service.
   * @param   {Object} $document AngularJs $document service.
   * @returns {Object}           Angular Clusterize directive.
   */
  function _angularClusterize($compile, $timeout, $document) {
    /**
     * AngularClusterize directive link.
     *
     * @param  {Object} $scope Directive scope.
     * @param  {Object} $elem   Directive element.
     *
     * @returns {undefined}
     */
    function _linkFn($scope, $elem) {
      var MIN_COL_WIDTH_IN_PX = 50;

      // ************************************************************
      // *********************** DOM Elements ***********************
      // ************************************************************
      //  Fixed header - wrapper element
      var headerDiv = $elem[0].querySelector('div.clusterize-header');

      //  Fixed header - scroll wrapper element
      var headerDivScroll = headerDiv.firstElementChild;

      // Fixed header - header TABLE element
      var headerTable = headerDivScroll.firstElementChild;

      // Fixed header - THEAD element
      var headerThead = headerTable.querySelector('.fixed-header');

      // Fixed header - COLGROUP element
      var headerColGrp = headerTable.querySelector('colgroup');

      // Content - wrapper element (Clusterize.js "Content area")
      var contentDiv = $elem[0].querySelector('div.clusterize-scroll');

      // Content - content TABLE element
      var contentTable = contentDiv.firstElementChild;

      // Content - COLGROUP element
      var contentColGrp = contentTable.querySelector('colgroup');

      // Content - TBODY elements (Clusterize.js "Scroll area")
      var contentTbody = contentTable.querySelector('tbody.clusterize-content');

      // ************************************************************
      // ********************** initialization **********************
      // ************************************************************
      $scope.colNames = $scope.colNames || [];
      $scope.rows = $scope.rows || [];

      // Initial Clusterize.js
      $scope.clusterize = new Clusterize({
        rows: _buildRows($scope.rows),
        scrollElem: contentDiv,
        contentElem: contentTbody
      });

      /**
       * Override clusterize html function.
       *
       * @param  {String[]} data Rows to compile.
       *
       * @returns {undefined}
       */
      $scope.clusterize.html = function (data) {
        contentTbody.innerHTML = data;

        // Update rendered rows
        $compile(contentTbody)($scope);

        if (contentTable.offsetWidth && contentTable.style.tableLayout !== 'fixed') {
          $scope.$applyAsync($scope.initialSyncWidths);
        }
      };

      /**
       * Synchronize element withs after row data is first added.  Table layout
       * intially set to auto resize then changed to fixed layout.
       *
       * @returns {undefined}
       */
      $scope.initialSyncWidths = function () {
        var contentRow = contentTbody.querySelectorAll('tr:first-child td');

        angular.forEach(contentRow, function (contentTd, colIndex) {
          if (contentTd.offsetWidth > MIN_COL_WIDTH_IN_PX) {
            headerColGrp.children[colIndex].style.width = contentTd.offsetWidth + 'px';
            contentColGrp.children[colIndex].style.width = contentTd.offsetWidth + 'px';
          } else {
            headerColGrp.children[colIndex].style.width = MIN_COL_WIDTH_IN_PX + 'px';
            contentColGrp.children[colIndex].style.width = MIN_COL_WIDTH_IN_PX + 'px';
          }
        });

        headerTable.style.tableLayout = 'fixed';
        contentTable.style.tableLayout = 'fixed';
        $scope.syncDimensions();
      };

      /**
       * Synchronize width of interdependent elements.
       *
       * @param {number} width Calculated width.
       *
       * @returns {undefined}
       */
      $scope.syncDimensions = function (width) {
        width = width || contentTable.offsetWidth;

        // contentDiv.style.width = width + 'px';
        headerTable.style.width = width + 'px';
        headerThead.style.width = width + 'px';
        contentTable.style.width = width + 'px';
      };

      // ************************************************************
      // ************************* watchers *************************
      // ************************************************************
      // watch for changes and update
      $scope.$watch(function () {
        return {
          rows: $scope.rows,
          colNames: $scope.colNames
        };
      }, function (newVal, oldVal) {
        var diffrowData;
        var scrollWidth;

        // If no data, do nothing
        if (!newVal.rows) {
          return;
        } else if (newVal.colNames && !angular.equals(newVal.colNames, oldVal.colNames)) {
          // If the columns change, rebuild the entire table
          $scope.clusterize.update(_buildRows($scope.rows, $scope.colNames));
          $scope.$applyAsync($scope.clusterize.refresh);
        } else if (newVal.rows.length !== oldVal.rows.length) {
          // If new row data, build new HTML rows and append to clusterize
          diffrowData = newVal.rows.slice(oldVal.rows.length);

          $scope.clusterize.append(_buildRows(diffrowData, $scope.colNames));

          $scope.$applyAsync($scope.clusterize.refresh);
        }

        // Offset for vertical scroll
        scrollWidth = contentDiv.offsetWidth - contentDiv.scrollWidth;
        headerDivScroll.style.marginRight = scrollWidth + 'px';

        // Check if table has been initialized
        if ($scope.rows.length && contentTable.offsetWidth
        && contentTable.style.tableLayout !== 'fixed') {
          $scope.$applyAsync($scope.initialSyncWidths);
        }
      }, true);

      // Link header and content horizontal scrolls
      angular.element(contentDiv).on('scroll', function () {
        headerDivScroll.scrollLeft = contentDiv.scrollLeft;

        if ((contentDiv.scrollTop >= (contentTable.offsetHeight - contentDiv.clientHeight - 1)) &&
            angular.isFunction($scope.onLastRow)) {
          // Fires on last row
          $scope.onLastRow();
        }
      });

      angular.element(headerDiv).on('scroll', function () {
        contentDiv.scrollLeft = headerDivScroll.scrollLeft;
      });

      // ************************************************************

      $timeout(function () {
        var scrollbarHeight = headerDivScroll.scrollHeight - headerDivScroll.offsetHeight;

        // HACK: hide header scrollbar.  Only webkit allows to hide scrollbar via CSS.
        headerDivScroll.style.marginBottom = scrollbarHeight + 'px';

        // HACK: This is needed to correctly bind angular when first loaded
        $compile(contentTbody)($scope);
      });

      // ************************************************************
      // ******************* Column resize functions ******************
      // ************************************************************
      /**
       * User triggered resize column function.
       *
       * @param {Object}  mousedownEvent  DOM event.
       * @param {number} colNum           Column number to find corresponding faux column.
       *
       * @returns {undefined}
       */
      $scope.resizeCol = function (mousedownEvent, colNum) {
        // Current header TH element
        var selectedTh = headerThead.firstElementChild.children[colNum];

        // Current content and header COLs
        var headerCol = headerColGrp.children[colNum];
        var contentCol = contentColGrp.children[colNum];

        // Initial width of content table
        var startTableWidth = contentTable.offsetWidth;

        // Initial width of column
        var startWidth = selectedTh.offsetWidth;

        // Initial offset
        var startXOffset = mousedownEvent.pageX;

        // Handle mouse up event and remove bidings
        var mouseupCol = function () {
          $document.off('mousemove', mousemoveCol);
          $document.off('mouseup', mouseupCol);
        };

        // Resize column based on mouse movment
        var mousemoveCol = function (mousemoveEvent) {
          var diff = mousemoveEvent.pageX - startXOffset;
          var tableWidth = startTableWidth + diff;
          var newWidth = startWidth + diff;

          if (selectedTh && newWidth >= MIN_COL_WIDTH_IN_PX) {
            headerTable.style.width = tableWidth + 'px';
            contentTable.style.width = headerTable.offsetWidth + 'px';
            selectedTh.style.width = newWidth + 'px';
            headerCol.style.width = newWidth + 'px';
            contentCol.style.width = newWidth + 'px';

            $scope.syncDimensions(tableWidth);
          }
        };

        // Bind mousemove and mouse up events to document
        $document.on('mousemove', mousemoveCol);
        $document.on('mouseup', mouseupCol);
      };
    }

    // ***********************************************************************

    /**
     * Create table rows from $scope.rows array.
     *
     * @param  {String[]} rows Raw row data.
     * @returns {String[]}      Table rows.
     */
    function _buildRows(rows) {
      return rows.map(function (val) {
        return ['<tr>',
                '<td>',
                '<span class="truncate">',
                val.name,
                '</span>',
                '<div class="resize-grip" ng-mousedown="resizeCol($event, 0)">&nbsp;</div>',
                '</td>',
                '<td>',
                '<span class="truncate">',
                val.genres.join(', '),
                '</span>',
                '<div class="resize-grip" ng-mousedown="resizeCol($event, 1)">&nbsp;</div>',
                '</td>',
                '</tr>'].join('');
      });
    }

    // ***********************************************************************

    return {
      restrict: 'E',
      scope: {
        rows: '=',
        colNames: '=',
        onLastRow: '&?'
      },
      template: [
        '<div class="clusterize-header">',
        '  <div class="clusterize-header-hidescroll">',
        '    <table>',
        '      <colgroup>',
        '        <col ng-repeat="colName in colNames" style="width:200px">',
        '      </colgroup>',
        '      <thead class="fixed-header">',
        '        <tr>',
        '          <th ng-repeat="colName in colNames">',
        '            <span class="truncate">{{ colName }}</span>',
        '           <div class="resize-grip" ng-mousedown="resizeCol($event, $index)">&nbsp;</div>',
        '          </th>',
        '        </tr>',
        '      </thead>',
        '    </table>',
        '  </div>',
        '</div>',
        '<div class="clusterize-scroll">',
        '  <table>',
        '    <colgroup>',
        '      <col ng-repeat="colName in colNames" style="width:200px">',
        '    </colgroup>',
        '    <tbody class="clusterize-content">',
        '      <tr class="clusterize-no-data">',
        '        <td colspan=2>Loading…</td>',
        '     </tr>',
        '    </tbody>',
        '  </table>',
        '</div>',
        '</div>'
      ].join(''),
      link: _linkFn
    };
  }
}());