blob: a42f3a4bd22b268c9099c0c88549e97ca80d0b63 [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',
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700292 type: 'custom',
293 formatter: (val) => {
294
Matteo Scandoloa7ad4992016-05-09 15:27:47 -0700295 let cdnEnabled = val.features.cdn ? 'enabled' : 'disabled';
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700296 return `
297 Cdn is ${cdnEnabled},
Matteo Scandoloa7ad4992016-05-09 15:27:47 -0700298 uplink speed is ${val.features.uplink_speed}
299 and downlink speed is ${val.features.downlink_speed}
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700300 `;
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>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700331 **/
332
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700333 .directive('xosTable', function(){
334 return {
335 restrict: 'E',
336 scope: {
337 data: '=',
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700338 config: '='
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700339 },
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700340 template: `
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700341 <div ng-show="vm.data.length > 0">
342 <div class="row" ng-if="vm.config.filter == 'fulltext'">
343 <div class="col-xs-12">
344 <input
345 class="form-control"
346 placeholder="Type to search.."
347 type="text"
348 ng-model="vm.query"/>
349 </div>
350 </div>
Matteo Scandolob0280752016-04-25 10:31:22 -0700351 <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700352 <thead>
353 <tr>
354 <th ng-repeat="col in vm.columns">
355 {{col.label}}
356 <span ng-if="vm.config.order">
357 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
358 <i class="glyphicon glyphicon-chevron-up"></i>
359 </a>
360 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
361 <i class="glyphicon glyphicon-chevron-down"></i>
362 </a>
363 </span>
364 </th>
Matteo Scandolodc249eb2016-04-26 11:44:36 -0700365 <th ng-if="vm.config.actions">Actions:</th>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700366 </tr>
367 </thead>
368 <tbody ng-if="vm.config.filter == 'field'">
369 <tr>
370 <td ng-repeat="col in vm.columns">
371 <input
372 class="form-control"
373 placeholder="Type to search by {{col.label}}"
374 type="text"
375 ng-model="vm.query[col.prop]"/>
376 </td>
377 <td ng-if="vm.config.actions"></td>
378 </tr>
379 </tbody>
380 <tbody>
381 <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 -0700382 <td ng-repeat="col in vm.columns" link-wrapper>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700383 <span ng-if="!col.type">{{item[col.prop]}}</span>
384 <span ng-if="col.type === 'boolean'">
385 <i class="glyphicon"
386 ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
387 </i>
388 </span>
389 <span ng-if="col.type === 'date'">
390 {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
391 </span>
392 <span ng-if="col.type === 'array'">
393 {{item[col.prop] | arrayToList}}
394 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700395 <span ng-if="col.type === 'object'">
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700396 <dl class="dl-horizontal">
397 <span ng-repeat="(k,v) in item[col.prop]">
398 <dt>{{k}}</dt>
399 <dd>{{v}}</dd>
400 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700401 </dl>
402 </span>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700403 <span ng-if="col.type === 'custom'">
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700404 {{col.formatter(item)}}
405 </span>
406 <span ng-if="col.type === 'icon'">
407 <i class="glyphicon glyphicon-{{col.formatter(item)}}">
408 </i>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700409 </span>
410 </td>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700411 <td ng-if="vm.config.actions">
412 <a href=""
413 ng-repeat="action in vm.config.actions"
414 ng-click="action.cb(item)"
415 title="{{action.label}}">
416 <i
417 class="glyphicon glyphicon-{{action.icon}}"
418 style="color: {{action.color}};"></i>
419 </a>
420 </td>
421 </tr>
422 </tbody>
423 </table>
424 <xos-pagination
425 ng-if="vm.config.pagination"
426 page-size="vm.config.pagination.pageSize"
427 total-elements="vm.data.length"
428 change="vm.goToPage">
429 </xos-pagination>
430 </div>
431 <div ng-show="vm.data.length == 0 || !vm.data">
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700432 <xos-alert config="{type: 'info'}">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700433 No data to show.
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700434 </xos-alert>
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700435 </div>
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700436 `,
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700437 bindToController: true,
438 controllerAs: 'vm',
Matteo Scandolof9700a32016-05-06 09:42:45 -0700439 controller: function(_){
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700440
441 if(!this.config){
442 throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
443 }
444
445 if(!this.config.columns){
446 throw new Error('[xosTable] Please provide a columns list in the configuration');
447 }
448
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700449 // handle default ordering
450 if(this.config.order && angular.isObject(this.config.order)){
451 this.reverse = this.config.order.reverse || false;
452 this.orderBy = this.config.order.field || 'id';
453 }
454
455 // if columns with type 'custom' are provided
456 // check that a custom formatte3 is provided too
Matteo Scandolof9700a32016-05-06 09:42:45 -0700457 let customCols = _.filter(this.config.columns, {type: 'custom'});
458 if(angular.isArray(customCols) && customCols.length > 0){
459 _.forEach(customCols, (col) => {
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700460 if(!col.formatter || !angular.isFunction(col.formatter)){
Matteo Scandolof9700a32016-05-06 09:42:45 -0700461 throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
462 }
463 })
464 }
465
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700466 // if columns with type 'icon' are provided
467 // check that a custom formatte3 is provided too
468 let iconCols = _.filter(this.config.columns, {type: 'icon'});
469 if(angular.isArray(iconCols) && iconCols.length > 0){
470 _.forEach(iconCols, (col) => {
471 if(!col.formatter || !angular.isFunction(col.formatter)){
472 throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
473 }
474 })
475 }
476
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700477 // if a link property is passed,
478 // it should be a function
479 let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
480 if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
481 _.forEach(linkedColumns, (col) => {
482 if(!angular.isFunction(col.link)){
483 throw new Error('[xosTable] The link property should be a function.');
484 }
485 })
486 }
487
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700488 this.columns = this.config.columns;
489 this.classes = this.config.classes || 'table table-striped table-bordered';
490
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700491 if(this.config.actions){
Matteo Scandolocc6954e2016-04-15 12:20:14 -0700492 // TODO validate action format
493 }
494 if(this.config.pagination){
495 this.currentPage = 0;
496 this.goToPage = (n) => {
497 this.currentPage = n;
498 };
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700499 }
500
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700501 }
502 }
503 })
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700504 // TODO move in separate files
505 // TODO test
506 .filter('arrayToList', function(){
507 return (input) => {
508 if(!angular.isArray(input)){
509 return input;
510 }
511 return input.join(', ');
512 }
513 })
514 // TODO test
515 .directive('linkWrapper', function() {
516 return {
517 restrict: 'A',
518 transclude: true,
519 template: `
520 <a ng-if="col.link" href="{{col.link(item)}}">
521 <div ng-transclude></div>
522 </a>
523 <div ng-transclude ng-if="!col.link"></div>
524 `
525 };
526 });
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700527})();