blob: e8f66b4b63cc0561cda191611e5be675c52b2ebf [file] [log] [blame]
Matteo Scandoloa5d03d52016-07-21 11:35:46 -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
12 angular.module('xos.uiComponents')
13
14 /**
15 * @ngdoc directive
16 * @name xos.uiComponents.directive:xosTable
17 * @restrict E
18 * @description The xos-table directive
19 * @param {Object} config The configuration for the component.
20 * ```
21 * {
22 * columns: [
23 * {
24 * label: 'Human readable name',
25 * 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
29 * }
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 }
41 ],
42 filter: 'field', // can be by `field` or `fulltext`
43 order: true | {field: 'property name', reverse: true | false} // whether to show ordering arrows, or a configuration for a default ordering
44 * }
45 * ```
46 * @param {Array} data The data that should be rendered
47 * @element ANY
48 * @scope
49 * @example
50
51 # Basic usage
52 <example module="sampleTable1">
53 <file name="index.html">
54 <div ng-controller="SampleCtrl1 as vm">
55 <xos-table data="vm.data" config="vm.config"></xos-table>
56 </div>
57 </file>
58 <file name="script.js">
59 angular.module('sampleTable1', ['xos.uiComponents'])
60 .factory('_', function($window){
61 return $window._;
62 })
63 .controller('SampleCtrl1', function(){
64 this.config = {
65 columns: [
66 {
67 label: 'First Name', // column title
68 prop: 'name' // property to read in the data array
69 },
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>
90
91 # Filtering
92 <example module="sampleTable2" animations="true">
93 <file name="index.html">
94 <div ng-controller="SampleCtrl2 as vm">
95 <xos-table data="vm.data" config="vm.config"></xos-table>
96 </div>
97 </file>
98 <file name="script.js">
99 angular.module('sampleTable2', ['xos.uiComponents', 'ngAnimate'])
100 .factory('_', function($window){
101 return $window._;
102 })
103 .controller('SampleCtrl2', function(){
104 this.config = {
105 columns: [
106 {
107 label: 'First Name', // column title
108 prop: 'name' // property to read in the data array
109 },
110 {
111 label: 'Last Name',
112 prop: 'lastname'
113 }
114 ],
115 classes: 'table table-striped table-condensed', // table classes, default to `table table-striped table-bordered`
116 actions: [ // if defined add an action column
117 {
118 label: 'delete', // label
119 icon: 'remove', // icons, refers to bootstraps glyphicon
120 cb: (user) => { // callback, get feeded with the full object
121 console.log(user);
122 },
123 color: 'red' // icon color
124 }
125 ],
126 filter: 'field', // can be by `field` or `fulltext`
127 order: true
128 };
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>
143
144 # Pagination
145 <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'])
153 .factory('_', function($window){
154 return $window._;
155 })
156 .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>
194
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',
212 prop: 'name',
213 link: item => `https://www.google.it/#q=${item.name}`
214 },
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'
229 },
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)
239 }
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'
251 },
252 created: new Date('December 17, 1995 03:24:00'),
253 icon: 'music'
254 },
255 {
256 name: 'Gili',
257 enabled: false,
258 services: ['Cdn', 'IpTv', 'Cache'],
259 details: {
260 c_tag: '675',
261 s_tag: '893'
262 },
263 created: new Date(),
264 icon: 'camera'
265 }
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 type: 'custom',
293 formatter: (val) => {
294
295 let cdnEnabled = val.features.cdn ? 'enabled' : 'disabled';
296 return `
297 Cdn is ${cdnEnabled},
298 uplink speed is ${val.features.uplink_speed}
299 and downlink speed is ${val.features.downlink_speed}
300 `;
301 }
302 }
303 ]
304 };
305
306 this.data = [
307 {
308 username: 'John',
309 features: {
310 "cdn": false,
311 "uplink_speed": 1000000000,
312 "downlink_speed": 1000000000,
313 "uverse": true,
314 "status": "enabled"
315 }
316 },
317 {
318 username: 'Gili',
319 features: {
320 "cdn": true,
321 "uplink_speed": 3000000000,
322 "downlink_speed": 2000000000,
323 "uverse": true,
324 "status": "enabled"
325 }
326 }
327 ]
328 });
329 </file>
330 </example>
331 **/
332
Arpit Agarwal34b63832016-08-08 11:59:45 -0700333 .component('xosTable', {
334 restrict: 'E',
335 bindings: {
336 data: '=',
337 config: '='
338 },
339 template: `
340 <div ng-show="vm.data.length > 0 && vm.loader == false">
341 <div class="row" ng-if="vm.config.filter == 'fulltext'">
342 <div class="col-xs-12">
343 <input
344 class="form-control"
345 placeholder="Type to search.."
346 type="text"
347 ng-model="vm.query"/>
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700348 </div>
Arpit Agarwal34b63832016-08-08 11:59:45 -0700349 </div>
350 <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
351 <thead>
352 <tr>
353 <th ng-repeat="col in vm.columns">
354 {{col.label}}
355 <span ng-if="vm.config.order">
356 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
357 <i class="glyphicon glyphicon-chevron-up"></i>
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700358 </a>
Arpit Agarwal34b63832016-08-08 11:59:45 -0700359 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
360 <i class="glyphicon glyphicon-chevron-down"></i>
361 </a>
362 </span>
363 </th>
364 <th ng-if="vm.config.actions">Actions:</th>
365 </tr>
366 </thead>
367 <tbody ng-if="vm.config.filter == 'field'">
368 <tr>
369 <td ng-repeat="col in vm.columns">
370 <input
371 ng-if="col.type !== 'boolean' && col.type !== 'array' && col.type !== 'object' && col.type !== 'custom'"
372 class="form-control"
373 placeholder="Type to search by {{col.label}}"
374 type="text"
375 ng-model="vm.query[col.prop]"/>
376 <select
377 ng-if="col.type === 'boolean'"
378 class="form-control"
379 ng-model="vm.query[col.prop]">
380 <option value="">-</option>
381 <option value="true">True</option>
382 <option value="false">False</option>
383 </select>
384 </td>
385 <td ng-if="vm.config.actions"></td>
386 </tr>
387 </tbody>
388 <tbody>
389 <tr ng-repeat="item in vm.data | filter:vm.query:vm.comparator | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">
390 <td ng-repeat="col in vm.columns" xos-link-wrapper>
391 <span ng-if="!col.type">{{item[col.prop]}}</span>
392 <span ng-if="col.type === 'boolean'">
393 <i class="glyphicon"
394 ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
395 </i>
396 </span>
397 <span ng-if="col.type === 'date'">
398 {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
399 </span>
400 <span ng-if="col.type === 'array'">
401 {{item[col.prop] | arrayToList}}
402 </span>
403 <span ng-if="col.type === 'object'">
404 <dl class="dl-horizontal">
405 <span ng-repeat="(k,v) in item[col.prop]">
406 <dt>{{k}}</dt>
407 <dd>{{v}}</dd>
408 </span>
409 </dl>
410 </span>
411 <span ng-if="col.type === 'custom'">
412 {{col.formatter(item)}}
413 </span>
414 <span ng-if="col.type === 'icon'">
415 <i class="glyphicon glyphicon-{{col.formatter(item)}}">
416 </i>
417 </span>
418 </td>
419 <td ng-if="vm.config.actions">
420 <a href=""
421 ng-repeat="action in vm.config.actions"
422 ng-click="action.cb(item)"
423 title="{{action.label}}">
424 <i
425 class="glyphicon glyphicon-{{action.icon}}"
426 style="color: {{action.color}};"></i>
427 </a>
428 </td>
429 </tr>
430 </tbody>
431 </table>
432 <xos-pagination
433 ng-if="vm.config.pagination"
434 page-size="vm.config.pagination.pageSize"
435 total-elements="vm.data.length"
436 change="vm.goToPage">
437 </xos-pagination>
438 </div>
439 <div ng-show="(vm.data.length == 0 || !vm.data) && vm.loader == false">
440 <xos-alert config="{type: 'info'}">
441 No data to show.
442 </xos-alert>
443 </div>
444 <div ng-show="vm.loader == true">
445 <div class="loader"></div>
446 </div>
447 `,
448 bindToController: true,
449 controllerAs: 'vm',
450 controller: function(_, $scope, Comparator){
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700451
Arpit Agarwal34b63832016-08-08 11:59:45 -0700452 this.comparator = Comparator;
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700453
Arpit Agarwal34b63832016-08-08 11:59:45 -0700454 this.loader = true;
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700455
Arpit Agarwal34b63832016-08-08 11:59:45 -0700456 $scope.$watch(() => this.data, data => {
457 if(angular.isDefined(data)){
458 this.loader = false;
459 }
460 });
461
462 if(!this.config){
463 throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
464 }
465
466 if(!this.config.columns){
467 throw new Error('[xosTable] Please provide a columns list in the configuration');
468 }
469
470 // handle default ordering
471 if(this.config.order && angular.isObject(this.config.order)){
472 this.reverse = this.config.order.reverse || false;
473 this.orderBy = this.config.order.field || 'id';
474 }
475
476 // if columns with type 'custom' are provided
477 // check that a custom formatte3 is provided too
478 let customCols = _.filter(this.config.columns, {type: 'custom'});
479 if(angular.isArray(customCols) && customCols.length > 0){
480 _.forEach(customCols, (col) => {
481 if(!col.formatter || !angular.isFunction(col.formatter)){
482 throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700483 }
Arpit Agarwal34b63832016-08-08 11:59:45 -0700484 })
485 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700486
Arpit Agarwal34b63832016-08-08 11:59:45 -0700487 // if columns with type 'icon' are provided
488 // check that a custom formatte3 is provided too
489 let iconCols = _.filter(this.config.columns, {type: 'icon'});
490 if(angular.isArray(iconCols) && iconCols.length > 0){
491 _.forEach(iconCols, (col) => {
492 if(!col.formatter || !angular.isFunction(col.formatter)){
493 throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
494 }
495 })
496 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700497
Arpit Agarwal34b63832016-08-08 11:59:45 -0700498 // if a link property is passed,
499 // it should be a function
500 let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
501 if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
502 _.forEach(linkedColumns, (col) => {
503 if(!angular.isFunction(col.link)){
504 throw new Error('[xosTable] The link property should be a function.');
505 }
506 })
507 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700508
Arpit Agarwal34b63832016-08-08 11:59:45 -0700509 this.columns = this.config.columns;
510 this.classes = this.config.classes || 'table table-striped table-bordered';
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700511
Arpit Agarwal34b63832016-08-08 11:59:45 -0700512 if(this.config.actions){
513 // TODO validate action format
514 }
515 if(this.config.pagination){
516 this.currentPage = 0;
517 this.goToPage = (n) => {
518 this.currentPage = n;
519 };
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700520 }
521 }
522 })
523 // TODO move in separate files
524 // TODO test
525 .filter('arrayToList', function(){
526 return (input) => {
527 if(!angular.isArray(input)){
528 return input;
529 }
530 return input.join(', ');
531 }
532 })
533 // TODO test
534 .directive('xosLinkWrapper', function() {
535 return {
536 restrict: 'A',
537 transclude: true,
538 template: `
539 <a ng-if="col.link" href="{{col.link(item)}}">
540 <div ng-transclude></div>
541 </a>
542 <div ng-transclude ng-if="!col.link"></div>
543 `
544 };
545 });
546})();