blob: dfbb700b603484fb41b240953e4d938e4d2b163b [file] [log] [blame]
Matteo Scandolo91fe03d2016-03-24 15:29:52 -07001/**
2 * © OpenCORD
3 *
4 * Visit http://guide.xosproject.org/devguide/addview/ for more information
5 *
6 * Created by teone on 3/24/16.
7 */
8
9(function () {
10 'use strict';
11
Matteo Scandolocc6954e2016-04-15 12:20:14 -070012 angular.module('xos.uiComponents')
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070013
14 /**
15 * @ngdoc directive
Matteo Scandoloe15a8202016-04-15 14:27:54 -070016 * @name xos.uiComponents.directive:xosTable
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070017 * @restrict E
18 * @description The xos-table directive
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070019 * @param {Object} config The configuration for the component.
20 * ```
21 * {
22 * columns: [
23 * {
24 * label: 'Human readable name',
Matteo Scandolo83369f32016-05-13 10:39:38 -070025 * prop: 'Property to read in the model object',
26 * type: 'boolean'| 'array'| 'object'| 'custom'| 'date' | 'icon' // see examples for more details
27 formatter: fn(), // receive the whole item if tipe is custom and return a string
28 link: fn() // receive the whole item and return an url
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070029 * }
30 * ],
31 * classes: 'table table-striped table-bordered',
32 * actions: [ // if defined add an action column
33 {
34 label: 'delete',
35 icon: 'remove', // refers to bootstraps glyphicon
36 cb: (user) => { // receive the model
37 console.log(user);
38 },
39 color: 'red'
40 }
Matteo Scandolo5677bb12016-04-14 17:21:45 -070041 ],
42 filter: 'field', // can be by `field` or `fulltext`
Matteo Scandolo83369f32016-05-13 10:39:38 -070043 order: true | {field: 'property name', reverse: true | false} // whether to show ordering arrows, or a configuration for a default ordering
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070044 * }
45 * ```
46 * @param {Array} data The data that should be rendered
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070047 * @element ANY
48 * @scope
49 * @example
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070050
51 # Basic usage
Matteo Scandoloe15a8202016-04-15 14:27:54 -070052 <example module="sampleTable1">
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070053 <file name="index.html">
54 <div ng-controller="SampleCtrl1 as vm">
55 <xos-table data="vm.data" config="vm.config"></xos-table>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070056 </div>
57 </file>
58 <file name="script.js">
Matteo Scandoloe15a8202016-04-15 14:27:54 -070059 angular.module('sampleTable1', ['xos.uiComponents'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070060 .factory('_', function($window){
61 return $window._;
62 })
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070063 .controller('SampleCtrl1', function(){
64 this.config = {
65 columns: [
66 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070067 label: 'First Name', // column title
68 prop: 'name' // property to read in the data array
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070069 },
70 {
71 label: 'Last Name',
72 prop: 'lastname'
73 }
74 ]
75 };
76
77 this.data = [
78 {
79 name: 'John',
80 lastname: 'Doe'
81 },
82 {
83 name: 'Gili',
84 lastname: 'Fereydoun'
85 }
86 ]
87 });
88 </file>
89 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070090
91 # Filtering
Matteo Scandolo88e18462016-04-25 14:24:18 -070092 <example module="sampleTable2" animations="true">
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070093 <file name="index.html">
94 <div ng-controller="SampleCtrl2 as vm">
95 <xos-table data="vm.data" config="vm.config"></xos-table>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070096 </div>
97 </file>
98 <file name="script.js">
Matteo Scandolo88e18462016-04-25 14:24:18 -070099 angular.module('sampleTable2', ['xos.uiComponents', 'ngAnimate'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700100 .factory('_', function($window){
101 return $window._;
102 })
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700103 .controller('SampleCtrl2', function(){
104 this.config = {
105 columns: [
106 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700107 label: 'First Name', // column title
108 prop: 'name' // property to read in the data array
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700109 },
110 {
111 label: 'Last Name',
112 prop: 'lastname'
113 }
114 ],
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700115 classes: 'table table-striped table-condensed', // table classes, default to `table table-striped table-bordered`
116 actions: [ // if defined add an action column
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700117 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700118 label: 'delete', // label
119 icon: 'remove', // icons, refers to bootstraps glyphicon
120 cb: (user) => { // callback, get feeded with the full object
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700121 console.log(user);
122 },
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700123 color: 'red' // icon color
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700124 }
Matteo Scandolo5677bb12016-04-14 17:21:45 -0700125 ],
126 filter: 'field', // can be by `field` or `fulltext`
127 order: true
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700128 };
129
130 this.data = [
131 {
132 name: 'John',
133 lastname: 'Doe'
134 },
135 {
136 name: 'Gili',
137 lastname: 'Fereydoun'
138 }
139 ]
140 });
141 </file>
142 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700143
144 # Pagination
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700145 <example module="sampleTable3">
146 <file name="index.html">
147 <div ng-controller="SampleCtrl3 as vm">
148 <xos-table data="vm.data" config="vm.config"></xos-table>
149 </div>
150 </file>
151 <file name="script.js">
152 angular.module('sampleTable3', ['xos.uiComponents'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700153 .factory('_', function($window){
154 return $window._;
155 })
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700156 .controller('SampleCtrl3', function(){
157 this.config = {
158 columns: [
159 {
160 label: 'First Name', // column title
161 prop: 'name' // property to read in the data array
162 },
163 {
164 label: 'Last Name',
165 prop: 'lastname'
166 }
167 ],
168 pagination: {
169 pageSize: 2
170 }
171 };
172
173 this.data = [
174 {
175 name: 'John',
176 lastname: 'Doe'
177 },
178 {
179 name: 'Gili',
180 lastname: 'Fereydoun'
181 },
182 {
183 name: 'Lucky',
184 lastname: 'Clarkson'
185 },
186 {
187 name: 'Tate',
188 lastname: 'Spalding'
189 }
190 ]
191 });
192 </file>
193 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700194
195 # Field formatter
196 <example module="sampleTable4">
197 <file name="index.html">
198 <div ng-controller="SampleCtrl as vm">
199 <xos-table data="vm.data" config="vm.config"></xos-table>
200 </div>
201 </file>
202 <file name="script.js">
203 angular.module('sampleTable4', ['xos.uiComponents'])
204 .factory('_', function($window){
205 return $window._;
206 })
207 .controller('SampleCtrl', function(){
208 this.config = {
209 columns: [
210 {
211 label: 'First Name',
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700212 prop: 'name',
213 link: item => `https://www.google.it/#q=${item.name}`
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700214 },
215 {
216 label: 'Enabled',
217 prop: 'enabled',
218 type: 'boolean'
219 },
220 {
221 label: 'Services',
222 prop: 'services',
223 type: 'array'
224 },
225 {
226 label: 'Details',
227 prop: 'details',
228 type: 'object'
Matteo Scandolo83369f32016-05-13 10:39:38 -0700229 },
230 {
231 label: 'Created',
232 prop: 'created',
233 type: 'date'
234 },
235 {
236 label: 'Icon',
237 type: 'icon',
238 formatter: item => item.icon //note that this refer to [Bootstrap Glyphicon](http://getbootstrap.com/components/#glyphicons)
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700239 }
240 ]
241 };
242
243 this.data = [
244 {
245 name: 'John',
246 enabled: true,
247 services: ['Cdn', 'IpTv'],
248 details: {
249 c_tag: '243',
250 s_tag: '444'
Matteo Scandolo83369f32016-05-13 10:39:38 -0700251 },
252 created: new Date('December 17, 1995 03:24:00'),
253 icon: 'music'
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700254 },
255 {
256 name: 'Gili',
257 enabled: false,
258 services: ['Cdn', 'IpTv', 'Cache'],
259 details: {
260 c_tag: '675',
261 s_tag: '893'
Matteo Scandolo83369f32016-05-13 10:39:38 -0700262 },
263 created: new Date(),
264 icon: 'camera'
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700265 }
266 ]
267 });
268 </file>
269 </example>
270
271 # Custom formatter
272 <example module="sampleTable5">
273 <file name="index.html">
274 <div ng-controller="SampleCtrl as vm">
275 <xos-table data="vm.data" config="vm.config"></xos-table>
276 </div>
277 </file>
278 <file name="script.js">
279 angular.module('sampleTable5', ['xos.uiComponents'])
280 .factory('_', function($window){
281 return $window._;
282 })
283 .controller('SampleCtrl', function(){
284 this.config = {
285 columns: [
286 {
287 label: 'Username',
288 prop: 'username'
289 },
290 {
291 label: 'Features',
292 prop: 'features',
293 type: 'custom',
294 formatter: (val) => {
295
296 let cdnEnabled = val.cdn ? 'enabled' : 'disabled';
297 return `
298 Cdn is ${cdnEnabled},
299 uplink speed is ${val.uplink_speed}
300 and downlink speed is ${val.downlink_speed}
301 `;
302 }
303 }
304 ]
305 };
306
307 this.data = [
308 {
309 username: 'John',
310 features: {
311 "cdn": false,
312 "uplink_speed": 1000000000,
313 "downlink_speed": 1000000000,
314 "uverse": true,
315 "status": "enabled"
316 }
317 },
318 {
319 username: 'Gili',
320 features: {
321 "cdn": true,
322 "uplink_speed": 3000000000,
323 "downlink_speed": 2000000000,
324 "uverse": true,
325 "status": "enabled"
326 }
327 }
328 ]
329 });
330 </file>
331 </example>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700332 **/
333
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700334 .directive('xosTable', function(){
335 return {
336 restrict: 'E',
337 scope: {
338 data: '=',
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700339 config: '='
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700340 },
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700341 template: `
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700342 <div ng-show="vm.data.length > 0">
343 <div class="row" ng-if="vm.config.filter == 'fulltext'">
344 <div class="col-xs-12">
345 <input
346 class="form-control"
347 placeholder="Type to search.."
348 type="text"
349 ng-model="vm.query"/>
350 </div>
351 </div>
Matteo Scandolob0280752016-04-25 10:31:22 -0700352 <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700353 <thead>
354 <tr>
355 <th ng-repeat="col in vm.columns">
356 {{col.label}}
357 <span ng-if="vm.config.order">
358 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
359 <i class="glyphicon glyphicon-chevron-up"></i>
360 </a>
361 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
362 <i class="glyphicon glyphicon-chevron-down"></i>
363 </a>
364 </span>
365 </th>
Matteo Scandolodc249eb2016-04-26 11:44:36 -0700366 <th ng-if="vm.config.actions">Actions:</th>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700367 </tr>
368 </thead>
369 <tbody ng-if="vm.config.filter == 'field'">
370 <tr>
371 <td ng-repeat="col in vm.columns">
372 <input
373 class="form-control"
374 placeholder="Type to search by {{col.label}}"
375 type="text"
376 ng-model="vm.query[col.prop]"/>
377 </td>
378 <td ng-if="vm.config.actions"></td>
379 </tr>
380 </tbody>
381 <tbody>
382 <tr ng-repeat="item in vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700383 <td ng-repeat="col in vm.columns" link-wrapper>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700384 <span ng-if="!col.type">{{item[col.prop]}}</span>
385 <span ng-if="col.type === 'boolean'">
386 <i class="glyphicon"
387 ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
388 </i>
389 </span>
390 <span ng-if="col.type === 'date'">
391 {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
392 </span>
393 <span ng-if="col.type === 'array'">
394 {{item[col.prop] | arrayToList}}
395 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700396 <span ng-if="col.type === 'object'">
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700397 <dl class="dl-horizontal">
398 <span ng-repeat="(k,v) in item[col.prop]">
399 <dt>{{k}}</dt>
400 <dd>{{v}}</dd>
401 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700402 </dl>
403 </span>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700404 <span ng-if="col.type === 'custom'">
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700405 {{col.formatter(item)}}
406 </span>
407 <span ng-if="col.type === 'icon'">
408 <i class="glyphicon glyphicon-{{col.formatter(item)}}">
409 </i>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700410 </span>
411 </td>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700412 <td ng-if="vm.config.actions">
413 <a href=""
414 ng-repeat="action in vm.config.actions"
415 ng-click="action.cb(item)"
416 title="{{action.label}}">
417 <i
418 class="glyphicon glyphicon-{{action.icon}}"
419 style="color: {{action.color}};"></i>
420 </a>
421 </td>
422 </tr>
423 </tbody>
424 </table>
425 <xos-pagination
426 ng-if="vm.config.pagination"
427 page-size="vm.config.pagination.pageSize"
428 total-elements="vm.data.length"
429 change="vm.goToPage">
430 </xos-pagination>
431 </div>
432 <div ng-show="vm.data.length == 0 || !vm.data">
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700433 <xos-alert config="{type: 'info'}">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700434 No data to show.
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700435 </xos-alert>
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700436 </div>
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700437 `,
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700438 bindToController: true,
439 controllerAs: 'vm',
Matteo Scandolof9700a32016-05-06 09:42:45 -0700440 controller: function(_){
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700441
442 if(!this.config){
443 throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
444 }
445
446 if(!this.config.columns){
447 throw new Error('[xosTable] Please provide a columns list in the configuration');
448 }
449
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700450 // handle default ordering
451 if(this.config.order && angular.isObject(this.config.order)){
452 this.reverse = this.config.order.reverse || false;
453 this.orderBy = this.config.order.field || 'id';
454 }
455
456 // if columns with type 'custom' are provided
457 // check that a custom formatte3 is provided too
Matteo Scandolof9700a32016-05-06 09:42:45 -0700458 let customCols = _.filter(this.config.columns, {type: 'custom'});
459 if(angular.isArray(customCols) && customCols.length > 0){
460 _.forEach(customCols, (col) => {
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700461 if(!col.formatter || !angular.isFunction(col.formatter)){
Matteo Scandolof9700a32016-05-06 09:42:45 -0700462 throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
463 }
464 })
465 }
466
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700467 // if columns with type 'icon' are provided
468 // check that a custom formatte3 is provided too
469 let iconCols = _.filter(this.config.columns, {type: 'icon'});
470 if(angular.isArray(iconCols) && iconCols.length > 0){
471 _.forEach(iconCols, (col) => {
472 if(!col.formatter || !angular.isFunction(col.formatter)){
473 throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
474 }
475 })
476 }
477
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700478 // if a link property is passed,
479 // it should be a function
480 let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
481 if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
482 _.forEach(linkedColumns, (col) => {
483 if(!angular.isFunction(col.link)){
484 throw new Error('[xosTable] The link property should be a function.');
485 }
486 })
487 }
488
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700489 this.columns = this.config.columns;
490 this.classes = this.config.classes || 'table table-striped table-bordered';
491
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700492 if(this.config.actions){
Matteo Scandolocc6954e2016-04-15 12:20:14 -0700493 // TODO validate action format
494 }
495 if(this.config.pagination){
496 this.currentPage = 0;
497 this.goToPage = (n) => {
498 this.currentPage = n;
499 };
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700500 }
501
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700502 }
503 }
504 })
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700505 // TODO move in separate files
506 // TODO test
507 .filter('arrayToList', function(){
508 return (input) => {
509 if(!angular.isArray(input)){
510 return input;
511 }
512 return input.join(', ');
513 }
514 })
515 // TODO test
516 .directive('linkWrapper', function() {
517 return {
518 restrict: 'A',
519 transclude: true,
520 template: `
521 <a ng-if="col.link" href="{{col.link(item)}}">
522 <div ng-transclude></div>
523 </a>
524 <div ng-transclude ng-if="!col.link"></div>
525 `
526 };
527 });
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700528})();