ezframe 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +11 -4
  4. data/app_template/asset/image/favicon.ico +0 -0
  5. data/app_template/asset/js/ezframe.js +288 -0
  6. data/app_template/config/generic.yml +3 -0
  7. data/app_template/config/materialize.yml +5 -0
  8. data/{example/chat → app_template}/config.ru +2 -10
  9. data/app_template/pages/basic.rb +5 -0
  10. data/exe/create_table.rb +1 -0
  11. data/exe/setup.rb +15 -0
  12. data/ezframe.gemspec +3 -4
  13. data/lib/ezframe/auth.rb +15 -12
  14. data/lib/ezframe/column_set.rb +68 -28
  15. data/lib/ezframe/column_type.rb +231 -68
  16. data/lib/ezframe/config.rb +4 -0
  17. data/lib/ezframe/controller.rb +20 -10
  18. data/lib/ezframe/database.rb +10 -3
  19. data/lib/ezframe/ht.rb +167 -0
  20. data/lib/ezframe/html.rb +28 -4
  21. data/lib/ezframe/japanese_utils.rb +40 -0
  22. data/lib/ezframe/{pages.rb → loader.rb} +3 -0
  23. data/lib/ezframe/materialize.rb +55 -20
  24. data/lib/ezframe/model.rb +0 -2
  25. data/lib/ezframe/page_base.rb +18 -12
  26. data/lib/ezframe/page_kit.rb +12 -33
  27. data/lib/ezframe/template.rb +20 -15
  28. data/lib/ezframe/util.rb +5 -0
  29. data/lib/ezframe/version.rb +1 -1
  30. data/lib/ezframe.rb +5 -4
  31. metadata +27 -70
  32. data/example/auth/Gemfile +0 -8
  33. data/example/auth/asset/css/materialize.min.css +0 -13
  34. data/example/auth/asset/js/common.js +0 -200
  35. data/example/auth/asset/js/htmlgen.js +0 -79
  36. data/example/auth/columns/user.yml +0 -12
  37. data/example/auth/config/view_conf.yml +0 -3
  38. data/example/auth/config.ru +0 -26
  39. data/example/auth/pages/app.rb +0 -61
  40. data/example/auth/template/base.html +0 -12
  41. data/example/chat/Gemfile +0 -9
  42. data/example/chat/asset/css/materialize.min.css +0 -13
  43. data/example/chat/asset/js/common.js +0 -200
  44. data/example/chat/asset/js/htmlgen.js +0 -79
  45. data/example/chat/columns/belong.yml +0 -6
  46. data/example/chat/columns/channel.yml +0 -3
  47. data/example/chat/columns/talk.yml +0 -6
  48. data/example/chat/columns/user.yml +0 -12
  49. data/example/chat/config/view_conf.yml +0 -3
  50. data/example/chat/pages/app.rb +0 -59
  51. data/example/chat/template/base.html +0 -12
  52. data/example/todo/Gemfile +0 -8
  53. data/example/todo/asset/css/datatable.css +0 -54
  54. data/example/todo/asset/css/materialize.min.css +0 -13
  55. data/example/todo/asset/js/common.js +0 -135
  56. data/example/todo/asset/js/datatable.js +0 -1814
  57. data/example/todo/asset/js/htmlgen.js +0 -79
  58. data/example/todo/asset/js/init.js +0 -3
  59. data/example/todo/asset/js/materialize.min.js +0 -6
  60. data/example/todo/asset/js/mydatatable.js +0 -9
  61. data/example/todo/asset/js/mymaterialize.js +0 -22
  62. data/example/todo/columns/todo.yml +0 -9
  63. data/example/todo/config/view_conf.yml +0 -3
  64. data/example/todo/config.ru +0 -15
  65. data/example/todo/pages/app.rb +0 -93
  66. data/example/todo/template/base.html +0 -12
  67. data/exe/myrackup +0 -5
  68. data/lib/ezframe/hthash.rb +0 -116
@@ -1,1814 +0,0 @@
1
- "use strict";
2
-
3
- var DataTable = function (table, opts) {
4
-
5
- this.options = {};
6
- this.table = table;
7
- this.currentPage = 0;
8
- this.currentStart = 0; // different from currentPage * pageSize because there is a filter
9
- this.filterIndex = [];
10
-
11
- for (var k in DataTable.defaultOptions) {
12
- if (DataTable.defaultOptions.hasOwnProperty(k)) {
13
- if (opts.hasOwnProperty(k)) {
14
- this.options[k] = opts[k] ;
15
- }
16
- else {
17
- this.options[k] = DataTable.defaultOptions[k] ;
18
- }
19
- }
20
- }
21
-
22
- if (opts.hasOwnProperty('data')) {
23
- this.options.data = opts.data ;
24
- }
25
-
26
- /* If nb columns not specified, count the number of column from thead. */
27
- if (this.options.nbColumns < 0) {
28
- this.options.nbColumns = this.table.tHead.rows[0].cells.length;
29
- }
30
-
31
- /* Create the base for pagination. */
32
- this.pagingDivs = document.querySelectorAll(this.options.pagingDivSelector);
33
- for (var i = 0; i < this.pagingDivs.length; ++i) {
34
- var div = this.pagingDivs[i];
35
- this.addClass(div, 'pagination-datatables');
36
- this.addClass(div, this.options.pagingDivClass);
37
- var ul = document.createElement('ul');
38
- this.addClass(ul, this.options.pagingListClass);
39
- div.appendChild(ul);
40
- }
41
- this.pagingLists = document.querySelectorAll(this.options.pagingDivSelector + ' ul');
42
- this.counterDivs = document.querySelectorAll(this.options.counterDivSelector);
43
- this.loadingDiv = document.querySelector(this.options.loadingDivSelector);
44
-
45
- /* DATA ! */
46
-
47
- var dataTable = this;
48
-
49
- if (!this.table.tHead) {
50
- this.table.tHead = document.createElement('thead');
51
- this.table.appendChild(this.table.rows[0]);
52
- }
53
-
54
- /* Compatibility issue (forceStrings => No defined data types). */
55
- if (this.options.forceStrings) {
56
- this.options.dataTypes = false ;
57
- }
58
-
59
- var ths = this.table.tHead.rows[0].cells ;
60
-
61
- if (!this.table.tBodies[0]) {
62
- this.table.tBodies[0] = document.createElement('tbody');
63
- }
64
-
65
- if (this.options.data instanceof Array) {
66
- this.data = this.options.data;
67
- }
68
- else if (this.options.data instanceof Object) {
69
- var ajaxOptions = DataTable.defaultAjaxOptions;
70
- for (var k in this.options.data) {
71
- ajaxOptions[k] = this.options.data[k];
72
- }
73
- this.options.data = ajaxOptions;
74
- var fetchData = true;
75
- if (fetchData) {
76
- if (this.table.dataset.size !== undefined) {
77
- this.options.data.size = parseInt(this.table.dataset.size, 10);
78
- }
79
- this.data = [];
80
- if (this.options.data.size !== undefined) {
81
- this.loadingDiv.innerHTML = '<div class="progress datatable-load-bar">'
82
- + '<div class="progress-bar progress-bar-striped active" style="width: 0%;">'
83
- + '</div></div>';
84
- if (this.options.data.allInOne) {
85
- this.getAjaxDataAsync(true);
86
- }
87
- else {
88
- for (var i = 0; i < this.options.data.size;
89
- i += this.options.pageSize * this.options.pagingNumberOfPages) {
90
- this.getAjaxDataAsync(i);
91
- }
92
- }
93
- }
94
- else {
95
- this.loadingDiv.innerHTML = '<div class="progress datatable-load-bar">'
96
- + '<div class="progress-bar progress-bar-striped active" style="width: 0%;">'
97
- + '</div></div>';
98
- this.getAjaxDataAsync(0, true);
99
- }
100
- }
101
- }
102
- else if (this.table.tBodies[0].rows.length > 0) {
103
- this.data = [];
104
- var rows = this.table.tBodies[0].rows;
105
- var nCols = rows[0].cells.length ;
106
- for (var i = 0; i < rows.length; ++i) {
107
- this.data.push ([]) ;
108
- }
109
- for (var j = 0 ; j < nCols ; ++j) {
110
- var dt = function (x) { return x ; } ;
111
- if (this.options.dataTypes instanceof Array) {
112
- switch (this.options.dataTypes[j]) {
113
- case 'int':
114
- dt = parseInt ;
115
- break ;
116
- case 'float':
117
- case 'double':
118
- dt = parseFloat ;
119
- break ;
120
- case 'date':
121
- case 'datetime':
122
- dt = function (x) { return new Date(x) ; } ;
123
- break ;
124
- case false:
125
- case true:
126
- case 'string':
127
- case 'str':
128
- dt = function (x) { return x ; } ;
129
- break ;
130
- default:
131
- dt = this.options.dataTypes[j] ;
132
- }
133
- }
134
- for (var i = 0; i < rows.length; ++i) {
135
- this.data[i].push(dt(rows[i].cells[j].innerHTML.trim())) ;
136
- }
137
- }
138
- if (this.options.dataTypes === true) {
139
- for (var c = 0; c < this.data[0].length; ++c) {
140
- var isNumeric = true;
141
- for (var i = 0; i < this.data.length; ++i) {
142
- if (this.data[i][c] !== ""
143
- && !((this.data[i][c] - parseFloat(this.data[i][c]) + 1) >= 0)) {
144
- isNumeric = false;
145
- }
146
- }
147
- if (isNumeric) {
148
- for (var i = 0; i < this.data.length; ++i) {
149
- if (this.data[i][c] !== "") {
150
- this.data[i][c] = parseFloat(this.data[i][c]);
151
- }
152
- }
153
- }
154
- }
155
- }
156
- }
157
-
158
- /* Add sorting class to all th and add callback. */
159
- this.createSort();
160
-
161
- /* Add filter where it's needed. */
162
- this.createFilter();
163
-
164
- this.triggerSort();
165
- this.filter();
166
-
167
- };
168
-
169
- DataTable.prototype = {
170
-
171
- constructor: DataTable,
172
-
173
- /**
174
- *
175
- * Add the specified class(es) to the specified DOM Element.
176
- *
177
- * @param node The DOM Element
178
- * @param classes (Array or String)
179
- *
180
- **/
181
- addClass: function (node, classes) {
182
- if (typeof classes === "string") {
183
- classes = classes.split(' ');
184
- }
185
- classes.forEach(function (c) {
186
- if (!c) { return; }
187
- node.classList.add(c);
188
- });
189
- },
190
-
191
- /**
192
- *
193
- * Remove the specified node from the DOM.
194
- *
195
- * @param node The node to removes
196
- *
197
- **/
198
- removeNode: function (node) {
199
- if (node) {
200
- node.parentNode.removeChild(node);
201
- }
202
- },
203
-
204
- /**
205
- *
206
- * Clear size option and set timeout (if specified) for refresh.
207
- *
208
- * Note: This function should be call when a ajax loading is finished.
209
- *
210
- * @update refreshTimeOut The new timeout
211
- *
212
- **/
213
- setRefreshTimeout: function () {
214
- if (this.options.data.refresh) {
215
- clearTimeout(this.refreshTimeOut);
216
- this.refreshTimeOut = setTimeout((function (datatable) {
217
- return function () { datatable.getAjaxDataAsync(0, true); };
218
- })(this), this.options.data.refresh);
219
- }
220
- },
221
-
222
- /**
223
- *
224
- * Hide the loading divs.
225
- *
226
- **/
227
- hideLoadingDivs: function () {
228
- this.removeNode(this.loadingDiv);
229
- },
230
-
231
-
232
- /**
233
- *
234
- * Update the loading divs with the current % of data load (according
235
- * to this.options.data.size).
236
- *
237
- * Note: Call setRefreshTimeout & hideLoadingDivs if all the data have been loaded.
238
- *
239
- **/
240
- updateLoadingDivs: function () {
241
- if (this.data.length >= this.options.data.size) {
242
- this.setRefreshTimeout();
243
- this.hideLoadingDivs();
244
- }
245
- else {
246
- this.loadingDiv.querySelector('div.progress .progress-bar').style.width =
247
- parseInt(100 * this.data.length / this.options.data.size, 10) + '%';
248
- }
249
- },
250
-
251
- /**
252
- *
253
- * Get data according to this.options.data, asynchronously, using recursivity.
254
- *
255
- * @param start The first offset to send to the server
256
- *
257
- * @update data Concat data received from server to old data
258
- *
259
- * Note: Each call increment start by pageSize * pagingNumberOfPages.
260
- *
261
- **/
262
- getAjaxDataAsync: function (start, recursive) {
263
- if (typeof recursive === "undefined") { recursive = false; }
264
- if (recursive && typeof this.syncData === "undefined") {
265
- this.syncData = {
266
- data: [],
267
- toAdd: [],
268
- toUpdate: {},
269
- toDelete: []
270
- };
271
- }
272
- var xhr = new XMLHttpRequest();
273
- xhr.timeout = this.options.data.timeout;
274
- xhr.onreadystatechange = function (datatable, start, recursive) {
275
- return function () {
276
- if (this.readyState == 4) {
277
- switch (this.status) {
278
- case 200:
279
- if (recursive) {
280
- if (this.response.length > 0) {
281
- datatable.syncData.data =
282
- datatable.syncData.data.concat(this.response);
283
- datatable.getAjaxDataAsync(start + datatable.options.pageSize * datatable.options.pagingNumberOfPages, true);
284
- }
285
- else {
286
- var syncData = datatable.syncData;
287
- delete datatable.syncData;
288
- datatable.data = syncData.data;
289
- datatable.addRows(syncData.toAdd);
290
- syncData.toDelete.forEach(function (e) {
291
- if (e instanceof Function) {
292
- datatable.deleteAll(e);
293
- }
294
- else {
295
- datatable.deleteRow(e);
296
- }
297
- });
298
- for (var id in syncData.toUpdate) {
299
- datatable.updateRow(id, syncData.toUpdate[id]);
300
- }
301
-
302
- datatable.sort(true);
303
- datatable.setRefreshTimeout();
304
- }
305
- }
306
- else {
307
- datatable.data = datatable.data.concat(this.response);
308
- datatable.updateLoadingDivs();
309
- datatable.sort(true);
310
- }
311
- break;
312
- case 404:
313
- case 500:
314
- console.log("ERROR: " + this.status + " - " + this.statusText);
315
- console.log(xhr);
316
- break;
317
- default:
318
- datatable.getAjaxDataAsync(start, recursive);
319
- break;
320
- }
321
- }
322
- }
323
- } (this, start, recursive);
324
- var url = this.options.data.url ;
325
- var limit = this.options.pageSize * this.options.pagingNumberOfPages ;
326
- var formdata = new FormData();
327
- if (start !== true) {
328
- if (this.options.data.type.toUpperCase() == 'GET') {
329
- url += '?start=' + start + '&limit=' + limit ;
330
- }
331
- else {
332
- formdata.append('offset', start);
333
- formdata.append('limit', limit);
334
- }
335
- }
336
- xhr.open(this.options.data.type, url, true);
337
- xhr.responseType = 'json';
338
- xhr.send(formdata);
339
- },
340
-
341
- /**
342
- *
343
- * @return The last page number according to options.pageSize and
344
- * current number of filtered elements.
345
- *
346
- **/
347
- getLastPageNumber: function () {
348
- return parseInt(Math.ceil(this.filterIndex.length / this.options.pageSize), 10);
349
- },
350
-
351
- createPagingLink: function (content, page, disabled) {
352
- var link = document.createElement('a');
353
-
354
- // Check options...
355
- if (this.options.pagingLinkClass) {
356
- link.classList.add(this.options.pagingLinkClass);
357
- }
358
-
359
- if (this.options.pagingLinkHref) {
360
- link.href = this.options.pagingLinkHref;
361
- }
362
-
363
- if (this.options.pagingLinkDisabledTabIndex !== false && disabled) {
364
- link.tabIndex = this.options.pagingLinkDisabledTabIndex;
365
- }
366
-
367
- link.dataset.page = page;
368
- link.innerHTML = content;
369
- return link;
370
- },
371
-
372
- /**
373
- *
374
- * Update the paging divs.
375
- *
376
- **/
377
- updatePaging: function () {
378
-
379
- /* Be carefull if you change something here, all this part calculate the first
380
- and last page to display. I choose to center the current page, it's more beautiful... */
381
-
382
- var nbPages = this.options.pagingNumberOfPages;
383
- var dataTable = this;
384
- var cp = parseInt(this.currentStart / this.options.pageSize, 10) + 1;
385
- var lp = this.getLastPageNumber();
386
- var start;
387
- var end;
388
- var first = this.filterIndex.length ? this.currentStart + 1 : 0;
389
- var last = (this.currentStart + this.options.pageSize) > this.filterIndex.length ?
390
- this.filterIndex.length : this.currentStart + this.options.pageSize;
391
-
392
- if (cp < nbPages / 2) {
393
- start = 1;
394
- }
395
- else if (cp >= lp - nbPages / 2) {
396
- start = lp - nbPages + 1;
397
- if (start < 1) {
398
- start = 1;
399
- }
400
- }
401
- else {
402
- start = parseInt(cp - nbPages / 2 + 1, 10);
403
- }
404
-
405
- if (start + nbPages < lp + 1) {
406
- end = start + nbPages - 1;
407
- }
408
- else {
409
- end = lp;
410
- }
411
-
412
- /* Juste iterate over each paging list and append li to ul. */
413
-
414
- for (var i = 0; i < this.pagingLists.length; ++i) {
415
- var childs = [];
416
- if (dataTable.options.firstPage) {
417
- var li = document.createElement('li');
418
- li.appendChild(dataTable.createPagingLink(
419
- dataTable.options.firstPage, "first", cp === 1
420
- ));
421
- if (cp === 1) { li.classList.add('active'); }
422
- childs.push(li);
423
- }
424
- if (dataTable.options.prevPage) {
425
- var li = document.createElement('li');
426
- li.appendChild(dataTable.createPagingLink(
427
- dataTable.options.prevPage, "prev", cp === 1
428
- ));
429
- if (cp === 1) { li.classList.add('active'); }
430
- childs.push(li);
431
- }
432
- if (dataTable.options.pagingPages) {
433
- var _childs = this.options.pagingPages.call(this.table, start,
434
- end, cp, first, last);
435
- if (_childs instanceof Array) {
436
- childs = childs.concat(_childs);
437
- }
438
- else {
439
- childs.push(_childs);
440
- }
441
- }
442
- else {
443
- for (var k = start; k <= end; k++) {
444
- var li = document.createElement('li');
445
- li.appendChild(dataTable.createPagingLink(
446
- k, k, cp === k
447
- ));
448
- if (k === cp) { li.classList.add('active'); }
449
- childs.push(li);
450
- }
451
- }
452
- if (dataTable.options.nextPage) {
453
- var li = document.createElement('li');
454
- li.appendChild(dataTable.createPagingLink(
455
- dataTable.options.nextPage, "next", cp === lp || lp === 0
456
- ));
457
- if (cp === lp || lp === 0) { li.classList.add('active'); }
458
- childs.push(li);
459
- }
460
- if (dataTable.options.lastPage) {
461
- var li = document.createElement('li');
462
- li.appendChild(dataTable.createPagingLink(
463
- dataTable.options.lastPage, "last", cp === lp || lp === 0
464
- ));
465
- if (cp === lp || lp === 0) { li.classList.add('active'); }
466
- childs.push(li);
467
- }
468
- this.pagingLists[i].innerHTML = '';
469
- childs.forEach(function (e) {
470
- if (dataTable.options.pagingItemClass) {
471
- e.classList.add(dataTable.options.pagingItemClass);
472
- }
473
- if (e.childNodes.length > 0) {
474
- e.childNodes[0].addEventListener('click', function (event) {
475
- event.preventDefault();
476
- if (this.parentNode.classList.contains('active') ||
477
- typeof this.dataset.page === 'undefined') {
478
- return;
479
- }
480
- switch (this.dataset.page) {
481
- case 'first':
482
- dataTable.loadPage(1);
483
- break;
484
- case 'prev':
485
- dataTable.loadPage(cp - 1);
486
- break;
487
- case 'next':
488
- dataTable.loadPage(cp + 1);
489
- break;
490
- case 'last':
491
- dataTable.loadPage(lp);
492
- break;
493
- default:
494
- dataTable.loadPage(parseInt(parseInt(this.dataset.page), 10));
495
- }
496
- }, false);
497
- }
498
- this.pagingLists[i].appendChild(e);
499
- }, this);
500
- }
501
-
502
- },
503
-
504
- /**
505
- *
506
- * Update the counter divs.
507
- *
508
- **/
509
- updateCounter: function () {
510
- var cp = this.filterIndex.length ?
511
- parseInt(this.currentStart / this.options.pageSize, 10) + 1 : 0;
512
- var lp = parseInt(Math.ceil(this.filterIndex.length / this.options.pageSize), 10);
513
- var first = this.filterIndex.length ? this.currentStart + 1 : 0;
514
- var last = (this.currentStart + this.options.pageSize) > this.filterIndex.length ?
515
- this.filterIndex.length : this.currentStart + this.options.pageSize;
516
- for (var i = 0; i < this.counterDivs.length; ++i) {
517
- this.counterDivs[i].innerHTML = this.options.counterText.call(this.table, cp, lp,
518
- first, last,
519
- this.filterIndex.length,
520
- this.data.length);
521
- }
522
- },
523
-
524
- /**
525
- *
526
- * @return The sort function according to options.sort, options.sortKey & options.sortDir.
527
- *
528
- * Note: This function could return false if no sort function can be generated.
529
- *
530
- **/
531
- getSortFunction: function () {
532
- if (this.options.sort === false) {
533
- return false;
534
- }
535
- if (this.options.sort instanceof Function) {
536
- return this.options.sort;
537
- }
538
- if (this.data.length === 0 || !(this.options.sortKey in this.data[0])) {
539
- return false;
540
- }
541
- var key = this.options.sortKey;
542
- var asc = this.options.sortDir === 'asc';
543
- if (this.options.sort[key] instanceof Function) {
544
- return function (s) {
545
- return function (a, b) {
546
- var vala = a[key], valb = b[key];
547
- return asc ? s(vala, valb) : -s(vala, valb);
548
- };
549
- } (this.options.sort[key]);
550
- }
551
- return function (a, b) {
552
- var vala = a[key], valb = b[key];
553
- if (vala > valb) { return asc ? 1 : -1; }
554
- if (vala < valb) { return asc ? -1 : 1; }
555
- return 0;
556
- };
557
- },
558
-
559
- /**
560
- *
561
- * Destroy the filters (remove the filter line).
562
- *
563
- **/
564
- destroyFilter: function () {
565
- this.removeNode(this.table.querySelector('.datatable-filter-line'));
566
- },
567
-
568
- /**
569
- *
570
- * Change the text input filter placeholder according to this.options.filterText.
571
- *
572
- **/
573
- changePlaceHolder: function () {
574
- var placeholder = this.options.filterText ? this.options.filterText : '';
575
- var inputTexts = this.table.querySelectorAll('.datatable-filter-line input[type="text"]');
576
- for (var i = 0; i < inputTexts.length; ++i) {
577
- inputTexts[i].placeholder = placeholder;
578
- }
579
- },
580
-
581
- /**
582
- *
583
- * Create a text filter for the specified field.
584
- *
585
- * @param field The field corresponding to the filter
586
- *
587
- * @update filters Add the new filter to the list of filter (calling addFilter)
588
- *
589
- * @return The input filter
590
- *
591
- **/
592
- createTextFilter: function (field) {
593
- var opt = this.options.filters[field];
594
- var input = opt instanceof HTMLInputElement ? opt : document.createElement('input');
595
- input.type = 'text';
596
- if (this.options.filterText) {
597
- input.placeholder = this.options.filterText;
598
- }
599
- this.addClass(input, 'datatable-filter datatable-input-text');
600
- input.dataset.filter = field;
601
- this.filterVals[field] = '';
602
- var typewatch = (function () {
603
- var timer = 0;
604
- return function (callback, ms) {
605
- clearTimeout(timer);
606
- timer = setTimeout(callback, ms);
607
- };
608
- })();
609
- input.onkeyup = function (datatable) {
610
- return function () {
611
- var val = this.value.toUpperCase();
612
- var field = this.dataset.filter;
613
- typewatch(function () {
614
- datatable.filterVals[field] = val;
615
- datatable.filter();
616
- }, 300);
617
- };
618
- } (this);
619
- input.onkeydown = input.onkeyup;
620
- var regexp = opt === 'regexp' || input.dataset.regexp;
621
- if (opt instanceof Function) {
622
- this.addFilter(field, opt);
623
- }
624
- else if (regexp) {
625
- this.addFilter(field, function (data, val) {
626
- return new RegExp(val).test(String(data));
627
- });
628
- }
629
- else {
630
- this.addFilter(field, function (data, val) {
631
- return String(data).toUpperCase().indexOf(val) !== -1;
632
- });
633
- }
634
- this.addClass(input, this.options.filterInputClass);
635
- return input;
636
- },
637
-
638
- /**
639
- * Check if the specified value is in the specified array, without strict type checking.
640
- *
641
- * @param val The val to search
642
- * @param arr The array
643
- *
644
- * @return true if the value was found in the array
645
- **/
646
- _isIn: function (val, arr) {
647
- var found = false;
648
- for (var i = 0; i < arr.length && !found; ++i) {
649
- found = arr[i] == val;
650
- }
651
- return found;
652
- },
653
-
654
- /**
655
- * Return the index of the specified element in the object.
656
- *
657
- * @param v
658
- * @param a
659
- *
660
- * @return The index, or -1
661
- **/
662
- _index: function (v, a) {
663
- if (a === undefined || a === null) {
664
- return -1;
665
- }
666
- var index = -1;
667
- for (var i = 0; i < a.length && index == -1; ++i) {
668
- if (a[i] === v) index = i;
669
- }
670
- return index;
671
- },
672
-
673
- /**
674
- * Return the keys of the specified object.
675
- *
676
- * @param obj
677
- *
678
- * @return The keys of the specified object.
679
- **/
680
- _keys: function (obj) {
681
- if (obj === undefined || obj === null) {
682
- return undefined;
683
- }
684
- var keys = [];
685
- for (var k in obj) {
686
- if (obj.hasOwnProperty(k)) keys.push(k);
687
- }
688
- return keys;
689
- },
690
-
691
- /**
692
- *
693
- * Create a select filter for the specified field.
694
- *
695
- * @param field The field corresponding to the filter
696
- *
697
- * @update filters Add the new filter to the list of filter (calling addFilter)
698
- *
699
- * @return The select filter.
700
- *
701
- **/
702
- createSelectFilter: function (field) {
703
- var opt = this.options.filters[field];
704
- var values = {}, selected = [], multiple = false,
705
- empty = true, emptyValue = this.options.filterEmptySelect;
706
- var tag = false;
707
- if (opt instanceof HTMLSelectElement) {
708
- tag = opt;
709
- }
710
- else if (opt instanceof Object && 'element' in opt && opt.element) {
711
- tag = opt.element;
712
- }
713
- if (opt instanceof HTMLSelectElement || opt === 'select') {
714
- values = this.getFilterOptions(field);
715
- }
716
- else {
717
- multiple = ('multiple' in opt) && (opt.multiple === true);
718
- empty = ('empty' in opt) && opt.empty;
719
- emptyValue = (('empty' in opt) && (typeof opt.empty === 'string')) ?
720
- opt.empty : this.options.filterEmptySelect;
721
- if ('values' in opt) {
722
- if (opt.values === 'auto') {
723
- values = this.getFilterOptions(field);
724
- }
725
- else {
726
- values = opt.values;
727
- }
728
- if ('default' in opt) {
729
- selected = opt['default'];
730
- }
731
- else if (multiple) {
732
- selected = [];
733
- for (var k in values) {
734
- if (values[k] instanceof Object) {
735
- selected = selected.concat(this._keys(values[k]));
736
- }
737
- else {
738
- selected.push(k);
739
- }
740
- }
741
- }
742
- else {
743
- selected = [];
744
- }
745
- if (!(selected instanceof Array)) {
746
- selected = [selected];
747
- }
748
- }
749
- else {
750
- values = opt;
751
- selected = multiple ? this._keys(values) : [];
752
- }
753
- }
754
- var select = tag ? tag : document.createElement('select');
755
- if (multiple) {
756
- select.multiple = true;
757
- }
758
- if (opt['default']) {
759
- select.dataset['default'] = opt['default'];
760
- }
761
- this.addClass(select, 'datatable-filter datatable-select');
762
- select.dataset.filter = field;
763
- if (empty) {
764
- var option = document.createElement('option');
765
- option.dataset.empty = true;
766
- option.value = "";
767
- option.innerHTML = emptyValue;
768
- select.appendChild(option);
769
- }
770
- var allKeys = [];
771
- for (var key in values) {
772
- if (values[key] instanceof Object) {
773
- var optgroup = document.createElement('optgroup');
774
- optgroup.label = key;
775
- for (var skey in values[key]) {
776
- if (values[key].hasOwnProperty(skey)) {
777
- allKeys.push(skey);
778
- var option = document.createElement('option');
779
- option.value = skey;
780
- option.selected = this._isIn(skey, selected);
781
- option.innerHTML = values[key][skey];
782
- optgroup.appendChild(option);
783
- }
784
- }
785
- select.appendChild(optgroup);
786
- }
787
- else {
788
- allKeys.push(key);
789
- var option = document.createElement('option');
790
- option.value = key;
791
- option.selected = this._isIn(key, selected);
792
- option.innerHTML = values[key];
793
- select.appendChild(option);
794
- }
795
- }
796
- var val = select.value;
797
- if (multiple) {
798
- val = [];
799
- for (var i = 0; i < select.options.length; ++i) {
800
- if (select.options[i].selected) { val.push(select.options[i].value); }
801
- }
802
- }
803
- this.filterVals[field] = multiple ? val : ((empty && !val) ? allKeys : [val]);
804
- select.onchange = function (allKeys, multiple, empty, datatable) {
805
- return function () {
806
- var val = this.value;
807
- if (multiple) {
808
- val = [];
809
- for (var i = 0; i < this.options.length; ++i) {
810
- if (this.options[i].selected) { val.push(this.options[i].value); }
811
- }
812
- }
813
- var field = this.dataset.filter;
814
- datatable.filterVals[field] = multiple ? val : ((empty && !val) ? allKeys : [val]);
815
- datatable.filter();
816
- };
817
- } (allKeys, multiple, empty, this);
818
- if (opt instanceof Object && opt.fn instanceof Function) {
819
- this.addFilter(field, opt.fn);
820
- select.dataset.filterType = 'function';
821
- }
822
- else {
823
- this.addFilter(field, function (aKeys, datatable) {
824
- return function (data, val) {
825
- if (!val) { return false; }
826
- if (val == aKeys && !data) { return true; }
827
- return datatable._isIn(data, val);
828
- };
829
- } (allKeys, this));
830
- select.dataset.filterType = 'default';
831
- }
832
- this.addClass(select, this.options.filterSelectClass);
833
- return select;
834
- },
835
-
836
- /**
837
- *
838
- * Create the filter line according to options.filters.
839
- *
840
- **/
841
- createFilter: function () {
842
- this.filters = [];
843
- this.filterTags = [];
844
- this.filterVals = [];
845
- // Speical options if '*'
846
- if (this.options.filters === '*') {
847
- var nThs = this.table.tHead.rows[0].cells.length;
848
- this.options.filters = [];
849
- while (nThs--) {
850
- this.options.filters.push(true);
851
- }
852
- }
853
- if (this.options.filters) {
854
- var filterLine = false;
855
- var tr = document.createElement('tr');
856
- tr.classList.add('datatable-filter-line');
857
- for (var field in this.options.filters) {
858
- if (this.options.filters.hasOwnProperty(field)) {
859
- var td = document.createElement('td');
860
- if (this.options.filters[field] !== false) {
861
- var opt = this.options.filters[field];
862
- var input = opt === true || opt === 'regexp' || opt === 'input' || opt instanceof Function || opt instanceof HTMLInputElement;
863
- var filter = input ? this.createTextFilter(field) : this.createSelectFilter(field);
864
- this.filterTags[field] = filter;
865
- if (!document.body.contains(filter)) {
866
- td.classList.add('datatable-filter-cell');
867
- td.appendChild(filter);
868
- }
869
- }
870
- if (!(this.options.filters[field] instanceof Object) || !this.options.filters[field].noColumn) {
871
- tr.appendChild(td);
872
- }
873
- }
874
- }
875
- if (tr.querySelectorAll('td.datatable-filter-cell').length > 0) {
876
- this.table.tHead.appendChild(tr);
877
- }
878
- }
879
- },
880
-
881
- /**
882
- *
883
- * Filter data and refresh.
884
- *
885
- * @param keepCurrentPage true if the current page should not be changed (on refresh
886
- * for example), if not specified or false, the current page will be set to 0.
887
- *
888
- * @update filterIndex Will contain the new filtered indexes
889
- * @update currentStart The new starting point
890
- *
891
- **/
892
- filter: function (keepCurrentPage) {
893
- if (typeof keepCurrentPage === 'undefined') {
894
- keepCurrentPage = false;
895
- }
896
- var oldCurrentStart = this.currentStart;
897
- this.currentStart = 0;
898
- this.filterIndex = [];
899
- for (var i = 0; i < this.data.length; i++) {
900
- if (this.checkFilter(this.data[i])) { this.filterIndex.push(i); }
901
- }
902
- if (keepCurrentPage) {
903
- this.currentStart = oldCurrentStart;
904
- while (this.currentStart >= this.filterIndex.length) {
905
- this.currentStart -= this.options.pageSize;
906
- }
907
- if (this.currentStart < 0) {
908
- this.currentStart = 0;
909
- }
910
- }
911
- if (this.options.filterSelectOptions && this.filterIndex.length > 0) {
912
- var dtable = this;
913
- var allKeys = [];
914
- for (var j = 0; j < this.data[0].length; ++j) {
915
- allKeys.push({});
916
- }
917
- for (var i = 0; i < this.filterIndex.length; ++i) {
918
- var row = this.data[this.filterIndex[i]];
919
- for (var j = 0; j < row.length; ++j) {
920
- allKeys[j][row[j]] = true;
921
- }
922
- }
923
- for (var k = 0; k < allKeys.length; ++k) {
924
- var keys = this._keys(allKeys[k]);
925
- if (this.filterTags[k]
926
- && this.filterTags[k] instanceof HTMLSelectElement
927
- && this.filterTags[k].dataset.filterType == 'default') {
928
- var options = this.filterTags[k].childNodes;
929
- for (var i = 0; i < options.length; ++i) {
930
- if (!options[i].dataset.empty) {
931
- options[i].style.display = dtable._isIn(options[i].value, keys)
932
- ? 'block' : 'none';
933
- }
934
- }
935
- }
936
- }
937
- }
938
- this.refresh();
939
- },
940
-
941
-
942
- /**
943
- *
944
- * Reset all filters.
945
- *
946
- **/
947
- resetFilters: function () {
948
- var dtable = this;
949
- this.filterTags.forEach(function (e) {
950
- var field = e.dataset.filter;
951
- if (e instanceof HTMLInputElement) {
952
- e.value = '';
953
- dtable.filterVals[field] = '';
954
- }
955
- else {
956
- if (e.multiple) {
957
- var allKeys = [];
958
- for (var i = 0; i < e.childNodes.length; ++i) {
959
- e.childNodes[i].selected = true;
960
- allKeys.push(e.childNodes[i].value);
961
- }
962
- dtable.filterVals[field] = allKeys;
963
- }
964
- else if (e.dataset['default']
965
- && e.querySelector('option[value="' + e.dataset['default'] + '"]').length > 0) {
966
- for (var i = 0; i < e.childNodes.length; ++i) {
967
- e.childNodes[i].selected = e.childNodes[i].value == e.dataset['default'];
968
- }
969
- dtable.filterVals[field] = [e.dataset['default']];
970
- }
971
- else if (e.childNodes.length > 0) {
972
- e.childNodes[0].selected = true;
973
- for (var i = 1; i < e.childNodes.length; ++i) {
974
- e.childNodes[i].selected = false;
975
- }
976
- if (e.childNodes[0].dataset.empty) {
977
- var allKeys = [];
978
- for (var i = 1; i < e.childNodes.length; ++i) {
979
- allKeys.push(e.childNodes[i].value);
980
- }
981
- dtable.filterVals[field] = allKeys;
982
- }
983
- else {
984
- dtable.filterVals[field] = [e.childNodes[0].value];
985
- }
986
- }
987
- }
988
- });
989
- this.filter();
990
- },
991
-
992
- /**
993
- * Strip HTML tags for the specified string.
994
- *
995
- * @param str The string from which tags must be stripped.
996
- *
997
- * @return The string with HTML tags removed.
998
- *
999
- **/
1000
- stripTags: function (str) {
1001
- var e = document.createElement('div');
1002
- e.innerHTML = str;
1003
- return e.textContent || e.innerText;
1004
- },
1005
-
1006
- /**
1007
- *
1008
- * Check if the specified data match the filters according to this.filters
1009
- * and this.filterVals.
1010
- *
1011
- * @param data The data to check
1012
- *
1013
- * @return true if the data match the filters, false otherwise
1014
- *
1015
- **/
1016
- checkFilter: function (data) {
1017
- var ok = true;
1018
- for (var fk in this.filters) {
1019
- var currentData = fk[0] === '_' ? data : data[fk];
1020
- if (typeof currentData === "string") {
1021
- currentData = this.stripTags(currentData);
1022
- }
1023
- if (!this.filters[fk](currentData, this.filterVals[fk])) {
1024
- ok = false;
1025
- break;
1026
- }
1027
- }
1028
- return ok;
1029
- },
1030
-
1031
- /**
1032
- *
1033
- * Add a new filter.
1034
- *
1035
- * @update filters
1036
- *
1037
- **/
1038
- addFilter: function (field, filter) {
1039
- this.filters[field] = filter;
1040
- },
1041
-
1042
- /**
1043
- *
1044
- * Get the filter select options for a specified field according
1045
- * to this.data.
1046
- *
1047
- * @return The options found.
1048
- *
1049
- **/
1050
- getFilterOptions: function (field) {
1051
- var options = {}, values = [];
1052
- for (var key in this.data) {
1053
- if (this.data[key][field] !== '') {
1054
- values.push(this.data[key][field]);
1055
- }
1056
- }
1057
- values.sort();
1058
- for (var i in values) {
1059
- if (values.hasOwnProperty(i)) {
1060
- var txt = this.stripTags(values[i]);
1061
- options[txt] = txt;
1062
- }
1063
- }
1064
- return options;
1065
- },
1066
-
1067
- /**
1068
- *
1069
- * Remove class, data and event on sort headers.
1070
- *
1071
- **/
1072
- destroySort: function () {
1073
- $('thead th').removeClass('sorting sorting-asc sorting-desc')
1074
- .unbind('click.datatable')
1075
- .removeData('sort');
1076
- },
1077
-
1078
- /**
1079
- *
1080
- * Add class, event & data to headers according to this.options.sort or data-sort attribute
1081
- * of headers.
1082
- *
1083
- * @update options.sort Will be set to true if not already and a data-sort attribute is found.
1084
- *
1085
- **/
1086
- createSort: function () {
1087
- var dataTable = this;
1088
- if (!(this.options.sort instanceof Function)) {
1089
-
1090
- var countTH = 0;
1091
- var ths = this.table.tHead.rows[0].cells;
1092
- for (var i = 0; i < ths.length; ++i) {
1093
-
1094
- if (ths[i].dataset.sort) {
1095
- dataTable.options.sort = true;
1096
- }
1097
- else if (dataTable.options.sort === '*') {
1098
- ths[i].dataset.sort = countTH;
1099
- }
1100
- else {
1101
- var key;
1102
- if (dataTable.options.sort instanceof Array) {
1103
- key = countTH;
1104
- }
1105
- else if (dataTable.options.sort instanceof Object) {
1106
- key = dataTable._keys(dataTable.options.sort)[countTH];
1107
- }
1108
- if (key !== undefined && dataTable.options.sort[key]) {
1109
- ths[i].dataset.sort = key;
1110
- }
1111
- }
1112
-
1113
- if (ths[i].dataset.sort !== undefined) {
1114
- ths[i].classList.add('sorting');
1115
- }
1116
-
1117
- countTH++;
1118
-
1119
- ths[i].addEventListener('click', function () {
1120
- if (this.dataset.sort) {
1121
- if (this.classList.contains('sorting-asc')) {
1122
- dataTable.options.sortDir = 'desc';
1123
- this.classList.remove('sorting-asc')
1124
- this.classList.add('sorting-desc');
1125
- }
1126
- else if (this.classList.contains('sorting-desc')) {
1127
- dataTable.options.sortDir = 'asc';
1128
- this.classList.remove('sorting-desc');
1129
- this.classList.add('sorting-asc');
1130
- }
1131
- else {
1132
- var oths = this.parentNode.cells;
1133
- for (var j = 0; j < oths.length; j++) {
1134
- oths[j].classList.remove('sorting-desc');
1135
- oths[j].classList.remove('sorting-asc');
1136
- }
1137
- dataTable.options.sortDir = 'asc';
1138
- dataTable.options.sortKey = this.dataset.sort;
1139
- this.classList.add('sorting-asc');
1140
- }
1141
- dataTable.sort();
1142
- dataTable.refresh();
1143
- }
1144
- }, false);
1145
-
1146
- }
1147
-
1148
- }
1149
- },
1150
-
1151
- /**
1152
- *
1153
- * Trigger sort event on the table: If options.sort is a function,
1154
- * sort the table, otherwize trigger click on the column specifid by options.sortKey.
1155
- *
1156
- **/
1157
- triggerSort: function () {
1158
- if (this.options.sort instanceof Function) {
1159
- this.sort();
1160
- this.refresh();
1161
- }
1162
- else if (this.options.sortKey !== false) {
1163
- var ths = this.table.tHead.rows[0].cells;
1164
- var th;
1165
- for (var j = 0; j < ths.length; j++) {
1166
- ths[j].classList.remove('sorting-desc');
1167
- ths[j].classList.remove('sorting-asc');
1168
- if (ths[j].dataset.sort === this.options.sortKey) {
1169
- th = ths[j];
1170
- }
1171
- }
1172
- if (th !== undefined) {
1173
- th.classList.add('sorting-' + this.options.sortDir);
1174
- this.sort();
1175
- this.refresh();
1176
- }
1177
- }
1178
- },
1179
-
1180
- /**
1181
- *
1182
- * Sort the data.
1183
- *
1184
- * @update data
1185
- *
1186
- **/
1187
- sort: function (keepCurrentPage) {
1188
- var fnSort = this.getSortFunction();
1189
- if (fnSort !== false) {
1190
- this.data.sort(fnSort);
1191
- }
1192
- this.filter(keepCurrentPage);
1193
- },
1194
-
1195
- /**
1196
- *
1197
- * Try to identify the specified data with the specify identifier according
1198
- * to this.options.identify.
1199
- *
1200
- * @return true if the data match, false otherwize
1201
- *
1202
- **/
1203
- identify: function (id, data) {
1204
- if (this.options.identify === false) {
1205
- return false;
1206
- }
1207
- if (this.options.identify instanceof Function) {
1208
- return this.options.identify(id, data);
1209
- }
1210
- return data[this.options.identify] == id;
1211
- },
1212
-
1213
- /**
1214
- *
1215
- * Find the index of the first element matching id in the data array.
1216
- *
1217
- * @param The id to find (will be match according to this.options.identify)
1218
- *
1219
- * @return The index of the first element found, or -1 if no element is found
1220
- *
1221
- **/
1222
- indexOf: function (id) {
1223
- var index = -1;
1224
- for (var i = 0; i < this.data.length && index === -1; i++) {
1225
- if (this.identify(id, this.data[i])) {
1226
- index = i;
1227
- }
1228
- }
1229
- return index;
1230
- },
1231
-
1232
- /**
1233
- *
1234
- * Get an elements from the data array.
1235
- *
1236
- * @param id An identifier for the element (see this.options.identify)
1237
- *
1238
- **/
1239
- row: function (id) {
1240
- if (this.options.identify === true) {
1241
- return this.data[id];
1242
- }
1243
- return this.data[this.indexOf(id)];
1244
- },
1245
-
1246
- /**
1247
- *
1248
- * Retrieve all data.
1249
- *
1250
- *
1251
- **/
1252
- all: function (filter) {
1253
- if (typeof filter === "undefined"
1254
- || filter === true) {
1255
- return this.data;
1256
- }
1257
- var returnData = [];
1258
- for (var i = 0; i < this.data.length; ++i) {
1259
- if (filter(this.data[i])) {
1260
- returnData.push(this.data[i]);
1261
- }
1262
- }
1263
- return returnData;
1264
- },
1265
-
1266
- /**
1267
- *
1268
- * Add an element to the data array.
1269
- *
1270
- * @param data The element to add
1271
- *
1272
- * @update data
1273
- *
1274
- **/
1275
- addRow: function (data) {
1276
- this.data.push(data);
1277
- if (typeof this.syncData !== "undefined") {
1278
- this.syncData.toAdd.push(data);
1279
- }
1280
- this.sort();
1281
- this.filter();
1282
- this.currentStart = parseInt(this._index(this._index(data, this.data),
1283
- this.filterIndex)
1284
- / this.options.pageSize, 10) * this.options.pageSize;
1285
- this.refresh();
1286
- },
1287
-
1288
- /**
1289
- *
1290
- * Add elements to the data array.
1291
- *
1292
- * @param data Array of elements to add
1293
- *
1294
- * @update data
1295
- *
1296
- **/
1297
- addRows: function (data) {
1298
- this.data = this.data.concat(data);
1299
- if (typeof this.syncData !== "undefined") {
1300
- this.syncData.toAdd = this.syncData.toAdd.concat(data);
1301
- }
1302
- this.sort();
1303
- this.filter();
1304
- this.currentStart = parseInt(this._index(this._index(data, this.data),
1305
- this.filterIndex)
1306
- / this.options.pageSize, 10) * this.options.pageSize;
1307
- this.refresh();
1308
- },
1309
-
1310
- /**
1311
- *
1312
- * Remove an element from the data array.
1313
- *
1314
- * @param id An identifier for the element (see this.options.identify)
1315
- *
1316
- **/
1317
- deleteRow: function (id) {
1318
- var oldCurrentStart = this.currentStart;
1319
- var index = this.indexOf(id);
1320
- if (index === -1) {
1321
- console.log('No data found with id: ' + id);
1322
- return;
1323
- }
1324
- this.data.splice(index, 1);
1325
- if (typeof this.syncData !== "undefined") {
1326
- this.syncData.toDelete.push(id);
1327
- }
1328
- this.filter();
1329
- if (oldCurrentStart < this.filterIndex.length) {
1330
- this.currentStart = oldCurrentStart;
1331
- }
1332
- else {
1333
- this.currentStart = oldCurrentStart - this.options.pageSize;
1334
- if (this.currentStart < 0) { this.currentStart = 0; }
1335
- }
1336
- this.refresh();
1337
- },
1338
-
1339
- /**
1340
- *
1341
- * Delete all elements matching the filter arg.
1342
- *
1343
- **/
1344
- deleteAll: function (filter) {
1345
- var oldCurrentStart = this.currentStart
1346
- var newData = [];
1347
- if (typeof this.syncData !== "undefined") {
1348
- this.syncData.toDelete.push(filter);
1349
- }
1350
- for (var i = 0; i < this.data.length; ++i) {
1351
- if (!filter(this.data[i])) {
1352
- newData.push(this.data[i]);
1353
- }
1354
- }
1355
- this.data = newData;
1356
- this.filter();
1357
- if (oldCurrentStart < this.filterIndex.length) {
1358
- this.currentStart = oldCurrentStart;
1359
- }
1360
- else {
1361
- this.currentStart = oldCurrentStart - this.options.pageSize;
1362
- if (this.currentStart < 0) { this.currentStart = 0; }
1363
- }
1364
- this.refresh();
1365
- },
1366
-
1367
- /**
1368
- *
1369
- * Update an element in the data array. Will add the element if it is not found.
1370
- *
1371
- * @param id An identifier for the element (see this.options.identify)
1372
- * @param data The new data (identifier value will be set to id)
1373
- *
1374
- **/
1375
- updateRow: function (id, data) {
1376
- var index = this.indexOf(id);
1377
- if (typeof this.syncData !== "undefined") {
1378
- this.syncData.toUpdate[id] = data;
1379
- }
1380
- if (index !== -1) {
1381
- if (id in data) {
1382
- delete data[id];
1383
- }
1384
- for (var key in this.data[index]) {
1385
- if (key in data) {
1386
- this.data[index][key] = data[key];
1387
- }
1388
- }
1389
- this.sort();
1390
- this.filter();
1391
- this.currentStart = parseInt(this._index(this.indexOf(id),
1392
- this.filterIndex)
1393
- / this.options.pageSize, 10) * this.options.pageSize;
1394
- this.refresh();
1395
- }
1396
- },
1397
-
1398
- /**
1399
- *
1400
- * Change the current page and refresh.
1401
- *
1402
- * @param page The number of the page to load
1403
- *
1404
- * @update currentStart
1405
- *
1406
- **/
1407
- loadPage: function (page) {
1408
- var oldPage = this.currentStart / this.options.pageSize;
1409
- if (page < 1) {
1410
- page = 1;
1411
- }
1412
- else if (page > this.getLastPageNumber()) {
1413
- page = this.getLastPageNumber();
1414
- }
1415
- this.currentStart = (page - 1) * this.options.pageSize;
1416
- this.refresh();
1417
- this.options.onChange.call(this.table, oldPage + 1, page);
1418
- },
1419
-
1420
- /**
1421
- *
1422
- * @return The current page
1423
- *
1424
- **/
1425
- getCurrentPage: function () {
1426
- return this.currentStart / this.options.pageSize + 1;
1427
- },
1428
-
1429
- /**
1430
- *
1431
- * Refresh the page according to current page (DO NOT SORT).
1432
- * This function call options.lineFormat.
1433
- *
1434
- **/
1435
- refresh: function () {
1436
- this.options.beforeRefresh.call(this.table);
1437
- this.updatePaging();
1438
- this.updateCounter();
1439
- this.table.tBodies[0].innerHTML = "";
1440
- if (this.currentStart >= this.currentDataLength) {
1441
- this.table.tBodies[0].innerHTML = '<tr><td colspan="' + this.options.nbColumns + '">'
1442
- + '<div class="progress progress-striped active">'
1443
- + '<div class="bar" style="width: 100%;"></div>'
1444
- + '</div></div></tr>';
1445
- return;
1446
- }
1447
- for (var i = 0;
1448
- i < this.options.pageSize && i + this.currentStart < this.filterIndex.length;
1449
- i++) {
1450
- var index = this.filterIndex[this.currentStart + i];
1451
- var data = this.data[index];
1452
- this.table.tBodies[0].appendChild(this.options.lineFormat.call(this.table,
1453
- index, data));
1454
- }
1455
- this.options.afterRefresh.call(this.table);
1456
- },
1457
-
1458
- /**
1459
- *
1460
- * Set a option and refresh the table if necessary.
1461
- *
1462
- * @param key The name of the option to change
1463
- * @param val The new option value
1464
- *
1465
- * @update options
1466
- *
1467
- **/
1468
- setOption: function (key, val) {
1469
- if (key in this.options) {
1470
- this.options[key] = val;
1471
- if (key === 'sort') {
1472
- this.destroySort();
1473
- this.createSort();
1474
- this.triggerSort();
1475
- }
1476
- if (key === 'sortKey' || key === 'sortDir') {
1477
- this.sort();
1478
- }
1479
- if (key === 'filters') {
1480
- this.destroyFilter();
1481
- this.createFilter();
1482
- }
1483
- if (key === 'filterText') {
1484
- this.changePlaceHolder();
1485
- }
1486
- this.filter();
1487
- }
1488
- },
1489
-
1490
- /**
1491
- *
1492
- * Set a list of options and refresh the table if necessary.
1493
- *
1494
- * @param options A list of options to set (plain object)
1495
- *
1496
- * @update options
1497
- *
1498
- **/
1499
- setOptions: function (options) {
1500
- for (var key in options) {
1501
- if (key in this.options) {
1502
- this.options[key] = options[key];
1503
- }
1504
- }
1505
- if ('sort' in options) {
1506
- this.destroySort();
1507
- this.createSort();
1508
- this.triggerSort();
1509
- }
1510
- else if ('sortKey' in options || 'sortDir' in options) {
1511
- this.sort();
1512
- }
1513
- if ('filters' in options) {
1514
- this.destroyFilter();
1515
- this.createFilter();
1516
- }
1517
- if ('filterText' in options) {
1518
- this.changePlaceHolder();
1519
- }
1520
- this.filter();
1521
- },
1522
-
1523
- /**
1524
- *
1525
- * Remove all the elements added by the datatable.
1526
- *
1527
- **/
1528
- destroy: function () {
1529
- if (this.refreshTimeOut !== undefined) {
1530
- clearTimeout(this.refreshTimeOut);
1531
- }
1532
- this.destroySort();
1533
- for (var i = 0; i < this.pagingDivs.length; ++i) {
1534
- this.pagingDivs[i].classList.remove('pagination-datatable');
1535
- this.pagingDivs[i].classList.remove(this.options.pagingDivClass);
1536
- this.pagingDivs[i].innerHTML = '';
1537
- }
1538
- this.destroyFilter();
1539
- this.table.classList.remove(this.options.tableClass);
1540
- this.removeNode(this.table.tBodies[0]);
1541
- this.table.appendChild(document.createElement('tbody'));
1542
- for (var i = 0; i < this.data.length; i++) {
1543
- var index = this.filterIndex[this.currentStart + i];
1544
- var data = this.data[index];
1545
- this.table.tBodies[0].appendChild(this.options.lineFormat.call(this.table,
1546
- index, data));
1547
- }
1548
-
1549
- }
1550
- };
1551
-
1552
- /**
1553
- * Default option for DataTable.
1554
- *
1555
- */
1556
- DataTable.defaultOptions = {
1557
-
1558
- /**
1559
- * Specify whether the type of the column should be deduced or not. If this option
1560
- * is true, the type is not deduced (mainly here for backward compatibility).
1561
- *
1562
- * @see dataTypes
1563
- */
1564
- forceStrings: false,
1565
-
1566
- /**
1567
- * Specify the class of the table.
1568
- *
1569
- */
1570
- tableClass: 'datatable',
1571
-
1572
- /**
1573
- * Specify the selector for the paging div element.
1574
- *
1575
- */
1576
- pagingDivSelector: '.paging',
1577
-
1578
- /**
1579
- * Specify the class for the paging div element.
1580
- *
1581
- */
1582
- pagingDivClass: 'text-center',
1583
-
1584
- /**
1585
- * Specify the class for the paging list element.
1586
- *
1587
- */
1588
- pagingListClass: 'pagination',
1589
-
1590
- /**
1591
- * Specify the class for the paging list item elements.
1592
- *
1593
- */
1594
- pagingItemClass: '',
1595
-
1596
- /**
1597
- * Specify the class for the paging list link elements.
1598
- *
1599
- */
1600
- pagingLinkClass: '',
1601
-
1602
- /**
1603
- * Specify the href attribute for the paging list link elements.
1604
- *
1605
- */
1606
- pagingLinkHref: '',
1607
-
1608
- /**
1609
- * Specify the tabindex attribute for the paging list link elements when
1610
- * disabled.
1611
- *
1612
- */
1613
- pagingLinkDisabledTabIndex: false,
1614
-
1615
- /**
1616
- * Specify the selector for the counter div element.
1617
- *
1618
- * @see counterText
1619
- */
1620
- counterDivSelector: '.counter',
1621
-
1622
- /**
1623
- * Specify the selector the loading div element.
1624
- *
1625
- * @see data
1626
- */
1627
- loadingDivSelector: '.loading',
1628
-
1629
- /**
1630
- * Sepcify the sort options.
1631
- *
1632
- * @type boolean|string|Array|Object
1633
- */
1634
- sort: false,
1635
-
1636
- /**
1637
- * Specify the default sort key.
1638
- *
1639
- * @type boolean|int|string.
1640
- */
1641
- sortKey: false,
1642
-
1643
- /**
1644
- * Specify the default sort directions, 'asc' or 'desc'.
1645
- *
1646
- */
1647
- sortDir: 'asc',
1648
-
1649
- /**
1650
- * Specify the number of columns, a value of -1 (default) specify
1651
- * the the number of columns should be retrieved for the <thead>
1652
- * elements of the table.
1653
- *
1654
- */
1655
- nbColumns: -1,
1656
-
1657
- /**
1658
- * Specify the number of elements to display per page.
1659
- *
1660
- */
1661
- pageSize: 20,
1662
-
1663
- /**
1664
- * Specify the number of pages to display in the paging list element.
1665
- *
1666
- */
1667
- pagingNumberOfPages: 9,
1668
-
1669
- /**
1670
- * Specify the way of identifying items from the data array:
1671
- *
1672
- * - if this option is false (default), no identification is provided.
1673
- * - if a Function is specified, the function is used to identify:
1674
- * function (id, item) -> boolean
1675
- * - if an int or a string is specified, items are identified by the
1676
- * value corresponding to the key.
1677
- *
1678
- * @type boolean|int|string|Function.
1679
- *
1680
- */
1681
- identify: false,
1682
-
1683
- /**
1684
- * Callback function when the table is updated.
1685
- *
1686
- */
1687
- onChange: function (oldPage, newPage) { },
1688
-
1689
- /**
1690
- * Function used to generate content for the counter div element.
1691
- *
1692
- */
1693
- counterText: function (currentPage, totalPage,
1694
- firstRow, lastRow,
1695
- totalRow, totalRowUnfiltered) {
1696
- var counterText = 'Page ' + currentPage + ' on ' + totalPage
1697
- + '. Showing ' + firstRow + ' to ' + lastRow + ' of ' + totalRow + ' entries';
1698
- if (totalRow != totalRowUnfiltered) {
1699
- counterText += ' (filtered from ' + totalRowUnfiltered + ' total entries)';
1700
- }
1701
- counterText += '.';
1702
- return counterText;
1703
- },
1704
-
1705
- /**
1706
- * Content of the paging item pointing to the first page.
1707
- *
1708
- */
1709
- firstPage: '&lt;&lt;',
1710
-
1711
- /**
1712
- * Content of the paging item pointing to the previous page.
1713
- *
1714
- */
1715
- prevPage: '&lt;',
1716
-
1717
- /**
1718
- *
1719
- */
1720
- pagingPages: false,
1721
-
1722
- /**
1723
- * Content of the paging item pointing to the next page.
1724
- *
1725
- */
1726
- nextPage: '&gt;',
1727
-
1728
- /**
1729
- * Content of the paging item pointing to the last page.
1730
- *
1731
- */
1732
- lastPage: '&gt;&gt;',
1733
-
1734
- /**
1735
- * Specify the type of the columns:
1736
- *
1737
- * - if false, the type is not deduced and values are treated as strings.
1738
- * - if true, the type is deduced automatically.
1739
- * - if an Array is specified, the type of each column is retrieve from the
1740
- * array values, possible values are 'int', 'float' <> 'double', 'date' <> 'datetime',
1741
- * false <> true <> 'string' <> 'str'. A function can also be specified to convert
1742
- * the value.
1743
- *
1744
- * @see forceStrings
1745
- *
1746
- */
1747
- dataTypes: true,
1748
-
1749
- /**
1750
- * Specify the filter options.
1751
- *
1752
- */
1753
- filters: {},
1754
-
1755
- /**
1756
- * Specify the placeholder for the textual input filters.
1757
- */
1758
- filterText: 'Search... ',
1759
-
1760
- /**
1761
- * Specify the placeholder for the select input filters.
1762
- */
1763
- filterEmptySelect: '',
1764
-
1765
- /**
1766
- *
1767
- */
1768
- filterSelectOptions: false,
1769
-
1770
- /**
1771
- *
1772
- */
1773
- filterInputClass: 'form-control',
1774
-
1775
- /**
1776
- *
1777
- */
1778
- filterSelectClass: 'form-control',
1779
-
1780
- /**
1781
- * Callback function before the display is reloaded.
1782
- *
1783
- */
1784
- beforeRefresh: function () { },
1785
-
1786
- /**
1787
- * Callback function after the display has been reloaded.
1788
- *
1789
- */
1790
- afterRefresh: function () { },
1791
-
1792
- /**
1793
- * Function used to generate the row of the table.
1794
- *
1795
- */
1796
- lineFormat: function (id, data) {
1797
- var res = document.createElement('tr');
1798
- res.dataset.id = id;
1799
- for (var key in data) {
1800
- if (data.hasOwnProperty(key)) {
1801
- res.innerHTML += '<td>' + data[key] + '</td>';
1802
- }
1803
- }
1804
- return res;
1805
- }
1806
- };
1807
-
1808
- DataTable.defaultAjaxOptions = {
1809
- url: null,
1810
- size: null,
1811
- refresh: false,
1812
- allInOne: false,
1813
- timeout: 2000
1814
- };