<!DOCTYPE html>
<html>

  <head>
    <link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <script data-require="angular.js@1.2.28" data-semver="1.2.28" src="https://code.angularjs.org/1.2.28/angular.js"></script>
    <script data-require="angular-ui-router@0.2.11" data-semver="0.2.11" src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.11/angular-ui-router.js"></script>
    <script src="smart-table.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-app="app">
    <a ui-sref="tables">Load Tables</a>
    <ui-view></ui-view>
  </body>

</html>
// Code goes here

angular.module('app', [
  'ui.router',
  'smart-table'
])

.config([
  '$stateProvider',
  function($stateProvider) {
    $stateProvider
      .state('index', {
        url: '/',
        templateUrl: 'index.html',
      })
      .state('tables', {
        url: '/tables',
        templateUrl: 'tables.html',
        controllerAs: 'tableCtrl',
        controller: 'Table1Controller'
      })
      .state('tables.table2', {
        url: '/tables/table2',
        templateUrl: 'table2.html',
        controllerAs: 'tableCtrl',
        controller: 'Table2Controller'
      })
  }
])

.controller('Table1Controller', function($scope) {
  $scope.data = JSON.parse('{"posts":[{"id":41,"title":"Into CM with HTML content table 1","publishAt":"2016-01-26T00:39:18.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/into-cm-with-html-content","exclusiveSite":null,"marketplaceCategory":"entertainment"},{"id":19,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":"beauty"},{"id":21,"title":"Recommended to Influencer 1 new","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":23,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":24,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":25,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":26,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":27,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":28,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":29,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":30,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":31,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":32,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":33,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":34,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":35,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":36,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":37,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":38,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":39,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":40,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""}]}')
})

.controller('Table2Controller', function($scope) {
  $scope.data = JSON.parse('{"posts":[{"id":41,"title":"Into CM with HTML content table 2","publishAt":"2016-01-26T00:39:18.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/into-cm-with-html-content","exclusiveSite":null,"marketplaceCategory":"entertainment"},{"id":19,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":"beauty"},{"id":21,"title":"Recommended to Influencer 1 new","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":23,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":24,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":25,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":26,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":27,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":28,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":29,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":30,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":31,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":32,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":33,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":34,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":35,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":36,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":37,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":38,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":39,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""},{"id":40,"title":"Recommended to Influencer 1","publishAt":"2016-01-20T23:51:20.000Z","url":"http://matt-admin.fanbread.io:3000/blogs/recommended-to-influencer-1","exclusiveSite":null,"marketplaceCategory":""}]}')
})
/* Styles go here */

<h1>TABLE 2</h1>
<table class='table table-striped' st-safe-src='data' st-table='dataCopy'>
  <thead>
    <tr>
      <th>Title</th>
      <th>Category</th>
      <th>Publish Date</th>
      <th>Written For</th>
    </tr>
  </thead>
  <tbody>
    <tr class='selectable' ng-repeat='row in data.posts' st-select-mode='multiple' st-select-row='row'>
      <td>
        {{row.title}}
        <a ng-href='{{row.url}}' target='_blank'>
          <fa name='external-link'></fa>
        </a>
      </td>
      <td>{{row.marketplaceCategory}}</td>
      <td>{{row.publishAt | date: 'short'}}</td>
      <td>{{row.exclusiveSite}}</td>
    </tr>
  </tbody>
</table>
/** 
* @version 2.1.6
* @license MIT
*/
(function (ng, undefined){
    'use strict';

ng.module('smart-table', []).run(['$templateCache', function ($templateCache) {
    $templateCache.put('template/smart-table/pagination.html',
        '<nav ng-if="numPages && pages.length >= 2"><ul class="pagination">' +
        '<li ng-repeat="page in pages" ng-class="{active: page==currentPage}"><a ng-click="selectPage(page)">{{page}}</a></li>' +
        '</ul></nav>');
}]);


ng.module('smart-table')
  .constant('stConfig', {
    pagination: {
      template: 'template/smart-table/pagination.html',
      itemsByPage: 10,
      displayedPages: 5
    },
    search: {
      delay: 400, // ms
      inputEvent: 'input'
    },
    select: {
      mode: 'single',
      selectedClass: 'st-selected'
    },
    sort: {
      ascentClass: 'st-sort-ascent',
      descentClass: 'st-sort-descent',
      skipNatural: false,
      delay:300
    },
    pipe: {
      delay: 100 //ms
    }
  });
ng.module('smart-table')
  .controller('stTableController', ['$scope', '$parse', '$filter', '$attrs', function StTableController ($scope, $parse, $filter, $attrs) {
    var propertyName = $attrs.stTable;
    var displayGetter = $parse(propertyName);
    var displaySetter = displayGetter.assign;
    var safeGetter;
    var orderBy = $filter('orderBy');
    var filter = $filter('filter');
    var safeCopy = copyRefs(displayGetter($scope));
    var tableState = {
      sort: {},
      search: {},
      pagination: {
        start: 0,
        totalItemCount: 0
      }
    };
    var filtered;
    var pipeAfterSafeCopy = true;
    var ctrl = this;
    var lastSelected;

    function copyRefs (src) {
      return src ? [].concat(src) : [];
    }

    function updateSafeCopy () {
      safeCopy = copyRefs(safeGetter($scope));
      if (pipeAfterSafeCopy === true) {
        ctrl.pipe();
      }
    }

    function deepDelete (object, path) {
      if (path.indexOf('.') != -1) {
        var partials = path.split('.');
        var key = partials.pop();
        var parentPath = partials.join('.');
        var parentObject = $parse(parentPath)(object)
        delete parentObject[key];
        if (Object.keys(parentObject).length == 0) {
          deepDelete(object, parentPath);
        }
      } else {
        delete object[path];
      }
    }

    if ($attrs.stSafeSrc) {
      safeGetter = $parse($attrs.stSafeSrc);
      $scope.$watch(function () {
        var safeSrc = safeGetter($scope);
        return safeSrc && safeSrc.length ? safeSrc[0] : undefined;
      }, function (newValue, oldValue) {
        if (newValue !== oldValue) {
          updateSafeCopy();
        }
      });
      $scope.$watch(function () {
        var safeSrc = safeGetter($scope);
        return safeSrc ? safeSrc.length : 0;
      }, function (newValue, oldValue) {
        if (newValue !== safeCopy.length) {
          updateSafeCopy();
        }
      });
      $scope.$watch(function () {
        return safeGetter($scope);
      }, function (newValue, oldValue) {
        if (newValue !== oldValue) {
          tableState.pagination.start = 0;
          updateSafeCopy();
        }
      });
    }

    /**
     * sort the rows
     * @param {Function | String} predicate - function or string which will be used as predicate for the sorting
     * @param [reverse] - if you want to reverse the order
     */
    this.sortBy = function sortBy (predicate, reverse) {
      tableState.sort.predicate = predicate;
      tableState.sort.reverse = reverse === true;

      if (ng.isFunction(predicate)) {
        tableState.sort.functionName = predicate.name;
      } else {
        delete tableState.sort.functionName;
      }

      tableState.pagination.start = 0;
      return this.pipe();
    };

    /**
     * search matching rows
     * @param {String} input - the input string
     * @param {String} [predicate] - the property name against you want to check the match, otherwise it will search on all properties
     */
    this.search = function search (input, predicate) {
      var predicateObject = tableState.search.predicateObject || {};
      var prop = predicate ? predicate : '$';

      input = ng.isString(input) ? input.trim() : input;
      $parse(prop).assign(predicateObject, input);
      // to avoid to filter out null value
      if (!input) {
        deepDelete(predicateObject, prop);
      }
      tableState.search.predicateObject = predicateObject;
      tableState.pagination.start = 0;
      return this.pipe();
    };

    /**
     * this will chain the operations of sorting and filtering based on the current table state (sort options, filtering, ect)
     */
    this.pipe = function pipe () {
      var pagination = tableState.pagination;
      var output;
      filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy;
      if (tableState.sort.predicate) {
        filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse);
      }
      pagination.totalItemCount = filtered.length;
      if (pagination.number !== undefined) {
        pagination.numberOfPages = filtered.length > 0 ? Math.ceil(filtered.length / pagination.number) : 1;
        pagination.start = pagination.start >= filtered.length ? (pagination.numberOfPages - 1) * pagination.number : pagination.start;
        output = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number));
      }
      displaySetter($scope, output || filtered);
    };

    /**
     * select a dataRow (it will add the attribute isSelected to the row object)
     * @param {Object} row - the row to select
     * @param {String} [mode] - "single" or "multiple" (multiple by default)
     */
    this.select = function select (row, mode) {
      var rows = copyRefs(displayGetter($scope));
      var index = rows.indexOf(row);
      if (index !== -1) {
        if (mode === 'single') {
          row.isSelected = row.isSelected !== true;
          if (lastSelected) {
            lastSelected.isSelected = false;
          }
          lastSelected = row.isSelected === true ? row : undefined;
        } else {
          rows[index].isSelected = !rows[index].isSelected;
        }
      }
    };

    /**
     * take a slice of the current sorted/filtered collection (pagination)
     *
     * @param {Number} start - start index of the slice
     * @param {Number} number - the number of item in the slice
     */
    this.slice = function splice (start, number) {
      tableState.pagination.start = start;
      tableState.pagination.number = number;
      return this.pipe();
    };

    /**
     * return the current state of the table
     * @returns {{sort: {}, search: {}, pagination: {start: number}}}
     */
    this.tableState = function getTableState () {
      return tableState;
    };

    this.getFilteredCollection = function getFilteredCollection () {
      return filtered || safeCopy;
    };

    /**
     * Use a different filter function than the angular FilterFilter
     * @param filterName the name under which the custom filter is registered
     */
    this.setFilterFunction = function setFilterFunction (filterName) {
      filter = $filter(filterName);
    };

    /**
     * Use a different function than the angular orderBy
     * @param sortFunctionName the name under which the custom order function is registered
     */
    this.setSortFunction = function setSortFunction (sortFunctionName) {
      orderBy = $filter(sortFunctionName);
    };

    /**
     * Usually when the safe copy is updated the pipe function is called.
     * Calling this method will prevent it, which is something required when using a custom pipe function
     */
    this.preventPipeOnWatch = function preventPipe () {
      pipeAfterSafeCopy = false;
    };
  }])
  .directive('stTable', function () {
    return {
      restrict: 'A',
      controller: 'stTableController',
      link: function (scope, element, attr, ctrl) {

        if (attr.stSetFilter) {
          ctrl.setFilterFunction(attr.stSetFilter);
        }

        if (attr.stSetSort) {
          ctrl.setSortFunction(attr.stSetSort);
        }
      }
    };
  });

ng.module('smart-table')
  .directive('stSearch', ['stConfig', '$timeout','$parse', function (stConfig, $timeout, $parse) {
    return {
      require: '^stTable',
      link: function (scope, element, attr, ctrl) {
        var tableCtrl = ctrl;
        var promise = null;
        var throttle = attr.stDelay || stConfig.search.delay;
        var event = attr.stInputEvent || stConfig.search.inputEvent;

        attr.$observe('stSearch', function (newValue, oldValue) {
          var input = element[0].value;
          if (newValue !== oldValue && input) {
            ctrl.tableState().search = {};
            tableCtrl.search(input, newValue);
          }
        });

        //table state -> view
        scope.$watch(function () {
          return ctrl.tableState().search;
        }, function (newValue, oldValue) {
          var predicateExpression = attr.stSearch || '$';
          if (newValue.predicateObject && $parse(predicateExpression)(newValue.predicateObject) !== element[0].value) {
            element[0].value = $parse(predicateExpression)(newValue.predicateObject) || '';
          }
        }, true);

        // view -> table state
        element.bind(event, function (evt) {
          evt = evt.originalEvent || evt;
          if (promise !== null) {
            $timeout.cancel(promise);
          }

          promise = $timeout(function () {
            tableCtrl.search(evt.target.value, attr.stSearch || '');
            promise = null;
          }, throttle);
        });
      }
    };
  }]);

ng.module('smart-table')
  .directive('stSelectRow', ['stConfig', function (stConfig) {
    return {
      restrict: 'A',
      require: '^stTable',
      scope: {
        row: '=stSelectRow'
      },
      link: function (scope, element, attr, ctrl) {
        var mode = attr.stSelectMode || stConfig.select.mode;
        element.bind('click', function () {
          scope.$apply(function () {
            ctrl.select(scope.row, mode);
          });
        });

        scope.$watch('row.isSelected', function (newValue) {
          if (newValue === true) {
            element.addClass(stConfig.select.selectedClass);
          } else {
            element.removeClass(stConfig.select.selectedClass);
          }
        });
      }
    };
  }]);

ng.module('smart-table')
	.directive('stSort', ['stConfig', '$parse', '$timeout', function (stConfig, $parse, $timeout) {
		return {
			restrict: 'A',
			require: '^stTable',
			link: function (scope, element, attr, ctrl) {

				var predicate = attr.stSort;
				var getter = $parse(predicate);
				var index = 0;
				var classAscent = attr.stClassAscent || stConfig.sort.ascentClass;
				var classDescent = attr.stClassDescent || stConfig.sort.descentClass;
				var stateClasses = [classAscent, classDescent];
				var sortDefault;
				var skipNatural = attr.stSkipNatural !== undefined ? attr.stSkipNatural : stConfig.sort.skipNatural;
				var promise = null;
				var throttle = attr.stDelay || stConfig.sort.delay;

				if (attr.stSortDefault) {
					sortDefault = scope.$eval(attr.stSortDefault) !== undefined ? scope.$eval(attr.stSortDefault) : attr.stSortDefault;
				}

				//view --> table state
				function sort () {
					index++;
					var func;
					predicate = ng.isFunction(getter(scope)) || ng.isArray(getter(scope)) ? getter(scope) : attr.stSort;
					if (index % 3 === 0 && !!skipNatural !== true) {
						//manual reset
						index = 0;
						ctrl.tableState().sort = {};
						ctrl.tableState().pagination.start = 0;
						func = ctrl.pipe.bind(ctrl);
					} else {
						func = ctrl.sortBy.bind(ctrl, predicate, index % 2 === 0);
					}
					if (promise !== null) {
						$timeout.cancel(promise);
					}
					if (throttle < 0) {
						func();
					} else {
						promise = $timeout(func, throttle);
					}
				}

				element.bind('click', function sortClick () {
					if (predicate) {
						scope.$apply(sort);
					}
				});

				if (sortDefault) {
					index = sortDefault === 'reverse' ? 1 : 0;
					sort();
				}

				//table state --> view
				scope.$watch(function () {
					return ctrl.tableState().sort;
				}, function (newValue) {
					if (newValue.predicate !== predicate) {
						index = 0;
						element
							.removeClass(classAscent)
							.removeClass(classDescent);
					} else {
						index = newValue.reverse === true ? 2 : 1;
						element
							.removeClass(stateClasses[index % 2])
							.addClass(stateClasses[index - 1]);
					}
				}, true);
			}
		};
	}]);

ng.module('smart-table')
  .directive('stPagination', ['stConfig', function (stConfig) {
    return {
      restrict: 'EA',
      require: '^stTable',
      scope: {
        stItemsByPage: '=?',
        stDisplayedPages: '=?',
        stPageChange: '&'
      },
      templateUrl: function (element, attrs) {
        if (attrs.stTemplate) {
          return attrs.stTemplate;
        }
        return stConfig.pagination.template;
      },
      link: function (scope, element, attrs, ctrl) {

        scope.stItemsByPage = scope.stItemsByPage ? +(scope.stItemsByPage) : stConfig.pagination.itemsByPage;
        scope.stDisplayedPages = scope.stDisplayedPages ? +(scope.stDisplayedPages) : stConfig.pagination.displayedPages;

        scope.currentPage = 1;
        scope.pages = [];

        function redraw () {
          var paginationState = ctrl.tableState().pagination;
          var start = 1;
          var end;
          var i;
          var prevPage = scope.currentPage;
          scope.totalItemCount = paginationState.totalItemCount;
          scope.currentPage = Math.floor(paginationState.start / paginationState.number) + 1;

          start = Math.max(start, scope.currentPage - Math.abs(Math.floor(scope.stDisplayedPages / 2)));
          end = start + scope.stDisplayedPages;

          if (end > paginationState.numberOfPages) {
            end = paginationState.numberOfPages + 1;
            start = Math.max(1, end - scope.stDisplayedPages);
          }

          scope.pages = [];
          scope.numPages = paginationState.numberOfPages;

          for (i = start; i < end; i++) {
            scope.pages.push(i);
          }

          if (prevPage !== scope.currentPage) {
            scope.stPageChange({newPage: scope.currentPage});
          }
        }

        //table state --> view
        scope.$watch(function () {
          return ctrl.tableState().pagination;
        }, redraw, true);

        //scope --> table state  (--> view)
        scope.$watch('stItemsByPage', function (newValue, oldValue) {
          if (newValue !== oldValue) {
            scope.selectPage(1);
          }
        });

        scope.$watch('stDisplayedPages', redraw);

        //view -> table state
        scope.selectPage = function (page) {
          if (page > 0 && page <= scope.numPages) {
            ctrl.slice((page - 1) * scope.stItemsByPage, scope.stItemsByPage);
          }
        };

        if (!ctrl.tableState().pagination.number) {
          ctrl.slice(0, scope.stItemsByPage);
        }
      }
    };
  }]);

ng.module('smart-table')
  .directive('stPipe', ['stConfig', '$timeout', function (config, $timeout) {
    return {
      require: 'stTable',
      scope: {
        stPipe: '='
      },
      link: {

        pre: function (scope, element, attrs, ctrl) {

          var pipePromise = null;

          if (ng.isFunction(scope.stPipe)) {
            ctrl.preventPipeOnWatch();
            ctrl.pipe = function () {

              if (pipePromise !== null) {
                $timeout.cancel(pipePromise)
              }

              pipePromise = $timeout(function () {
                scope.stPipe(ctrl.tableState(), ctrl);
              }, config.pipe.delay);

              return pipePromise;
            }
          }
        },

        post: function (scope, element, attrs, ctrl) {
          ctrl.pipe();
        }
      }
    };
  }]);

})(angular);
<div>
  <a ui-sref="tables">Table 1</a>
  <a ui-sref="tables.table2">Table 2</a>
  
  <ui-view>
    <h1>TABLE 1</h1>
    <table class='table table-striped' st-safe-src='data' st-table='dataCopy'>
      <thead>
        <tr>
          <th>Title</th>
          <th>Category</th>
          <th>Publish Date</th>
          <th>Written For</th>
        </tr>
      </thead>
      <tbody>
        <tr class='selectable' ng-repeat='row in data.posts' st-select-mode='multiple' st-select-row='row'>
          <td>
            {{row.title}}
            <a ng-href='{{row.url}}' target='_blank'>
              <fa name='external-link'></fa>
            </a>
          </td>
          <td>{{row.marketplaceCategory}}</td>
          <td>{{row.publishAt | date: 'short'}}</td>
          <td>{{row.exclusiveSite}}</td>
        </tr>
      </tbody>
    </table>

  </ui-view>
</div>