blob: f677e2a1c65c35ba46e7219fa79ed16b0ebc400e [file] [log] [blame]
Matteo Scandolo686547a2017-08-08 13:05:25 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandoloa5d03d52016-07-21 11:35:46 -070019/**
20 * © OpenCORD
21 *
22 * Visit http://guide.xosproject.org/devguide/addview/ for more information
23 *
24 * Created by teone on 3/24/16.
25 */
26
27(function () {
28 'use strict';
29
30 angular.module('xos.uiComponents')
31
32 /**
33 * @ngdoc directive
34 * @name xos.uiComponents.directive:xosTable
35 * @restrict E
36 * @description The xos-table directive
37 * @param {Object} config The configuration for the component.
38 * ```
39 * {
40 * columns: [
41 * {
42 * label: 'Human readable name',
43 * prop: 'Property to read in the model object',
44 * type: 'boolean'| 'array'| 'object'| 'custom'| 'date' | 'icon' // see examples for more details
45 formatter: fn(), // receive the whole item if tipe is custom and return a string
46 link: fn() // receive the whole item and return an url
47 * }
48 * ],
49 * classes: 'table table-striped table-bordered',
50 * actions: [ // if defined add an action column
51 {
52 label: 'delete',
53 icon: 'remove', // refers to bootstraps glyphicon
54 cb: (user) => { // receive the model
55 console.log(user);
56 },
57 color: 'red'
58 }
59 ],
60 filter: 'field', // can be by `field` or `fulltext`
61 order: true | {field: 'property name', reverse: true | false} // whether to show ordering arrows, or a configuration for a default ordering
62 * }
63 * ```
64 * @param {Array} data The data that should be rendered
65 * @element ANY
66 * @scope
67 * @example
68
69 # Basic usage
70 <example module="sampleTable1">
71 <file name="index.html">
72 <div ng-controller="SampleCtrl1 as vm">
73 <xos-table data="vm.data" config="vm.config"></xos-table>
74 </div>
75 </file>
76 <file name="script.js">
77 angular.module('sampleTable1', ['xos.uiComponents'])
78 .factory('_', function($window){
79 return $window._;
80 })
81 .controller('SampleCtrl1', function(){
82 this.config = {
83 columns: [
84 {
85 label: 'First Name', // column title
86 prop: 'name' // property to read in the data array
87 },
88 {
89 label: 'Last Name',
90 prop: 'lastname'
91 }
92 ]
93 };
94
95 this.data = [
96 {
97 name: 'John',
98 lastname: 'Doe'
99 },
100 {
101 name: 'Gili',
102 lastname: 'Fereydoun'
103 }
104 ]
105 });
106 </file>
107 </example>
108
109 # Filtering
110 <example module="sampleTable2" animations="true">
111 <file name="index.html">
112 <div ng-controller="SampleCtrl2 as vm">
113 <xos-table data="vm.data" config="vm.config"></xos-table>
114 </div>
115 </file>
116 <file name="script.js">
117 angular.module('sampleTable2', ['xos.uiComponents', 'ngAnimate'])
118 .factory('_', function($window){
119 return $window._;
120 })
121 .controller('SampleCtrl2', function(){
122 this.config = {
123 columns: [
124 {
125 label: 'First Name', // column title
126 prop: 'name' // property to read in the data array
127 },
128 {
129 label: 'Last Name',
130 prop: 'lastname'
131 }
132 ],
133 classes: 'table table-striped table-condensed', // table classes, default to `table table-striped table-bordered`
134 actions: [ // if defined add an action column
135 {
136 label: 'delete', // label
137 icon: 'remove', // icons, refers to bootstraps glyphicon
138 cb: (user) => { // callback, get feeded with the full object
139 console.log(user);
140 },
141 color: 'red' // icon color
142 }
143 ],
144 filter: 'field', // can be by `field` or `fulltext`
145 order: true
146 };
147
148 this.data = [
149 {
150 name: 'John',
151 lastname: 'Doe'
152 },
153 {
154 name: 'Gili',
155 lastname: 'Fereydoun'
156 }
157 ]
158 });
159 </file>
160 </example>
161
162 # Pagination
163 <example module="sampleTable3">
164 <file name="index.html">
165 <div ng-controller="SampleCtrl3 as vm">
166 <xos-table data="vm.data" config="vm.config"></xos-table>
167 </div>
168 </file>
169 <file name="script.js">
170 angular.module('sampleTable3', ['xos.uiComponents'])
171 .factory('_', function($window){
172 return $window._;
173 })
174 .controller('SampleCtrl3', function(){
175 this.config = {
176 columns: [
177 {
178 label: 'First Name', // column title
179 prop: 'name' // property to read in the data array
180 },
181 {
182 label: 'Last Name',
183 prop: 'lastname'
184 }
185 ],
186 pagination: {
187 pageSize: 2
188 }
189 };
190
191 this.data = [
192 {
193 name: 'John',
194 lastname: 'Doe'
195 },
196 {
197 name: 'Gili',
198 lastname: 'Fereydoun'
199 },
200 {
201 name: 'Lucky',
202 lastname: 'Clarkson'
203 },
204 {
205 name: 'Tate',
206 lastname: 'Spalding'
207 }
208 ]
209 });
210 </file>
211 </example>
212
213 # Field formatter
214 <example module="sampleTable4">
215 <file name="index.html">
216 <div ng-controller="SampleCtrl as vm">
217 <xos-table data="vm.data" config="vm.config"></xos-table>
218 </div>
219 </file>
220 <file name="script.js">
221 angular.module('sampleTable4', ['xos.uiComponents'])
222 .factory('_', function($window){
223 return $window._;
224 })
225 .controller('SampleCtrl', function(){
226 this.config = {
227 columns: [
228 {
229 label: 'First Name',
230 prop: 'name',
231 link: item => `https://www.google.it/#q=${item.name}`
232 },
233 {
234 label: 'Enabled',
235 prop: 'enabled',
236 type: 'boolean'
237 },
238 {
239 label: 'Services',
240 prop: 'services',
241 type: 'array'
242 },
243 {
244 label: 'Details',
245 prop: 'details',
246 type: 'object'
247 },
248 {
249 label: 'Created',
250 prop: 'created',
251 type: 'date'
252 },
253 {
254 label: 'Icon',
255 type: 'icon',
256 formatter: item => item.icon //note that this refer to [Bootstrap Glyphicon](http://getbootstrap.com/components/#glyphicons)
257 }
258 ]
259 };
260
261 this.data = [
262 {
263 name: 'John',
264 enabled: true,
265 services: ['Cdn', 'IpTv'],
266 details: {
267 c_tag: '243',
268 s_tag: '444'
269 },
270 created: new Date('December 17, 1995 03:24:00'),
271 icon: 'music'
272 },
273 {
274 name: 'Gili',
275 enabled: false,
276 services: ['Cdn', 'IpTv', 'Cache'],
277 details: {
278 c_tag: '675',
279 s_tag: '893'
280 },
281 created: new Date(),
282 icon: 'camera'
283 }
284 ]
285 });
286 </file>
287 </example>
288
289 # Custom formatter
290 <example module="sampleTable5">
291 <file name="index.html">
292 <div ng-controller="SampleCtrl as vm">
293 <xos-table data="vm.data" config="vm.config"></xos-table>
294 </div>
295 </file>
296 <file name="script.js">
297 angular.module('sampleTable5', ['xos.uiComponents'])
298 .factory('_', function($window){
299 return $window._;
300 })
301 .controller('SampleCtrl', function(){
302 this.config = {
303 columns: [
304 {
305 label: 'Username',
306 prop: 'username'
307 },
308 {
309 label: 'Features',
310 type: 'custom',
311 formatter: (val) => {
312
313 let cdnEnabled = val.features.cdn ? 'enabled' : 'disabled';
314 return `
315 Cdn is ${cdnEnabled},
316 uplink speed is ${val.features.uplink_speed}
317 and downlink speed is ${val.features.downlink_speed}
318 `;
319 }
320 }
321 ]
322 };
323
324 this.data = [
325 {
326 username: 'John',
327 features: {
328 "cdn": false,
329 "uplink_speed": 1000000000,
330 "downlink_speed": 1000000000,
331 "uverse": true,
332 "status": "enabled"
333 }
334 },
335 {
336 username: 'Gili',
337 features: {
338 "cdn": true,
339 "uplink_speed": 3000000000,
340 "downlink_speed": 2000000000,
341 "uverse": true,
342 "status": "enabled"
343 }
344 }
345 ]
346 });
347 </file>
348 </example>
349 **/
350
Arpit Agarwal34b63832016-08-08 11:59:45 -0700351 .component('xosTable', {
352 restrict: 'E',
353 bindings: {
354 data: '=',
355 config: '='
356 },
357 template: `
358 <div ng-show="vm.data.length > 0 && vm.loader == false">
359 <div class="row" ng-if="vm.config.filter == 'fulltext'">
360 <div class="col-xs-12">
361 <input
362 class="form-control"
363 placeholder="Type to search.."
364 type="text"
365 ng-model="vm.query"/>
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700366 </div>
Arpit Agarwal34b63832016-08-08 11:59:45 -0700367 </div>
368 <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
369 <thead>
370 <tr>
371 <th ng-repeat="col in vm.columns">
372 {{col.label}}
373 <span ng-if="vm.config.order">
374 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
375 <i class="glyphicon glyphicon-chevron-up"></i>
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700376 </a>
Arpit Agarwal34b63832016-08-08 11:59:45 -0700377 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
378 <i class="glyphicon glyphicon-chevron-down"></i>
379 </a>
380 </span>
381 </th>
382 <th ng-if="vm.config.actions">Actions:</th>
383 </tr>
384 </thead>
385 <tbody ng-if="vm.config.filter == 'field'">
386 <tr>
387 <td ng-repeat="col in vm.columns">
388 <input
389 ng-if="col.type !== 'boolean' && col.type !== 'array' && col.type !== 'object' && col.type !== 'custom'"
390 class="form-control"
391 placeholder="Type to search by {{col.label}}"
392 type="text"
393 ng-model="vm.query[col.prop]"/>
394 <select
395 ng-if="col.type === 'boolean'"
396 class="form-control"
397 ng-model="vm.query[col.prop]">
398 <option value="">-</option>
399 <option value="true">True</option>
400 <option value="false">False</option>
401 </select>
402 </td>
403 <td ng-if="vm.config.actions"></td>
404 </tr>
405 </tbody>
406 <tbody>
407 <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">
408 <td ng-repeat="col in vm.columns" xos-link-wrapper>
Matteo Scandoloe57712f2016-09-21 15:27:36 -0700409 <span ng-if="!col.type || col.type === 'text'">{{item[col.prop]}}</span>
Arpit Agarwal34b63832016-08-08 11:59:45 -0700410 <span ng-if="col.type === 'boolean'">
411 <i class="glyphicon"
412 ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
413 </i>
414 </span>
415 <span ng-if="col.type === 'date'">
416 {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
417 </span>
418 <span ng-if="col.type === 'array'">
419 {{item[col.prop] | arrayToList}}
420 </span>
421 <span ng-if="col.type === 'object'">
422 <dl class="dl-horizontal">
423 <span ng-repeat="(k,v) in item[col.prop]">
424 <dt>{{k}}</dt>
425 <dd>{{v}}</dd>
426 </span>
427 </dl>
428 </span>
429 <span ng-if="col.type === 'custom'">
430 {{col.formatter(item)}}
431 </span>
432 <span ng-if="col.type === 'icon'">
433 <i class="glyphicon glyphicon-{{col.formatter(item)}}">
434 </i>
435 </span>
436 </td>
437 <td ng-if="vm.config.actions">
438 <a href=""
439 ng-repeat="action in vm.config.actions"
440 ng-click="action.cb(item)"
441 title="{{action.label}}">
442 <i
443 class="glyphicon glyphicon-{{action.icon}}"
444 style="color: {{action.color}};"></i>
445 </a>
446 </td>
447 </tr>
448 </tbody>
449 </table>
450 <xos-pagination
451 ng-if="vm.config.pagination"
452 page-size="vm.config.pagination.pageSize"
453 total-elements="vm.data.length"
454 change="vm.goToPage">
455 </xos-pagination>
456 </div>
457 <div ng-show="(vm.data.length == 0 || !vm.data) && vm.loader == false">
458 <xos-alert config="{type: 'info'}">
459 No data to show.
460 </xos-alert>
461 </div>
462 <div ng-show="vm.loader == true">
463 <div class="loader"></div>
464 </div>
465 `,
466 bindToController: true,
467 controllerAs: 'vm',
468 controller: function(_, $scope, Comparator){
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700469
Arpit Agarwal34b63832016-08-08 11:59:45 -0700470 this.comparator = Comparator;
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700471
Arpit Agarwal34b63832016-08-08 11:59:45 -0700472 this.loader = true;
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700473
Arpit Agarwal34b63832016-08-08 11:59:45 -0700474 $scope.$watch(() => this.data, data => {
475 if(angular.isDefined(data)){
476 this.loader = false;
477 }
478 });
479
480 if(!this.config){
481 throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
482 }
483
484 if(!this.config.columns){
485 throw new Error('[xosTable] Please provide a columns list in the configuration');
486 }
487
488 // handle default ordering
489 if(this.config.order && angular.isObject(this.config.order)){
490 this.reverse = this.config.order.reverse || false;
491 this.orderBy = this.config.order.field || 'id';
492 }
493
494 // if columns with type 'custom' are provided
495 // check that a custom formatte3 is provided too
496 let customCols = _.filter(this.config.columns, {type: 'custom'});
497 if(angular.isArray(customCols) && customCols.length > 0){
498 _.forEach(customCols, (col) => {
499 if(!col.formatter || !angular.isFunction(col.formatter)){
500 throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700501 }
Arpit Agarwal34b63832016-08-08 11:59:45 -0700502 })
503 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700504
Arpit Agarwal34b63832016-08-08 11:59:45 -0700505 // if columns with type 'icon' are provided
506 // check that a custom formatte3 is provided too
507 let iconCols = _.filter(this.config.columns, {type: 'icon'});
508 if(angular.isArray(iconCols) && iconCols.length > 0){
509 _.forEach(iconCols, (col) => {
510 if(!col.formatter || !angular.isFunction(col.formatter)){
511 throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
512 }
513 })
514 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700515
Arpit Agarwal34b63832016-08-08 11:59:45 -0700516 // if a link property is passed,
517 // it should be a function
518 let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
519 if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
520 _.forEach(linkedColumns, (col) => {
521 if(!angular.isFunction(col.link)){
522 throw new Error('[xosTable] The link property should be a function.');
523 }
524 })
525 }
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700526
Arpit Agarwal34b63832016-08-08 11:59:45 -0700527 this.columns = this.config.columns;
528 this.classes = this.config.classes || 'table table-striped table-bordered';
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700529
Arpit Agarwal34b63832016-08-08 11:59:45 -0700530 if(this.config.actions){
531 // TODO validate action format
532 }
533 if(this.config.pagination){
534 this.currentPage = 0;
535 this.goToPage = (n) => {
536 this.currentPage = n;
537 };
Matteo Scandoloa5d03d52016-07-21 11:35:46 -0700538 }
539 }
540 })
541 // TODO move in separate files
542 // TODO test
543 .filter('arrayToList', function(){
544 return (input) => {
545 if(!angular.isArray(input)){
546 return input;
547 }
548 return input.join(', ');
549 }
550 })
551 // TODO test
552 .directive('xosLinkWrapper', function() {
553 return {
554 restrict: 'A',
555 transclude: true,
556 template: `
557 <a ng-if="col.link" href="{{col.link(item)}}">
558 <div ng-transclude></div>
559 </a>
560 <div ng-transclude ng-if="!col.link"></div>
561 `
562 };
563 });
564})();