Smart Table

Get the code

The development of smart-table is sponsored by TropicalDev

Introduction

Smart table is an Angularjs module to easily display data in a table with a set of built in features such filtering,sorting, etc in a declarative way. While developing this module I made sure to focus on these particular points:

  • lightweight: smart-table is less than 4kb minified and has no dependencies other than Angular itself. And you can even reduce its size with a custom build if you are not interested in some of the plugins.
  • robust: smart-table is widely tested which makes the module really stable
  • modular and extensible: The core features can be accessed with the controller API which is created by the top level directive. Then a set of directives (plugins) allow you to compose you table behavior in a declarative way.
  • developer friendly: the design of the module has been thought carefully and it is really easy to get into the source code to modify/customise the module to best fit your needs.

An example of server side driven smart-table used for React CRM

Although smart-table is by far the best table module for angular :D, there are other table modules in the angular ecosystem you might be interested in. The approach and philosophy are different and maybe more appropriate to your way of building web application. Among the most popular:

If you want to play around, try this plunker

clone the repository or install with bower:

bower install angular-smart-table

Or use npm:

npm install angular-smart-table

The basics

The first thing is to add the module angular.module('myApp',['smart-table'] to your angular application. Then use the markup you would normally do for html tables. On the table element add the st-table attribute to tell smart-table which collection holds your displayed data, (ie the one you are going to use in the repeater). Now you will be able to compose the table using the plugins.

<table st-table="rowCollection" class="table table-striped">
	<thead>
	<tr>
		<th>first name</th>
		<th>last name</th>
		<th>birth date</th>
		<th>balance</th>
		<th>email</th>
	</tr>
	</thead>
	<tbody>
	<tr ng-repeat="row in rowCollection">
		<td>{{row.firstName}}</td>
		<td>{{row.lastName}}</td>
		<td>{{row.birthDate}}</td>
		<td>{{row.balance}}</td>
		<td>{{row.email}}</td>
	</tr>
	</tbody>
</table>
app.controller('basicsCtrl', ['$scope', function (scope) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];
}]);
first name last name birth date balance email
{{row.firstName}} {{row.lastName}} {{row.birthDate}} {{row.balance}} {{row.email}}

Of course in this short example, smart-table does not bring much as we don't use any of the plugins but we are now ready to compose our table.

smart-table first creates a safe copy of your displayed collection: it creates an other array by copying the references of the items. It will then modify the displayed collection (when sorting, filtering etc) based on its safe copy. So if you don't intend to modify the collection outside of the table, it will be all fine. However, if you want to modify the collection (add item, remove item), or if you load your data asynchronously (via AJAX-Call, timeout, etc) you will have to tell smart-table to watch the original collection so it can update its safe copy. This is were you use the stSafeSrc attribute

stSafeSrc attribute

If you are bringing in data asynchronously (from a remote database, restful endpoint, ajax call, etc) you must use the stSafeSrc attribute. You must use a seperate collection for both the base and safe collections or you may end up with an infinite loop.

Use it to tell smart-table which collection to watch if you intend to modify its content. Don't pay attention to the sort and filter directives for the moment but use the table and note how the data is correctly synced

	<div ng-controller="safeCtrl">

		<button type="button" ng-click="addRandomItem(row)" class="btn btn-sm btn-success">
			<i class="glyphicon glyphicon-plus">
			</i> Add random item
		</button>

		<table st-table="displayedCollection" st-safe-src="rowCollection" class="table table-striped">
			<thead>
			<tr>
				<th st-sort="firstName">first name</th>
				<th st-sort="lastName">last name</th>
				<th st-sort="birthDate">birth date</th>
				<th st-sort="balance">balance</th>
			</tr>
			<tr>
				<th colspan="5"><input st-search="" class="form-control" placeholder="global search ..." type="text"/></th>
			</tr>
			</thead>
			<tbody>
			<tr ng-repeat="row in displayedCollection">
				<td>{{row.firstName}}</td>
				<td>{{row.lastName}}</td>
				<td>{{row.birthDate}}</td>
				<td>{{row.balance}}</td>
				<td>
				<button type="button" ng-click="removeItem(row)" class="btn btn-sm btn-danger">
					<i class="glyphicon glyphicon-remove-circle">
					</i>
				</button>
				</td>
			</tr>
			</tbody>
		</table>

	</div>
app.controller('safeCtrl', ['$scope', function ($scope) {

    var firstnames = ['Laurent', 'Blandine', 'Olivier', 'Max'];
    var lastnames = ['Renard', 'Faivre', 'Frere', 'Eponge'];
    var dates = ['1987-05-21', '1987-04-25', '1955-08-27', '1966-06-06'];
    var id = 1;

    function generateRandomItem(id) {

        var firstname = firstnames[Math.floor(Math.random() * 3)];
        var lastname = lastnames[Math.floor(Math.random() * 3)];
        var birthdate = dates[Math.floor(Math.random() * 3)];
        var balance = Math.floor(Math.random() * 2000);

        return {
            id: id,
            firstName: firstname,
            lastName: lastname,
            birthDate: new Date(birthdate),
            balance: balance
        }
    }

    $scope.rowCollection = [];

    for (id; id < 5; id++) {
        $scope.rowCollection.push(generateRandomItem(id));
    }

    //add to the real data holder
    $scope.addRandomItem = function addRandomItem() {
        $scope.rowCollection.push(generateRandomItem(id));
        id++;
    };

    //remove to the real data holder
    $scope.removeItem = function removeItem(row) {
        var index = $scope.rowCollection.indexOf(row);
        if (index !== -1) {
            $scope.rowCollection.splice(index, 1);
        }
    }
}]);
first name last name birth date balance
{{row.firstName}} {{row.lastName}} {{row.birthDate}} {{row.balance}}

Format data & cell templating

Well, the point is that you keep full control on the markup, so formatting and templating gets as easy as using standard markup and angular framework features, fancy directives, etc.

<table st-table="rowCollection" class="table table-striped">
	<thead>
	<tr>
		<th>first name</th>
		<th>last name</th>
		<th>birth date</th>
		<th>balance</th>
		<th>email</th>
	</tr>
	</thead>
	<tbody>
	<tr ng-repeat="row in rowCollection">
		<td>{{row.firstName | uppercase}}</td>
		<td>{{row.lastName}}</td>
		<td>{{row.birthDate | date}}</td>
		<td>{{row.balance | currency}}</td>
		<td>
			<button class="btn btn-sm" popover-placement="top" popover="{{row.email}}" type="button">
				<i class="glyphicon glyphicon-eye-open"></i>
			</button>
			<a ng-href="mailto:{{row.email}}">email</a></td>
		<td>
			<button type="button" ng-click="removeRow(row)" class="btn btn-sm btn-danger">
				<i class="glyphicon glyphicon-remove-circle">
				</i>
			</button>
		</td>
	</tr>
	</tbody>
</table>
app.controller('formatCtrl', ['$scope', function (scope) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];

    scope.removeRow = function removeRow(row) {
        var index = scope.rowCollection.indexOf(row);
        if (index !== -1) {
            scope.rowCollection.splice(index, 1);
        }
    }
}]);
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

Sort data

We can use the st-sort directive to have a built in sort feature. Again, it is all declarative. You add the attribute st-sort to your table headers. this will specify the property name you want to sort by or the name of a getter function available on the scope. There are three different sort states: ascending, descending and back to the primary order.

<table st-table="rowCollection" class="table table-striped">
	<thead>
	<tr>
		<th st-sort="getters.firstName">first name</th>
		<th st-sort="lastName">last name</th>
		<th st-sort="birthDate">birth date</th>
		<th st-sort="balance" st-skip-natural="true" >balance</th>
		<th>email</th>
	</tr>
	</thead>
	<tbody>
	<tr ng-repeat="row in rowCollection">
		<td>{{row.firstName | uppercase}}</td>
		<td>{{row.lastName}}</td>
		<td>{{row.birthDate | date}}</td>
		<td>{{row.balance | currency}}</td>
		<td><a ng-href="mailto:{{row.email}}">email</a></td>
	</tr>
	</tbody>
</table>
app.controller('sortCtrl', ['$scope', '$filter', function (scope, filter) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];

    scope.getters={
        firstName: function (value) {
            //this will sort by the length of the first name string
            return value.firstName.length;
        }
    }
}]);
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

Note you can ask to sort a column by default by adding the attribute st-sort-default="true". And if you set the value of this attribute to "reverse" then it will sort this column and reverse the order by default.

You can skip the "natural order" state by adding st-skip-natural="true" as attribute of your th element.

smart-table add css classes when the sort state changes st-sort-ascent and st-sort-descent. If you use a third party css framework with specific class names you can overwrite the default ones by passing the attributes st-class-ascent="myClassName" and st-class-descent="className". The css used for this website is


  .st-sort-ascent:before{
    content: '\25B2';
  }

  .st-sort-descent:before{
    content: '\25BC';
  }

Search/filter data

The stSearch gives you a way to filter rows globally if you don't set any value to the attribute. Or you can also define the predicate to use as value of the directive attribute. Use a property name, or alternatively you can interpolate the predicate with a scope property

<form>
	<label for="predicate">selected predicate:</label>
	<select class="form-control" id="predicate" ng-model="selectedPredicate" ng-options="predicate for predicate in predicates"></select>
</form>
<table st-table="rowCollection" class="table table-striped">
	<thead>
	<tr>
		<th st-sort="firstName">first name</th>
		<th st-sort="lastName">last name</th>
		<th st-sort="birthDate">birth date</th>
		<th st-sort="balance">balance</th>
		<th>email</th>
	</tr>
	<tr>
		<th>
			<input st-search="firstName" placeholder="search for firstname" class="input-sm form-control" type="search"/>
		</th>
		<th colspan="3">
			<input st-search placeholder="global search" class="input-sm form-control" type="search"/>
		</th>
		<th>
			<input st-search="{{selectedPredicate}}" placeholder="bound predicate" class="input-sm form-control" type="search"/>
		</th>
	</tr>
	</thead>
	<tbody>
	<tr ng-repeat="row in rowCollection">
		<td>{{row.firstName | uppercase}}</td>
		<td>{{row.lastName}}</td>
		<td>{{row.birthDate | date}}</td>
		<td>{{row.balance | currency}}</td>
		<td><a ng-href="mailto:{{row.email}}">email</a></td>
	</tr>
	</tbody>
</table>
app.controller('filterCtrl', ['$scope', '$filter', function (scope, filter) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];

    scope.predicates = ['firstName', 'lastName', 'birthDate', 'balance', 'email'];
    scope.selectedPredicate = scope.predicates[0];
}]);
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

Note there is a throttle of 400 ms by default which you can overwrite with the attribute st-delay

By default the search will be triggered on the input event but you can change it using the attribute st-input-event

<select st-input-event="change" st-search="myProp"></select>

Strict mode filtering

The stSetFilter replaces the filter used when searching through Smart Table. When the default behavior for stSearch does not meet your demands, like in a select where one entry is a substring of another, use a custom filter to achieve your goals.

<table st-safe-src="rowCollection" st-table="displayCollection" st-set-filter="myStrictFilter" class="table table-striped">
    <thead>
        <tr>
            <th st-sort="firstName">first name</th>
            <th st-sort="lastName">last name</th>
            <th st-sort="strictSearchValue">strict search</th>
            <th st-sort="strictSelectValue">strict select</th>
        </tr>
        <tr>
            <th>
                <input st-search="firstName" placeholder="search for firstname" class="input-sm form-control" type="search"/>
            </th>
            <th>
                <input st-search="lastName" placeholder="search for lastname" class="input-sm form-control" type="search"/>
            </th>
            <th>
                <input st-search="strictSearchValue" placeholder="search for equal match" class="input-sm form-control" type="search"/>
            </th>
            <th>
                <select st-search="strictSelectValue">
                    <option value="">All</option>
                    <option ng-repeat="row in rowCollection | unique:'strictSelectValue'" value="{{row.strictSelectValue}}">{{row.strictSelectValue}}</option>
                </select>
            </th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="row in displayCollection">
            <td>{{row.firstName | uppercase}}</td>
            <td>{{row.lastName}}</td>
            <td>{{row.strictSearchValue}}</td>
            <td>{{row.strictSelectValue}}</td>
        </tr>
    </tbody>
</table>
app.controller('filterCtrl', ['$scope', '$filter', function (scope, filter) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com', strictSearchValue: "abc", strictSelectValue: "ab"},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com', strictSearchValue: "ab", strictSelectValue: "abc"},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com', strictSearchValue: "abc", strictSelectValue: "abc"}
    ];

    scope.displayCollection = [].concat(scope.rowCollection);

    scope.predicates = ['firstName', 'lastName', 'birthDate', 'balance', 'email'];
    scope.selectedPredicate = scope.predicates[0];
]);

app.filter('myStrictFilter', function($filter){
    return function(input, predicate){
        return $filter('filter')(input, predicate, true);
    }
});

app.filter('unique', function() {
    return function (arr, field) {
        var o = {}, i, l = arr.length, r = [];
        for(i=0; i<l;i+=1) {
            o[arr[i][field]] = arr[i];
        }
        for(i in o) {
            r.push(o[i]);
        }
        return r;
    };
  })
first name last name strict search strict select
{{row.firstName | uppercase}} {{row.lastName}} {{row.strictSearchValue}} {{row.strictSelectValue}}

Note that st-safe-src is required for the select to properly display all distinct elements in the collection. Should this be omitted, the select would only contain values from elements visible in table, also affected by paging.

Custom search plugins

It is easy to write advanced plugins to smart table. The following example shows how you can develop a dropdown to filter on a specific value or a multiple selection plugin which lets you filter in a excel-like style.

Date and numeric filtering

In this example, date range or number range may filter data.

Select data rows

The stSelectRow will make the rows selectable. Bind it with the rows and then set the selection mode with the stSelectRow plugin. If you don't specify the selection mode, it will be single When a row is selected, the property isSelected is set to true on the row object and the class attribute st-selected is set on the tr element.

<table st-table="rowCollection" class="table">
<thead>
<tr>
	<th st-sort="firstName">first name</th>
	<th st-sort="lastName">last name</th>
	<th st-sort="birthDate">birth date</th>
	<th st-sort="balance">balance</th>
	<th>email</th>
</tr>
</thead>
<tbody>
<tr st-select-row="row" st-select-mode="multiple" ng-repeat="row in rowCollection">
	<td>{{row.firstName | uppercase}}</td>
	<td>{{row.lastName}}</td>
	<td>{{row.birthDate | date}}</td>
	<td>{{row.balance | currency}}</td>
	<td><a ng-href="mailto:{{row.email}}">email</a></td>
</tr>
</tbody>
</table>
app.controller('selectionCtrl', ['$scope', '$filter', function (scope, filter) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];
}]);
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

Client side Pagination

This is actually the only plugin which defines its own template. But the template fits good with the popular css library bootstrap from Twitter. You can also change the number of items by page dynamically with the bound attribute st-items-by-page and the number of displayed pages in the pagination directive with the attribute st-displayed-pages. You can set these attributes to a static value as well

<table st-table="rowCollection" class="table table-striped">
		<thead>
		<tr>
			<th st-sort="firstName">first name</th>
			<th st-sort="lastName">last name</th>
			<th st-sort="birthDate">birth date</th>
			<th st-sort="balance">balance</th>
			<th>email</th>
		</tr>
		<tr>
			<th>
				<input st-search="'firstName'" placeholder="search for firstname" class="input-sm form-control" type="search"/>
			</th>
			<th colspan="4">
				<input st-search placeholder="global search" class="input-sm form-control" type="search"/>
			</th>
		</tr>
		</thead>
		<tbody>
		<tr ng-repeat="row in rowCollection">
			<td>{{row.firstName | uppercase}}</td>
			<td>{{row.lastName}}</td>
			<td>{{row.birthDate | date}}</td>
			<td>{{row.balance | currency}}</td>
			<td><a ng-href="mailto:{{row.email}}">email</a></td>
		</tr>
		</tbody>
		<tfoot>
			<tr>
				<td colspan="5" class="text-center">
					<div st-pagination="" st-items-by-page="itemsByPage" st-displayed-pages="7"></div>
				</td>
			</tr>
		</tfoot>
	</table>
app.controller('paginationCtrl', ['$scope', function (scope) {
    var
        nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'],
        familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez'];

    function createRandomItem() {
        var
            firstName = nameList[Math.floor(Math.random() * 4)],
            lastName = familyName[Math.floor(Math.random() * 4)],
            age = Math.floor(Math.random() * 100),
            email = firstName + lastName + '@whatever.com',
            balance = Math.random() * 3000;

        return{
            firstName: firstName,
            lastName: lastName,
            age: age,
            email: email,
            balance: balance
        };
    }

		scope.itemsByPage=15;

    scope.rowCollection = [];
    for (var j = 0; j < 200; j++) {
        scope.rowCollection.push(createRandomItem());
    }
}]);
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

pipe/ajax plugin

This might be the most powerful plugin. When a directive calls the table controller api (sortBy, search, slice, ...) a variable representing the internal table state is updated and a pipe function is called. By default it basically pipes the filter, sort and slice(pagination) operations on the bound array. But with this directive you can overwrite the default pipe function which can be handy to call your server. The function takes as arguments, the object which represents the table state, and a reference to the table controller.

Note that if you want the pagination directive to update, you must set in your pipe function, the total number of pages as smart-table has no clue about your dataset and this information has to come from your server.


<table class="table" st-pipe="mc.callServer" st-table="mc.displayed">
	<thead>
	<tr>
		<th st-sort="id">id</th>
		<th st-sort="name">name</th>
		<th st-sort="age">age</th>
		<th st-sort="saved">saved people</th>
	</tr>
	<tr>
		<th><input st-search="id"/></th>
		<th><input st-search="name"/></th>
		<th><input st-search="age"/></th>
		<th><input st-search="saved"/></th>
	</tr>
	</thead>
	<tbody ng-show="!mc.isLoading">
	<tr ng-repeat="row in mc.displayed">
		<td>{{row.id}}</td>
		<td>{{row.name}}</td>
		<td>{{row.age}}</td>
		<td>{{row.saved}}</td>
	</tr>
	</tbody>
	<tbody ng-show="mc.isLoading">
	<tr>
		<td colspan="4" class="text-center">Loading ... </td>
	</tr>
	</tbody>
	<tfoot>
	<tr>
		<td class="text-center" st-pagination="" st-items-by-page="10" colspan="4">
		</td>
	</tr>
	</tfoot>
</table>	
app.controller('pipeCtrl', ['Resource', function (service) {

  var ctrl = this;

  this.displayed = [];

  this.callServer = function callServer(tableState) {

    ctrl.isLoading = true;

    var pagination = tableState.pagination;

    var start = pagination.start || 0;     // This is NOT the page number, but the index of item in the list that you want to use to display the table.
    var number = pagination.number || 10;  // Number of entries showed per page.

    service.getPage(start, number, tableState).then(function (result) {
      ctrl.displayed = result.data;
      tableState.pagination.numberOfPages = result.numberOfPages;//set the number of pages so the pagination can update
      ctrl.isLoading = false;
    });
  };

}])

	app.factory('Resource', ['$q', '$filter', '$timeout', function ($q, $filter, $timeout) {

	//this would be the service to call your server, a standard bridge between your model an $http

	// the database (normally on your server)
	var randomsItems = [];

	function createRandomItem(id) {
		var heroes = ['Batman', 'Superman', 'Robin', 'Thor', 'Hulk', 'Niki Larson', 'Stark', 'Bob Leponge'];
		return {
			id: id,
			name: heroes[Math.floor(Math.random() * 7)],
			age: Math.floor(Math.random() * 1000),
			saved: Math.floor(Math.random() * 10000)
		};

	}

	for (var i = 0; i < 1000; i++) {
		randomsItems.push(createRandomItem(i));
	}


	//fake call to the server, normally this service would serialize table state to send it to the server (with query parameters for example) and parse the response
	//in our case, it actually performs the logic which would happened in the server
	function getPage(start, number, params) {

		var deferred = $q.defer();

		var filtered = params.search.predicateObject ? $filter('filter')(randomsItems, params.search.predicateObject) : randomsItems;

		if (params.sort.predicate) {
			filtered = $filter('orderBy')(filtered, params.sort.predicate, params.sort.reverse);
		}

		var result = filtered.slice(start, start + number);

		$timeout(function () {
			//note, the server passes the information about the data set size
			deferred.resolve({
				data: result,
				numberOfPages: Math.ceil(filtered.length / number)
			});
		}, 1500);


		return deferred.promise;
	}

	return {
		getPage: getPage
	};

}]);
id name age saved people
{{row.id}} {{row.name}} {{row.age}} {{row.saved}}

Wow impressive, we just have plugged our table with a server. Of course in our example the data is random and not consistent with the event which triggered the request, but in reallity the server would sort, filter, slice according to the query

Global configuration

Instead of changing configurable properties via attributes, you can inject stConfig during the configuration step, and change specific global configurable properties. Attributes will always override any global stConfig properties. See the defaults here. For example:

angular.module('myModule', []).config(function(stConfig) {
  stConfig.pagination.template = 'my-custom-pagination-tmpl.html';
});

Create your own plugin

It is pretty easy to plug your own plugin into smart table. You just have to require the main directive so you will get access to the controller API. Let's have an example: I want my plugin to give me a way to select data with a check box.

				
<table st-table="rowCollection" class="table">
	<thead>
	<tr>
		<th></th>
		<th st-sort="firstName">first name</th>
		<th st-sort="lastName">last name</th>
		<th st-sort="birthDate">birth date</th>
		<th st-sort="balance">balance</th>
		<th>email</th>
	</tr>
	</thead>
	<tbody>
	<tr ng-repeat="row in rowCollection">
		<td cs-select="row"></td>
		<td>{{row.firstName | uppercase}}</td>
		<td>{{row.lastName}}</td>
		<td>{{row.birthDate | date}}</td>
		<td>{{row.balance | currency}}</td>
		<td><a ng-href="mailto:{{row.email}}">email</a></td>
	</tr>
	</tbody>
</table>
				
			
				
app.controller('customCtrl', ['$scope', function (scope) {
    scope.rowCollection = [
        {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
        {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
        {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
    ];
}]);
app.directive('csSelect', function () {
    return {
        require: '^stTable',
        template: '<input type="checkbox"/>',
        scope: {
            row: '=csSelect'
        },
        link: function (scope, element, attr, ctrl) {

            element.bind('change', function (evt) {
                scope.$apply(function () {
                    ctrl.select(scope.row, 'multiple');
                });
            });

            scope.$watch('row.isSelected', function (newValue, oldValue) {
                if (newValue === true) {
                    element.parent().addClass('st-selected');
                } else {
                    element.parent().removeClass('st-selected');
                }
            });
        }
    };
});

				
			
first name last name birth date balance email
{{row.firstName | uppercase}} {{row.lastName}} {{row.birthDate | date}} {{row.balance | currency}} email

Custom build

You can easily custom you build of smart-table. Let's say, since you have created you custom selection directive, you don't want the stSelectRow plugin in smart-table, so it will be even lighter than it is now. Go to the gulpFile modify the the pluginList variable, then run gulp build and here you are !

More sophisticated examples

This section aims to provide example to show how you can compose around smart-table. They remain basic but can give ideas and inspiration.

Draggable headers

An example on how to implement draggable headers so the row template can be reordered. It uses lrDragNDrop for the drag and drop logic.

Pagination on scroll

This example shows how to implement pagination on scroll and remove the previous tr nodes as the user scroll down. You will need to implement the same kind of logic when scrolling up though. The scroll logic is based on lrInfiniteScroll

Persist table state in local storage

This example shows how to implement a plugin to persist table state in the local storage so you are able to restore you table when the page is refreshed.

Fix headers

This example shows how to have fix headers with a pure css solution. It is also responsive as based on the flex-model. A directive has been added to be able to change the space ratio occupied by each column.

If you want sticky headers you can have a look at lrStickyHeader and its specific smart-table directive

sticky table header

Custom pagination

This example shows how to use a custom template for the paginator, and how you can add more advanced functionality such as an input field to jump to any page easily (great for very large sets of data).