├── .gitignore ├── sections ├── foot │ └── markup.html ├── basics │ ├── code.js │ └── markup.html ├── config │ ├── markup.html │ └── code.js ├── style │ ├── code.js │ └── markup.html ├── cellTemplate │ ├── code.js │ └── markup.html ├── format │ ├── code.js │ └── markup.html ├── filter │ ├── code.js │ └── markup.html ├── edit │ ├── code.js │ └── markup.html ├── sort │ ├── code.js │ └── markup.html ├── selection │ ├── code.js │ └── markup.html ├── pagination │ ├── code.js │ └── markup.html ├── head │ ├── markup.html │ └── code.js └── Introduction │ └── markup.html ├── assets ├── template │ ├── custom.html │ ├── pane.html │ ├── customHeader.html │ └── tabs.html ├── img │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── lib │ ├── angular │ │ ├── bower.json │ │ └── .bower.json │ ├── bootstrap │ │ └── ui-bootstrap-custom-tpls-0.4.0-SNAPSHOT.js │ ├── prism │ │ ├── prism.css │ │ └── prism.js │ └── smart-table │ │ ├── Smart-Table.min.js │ │ └── Smart-Table.debug.js ├── css │ └── mainStyle.css └── js │ └── app.js ├── README.md ├── bower_components └── Smart-Table │ ├── bower_components │ ├── angular │ │ ├── bower.json │ │ ├── angular.min.js.gzip │ │ ├── .bower.json │ │ ├── angular-csp.css │ │ └── README.md │ └── angular-mocks │ │ ├── bower.json │ │ ├── .bower.json │ │ └── README.md │ └── example-app │ ├── index.html │ └── app.js ├── package.json ├── Gruntfile.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | -------------------------------------------------------------------------------- /sections/foot/markup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/template/custom.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | smart-table-website 2 | =================== 3 | 4 | documentation website for smart table 5 | -------------------------------------------------------------------------------- /assets/template/pane.html: -------------------------------------------------------------------------------- 1 |
2 |
-------------------------------------------------------------------------------- /assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/smart-table-website/master/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /assets/template/customHeader.html: -------------------------------------------------------------------------------- 1 | {{column.label}} 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/smart-table-website/master/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /assets/lib/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.0.8", 4 | "main": "./angular.js", 5 | "dependencies": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.22", 4 | "main": "./angular.js", 5 | "dependencies": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/smart-table-website/master/bower_components/Smart-Table/bower_components/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular-mocks/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.2.22", 4 | "main": "./angular-mocks.js", 5 | "dependencies": { 6 | "angular": "1.2.22" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /assets/template/tabs.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-table", 3 | "version": "0.0.1", 4 | "description": "documentation site for Smart-Table", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": "", 10 | "author": "Laurent Renard", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "grunt": "~0.4.1", 14 | "grunt-contrib-concat": "~0.3.0", 15 | "grunt-contrib-copy": "~0.4.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/lib/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.0.8", 4 | "main": "./angular.js", 5 | "dependencies": {}, 6 | "homepage": "https://github.com/angular/bower-angular", 7 | "_release": "1.0.8", 8 | "_resolution": { 9 | "type": "version", 10 | "tag": "v1.0.8", 11 | "commit": "b2006330eef656b8e89a883feff7d7d2f3e5afed" 12 | }, 13 | "_source": "git://github.com/angular/bower-angular.git", 14 | "_target": "~1.0.8", 15 | "_originalSource": "angular", 16 | "_direct": true 17 | } -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.22", 4 | "main": "./angular.js", 5 | "dependencies": {}, 6 | "homepage": "https://github.com/angular/bower-angular", 7 | "_release": "1.2.22", 8 | "_resolution": { 9 | "type": "version", 10 | "tag": "v1.2.22", 11 | "commit": "c1579c4a2cd885894d9414bfc496cf50da7ffbf6" 12 | }, 13 | "_source": "git://github.com/angular/bower-angular.git", 14 | "_target": "~1.2.22", 15 | "_originalSource": "angular", 16 | "_direct": true 17 | } -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular-mocks/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.2.22", 4 | "main": "./angular-mocks.js", 5 | "dependencies": { 6 | "angular": "1.2.22" 7 | }, 8 | "homepage": "https://github.com/angular/bower-angular-mocks", 9 | "_release": "1.2.22", 10 | "_resolution": { 11 | "type": "version", 12 | "tag": "v1.2.22", 13 | "commit": "5ccc6999d5f229ef4d8b46971dbcf0a2bfdde021" 14 | }, 15 | "_source": "git://github.com/angular/bower-angular-mocks.git", 16 | "_target": "~1.2.22", 17 | "_originalSource": "angular-mocks", 18 | "_direct": true 19 | } -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-block-transitions { 16 | transition:0s all!important; 17 | -webkit-transition:0s all!important; 18 | } 19 | 20 | /* show the element during a show/hide animation when the 21 | * animation is ongoing, but the .ng-hide class is active */ 22 | .ng-hide-add-active, .ng-hide-remove { 23 | display: block!important; 24 | } 25 | -------------------------------------------------------------------------------- /sections/basics/code.js: -------------------------------------------------------------------------------- 1 | app.controller('basicsCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName'}, 10 | {label: 'same same but different', map: 'firstName'}, 11 | {label: 'Last Name', map: 'lastName'} 12 | ]; 13 | }]); 14 | -------------------------------------------------------------------------------- /sections/config/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Configuration Summary

3 | improve the documentation 4 |

Here is a summary of available configuration properties for column, and for global configuration

5 |

Column

6 |
7 | 8 |
9 |

Global config

10 |
11 | 12 |
13 |
-------------------------------------------------------------------------------- /sections/style/code.js: -------------------------------------------------------------------------------- 1 | app.controller('styleCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName', headerClass: "firstName-header"}, 10 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', cellClass: "lastName-cell"}, 11 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 12 | {label: 'Balance', map: 'balance', formatFunction: 'currency'}, 13 | {label: 'e-mail', map: 'email'} 14 | ]; 15 | }]); 16 | -------------------------------------------------------------------------------- /sections/cellTemplate/code.js: -------------------------------------------------------------------------------- 1 | app.controller('cellTemplateCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName'}, 10 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 11 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 12 | {label: 'Balance', map: 'balance', formatFunction: 'currency'}, 13 | {label: 'e-mail', map: 'email'}, 14 | {label: 'Favourite color', cellTemplateUrl: 'assets/template/custom.html'} 15 | ]; 16 | }]); 17 | -------------------------------------------------------------------------------- /sections/format/code.js: -------------------------------------------------------------------------------- 1 | app.controller('formatCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName', formatFunction: function (value, formatParameter) { 10 | //this only display the first letter 11 | return value[0]; 12 | }}, 13 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 14 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 15 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'} 16 | ]; 17 | }]); -------------------------------------------------------------------------------- /sections/filter/code.js: -------------------------------------------------------------------------------- 1 | app.controller('filterCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName'}, 10 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'}, 11 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 12 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 13 | {label: 'e-mail', map: 'email'} 14 | ]; 15 | scope.globalConfig = { 16 | isGlobalSearchActivated: true 17 | }; 18 | }]); 19 | -------------------------------------------------------------------------------- /sections/edit/code.js: -------------------------------------------------------------------------------- 1 | app.controller('editCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.$on('updateDataRow', function (event, arg) { 9 | console.log(arg); 10 | }); 11 | 12 | scope.columnCollection = [ 13 | {label: 'First Name', map: 'firstName', isEditable: true}, 14 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 15 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date', isEditable: true, type: 'date'}, 16 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$', isEditable: true, type: 'number'}, 17 | {label: 'e-mail', map: 'email', isEditable: true, type: 'email'} 18 | ]; 19 | }]); 20 | -------------------------------------------------------------------------------- /sections/sort/code.js: -------------------------------------------------------------------------------- 1 | app.controller('sortCtrl', ['$scope', '$filter', function (scope, filter) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName', sortPredicate: function (dataRow) { 10 | //predicate as a function (see angular orderby documentation) : it will sort by the string length 11 | return dataRow.firstName.length; 12 | } }, 13 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 14 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 15 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 16 | {label: 'e-mail', map: 'email', isSortable: false} 17 | ]; 18 | }]); 19 | -------------------------------------------------------------------------------- /sections/selection/code.js: -------------------------------------------------------------------------------- 1 | app.controller('selectionCtrl', ['$scope', function (scope) { 2 | scope.rowCollection = [ 3 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 4 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 5 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 6 | ]; 7 | 8 | scope.columnCollection = [ 9 | {label: 'First Name', map: 'firstName'}, 10 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 11 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 12 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 13 | {label: 'e-mail', map: 'email'} 14 | ]; 15 | scope.globalConfig = { 16 | selectionMode: 'single', 17 | displaySelectionCheckbox: false 18 | }; 19 | 20 | scope.$on('selectionChange', function (event, args) { 21 | console.log(args); 22 | }); 23 | 24 | scope.canDisplayCheckbox = function () { 25 | return scope.globalConfig.selectionMode === 'multiple'; 26 | }; 27 | }]); 28 | -------------------------------------------------------------------------------- /sections/pagination/code.js: -------------------------------------------------------------------------------- 1 | app.controller('paginationCtrl', ['$scope', function (scope) { 2 | var 3 | nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'], 4 | familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez']; 5 | 6 | function createRandomItem() { 7 | var 8 | firstName = nameList[Math.floor(Math.random() * 4)], 9 | lastName = familyName[Math.floor(Math.random() * 4)], 10 | age = Math.floor(Math.random() * 100), 11 | email = firstName + lastName + '@whatever.com', 12 | balance = Math.random() * 3000; 13 | 14 | return{ 15 | firstName: firstName, 16 | lastName: lastName, 17 | age: age, 18 | email: email, 19 | balance: balance 20 | }; 21 | } 22 | 23 | scope.rowCollection = []; 24 | for (var j = 0; j < 200; j++) { 25 | scope.rowCollection.push(createRandomItem()); 26 | } 27 | 28 | scope.columnCollection = [ 29 | {label: 'First Name', map: 'firstName'}, 30 | {label: 'Last Name', map: 'lastName'}, 31 | {label: 'Age', map: 'age'}, 32 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 33 | {label: 'e-mail', map: 'email'} 34 | ]; 35 | 36 | scope.globalConfig = { 37 | isPaginationEnabled: true, 38 | itemsByPage: 12, 39 | maxSize: 8 40 | }; 41 | }]); 42 | -------------------------------------------------------------------------------- /bower_components/Smart-Table/example-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 |

{{greetings}}

26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
firstnamelastnameaction
37 | 38 |
{{row.firstname}}{{row.name}}edit
49 |
50 |
51 | 52 | 53 | clik-me 54 | 59 |
60 | 61 | -------------------------------------------------------------------------------- /bower_components/Smart-Table/example-app/app.js: -------------------------------------------------------------------------------- 1 | (function (ng) { 2 | 'use strict'; 3 | 4 | ng.module('myApp', ['smart-table']) 5 | .controller('mainCtrl', ['$scope', '$filter', '$timeout', function mainController($scope, $filter, $timeout) { 6 | $scope.greetings = 'hello world'; 7 | 8 | $scope.rowCollection = [ 9 | {name: 'Renard', firstname: 'v'}, 10 | {name: 'Raymond', firstname: 'blah'}, 11 | {name: 'Bob', firstname: 'sdfa'}, 12 | {name: 'Arthur', firstname: 'ip'}, 13 | {name: 'Bla', firstname: 'zrr'}, 14 | {name: 'Xav', firstname: 'ribo'}, 15 | {name: 'Git', firstname: 'adios'}, 16 | {name: 'Zut', firstname: 'Laurent'} 17 | ]; 18 | 19 | $scope.clickHandler = function () { 20 | $timeout(function () { 21 | $scope.rowCollection=$scope.rowCollection.concat([{name:'dude', firstname:'what'}]); 22 | }, 1000) 23 | }; 24 | 25 | $scope.edit = function (item) { 26 | var index = $scope.rowCollection.indexOf(item); 27 | if (index !== -1) { 28 | $scope.rowCollection.splice(index, 1); 29 | } 30 | }; 31 | 32 | 33 | $scope.getters = { 34 | firstname: function getter(value) { 35 | return value.firstname.length; 36 | }}; 37 | 38 | 39 | }]); 40 | 41 | })(angular); 42 | -------------------------------------------------------------------------------- /sections/head/markup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Smart Table documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

Smart Table

17 | Code on github 18 | 31 |
32 |
-------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular-mocks/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular-mocks 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngMock). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular-mocks 13 | ``` 14 | 15 | ## Documentation 16 | 17 | Documentation is available on the 18 | [AngularJS docs site](http://docs.angularjs.org/guide/dev_guide.unit-testing). 19 | 20 | ## License 21 | 22 | The MIT License 23 | 24 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in 34 | all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 42 | THE SOFTWARE. 43 | -------------------------------------------------------------------------------- /bower_components/Smart-Table/bower_components/angular/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular 13 | ``` 14 | 15 | Add a ` 19 | ``` 20 | 21 | ## Documentation 22 | 23 | Documentation is available on the 24 | [AngularJS docs site](http://docs.angularjs.org/). 25 | 26 | ## License 27 | 28 | The MIT License 29 | 30 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | // Load the plugin that provides the "uglify" task. 4 | grunt.loadNpmTasks('grunt-contrib-concat'); 5 | grunt.loadNpmTasks('grunt-contrib-copy'); 6 | 7 | // Project configuration. 8 | grunt.initConfig({ 9 | pkg: grunt.file.readJSON('package.json'), 10 | src: { 11 | js: ['sections/head/code.js', 12 | 'sections/basics/code.js', 13 | 'sections/format/code.js', 14 | 'sections/sort/code.js', 15 | 'sections/filter/code.js', 16 | 'sections/selection/code.js', 17 | 'sections/edit/code.js', 18 | 'sections/style/code.js', 19 | 'sections/cellTemplate/code.js', 20 | 'sections/pagination/code.js', 21 | 'sections/config/code.js'], 22 | html: ['sections/head/markup.html', 23 | 'sections/introduction/markup.html', 24 | 'sections/basics/markup.html', 25 | 'sections/format/markup.html', 26 | 'sections/sort/markup.html', 27 | 'sections/filter/markup.html', 28 | 'sections/selection/markup.html', 29 | 'sections/edit/markup.html', 30 | 'sections/style/markup.html', 31 | 'sections/cellTemplate/markup.html', 32 | 'sections/pagination/markup.html', 33 | 'sections/config/markup.html', 34 | 'sections/foot/markup.html'] 35 | }, 36 | concat: { 37 | options: { 38 | }, 39 | js: { 40 | src: ['<%= src.js %>'], 41 | dest: 'assets/js/app.js' 42 | }, 43 | html: { 44 | src: ['<%= src.html %>'], 45 | dest: 'index.html' 46 | } 47 | } 48 | }); 49 | // Default task(s). 50 | grunt.registerTask('default', ['concat']); 51 | }; 52 | -------------------------------------------------------------------------------- /sections/head/code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: laurentrenard 3 | * Date: 10/3/13 4 | * Time: 9:55 PM 5 | **/ 6 | var app = angular.module('app', ['ui.bootstrap.tabs', 'smartTable.table']); 7 | app.directive('custom', ['$log', function (log) { 8 | return { 9 | restrict: 'E', 10 | //include smart table controller to use its API if needed 11 | require: '^smartTable', 12 | template: '', 18 | replace: true, 19 | link: function (scope, element, attrs, ctrl) { 20 | 21 | var allowedColors = ['red', 'yellow', 'blue']; 22 | 23 | //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API 24 | scope.$watch('favouriteColor', function (value) { 25 | if (allowedColors.indexOf(value) != -1) { 26 | scope.dataRow.favouriteColor = scope.favouriteColor; 27 | } 28 | }); 29 | } 30 | } 31 | }]); 32 | app.directive('columnFilter', function () { 33 | return { 34 | restrict: 'C', 35 | require: '^smartTable', 36 | link: function (scope, element, attrs, ctrl) { 37 | scope.searchValue = ''; 38 | scope.$watch('searchValue', function (value) { 39 | ctrl.search(value, scope.column); 40 | }) 41 | } 42 | } 43 | }); 44 | app.controller('mainCtrl', ['$scope', function (scope) { 45 | scope.greeting = 'hello Laurent'; 46 | }]); 47 | 48 | app.directive('scrollTreshold', ['$window', function (window) { 49 | return { 50 | link: function (scope, element, attr) { 51 | var treshold = attr.scrollTreshold || 100; 52 | window.addEventListener('scroll', function (event) { 53 | if (window.scrollY > treshold) { 54 | element.addClass('scroll-treshold'); 55 | } else { 56 | element.removeClass('scroll-treshold'); 57 | } 58 | }); 59 | } 60 | } 61 | }]); -------------------------------------------------------------------------------- /sections/style/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Simple styling

3 | improve the documentation 4 |

This allows you to add a class name for the cells in a given column, and for the header as well. You'll have to specify the 5 | headerClass and cellClass property in the column configuration

6 | 7 | 8 |
app.controller('styleCtrl', ['$scope', function (scope) {
 9 |     scope.rowCollection = [
10 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
11 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
12 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
13 |     ];
14 | 
15 |     scope.columnCollection = [
16 |         {label: 'First Name', map: 'firstName', headerClass: "firstName-header"},
17 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', cellClass: "lastName-cell"},
18 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
19 |         {label: 'Balance', map: 'balance', formatFunction: 'currency'},
20 |         {label: 'e-mail', map: 'email'}
21 |     ];
22 | }]);
23 |
24 | 25 |
.firstName-header{
26 | text-decoration: underline;
27 | }
28 | 
29 | .lastName-cell{
30 | color: red;
31 | }
32 |
33 | 34 |
<div ng-controller="styleCtrl>
35 |     <smart-table  columns="columnCollection" rows="rowCollection"></smart-table>
36 | </div>
37 |
38 |
39 |
40 | 41 |
42 |
-------------------------------------------------------------------------------- /assets/lib/bootstrap/ui-bootstrap-custom-tpls-0.4.0-SNAPSHOT.js: -------------------------------------------------------------------------------- 1 | angular.module('ui.bootstrap.tabs', []) 2 | .controller('TabsController', ['$scope', '$element', function($scope, $element) { 3 | var panes = $scope.panes = []; 4 | 5 | this.select = $scope.select = function selectPane(pane) { 6 | angular.forEach(panes, function(pane) { 7 | pane.selected = false; 8 | }); 9 | pane.selected = true; 10 | }; 11 | 12 | this.addPane = function addPane(pane) { 13 | if (!panes.length) { 14 | $scope.select(pane); 15 | } 16 | panes.push(pane); 17 | }; 18 | 19 | this.removePane = function removePane(pane) { 20 | var index = panes.indexOf(pane); 21 | panes.splice(index, 1); 22 | //Select a new pane if removed pane was selected 23 | if (pane.selected && panes.length > 0) { 24 | $scope.select(panes[index < panes.length ? index : index-1]); 25 | } 26 | }; 27 | }]) 28 | .directive('tabs', function() { 29 | return { 30 | restrict: 'EA', 31 | transclude: true, 32 | scope: {}, 33 | controller: 'TabsController', 34 | templateUrl: 'assets/template/tabs.html', 35 | replace: true 36 | }; 37 | }) 38 | .directive('pane', ['$parse', function($parse) { 39 | return { 40 | require: '^tabs', 41 | restrict: 'EA', 42 | transclude: true, 43 | scope:{ 44 | heading:'@' 45 | }, 46 | link: function(scope, element, attrs, tabsCtrl) { 47 | var getSelected, setSelected; 48 | scope.selected = false; 49 | if (attrs.active) { 50 | getSelected = $parse(attrs.active); 51 | setSelected = getSelected.assign; 52 | scope.$watch( 53 | function watchSelected() {return getSelected(scope.$parent);}, 54 | function updateSelected(value) {scope.selected = value;} 55 | ); 56 | scope.selected = getSelected ? getSelected(scope.$parent) : false; 57 | } 58 | scope.$watch('selected', function(selected) { 59 | if(selected) { 60 | tabsCtrl.select(scope); 61 | } 62 | if(setSelected) { 63 | setSelected(scope.$parent, selected); 64 | } 65 | }); 66 | 67 | tabsCtrl.addPane(scope); 68 | scope.$on('$destroy', function() { 69 | tabsCtrl.removePane(scope); 70 | }); 71 | }, 72 | templateUrl: 'assets/template/pane.html', 73 | replace: true 74 | }; 75 | }]); 76 | -------------------------------------------------------------------------------- /sections/Introduction/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Introduction

3 | improve the documentation 4 | 5 |

Smart table is an Angularjs module to easily display data in a table with a set 6 | of built in functionalities such filetering, 7 | sorting, etc. While developing this module I made sure to focus on this particular points:

8 | 29 |

Although smart-table is by far the best table module for angular :D, there are other table modules in the 30 | 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:

31 | 35 | 36 |

If you want to play around, try this plunker

37 |
38 | -------------------------------------------------------------------------------- /sections/config/code.js: -------------------------------------------------------------------------------- 1 | app.controller('configCtrl', ['$scope', function (scope) { 2 | 3 | scope.rowCollectionColumn = [ 4 | {name: 'isSortable', description: 'tell whether we can sort by the given column', defaultValue: 'true'}, 5 | {name: 'isEditable', description: 'tell whether we can Edit the cell in the given column', defaultValue: 'false'}, 6 | {name: 'type', description: 'the type of the input if the column cells are editable', defaultValue: 'text'}, 7 | {name: 'headerTemplateUrl', description: 'the url to a custom template for the column header', defaultValue: 'partials/defaultHeader.html'}, 8 | {name: 'map', description: 'the property of the data model objects the column cell will be bound to', defaultValue: 'undefined'}, 9 | {name: 'label', description: 'the label of the header column', defaultValue: 'undefined'}, 10 | {name: 'sortPredicate', description: 'the predicate to use when we sort by the column', defaultValue: 'undefined'}, 11 | {name: 'formatFunction', description: 'the function or the filter name to use when formatting the column cells', defaultValue: 'undefined'}, 12 | {name: 'formatParameter', description: 'a parameter to pass to the formatFunction', defaultValue: 'undefined'}, 13 | {name: 'cellTemplateUrl', description: 'the url of the template if custom template is used for the column cells', defaultValue: 'undefined'}, 14 | {name: 'headerClass', description: 'a class name to add to the column header', defaultValue: 'undefined'}, 15 | {name: 'cellClass', description: 'a class name to add to the column cells', defaultValue: 'undefined'} 16 | ]; 17 | 18 | scope.rowCollectionConfig = [ 19 | {name: 'selectionMode', description: 'the selection mode used ("multiple","single","none"', defaultValue: 'none'}, 20 | {name: 'isGlobalSearchActivated', description: 'display or not a input to filter data rows globally', defaultValue: 'false'}, 21 | {name: 'displaySelectionCheckBox', description: 'if in multiple selection mode tell whether to display a column with checkbox for selection', defaultValue: 'false'}, 22 | {name: 'isPaginationEnabled', description: 'tell whether to display the pagination at the bottom of the table', defaultValue: 'true'}, 23 | {name: 'itemsByPage', description: 'the number of items displayed by page', defaultValue: '10'}, 24 | {name: 'maxSize', description: 'the maximum number of page links to display at the bottom', defaultValue: '5'}, 25 | {name: 'sortAlgorithm', description: 'a function if you want to use your own sort algorithm', defaultValue: 'undefined'}, 26 | {name: 'filterAlgorithm', description: 'a function if you want to use your own filter algorithm', defaultValue: 'undefined'} 27 | ]; 28 | 29 | scope.columnCollection = [ 30 | {label: 'Property Name', map: 'name', cellClass: 'property-name'}, 31 | {label: 'Description', map: 'description'}, 32 | {label: 'Default value', map: 'defaultValue'} 33 | ]; 34 | }]); 35 | -------------------------------------------------------------------------------- /sections/format/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Format data

3 | improve the documentation 4 |

5 | In our previous examples the data were displayed as raw strings,but what if we want to format date, currency and so on in a proper way ? 6 | In the column configuration, you can specify how to format the data for the given column by adding formatFunction and formatParameter 7 | properties. 8 |

9 | 23 | 24 | 25 |
app.controller('sortCtrl', ['$scope', function (scope) {
26 |     scope.rowCollection = [
27 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
28 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
29 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
30 |     ];
31 | 
32 |     scope.columnCollection = [
33 |         {label: 'First Name', map: 'firstName', formatFunction: function (value, formatParameter) {
34 |             //this only display the first letter
35 |             return value[0];
36 |         }},
37 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
38 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
39 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}
40 |     ];
41 | }]);
42 |
43 | 44 |
<div ng-controller="formatCtrl">
45 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
46 | </div>
47 |
48 |
49 |
50 | 51 |
52 |
-------------------------------------------------------------------------------- /sections/filter/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Search/filter data

3 | improve the documentation 4 |

The table controller API gives you a way to filter the data comparing an input to both a given column values or in the whole table. 5 | To display an input textbox for a global search (in the whole table) you just have to set the isGlobalSearchActivated property to true in the table global config object. The algorithm used 6 | will be the one used by the filter filter from Angular. Of course, if you want to use your own, you can specify it in the global config (see below) 7 |

8 | 9 | 10 |
app.controller('filterCtrl', ['$scope', function (scope) {
11 |     scope.rowCollection = [
12 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
13 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
14 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
15 |     ];
16 | 
17 |     scope.columnCollection = [
18 |         {label: 'First Name', map: 'firstName'},
19 |         //the headerTemplateUrl property will be explained later, the purpose here is to show we can filter also by column only
20 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'},
21 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
22 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
23 |         {label: 'e-mail', map: 'email', isSortable: false}
24 |     ];
25 |     scope.globalConfig = {
26 |         isGlobalSearchActivated: true
27 |     };
28 | }]);
29 |
30 | 31 |
<div ng-controller="filterCtrl">
32 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
33 | </div>
34 |
35 |
36 |
37 | 38 |
39 |

Use your own algorithm

40 |

In the same way than for the sort operation you can use you own algorithm, you just have to specify the function you want to use in the global config 41 | under the property filterAlgorithm

42 |
function customFilter(arrayRef, expression) {
43 |     //do some stuff
44 |     return filteredArray;
45 | }
46 |
-------------------------------------------------------------------------------- /sections/edit/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Edit cell

3 | improve the documentation 4 |

You can make the cells of a given column editable by simply setting the isEditable property to true in this column config. This will allow 5 | you to edit directly the property on the data model object. Therefore, you may want to specify which type of input the cell will accept. You just 6 | have to set the type property to the the type you want (email, number, etc). If the input is valid the model is updated, otherwise it will keep 7 | the previous value

8 |

notify an item has been updated

9 |

In the same way than for selection changes, you don't want to watch the whole collection to know when a given property of a given item has bee updated. That 10 | is why smart-table will raise the updateDataRow event to notify the parent scope when a row has been modified.

11 | 15 | 16 | 17 |
app.controller('editCtrl', ['$scope', function (scope) {
18 |     scope.rowCollection = [
19 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
20 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
21 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
22 |     ];
23 | 
24 |     //every time a data row is updated
25 |     scope.$on('updateDataRow', function (event, arg) {
26 |         console.log(arg);
27 |     });
28 | 
29 |     scope.columnCollection = [
30 |         {label: 'First Name', map: 'firstName', isEditable: true},
31 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
32 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date', isEditable: true, type: 'date'},
33 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$', isEditable: true, type: 'number'},
34 |         {label: 'e-mail', map: 'email', isEditable: true, type: 'email'}
35 |     ];
36 | }]);
37 |
38 | 39 |
<div ng-controller="editCtrl">
40 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
41 | </div>
42 |
43 |
44 |
45 | 46 |
47 | 50 |
51 | -------------------------------------------------------------------------------- /assets/lib/prism/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | text-shadow: 0 1px white; 11 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | 27 | @media print { 28 | code[class*="language-"], 29 | pre[class*="language-"] { 30 | text-shadow: none; 31 | } 32 | } 33 | 34 | /* Code blocks */ 35 | pre[class*="language-"] { 36 | padding: 1em; 37 | overflow: auto; 38 | } 39 | 40 | :not(pre) > code[class*="language-"], 41 | pre[class*="language-"] { 42 | background: #f5f2f0; 43 | } 44 | 45 | /* Inline code */ 46 | :not(pre) > code[class*="language-"] { 47 | padding: .1em; 48 | border-radius: .3em; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #999; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.boolean, 69 | .token.number { 70 | color: #905; 71 | } 72 | 73 | .token.selector, 74 | .token.attr-name, 75 | .token.string { 76 | color: #690; 77 | } 78 | 79 | .token.operator, 80 | .token.entity, 81 | .token.url, 82 | .language-css .token.string, 83 | .style .token.string { 84 | color: #a67f59; 85 | background: hsla(0,0%,100%,.5); 86 | } 87 | 88 | .token.atrule, 89 | .token.attr-value, 90 | .token.keyword { 91 | color: #07a; 92 | } 93 | 94 | 95 | .token.regex, 96 | .token.important { 97 | color: #e90; 98 | } 99 | 100 | .token.important { 101 | font-weight: bold; 102 | } 103 | 104 | .token.entity { 105 | cursor: help; 106 | } 107 | pre[data-line] { 108 | position: relative; 109 | padding: 1em 0 1em 3em; 110 | } 111 | 112 | .line-highlight { 113 | position: absolute; 114 | left: 0; 115 | right: 0; 116 | padding: inherit 0; 117 | margin-top: 1em; /* Same as .prism’s padding-top */ 118 | 119 | background: hsla(24, 20%, 50%,.08); 120 | background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 121 | background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 122 | background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 123 | background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 124 | 125 | pointer-events: none; 126 | 127 | line-height: inherit; 128 | white-space: pre; 129 | } 130 | 131 | .line-highlight:before, 132 | .line-highlight[data-end]:after { 133 | content: attr(data-start); 134 | position: absolute; 135 | top: .4em; 136 | left: .6em; 137 | min-width: 1em; 138 | padding: 0 .5em; 139 | background-color: hsla(24, 20%, 50%,.4); 140 | color: hsl(24, 20%, 95%); 141 | font: bold 65%/1.5 sans-serif; 142 | text-align: center; 143 | vertical-align: .3em; 144 | border-radius: 999px; 145 | text-shadow: none; 146 | box-shadow: 0 1px white; 147 | } 148 | 149 | .line-highlight[data-end]:after { 150 | content: attr(data-end); 151 | top: auto; 152 | bottom: .4em; 153 | } 154 | -------------------------------------------------------------------------------- /sections/pagination/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Client side Pagination

3 | improve the documentation 4 |

If you don't want to display too much data at the time you'll be happy using the pagination feature. It uses the angular.ui-boostrap pagination directive and you'll be able 5 | to configure it through some global config properties. First of all, the property isPaginationEnabled must be set to true. Then you can decide the 6 | number of pages you want to display with the maxSize property and the number of items you want to display on a page thanks to the itemsByPage property

7 | 12 | 13 | 14 | 15 |
app.controller('paginationCtrl', ['$scope', function (scope) {
16 |     var
17 |             nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'],
18 |             familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez'];
19 | 
20 |     function createRandomItem() {
21 |         var
22 |                 firstName = nameList[Math.floor(Math.random() * 4)],
23 |                 lastName = familyName[Math.floor(Math.random() * 4)],
24 |                 age = Math.floor(Math.random() * 100),
25 |                 email = firstName + lastName + '@whatever.com',
26 |                 balance = Math.random() * 3000;
27 | 
28 |         return{
29 |             firstName: firstName,
30 |             lastName: lastName,
31 |             age: age,
32 |             email: email,
33 |             balance: balance
34 |         };
35 |     }
36 | 
37 |     scope.rowCollection = [];
38 |     for (var j = 0; j < 200; j++) {
39 |         scope.rowCollection.push(createRandomItem());
40 |     }
41 | 
42 |     scope.columnCollection = [
43 |         {label: 'First Name', map: 'firstName'},
44 |         {label: 'Last Name', map: 'lastName'},
45 |         {label: 'Age', map: 'age'},
46 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
47 |         {label: 'e-mail', map: 'email'}
48 |     ];
49 | 
50 |     scope.globalConfig = {
51 |         isPaginationEnabled: true,
52 |         itemsByPage: 12,
53 |         maxSize: 8
54 |     };
55 | }]);
56 |
57 | 58 |
<div ng-controller="paginationCtrl">
59 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
60 | </div>
61 |
62 |
63 | 64 |
65 | 66 |
67 |
-------------------------------------------------------------------------------- /sections/sort/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Sort data

3 | improve the documentation 4 |

You may also want to sort the data rows. By default the table sort the data rows following the orderBy angular algorithm but you can also use your own 5 | algorithm (see below). When you click on one of the column header the orderBy algorithm will sort the data rows according to these rules 6 |

7 | 11 | 12 |

You can also disable the sort function for a given column by setting the isSortable property to false (it is true by default)

13 | 14 | 15 |
app.controller('sortCtrl', ['$scope', function (scope) {
16 |     scope.rowCollection = [
17 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
18 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
19 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
20 |     ];
21 | 
22 |     scope.columnCollection = [
23 |         {label: 'First Name', map: 'firstName', sortPredicate: function (dataRow) {
24 | //predicate as a function (see angular orderby documentation) : it will sort by the string length
25 |             return dataRow.firstName.length;
26 |         } },
27 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
28 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
29 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
30 |         {label: 'e-mail', map: 'email', isSortable: false}
31 |     ];
32 | }]);
33 |
34 | 35 |
<div ng-controller="sortCtrl">
36 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
37 | </div>
38 |
39 |
40 |
41 | 42 |
43 |

Use your own algorithm

44 |

If you would like to use your own sort algorithm instead of the orderBy from Angular you also can. That is where our table configuration comes. 45 | You can bind the table-configuration attribute to an object that will represent the table "global" configuration. One of the global property is the 46 | sort algorithm (sortAlgorithm). 47 |

48 |
function customSortAlgorithm(arrayRef, sortPredicate, reverse) {
49 | //do some stuff
50 |     return sortedArray;
51 | }
52 |
-------------------------------------------------------------------------------- /sections/basics/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

The basics

3 | improve the documentation 4 |

5 | If you want to display data in a table it is really easy. You simply have to add the smart-table tag 6 | into your markup (it is an element directive) and bind the rows (see markup tab) attribute 7 | to an array somewhere in the scope. In our example we specify the data used to fill the table in a 8 | controller. 9 |

10 | 18 |
19 | 20 | 21 |
app.controller('basicsCtrl', ['$scope', function (scope) {
22 |     scope.rowCollection = [
23 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
24 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
25 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
26 |     ];
27 | }]);
28 |
29 | 30 |
<div ng-controller="basicsCtrl">
31 |     <smart-table rows="rowCollection"></smart-table>
32 | </div>
33 |
34 |
35 |
36 |
37 | 38 |
39 |

40 | You can also specify the layout you want. That is where we introduce the column configuration. On our markup 41 | we bind the columns attribute to an array in which we specify 42 | how to display the data. The label property will give the header of the column whereas the map property 43 | is the property in our data-model object we want a given column 44 | to be bound to. A column will be inserted by column configuration. 45 |

46 | 47 |

The mapping can support multi-level mapping like "myObject.myInnerProperty"

48 | 49 |
50 | 51 | 52 |
app.controller('basicsCtrl', ['$scope', function (scope) {
53 |     scope.rowCollection = [
54 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
55 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
56 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
57 |     ];
58 | 
59 |     scope.columnCollection = [
60 |         {label: 'First Name', map: 'firstName'},
61 |         {label: 'same same but different', map: 'firstName'},
62 |         {label: 'Last Name', map: 'lastName'}
63 |     ];
64 | }]);
65 |
66 | 67 |
<div ng-controller="basicsCtrl">
68 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
69 | </div>
70 |
71 |
72 |
73 |
74 | 75 |
76 |
77 | -------------------------------------------------------------------------------- /sections/selection/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Select data rows

3 | improve the documentation 4 |

You can select data row according to two different modes : single or multiple (of course you can disable selection) by setting the global 5 | property selectionMode to single or multiple (any other value will be taken as selectionMode='none'). This is the only time the table changes 6 | a property value into your model: it add the "isSelected" property to your data model object. On one hand you can consider it "pollutes" your model but 7 | on the other hand it allows the parent scope to quickly have a list of the selected items without relying on events. If you are in selectionMode="multiple" you can add a selection checkbox 8 | column by setting the global property displaySelectionCheckbox to true

9 |

Even though the configuration is dynamic the table does not prevent forbidden states : for example if you switch from selectionMode='single' 10 | to 'none' with a selected item, the item will remain selected. It is the responsability of the parent scope/controller to reset the selected item

11 | 12 |

Keep track of the changes in selection

13 |

As mentioned before, whenever a row is selected/unselected the property isSelected change on the particular bound item. You can of course watch your model 14 | to see when the selected items change however this is not really efficient regarding the performances. That is why an selectionChange event is emitted every time 15 | there is something changed. 16 |

17 | 18 | 19 | 20 |
app.controller('selectionCtrl', ['$scope', function (scope) {
21 |     scope.rowCollection = [
22 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
23 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
24 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
25 |     ];
26 | 
27 |     scope.columnCollection = [
28 |         {label: 'First Name', map: 'firstName'},
29 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
30 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
31 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
32 |         {label: 'e-mail', map: 'email'}
33 |     ];
34 | 
35 |     //keep track of selected items
36 |     scope.$on('selectionChange', function (event, args) {
37 |         console.log(args);
38 |     });
39 | 
40 |     scope.globalConfig = {
41 |         selectionMode: 'multiple',
42 |         displaySelectionCheckbox: true
43 |     };
44 | }]);
45 |
46 | 47 |
<div ng-controller="selectionCtrl">
48 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
49 | </div>
50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 | 58 | 59 | 64 |
65 | 68 | 69 | 70 |
-------------------------------------------------------------------------------- /sections/cellTemplate/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

Cell Template

3 | improve the documentation 4 |

If you prefer to use a particular template for the cells in a given column, you can do it by specifying the cellTemplateUrl property in the column config 5 | this is useful not only for styling purpose but also if you want to enhance the behavior of the cell. The template will be compiled so that if it contains 6 | directives they will be considered as angular directives. Moreover, these directives can have access to all the row and column data through the scope 7 |

8 |
scope.column;//the column object
  9 | scope.dataRow;//the data row object
 10 | scope.formatedValue;//the formated value (not the raw string)
11 | 14 | 15 | 16 |
app.controller('cellTemplateCtrl', ['$scope', function (scope) {
 18 |     scope.rowCollection = [
 19 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
 20 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
 21 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
 22 |     ];
 23 | 
 24 |     scope.columnCollection = [
 25 |         {label: 'First Name', map: 'firstName'},
 26 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
 27 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
 28 |         {label: 'Balance', map: 'balance', formatFunction: 'currency'},
 29 |         {label: 'e-mail', map: 'email'},
 30 |         {label: 'Favourite color', cellTemplateUrl: 'assets/template/custom.html'}
 31 |     ];
 32 | }]);
33 |
34 | 35 |
<div>
 36 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
 37 | </div>
38 |
39 | 40 |
<custom></custom>
41 |
42 | 43 |
app.directive('custom', function () {
 44 |     return {
 45 |         restrict: 'E',
 46 |         //include smart table controller to use its API if needed
 47 |         require: '^smartTable',
 48 |         template: '<select ng-model="favouriteColor">' +
 49 |                 '<option value="">--choose favorite color--</option>' +
 50 |                 '<option value="red">red</option>' +
 51 |                 '<option value="blue">blue</option>' +
 52 |                 '<option value="yellow">yellow</option>' +
 53 |                 '</select>',
 54 |         replace: true,
 55 |         link: function (scope, element, attrs, ctrl) {
 56 |             //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API
 57 | 
 58 |             var allowedColors = ['red', 'yellow', 'blue'];
 59 | 
 60 |             scope.$watch('favouriteColor', function (value) {
 61 |                 if (allowedColors.indexOf(value) != -1) {
 62 |                     scope.dataRow.favouriteColor = scope.favouriteColor;
 63 |                 }
 64 |             });
 65 |         }
 66 |     };
 67 | });
68 |
69 |
70 |
71 | 72 |
73 | 76 |

In the same way you can assign a custom template to the column header thanks to the column property headerTemplateUrl. See the source code 77 | of the previous filter section as an example.

78 | 79 | 80 |
app.controller('filterCtrl', ['$scope', function (scope) {
 81 |     scope.rowCollection = [
 82 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
 83 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
 84 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
 85 |     ];
 86 | 
 87 |     scope.columnCollection = [
 88 |         {label: 'First Name', map: 'firstName'},
 89 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'},
 90 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
 91 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
 92 |         {label: 'e-mail', map: 'email'}
 93 |     ];
 94 |     scope.globalConfig = {
 95 |         isGlobalSearchActivated: true
 96 |     };
 97 | }]);
98 |
99 | 100 |
<div ng-controller="filterCtrl">
101 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
102 | </div>
103 |
104 | 105 |
<span>{{column.label}}</span>
106 | <input class="column-filter" type="text" ng-model="searchValue" />
107 |
108 | 109 |
app.directive('columnFilter', function () {
110 |     return {
111 |         restrict: 'C',
112 |         require: '^smartTable',
113 |         link: function (scope, element, attrs, ctrl) {
114 |             scope.searchValue = '';
115 |             scope.$watch('searchValue', function (value) {
116 |                 ctrl.search(value, scope.column);
117 |             })
118 |         }
119 |     }
120 | });
121 |
122 |
123 |
124 | -------------------------------------------------------------------------------- /assets/lib/prism/prism.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * MIT license http://www.opensource.org/licenses/mit-license.php/ 4 | * @author Lea Verou http://lea.verou.me 5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(//g,">").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data),o);l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar,l.language);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r,i){return n.stringify(t.tokenize(e,r),i)},tokenize:function(e,n,r){var i=t.Token,s=[e],o=n.rest;if(o){for(var u in o)n[u]=o[u];delete n.rest}e:for(var u in n){if(!n.hasOwnProperty(u)||!n[u])continue;var a=n[u],f=a.inside,l=!!a.lookbehind||0;a=a.pattern||a;for(var c=0;ce.length)break e;if(h instanceof i)continue;a.lastIndex=0;var p=a.exec(h);if(p){l&&(l=p[1].length);var d=p.index-1+l,p=p[0].slice(l),v=p.length,m=d+v,g=h.slice(0,d+1),y=h.slice(m+1),b=[c,1];g&&b.push(g);var w=new i(u,f?t.tokenize(p,f):p);b.push(w);y&&b.push(y);Array.prototype.splice.apply(s,b)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; 6 | Prism.languages.markup={comment:/<!--[\w\W]*?--(>|>)/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]+?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; 7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:/@[\w-]+?(\s+[^;{]+)?(?=\s*{|\s*;)/gi,url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\}]*(?=\s*\{)/g,property:/(\b|\B)[a-z-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});; 8 | Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|[^:]\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,number:/\b-?(0x)?\d*\.?[\da-f]+\b/g,operator:/[-+]{1,2}|!|=?<|=?>|={1,2}|(&){1,2}|\|?\||\?|\*|\//g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; 9 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,number:/\b(-?(0x)?\d*\.?[\da-f]+|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; 10 | (function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function n(e,t,n){var r=t.replace(/\s+/g,"").split(","),i=+e.getAttribute("data-line-offset")||0,s=parseFloat(getComputedStyle(e).lineHeight);for(var o=0,u;u=r[o++];){u=u.split("-");var a=+u[0],f=+u[1]||a,l=document.createElement("div");l.textContent=Array(f-a+2).join(" \r\n");l.className=(n||"")+" line-highlight";l.setAttribute("data-start",a);f>a&&l.setAttribute("data-end",f);l.style.top=(a-i-1)*s+"px";(e.querySelector("code")||e).appendChild(l)}}function r(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var r=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(!r||document.getElementById(t))return;var i=t.slice(0,t.lastIndexOf(".")),s=document.getElementById(i);if(!s)return;s.hasAttribute("data-line")||s.setAttribute("data-line","");n(s,r,"temporary ");document.querySelector(".temporary.line-highlight").scrollIntoView()}if(!window.Prism)return;var t=crlf=/\r?\n|\r/g,i=0;Prism.hooks.add("after-highlight",function(t){var s=t.element.parentNode,o=s&&s.getAttribute("data-line");if(!s||!o||!/pre/i.test(s.nodeName))return;clearTimeout(i);e(".line-highlight",s).forEach(function(e){e.parentNode.removeChild(e)});n(s,o);i=setTimeout(r,1)});addEventListener("hashchange",r)})();; 11 | -------------------------------------------------------------------------------- /assets/css/mainStyle.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | body{ 4 | margin:0; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | } 7 | 8 | a{ 9 | text-decoration: none; 10 | color: inherit; 11 | } 12 | 13 | p a{ 14 | color: #269; 15 | } 16 | 17 | strong{ 18 | color: #269; 19 | } 20 | 21 | header { 22 | text-align: center; 23 | background-color: #269; 24 | background-image: linear-gradient(rgba(255, 255, 255, .5) 1px, transparent 4px), linear-gradient(90deg, rgba(255, 255, 255, .5) 1px, transparent 4px), linear-gradient(rgba(255, 255, 255, .3) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, .3) 1px, transparent 1px); 25 | background-size: 40px 40px, 40px 40px, 10px 10px, 10px 10px; 26 | background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; 27 | box-shadow: 0px 3px 8px 2px rgba(80, 80, 80, 1); 28 | } 29 | 30 | .banner { 31 | padding: 30px 0; 32 | color: #ffffff; 33 | text-shadow: 0px 3px 30px #fafaff; 34 | z-index: 999; 35 | } 36 | 37 | ul{ 38 | list-style: square inside; 39 | } 40 | 41 | .main { 42 | padding: 0 20px; 43 | } 44 | 45 | .tab-container { 46 | margin: auto; 47 | } 48 | 49 | pre { 50 | margin: 0; 51 | border-radius: 3px; 52 | border: 3px solid rgba(240, 240, 240, 0.7); 53 | -webkit-box-shadow: inset 0px 0px 5px 1px rgba(0, 0, 0, 1); 54 | box-shadow: inset 0px 0px 5px 1px rgba(0, 0, 0, 1); 55 | } 56 | 57 | .table-container { 58 | width: 75%; 59 | margin: 20px auto; 60 | } 61 | 62 | .menu { 63 | margin: auto; 64 | text-align: center; 65 | position: fixed; 66 | background: #ffffff; 67 | top:0; 68 | padding: 0; 69 | width: 100%; 70 | } 71 | 72 | .menu.scroll-treshold{ 73 | background: #269; 74 | color: white; 75 | -webkit-transition: all 0.4s; 76 | -moz-transition: all 0.4s; 77 | -ms-transition: all 0.4s; 78 | -o-transition: all 0.4s; 79 | transition: all 0.4s; 80 | } 81 | 82 | .menu > ul{ 83 | margin: 0; 84 | display: inline-block; 85 | } 86 | 87 | .menu li { 88 | list-style: none; 89 | display: inline; 90 | padding: 8px; 91 | float: left; 92 | } 93 | 94 | .menu li:hover{ 95 | background: #226a9e; 96 | color: #ffffff; 97 | -webkit-transition: all 0.5s; 98 | -moz-transition: all 0.5s; 99 | -ms-transition: all 0.5s; 100 | -o-transition: all 0.5s; 101 | transition: all 0.5s; 102 | } 103 | 104 | .menu.scroll-treshold li:hover{ 105 | background: white; 106 | color: #269; 107 | } 108 | 109 | .menu li:not(:last-child){ 110 | border-right: 1px solid #269; 111 | } 112 | 113 | .menu.scroll-treshold li:not(:last-child){ 114 | border-right: 1px solid #ffffff; 115 | } 116 | 117 | .selected { 118 | background-color: rgb(255, 238, 126); 119 | } 120 | 121 | .side-controls { 122 | margin-left: 15px; 123 | float: left; 124 | width: 20%; 125 | background: #d9e1e8; 126 | color: rgb(80, 80, 80); 127 | padding: 5px; 128 | border: 2px dashed #226a9e; 129 | } 130 | 131 | .output-list { 132 | display: inline-block; 133 | width: 70%; 134 | margin-left: 25px; 135 | list-style: none; 136 | background: #d9e1e8; 137 | color: rgb(80, 80, 80); 138 | border: 2px dashed #226a9e; 139 | padding: 10px; 140 | } 141 | 142 | section { 143 | margin-top: 20px; 144 | clear: both; 145 | overflow-y: hidden; 146 | text-align: center; 147 | } 148 | 149 | p { 150 | text-align: left; 151 | } 152 | 153 | ul { 154 | text-align: left; 155 | } 156 | 157 | .firstName-header { 158 | text-decoration: underline; 159 | } 160 | 161 | .lastName-cell { 162 | color: red; 163 | } 164 | 165 | em { 166 | color: #226a9e; 167 | background: #c1c2c2; 168 | } 169 | 170 | .note { 171 | padding: 10px; 172 | border-radius: 4px; 173 | background: #269; 174 | font-size: 0.9em; 175 | color: white; 176 | } 177 | 178 | .pagination { 179 | text-align: center; 180 | margin: 0; 181 | } 182 | 183 | th:not(.selection-header) { 184 | min-width: 100px; 185 | } 186 | 187 | h2 { 188 | border-bottom: 2px solid #226a9e; 189 | border-top: 2px solid #226a9e; 190 | } 191 | 192 | .header-content:before { 193 | content: ' '; 194 | position: relative; 195 | left: -5px; 196 | } 197 | 198 | .sort-ascent:before { 199 | content: "\25B4"; 200 | } 201 | 202 | .sort-descent:before { 203 | content: "\25BE"; 204 | } 205 | 206 | .property-name{ 207 | font-weight: bold; 208 | } 209 | 210 | .git{ 211 | float: right; 212 | color: white; 213 | background: #226a9e; 214 | margin-right: 30px; 215 | } 216 | 217 | .btn{ 218 | padding: 8px; 219 | cursor: pointer; 220 | border-radius: 3px; 221 | } 222 | 223 | .nav { 224 | margin-left: 0; 225 | list-style: none; 226 | } 227 | 228 | .nav > li > a { 229 | display: block; 230 | } 231 | 232 | .nav > li > a:hover, 233 | .nav > li > a:focus { 234 | text-decoration: none; 235 | background-color: #eeeeee; 236 | } 237 | 238 | .nav > li > a > img { 239 | max-width: none; 240 | } 241 | 242 | .nav > .pull-right { 243 | float: right; 244 | } 245 | 246 | .nav-header { 247 | display: block; 248 | padding: 3px 15px; 249 | font-size: 11px; 250 | font-weight: bold; 251 | line-height: 20px; 252 | color: #999999; 253 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 254 | text-transform: uppercase; 255 | } 256 | 257 | .nav li + .nav-header { 258 | margin-top: 9px; 259 | } 260 | 261 | .nav-list { 262 | padding-right: 15px; 263 | padding-left: 15px; 264 | margin-bottom: 0; 265 | } 266 | 267 | .nav-list > li > a, 268 | .nav-list .nav-header { 269 | margin-right: -15px; 270 | margin-left: -15px; 271 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 272 | } 273 | 274 | .nav-list > li > a { 275 | padding: 3px 15px; 276 | } 277 | 278 | .nav-list > .active > a, 279 | .nav-list > .active > a:hover, 280 | .nav-list > .active > a:focus { 281 | color: #ffffff; 282 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 283 | background-color: #0088cc; 284 | } 285 | 286 | .nav-list [class^="icon-"], 287 | .nav-list [class*=" icon-"] { 288 | margin-right: 2px; 289 | } 290 | 291 | .nav.nav-tabs { 292 | margin-bottom: 0; 293 | padding-left: 0; 294 | } 295 | 296 | .nav-tabs, 297 | .nav-pills { 298 | *zoom: 1; 299 | } 300 | 301 | .nav-tabs:before, 302 | .nav-pills:before, 303 | .nav-tabs:after, 304 | .nav-pills:after { 305 | display: table; 306 | line-height: 0; 307 | content: ""; 308 | } 309 | 310 | .nav-tabs:after, 311 | .nav-pills:after { 312 | clear: both; 313 | } 314 | 315 | .nav-tabs > li, 316 | .nav-pills > li { 317 | float: left; 318 | } 319 | 320 | .nav-tabs > li > a, 321 | .nav-pills > li > a { 322 | padding-right: 12px; 323 | padding-left: 12px; 324 | margin-right: 2px; 325 | line-height: 14px; 326 | } 327 | 328 | .nav-tabs > li { 329 | margin-bottom: -1px; 330 | } 331 | 332 | .nav-tabs > li > a { 333 | padding-top: 8px; 334 | padding-bottom: 8px; 335 | line-height: 20px; 336 | background: white; 337 | color: #269; 338 | border: 1px solid #269; 339 | -webkit-border-radius: 4px 4px 0 0; 340 | -moz-border-radius: 4px 4px 0 0; 341 | border-radius: 4px 4px 0 0; 342 | cursor: pointer; 343 | } 344 | 345 | .nav-tabs > li > a:hover{ 346 | background-color: rgba(34, 102, 153, 0.64); 347 | color: #f2f2f2; 348 | border-color: transparent; 349 | } 350 | 351 | .nav-tabs > .active > a, 352 | .nav-tabs > .active > a:hover, 353 | .nav-tabs > .active > a:focus { 354 | background-color: #269; 355 | color: white; 356 | } 357 | 358 | 359 | 360 | .tabbable { 361 | *zoom: 1; 362 | margin: 20px; 363 | } 364 | 365 | .tabbable:before, 366 | .tabbable:after { 367 | display: table; 368 | line-height: 0; 369 | content: ""; 370 | } 371 | 372 | .tabbable:after { 373 | clear: both; 374 | } 375 | 376 | .tab-content { 377 | border-top: 0; 378 | background-color: #269; 379 | background-image:linear-gradient(to right, rgba(255,255,255,0) 0%,rgba(255,255,255,0.4) 80%); 380 | overflow: auto; 381 | padding: 10px; 382 | } 383 | 384 | .tab-content > .tab-pane, 385 | .pill-content > .pill-pane { 386 | display: none; 387 | } 388 | 389 | .tab-content > .active, 390 | .pill-content > .active { 391 | display: block; 392 | } 393 | 394 | table { 395 | max-width: 100%; 396 | background-color: transparent; 397 | border-collapse: collapse; 398 | border-spacing: 0; 399 | margin: auto; 400 | } 401 | 402 | .table { 403 | width: 100%; 404 | margin-bottom: 20px; 405 | } 406 | 407 | .table .smart-table-header-title{ 408 | padding: 0; 409 | } 410 | 411 | .table th, 412 | .table td { 413 | padding: 8px; 414 | line-height: 20px; 415 | text-align: left; 416 | vertical-align: top; 417 | border: 1px solid rgba(34, 102, 153, 0.52); 418 | } 419 | 420 | .table th { 421 | background: #269; 422 | color: white; 423 | font-weight: bold; 424 | text-align: center; 425 | 426 | } 427 | 428 | .table th:not(:last-child){ 429 | border-right:1px solid white; 430 | } 431 | 432 | .table thead th { 433 | vertical-align: bottom; 434 | } 435 | 436 | .table tbody + tbody { 437 | border-top: 2px solid #dddddd; 438 | } 439 | 440 | .pagination ul { 441 | display: inline-block; 442 | *display: inline; 443 | margin-top: 0; 444 | margin-bottom: 0; 445 | margin-left: 0; 446 | -webkit-border-radius: 4px; 447 | -moz-border-radius: 4px; 448 | border-radius: 4px; 449 | *zoom: 1; 450 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 451 | -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 452 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 453 | } 454 | 455 | .pagination ul > li { 456 | display: inline; 457 | } 458 | 459 | .pagination ul > li > a, 460 | .pagination ul > li > span { 461 | float: left; 462 | padding: 4px 12px; 463 | line-height: 20px; 464 | text-decoration: none; 465 | background-color: #ffffff; 466 | border: 1px solid #dddddd; 467 | border-left-width: 0; 468 | } 469 | 470 | .pagination ul > li > a:hover, 471 | .pagination ul > li > a:focus, 472 | .pagination ul > .active > a, 473 | .pagination ul > .active > span { 474 | background-color: #f5f5f5; 475 | } 476 | 477 | .pagination ul > .active > a, 478 | .pagination ul > .active > span { 479 | color: #999999; 480 | cursor: default; 481 | } 482 | 483 | .pagination ul > .disabled > span, 484 | .pagination ul > .disabled > a, 485 | .pagination ul > .disabled > a:hover, 486 | .pagination ul > .disabled > a:focus { 487 | color: #999999; 488 | cursor: default; 489 | background-color: transparent; 490 | } 491 | 492 | .pagination ul > li:first-child > a, 493 | .pagination ul > li:first-child > span { 494 | border-left-width: 1px; 495 | -webkit-border-bottom-left-radius: 4px; 496 | border-bottom-left-radius: 4px; 497 | -webkit-border-top-left-radius: 4px; 498 | border-top-left-radius: 4px; 499 | -moz-border-radius-bottomleft: 4px; 500 | -moz-border-radius-topleft: 4px; 501 | } 502 | 503 | .pagination ul > li:last-child > a, 504 | .pagination ul > li:last-child > span { 505 | -webkit-border-top-right-radius: 4px; 506 | border-top-right-radius: 4px; 507 | -webkit-border-bottom-right-radius: 4px; 508 | border-bottom-right-radius: 4px; 509 | -moz-border-radius-topright: 4px; 510 | -moz-border-radius-bottomright: 4px; 511 | } 512 | 513 | 514 | .icon{ 515 | background-image: url("../img/glyphicons-halflings.png"); 516 | } 517 | 518 | .icon-white { 519 | background-image: url("../img/glyphicons-halflings-white.png"); 520 | } 521 | 522 | [class^="icon-"], 523 | [class*=" icon-"] { 524 | display: inline-block; 525 | width: 14px; 526 | height: 14px; 527 | margin-top: 1px; 528 | *margin-right: .3em; 529 | line-height: 14px; 530 | vertical-align: text-top; 531 | background-position: 14px 14px; 532 | background-repeat: no-repeat; 533 | } 534 | 535 | 536 | .icon-download-alt { 537 | background-position: -96px -24px; 538 | } 539 | 540 | button, 541 | input, 542 | select, 543 | textarea { 544 | margin: 0; 545 | font-size: 100%; 546 | vertical-align: middle; 547 | } 548 | 549 | button, 550 | input { 551 | *overflow: visible; 552 | line-height: normal; 553 | } 554 | 555 | button::-moz-focus-inner, 556 | input::-moz-focus-inner { 557 | padding: 0; 558 | border: 0; 559 | } 560 | 561 | button, 562 | html input[type="button"], 563 | input[type="reset"], 564 | input[type="submit"] { 565 | cursor: pointer; 566 | -webkit-appearance: button; 567 | } 568 | 569 | label, 570 | select, 571 | button, 572 | input[type="button"], 573 | input[type="reset"], 574 | input[type="submit"], 575 | input[type="radio"], 576 | input[type="checkbox"] { 577 | cursor: pointer; 578 | } 579 | 580 | input[type="search"] { 581 | -webkit-box-sizing: content-box; 582 | -moz-box-sizing: content-box; 583 | box-sizing: content-box; 584 | -webkit-appearance: textfield; 585 | } 586 | 587 | input[type="search"]::-webkit-search-decoration, 588 | input[type="search"]::-webkit-search-cancel-button { 589 | -webkit-appearance: none; 590 | } 591 | 592 | textarea { 593 | overflow: auto; 594 | vertical-align: top; 595 | } 596 | 597 | textarea:focus, 598 | input[type="text"]:focus, 599 | input[type="password"]:focus, 600 | input[type="datetime"]:focus, 601 | input[type="datetime-local"]:focus, 602 | input[type="date"]:focus, 603 | input[type="month"]:focus, 604 | input[type="time"]:focus, 605 | input[type="week"]:focus, 606 | input[type="number"]:focus, 607 | input[type="email"]:focus, 608 | input[type="url"]:focus, 609 | input[type="search"]:focus, 610 | input[type="tel"]:focus, 611 | input[type="color"]:focus, 612 | .uneditable-input:focus { 613 | border-color: rgba(82, 168, 236, 0.8); 614 | outline: 0; 615 | outline: thin dotted \9; 616 | /* IE6-9 */ 617 | 618 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 619 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 620 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 621 | } 622 | 623 | .icon-edit { 624 | background-position: -96px -72px; 625 | } 626 | 627 | .bubble{ 628 | padding: 5px; 629 | background: #269; 630 | border-radius: 3px; 631 | color: #ffffff; 632 | margin-left: 10px; 633 | } 634 | 635 | .bubble:before{ 636 | content: ""; 637 | position: relative; 638 | display: inline-block; 639 | width: 0; 640 | height: 0; 641 | border: 9px solid transparent; 642 | border-right-color:#269; 643 | left: -22px; 644 | top:2px; 645 | } 646 | 647 | .doc{ 648 | padding: 5px; 649 | border-radius: 3px; 650 | } 651 | 652 | .doc:hover{ 653 | background: #269; 654 | } 655 | 656 | .btn:hover{ 657 | background-color: rgba(34, 102, 153, 0.64); 658 | } 659 | 660 | #section-intro a{ 661 | color: #269; 662 | } 663 | 664 | #section-intro a:hover{ 665 | text-decoration: underline; 666 | } 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: laurentrenard 3 | * Date: 10/3/13 4 | * Time: 9:55 PM 5 | **/ 6 | var app = angular.module('app', ['ui.bootstrap.tabs', 'smartTable.table']); 7 | app.directive('custom', ['$log', function (log) { 8 | return { 9 | restrict: 'E', 10 | //include smart table controller to use its API if needed 11 | require: '^smartTable', 12 | template: '', 18 | replace: true, 19 | link: function (scope, element, attrs, ctrl) { 20 | 21 | var allowedColors = ['red', 'yellow', 'blue']; 22 | 23 | //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API 24 | scope.$watch('favouriteColor', function (value) { 25 | if (allowedColors.indexOf(value) != -1) { 26 | scope.dataRow.favouriteColor = scope.favouriteColor; 27 | } 28 | }); 29 | } 30 | } 31 | }]); 32 | app.directive('columnFilter', function () { 33 | return { 34 | restrict: 'C', 35 | require: '^smartTable', 36 | link: function (scope, element, attrs, ctrl) { 37 | scope.searchValue = ''; 38 | scope.$watch('searchValue', function (value) { 39 | ctrl.search(value, scope.column); 40 | }) 41 | } 42 | } 43 | }); 44 | app.controller('mainCtrl', ['$scope', function (scope) { 45 | scope.greeting = 'hello Laurent'; 46 | }]); 47 | 48 | app.directive('scrollTreshold', ['$window', function (window) { 49 | return { 50 | link: function (scope, element, attr) { 51 | var treshold = attr.scrollTreshold || 100; 52 | window.addEventListener('scroll', function (event) { 53 | if (window.scrollY > treshold) { 54 | element.addClass('scroll-treshold'); 55 | } else { 56 | element.removeClass('scroll-treshold'); 57 | } 58 | }); 59 | } 60 | } 61 | }]); 62 | app.controller('basicsCtrl', ['$scope', function (scope) { 63 | scope.rowCollection = [ 64 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 65 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 66 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 67 | ]; 68 | 69 | scope.columnCollection = [ 70 | {label: 'First Name', map: 'firstName'}, 71 | {label: 'same same but different', map: 'firstName'}, 72 | {label: 'Last Name', map: 'lastName'} 73 | ]; 74 | }]); 75 | 76 | app.controller('formatCtrl', ['$scope', function (scope) { 77 | scope.rowCollection = [ 78 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 79 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 80 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 81 | ]; 82 | 83 | scope.columnCollection = [ 84 | {label: 'First Name', map: 'firstName', formatFunction: function (value, formatParameter) { 85 | //this only display the first letter 86 | return value[0]; 87 | }}, 88 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 89 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 90 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'} 91 | ]; 92 | }]); 93 | app.controller('sortCtrl', ['$scope', '$filter', function (scope, filter) { 94 | scope.rowCollection = [ 95 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 96 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 97 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 98 | ]; 99 | 100 | scope.columnCollection = [ 101 | {label: 'First Name', map: 'firstName', sortPredicate: function (dataRow) { 102 | //predicate as a function (see angular orderby documentation) : it will sort by the string length 103 | return dataRow.firstName.length; 104 | } }, 105 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 106 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 107 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 108 | {label: 'e-mail', map: 'email', isSortable: false} 109 | ]; 110 | }]); 111 | 112 | app.controller('filterCtrl', ['$scope', function (scope) { 113 | scope.rowCollection = [ 114 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 115 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 116 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 117 | ]; 118 | 119 | scope.columnCollection = [ 120 | {label: 'First Name', map: 'firstName'}, 121 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'}, 122 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 123 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 124 | {label: 'e-mail', map: 'email'} 125 | ]; 126 | scope.globalConfig = { 127 | isGlobalSearchActivated: true 128 | }; 129 | }]); 130 | 131 | app.controller('selectionCtrl', ['$scope', function (scope) { 132 | scope.rowCollection = [ 133 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 134 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 135 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 136 | ]; 137 | 138 | scope.columnCollection = [ 139 | {label: 'First Name', map: 'firstName'}, 140 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 141 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 142 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 143 | {label: 'e-mail', map: 'email'} 144 | ]; 145 | scope.globalConfig = { 146 | selectionMode: 'single', 147 | displaySelectionCheckbox: false 148 | }; 149 | 150 | scope.$on('selectionChange', function (event, args) { 151 | console.log(args); 152 | }); 153 | 154 | scope.canDisplayCheckbox = function () { 155 | return scope.globalConfig.selectionMode === 'multiple'; 156 | }; 157 | }]); 158 | 159 | app.controller('editCtrl', ['$scope', function (scope) { 160 | scope.rowCollection = [ 161 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 162 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 163 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 164 | ]; 165 | 166 | scope.$on('updateDataRow', function (event, arg) { 167 | console.log(arg); 168 | }); 169 | 170 | scope.columnCollection = [ 171 | {label: 'First Name', map: 'firstName', isEditable: true}, 172 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 173 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date', isEditable: true, type: 'date'}, 174 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$', isEditable: true, type: 'number'}, 175 | {label: 'e-mail', map: 'email', isEditable: true, type: 'email'} 176 | ]; 177 | }]); 178 | 179 | app.controller('styleCtrl', ['$scope', function (scope) { 180 | scope.rowCollection = [ 181 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 182 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 183 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 184 | ]; 185 | 186 | scope.columnCollection = [ 187 | {label: 'First Name', map: 'firstName', headerClass: "firstName-header"}, 188 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', cellClass: "lastName-cell"}, 189 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 190 | {label: 'Balance', map: 'balance', formatFunction: 'currency'}, 191 | {label: 'e-mail', map: 'email'} 192 | ]; 193 | }]); 194 | 195 | app.controller('cellTemplateCtrl', ['$scope', function (scope) { 196 | scope.rowCollection = [ 197 | {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'}, 198 | {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'}, 199 | {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'} 200 | ]; 201 | 202 | scope.columnCollection = [ 203 | {label: 'First Name', map: 'firstName'}, 204 | {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'}, 205 | {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'}, 206 | {label: 'Balance', map: 'balance', formatFunction: 'currency'}, 207 | {label: 'e-mail', map: 'email'}, 208 | {label: 'Favourite color', cellTemplateUrl: 'assets/template/custom.html'} 209 | ]; 210 | }]); 211 | 212 | app.controller('paginationCtrl', ['$scope', function (scope) { 213 | var 214 | nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'], 215 | familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez']; 216 | 217 | function createRandomItem() { 218 | var 219 | firstName = nameList[Math.floor(Math.random() * 4)], 220 | lastName = familyName[Math.floor(Math.random() * 4)], 221 | age = Math.floor(Math.random() * 100), 222 | email = firstName + lastName + '@whatever.com', 223 | balance = Math.random() * 3000; 224 | 225 | return{ 226 | firstName: firstName, 227 | lastName: lastName, 228 | age: age, 229 | email: email, 230 | balance: balance 231 | }; 232 | } 233 | 234 | scope.rowCollection = []; 235 | for (var j = 0; j < 200; j++) { 236 | scope.rowCollection.push(createRandomItem()); 237 | } 238 | 239 | scope.columnCollection = [ 240 | {label: 'First Name', map: 'firstName'}, 241 | {label: 'Last Name', map: 'lastName'}, 242 | {label: 'Age', map: 'age'}, 243 | {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}, 244 | {label: 'e-mail', map: 'email'} 245 | ]; 246 | 247 | scope.globalConfig = { 248 | isPaginationEnabled: true, 249 | itemsByPage: 12, 250 | maxSize: 8 251 | }; 252 | }]); 253 | 254 | app.controller('configCtrl', ['$scope', function (scope) { 255 | 256 | scope.rowCollectionColumn = [ 257 | {name: 'isSortable', description: 'tell whether we can sort by the given column', defaultValue: 'true'}, 258 | {name: 'isEditable', description: 'tell whether we can Edit the cell in the given column', defaultValue: 'false'}, 259 | {name: 'type', description: 'the type of the input if the column cells are editable', defaultValue: 'text'}, 260 | {name: 'headerTemplateUrl', description: 'the url to a custom template for the column header', defaultValue: 'partials/defaultHeader.html'}, 261 | {name: 'map', description: 'the property of the data model objects the column cell will be bound to', defaultValue: 'undefined'}, 262 | {name: 'label', description: 'the label of the header column', defaultValue: 'undefined'}, 263 | {name: 'sortPredicate', description: 'the predicate to use when we sort by the column', defaultValue: 'undefined'}, 264 | {name: 'formatFunction', description: 'the function or the filter name to use when formatting the column cells', defaultValue: 'undefined'}, 265 | {name: 'formatParameter', description: 'a parameter to pass to the formatFunction', defaultValue: 'undefined'}, 266 | {name: 'cellTemplateUrl', description: 'the url of the template if custom template is used for the column cells', defaultValue: 'undefined'}, 267 | {name: 'headerClass', description: 'a class name to add to the column header', defaultValue: 'undefined'}, 268 | {name: 'cellClass', description: 'a class name to add to the column cells', defaultValue: 'undefined'} 269 | ]; 270 | 271 | scope.rowCollectionConfig = [ 272 | {name: 'selectionMode', description: 'the selection mode used ("multiple","single","none"', defaultValue: 'none'}, 273 | {name: 'isGlobalSearchActivated', description: 'display or not a input to filter data rows globally', defaultValue: 'false'}, 274 | {name: 'displaySelectionCheckBox', description: 'if in multiple selection mode tell whether to display a column with checkbox for selection', defaultValue: 'false'}, 275 | {name: 'isPaginationEnabled', description: 'tell whether to display the pagination at the bottom of the table', defaultValue: 'true'}, 276 | {name: 'itemsByPage', description: 'the number of items displayed by page', defaultValue: '10'}, 277 | {name: 'maxSize', description: 'the maximum number of page links to display at the bottom', defaultValue: '5'}, 278 | {name: 'sortAlgorithm', description: 'a function if you want to use your own sort algorithm', defaultValue: 'undefined'}, 279 | {name: 'filterAlgorithm', description: 'a function if you want to use your own filter algorithm', defaultValue: 'undefined'} 280 | ]; 281 | 282 | scope.columnCollection = [ 283 | {label: 'Property Name', map: 'name', cellClass: 'property-name'}, 284 | {label: 'Description', map: 'description'}, 285 | {label: 'Default value', map: 'defaultValue'} 286 | ]; 287 | }]); 288 | -------------------------------------------------------------------------------- /assets/lib/smart-table/Smart-Table.min.js: -------------------------------------------------------------------------------- 1 | !function (a, b) { 2 | "use strict"; 3 | function c(a, c) { 4 | function d(a) { 5 | return this instanceof d ? (b.extend(this, a), void 0) : new d(a) 6 | } 7 | 8 | this.setDefaultOption = function (a) { 9 | b.extend(d.prototype, a) 10 | }, a.headerTemplateUrl = c.defaultHeader, this.setDefaultOption(a), this.$get = function () { 11 | return d 12 | } 13 | } 14 | 15 | var d = b.module("smartTable.column", ["smartTable.templateUrlList"]).constant("DefaultColumnConfiguration", {isSortable: !0, isEditable: !1, type: "text", headerTemplateUrl: "", map: "", label: "", sortPredicate: "", formatFunction: "", formatParameter: "", filterPredicate: "", cellTemplateUrl: "", headerClass: "", cellClass: ""}); 16 | c.$inject = ["DefaultColumnConfiguration", "templateUrlList"], d.provider("Column", c), a.ColumnProvider = c 17 | }(window, angular), function (a) { 18 | "use strict"; 19 | a.module("smartTable.directives", ["smartTable.templateUrlList", "smartTable.templates"]).directive("smartTable", ["templateUrlList", "DefaultTableConfiguration", function (b, c) { 20 | return{restrict: "EA", scope: {columnCollection: "=columns", dataCollection: "=rows", config: "="}, replace: "true", templateUrl: b.smartTable, controller: "TableCtrl", link: function (d, e, f, g) { 21 | var h; 22 | d.$watch("config", function (e) { 23 | var f = a.extend({}, c, e), h = void 0 !== d.columns ? d.columns.length : 0; 24 | if (g.setGlobalConfig(f), "multiple" !== f.selectionMode || f.displaySelectionCheckbox !== !0)for (var i = h - 1; i >= 0; i--)d.columns[i].isSelectionColumn === !0 && g.removeColumn(i); else g.insertColumn({cellTemplateUrl: b.selectionCheckbox, headerTemplateUrl: b.selectAllCheckbox, isSelectionColumn: !0}, 0) 25 | }, !0), d.$watch("columnCollection", function () { 26 | if (g.clearColumns(), d.columnCollection)for (var b = 0, c = d.columnCollection.length; c > b; b++)g.insertColumn(d.columnCollection[b]); else d.dataCollection && d.dataCollection.length > 0 && (h = d.dataCollection[0], a.forEach(h, function (a, b) { 27 | "$" != b[0] && g.insertColumn({label: b, map: b}) 28 | })) 29 | }, !0), d.$watch("dataCollection.length", function (a, b) { 30 | a !== b && g.sortBy() 31 | }) 32 | }} 33 | }]).directive("smartTableDataRow",function () { 34 | return{require: "^smartTable", restrict: "C", link: function (a, b, c, d) { 35 | b.bind("click", function () { 36 | a.$apply(function () { 37 | d.toggleSelection(a.dataRow) 38 | }) 39 | }) 40 | }} 41 | }).directive("smartTableHeaderCell",function () { 42 | return{restrict: "C", require: "^smartTable", link: function (a, b, c, d) { 43 | b.bind("click", function () { 44 | a.$apply(function () { 45 | d.sortBy(a.column) 46 | }) 47 | }) 48 | }} 49 | }).directive("smartTableSelectAll",function () { 50 | return{restrict: "C", require: "^smartTable", link: function (a, b, c, d) { 51 | b.bind("click", function () { 52 | d.toggleSelectionAll(b[0].checked === !0) 53 | }) 54 | }} 55 | }).directive("stopEvent",function () { 56 | return{restrict: "A", link: function (a, b, c) { 57 | b.bind(c.stopEvent, function (a) { 58 | a.stopPropagation() 59 | }) 60 | }} 61 | }).directive("smartTableGlobalSearch", ["templateUrlList", function (a) { 62 | return{restrict: "C", require: "^smartTable", scope: {columnSpan: "@"}, templateUrl: a.smartTableGlobalSearch, replace: !1, link: function (a, b, c, d) { 63 | a.searchValue = "", a.$watch("searchValue", function (a) { 64 | d.search(a) 65 | }) 66 | }} 67 | }]).directive("smartTableDataCell", ["$filter", "$http", "$templateCache", "$compile", "$parse", function (a, b, c, d, e) { 68 | return{restrict: "C", link: function (f, g) { 69 | function h() { 70 | j.isEditable ? (g.html('
'), d(g.contents())(f)) : g.text(f.formatedValue) 71 | } 72 | 73 | var i, j = f.column, k = !j.isEditable, l = f.dataRow, m = a("format"), n = e(j.map); 74 | f.$watch("dataRow", function () { 75 | f.formatedValue = m(n(l), j.formatFunction, j.formatParameter), k === !0 && g.text(f.formatedValue) 76 | }, !0), f.$watch("column.cellTemplateUrl", function (a) { 77 | a ? b.get(a, {cache: c}).success(function (a) { 78 | k = !1, i = f.$new(), g.html(a), d(g.contents())(i) 79 | }).error(h) : h() 80 | }) 81 | }} 82 | }]).directive("inputType",function () { 83 | return{restrict: "A", priority: 1, link: function (a, b, c) { 84 | var d = a.$eval(c.type); 85 | c.$set("type", d) 86 | }} 87 | }).directive("editableCell", ["templateUrlList", "$parse", function (b, c) { 88 | return{restrict: "EA", require: "^smartTable", templateUrl: b.editableCell, scope: {row: "=", column: "=", type: "="}, replace: !0, link: function (b, d, e, f) { 89 | var g = a.element(d.children()[1]), h = a.element(g.children()[0]), i = c(b.column.map); 90 | b.isEditMode = !1, b.$watch("row", function () { 91 | b.value = i(b.row) 92 | }, !0), b.submit = function () { 93 | b.myForm.$valid === !0 && (f.updateDataRow(b.row, b.column.map, b.value), f.sortBy()), b.toggleEditMode() 94 | }, b.toggleEditMode = function () { 95 | b.value = i(b.row), b.isEditMode = b.isEditMode !== !0 96 | }, b.$watch("isEditMode", function (a) { 97 | a === !0 && (h[0].select(), h[0].focus()) 98 | }), h.bind("blur", function () { 99 | b.$apply(function () { 100 | b.submit() 101 | }) 102 | }) 103 | }} 104 | }]) 105 | }(angular), function (a) { 106 | "use strict"; 107 | a.module("smartTable.filters", []).constant("DefaultFilters", ["currency", "date", "json", "lowercase", "number", "uppercase"]).filter("format", ["$filter", "DefaultFilters", function (b, c) { 108 | return function (d, e, f) { 109 | var g; 110 | return g = e && a.isFunction(e) ? e : -1 !== c.indexOf(e) ? b(e) : function (a) { 111 | return a 112 | }, g(d, f) 113 | } 114 | }]) 115 | }(angular), function (a) { 116 | "use strict"; 117 | a.module("smartTable.table", ["smartTable.column", "smartTable.utilities", "smartTable.directives", "smartTable.filters", "ui.bootstrap.pagination.smartTable"]).constant("DefaultTableConfiguration", {selectionMode: "none", isGlobalSearchActivated: !1, displaySelectionCheckbox: !1, isPaginationEnabled: !0, itemsByPage: 10, maxSize: 5, sortAlgorithm: "", filterAlgorithm: ""}).controller("TableCtrl", ["$scope", "Column", "$filter", "$parse", "ArrayUtility", "DefaultTableConfiguration", function (b, c, d, e, f, g) { 118 | function h() { 119 | var a, c = b.displayedCollection.length; 120 | for (a = 0; c > a; a++)if (b.displayedCollection[a].isSelected !== !0)return!1; 121 | return!0 122 | } 123 | 124 | function i(c) { 125 | return!a.isArray(c) || 0 === c.length || b.itemsByPage < 1 ? 1 : Math.ceil(c.length / b.itemsByPage) 126 | } 127 | 128 | function j(c, e) { 129 | var g = (b.sortAlgorithm && a.isFunction(b.sortAlgorithm)) === !0 ? b.sortAlgorithm : d("orderBy"); 130 | return e ? f.sort(c, g, e.sortPredicate, e.reverse) : c 131 | } 132 | 133 | function k(c, d, e, f) { 134 | var g, i; 135 | if (a.isArray(c) && ("multiple" === d || "single" === d) && e >= 0 && e < c.length) { 136 | if (g = c[e], "single" === d)for (var j = 0, k = c.length; k > j; j++)i = c[j].isSelected, c[j].isSelected = !1, i === !0 && b.$emit("selectionChange", {item: c[j]}); 137 | g.isSelected = f, b.holder.isAllSelected = h(), b.$emit("selectionChange", {item: g}) 138 | } 139 | } 140 | 141 | b.columns = [], b.dataCollection = b.dataCollection || [], b.displayedCollection = [], b.numberOfPages = i(b.dataCollection), b.currentPage = 1, b.holder = {isAllSelected: !1}; 142 | var l, m = {}; 143 | this.setGlobalConfig = function (c) { 144 | a.extend(b, g, c) 145 | }, this.changePage = function (c) { 146 | var d = b.currentPage; 147 | a.isNumber(c.page) && (b.currentPage = c.page, b.displayedCollection = this.pipe(b.dataCollection), b.holder.isAllSelected = h(), b.$emit("changePage", {oldValue: d, newValue: b.currentPage})) 148 | }, this.sortBy = function (a) { 149 | var c = b.columns.indexOf(a); 150 | -1 !== c && a.isSortable === !0 && (l && l !== a && (l.reverse = "none"), a.sortPredicate = a.sortPredicate || a.map, a.reverse = a.reverse !== !0, l = a), b.displayedCollection = this.pipe(b.dataCollection) 151 | }, this.search = function (a, c) { 152 | var d, e = b.columns.length; 153 | if (c && -1 !== b.columns.indexOf(c))m.$ = "", c.filterPredicate = a; else { 154 | for (d = 0; e > d; d++)b.columns[d].filterPredicate = ""; 155 | m.$ = a 156 | } 157 | for (d = 0; e > d; d++)m[b.columns[d].map] = b.columns[d].filterPredicate; 158 | b.displayedCollection = this.pipe(b.dataCollection) 159 | }, this.pipe = function (c) { 160 | var e, g = (b.filterAlgorithm && a.isFunction(b.filterAlgorithm)) === !0 ? b.filterAlgorithm : d("filter"); 161 | return e = j(f.filter(c, g, m), l), b.numberOfPages = i(e), b.isPaginationEnabled ? f.fromTo(e, (b.currentPage - 1) * b.itemsByPage, b.itemsByPage) : e 162 | }, this.insertColumn = function (a, d) { 163 | var e = new c(a); 164 | f.insertAt(b.columns, d, e) 165 | }, this.removeColumn = function (a) { 166 | f.removeAt(b.columns, a) 167 | }, this.moveColumn = function (a, c) { 168 | f.moveAt(b.columns, a, c) 169 | }, this.clearColumns = function () { 170 | b.columns.length = 0 171 | }, this.toggleSelection = function (a) { 172 | var c = b.dataCollection.indexOf(a); 173 | -1 !== c && k(b.dataCollection, b.selectionMode, c, a.isSelected !== !0) 174 | }, this.toggleSelectionAll = function (a) { 175 | var c = 0, d = b.displayedCollection.length; 176 | if ("multiple" === b.selectionMode)for (; d > c; c++)k(b.displayedCollection, b.selectionMode, c, a === !0) 177 | }, this.removeDataRow = function (a) { 178 | var c = f.removeAt(b.displayedCollection, a); 179 | f.removeAt(b.dataCollection, b.dataCollection.indexOf(c)) 180 | }, this.moveDataRow = function (a, c) { 181 | f.moveAt(b.displayedCollection, a, c) 182 | }, this.updateDataRow = function (a, c, d) { 183 | var f, g = b.displayedCollection.indexOf(a), h = e(c), i = h.assign; 184 | -1 !== g && (f = h(b.displayedCollection[g]), f !== d && (i(b.displayedCollection[g], d), b.$emit("updateDataRow", {item: b.displayedCollection[g]}))) 185 | } 186 | }]) 187 | }(angular), angular.module("smartTable.templates", ["partials/defaultCell.html", "partials/defaultHeader.html", "partials/editableCell.html", "partials/globalSearchCell.html", "partials/pagination.html", "partials/selectAllCheckbox.html", "partials/selectionCheckbox.html", "partials/smartTable.html"]), angular.module("partials/defaultCell.html", []).run(["$templateCache", function (a) { 188 | a.put("partials/defaultCell.html", "{{formatedValue}}") 189 | }]), angular.module("partials/defaultHeader.html", []).run(["$templateCache", function (a) { 190 | a.put("partials/defaultHeader.html", "{{column.label}}") 191 | }]), angular.module("partials/editableCell.html", []).run(["$templateCache", function (a) { 192 | a.put("partials/editableCell.html", '
\n {{value | format:column.formatFunction:column.formatParameter}}\n\n
\n \n
\n
') 193 | }]), angular.module("partials/globalSearchCell.html", []).run(["$templateCache", function (a) { 194 | a.put("partials/globalSearchCell.html", '\n') 195 | }]), angular.module("partials/pagination.html", []).run(["$templateCache", function (a) { 196 | a.put("partials/pagination.html", ' ') 197 | }]), angular.module("partials/selectAllCheckbox.html", []).run(["$templateCache", function (a) { 198 | a.put("partials/selectAllCheckbox.html", '') 199 | }]), angular.module("partials/selectionCheckbox.html", []).run(["$templateCache", function (a) { 200 | a.put("partials/selectionCheckbox.html", '') 201 | }]), angular.module("partials/smartTable.html", []).run(["$templateCache", function (a) { 202 | a.put("partials/smartTable.html", '\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n\n\n') 203 | }]), function (a) { 204 | "use strict"; 205 | a.module("smartTable.templateUrlList", []).constant("templateUrlList", {smartTable: "partials/smartTable.html", smartTableGlobalSearch: "partials/globalSearchCell.html", editableCell: "partials/editableCell.html", selectionCheckbox: "partials/selectionCheckbox.html", selectAllCheckbox: "partials/selectAllCheckbox.html", defaultHeader: "partials/defaultHeader.html", pagination: "partials/pagination.html"}) 206 | }(angular), function (a) { 207 | "use strict"; 208 | a.module("smartTable.utilities", []).factory("ArrayUtility", function () { 209 | var b = function (a, b) { 210 | return b >= 0 && b < a.length ? a.splice(b, 1)[0] : void 0 211 | }, c = function (a, b, c) { 212 | b >= 0 && b < a.length ? a.splice(b, 0, c) : a.push(c) 213 | }, d = function (a, b, c) { 214 | var d; 215 | b >= 0 && b < a.length && c >= 0 && c < a.length && (d = a.splice(b, 1)[0], a.splice(c, 0, d)) 216 | }, e = function (b, c, d, e) { 217 | return c && a.isFunction(c) ? c(b, d, e === !0) : b 218 | }, f = function (b, c, d) { 219 | return c && a.isFunction(c) ? c(b, d) : b 220 | }, g = function (b, c, d) { 221 | var e, f, g = []; 222 | if (!a.isArray(b))return b; 223 | f = Math.max(c, 0), f = Math.min(f, b.length - 1 > 0 ? b.length - 1 : 0), d = Math.max(0, d), e = Math.min(f + d, b.length); 224 | for (var h = f; e > h; h++)g.push(b[h]); 225 | return g 226 | }; 227 | return{removeAt: b, insertAt: c, moveAt: d, sort: e, filter: f, fromTo: g} 228 | }) 229 | }(angular), function (a) { 230 | a.module("ui.bootstrap.pagination.smartTable", ["smartTable.templateUrlList"]).constant("paginationConfig", {boundaryLinks: !1, directionLinks: !0, firstText: "First", previousText: "<", nextText: ">", lastText: "Last"}).directive("paginationSmartTable", ["paginationConfig", "templateUrlList", function (b, c) { 231 | return{restrict: "EA", require: "^smartTable", scope: {numPages: "=", currentPage: "=", maxSize: "="}, templateUrl: c.pagination, replace: !0, link: function (c, d, e, f) { 232 | function g(a, b, c, d) { 233 | return{number: a, text: b, active: c, disabled: d} 234 | } 235 | 236 | var h = a.isDefined(e.boundaryLinks) ? c.$eval(e.boundaryLinks) : b.boundaryLinks, i = a.isDefined(e.directionLinks) ? c.$eval(e.directionLinks) : b.directionLinks, j = a.isDefined(e.firstText) ? e.firstText : b.firstText, k = a.isDefined(e.previousText) ? e.previousText : b.previousText, l = a.isDefined(e.nextText) ? e.nextText : b.nextText, m = a.isDefined(e.lastText) ? e.lastText : b.lastText; 237 | c.$watch("numPages + currentPage + maxSize", function () { 238 | c.pages = []; 239 | var a = 1, b = c.numPages; 240 | c.maxSize && c.maxSize < c.numPages && (a = Math.max(c.currentPage - Math.floor(c.maxSize / 2), 1), b = a + c.maxSize - 1, b > c.numPages && (b = c.numPages, a = b - c.maxSize + 1)); 241 | for (var d = a; b >= d; d++) { 242 | var e = g(d, d, c.isActive(d), !1); 243 | c.pages.push(e) 244 | } 245 | if (i) { 246 | var f = g(c.currentPage - 1, k, !1, c.noPrevious()); 247 | c.pages.unshift(f); 248 | var n = g(c.currentPage + 1, l, !1, c.noNext()); 249 | c.pages.push(n) 250 | } 251 | if (h) { 252 | var o = g(1, j, !1, c.noPrevious()); 253 | c.pages.unshift(o); 254 | var p = g(c.numPages, m, !1, c.noNext()); 255 | c.pages.push(p) 256 | } 257 | c.currentPage > c.numPages && c.selectPage(c.numPages) 258 | }), c.noPrevious = function () { 259 | return 1 === c.currentPage 260 | }, c.noNext = function () { 261 | return c.currentPage === c.numPages 262 | }, c.isActive = function (a) { 263 | return c.currentPage === a 264 | }, c.selectPage = function (a) { 265 | !c.isActive(a) && a > 0 && a <= c.numPages && (c.currentPage = a, f.changePage({page: a})) 266 | } 267 | }} 268 | }]) 269 | }(angular); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Smart Table documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

Smart Table

17 | Code on github 18 | 31 |
32 |
33 |
34 |

Introduction

35 | improve the documentation 36 | 37 |

Smart table is an Angularjs module to easily display data in a table with a set 38 | of built in functionalities such filetering, 39 | sorting, etc. While developing this module I made sure to focus on this particular points:

40 |
    41 |
  • lightweight: smart-table is less than 13kb minified and has no 42 | dependencies other than Angular itself 43 |
  • 44 |
  • robust: smart-table is widely tested which makes the module really stable
  • 45 |
  • useful: smart-table will allow you to have a table on which you will be able to perform 46 | common operations with simple configuration 47 |
  • 48 |
  • developer friendly: the design of the module has been thought carefully and it is really 49 | easy to get into the source code to modify/customise the module to best fit your needs. 50 | You will find for example: 51 |
      52 |
    • on the different branches of the project some custom implementation to turn 53 | smart-table into a server driven table while changing less than 10 lines 54 |
    • 55 | 56 |
    • on plunker(here made by Jiri Kavulak) some custom implementation with richer 57 | functionalities 58 |
    • 59 |
    60 |
61 |

Although smart-table is from far the best table module for angular :D, there are other table modules in the 62 | 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:

63 | 67 | 68 |

If you want to play around, try this plunker

69 |
70 |
71 |

The basics

72 | improve the documentation 73 |

74 | If you want to display data in a table it is really easy. You simply have to add the smart-table tag 75 | into your markup (it is an element directive) and bind the rows (see markup tab) attribute 76 | to an array somewhere in the scope. In our example we specify the data used to fill the table in a 77 | controller. 78 |

79 |
    80 |
  • Without anything else, the table will use all the keys (which don't start with '$' to avoid collision 81 | with angular related properties) as headers for the table 82 |
  • 83 |
  • It will also take default configuration values : here for instance you can sort by column as the 84 | columns are sortable by default 85 |
  • 86 |
87 |
88 | 89 | 90 |
app.controller('basicsCtrl', ['$scope', function (scope) {
 91 |     scope.rowCollection = [
 92 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
 93 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
 94 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
 95 |     ];
 96 | }]);
97 |
98 | 99 |
<div ng-controller="basicsCtrl">
100 |     <smart-table rows="rowCollection"></smart-table>
101 | </div>
102 |
103 |
104 |
105 |
106 | 107 |
108 |

109 | You can also specify the layout you want. That is where we introduce the column configuration. On our markup 110 | we bind the columns attribute to an array in which we specify 111 | how to display the data. The label property will give the header of the column whereas the map property 112 | is the property in our data-model object we want a given column 113 | to be bound to. A column will be inserted by column configuration. 114 |

115 | 116 |

The mapping can support multi-level mapping like "myObject.myInnerProperty"

117 | 118 |
119 | 120 | 121 |
app.controller('basicsCtrl', ['$scope', function (scope) {
122 |     scope.rowCollection = [
123 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
124 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
125 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
126 |     ];
127 | 
128 |     scope.columnCollection = [
129 |         {label: 'First Name', map: 'firstName'},
130 |         {label: 'same same but different', map: 'firstName'},
131 |         {label: 'Last Name', map: 'lastName'}
132 |     ];
133 | }]);
134 |
135 | 136 |
<div ng-controller="basicsCtrl">
137 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
138 | </div>
139 |
140 |
141 |
142 |
143 | 144 |
145 |
146 | 147 |
148 |

Format data

149 | improve the documentation 150 |

151 | In our previous examples the data were displayed as raw strings,but what if we want to format date, currency and so on in a proper way ? 152 | In the column configuration, you can specify how to format the data for the given column by adding formatFunction and formatParameter 153 | properties. 154 |

155 |
    156 |
  • The formatFunction can be a function either a string 157 |
      158 |
    • If it is a string, it is expected to be one of the angular filters name. The displayed data will be as if you have used the angular filter.
    • 159 |
    • If you use a function, it must take the value to format as first parameter, then an optional formatParameter 160 |
      function (value, formatParameter) {
      161 |     //do some stuff
      162 |     return formatedValue;
      163 | }
      164 |
    • 165 |
    166 |
  • 167 |
  • The formatParameter is an optional parameter to pass to the format function. If you use one of the built in angular filter it will be for example the symbol you pass to the currency filter.
  • 168 |
169 | 170 | 171 |
app.controller('sortCtrl', ['$scope', function (scope) {
172 |     scope.rowCollection = [
173 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
174 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
175 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
176 |     ];
177 | 
178 |     scope.columnCollection = [
179 |         {label: 'First Name', map: 'firstName', formatFunction: function (value, formatParameter) {
180 |             //this only display the first letter
181 |             return value[0];
182 |         }},
183 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
184 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
185 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'}
186 |     ];
187 | }]);
188 |
189 | 190 |
<div ng-controller="formatCtrl">
191 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
192 | </div>
193 |
194 |
195 |
196 | 197 |
198 |
199 |
200 |

Sort data

201 | improve the documentation 202 |

You may also want to sort the data rows. By default the table sort the data rows following the orderBy angular algorithm but you can also use your own 203 | algorithm (see below). When you click on one of the column header the orderBy algorithm will sort the data rows according to these rules 204 |

205 |
    206 |
  • if you have set the sortPredicate property on the column config : it will use it as predicate for the orderBy algorithm (see angular documentation)
  • 207 |
  • otherwise it will use the property name the column is mapped to as predicate for the orderBy algorithm (it simply means it will sort the data rows according to the clicked column values)
  • 208 |
209 | 210 |

You can also disable the sort function for a given column by setting the isSortable property to false (it is true by default)

211 | 212 | 213 |
app.controller('sortCtrl', ['$scope', function (scope) {
214 |     scope.rowCollection = [
215 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
216 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
217 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
218 |     ];
219 | 
220 |     scope.columnCollection = [
221 |         {label: 'First Name', map: 'firstName', sortPredicate: function (dataRow) {
222 | //predicate as a function (see angular orderby documentation) : it will sort by the string length
223 |             return dataRow.firstName.length;
224 |         } },
225 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
226 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
227 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
228 |         {label: 'e-mail', map: 'email', isSortable: false}
229 |     ];
230 | }]);
231 |
232 | 233 |
<div ng-controller="sortCtrl">
234 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
235 | </div>
236 |
237 |
238 |
239 | 240 |
241 |

Use your own algorithm

242 |

If you would like to use your own sort algorithm instead of the orderBy from Angular you also can. That is where our table configuration comes. 243 | You can bind the table-configuration attribute to an object that will represent the table "global" configuration. One of the global property is the 244 | sort algorithm (sortAlgorithm). 245 |

246 |
function customSortAlgorithm(arrayRef, sortPredicate, reverse) {
247 | //do some stuff
248 |     return sortedArray;
249 | }
250 |
251 |
252 |

Search/filter data

253 | improve the documentation 254 |

The table controller API gives you a way to filter the data comparing an input to both a given column values or in the whole table. 255 | To display an input textbox for a global search (in the whole table) you just have to set the isGlobalSearchActivated property to true in the table global config object. The algorithm used 256 | will be the one used by the filter filter from Angular. Of course, if you want to use your own, you can specify it in the global config (see below) 257 |

258 | 259 | 260 |
app.controller('filterCtrl', ['$scope', function (scope) {
261 |     scope.rowCollection = [
262 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
263 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
264 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
265 |     ];
266 | 
267 |     scope.columnCollection = [
268 |         {label: 'First Name', map: 'firstName'},
269 |         //the headerTemplateUrl property will be explained later, the purpose here is to show we can filter also by column only
270 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'},
271 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
272 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
273 |         {label: 'e-mail', map: 'email', isSortable: false}
274 |     ];
275 |     scope.globalConfig = {
276 |         isGlobalSearchActivated: true
277 |     };
278 | }]);
279 |
280 | 281 |
<div ng-controller="filterCtrl">
282 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
283 | </div>
284 |
285 |
286 |
287 | 288 |
289 |

Use your own algorithm

290 |

In the same way than for the sort operation you can use you own algorithm, you just have to specify the function you want to use in the global config 291 | under the property filterAlgorithm

292 |
function customFilter(arrayRef, expression) {
293 |     //do some stuff
294 |     return filteredArray;
295 | }
296 |
297 |
298 |

Select data rows

299 | improve the documentation 300 |

You can select data row according to two different modes : single or multiple (of course you can disable selection) by setting the global 301 | property selectionMode to single or multiple (any other value will be taken as selectionMode='none'). This is the only time the table changes 302 | a property value into your model: it add the "isSelected" property to your data model object. On one hand you can consider it "pollutes" your model but 303 | on the other hand it allows the parent scope to quickly have a list of the selected items without relying on events. If you are in selectionMode="multiple" you can add a selection checkbox 304 | column by setting the global property displaySelectionCheckbox to true

305 |

Even though the configuration is dynamic the table does not prevent forbidden states : for example if you switch from selectionMode='single' 306 | to 'none' with a selected item, the item will remain selected. It is the responsability of the parent scope/controller to reset the selected item

307 | 308 |

Keep track of the changes in selection

309 |

As mentioned before, whenever a row is selected/unselected the property isSelected change on the particular bound item. You can of course watch your model 310 | to see when the selected items change however this is not really efficient regarding the performances. That is why an selectionChange event is emitted every time 311 | there is something changed. 312 |

313 | 314 | 315 | 316 |
app.controller('selectionCtrl', ['$scope', function (scope) {
317 |     scope.rowCollection = [
318 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
319 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
320 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
321 |     ];
322 | 
323 |     scope.columnCollection = [
324 |         {label: 'First Name', map: 'firstName'},
325 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
326 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
327 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
328 |         {label: 'e-mail', map: 'email'}
329 |     ];
330 | 
331 |     //keep track of selected items
332 |     scope.$on('selectionChange', function (event, args) {
333 |         console.log(args);
334 |     });
335 | 
336 |     scope.globalConfig = {
337 |         selectionMode: 'multiple',
338 |         displaySelectionCheckbox: true
339 |     };
340 | }]);
341 |
342 | 343 |
<div ng-controller="selectionCtrl">
344 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
345 | </div>
346 |
347 |
348 |
349 | 350 |
351 |
352 | 353 | 354 | 355 | 360 |
361 |
    362 |
  • {{ row | json}}
  • 363 |
364 | 365 | 366 |
367 |
368 |

Edit cell

369 | improve the documentation 370 |

You can make the cells of a given column editable by simply setting the isEditable property to true in this column config. This will allow 371 | you to edit directly the property on the data model object. Therefore, you may want to specify which type of input the cell will accept. You just 372 | have to set the type property to the the type you want (email, number, etc). If the input is valid the model is updated, otherwise it will keep 373 | the previous value

374 |

notify an item has been updated

375 |

In the same way than for selection changes, you don't want to watch the whole collection to know when a given property of a given item has bee updated. That 376 | is why smart-table will raise the updateDataRow event to notify the parent scope when a row has been modified.

377 |
    378 |
  • The data will be filtered/sorted after editing accordingly to the last settings
  • 379 |
  • Double click to edit
  • 380 |
381 | 382 | 383 |
app.controller('editCtrl', ['$scope', function (scope) {
384 |     scope.rowCollection = [
385 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
386 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
387 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
388 |     ];
389 | 
390 |     //every time a data row is updated
391 |     scope.$on('updateDataRow', function (event, arg) {
392 |         console.log(arg);
393 |     });
394 | 
395 |     scope.columnCollection = [
396 |         {label: 'First Name', map: 'firstName', isEditable: true},
397 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
398 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date', isEditable: true, type: 'date'},
399 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$', isEditable: true, type: 'number'},
400 |         {label: 'e-mail', map: 'email', isEditable: true, type: 'email'}
401 |     ];
402 | }]);
403 |
404 | 405 |
<div ng-controller="editCtrl">
406 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
407 | </div>
408 |
409 |
410 |
411 | 412 |
413 |
    414 |
  • {{ row | json}}
  • 415 |
416 |
417 | 418 |
419 |

Simple styling

420 | improve the documentation 421 |

This allows you to add a class name for the cells in a given column, and for the header as well. You'll have to specify the 422 | headerClass and cellClass property in the column configuration

423 | 424 | 425 |
app.controller('styleCtrl', ['$scope', function (scope) {
426 |     scope.rowCollection = [
427 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
428 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
429 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
430 |     ];
431 | 
432 |     scope.columnCollection = [
433 |         {label: 'First Name', map: 'firstName', headerClass: "firstName-header"},
434 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', cellClass: "lastName-cell"},
435 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
436 |         {label: 'Balance', map: 'balance', formatFunction: 'currency'},
437 |         {label: 'e-mail', map: 'email'}
438 |     ];
439 | }]);
440 |
441 | 442 |
.firstName-header{
443 | text-decoration: underline;
444 | }
445 | 
446 | .lastName-cell{
447 | color: red;
448 | }
449 |
450 | 451 |
<div ng-controller="styleCtrl>
452 |     <smart-table  columns="columnCollection" rows="rowCollection"></smart-table>
453 | </div>
454 |
455 |
456 |
457 | 458 |
459 |
460 |
461 |

Cell Template

462 | improve the documentation 463 |

If you prefer to use a particular template for the cells in a given column, you can do it by specifying the cellTemplateUrl property in the column config 464 | this is useful not only for styling purpose but also if you want to enhance the behavior of the cell. The template will be compiled so that if it contains 465 | directives they will be considered as angular directives. Moreover, these directives can have access to all the row and column data through the scope 466 |

467 |
scope.column;//the column object
468 | scope.dataRow;//the data row object
469 | scope.formatedValue;//the formated value (not the raw string)
470 |
  • You can use the table controller api in your directive
  • 471 |
  • If you use your own template you may break some functionalities (particularly the edit function) and you will have to implement them 472 | in your template/directive
473 | 474 | 475 |
app.controller('cellTemplateCtrl', ['$scope', function (scope) {
477 |     scope.rowCollection = [
478 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
479 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
480 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
481 |     ];
482 | 
483 |     scope.columnCollection = [
484 |         {label: 'First Name', map: 'firstName'},
485 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase'},
486 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
487 |         {label: 'Balance', map: 'balance', formatFunction: 'currency'},
488 |         {label: 'e-mail', map: 'email'},
489 |         {label: 'Favourite color', cellTemplateUrl: 'assets/template/custom.html'}
490 |     ];
491 | }]);
492 |
493 | 494 |
<div>
495 |     <smart-table columns="columnCollection" rows="rowCollection"></smart-table>
496 | </div>
497 |
498 | 499 |
<custom></custom>
500 |
501 | 502 |
app.directive('custom', function () {
503 |     return {
504 |         restrict: 'E',
505 |         //include smart table controller to use its API if needed
506 |         require: '^smartTable',
507 |         template: '<select ng-model="favouriteColor">' +
508 |                 '<option value="">--choose favorite color--</option>' +
509 |                 '<option value="red">red</option>' +
510 |                 '<option value="blue">blue</option>' +
511 |                 '<option value="yellow">yellow</option>' +
512 |                 '</select>',
513 |         replace: true,
514 |         link: function (scope, element, attrs, ctrl) {
515 |             //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API
516 | 
517 |             var allowedColors = ['red', 'yellow', 'blue'];
518 | 
519 |             scope.$watch('favouriteColor', function (value) {
520 |                 if (allowedColors.indexOf(value) != -1) {
521 |                     scope.dataRow.favouriteColor = scope.favouriteColor;
522 |                 }
523 |             });
524 |         }
525 |     };
526 | });
527 |
528 |
529 |
530 | 531 |
532 |
    533 |
  • {{ row | json}}
  • 534 |
535 |

In the same way you can assign a custom template to the column header thanks to the column property headerTemplateUrl. See the source code 536 | of the previous filter section as an example.

537 | 538 | 539 |
app.controller('filterCtrl', ['$scope', function (scope) {
540 |     scope.rowCollection = [
541 |         {firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever@gmail.com'},
542 |         {firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou@gmail.com'},
543 |         {firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef@gmail.com'}
544 |     ];
545 | 
546 |     scope.columnCollection = [
547 |         {label: 'First Name', map: 'firstName'},
548 |         {label: 'Last Name', map: 'lastName', formatFunction: 'uppercase', headerTemplateUrl: 'assets/template/customHeader.html'},
549 |         {label: 'Birth Date', map: 'birthDate', formatFunction: 'date'},
550 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
551 |         {label: 'e-mail', map: 'email'}
552 |     ];
553 |     scope.globalConfig = {
554 |         isGlobalSearchActivated: true
555 |     };
556 | }]);
557 |
558 | 559 |
<div ng-controller="filterCtrl">
560 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
561 | </div>
562 |
563 | 564 |
<span>{{column.label}}</span>
565 | <input class="column-filter" type="text" ng-model="searchValue" />
566 |
567 | 568 |
app.directive('columnFilter', function () {
569 |     return {
570 |         restrict: 'C',
571 |         require: '^smartTable',
572 |         link: function (scope, element, attrs, ctrl) {
573 |             scope.searchValue = '';
574 |             scope.$watch('searchValue', function (value) {
575 |                 ctrl.search(value, scope.column);
576 |             })
577 |         }
578 |     }
579 | });
580 |
581 |
582 |
583 | 584 |
585 |

Client side Pagination

586 | improve the documentation 587 |

If you don't want to display too much data at the time you'll be happy using the pagination feature. It uses the angular.ui-boostrap pagination directive and you'll be able 588 | to configure it through some global config properties. First of all, the property isPaginationEnabled must be set to true. Then you can decide the 589 | number of pages you want to display with the maxSize property and the number of items you want to display on a page thanks to the itemsByPage property

590 |
  • You may wonder why it has been decided to implement a client side pagination and not a server side pagination. Well in my opinion it does not 591 | make sens to have the pagination on server side when you have the sort/filter on the client side. If you sort the data on the client side, but some are not loaded yet do you think the displayed 592 | data are really relevant. I don't.
  • 593 |
  • the event "pageChange" is emitted when the page is changed
  • 594 |
595 | 596 | 597 | 598 |
app.controller('paginationCtrl', ['$scope', function (scope) {
599 |     var
600 |             nameList = ['Pierre', 'Pol', 'Jacques', 'Robert', 'Elisa'],
601 |             familyName = ['Dupont', 'Germain', 'Delcourt', 'bjip', 'Menez'];
602 | 
603 |     function createRandomItem() {
604 |         var
605 |                 firstName = nameList[Math.floor(Math.random() * 4)],
606 |                 lastName = familyName[Math.floor(Math.random() * 4)],
607 |                 age = Math.floor(Math.random() * 100),
608 |                 email = firstName + lastName + '@whatever.com',
609 |                 balance = Math.random() * 3000;
610 | 
611 |         return{
612 |             firstName: firstName,
613 |             lastName: lastName,
614 |             age: age,
615 |             email: email,
616 |             balance: balance
617 |         };
618 |     }
619 | 
620 |     scope.rowCollection = [];
621 |     for (var j = 0; j < 200; j++) {
622 |         scope.rowCollection.push(createRandomItem());
623 |     }
624 | 
625 |     scope.columnCollection = [
626 |         {label: 'First Name', map: 'firstName'},
627 |         {label: 'Last Name', map: 'lastName'},
628 |         {label: 'Age', map: 'age'},
629 |         {label: 'Balance', map: 'balance', formatFunction: 'currency', formatParameter: '$'},
630 |         {label: 'e-mail', map: 'email'}
631 |     ];
632 | 
633 |     scope.globalConfig = {
634 |         isPaginationEnabled: true,
635 |         itemsByPage: 12,
636 |         maxSize: 8
637 |     };
638 | }]);
639 |
640 | 641 |
<div ng-controller="paginationCtrl">
642 |     <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"></smart-table>
643 | </div>
644 |
645 |
646 | 647 |
648 | 649 |
650 |
651 |
652 |

Configuration Summary

653 | improve the documentation 654 |

Here is a summary of available configuration properties for column, and for global configuration

655 |

Column

656 |
657 | 658 |
659 |

Global config

660 |
661 | 662 |
663 |
664 |
665 | 666 | -------------------------------------------------------------------------------- /assets/lib/smart-table/Smart-Table.debug.js: -------------------------------------------------------------------------------- 1 | /* Column module */ 2 | 3 | (function (global, angular) { 4 | "use strict"; 5 | var smartTableColumnModule = angular.module('smartTable.column', ['smartTable.templateUrlList']).constant('DefaultColumnConfiguration', { 6 | isSortable: true, 7 | isEditable: false, 8 | type: 'text', 9 | 10 | 11 | //it is useless to have that empty strings, but it reminds what is available 12 | headerTemplateUrl: '', 13 | map: '', 14 | label: '', 15 | sortPredicate: '', 16 | formatFunction: '', 17 | formatParameter: '', 18 | filterPredicate: '', 19 | cellTemplateUrl: '', 20 | headerClass: '', 21 | cellClass: '' 22 | }); 23 | 24 | function ColumnProvider(DefaultColumnConfiguration, templateUrlList) { 25 | 26 | function Column(config) { 27 | if (!(this instanceof Column)) { 28 | return new Column(config); 29 | } 30 | angular.extend(this, config); 31 | } 32 | 33 | this.setDefaultOption = function (option) { 34 | angular.extend(Column.prototype, option); 35 | }; 36 | 37 | DefaultColumnConfiguration.headerTemplateUrl = templateUrlList.defaultHeader; 38 | this.setDefaultOption(DefaultColumnConfiguration); 39 | 40 | this.$get = function () { 41 | return Column; 42 | }; 43 | } 44 | 45 | ColumnProvider.$inject = ['DefaultColumnConfiguration', 'templateUrlList']; 46 | smartTableColumnModule.provider('Column', ColumnProvider); 47 | 48 | //make it global so it can be tested 49 | global.ColumnProvider = ColumnProvider; 50 | })(window, angular); 51 | 52 | 53 | /* Directives */ 54 | (function (angular) { 55 | "use strict"; 56 | angular.module('smartTable.directives', ['smartTable.templateUrlList', 'smartTable.templates']) 57 | .directive('smartTable', ['templateUrlList', 'DefaultTableConfiguration', function (templateList, defaultConfig) { 58 | return { 59 | restrict: 'EA', 60 | scope: { 61 | columnCollection: '=columns', 62 | dataCollection: '=rows', 63 | config: '=' 64 | }, 65 | replace: 'true', 66 | templateUrl: templateList.smartTable, 67 | controller: 'TableCtrl', 68 | link: function (scope, element, attr, ctrl) { 69 | 70 | var templateObject; 71 | 72 | scope.$watch('config', function (config) { 73 | var newConfig = angular.extend({}, defaultConfig, config), 74 | length = scope.columns !== undefined ? scope.columns.length : 0; 75 | 76 | ctrl.setGlobalConfig(newConfig); 77 | 78 | //remove the checkbox column if needed 79 | if (newConfig.selectionMode !== 'multiple' || newConfig.displaySelectionCheckbox !== true) { 80 | for (var i = length - 1; i >= 0; i--) { 81 | if (scope.columns[i].isSelectionColumn === true) { 82 | ctrl.removeColumn(i); 83 | } 84 | } 85 | } else { 86 | //add selection box column if required 87 | ctrl.insertColumn({cellTemplateUrl: templateList.selectionCheckbox, headerTemplateUrl: templateList.selectAllCheckbox, isSelectionColumn: true}, 0); 88 | } 89 | }, true); 90 | 91 | //insert columns from column config 92 | scope.$watch('columnCollection', function (oldValue, newValue) { 93 | 94 | ctrl.clearColumns(); 95 | 96 | if (scope.columnCollection) { 97 | for (var i = 0, l = scope.columnCollection.length; i < l; i++) { 98 | ctrl.insertColumn(scope.columnCollection[i]); 99 | } 100 | } else { 101 | //or guess data Structure 102 | if (scope.dataCollection && scope.dataCollection.length > 0) { 103 | templateObject = scope.dataCollection[0]; 104 | angular.forEach(templateObject, function (value, key) { 105 | if (key[0] != '$') { 106 | ctrl.insertColumn({label: key, map: key}); 107 | } 108 | }); 109 | } 110 | } 111 | }, true); 112 | 113 | //if item are added or removed into the data model from outside the grid 114 | scope.$watch('dataCollection.length', function (oldValue, newValue) { 115 | if (oldValue !== newValue) { 116 | ctrl.sortBy();//it will trigger the refresh... some hack ? 117 | } 118 | }); 119 | } 120 | }; 121 | }]) 122 | //just to be able to select the row 123 | .directive('smartTableDataRow', function () { 124 | 125 | return { 126 | require: '^smartTable', 127 | restrict: 'C', 128 | link: function (scope, element, attr, ctrl) { 129 | 130 | element.bind('click', function () { 131 | scope.$apply(function () { 132 | ctrl.toggleSelection(scope.dataRow); 133 | }) 134 | }); 135 | } 136 | }; 137 | }) 138 | //header cell with sorting functionality or put a checkbox if this column is a selection column 139 | .directive('smartTableHeaderCell',function () { 140 | return { 141 | restrict: 'C', 142 | require: '^smartTable', 143 | link: function (scope, element, attr, ctrl) { 144 | element.bind('click', function () { 145 | scope.$apply(function () { 146 | ctrl.sortBy(scope.column); 147 | }); 148 | }) 149 | } 150 | }; 151 | }).directive('smartTableSelectAll', function () { 152 | return { 153 | restrict: 'C', 154 | require: '^smartTable', 155 | link: function (scope, element, attr, ctrl) { 156 | element.bind('click', function (event) { 157 | ctrl.toggleSelectionAll(element[0].checked === true); 158 | }) 159 | } 160 | }; 161 | }) 162 | //credit to Valentyn shybanov : http://stackoverflow.com/questions/14544741/angularjs-directive-to-stoppropagation 163 | .directive('stopEvent', function () { 164 | return { 165 | restrict: 'A', 166 | link: function (scope, element, attr) { 167 | element.bind(attr.stopEvent, function (e) { 168 | e.stopPropagation(); 169 | }); 170 | } 171 | } 172 | }) 173 | //the global filter 174 | .directive('smartTableGlobalSearch', ['templateUrlList', function (templateList) { 175 | return { 176 | restrict: 'C', 177 | require: '^smartTable', 178 | scope: { 179 | columnSpan: '@' 180 | }, 181 | templateUrl: templateList.smartTableGlobalSearch, 182 | replace: false, 183 | link: function (scope, element, attr, ctrl) { 184 | 185 | scope.searchValue = ''; 186 | 187 | scope.$watch('searchValue', function (value) { 188 | //todo perf improvement only filter on blur ? 189 | ctrl.search(value); 190 | }); 191 | } 192 | } 193 | }]) 194 | //a customisable cell (see templateUrl) and editable 195 | //TODO check with the ng-include strategy 196 | .directive('smartTableDataCell', ['$filter', '$http', '$templateCache', '$compile', '$parse', function (filter, http, templateCache, compile, parse) { 197 | return { 198 | restrict: 'C', 199 | link: function (scope, element) { 200 | var 201 | column = scope.column, 202 | isSimpleCell = !column.isEditable, 203 | row = scope.dataRow, 204 | format = filter('format'), 205 | getter = parse(column.map), 206 | childScope; 207 | 208 | //can be useful for child directives 209 | scope.$watch('dataRow', function (value) { 210 | scope.formatedValue = format(getter(row), column.formatFunction, column.formatParameter); 211 | if (isSimpleCell === true) { 212 | element.text(scope.formatedValue); 213 | } 214 | }, true); 215 | 216 | function defaultContent() { 217 | if (column.isEditable) { 218 | element.html('
'); 219 | compile(element.contents())(scope); 220 | } else { 221 | element.text(scope.formatedValue); 222 | } 223 | } 224 | 225 | scope.$watch('column.cellTemplateUrl', function (value) { 226 | 227 | if (value) { 228 | //we have to load the template (and cache it) : a kind of ngInclude 229 | http.get(value, {cache: templateCache}).success(function (response) { 230 | 231 | isSimpleCell = false; 232 | 233 | //create a scope 234 | childScope = scope.$new(); 235 | //compile the element with its new content and new scope 236 | element.html(response); 237 | compile(element.contents())(childScope); 238 | }).error(defaultContent); 239 | 240 | } else { 241 | defaultContent(); 242 | } 243 | }); 244 | } 245 | }; 246 | }]) 247 | //directive that allows type to be bound in input 248 | .directive('inputType', function () { 249 | return { 250 | restrict: 'A', 251 | priority: 1, 252 | link: function (scope, ielement, iattr) { 253 | //force the type to be set before inputDirective is called 254 | var type = scope.$eval(iattr.type); 255 | iattr.$set('type', type); 256 | } 257 | }; 258 | }) 259 | //an editable content in the context of a cell (see row, column) 260 | .directive('editableCell', ['templateUrlList', '$parse', function (templateList, parse) { 261 | return { 262 | restrict: 'EA', 263 | require: '^smartTable', 264 | templateUrl: templateList.editableCell, 265 | scope: { 266 | row: '=', 267 | column: '=', 268 | type: '=' 269 | }, 270 | replace: true, 271 | link: function (scope, element, attrs, ctrl) { 272 | var form = angular.element(element.children()[1]), 273 | input = angular.element(form.children()[0]), 274 | getter = parse(scope.column.map); 275 | 276 | //init values 277 | scope.isEditMode = false; 278 | scope.$watch('row', function () { 279 | scope.value = getter(scope.row); 280 | }, true); 281 | 282 | 283 | scope.submit = function () { 284 | //update model if valid 285 | if (scope.myForm.$valid === true) { 286 | ctrl.updateDataRow(scope.row, scope.column.map, scope.value); 287 | ctrl.sortBy();//it will trigger the refresh... (ie it will sort, filter, etc with the new value) 288 | } 289 | scope.toggleEditMode(); 290 | }; 291 | 292 | scope.toggleEditMode = function () { 293 | scope.value = getter(scope.row); 294 | scope.isEditMode = scope.isEditMode !== true; 295 | }; 296 | 297 | scope.$watch('isEditMode', function (newValue) { 298 | if (newValue === true) { 299 | input[0].select(); 300 | input[0].focus(); 301 | } 302 | }); 303 | 304 | input.bind('blur', function () { 305 | scope.$apply(function () { 306 | scope.submit(); 307 | }); 308 | }); 309 | } 310 | }; 311 | }]); 312 | })(angular); 313 | /* Filters */ 314 | (function (angular) { 315 | "use strict"; 316 | angular.module('smartTable.filters', []). 317 | constant('DefaultFilters', ['currency', 'date', 'json', 'lowercase', 'number', 'uppercase']). 318 | filter('format', ['$filter', 'DefaultFilters', function (filter, defaultfilters) { 319 | return function (value, formatFunction, filterParameter) { 320 | 321 | var returnFunction; 322 | 323 | if (formatFunction && angular.isFunction(formatFunction)) { 324 | returnFunction = formatFunction; 325 | } else { 326 | returnFunction = defaultfilters.indexOf(formatFunction) !== -1 ? filter(formatFunction) : function (value) { 327 | return value; 328 | }; 329 | } 330 | return returnFunction(value, filterParameter); 331 | }; 332 | }]); 333 | })(angular); 334 | 335 | 336 | /*table module */ 337 | 338 | (function (angular) { 339 | "use strict"; 340 | angular.module('smartTable.table', ['smartTable.column', 'smartTable.utilities', 'smartTable.directives', 'smartTable.filters', 'ui.bootstrap.pagination.smartTable']) 341 | .constant('DefaultTableConfiguration', { 342 | selectionMode: 'none', 343 | isGlobalSearchActivated: false, 344 | displaySelectionCheckbox: false, 345 | isPaginationEnabled: false, 346 | itemsByPage: 10, 347 | maxSize: 5, 348 | 349 | //just to remind available option 350 | sortAlgorithm: '', 351 | filterAlgorithm: '' 352 | }) 353 | .controller('TableCtrl', ['$scope', 'Column', '$filter', '$parse', 'ArrayUtility', 'DefaultTableConfiguration', function (scope, Column, filter, parse, arrayUtility, defaultConfig) { 354 | 355 | scope.columns = []; 356 | scope.dataCollection = scope.dataCollection || []; 357 | scope.displayedCollection = []; //init empty array so that if pagination is enabled, it does not spoil performances 358 | scope.numberOfPages = calculateNumberOfPages(scope.dataCollection); 359 | scope.currentPage = 1; 360 | scope.holder = {isAllSelected: false}; 361 | 362 | var predicate = {}, 363 | lastColumnSort; 364 | 365 | function isAllSelected() { 366 | var i, 367 | l = scope.displayedCollection.length; 368 | for (i = 0; i < l; i++) { 369 | if (scope.displayedCollection[i].isSelected !== true) { 370 | return false; 371 | } 372 | } 373 | return true; 374 | } 375 | 376 | function calculateNumberOfPages(array) { 377 | 378 | if (!angular.isArray(array) || array.length === 0 || scope.itemsByPage < 1) { 379 | return 1; 380 | } 381 | return Math.ceil(array.length / scope.itemsByPage); 382 | } 383 | 384 | function sortDataRow(array, column) { 385 | var sortAlgo = (scope.sortAlgorithm && angular.isFunction(scope.sortAlgorithm)) === true ? scope.sortAlgorithm : filter('orderBy'); 386 | if (column) { 387 | return arrayUtility.sort(array, sortAlgo, column.sortPredicate, column.reverse); 388 | } else { 389 | return array; 390 | } 391 | } 392 | 393 | function selectDataRow(array, selectionMode, index, select) { 394 | 395 | var dataRow, oldValue; 396 | 397 | if ((!angular.isArray(array)) || (selectionMode !== 'multiple' && selectionMode !== 'single')) { 398 | return; 399 | } 400 | 401 | if (index >= 0 && index < array.length) { 402 | dataRow = array[index]; 403 | if (selectionMode === 'single') { 404 | //unselect all the others 405 | for (var i = 0, l = array.length; i < l; i++) { 406 | oldValue = array[i].isSelected; 407 | array[i].isSelected = false; 408 | if (oldValue === true) { 409 | scope.$emit('selectionChange', {item: array[i]}); 410 | } 411 | } 412 | } 413 | dataRow.isSelected = select; 414 | scope.holder.isAllSelected = isAllSelected(); 415 | scope.$emit('selectionChange', {item: dataRow}); 416 | } 417 | } 418 | 419 | /** 420 | * set the config (config parameters will be available through scope 421 | * @param config 422 | */ 423 | this.setGlobalConfig = function (config) { 424 | angular.extend(scope, defaultConfig, config); 425 | }; 426 | 427 | /** 428 | * change the current page displayed 429 | * @param page 430 | */ 431 | this.changePage = function (page) { 432 | var oldPage = scope.currentPage; 433 | if (angular.isNumber(page.page)) { 434 | scope.currentPage = page.page; 435 | scope.displayedCollection = this.pipe(scope.dataCollection); 436 | scope.holder.isAllSelected = isAllSelected(); 437 | scope.$emit('changePage', {oldValue: oldPage, newValue: scope.currentPage}); 438 | } 439 | }; 440 | 441 | /** 442 | * set column as the column used to sort the data (if it is already the case, it will change the reverse value) 443 | * @method sortBy 444 | * @param column 445 | */ 446 | this.sortBy = function (column) { 447 | var index = scope.columns.indexOf(column); 448 | if (index !== -1) { 449 | if (column.isSortable === true) { 450 | // reset the last column used 451 | if (lastColumnSort && lastColumnSort !== column) { 452 | lastColumnSort.reverse = 'none'; 453 | } 454 | 455 | column.sortPredicate = column.sortPredicate || column.map; 456 | column.reverse = column.reverse !== true; 457 | lastColumnSort = column; 458 | } 459 | } 460 | 461 | scope.displayedCollection = this.pipe(scope.dataCollection); 462 | }; 463 | 464 | /** 465 | * set the filter predicate used for searching 466 | * @param input 467 | * @param column 468 | */ 469 | this.search = function (input, column) { 470 | 471 | var j, l = scope.columns.length; 472 | //update column and global predicate 473 | if (column && scope.columns.indexOf(column) !== -1) { 474 | predicate.$ = ''; 475 | column.filterPredicate = input; 476 | } else { 477 | for (j = 0; j < l; j++) { 478 | scope.columns[j].filterPredicate = ''; 479 | } 480 | predicate.$ = input; 481 | } 482 | 483 | for (j = 0; j < l; j++) { 484 | predicate[scope.columns[j].map] = scope.columns[j].filterPredicate; 485 | } 486 | scope.displayedCollection = this.pipe(scope.dataCollection); 487 | 488 | }; 489 | 490 | /** 491 | * combine sort, search and limitTo operations on an array, 492 | * @param array 493 | * @returns Array, an array result of the operations on input array 494 | */ 495 | this.pipe = function (array) { 496 | var filterAlgo = (scope.filterAlgorithm && angular.isFunction(scope.filterAlgorithm)) === true ? scope.filterAlgorithm : filter('filter'), 497 | output; 498 | //filter and sort are commutative 499 | output = sortDataRow(arrayUtility.filter(array, filterAlgo, predicate), lastColumnSort); 500 | scope.numberOfPages = calculateNumberOfPages(output); 501 | return scope.isPaginationEnabled ? arrayUtility.fromTo(output, (scope.currentPage - 1) * scope.itemsByPage, scope.itemsByPage) : output; 502 | }; 503 | 504 | /*//////////// 505 | Column API 506 | ///////////*/ 507 | 508 | 509 | /** 510 | * insert a new column in scope.collection at index or push at the end if no index 511 | * @param columnConfig column configuration used to instantiate the new Column 512 | * @param index where to insert the column (at the end if not specified) 513 | */ 514 | this.insertColumn = function (columnConfig, index) { 515 | var column = new Column(columnConfig); 516 | arrayUtility.insertAt(scope.columns, index, column); 517 | }; 518 | 519 | /** 520 | * remove the column at columnIndex from scope.columns 521 | * @param columnIndex index of the column to be removed 522 | */ 523 | this.removeColumn = function (columnIndex) { 524 | arrayUtility.removeAt(scope.columns, columnIndex); 525 | }; 526 | 527 | /** 528 | * move column located at oldIndex to the newIndex in scope.columns 529 | * @param oldIndex index of the column before it is moved 530 | * @param newIndex index of the column after the column is moved 531 | */ 532 | this.moveColumn = function (oldIndex, newIndex) { 533 | arrayUtility.moveAt(scope.columns, oldIndex, newIndex); 534 | }; 535 | 536 | /** 537 | * remove all columns 538 | */ 539 | this.clearColumns = function () { 540 | scope.columns.length = 0; 541 | }; 542 | 543 | /*/////////// 544 | ROW API 545 | */ 546 | 547 | /** 548 | * select or unselect the item of the displayedCollection with the selection mode set in the scope 549 | * @param dataRow 550 | */ 551 | this.toggleSelection = function (dataRow) { 552 | var index = scope.dataCollection.indexOf(dataRow); 553 | if (index !== -1) { 554 | selectDataRow(scope.dataCollection, scope.selectionMode, index, dataRow.isSelected !== true); 555 | } 556 | }; 557 | 558 | /** 559 | * select/unselect all the currently displayed rows 560 | * @param value if true select, else unselect 561 | */ 562 | this.toggleSelectionAll = function (value) { 563 | var i = 0, 564 | l = scope.displayedCollection.length; 565 | 566 | if (scope.selectionMode !== 'multiple') { 567 | return; 568 | } 569 | for (; i < l; i++) { 570 | selectDataRow(scope.displayedCollection, scope.selectionMode, i, value === true); 571 | } 572 | }; 573 | 574 | /** 575 | * remove the item at index rowIndex from the displayed collection 576 | * @param rowIndex 577 | * @returns {*} item just removed or undefined 578 | */ 579 | this.removeDataRow = function (rowIndex) { 580 | var toRemove = arrayUtility.removeAt(scope.displayedCollection, rowIndex); 581 | arrayUtility.removeAt(scope.dataCollection, scope.dataCollection.indexOf(toRemove)); 582 | }; 583 | 584 | /** 585 | * move an item from oldIndex to newIndex in displayedCollection 586 | * @param oldIndex 587 | * @param newIndex 588 | */ 589 | this.moveDataRow = function (oldIndex, newIndex) { 590 | arrayUtility.moveAt(scope.displayedCollection, oldIndex, newIndex); 591 | }; 592 | 593 | /** 594 | * update the model, it can be a non existing yet property 595 | * @param dataRow the dataRow to update 596 | * @param propertyName the property on the dataRow ojbect to update 597 | * @param newValue the value to set 598 | */ 599 | this.updateDataRow = function (dataRow, propertyName, newValue) { 600 | var index = scope.displayedCollection.indexOf(dataRow), 601 | getter = parse(propertyName), 602 | setter = getter.assign, 603 | oldValue; 604 | if (index !== -1) { 605 | oldValue = getter(scope.displayedCollection[index]); 606 | if (oldValue !== newValue) { 607 | setter(scope.displayedCollection[index], newValue); 608 | scope.$emit('updateDataRow', {item: scope.displayedCollection[index]}); 609 | } 610 | } 611 | }; 612 | }]); 613 | })(angular); 614 | 615 | 616 | angular.module('smartTable.templates', ['partials/defaultCell.html', 'partials/defaultHeader.html', 'partials/editableCell.html', 'partials/globalSearchCell.html', 'partials/pagination.html', 'partials/selectAllCheckbox.html', 'partials/selectionCheckbox.html', 'partials/smartTable.html']); 617 | 618 | angular.module("partials/defaultCell.html", []).run(["$templateCache", function ($templateCache) { 619 | $templateCache.put("partials/defaultCell.html", 620 | "{{formatedValue}}"); 621 | }]); 622 | 623 | angular.module("partials/defaultHeader.html", []).run(["$templateCache", function ($templateCache) { 624 | $templateCache.put("partials/defaultHeader.html", 625 | "{{column.label}}"); 626 | }]); 627 | 628 | angular.module("partials/editableCell.html", []).run(["$templateCache", function ($templateCache) { 629 | $templateCache.put("partials/editableCell.html", 630 | "
\n" + 631 | " {{value | format:column.formatFunction:column.formatParameter}}\n" + 632 | "\n" + 633 | "
\n" + 634 | " \n" + 635 | "
\n" + 636 | "
"); 637 | }]); 638 | 639 | angular.module("partials/globalSearchCell.html", []).run(["$templateCache", function ($templateCache) { 640 | $templateCache.put("partials/globalSearchCell.html", 641 | "\n" + 642 | ""); 643 | }]); 644 | 645 | angular.module("partials/pagination.html", []).run(["$templateCache", function ($templateCache) { 646 | $templateCache.put("partials/pagination.html", 647 | "
\n" + 648 | "
    \n" + 649 | "
  • {{page.text}}
  • \n" + 651 | "
\n" + 652 | "
"); 653 | }]); 654 | 655 | angular.module("partials/selectAllCheckbox.html", []).run(["$templateCache", function ($templateCache) { 656 | $templateCache.put("partials/selectAllCheckbox.html", 657 | ""); 658 | }]); 659 | 660 | angular.module("partials/selectionCheckbox.html", []).run(["$templateCache", function ($templateCache) { 661 | $templateCache.put("partials/selectionCheckbox.html", 662 | ""); 663 | }]); 664 | 665 | angular.module("partials/smartTable.html", []).run(["$templateCache", function ($templateCache) { 666 | $templateCache.put("partials/smartTable.html", 667 | "\n" + 668 | " \n" + 669 | " \n" + 670 | " \n" + 672 | " \n" + 673 | " \n" + 674 | " \n" + 677 | " \n" + 678 | " \n" + 679 | " \n" + 680 | " \n" + 682 | " \n" + 683 | " \n" + 684 | " \n" + 685 | " \n" + 686 | " \n" + 687 | " \n" + 690 | " \n" + 691 | " \n" + 692 | "
\n" + 671 | "
\n" + 676 | "
\n" + 693 | "\n" + 694 | "\n" + 695 | ""); 696 | }]); 697 | 698 | (function (angular) { 699 | "use strict"; 700 | angular.module('smartTable.templateUrlList', []) 701 | .constant('templateUrlList', { 702 | smartTable: 'partials/smartTable.html', 703 | smartTableGlobalSearch: 'partials/globalSearchCell.html', 704 | editableCell: 'partials/editableCell.html', 705 | selectionCheckbox: 'partials/selectionCheckbox.html', 706 | selectAllCheckbox: 'partials/selectAllCheckbox.html', 707 | defaultHeader: 'partials/defaultHeader.html', 708 | pagination: 'partials/pagination.html' 709 | }); 710 | })(angular); 711 | 712 | 713 | (function (angular) { 714 | "use strict"; 715 | angular.module('smartTable.utilities', []) 716 | 717 | .factory('ArrayUtility', function () { 718 | 719 | /** 720 | * remove the item at index from arrayRef and return the removed item 721 | * @param arrayRef 722 | * @param index 723 | * @returns {*} 724 | */ 725 | var removeAt = function (arrayRef, index) { 726 | if (index >= 0 && index < arrayRef.length) { 727 | return arrayRef.splice(index, 1)[0]; 728 | } 729 | }, 730 | 731 | /** 732 | * insert item in arrayRef at index or a the end if index is wrong 733 | * @param arrayRef 734 | * @param index 735 | * @param item 736 | */ 737 | insertAt = function (arrayRef, index, item) { 738 | if (index >= 0 && index < arrayRef.length) { 739 | arrayRef.splice(index, 0, item); 740 | } else { 741 | arrayRef.push(item); 742 | } 743 | }, 744 | 745 | /** 746 | * move the item at oldIndex to newIndex in arrayRef 747 | * @param arrayRef 748 | * @param oldIndex 749 | * @param newIndex 750 | */ 751 | moveAt = function (arrayRef, oldIndex, newIndex) { 752 | var elementToMove; 753 | if (oldIndex >= 0 && oldIndex < arrayRef.length && newIndex >= 0 && newIndex < arrayRef.length) { 754 | elementToMove = arrayRef.splice(oldIndex, 1)[0]; 755 | arrayRef.splice(newIndex, 0, elementToMove); 756 | } 757 | }, 758 | 759 | /** 760 | * sort arrayRef according to sortAlgorithm following predicate and reverse 761 | * @param arrayRef 762 | * @param sortAlgorithm 763 | * @param predicate 764 | * @param reverse 765 | * @returns {*} 766 | */ 767 | sort = function (arrayRef, sortAlgorithm, predicate, reverse) { 768 | 769 | if (!sortAlgorithm || !angular.isFunction(sortAlgorithm)) { 770 | return arrayRef; 771 | } else { 772 | return sortAlgorithm(arrayRef, predicate, reverse === true);//excpet if reverse is true it will take it as false 773 | } 774 | }, 775 | 776 | /** 777 | * filter arrayRef according with filterAlgorithm and predicate 778 | * @param arrayRef 779 | * @param filterAlgorithm 780 | * @param predicate 781 | * @returns {*} 782 | */ 783 | filter = function (arrayRef, filterAlgorithm, predicate) { 784 | if (!filterAlgorithm || !angular.isFunction(filterAlgorithm)) { 785 | return arrayRef; 786 | } else { 787 | return filterAlgorithm(arrayRef, predicate); 788 | } 789 | }, 790 | 791 | /** 792 | * return an array, part of array ref starting at min and the size of length 793 | * @param arrayRef 794 | * @param min 795 | * @param length 796 | * @returns {*} 797 | */ 798 | fromTo = function (arrayRef, min, length) { 799 | 800 | var out = [], 801 | limit, 802 | start; 803 | 804 | if (!angular.isArray(arrayRef)) { 805 | return arrayRef; 806 | } 807 | 808 | start = Math.max(min, 0); 809 | start = Math.min(start, (arrayRef.length - 1) > 0 ? arrayRef.length - 1 : 0); 810 | 811 | length = Math.max(0, length); 812 | limit = Math.min(start + length, arrayRef.length); 813 | 814 | for (var i = start; i < limit; i++) { 815 | out.push(arrayRef[i]); 816 | } 817 | return out; 818 | }; 819 | 820 | 821 | return { 822 | removeAt: removeAt, 823 | insertAt: insertAt, 824 | moveAt: moveAt, 825 | sort: sort, 826 | filter: filter, 827 | fromTo: fromTo 828 | }; 829 | }); 830 | })(angular); 831 | 832 | 833 | (function (angular) { 834 | angular.module('ui.bootstrap.pagination.smartTable', ['smartTable.templateUrlList']) 835 | 836 | .constant('paginationConfig', { 837 | boundaryLinks: false, 838 | directionLinks: true, 839 | firstText: 'First', 840 | previousText: '<', 841 | nextText: '>', 842 | lastText: 'Last' 843 | }) 844 | 845 | .directive('paginationSmartTable', ['paginationConfig', 'templateUrlList', function (paginationConfig, templateUrlList) { 846 | return { 847 | restrict: 'EA', 848 | require: '^smartTable', 849 | scope: { 850 | numPages: '=', 851 | currentPage: '=', 852 | maxSize: '=' 853 | }, 854 | templateUrl: templateUrlList.pagination, 855 | replace: true, 856 | link: function (scope, element, attrs, ctrl) { 857 | 858 | // Setup configuration parameters 859 | var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; 860 | var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks; 861 | var firstText = angular.isDefined(attrs.firstText) ? attrs.firstText : paginationConfig.firstText; 862 | var previousText = angular.isDefined(attrs.previousText) ? attrs.previousText : paginationConfig.previousText; 863 | var nextText = angular.isDefined(attrs.nextText) ? attrs.nextText : paginationConfig.nextText; 864 | var lastText = angular.isDefined(attrs.lastText) ? attrs.lastText : paginationConfig.lastText; 865 | 866 | // Create page object used in template 867 | function makePage(number, text, isActive, isDisabled) { 868 | return { 869 | number: number, 870 | text: text, 871 | active: isActive, 872 | disabled: isDisabled 873 | }; 874 | } 875 | 876 | scope.$watch('numPages + currentPage + maxSize', function () { 877 | scope.pages = []; 878 | 879 | // Default page limits 880 | var startPage = 1, endPage = scope.numPages; 881 | 882 | // recompute if maxSize 883 | if (scope.maxSize && scope.maxSize < scope.numPages) { 884 | startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize / 2), 1); 885 | endPage = startPage + scope.maxSize - 1; 886 | 887 | // Adjust if limit is exceeded 888 | if (endPage > scope.numPages) { 889 | endPage = scope.numPages; 890 | startPage = endPage - scope.maxSize + 1; 891 | } 892 | } 893 | 894 | // Add page number links 895 | for (var number = startPage; number <= endPage; number++) { 896 | var page = makePage(number, number, scope.isActive(number), false); 897 | scope.pages.push(page); 898 | } 899 | 900 | // Add previous & next links 901 | if (directionLinks) { 902 | var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious()); 903 | scope.pages.unshift(previousPage); 904 | 905 | var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext()); 906 | scope.pages.push(nextPage); 907 | } 908 | 909 | // Add first & last links 910 | if (boundaryLinks) { 911 | var firstPage = makePage(1, firstText, false, scope.noPrevious()); 912 | scope.pages.unshift(firstPage); 913 | 914 | var lastPage = makePage(scope.numPages, lastText, false, scope.noNext()); 915 | scope.pages.push(lastPage); 916 | } 917 | 918 | 919 | if (scope.currentPage > scope.numPages) { 920 | scope.selectPage(scope.numPages); 921 | } 922 | }); 923 | scope.noPrevious = function () { 924 | return scope.currentPage === 1; 925 | }; 926 | scope.noNext = function () { 927 | return scope.currentPage === scope.numPages; 928 | }; 929 | scope.isActive = function (page) { 930 | return scope.currentPage === page; 931 | }; 932 | 933 | scope.selectPage = function (page) { 934 | if (!scope.isActive(page) && page > 0 && page <= scope.numPages) { 935 | scope.currentPage = page; 936 | ctrl.changePage({ page: page }); 937 | } 938 | }; 939 | } 940 | }; 941 | }]); 942 | })(angular); 943 | 944 | --------------------------------------------------------------------------------