blob: 0e28a195ebcd535907516e4300f89a76fe2b8500 [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',
25 * prop: 'Property to read in the model object'
26 * }
27 * ],
28 * classes: 'table table-striped table-bordered',
29 * actions: [ // if defined add an action column
30 {
31 label: 'delete',
32 icon: 'remove', // refers to bootstraps glyphicon
33 cb: (user) => { // receive the model
34 console.log(user);
35 },
36 color: 'red'
37 }
Matteo Scandolo5677bb12016-04-14 17:21:45 -070038 ],
39 filter: 'field', // can be by `field` or `fulltext`
40 order: true // whether to show ordering arrows
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070041 * }
42 * ```
43 * @param {Array} data The data that should be rendered
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070044 * @element ANY
45 * @scope
46 * @example
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070047
48 # Basic usage
Matteo Scandoloe15a8202016-04-15 14:27:54 -070049 <example module="sampleTable1">
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070050 <file name="index.html">
51 <div ng-controller="SampleCtrl1 as vm">
52 <xos-table data="vm.data" config="vm.config"></xos-table>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070053 </div>
54 </file>
55 <file name="script.js">
Matteo Scandoloe15a8202016-04-15 14:27:54 -070056 angular.module('sampleTable1', ['xos.uiComponents'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070057 .factory('_', function($window){
58 return $window._;
59 })
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070060 .controller('SampleCtrl1', function(){
61 this.config = {
62 columns: [
63 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -070064 label: 'First Name', // column title
65 prop: 'name' // property to read in the data array
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070066 },
67 {
68 label: 'Last Name',
69 prop: 'lastname'
70 }
71 ]
72 };
73
74 this.data = [
75 {
76 name: 'John',
77 lastname: 'Doe'
78 },
79 {
80 name: 'Gili',
81 lastname: 'Fereydoun'
82 }
83 ]
84 });
85 </file>
86 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070087
88 # Filtering
Matteo Scandolo88e18462016-04-25 14:24:18 -070089 <example module="sampleTable2" animations="true">
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070090 <file name="index.html">
91 <div ng-controller="SampleCtrl2 as vm">
92 <xos-table data="vm.data" config="vm.config"></xos-table>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -070093 </div>
94 </file>
95 <file name="script.js">
Matteo Scandolo88e18462016-04-25 14:24:18 -070096 angular.module('sampleTable2', ['xos.uiComponents', 'ngAnimate'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -070097 .factory('_', function($window){
98 return $window._;
99 })
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700100 .controller('SampleCtrl2', function(){
101 this.config = {
102 columns: [
103 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700104 label: 'First Name', // column title
105 prop: 'name' // property to read in the data array
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700106 },
107 {
108 label: 'Last Name',
109 prop: 'lastname'
110 }
111 ],
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700112 classes: 'table table-striped table-condensed', // table classes, default to `table table-striped table-bordered`
113 actions: [ // if defined add an action column
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700114 {
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700115 label: 'delete', // label
116 icon: 'remove', // icons, refers to bootstraps glyphicon
117 cb: (user) => { // callback, get feeded with the full object
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700118 console.log(user);
119 },
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700120 color: 'red' // icon color
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700121 }
Matteo Scandolo5677bb12016-04-14 17:21:45 -0700122 ],
123 filter: 'field', // can be by `field` or `fulltext`
124 order: true
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700125 };
126
127 this.data = [
128 {
129 name: 'John',
130 lastname: 'Doe'
131 },
132 {
133 name: 'Gili',
134 lastname: 'Fereydoun'
135 }
136 ]
137 });
138 </file>
139 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700140
141 # Pagination
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700142 <example module="sampleTable3">
143 <file name="index.html">
144 <div ng-controller="SampleCtrl3 as vm">
145 <xos-table data="vm.data" config="vm.config"></xos-table>
146 </div>
147 </file>
148 <file name="script.js">
149 angular.module('sampleTable3', ['xos.uiComponents'])
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700150 .factory('_', function($window){
151 return $window._;
152 })
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700153 .controller('SampleCtrl3', function(){
154 this.config = {
155 columns: [
156 {
157 label: 'First Name', // column title
158 prop: 'name' // property to read in the data array
159 },
160 {
161 label: 'Last Name',
162 prop: 'lastname'
163 }
164 ],
165 pagination: {
166 pageSize: 2
167 }
168 };
169
170 this.data = [
171 {
172 name: 'John',
173 lastname: 'Doe'
174 },
175 {
176 name: 'Gili',
177 lastname: 'Fereydoun'
178 },
179 {
180 name: 'Lucky',
181 lastname: 'Clarkson'
182 },
183 {
184 name: 'Tate',
185 lastname: 'Spalding'
186 }
187 ]
188 });
189 </file>
190 </example>
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700191
192 # Field formatter
193 <example module="sampleTable4">
194 <file name="index.html">
195 <div ng-controller="SampleCtrl as vm">
196 <xos-table data="vm.data" config="vm.config"></xos-table>
197 </div>
198 </file>
199 <file name="script.js">
200 angular.module('sampleTable4', ['xos.uiComponents'])
201 .factory('_', function($window){
202 return $window._;
203 })
204 .controller('SampleCtrl', function(){
205 this.config = {
206 columns: [
207 {
208 label: 'First Name',
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700209 prop: 'name',
210 link: item => `https://www.google.it/#q=${item.name}`
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700211 },
212 {
213 label: 'Enabled',
214 prop: 'enabled',
215 type: 'boolean'
216 },
217 {
218 label: 'Services',
219 prop: 'services',
220 type: 'array'
221 },
222 {
223 label: 'Details',
224 prop: 'details',
225 type: 'object'
226 }
227 ]
228 };
229
230 this.data = [
231 {
232 name: 'John',
233 enabled: true,
234 services: ['Cdn', 'IpTv'],
235 details: {
236 c_tag: '243',
237 s_tag: '444'
238 }
239 },
240 {
241 name: 'Gili',
242 enabled: false,
243 services: ['Cdn', 'IpTv', 'Cache'],
244 details: {
245 c_tag: '675',
246 s_tag: '893'
247 }
248 }
249 ]
250 });
251 </file>
252 </example>
253
254 # Custom formatter
255 <example module="sampleTable5">
256 <file name="index.html">
257 <div ng-controller="SampleCtrl as vm">
258 <xos-table data="vm.data" config="vm.config"></xos-table>
259 </div>
260 </file>
261 <file name="script.js">
262 angular.module('sampleTable5', ['xos.uiComponents'])
263 .factory('_', function($window){
264 return $window._;
265 })
266 .controller('SampleCtrl', function(){
267 this.config = {
268 columns: [
269 {
270 label: 'Username',
271 prop: 'username'
272 },
273 {
274 label: 'Features',
275 prop: 'features',
276 type: 'custom',
277 formatter: (val) => {
278
279 let cdnEnabled = val.cdn ? 'enabled' : 'disabled';
280 return `
281 Cdn is ${cdnEnabled},
282 uplink speed is ${val.uplink_speed}
283 and downlink speed is ${val.downlink_speed}
284 `;
285 }
286 }
287 ]
288 };
289
290 this.data = [
291 {
292 username: 'John',
293 features: {
294 "cdn": false,
295 "uplink_speed": 1000000000,
296 "downlink_speed": 1000000000,
297 "uverse": true,
298 "status": "enabled"
299 }
300 },
301 {
302 username: 'Gili',
303 features: {
304 "cdn": true,
305 "uplink_speed": 3000000000,
306 "downlink_speed": 2000000000,
307 "uverse": true,
308 "status": "enabled"
309 }
310 }
311 ]
312 });
313 </file>
314 </example>
Matteo Scandoloaa683dd2016-04-14 15:34:12 -0700315 **/
316
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700317 .directive('xosTable', function(){
318 return {
319 restrict: 'E',
320 scope: {
321 data: '=',
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700322 config: '='
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700323 },
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700324 template: `
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700325 <div ng-show="vm.data.length > 0">
326 <div class="row" ng-if="vm.config.filter == 'fulltext'">
327 <div class="col-xs-12">
328 <input
329 class="form-control"
330 placeholder="Type to search.."
331 type="text"
332 ng-model="vm.query"/>
333 </div>
334 </div>
Matteo Scandolob0280752016-04-25 10:31:22 -0700335 <table ng-class="vm.classes" ng-hide="vm.data.length == 0">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700336 <thead>
337 <tr>
338 <th ng-repeat="col in vm.columns">
339 {{col.label}}
340 <span ng-if="vm.config.order">
341 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">
342 <i class="glyphicon glyphicon-chevron-up"></i>
343 </a>
344 <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">
345 <i class="glyphicon glyphicon-chevron-down"></i>
346 </a>
347 </span>
348 </th>
Matteo Scandolodc249eb2016-04-26 11:44:36 -0700349 <th ng-if="vm.config.actions">Actions:</th>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700350 </tr>
351 </thead>
352 <tbody ng-if="vm.config.filter == 'field'">
353 <tr>
354 <td ng-repeat="col in vm.columns">
355 <input
356 class="form-control"
357 placeholder="Type to search by {{col.label}}"
358 type="text"
359 ng-model="vm.query[col.prop]"/>
360 </td>
361 <td ng-if="vm.config.actions"></td>
362 </tr>
363 </tbody>
364 <tbody>
365 <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 -0700366 <td ng-repeat="col in vm.columns" link-wrapper>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700367 <span ng-if="!col.type">{{item[col.prop]}}</span>
368 <span ng-if="col.type === 'boolean'">
369 <i class="glyphicon"
370 ng-class="{'glyphicon-ok': item[col.prop], 'glyphicon-remove': !item[col.prop]}">
371 </i>
372 </span>
373 <span ng-if="col.type === 'date'">
374 {{item[col.prop] | date:'H:mm MMM d, yyyy'}}
375 </span>
376 <span ng-if="col.type === 'array'">
377 {{item[col.prop] | arrayToList}}
378 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700379 <span ng-if="col.type === 'object'">
Matteo Scandolo0cd52c12016-05-06 11:39:56 -0700380 <dl class="dl-horizontal">
381 <span ng-repeat="(k,v) in item[col.prop]">
382 <dt>{{k}}</dt>
383 <dd>{{v}}</dd>
384 </span>
Matteo Scandolo58705a42016-05-06 10:08:34 -0700385 </dl>
386 </span>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700387 <span ng-if="col.type === 'custom'">
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700388 {{col.formatter(item)}}
389 </span>
390 <span ng-if="col.type === 'icon'">
391 <i class="glyphicon glyphicon-{{col.formatter(item)}}">
392 </i>
Matteo Scandolof9700a32016-05-06 09:42:45 -0700393 </span>
394 </td>
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700395 <td ng-if="vm.config.actions">
396 <a href=""
397 ng-repeat="action in vm.config.actions"
398 ng-click="action.cb(item)"
399 title="{{action.label}}">
400 <i
401 class="glyphicon glyphicon-{{action.icon}}"
402 style="color: {{action.color}};"></i>
403 </a>
404 </td>
405 </tr>
406 </tbody>
407 </table>
408 <xos-pagination
409 ng-if="vm.config.pagination"
410 page-size="vm.config.pagination.pageSize"
411 total-elements="vm.data.length"
412 change="vm.goToPage">
413 </xos-pagination>
414 </div>
415 <div ng-show="vm.data.length == 0 || !vm.data">
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700416 <xos-alert config="{type: 'info'}">
Matteo Scandoloe15a8202016-04-15 14:27:54 -0700417 No data to show.
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700418 </xos-alert>
Matteo Scandoloa6a9e612016-04-14 16:52:13 -0700419 </div>
Matteo Scandolob7a86dc2016-04-14 11:46:30 -0700420 `,
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700421 bindToController: true,
422 controllerAs: 'vm',
Matteo Scandolof9700a32016-05-06 09:42:45 -0700423 controller: function(_){
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700424
425 if(!this.config){
426 throw new Error('[xosTable] Please provide a configuration via the "config" attribute');
427 }
428
429 if(!this.config.columns){
430 throw new Error('[xosTable] Please provide a columns list in the configuration');
431 }
432
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700433 // handle default ordering
434 if(this.config.order && angular.isObject(this.config.order)){
435 this.reverse = this.config.order.reverse || false;
436 this.orderBy = this.config.order.field || 'id';
437 }
438
439 // if columns with type 'custom' are provided
440 // check that a custom formatte3 is provided too
Matteo Scandolof9700a32016-05-06 09:42:45 -0700441 let customCols = _.filter(this.config.columns, {type: 'custom'});
442 if(angular.isArray(customCols) && customCols.length > 0){
443 _.forEach(customCols, (col) => {
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700444 if(!col.formatter || !angular.isFunction(col.formatter)){
Matteo Scandolof9700a32016-05-06 09:42:45 -0700445 throw new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.');
446 }
447 })
448 }
449
Matteo Scandolod49ed5f2016-05-13 10:12:09 -0700450 // if columns with type 'icon' are provided
451 // check that a custom formatte3 is provided too
452 let iconCols = _.filter(this.config.columns, {type: 'icon'});
453 if(angular.isArray(iconCols) && iconCols.length > 0){
454 _.forEach(iconCols, (col) => {
455 if(!col.formatter || !angular.isFunction(col.formatter)){
456 throw new Error('[xosTable] You have provided an icon field type, a formatter function should provided too.');
457 }
458 })
459 }
460
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700461 // if a link property is passed,
462 // it should be a function
463 let linkedColumns = _.filter(this.config.columns, col => angular.isDefined(col.link));
464 if(angular.isArray(linkedColumns) && linkedColumns.length > 0){
465 _.forEach(linkedColumns, (col) => {
466 if(!angular.isFunction(col.link)){
467 throw new Error('[xosTable] The link property should be a function.');
468 }
469 })
470 }
471
Matteo Scandolo18adcb52016-04-14 12:06:50 -0700472 this.columns = this.config.columns;
473 this.classes = this.config.classes || 'table table-striped table-bordered';
474
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700475 if(this.config.actions){
Matteo Scandolocc6954e2016-04-15 12:20:14 -0700476 // TODO validate action format
477 }
478 if(this.config.pagination){
479 this.currentPage = 0;
480 this.goToPage = (n) => {
481 this.currentPage = n;
482 };
Matteo Scandolo9e6c6fc2016-04-14 14:59:09 -0700483 }
484
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700485 }
486 }
487 })
Matteo Scandolobee3eaf2016-05-06 13:09:19 -0700488 // TODO move in separate files
489 // TODO test
490 .filter('arrayToList', function(){
491 return (input) => {
492 if(!angular.isArray(input)){
493 return input;
494 }
495 return input.join(', ');
496 }
497 })
498 // TODO test
499 .directive('linkWrapper', function() {
500 return {
501 restrict: 'A',
502 transclude: true,
503 template: `
504 <a ng-if="col.link" href="{{col.link(item)}}">
505 <div ng-transclude></div>
506 </a>
507 <div ng-transclude ng-if="!col.link"></div>
508 `
509 };
510 });
Matteo Scandolo91fe03d2016-03-24 15:29:52 -0700511})();