Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/dev-docs/feature-format-matrix/dist/js/tabulator_esm.js
3562 views
1
/* Tabulator v5.5.2 (c) Oliver Folkerd 2023 */
2
class CoreFeature{
3
4
constructor(table){
5
this.table = table;
6
}
7
8
//////////////////////////////////////////
9
/////////////// DataLoad /////////////////
10
//////////////////////////////////////////
11
12
reloadData(data, silent, columnsChanged){
13
return this.table.dataLoader.load(data, undefined, undefined, undefined, silent, columnsChanged);
14
}
15
16
//////////////////////////////////////////
17
///////////// Localization ///////////////
18
//////////////////////////////////////////
19
20
langText(){
21
return this.table.modules.localize.getText(...arguments);
22
}
23
24
langBind(){
25
return this.table.modules.localize.bind(...arguments);
26
}
27
28
langLocale(){
29
return this.table.modules.localize.getLocale(...arguments);
30
}
31
32
33
//////////////////////////////////////////
34
////////// Inter Table Comms /////////////
35
//////////////////////////////////////////
36
37
commsConnections(){
38
return this.table.modules.comms.getConnections(...arguments);
39
}
40
41
commsSend(){
42
return this.table.modules.comms.send(...arguments);
43
}
44
45
//////////////////////////////////////////
46
//////////////// Layout /////////////////
47
//////////////////////////////////////////
48
49
layoutMode(){
50
return this.table.modules.layout.getMode();
51
}
52
53
layoutRefresh(force){
54
return this.table.modules.layout.layout(force);
55
}
56
57
58
//////////////////////////////////////////
59
/////////////// Event Bus ////////////////
60
//////////////////////////////////////////
61
62
subscribe(){
63
return this.table.eventBus.subscribe(...arguments);
64
}
65
66
unsubscribe(){
67
return this.table.eventBus.unsubscribe(...arguments);
68
}
69
70
subscribed(key){
71
return this.table.eventBus.subscribed(key);
72
}
73
74
subscriptionChange(){
75
return this.table.eventBus.subscriptionChange(...arguments);
76
}
77
78
dispatch(){
79
return this.table.eventBus.dispatch(...arguments);
80
}
81
82
chain(){
83
return this.table.eventBus.chain(...arguments);
84
}
85
86
confirm(){
87
return this.table.eventBus.confirm(...arguments);
88
}
89
90
dispatchExternal(){
91
return this.table.externalEvents.dispatch(...arguments);
92
}
93
94
subscribedExternal(key){
95
return this.table.externalEvents.subscribed(key);
96
}
97
98
subscriptionChangeExternal(){
99
return this.table.externalEvents.subscriptionChange(...arguments);
100
}
101
102
//////////////////////////////////////////
103
//////////////// Options /////////////////
104
//////////////////////////////////////////
105
106
options(key){
107
return this.table.options[key];
108
}
109
110
setOption(key, value){
111
if(typeof value !== "undefined"){
112
this.table.options[key] = value;
113
}
114
115
return this.table.options[key];
116
}
117
118
//////////////////////////////////////////
119
/////////// Deprecation Checks ///////////
120
//////////////////////////////////////////
121
122
deprecationCheck(oldOption, newOption){
123
return this.table.deprecationAdvisor.check(oldOption, newOption);
124
}
125
126
deprecationCheckMsg(oldOption, msg){
127
return this.table.deprecationAdvisor.checkMsg(oldOption, msg);
128
}
129
130
deprecationMsg(msg){
131
return this.table.deprecationAdvisor.msg(msg);
132
}
133
//////////////////////////////////////////
134
//////////////// Modules /////////////////
135
//////////////////////////////////////////
136
137
module(key){
138
return this.table.module(key);
139
}
140
}
141
142
class Helpers{
143
144
static elVisible(el){
145
return !(el.offsetWidth <= 0 && el.offsetHeight <= 0);
146
}
147
148
static elOffset(el){
149
var box = el.getBoundingClientRect();
150
151
return {
152
top: box.top + window.pageYOffset - document.documentElement.clientTop,
153
left: box.left + window.pageXOffset - document.documentElement.clientLeft
154
};
155
}
156
157
static deepClone(obj, clone, list = []){
158
var objectProto = {}.__proto__,
159
arrayProto = [].__proto__;
160
161
if (!clone){
162
clone = Object.assign(Array.isArray(obj) ? [] : {}, obj);
163
}
164
165
for(var i in obj) {
166
let subject = obj[i],
167
match, copy;
168
169
if(subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)){
170
match = list.findIndex((item) => {
171
return item.subject === subject;
172
});
173
174
if(match > -1){
175
clone[i] = list[match].copy;
176
}else {
177
copy = Object.assign(Array.isArray(subject) ? [] : {}, subject);
178
179
list.unshift({subject, copy});
180
181
clone[i] = this.deepClone(subject, copy, list);
182
}
183
}
184
}
185
186
return clone;
187
}
188
}
189
190
class Popup extends CoreFeature{
191
constructor(table, element, parent){
192
super(table);
193
194
this.element = element;
195
this.container = this._lookupContainer();
196
197
this.parent = parent;
198
199
this.reversedX = false;
200
this.childPopup = null;
201
this.blurable = false;
202
this.blurCallback = null;
203
this.blurEventsBound = false;
204
this.renderedCallback = null;
205
206
this.visible = false;
207
this.hideable = true;
208
209
this.element.classList.add("tabulator-popup-container");
210
211
this.blurEvent = this.hide.bind(this, false);
212
this.escEvent = this._escapeCheck.bind(this);
213
214
this.destroyBinding = this.tableDestroyed.bind(this);
215
this.destroyed = false;
216
}
217
218
tableDestroyed(){
219
this.destroyed = true;
220
this.hide(true);
221
}
222
223
_lookupContainer(){
224
var container = this.table.options.popupContainer;
225
226
if(typeof container === "string"){
227
container = document.querySelector(container);
228
229
if(!container){
230
console.warn("Menu Error - no container element found matching selector:", this.table.options.popupContainer , "(defaulting to document body)");
231
}
232
}else if (container === true){
233
container = this.table.element;
234
}
235
236
if(container && !this._checkContainerIsParent(container)){
237
container = false;
238
console.warn("Menu Error - container element does not contain this table:", this.table.options.popupContainer , "(defaulting to document body)");
239
}
240
241
if(!container){
242
container = document.body;
243
}
244
245
return container;
246
}
247
248
_checkContainerIsParent(container, element = this.table.element){
249
if(container === element){
250
return true;
251
}else {
252
return element.parentNode ? this._checkContainerIsParent(container, element.parentNode) : false;
253
}
254
}
255
256
renderCallback(callback){
257
this.renderedCallback = callback;
258
}
259
260
containerEventCoords(e){
261
var touch = !(e instanceof MouseEvent);
262
263
var x = touch ? e.touches[0].pageX : e.pageX;
264
var y = touch ? e.touches[0].pageY : e.pageY;
265
266
if(this.container !== document.body){
267
let parentOffset = Helpers.elOffset(this.container);
268
269
x -= parentOffset.left;
270
y -= parentOffset.top;
271
}
272
273
return {x, y};
274
}
275
276
elementPositionCoords(element, position = "right"){
277
var offset = Helpers.elOffset(element),
278
containerOffset, x, y;
279
280
if(this.container !== document.body){
281
containerOffset = Helpers.elOffset(this.container);
282
283
offset.left -= containerOffset.left;
284
offset.top -= containerOffset.top;
285
}
286
287
switch(position){
288
case "right":
289
x = offset.left + element.offsetWidth;
290
y = offset.top - 1;
291
break;
292
293
case "bottom":
294
x = offset.left;
295
y = offset.top + element.offsetHeight;
296
break;
297
298
case "left":
299
x = offset.left;
300
y = offset.top - 1;
301
break;
302
303
case "top":
304
x = offset.left;
305
y = offset.top;
306
break;
307
308
case "center":
309
x = offset.left + (element.offsetWidth / 2);
310
y = offset.top + (element.offsetHeight / 2);
311
break;
312
313
}
314
315
return {x, y, offset};
316
}
317
318
show(origin, position){
319
var x, y, parentEl, parentOffset, coords;
320
321
if(this.destroyed || this.table.destroyed){
322
return this;
323
}
324
325
if(origin instanceof HTMLElement){
326
parentEl = origin;
327
coords = this.elementPositionCoords(origin, position);
328
329
parentOffset = coords.offset;
330
x = coords.x;
331
y = coords.y;
332
333
}else if(typeof origin === "number"){
334
parentOffset = {top:0, left:0};
335
x = origin;
336
y = position;
337
}else {
338
coords = this.containerEventCoords(origin);
339
340
x = coords.x;
341
y = coords.y;
342
343
this.reversedX = false;
344
}
345
346
this.element.style.top = y + "px";
347
this.element.style.left = x + "px";
348
349
this.container.appendChild(this.element);
350
351
if(typeof this.renderedCallback === "function"){
352
this.renderedCallback();
353
}
354
355
this._fitToScreen(x, y, parentEl, parentOffset, position);
356
357
this.visible = true;
358
359
this.subscribe("table-destroy", this.destroyBinding);
360
361
this.element.addEventListener("mousedown", (e) => {
362
e.stopPropagation();
363
});
364
365
return this;
366
}
367
368
_fitToScreen(x, y, parentEl, parentOffset, position){
369
var scrollTop = this.container === document.body ? document.documentElement.scrollTop : this.container.scrollTop;
370
371
//move menu to start on right edge if it is too close to the edge of the screen
372
if((x + this.element.offsetWidth) >= this.container.offsetWidth || this.reversedX){
373
this.element.style.left = "";
374
375
if(parentEl){
376
this.element.style.right = (this.container.offsetWidth - parentOffset.left) + "px";
377
}else {
378
this.element.style.right = (this.container.offsetWidth - x) + "px";
379
}
380
381
this.reversedX = true;
382
}
383
384
//move menu to start on bottom edge if it is too close to the edge of the screen
385
if((y + this.element.offsetHeight) > Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0)) {
386
if(parentEl){
387
switch(position){
388
case "bottom":
389
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1) + "px";
390
break;
391
392
default:
393
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1) + "px";
394
}
395
396
}else {
397
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight) + "px";
398
}
399
}
400
}
401
402
isVisible(){
403
return this.visible;
404
}
405
406
hideOnBlur(callback){
407
this.blurable = true;
408
409
if(this.visible){
410
setTimeout(() => {
411
if(this.visible){
412
this.table.rowManager.element.addEventListener("scroll", this.blurEvent);
413
this.subscribe("cell-editing", this.blurEvent);
414
document.body.addEventListener("click", this.blurEvent);
415
document.body.addEventListener("contextmenu", this.blurEvent);
416
document.body.addEventListener("mousedown", this.blurEvent);
417
window.addEventListener("resize", this.blurEvent);
418
document.body.addEventListener("keydown", this.escEvent);
419
420
this.blurEventsBound = true;
421
}
422
}, 100);
423
424
this.blurCallback = callback;
425
}
426
427
return this;
428
}
429
430
_escapeCheck(e){
431
if(e.keyCode == 27){
432
this.hide();
433
}
434
}
435
436
blockHide(){
437
this.hideable = false;
438
}
439
440
restoreHide(){
441
this.hideable = true;
442
}
443
444
hide(silent = false){
445
if(this.visible && this.hideable){
446
if(this.blurable && this.blurEventsBound){
447
document.body.removeEventListener("keydown", this.escEvent);
448
document.body.removeEventListener("click", this.blurEvent);
449
document.body.removeEventListener("contextmenu", this.blurEvent);
450
document.body.removeEventListener("mousedown", this.blurEvent);
451
window.removeEventListener("resize", this.blurEvent);
452
this.table.rowManager.element.removeEventListener("scroll", this.blurEvent);
453
this.unsubscribe("cell-editing", this.blurEvent);
454
455
this.blurEventsBound = false;
456
}
457
458
if(this.childPopup){
459
this.childPopup.hide();
460
}
461
462
if(this.parent){
463
this.parent.childPopup = null;
464
}
465
466
if(this.element.parentNode){
467
this.element.parentNode.removeChild(this.element);
468
}
469
470
this.visible = false;
471
472
if(this.blurCallback && !silent){
473
this.blurCallback();
474
}
475
476
this.unsubscribe("table-destroy", this.destroyBinding);
477
}
478
479
return this;
480
}
481
482
child(element){
483
if(this.childPopup){
484
this.childPopup.hide();
485
}
486
487
this.childPopup = new Popup(this.table, element, this);
488
489
return this.childPopup;
490
}
491
}
492
493
class Module extends CoreFeature{
494
495
constructor(table, name){
496
super(table);
497
498
this._handler = null;
499
}
500
501
initialize(){
502
// setup module when table is initialized, to be overridden in module
503
}
504
505
506
///////////////////////////////////
507
////// Options Registration ///////
508
///////////////////////////////////
509
510
registerTableOption(key, value){
511
this.table.optionsList.register(key, value);
512
}
513
514
registerColumnOption(key, value){
515
this.table.columnManager.optionsList.register(key, value);
516
}
517
518
///////////////////////////////////
519
/// Public Function Registration ///
520
///////////////////////////////////
521
522
registerTableFunction(name, func){
523
if(typeof this.table[name] === "undefined"){
524
this.table[name] = (...args) => {
525
this.table.initGuard(name);
526
527
return func(...args);
528
};
529
}else {
530
console.warn("Unable to bind table function, name already in use", name);
531
}
532
}
533
534
registerComponentFunction(component, func, handler){
535
return this.table.componentFunctionBinder.bind(component, func, handler);
536
}
537
538
///////////////////////////////////
539
////////// Data Pipeline //////////
540
///////////////////////////////////
541
542
registerDataHandler(handler, priority){
543
this.table.rowManager.registerDataPipelineHandler(handler, priority);
544
this._handler = handler;
545
}
546
547
registerDisplayHandler(handler, priority){
548
this.table.rowManager.registerDisplayPipelineHandler(handler, priority);
549
this._handler = handler;
550
}
551
552
displayRows(adjust){
553
var index = this.table.rowManager.displayRows.length - 1,
554
lookupIndex;
555
556
if(this._handler){
557
lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => {
558
return item.handler === this._handler;
559
});
560
561
if(lookupIndex > -1){
562
index = lookupIndex;
563
}
564
}
565
566
if(adjust){
567
index = index + adjust;
568
}
569
570
if(this._handler){
571
if(index > -1){
572
return this.table.rowManager.getDisplayRows(index);
573
}else {
574
return this.activeRows();
575
}
576
}
577
}
578
579
activeRows(){
580
return this.table.rowManager.activeRows;
581
}
582
583
refreshData(renderInPosition, handler){
584
if(!handler){
585
handler = this._handler;
586
}
587
588
if(handler){
589
this.table.rowManager.refreshActiveData(handler, false, renderInPosition);
590
}
591
}
592
593
///////////////////////////////////
594
//////// Footer Management ////////
595
///////////////////////////////////
596
597
footerAppend(element){
598
return this.table.footerManager.append(element);
599
}
600
601
footerPrepend(element){
602
return this.table.footerManager.prepend(element);
603
}
604
605
footerRemove(element){
606
return this.table.footerManager.remove(element);
607
}
608
609
///////////////////////////////////
610
//////// Popups Management ////////
611
///////////////////////////////////
612
613
popup(menuEl, menuContainer){
614
return new Popup(this.table, menuEl, menuContainer);
615
}
616
617
///////////////////////////////////
618
//////// Alert Management ////////
619
///////////////////////////////////
620
621
alert(content, type){
622
return this.table.alertManager.alert(content, type);
623
}
624
625
clearAlert(){
626
return this.table.alertManager.clear();
627
}
628
629
}
630
631
var defaultAccessors = {};
632
633
class Accessor extends Module{
634
635
constructor(table){
636
super(table);
637
638
this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types
639
640
this.registerColumnOption("accessor");
641
this.registerColumnOption("accessorParams");
642
this.registerColumnOption("accessorData");
643
this.registerColumnOption("accessorDataParams");
644
this.registerColumnOption("accessorDownload");
645
this.registerColumnOption("accessorDownloadParams");
646
this.registerColumnOption("accessorClipboard");
647
this.registerColumnOption("accessorClipboardParams");
648
this.registerColumnOption("accessorPrint");
649
this.registerColumnOption("accessorPrintParams");
650
this.registerColumnOption("accessorHtmlOutput");
651
this.registerColumnOption("accessorHtmlOutputParams");
652
}
653
654
initialize(){
655
this.subscribe("column-layout", this.initializeColumn.bind(this));
656
this.subscribe("row-data-retrieve", this.transformRow.bind(this));
657
}
658
659
//initialize column accessor
660
initializeColumn(column){
661
var match = false,
662
config = {};
663
664
this.allowedTypes.forEach((type) => {
665
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
666
accessor;
667
668
if(column.definition[key]){
669
accessor = this.lookupAccessor(column.definition[key]);
670
671
if(accessor){
672
match = true;
673
674
config[key] = {
675
accessor:accessor,
676
params: column.definition[key + "Params"] || {},
677
};
678
}
679
}
680
});
681
682
if(match){
683
column.modules.accessor = config;
684
}
685
}
686
687
lookupAccessor(value){
688
var accessor = false;
689
690
//set column accessor
691
switch(typeof value){
692
case "string":
693
if(Accessor.accessors[value]){
694
accessor = Accessor.accessors[value];
695
}else {
696
console.warn("Accessor Error - No such accessor found, ignoring: ", value);
697
}
698
break;
699
700
case "function":
701
accessor = value;
702
break;
703
}
704
705
return accessor;
706
}
707
708
//apply accessor to row
709
transformRow(row, type){
710
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
711
rowComponent = row.getComponent();
712
713
//clone data object with deep copy to isolate internal data from returned result
714
var data = Helpers.deepClone(row.data || {});
715
716
this.table.columnManager.traverse(function(column){
717
var value, accessor, params, colComponent;
718
719
if(column.modules.accessor){
720
721
accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false;
722
723
if(accessor){
724
value = column.getFieldValue(data);
725
726
if(value != "undefined"){
727
colComponent = column.getComponent();
728
params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params;
729
column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent));
730
}
731
}
732
}
733
});
734
735
return data;
736
}
737
}
738
739
//load defaults
740
Accessor.moduleName = "accessor";
741
Accessor.accessors = defaultAccessors;
742
743
var defaultConfig = {
744
method: "GET",
745
};
746
747
function generateParamsList(data, prefix){
748
var output = [];
749
750
prefix = prefix || "";
751
752
if(Array.isArray(data)){
753
data.forEach((item, i) => {
754
output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i));
755
});
756
}else if (typeof data === "object"){
757
for (var key in data){
758
output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key));
759
}
760
}else {
761
output.push({key:prefix, value:data});
762
}
763
764
return output;
765
}
766
767
function serializeParams(params){
768
var output = generateParamsList(params),
769
encoded = [];
770
771
output.forEach(function(item){
772
encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value));
773
});
774
775
return encoded.join("&");
776
}
777
778
function urlBuilder(url, config, params){
779
if(url){
780
if(params && Object.keys(params).length){
781
if(!config.method || config.method.toLowerCase() == "get"){
782
config.method = "get";
783
784
url += (url.includes("?") ? "&" : "?") + serializeParams(params);
785
}
786
}
787
}
788
789
return url;
790
}
791
792
function defaultLoaderPromise(url, config, params){
793
var contentType;
794
795
return new Promise((resolve, reject) => {
796
//set url
797
url = this.urlGenerator.call(this.table, url, config, params);
798
799
//set body content if not GET request
800
if(config.method.toUpperCase() != "GET"){
801
contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType];
802
if(contentType){
803
804
for(var key in contentType.headers){
805
if(!config.headers){
806
config.headers = {};
807
}
808
809
if(typeof config.headers[key] === "undefined"){
810
config.headers[key] = contentType.headers[key];
811
}
812
}
813
814
config.body = contentType.body.call(this, url, config, params);
815
816
}else {
817
console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType);
818
}
819
}
820
821
if(url){
822
//configure headers
823
if(typeof config.headers === "undefined"){
824
config.headers = {};
825
}
826
827
if(typeof config.headers.Accept === "undefined"){
828
config.headers.Accept = "application/json";
829
}
830
831
if(typeof config.headers["X-Requested-With"] === "undefined"){
832
config.headers["X-Requested-With"] = "XMLHttpRequest";
833
}
834
835
if(typeof config.mode === "undefined"){
836
config.mode = "cors";
837
}
838
839
if(config.mode == "cors"){
840
if(typeof config.headers["Origin"] === "undefined"){
841
config.headers["Origin"] = window.location.origin;
842
}
843
844
if(typeof config.credentials === "undefined"){
845
config.credentials = 'same-origin';
846
}
847
}else {
848
if(typeof config.credentials === "undefined"){
849
config.credentials = 'include';
850
}
851
}
852
853
//send request
854
fetch(url, config)
855
.then((response)=>{
856
if(response.ok) {
857
response.json()
858
.then((data)=>{
859
resolve(data);
860
}).catch((error)=>{
861
reject(error);
862
console.warn("Ajax Load Error - Invalid JSON returned", error);
863
});
864
}else {
865
console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText);
866
reject(response);
867
}
868
})
869
.catch((error)=>{
870
console.error("Ajax Load Error - Connection Error: ", error);
871
reject(error);
872
});
873
}else {
874
console.warn("Ajax Load Error - No URL Set");
875
resolve([]);
876
}
877
});
878
}
879
880
function generateParamsList$1(data, prefix){
881
var output = [];
882
883
prefix = prefix || "";
884
885
if(Array.isArray(data)){
886
data.forEach((item, i) => {
887
output = output.concat(generateParamsList$1(item, prefix ? prefix + "[" + i + "]" : i));
888
});
889
}else if (typeof data === "object"){
890
for (var key in data){
891
output = output.concat(generateParamsList$1(data[key], prefix ? prefix + "[" + key + "]" : key));
892
}
893
}else {
894
output.push({key:prefix, value:data});
895
}
896
897
return output;
898
}
899
900
var defaultContentTypeFormatters = {
901
"json":{
902
headers:{
903
'Content-Type': 'application/json',
904
},
905
body:function(url, config, params){
906
return JSON.stringify(params);
907
},
908
},
909
"form":{
910
headers:{
911
},
912
body:function(url, config, params){
913
914
var output = generateParamsList$1(params),
915
form = new FormData();
916
917
output.forEach(function(item){
918
form.append(item.key, item.value);
919
});
920
921
return form;
922
},
923
},
924
};
925
926
class Ajax extends Module{
927
928
constructor(table){
929
super(table);
930
931
this.config = {}; //hold config object for ajax request
932
this.url = ""; //request URL
933
this.urlGenerator = false;
934
this.params = false; //request parameters
935
936
this.loaderPromise = false;
937
938
this.registerTableOption("ajaxURL", false); //url for ajax loading
939
this.registerTableOption("ajaxURLGenerator", false);
940
this.registerTableOption("ajaxParams", {}); //params for ajax loading
941
this.registerTableOption("ajaxConfig", "get"); //ajax request type
942
this.registerTableOption("ajaxContentType", "form"); //ajax request type
943
this.registerTableOption("ajaxRequestFunc", false); //promise function
944
945
this.registerTableOption("ajaxRequesting", function(){});
946
this.registerTableOption("ajaxResponse", false);
947
948
this.contentTypeFormatters = Ajax.contentTypeFormatters;
949
}
950
951
//initialize setup options
952
initialize(){
953
this.loaderPromise = this.table.options.ajaxRequestFunc || Ajax.defaultLoaderPromise;
954
this.urlGenerator = this.table.options.ajaxURLGenerator || Ajax.defaultURLGenerator;
955
956
if(this.table.options.ajaxURL){
957
this.setUrl(this.table.options.ajaxURL);
958
}
959
960
961
this.setDefaultConfig(this.table.options.ajaxConfig);
962
963
this.registerTableFunction("getAjaxUrl", this.getUrl.bind(this));
964
965
this.subscribe("data-loading", this.requestDataCheck.bind(this));
966
this.subscribe("data-params", this.requestParams.bind(this));
967
this.subscribe("data-load", this.requestData.bind(this));
968
}
969
970
requestParams(data, config, silent, params){
971
var ajaxParams = this.table.options.ajaxParams;
972
973
if(ajaxParams){
974
if(typeof ajaxParams === "function"){
975
ajaxParams = ajaxParams.call(this.table);
976
}
977
978
params = Object.assign(params, ajaxParams);
979
}
980
981
return params;
982
}
983
984
requestDataCheck(data, params, config, silent){
985
return !!((!data && this.url) || typeof data === "string");
986
}
987
988
requestData(url, params, config, silent, previousData){
989
var ajaxConfig;
990
991
if(!previousData && this.requestDataCheck(url)){
992
if(url){
993
this.setUrl(url);
994
}
995
996
ajaxConfig = this.generateConfig(config);
997
998
return this.sendRequest(this.url, params, ajaxConfig);
999
}else {
1000
return previousData;
1001
}
1002
}
1003
1004
setDefaultConfig(config = {}){
1005
this.config = Object.assign({}, Ajax.defaultConfig);
1006
1007
if(typeof config == "string"){
1008
this.config.method = config;
1009
}else {
1010
Object.assign(this.config, config);
1011
}
1012
}
1013
1014
//load config object
1015
generateConfig(config = {}){
1016
var ajaxConfig = Object.assign({}, this.config);
1017
1018
if(typeof config == "string"){
1019
ajaxConfig.method = config;
1020
}else {
1021
Object.assign(ajaxConfig, config);
1022
}
1023
1024
return ajaxConfig;
1025
}
1026
1027
//set request url
1028
setUrl(url){
1029
this.url = url;
1030
}
1031
1032
//get request url
1033
getUrl(){
1034
return this.url;
1035
}
1036
1037
//send ajax request
1038
sendRequest(url, params, config){
1039
if(this.table.options.ajaxRequesting.call(this.table, url, params) !== false){
1040
return this.loaderPromise(url, config, params)
1041
.then((data)=>{
1042
if(this.table.options.ajaxResponse){
1043
data = this.table.options.ajaxResponse.call(this.table, url, params, data);
1044
}
1045
1046
return data;
1047
});
1048
}else {
1049
return Promise.reject();
1050
}
1051
}
1052
}
1053
1054
Ajax.moduleName = "ajax";
1055
1056
//load defaults
1057
Ajax.defaultConfig = defaultConfig;
1058
Ajax.defaultURLGenerator = urlBuilder;
1059
Ajax.defaultLoaderPromise = defaultLoaderPromise;
1060
Ajax.contentTypeFormatters = defaultContentTypeFormatters;
1061
1062
var defaultPasteActions = {
1063
replace:function(rows){
1064
return this.table.setData(rows);
1065
},
1066
update:function(rows){
1067
return this.table.updateOrAddData(rows);
1068
},
1069
insert:function(rows){
1070
return this.table.addData(rows);
1071
},
1072
};
1073
1074
var defaultPasteParsers = {
1075
table:function(clipboard){
1076
var data = [],
1077
headerFindSuccess = true,
1078
columns = this.table.columnManager.columns,
1079
columnMap = [],
1080
rows = [];
1081
1082
//get data from clipboard into array of columns and rows.
1083
clipboard = clipboard.split("\n");
1084
1085
clipboard.forEach(function(row){
1086
data.push(row.split("\t"));
1087
});
1088
1089
if(data.length && !(data.length === 1 && data[0].length < 2)){
1090
1091
//check if headers are present by title
1092
data[0].forEach(function(value){
1093
var column = columns.find(function(column){
1094
return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim();
1095
});
1096
1097
if(column){
1098
columnMap.push(column);
1099
}else {
1100
headerFindSuccess = false;
1101
}
1102
});
1103
1104
//check if column headers are present by field
1105
if(!headerFindSuccess){
1106
headerFindSuccess = true;
1107
columnMap = [];
1108
1109
data[0].forEach(function(value){
1110
var column = columns.find(function(column){
1111
return value && column.field && value.trim() && column.field.trim() === value.trim();
1112
});
1113
1114
if(column){
1115
columnMap.push(column);
1116
}else {
1117
headerFindSuccess = false;
1118
}
1119
});
1120
1121
if(!headerFindSuccess){
1122
columnMap = this.table.columnManager.columnsByIndex;
1123
}
1124
}
1125
1126
//remove header row if found
1127
if(headerFindSuccess){
1128
data.shift();
1129
}
1130
1131
data.forEach(function(item){
1132
var row = {};
1133
1134
item.forEach(function(value, i){
1135
if(columnMap[i]){
1136
row[columnMap[i].field] = value;
1137
}
1138
});
1139
1140
rows.push(row);
1141
});
1142
1143
return rows;
1144
}else {
1145
return false;
1146
}
1147
}
1148
};
1149
1150
class Clipboard extends Module{
1151
1152
constructor(table){
1153
super(table);
1154
1155
this.mode = true;
1156
this.pasteParser = function(){};
1157
this.pasteAction = function(){};
1158
this.customSelection = false;
1159
this.rowRange = false;
1160
this.blocked = true; //block copy actions not originating from this command
1161
1162
this.registerTableOption("clipboard", false); //enable clipboard
1163
this.registerTableOption("clipboardCopyStyled", true); //formatted table data
1164
this.registerTableOption("clipboardCopyConfig", false); //clipboard config
1165
this.registerTableOption("clipboardCopyFormatter", false); //DEPRECATED - REMOVE in 5.0
1166
this.registerTableOption("clipboardCopyRowRange", "active"); //restrict clipboard to visible rows only
1167
this.registerTableOption("clipboardPasteParser", "table"); //convert pasted clipboard data to rows
1168
this.registerTableOption("clipboardPasteAction", "insert"); //how to insert pasted data into the table
1169
1170
this.registerColumnOption("clipboard");
1171
this.registerColumnOption("titleClipboard");
1172
}
1173
1174
initialize(){
1175
this.mode = this.table.options.clipboard;
1176
1177
this.rowRange = this.table.options.clipboardCopyRowRange;
1178
1179
if(this.mode === true || this.mode === "copy"){
1180
this.table.element.addEventListener("copy", (e) => {
1181
var plain, html, list;
1182
1183
if(!this.blocked){
1184
e.preventDefault();
1185
1186
if(this.customSelection){
1187
plain = this.customSelection;
1188
1189
if(this.table.options.clipboardCopyFormatter){
1190
plain = this.table.options.clipboardCopyFormatter("plain", plain);
1191
}
1192
}else {
1193
1194
list = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard");
1195
1196
html = this.table.modules.export.generateHTMLTable(list);
1197
plain = html ? this.generatePlainContent(list) : "";
1198
1199
if(this.table.options.clipboardCopyFormatter){
1200
plain = this.table.options.clipboardCopyFormatter("plain", plain);
1201
html = this.table.options.clipboardCopyFormatter("html", html);
1202
}
1203
}
1204
1205
if (window.clipboardData && window.clipboardData.setData) {
1206
window.clipboardData.setData('Text', plain);
1207
} else if (e.clipboardData && e.clipboardData.setData) {
1208
e.clipboardData.setData('text/plain', plain);
1209
if(html){
1210
e.clipboardData.setData('text/html', html);
1211
}
1212
} else if (e.originalEvent && e.originalEvent.clipboardData.setData) {
1213
e.originalEvent.clipboardData.setData('text/plain', plain);
1214
if(html){
1215
e.originalEvent.clipboardData.setData('text/html', html);
1216
}
1217
}
1218
1219
this.dispatchExternal("clipboardCopied", plain, html);
1220
1221
this.reset();
1222
}
1223
});
1224
}
1225
1226
if(this.mode === true || this.mode === "paste"){
1227
this.table.element.addEventListener("paste", (e) => {
1228
this.paste(e);
1229
});
1230
}
1231
1232
this.setPasteParser(this.table.options.clipboardPasteParser);
1233
this.setPasteAction(this.table.options.clipboardPasteAction);
1234
1235
this.registerTableFunction("copyToClipboard", this.copy.bind(this));
1236
}
1237
1238
reset(){
1239
this.blocked = true;
1240
this.customSelection = false;
1241
}
1242
1243
generatePlainContent (list) {
1244
var output = [];
1245
1246
list.forEach((row) => {
1247
var rowData = [];
1248
1249
row.columns.forEach((col) => {
1250
var value = "";
1251
1252
if(col){
1253
1254
if(row.type === "group"){
1255
col.value = col.component.getKey();
1256
}
1257
1258
if(col.value === null){
1259
value = "";
1260
}else {
1261
switch(typeof col.value){
1262
case "object":
1263
value = JSON.stringify(col.value);
1264
break;
1265
1266
case "undefined":
1267
value = "";
1268
break;
1269
1270
default:
1271
value = col.value;
1272
}
1273
}
1274
}
1275
1276
rowData.push(value);
1277
});
1278
1279
output.push(rowData.join("\t"));
1280
});
1281
1282
return output.join("\n");
1283
}
1284
1285
copy (range, internal) {
1286
var sel, textRange;
1287
this.blocked = false;
1288
this.customSelection = false;
1289
1290
if (this.mode === true || this.mode === "copy") {
1291
1292
this.rowRange = range || this.table.options.clipboardCopyRowRange;
1293
1294
if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
1295
range = document.createRange();
1296
range.selectNodeContents(this.table.element);
1297
sel = window.getSelection();
1298
1299
if (sel.toString() && internal) {
1300
this.customSelection = sel.toString();
1301
}
1302
1303
sel.removeAllRanges();
1304
sel.addRange(range);
1305
} else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") {
1306
textRange = document.body.createTextRange();
1307
textRange.moveToElementText(this.table.element);
1308
textRange.select();
1309
}
1310
1311
document.execCommand('copy');
1312
1313
if (sel) {
1314
sel.removeAllRanges();
1315
}
1316
}
1317
}
1318
1319
//PASTE EVENT HANDLING
1320
setPasteAction(action){
1321
1322
switch(typeof action){
1323
case "string":
1324
this.pasteAction = Clipboard.pasteActions[action];
1325
1326
if(!this.pasteAction){
1327
console.warn("Clipboard Error - No such paste action found:", action);
1328
}
1329
break;
1330
1331
case "function":
1332
this.pasteAction = action;
1333
break;
1334
}
1335
}
1336
1337
setPasteParser(parser){
1338
switch(typeof parser){
1339
case "string":
1340
this.pasteParser = Clipboard.pasteParsers[parser];
1341
1342
if(!this.pasteParser){
1343
console.warn("Clipboard Error - No such paste parser found:", parser);
1344
}
1345
break;
1346
1347
case "function":
1348
this.pasteParser = parser;
1349
break;
1350
}
1351
}
1352
1353
paste(e){
1354
var data, rowData, rows;
1355
1356
if(this.checkPaseOrigin(e)){
1357
1358
data = this.getPasteData(e);
1359
1360
rowData = this.pasteParser.call(this, data);
1361
1362
if(rowData){
1363
e.preventDefault();
1364
1365
if(this.table.modExists("mutator")){
1366
rowData = this.mutateData(rowData);
1367
}
1368
1369
rows = this.pasteAction.call(this, rowData);
1370
1371
this.dispatchExternal("clipboardPasted", data, rowData, rows);
1372
}else {
1373
this.dispatchExternal("clipboardPasteError", data);
1374
}
1375
}
1376
}
1377
1378
mutateData(data){
1379
var output = [];
1380
1381
if(Array.isArray(data)){
1382
data.forEach((row) => {
1383
output.push(this.table.modules.mutator.transformRow(row, "clipboard"));
1384
});
1385
}else {
1386
output = data;
1387
}
1388
1389
return output;
1390
}
1391
1392
1393
checkPaseOrigin(e){
1394
var valid = true;
1395
1396
if(e.target.tagName != "DIV" || this.table.modules.edit.currentCell){
1397
valid = false;
1398
}
1399
1400
return valid;
1401
}
1402
1403
getPasteData(e){
1404
var data;
1405
1406
if (window.clipboardData && window.clipboardData.getData) {
1407
data = window.clipboardData.getData('Text');
1408
} else if (e.clipboardData && e.clipboardData.getData) {
1409
data = e.clipboardData.getData('text/plain');
1410
} else if (e.originalEvent && e.originalEvent.clipboardData.getData) {
1411
data = e.originalEvent.clipboardData.getData('text/plain');
1412
}
1413
1414
return data;
1415
}
1416
}
1417
1418
Clipboard.moduleName = "clipboard";
1419
1420
//load defaults
1421
Clipboard.pasteActions = defaultPasteActions;
1422
Clipboard.pasteParsers = defaultPasteParsers;
1423
1424
class CalcComponent{
1425
constructor (row){
1426
this._row = row;
1427
1428
return new Proxy(this, {
1429
get: function(target, name, receiver) {
1430
if (typeof target[name] !== "undefined") {
1431
return target[name];
1432
}else {
1433
return target._row.table.componentFunctionBinder.handle("row", target._row, name);
1434
}
1435
}
1436
});
1437
}
1438
1439
getData(transform){
1440
return this._row.getData(transform);
1441
}
1442
1443
getElement(){
1444
return this._row.getElement();
1445
}
1446
1447
getTable(){
1448
return this._row.table;
1449
}
1450
1451
getCells(){
1452
var cells = [];
1453
1454
this._row.getCells().forEach(function(cell){
1455
cells.push(cell.getComponent());
1456
});
1457
1458
return cells;
1459
}
1460
1461
getCell(column){
1462
var cell = this._row.getCell(column);
1463
return cell ? cell.getComponent() : false;
1464
}
1465
1466
_getSelf(){
1467
return this._row;
1468
}
1469
}
1470
1471
//public cell object
1472
class CellComponent {
1473
1474
constructor (cell){
1475
this._cell = cell;
1476
1477
return new Proxy(this, {
1478
get: function(target, name, receiver) {
1479
if (typeof target[name] !== "undefined") {
1480
return target[name];
1481
}else {
1482
return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name);
1483
}
1484
}
1485
});
1486
}
1487
1488
getValue(){
1489
return this._cell.getValue();
1490
}
1491
1492
getOldValue(){
1493
return this._cell.getOldValue();
1494
}
1495
1496
getInitialValue(){
1497
return this._cell.initialValue;
1498
}
1499
1500
getElement(){
1501
return this._cell.getElement();
1502
}
1503
1504
getRow(){
1505
return this._cell.row.getComponent();
1506
}
1507
1508
getData(transform){
1509
return this._cell.row.getData(transform);
1510
}
1511
getType(){
1512
return "cell";
1513
}
1514
getField(){
1515
return this._cell.column.getField();
1516
}
1517
1518
getColumn(){
1519
return this._cell.column.getComponent();
1520
}
1521
1522
setValue(value, mutate){
1523
if(typeof mutate == "undefined"){
1524
mutate = true;
1525
}
1526
1527
this._cell.setValue(value, mutate);
1528
}
1529
1530
restoreOldValue(){
1531
this._cell.setValueActual(this._cell.getOldValue());
1532
}
1533
1534
restoreInitialValue(){
1535
this._cell.setValueActual(this._cell.initialValue);
1536
}
1537
1538
checkHeight(){
1539
this._cell.checkHeight();
1540
}
1541
1542
getTable(){
1543
return this._cell.table;
1544
}
1545
1546
_getSelf(){
1547
return this._cell;
1548
}
1549
}
1550
1551
class Cell extends CoreFeature{
1552
constructor(column, row){
1553
super(column.table);
1554
1555
this.table = column.table;
1556
this.column = column;
1557
this.row = row;
1558
this.element = null;
1559
this.value = null;
1560
this.initialValue;
1561
this.oldValue = null;
1562
this.modules = {};
1563
1564
this.height = null;
1565
this.width = null;
1566
this.minWidth = null;
1567
1568
this.component = null;
1569
1570
this.loaded = false; //track if the cell has been added to the DOM yet
1571
1572
this.build();
1573
}
1574
1575
//////////////// Setup Functions /////////////////
1576
//generate element
1577
build(){
1578
this.generateElement();
1579
1580
this.setWidth();
1581
1582
this._configureCell();
1583
1584
this.setValueActual(this.column.getFieldValue(this.row.data));
1585
1586
this.initialValue = this.value;
1587
}
1588
1589
generateElement(){
1590
this.element = document.createElement('div');
1591
this.element.className = "tabulator-cell";
1592
this.element.setAttribute("role", "gridcell");
1593
}
1594
1595
_configureCell(){
1596
var element = this.element,
1597
field = this.column.getField(),
1598
vertAligns = {
1599
top:"flex-start",
1600
bottom:"flex-end",
1601
middle:"center",
1602
},
1603
hozAligns = {
1604
left:"flex-start",
1605
right:"flex-end",
1606
center:"center",
1607
};
1608
1609
//set text alignment
1610
element.style.textAlign = this.column.hozAlign;
1611
1612
if(this.column.vertAlign){
1613
element.style.display = "inline-flex";
1614
1615
element.style.alignItems = vertAligns[this.column.vertAlign] || "";
1616
1617
if(this.column.hozAlign){
1618
element.style.justifyContent = hozAligns[this.column.hozAlign] || "";
1619
}
1620
}
1621
1622
if(field){
1623
element.setAttribute("tabulator-field", field);
1624
}
1625
1626
//add class to cell if needed
1627
if(this.column.definition.cssClass){
1628
var classNames = this.column.definition.cssClass.split(" ");
1629
classNames.forEach((className) => {
1630
element.classList.add(className);
1631
});
1632
}
1633
1634
this.dispatch("cell-init", this);
1635
1636
//hide cell if not visible
1637
if(!this.column.visible){
1638
this.hide();
1639
}
1640
}
1641
1642
//generate cell contents
1643
_generateContents(){
1644
var val;
1645
1646
val = this.chain("cell-format", this, null, () => {
1647
return this.element.innerHTML = this.value;
1648
});
1649
1650
switch(typeof val){
1651
case "object":
1652
if(val instanceof Node){
1653
1654
//clear previous cell contents
1655
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
1656
1657
this.element.appendChild(val);
1658
}else {
1659
this.element.innerHTML = "";
1660
1661
if(val != null){
1662
console.warn("Format Error - Formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", val);
1663
}
1664
}
1665
break;
1666
case "undefined":
1667
this.element.innerHTML = "";
1668
break;
1669
default:
1670
this.element.innerHTML = val;
1671
}
1672
}
1673
1674
cellRendered(){
1675
this.dispatch("cell-rendered", this);
1676
}
1677
1678
//////////////////// Getters ////////////////////
1679
getElement(containerOnly){
1680
if(!this.loaded){
1681
this.loaded = true;
1682
if(!containerOnly){
1683
this.layoutElement();
1684
}
1685
}
1686
1687
return this.element;
1688
}
1689
1690
getValue(){
1691
return this.value;
1692
}
1693
1694
getOldValue(){
1695
return this.oldValue;
1696
}
1697
1698
//////////////////// Actions ////////////////////
1699
setValue(value, mutate, force){
1700
var changed = this.setValueProcessData(value, mutate, force);
1701
1702
if(changed){
1703
this.dispatch("cell-value-updated", this);
1704
1705
this.cellRendered();
1706
1707
if(this.column.definition.cellEdited){
1708
this.column.definition.cellEdited.call(this.table, this.getComponent());
1709
}
1710
1711
this.dispatchExternal("cellEdited", this.getComponent());
1712
1713
if(this.subscribedExternal("dataChanged")){
1714
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
1715
}
1716
}
1717
}
1718
1719
setValueProcessData(value, mutate, force){
1720
var changed = false;
1721
1722
if(this.value !== value || force){
1723
1724
changed = true;
1725
1726
if(mutate){
1727
value = this.chain("cell-value-changing", [this, value], null, value);
1728
}
1729
}
1730
1731
this.setValueActual(value);
1732
1733
if(changed){
1734
this.dispatch("cell-value-changed", this);
1735
}
1736
1737
return changed;
1738
}
1739
1740
setValueActual(value){
1741
this.oldValue = this.value;
1742
1743
this.value = value;
1744
1745
this.dispatch("cell-value-save-before", this);
1746
1747
this.column.setFieldValue(this.row.data, value);
1748
1749
this.dispatch("cell-value-save-after", this);
1750
1751
if(this.loaded){
1752
this.layoutElement();
1753
}
1754
}
1755
1756
layoutElement(){
1757
this._generateContents();
1758
1759
this.dispatch("cell-layout", this);
1760
}
1761
1762
setWidth(){
1763
this.width = this.column.width;
1764
this.element.style.width = this.column.widthStyled;
1765
}
1766
1767
clearWidth(){
1768
this.width = "";
1769
this.element.style.width = "";
1770
}
1771
1772
getWidth(){
1773
return this.width || this.element.offsetWidth;
1774
}
1775
1776
setMinWidth(){
1777
this.minWidth = this.column.minWidth;
1778
this.element.style.minWidth = this.column.minWidthStyled;
1779
}
1780
1781
setMaxWidth(){
1782
this.maxWidth = this.column.maxWidth;
1783
this.element.style.maxWidth = this.column.maxWidthStyled;
1784
}
1785
1786
checkHeight(){
1787
// var height = this.element.css("height");
1788
this.row.reinitializeHeight();
1789
}
1790
1791
clearHeight(){
1792
this.element.style.height = "";
1793
this.height = null;
1794
1795
this.dispatch("cell-height", this, "");
1796
}
1797
1798
setHeight(){
1799
this.height = this.row.height;
1800
this.element.style.height = this.row.heightStyled;
1801
1802
this.dispatch("cell-height", this, this.row.heightStyled);
1803
}
1804
1805
getHeight(){
1806
return this.height || this.element.offsetHeight;
1807
}
1808
1809
show(){
1810
this.element.style.display = this.column.vertAlign ? "inline-flex" : "";
1811
}
1812
1813
hide(){
1814
this.element.style.display = "none";
1815
}
1816
1817
delete(){
1818
this.dispatch("cell-delete", this);
1819
1820
if(!this.table.rowManager.redrawBlock && this.element.parentNode){
1821
this.element.parentNode.removeChild(this.element);
1822
}
1823
1824
this.element = false;
1825
this.column.deleteCell(this);
1826
this.row.deleteCell(this);
1827
this.calcs = {};
1828
}
1829
1830
getIndex(){
1831
return this.row.getCellIndex(this);
1832
}
1833
1834
//////////////// Object Generation /////////////////
1835
getComponent(){
1836
if(!this.component){
1837
this.component = new CellComponent(this);
1838
}
1839
1840
return this.component;
1841
}
1842
}
1843
1844
//public column object
1845
class ColumnComponent {
1846
constructor (column){
1847
this._column = column;
1848
this.type = "ColumnComponent";
1849
1850
return new Proxy(this, {
1851
get: function(target, name, receiver) {
1852
if (typeof target[name] !== "undefined") {
1853
return target[name];
1854
}else {
1855
return target._column.table.componentFunctionBinder.handle("column", target._column, name);
1856
}
1857
}
1858
});
1859
}
1860
1861
getElement(){
1862
return this._column.getElement();
1863
}
1864
1865
getDefinition(){
1866
return this._column.getDefinition();
1867
}
1868
1869
getField(){
1870
return this._column.getField();
1871
}
1872
1873
getTitleDownload() {
1874
return this._column.getTitleDownload();
1875
}
1876
1877
getCells(){
1878
var cells = [];
1879
1880
this._column.cells.forEach(function(cell){
1881
cells.push(cell.getComponent());
1882
});
1883
1884
return cells;
1885
}
1886
1887
isVisible(){
1888
return this._column.visible;
1889
}
1890
1891
show(){
1892
if(this._column.isGroup){
1893
this._column.columns.forEach(function(column){
1894
column.show();
1895
});
1896
}else {
1897
this._column.show();
1898
}
1899
}
1900
1901
hide(){
1902
if(this._column.isGroup){
1903
this._column.columns.forEach(function(column){
1904
column.hide();
1905
});
1906
}else {
1907
this._column.hide();
1908
}
1909
}
1910
1911
toggle(){
1912
if(this._column.visible){
1913
this.hide();
1914
}else {
1915
this.show();
1916
}
1917
}
1918
1919
delete(){
1920
return this._column.delete();
1921
}
1922
1923
getSubColumns(){
1924
var output = [];
1925
1926
if(this._column.columns.length){
1927
this._column.columns.forEach(function(column){
1928
output.push(column.getComponent());
1929
});
1930
}
1931
1932
return output;
1933
}
1934
1935
getParentColumn(){
1936
return this._column.parent instanceof Column ? this._column.parent.getComponent() : false;
1937
}
1938
1939
_getSelf(){
1940
return this._column;
1941
}
1942
1943
scrollTo(position, ifVisible){
1944
return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible);
1945
}
1946
1947
getTable(){
1948
return this._column.table;
1949
}
1950
1951
move(to, after){
1952
var toColumn = this._column.table.columnManager.findColumn(to);
1953
1954
if(toColumn){
1955
this._column.table.columnManager.moveColumn(this._column, toColumn, after);
1956
}else {
1957
console.warn("Move Error - No matching column found:", toColumn);
1958
}
1959
}
1960
1961
getNextColumn(){
1962
var nextCol = this._column.nextColumn();
1963
1964
return nextCol ? nextCol.getComponent() : false;
1965
}
1966
1967
getPrevColumn(){
1968
var prevCol = this._column.prevColumn();
1969
1970
return prevCol ? prevCol.getComponent() : false;
1971
}
1972
1973
updateDefinition(updates){
1974
return this._column.updateDefinition(updates);
1975
}
1976
1977
getWidth(){
1978
return this._column.getWidth();
1979
}
1980
1981
setWidth(width){
1982
var result;
1983
1984
if(width === true){
1985
result = this._column.reinitializeWidth(true);
1986
}else {
1987
result = this._column.setWidth(width);
1988
}
1989
1990
this._column.table.columnManager.rerenderColumns(true);
1991
1992
return result;
1993
}
1994
}
1995
1996
var defaultColumnOptions = {
1997
"title": undefined,
1998
"field": undefined,
1999
"columns": undefined,
2000
"visible": undefined,
2001
"hozAlign": undefined,
2002
"vertAlign": undefined,
2003
"width": undefined,
2004
"minWidth": 40,
2005
"maxWidth": undefined,
2006
"maxInitialWidth": undefined,
2007
"cssClass": undefined,
2008
"variableHeight": undefined,
2009
"headerVertical": undefined,
2010
"headerHozAlign": undefined,
2011
"headerWordWrap": false,
2012
"editableTitle": undefined,
2013
};
2014
2015
class Column extends CoreFeature{
2016
2017
constructor(def, parent){
2018
super(parent.table);
2019
2020
this.definition = def; //column definition
2021
this.parent = parent; //hold parent object
2022
this.type = "column"; //type of element
2023
this.columns = []; //child columns
2024
this.cells = []; //cells bound to this column
2025
this.element = this.createElement(); //column header element
2026
this.contentElement = false;
2027
this.titleHolderElement = false;
2028
this.titleElement = false;
2029
this.groupElement = this.createGroupElement(); //column group holder element
2030
this.isGroup = false;
2031
this.hozAlign = ""; //horizontal text alignment
2032
this.vertAlign = ""; //vert text alignment
2033
2034
//multi dimensional filed handling
2035
this.field ="";
2036
this.fieldStructure = "";
2037
this.getFieldValue = "";
2038
this.setFieldValue = "";
2039
2040
this.titleDownload = null;
2041
this.titleFormatterRendered = false;
2042
2043
this.mapDefinitions();
2044
2045
this.setField(this.definition.field);
2046
2047
this.modules = {}; //hold module variables;
2048
2049
this.width = null; //column width
2050
this.widthStyled = ""; //column width pre-styled to improve render efficiency
2051
this.maxWidth = null; //column maximum width
2052
this.maxWidthStyled = ""; //column maximum pre-styled to improve render efficiency
2053
this.maxInitialWidth = null;
2054
this.minWidth = null; //column minimum width
2055
this.minWidthStyled = ""; //column minimum pre-styled to improve render efficiency
2056
this.widthFixed = false; //user has specified a width for this column
2057
2058
this.visible = true; //default visible state
2059
2060
this.component = null;
2061
2062
//initialize column
2063
if(this.definition.columns){
2064
2065
this.isGroup = true;
2066
2067
this.definition.columns.forEach((def, i) => {
2068
var newCol = new Column(def, this);
2069
this.attachColumn(newCol);
2070
});
2071
2072
this.checkColumnVisibility();
2073
}else {
2074
parent.registerColumnField(this);
2075
}
2076
2077
this._initialize();
2078
}
2079
2080
createElement (){
2081
var el = document.createElement("div");
2082
2083
el.classList.add("tabulator-col");
2084
el.setAttribute("role", "columnheader");
2085
el.setAttribute("aria-sort", "none");
2086
2087
switch(this.table.options.columnHeaderVertAlign){
2088
case "middle":
2089
el.style.justifyContent = "center";
2090
break;
2091
case "bottom":
2092
el.style.justifyContent = "flex-end";
2093
break;
2094
}
2095
2096
return el;
2097
}
2098
2099
createGroupElement (){
2100
var el = document.createElement("div");
2101
2102
el.classList.add("tabulator-col-group-cols");
2103
2104
return el;
2105
}
2106
2107
mapDefinitions(){
2108
var defaults = this.table.options.columnDefaults;
2109
2110
//map columnDefaults onto column definitions
2111
if(defaults){
2112
for(let key in defaults){
2113
if(typeof this.definition[key] === "undefined"){
2114
this.definition[key] = defaults[key];
2115
}
2116
}
2117
}
2118
2119
this.definition = this.table.columnManager.optionsList.generate(Column.defaultOptionList, this.definition);
2120
}
2121
2122
checkDefinition(){
2123
Object.keys(this.definition).forEach((key) => {
2124
if(Column.defaultOptionList.indexOf(key) === -1){
2125
console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key);
2126
}
2127
});
2128
}
2129
2130
setField(field){
2131
this.field = field;
2132
this.fieldStructure = field ? (this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field]) : [];
2133
this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData;
2134
this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData;
2135
}
2136
2137
//register column position with column manager
2138
registerColumnPosition(column){
2139
this.parent.registerColumnPosition(column);
2140
}
2141
2142
//register column position with column manager
2143
registerColumnField(column){
2144
this.parent.registerColumnField(column);
2145
}
2146
2147
//trigger position registration
2148
reRegisterPosition(){
2149
if(this.isGroup){
2150
this.columns.forEach(function(column){
2151
column.reRegisterPosition();
2152
});
2153
}else {
2154
this.registerColumnPosition(this);
2155
}
2156
}
2157
2158
//build header element
2159
_initialize(){
2160
var def = this.definition;
2161
2162
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
2163
2164
if(def.headerVertical){
2165
this.element.classList.add("tabulator-col-vertical");
2166
2167
if(def.headerVertical === "flip"){
2168
this.element.classList.add("tabulator-col-vertical-flip");
2169
}
2170
}
2171
2172
this.contentElement = this._buildColumnHeaderContent();
2173
2174
this.element.appendChild(this.contentElement);
2175
2176
if(this.isGroup){
2177
this._buildGroupHeader();
2178
}else {
2179
this._buildColumnHeader();
2180
}
2181
2182
this.dispatch("column-init", this);
2183
}
2184
2185
//build header element for header
2186
_buildColumnHeader(){
2187
var def = this.definition;
2188
2189
this.dispatch("column-layout", this);
2190
2191
//set column visibility
2192
if(typeof def.visible != "undefined"){
2193
if(def.visible){
2194
this.show(true);
2195
}else {
2196
this.hide(true);
2197
}
2198
}
2199
2200
//assign additional css classes to column header
2201
if(def.cssClass){
2202
var classNames = def.cssClass.split(" ");
2203
classNames.forEach((className) => {
2204
this.element.classList.add(className);
2205
});
2206
}
2207
2208
if(def.field){
2209
this.element.setAttribute("tabulator-field", def.field);
2210
}
2211
2212
//set min width if present
2213
this.setMinWidth(parseInt(def.minWidth));
2214
2215
if (def.maxInitialWidth) {
2216
this.maxInitialWidth = parseInt(def.maxInitialWidth);
2217
}
2218
2219
if(def.maxWidth){
2220
this.setMaxWidth(parseInt(def.maxWidth));
2221
}
2222
2223
this.reinitializeWidth();
2224
2225
//set horizontal text alignment
2226
this.hozAlign = this.definition.hozAlign;
2227
this.vertAlign = this.definition.vertAlign;
2228
2229
this.titleElement.style.textAlign = this.definition.headerHozAlign;
2230
}
2231
2232
_buildColumnHeaderContent(){
2233
var contentElement = document.createElement("div");
2234
contentElement.classList.add("tabulator-col-content");
2235
2236
this.titleHolderElement = document.createElement("div");
2237
this.titleHolderElement.classList.add("tabulator-col-title-holder");
2238
2239
contentElement.appendChild(this.titleHolderElement);
2240
2241
this.titleElement = this._buildColumnHeaderTitle();
2242
2243
this.titleHolderElement.appendChild(this.titleElement);
2244
2245
return contentElement;
2246
}
2247
2248
//build title element of column
2249
_buildColumnHeaderTitle(){
2250
var def = this.definition;
2251
2252
var titleHolderElement = document.createElement("div");
2253
titleHolderElement.classList.add("tabulator-col-title");
2254
2255
if(def.headerWordWrap){
2256
titleHolderElement.classList.add("tabulator-col-title-wrap");
2257
}
2258
2259
if(def.editableTitle){
2260
var titleElement = document.createElement("input");
2261
titleElement.classList.add("tabulator-title-editor");
2262
2263
titleElement.addEventListener("click", (e) => {
2264
e.stopPropagation();
2265
titleElement.focus();
2266
});
2267
2268
titleElement.addEventListener("change", () => {
2269
def.title = titleElement.value;
2270
this.dispatchExternal("columnTitleChanged", this.getComponent());
2271
});
2272
2273
titleHolderElement.appendChild(titleElement);
2274
2275
if(def.field){
2276
this.langBind("columns|" + def.field, (text) => {
2277
titleElement.value = text || (def.title || "&nbsp;");
2278
});
2279
}else {
2280
titleElement.value = def.title || "&nbsp;";
2281
}
2282
2283
}else {
2284
if(def.field){
2285
this.langBind("columns|" + def.field, (text) => {
2286
this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || "&nbsp;"));
2287
});
2288
}else {
2289
this._formatColumnHeaderTitle(titleHolderElement, def.title || "&nbsp;");
2290
}
2291
}
2292
2293
return titleHolderElement;
2294
}
2295
2296
_formatColumnHeaderTitle(el, title){
2297
var contents = this.chain("column-format", [this, title, el], null, () => {
2298
return title;
2299
});
2300
2301
switch(typeof contents){
2302
case "object":
2303
if(contents instanceof Node){
2304
el.appendChild(contents);
2305
}else {
2306
el.innerHTML = "";
2307
console.warn("Format Error - Title formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", contents);
2308
}
2309
break;
2310
case "undefined":
2311
el.innerHTML = "";
2312
break;
2313
default:
2314
el.innerHTML = contents;
2315
}
2316
}
2317
2318
//build header element for column group
2319
_buildGroupHeader(){
2320
this.element.classList.add("tabulator-col-group");
2321
this.element.setAttribute("role", "columngroup");
2322
this.element.setAttribute("aria-title", this.definition.title);
2323
2324
//asign additional css classes to column header
2325
if(this.definition.cssClass){
2326
var classNames = this.definition.cssClass.split(" ");
2327
classNames.forEach((className) => {
2328
this.element.classList.add(className);
2329
});
2330
}
2331
2332
this.titleElement.style.textAlign = this.definition.headerHozAlign;
2333
2334
this.element.appendChild(this.groupElement);
2335
}
2336
2337
//flat field lookup
2338
_getFlatData(data){
2339
return data[this.field];
2340
}
2341
2342
//nested field lookup
2343
_getNestedData(data){
2344
var dataObj = data,
2345
structure = this.fieldStructure,
2346
length = structure.length,
2347
output;
2348
2349
for(let i = 0; i < length; i++){
2350
2351
dataObj = dataObj[structure[i]];
2352
2353
output = dataObj;
2354
2355
if(!dataObj){
2356
break;
2357
}
2358
}
2359
2360
return output;
2361
}
2362
2363
//flat field set
2364
_setFlatData(data, value){
2365
if(this.field){
2366
data[this.field] = value;
2367
}
2368
}
2369
2370
//nested field set
2371
_setNestedData(data, value){
2372
var dataObj = data,
2373
structure = this.fieldStructure,
2374
length = structure.length;
2375
2376
for(let i = 0; i < length; i++){
2377
2378
if(i == length -1){
2379
dataObj[structure[i]] = value;
2380
}else {
2381
if(!dataObj[structure[i]]){
2382
if(typeof value !== "undefined"){
2383
dataObj[structure[i]] = {};
2384
}else {
2385
break;
2386
}
2387
}
2388
2389
dataObj = dataObj[structure[i]];
2390
}
2391
}
2392
}
2393
2394
//attach column to this group
2395
attachColumn(column){
2396
if(this.groupElement){
2397
this.columns.push(column);
2398
this.groupElement.appendChild(column.getElement());
2399
2400
column.columnRendered();
2401
}else {
2402
console.warn("Column Warning - Column being attached to another column instead of column group");
2403
}
2404
}
2405
2406
//vertically align header in column
2407
verticalAlign(alignment, height){
2408
2409
//calculate height of column header and group holder element
2410
var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : (height || this.parent.getHeadersElement().clientHeight);
2411
// var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : this.parent.getHeadersElement().clientHeight;
2412
2413
this.element.style.height = parentHeight + "px";
2414
2415
this.dispatch("column-height", this, this.element.style.height);
2416
2417
if(this.isGroup){
2418
this.groupElement.style.minHeight = (parentHeight - this.contentElement.offsetHeight) + "px";
2419
}
2420
2421
//vertically align cell contents
2422
// if(!this.isGroup && alignment !== "top"){
2423
// if(alignment === "bottom"){
2424
// this.element.style.paddingTop = (this.element.clientHeight - this.contentElement.offsetHeight) + "px";
2425
// }else{
2426
// this.element.style.paddingTop = ((this.element.clientHeight - this.contentElement.offsetHeight) / 2) + "px";
2427
// }
2428
// }
2429
2430
this.columns.forEach(function(column){
2431
column.verticalAlign(alignment);
2432
});
2433
}
2434
2435
//clear vertical alignment
2436
clearVerticalAlign(){
2437
this.element.style.paddingTop = "";
2438
this.element.style.height = "";
2439
this.element.style.minHeight = "";
2440
this.groupElement.style.minHeight = "";
2441
2442
this.columns.forEach(function(column){
2443
column.clearVerticalAlign();
2444
});
2445
2446
this.dispatch("column-height", this, "");
2447
}
2448
2449
//// Retrieve Column Information ////
2450
//return column header element
2451
getElement(){
2452
return this.element;
2453
}
2454
2455
//return column group element
2456
getGroupElement(){
2457
return this.groupElement;
2458
}
2459
2460
//return field name
2461
getField(){
2462
return this.field;
2463
}
2464
2465
getTitleDownload() {
2466
return this.titleDownload;
2467
}
2468
2469
//return the first column in a group
2470
getFirstColumn(){
2471
if(!this.isGroup){
2472
return this;
2473
}else {
2474
if(this.columns.length){
2475
return this.columns[0].getFirstColumn();
2476
}else {
2477
return false;
2478
}
2479
}
2480
}
2481
2482
//return the last column in a group
2483
getLastColumn(){
2484
if(!this.isGroup){
2485
return this;
2486
}else {
2487
if(this.columns.length){
2488
return this.columns[this.columns.length -1].getLastColumn();
2489
}else {
2490
return false;
2491
}
2492
}
2493
}
2494
2495
//return all columns in a group
2496
getColumns(traverse){
2497
var columns = [];
2498
2499
if(traverse){
2500
this.columns.forEach((column) => {
2501
columns.push(column);
2502
2503
columns = columns.concat(column.getColumns(true));
2504
});
2505
}else {
2506
columns = this.columns;
2507
}
2508
2509
return columns;
2510
}
2511
2512
//return all columns in a group
2513
getCells(){
2514
return this.cells;
2515
}
2516
2517
//retrieve the top column in a group of columns
2518
getTopColumn(){
2519
if(this.parent.isGroup){
2520
return this.parent.getTopColumn();
2521
}else {
2522
return this;
2523
}
2524
}
2525
2526
//return column definition object
2527
getDefinition(updateBranches){
2528
var colDefs = [];
2529
2530
if(this.isGroup && updateBranches){
2531
this.columns.forEach(function(column){
2532
colDefs.push(column.getDefinition(true));
2533
});
2534
2535
this.definition.columns = colDefs;
2536
}
2537
2538
return this.definition;
2539
}
2540
2541
//////////////////// Actions ////////////////////
2542
checkColumnVisibility(){
2543
var visible = false;
2544
2545
this.columns.forEach(function(column){
2546
if(column.visible){
2547
visible = true;
2548
}
2549
});
2550
2551
if(visible){
2552
this.show();
2553
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
2554
}else {
2555
this.hide();
2556
}
2557
}
2558
2559
//show column
2560
show(silent, responsiveToggle){
2561
if(!this.visible){
2562
this.visible = true;
2563
2564
this.element.style.display = "";
2565
2566
if(this.parent.isGroup){
2567
this.parent.checkColumnVisibility();
2568
}
2569
2570
this.cells.forEach(function(cell){
2571
cell.show();
2572
});
2573
2574
if(!this.isGroup && this.width === null){
2575
this.reinitializeWidth();
2576
}
2577
2578
this.table.columnManager.verticalAlignHeaders();
2579
2580
this.dispatch("column-show", this, responsiveToggle);
2581
2582
if(!silent){
2583
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), true);
2584
}
2585
2586
if(this.parent.isGroup){
2587
this.parent.matchChildWidths();
2588
}
2589
2590
if(!this.silent){
2591
this.table.columnManager.rerenderColumns();
2592
}
2593
}
2594
}
2595
2596
//hide column
2597
hide(silent, responsiveToggle){
2598
if(this.visible){
2599
this.visible = false;
2600
2601
this.element.style.display = "none";
2602
2603
this.table.columnManager.verticalAlignHeaders();
2604
2605
if(this.parent.isGroup){
2606
this.parent.checkColumnVisibility();
2607
}
2608
2609
this.cells.forEach(function(cell){
2610
cell.hide();
2611
});
2612
2613
this.dispatch("column-hide", this, responsiveToggle);
2614
2615
if(!silent){
2616
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
2617
}
2618
2619
if(this.parent.isGroup){
2620
this.parent.matchChildWidths();
2621
}
2622
2623
if(!this.silent){
2624
this.table.columnManager.rerenderColumns();
2625
}
2626
}
2627
}
2628
2629
matchChildWidths(){
2630
var childWidth = 0;
2631
2632
if(this.contentElement && this.columns.length){
2633
this.columns.forEach(function(column){
2634
if(column.visible){
2635
childWidth += column.getWidth();
2636
}
2637
});
2638
2639
this.contentElement.style.maxWidth = (childWidth - 1) + "px";
2640
2641
if(this.parent.isGroup){
2642
this.parent.matchChildWidths();
2643
}
2644
}
2645
}
2646
2647
removeChild(child){
2648
var index = this.columns.indexOf(child);
2649
2650
if(index > -1){
2651
this.columns.splice(index, 1);
2652
}
2653
2654
if(!this.columns.length){
2655
this.delete();
2656
}
2657
}
2658
2659
setWidth(width){
2660
this.widthFixed = true;
2661
this.setWidthActual(width);
2662
}
2663
2664
setWidthActual(width){
2665
if(isNaN(width)){
2666
width = Math.floor((this.table.element.clientWidth/100) * parseInt(width));
2667
}
2668
2669
width = Math.max(this.minWidth, width);
2670
2671
if(this.maxWidth){
2672
width = Math.min(this.maxWidth, width);
2673
}
2674
2675
this.width = width;
2676
this.widthStyled = width ? width + "px" : "";
2677
2678
this.element.style.width = this.widthStyled;
2679
2680
if(!this.isGroup){
2681
this.cells.forEach(function(cell){
2682
cell.setWidth();
2683
});
2684
}
2685
2686
if(this.parent.isGroup){
2687
this.parent.matchChildWidths();
2688
}
2689
2690
this.dispatch("column-width", this);
2691
}
2692
2693
checkCellHeights(){
2694
var rows = [];
2695
2696
this.cells.forEach(function(cell){
2697
if(cell.row.heightInitialized){
2698
if(cell.row.getElement().offsetParent !== null){
2699
rows.push(cell.row);
2700
cell.row.clearCellHeight();
2701
}else {
2702
cell.row.heightInitialized = false;
2703
}
2704
}
2705
});
2706
2707
rows.forEach(function(row){
2708
row.calcHeight();
2709
});
2710
2711
rows.forEach(function(row){
2712
row.setCellHeight();
2713
});
2714
}
2715
2716
getWidth(){
2717
var width = 0;
2718
2719
if(this.isGroup){
2720
this.columns.forEach(function(column){
2721
if(column.visible){
2722
width += column.getWidth();
2723
}
2724
});
2725
}else {
2726
width = this.width;
2727
}
2728
2729
return width;
2730
}
2731
2732
getLeftOffset(){
2733
var offset = this.element.offsetLeft;
2734
2735
if(this.parent.isGroup){
2736
offset += this.parent.getLeftOffset();
2737
}
2738
2739
return offset;
2740
}
2741
2742
getHeight(){
2743
return Math.ceil(this.element.getBoundingClientRect().height);
2744
}
2745
2746
setMinWidth(minWidth){
2747
if(this.maxWidth && minWidth > this.maxWidth){
2748
minWidth = this.maxWidth;
2749
2750
console.warn("the minWidth ("+ minWidth + "px) for column '" + this.field + "' cannot be bigger that its maxWidth ("+ this.maxWidthStyled + ")");
2751
}
2752
2753
this.minWidth = minWidth;
2754
this.minWidthStyled = minWidth ? minWidth + "px" : "";
2755
2756
this.element.style.minWidth = this.minWidthStyled;
2757
2758
this.cells.forEach(function(cell){
2759
cell.setMinWidth();
2760
});
2761
}
2762
2763
setMaxWidth(maxWidth){
2764
if(this.minWidth && maxWidth < this.minWidth){
2765
maxWidth = this.minWidth;
2766
2767
console.warn("the maxWidth ("+ maxWidth + "px) for column '" + this.field + "' cannot be smaller that its minWidth ("+ this.minWidthStyled + ")");
2768
}
2769
2770
this.maxWidth = maxWidth;
2771
this.maxWidthStyled = maxWidth ? maxWidth + "px" : "";
2772
2773
this.element.style.maxWidth = this.maxWidthStyled;
2774
2775
this.cells.forEach(function(cell){
2776
cell.setMaxWidth();
2777
});
2778
}
2779
2780
delete(){
2781
return new Promise((resolve, reject) => {
2782
if(this.isGroup){
2783
this.columns.forEach(function(column){
2784
column.delete();
2785
});
2786
}
2787
2788
this.dispatch("column-delete", this);
2789
2790
var cellCount = this.cells.length;
2791
2792
for(let i = 0; i < cellCount; i++){
2793
this.cells[0].delete();
2794
}
2795
2796
if(this.element.parentNode){
2797
this.element.parentNode.removeChild(this.element);
2798
}
2799
2800
this.element = false;
2801
this.contentElement = false;
2802
this.titleElement = false;
2803
this.groupElement = false;
2804
2805
if(this.parent.isGroup){
2806
this.parent.removeChild(this);
2807
}
2808
2809
this.table.columnManager.deregisterColumn(this);
2810
2811
this.table.columnManager.rerenderColumns(true);
2812
2813
resolve();
2814
});
2815
}
2816
2817
columnRendered(){
2818
if(this.titleFormatterRendered){
2819
this.titleFormatterRendered();
2820
}
2821
2822
this.dispatch("column-rendered", this);
2823
}
2824
2825
//////////////// Cell Management /////////////////
2826
//generate cell for this column
2827
generateCell(row){
2828
var cell = new Cell(this, row);
2829
2830
this.cells.push(cell);
2831
2832
return cell;
2833
}
2834
2835
nextColumn(){
2836
var index = this.table.columnManager.findColumnIndex(this);
2837
return index > -1 ? this._nextVisibleColumn(index + 1) : false;
2838
}
2839
2840
_nextVisibleColumn(index){
2841
var column = this.table.columnManager.getColumnByIndex(index);
2842
return !column || column.visible ? column : this._nextVisibleColumn(index + 1);
2843
}
2844
2845
prevColumn(){
2846
var index = this.table.columnManager.findColumnIndex(this);
2847
return index > -1 ? this._prevVisibleColumn(index - 1) : false;
2848
}
2849
2850
_prevVisibleColumn(index){
2851
var column = this.table.columnManager.getColumnByIndex(index);
2852
return !column || column.visible ? column : this._prevVisibleColumn(index - 1);
2853
}
2854
2855
reinitializeWidth(force){
2856
this.widthFixed = false;
2857
2858
//set width if present
2859
if(typeof this.definition.width !== "undefined" && !force){
2860
// maxInitialWidth ignored here as width specified
2861
this.setWidth(this.definition.width);
2862
}
2863
2864
this.dispatch("column-width-fit-before", this);
2865
2866
this.fitToData(force);
2867
2868
this.dispatch("column-width-fit-after", this);
2869
}
2870
2871
//set column width to maximum cell width for non group columns
2872
fitToData(force){
2873
if(this.isGroup){
2874
return;
2875
}
2876
2877
if(!this.widthFixed){
2878
this.element.style.width = "";
2879
2880
this.cells.forEach((cell) => {
2881
cell.clearWidth();
2882
});
2883
}
2884
2885
var maxWidth = this.element.offsetWidth;
2886
2887
if(!this.width || !this.widthFixed){
2888
this.cells.forEach((cell) => {
2889
var width = cell.getWidth();
2890
2891
if(width > maxWidth){
2892
maxWidth = width;
2893
}
2894
});
2895
2896
if(maxWidth){
2897
var setTo = maxWidth + 1;
2898
if (this.maxInitialWidth && !force) {
2899
setTo = Math.min(setTo, this.maxInitialWidth);
2900
}
2901
this.setWidthActual(setTo);
2902
}
2903
}
2904
}
2905
2906
updateDefinition(updates){
2907
var definition;
2908
2909
if(!this.isGroup){
2910
if(!this.parent.isGroup){
2911
definition = Object.assign({}, this.getDefinition());
2912
definition = Object.assign(definition, updates);
2913
2914
return this.table.columnManager.addColumn(definition, false, this)
2915
.then((column) => {
2916
2917
if(definition.field == this.field){
2918
this.field = false; //clear field name to prevent deletion of duplicate column from arrays
2919
}
2920
2921
return this.delete()
2922
.then(() => {
2923
return column.getComponent();
2924
});
2925
2926
});
2927
}else {
2928
console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
2929
return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
2930
}
2931
}else {
2932
console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
2933
return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
2934
}
2935
}
2936
2937
deleteCell(cell){
2938
var index = this.cells.indexOf(cell);
2939
2940
if(index > -1){
2941
this.cells.splice(index, 1);
2942
}
2943
}
2944
2945
//////////////// Object Generation /////////////////
2946
getComponent(){
2947
if(!this.component){
2948
this.component = new ColumnComponent(this);
2949
}
2950
2951
return this.component;
2952
}
2953
}
2954
2955
Column.defaultOptionList = defaultColumnOptions;
2956
2957
//public row object
2958
class RowComponent {
2959
2960
constructor (row){
2961
this._row = row;
2962
2963
return new Proxy(this, {
2964
get: function(target, name, receiver) {
2965
if (typeof target[name] !== "undefined") {
2966
return target[name];
2967
}else {
2968
return target._row.table.componentFunctionBinder.handle("row", target._row, name);
2969
}
2970
}
2971
});
2972
}
2973
2974
getData(transform){
2975
return this._row.getData(transform);
2976
}
2977
2978
getElement(){
2979
return this._row.getElement();
2980
}
2981
2982
getCells(){
2983
var cells = [];
2984
2985
this._row.getCells().forEach(function(cell){
2986
cells.push(cell.getComponent());
2987
});
2988
2989
return cells;
2990
}
2991
2992
getCell(column){
2993
var cell = this._row.getCell(column);
2994
return cell ? cell.getComponent() : false;
2995
}
2996
2997
getIndex(){
2998
return this._row.getData("data")[this._row.table.options.index];
2999
}
3000
3001
getPosition(){
3002
return this._row.getPosition();
3003
}
3004
3005
watchPosition(callback){
3006
return this._row.watchPosition(callback);
3007
}
3008
3009
delete(){
3010
return this._row.delete();
3011
}
3012
3013
scrollTo(position, ifVisible){
3014
return this._row.table.rowManager.scrollToRow(this._row, position, ifVisible);
3015
}
3016
3017
move(to, after){
3018
this._row.moveToRow(to, after);
3019
}
3020
3021
update(data){
3022
return this._row.updateData(data);
3023
}
3024
3025
normalizeHeight(){
3026
this._row.normalizeHeight(true);
3027
}
3028
3029
_getSelf(){
3030
return this._row;
3031
}
3032
3033
reformat(){
3034
return this._row.reinitialize();
3035
}
3036
3037
getTable(){
3038
return this._row.table;
3039
}
3040
3041
getNextRow(){
3042
var row = this._row.nextRow();
3043
return row ? row.getComponent() : row;
3044
}
3045
3046
getPrevRow(){
3047
var row = this._row.prevRow();
3048
return row ? row.getComponent() : row;
3049
}
3050
}
3051
3052
class Row extends CoreFeature{
3053
constructor (data, parent, type = "row"){
3054
super(parent.table);
3055
3056
this.parent = parent;
3057
this.data = {};
3058
this.type = type; //type of element
3059
this.element = false;
3060
this.modules = {}; //hold module variables;
3061
this.cells = [];
3062
this.height = 0; //hold element height
3063
this.heightStyled = ""; //hold element height pre-styled to improve render efficiency
3064
this.manualHeight = false; //user has manually set row height
3065
this.outerHeight = 0; //hold elements outer height
3066
this.initialized = false; //element has been rendered
3067
this.heightInitialized = false; //element has resized cells to fit
3068
this.position = 0; //store position of element in row list
3069
this.positionWatchers = [];
3070
3071
this.component = null;
3072
3073
this.created = false;
3074
3075
this.setData(data);
3076
}
3077
3078
create(){
3079
if(!this.created){
3080
this.created = true;
3081
this.generateElement();
3082
}
3083
}
3084
3085
createElement (){
3086
var el = document.createElement("div");
3087
3088
el.classList.add("tabulator-row");
3089
el.setAttribute("role", "row");
3090
3091
this.element = el;
3092
}
3093
3094
getElement(){
3095
this.create();
3096
return this.element;
3097
}
3098
3099
detachElement(){
3100
if (this.element && this.element.parentNode){
3101
this.element.parentNode.removeChild(this.element);
3102
}
3103
}
3104
3105
generateElement(){
3106
this.createElement();
3107
this.dispatch("row-init", this);
3108
}
3109
3110
generateCells(){
3111
this.cells = this.table.columnManager.generateCells(this);
3112
}
3113
3114
//functions to setup on first render
3115
initialize(force, inFragment){
3116
this.create();
3117
3118
if(!this.initialized || force){
3119
3120
this.deleteCells();
3121
3122
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
3123
3124
this.dispatch("row-layout-before", this);
3125
3126
this.generateCells();
3127
3128
this.initialized = true;
3129
3130
this.table.columnManager.renderer.renderRowCells(this, inFragment);
3131
3132
if(force){
3133
this.normalizeHeight();
3134
}
3135
3136
this.dispatch("row-layout", this);
3137
3138
if(this.table.options.rowFormatter){
3139
this.table.options.rowFormatter(this.getComponent());
3140
}
3141
3142
this.dispatch("row-layout-after", this);
3143
}else {
3144
this.table.columnManager.renderer.rerenderRowCells(this, inFragment);
3145
}
3146
}
3147
3148
rendered(){
3149
this.cells.forEach((cell) => {
3150
cell.cellRendered();
3151
});
3152
}
3153
3154
reinitializeHeight(){
3155
this.heightInitialized = false;
3156
3157
if(this.element && this.element.offsetParent !== null){
3158
this.normalizeHeight(true);
3159
}
3160
}
3161
3162
deinitialize(){
3163
this.initialized = false;
3164
}
3165
3166
deinitializeHeight(){
3167
this.heightInitialized = false;
3168
}
3169
3170
reinitialize(children){
3171
this.initialized = false;
3172
this.heightInitialized = false;
3173
3174
if(!this.manualHeight){
3175
this.height = 0;
3176
this.heightStyled = "";
3177
}
3178
3179
if(this.element && this.element.offsetParent !== null){
3180
this.initialize(true);
3181
}
3182
3183
this.dispatch("row-relayout", this);
3184
}
3185
3186
//get heights when doing bulk row style calcs in virtual DOM
3187
calcHeight(force){
3188
var maxHeight = 0,
3189
minHeight;
3190
3191
if(this.table.options.rowHeight){
3192
this.height = this.table.options.rowHeight;
3193
}else {
3194
minHeight = this.table.options.resizableRows ? this.element.clientHeight : 0;
3195
3196
this.cells.forEach(function(cell){
3197
var height = cell.getHeight();
3198
if(height > maxHeight){
3199
maxHeight = height;
3200
}
3201
});
3202
3203
if(force){
3204
this.height = Math.max(maxHeight, minHeight);
3205
}else {
3206
this.height = this.manualHeight ? this.height : Math.max(maxHeight, minHeight);
3207
}
3208
}
3209
3210
this.heightStyled = this.height ? this.height + "px" : "";
3211
this.outerHeight = this.element.offsetHeight;
3212
}
3213
3214
//set of cells
3215
setCellHeight(){
3216
this.cells.forEach(function(cell){
3217
cell.setHeight();
3218
});
3219
3220
this.heightInitialized = true;
3221
}
3222
3223
clearCellHeight(){
3224
this.cells.forEach(function(cell){
3225
cell.clearHeight();
3226
});
3227
}
3228
3229
//normalize the height of elements in the row
3230
normalizeHeight(force){
3231
if(force && !this.table.options.rowHeight){
3232
this.clearCellHeight();
3233
}
3234
3235
this.calcHeight(force);
3236
3237
this.setCellHeight();
3238
}
3239
3240
//set height of rows
3241
setHeight(height, force){
3242
if(this.height != height || force){
3243
3244
this.manualHeight = true;
3245
3246
this.height = height;
3247
this.heightStyled = height ? height + "px" : "";
3248
3249
this.setCellHeight();
3250
3251
// this.outerHeight = this.element.outerHeight();
3252
this.outerHeight = this.element.offsetHeight;
3253
}
3254
}
3255
3256
//return rows outer height
3257
getHeight(){
3258
return this.outerHeight;
3259
}
3260
3261
//return rows outer Width
3262
getWidth(){
3263
return this.element.offsetWidth;
3264
}
3265
3266
//////////////// Cell Management /////////////////
3267
deleteCell(cell){
3268
var index = this.cells.indexOf(cell);
3269
3270
if(index > -1){
3271
this.cells.splice(index, 1);
3272
}
3273
}
3274
3275
//////////////// Data Management /////////////////
3276
setData(data){
3277
this.data = this.chain("row-data-init-before", [this, data], undefined, data);
3278
3279
this.dispatch("row-data-init-after", this);
3280
}
3281
3282
//update the rows data
3283
updateData(updatedData){
3284
var visible = this.element && Helpers.elVisible(this.element),
3285
tempData = {},
3286
newRowData;
3287
3288
return new Promise((resolve, reject) => {
3289
3290
if(typeof updatedData === "string"){
3291
updatedData = JSON.parse(updatedData);
3292
}
3293
3294
this.dispatch("row-data-save-before", this);
3295
3296
if(this.subscribed("row-data-changing")){
3297
tempData = Object.assign(tempData, this.data);
3298
tempData = Object.assign(tempData, updatedData);
3299
}
3300
3301
newRowData = this.chain("row-data-changing", [this, tempData, updatedData], null, updatedData);
3302
3303
//set data
3304
for (let attrname in newRowData) {
3305
this.data[attrname] = newRowData[attrname];
3306
}
3307
3308
this.dispatch("row-data-save-after", this);
3309
3310
//update affected cells only
3311
for (let attrname in updatedData) {
3312
3313
let columns = this.table.columnManager.getColumnsByFieldRoot(attrname);
3314
3315
columns.forEach((column) => {
3316
let cell = this.getCell(column.getField());
3317
3318
if(cell){
3319
let value = column.getFieldValue(newRowData);
3320
if(cell.getValue() !== value){
3321
cell.setValueProcessData(value);
3322
3323
if(visible){
3324
cell.cellRendered();
3325
}
3326
}
3327
}
3328
});
3329
}
3330
3331
//Partial reinitialization if visible
3332
if(visible){
3333
this.normalizeHeight(true);
3334
3335
if(this.table.options.rowFormatter){
3336
this.table.options.rowFormatter(this.getComponent());
3337
}
3338
}else {
3339
this.initialized = false;
3340
this.height = 0;
3341
this.heightStyled = "";
3342
}
3343
3344
this.dispatch("row-data-changed", this, visible, updatedData);
3345
3346
//this.reinitialize();
3347
3348
this.dispatchExternal("rowUpdated", this.getComponent());
3349
3350
if(this.subscribedExternal("dataChanged")){
3351
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
3352
}
3353
3354
resolve();
3355
});
3356
}
3357
3358
getData(transform){
3359
if(transform){
3360
return this.chain("row-data-retrieve", [this, transform], null, this.data);
3361
}
3362
3363
return this.data;
3364
}
3365
3366
getCell(column){
3367
var match = false;
3368
3369
column = this.table.columnManager.findColumn(column);
3370
3371
if(!this.initialized && this.cells.length === 0){
3372
this.generateCells();
3373
}
3374
3375
match = this.cells.find(function(cell){
3376
return cell.column === column;
3377
});
3378
3379
return match;
3380
}
3381
3382
getCellIndex(findCell){
3383
return this.cells.findIndex(function(cell){
3384
return cell === findCell;
3385
});
3386
}
3387
3388
findCell(subject){
3389
return this.cells.find((cell) => {
3390
return cell.element === subject;
3391
});
3392
}
3393
3394
getCells(){
3395
if(!this.initialized && this.cells.length === 0){
3396
this.generateCells();
3397
}
3398
3399
return this.cells;
3400
}
3401
3402
nextRow(){
3403
var row = this.table.rowManager.nextDisplayRow(this, true);
3404
return row || false;
3405
}
3406
3407
prevRow(){
3408
var row = this.table.rowManager.prevDisplayRow(this, true);
3409
return row || false;
3410
}
3411
3412
moveToRow(to, before){
3413
var toRow = this.table.rowManager.findRow(to);
3414
3415
if(toRow){
3416
this.table.rowManager.moveRowActual(this, toRow, !before);
3417
this.table.rowManager.refreshActiveData("display", false, true);
3418
}else {
3419
console.warn("Move Error - No matching row found:", to);
3420
}
3421
}
3422
3423
///////////////////// Actions /////////////////////
3424
delete(){
3425
this.dispatch("row-delete", this);
3426
3427
this.deleteActual();
3428
3429
return Promise.resolve();
3430
}
3431
3432
deleteActual(blockRedraw){
3433
this.detachModules();
3434
3435
this.table.rowManager.deleteRow(this, blockRedraw);
3436
3437
this.deleteCells();
3438
3439
this.initialized = false;
3440
this.heightInitialized = false;
3441
this.element = false;
3442
3443
this.dispatch("row-deleted", this);
3444
}
3445
3446
detachModules(){
3447
this.dispatch("row-deleting", this);
3448
}
3449
3450
deleteCells(){
3451
var cellCount = this.cells.length;
3452
3453
for(let i = 0; i < cellCount; i++){
3454
this.cells[0].delete();
3455
}
3456
}
3457
3458
wipe(){
3459
this.detachModules();
3460
this.deleteCells();
3461
3462
if(this.element){
3463
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
3464
3465
if(this.element.parentNode){
3466
this.element.parentNode.removeChild(this.element);
3467
}
3468
}
3469
3470
this.element = false;
3471
this.modules = {};
3472
}
3473
3474
isDisplayed(){
3475
return this.table.rowManager.getDisplayRows().includes(this);
3476
}
3477
3478
getPosition(){
3479
return this.isDisplayed() ? this.position : false;
3480
}
3481
3482
setPosition(position){
3483
if(position != this.position){
3484
this.position = position;
3485
3486
this.positionWatchers.forEach((callback) => {
3487
callback(this.position);
3488
});
3489
}
3490
}
3491
3492
watchPosition(callback){
3493
this.positionWatchers.push(callback);
3494
3495
callback(this.position);
3496
}
3497
3498
getGroup(){
3499
return this.modules.group || false;
3500
}
3501
3502
//////////////// Object Generation /////////////////
3503
getComponent(){
3504
if(!this.component){
3505
this.component = new RowComponent(this);
3506
}
3507
3508
return this.component;
3509
}
3510
}
3511
3512
var defaultCalculations = {
3513
"avg":function(values, data, calcParams){
3514
var output = 0,
3515
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : 2;
3516
3517
if(values.length){
3518
output = values.reduce(function(sum, value){
3519
return Number(sum) + Number(value);
3520
});
3521
3522
output = output / values.length;
3523
3524
output = precision !== false ? output.toFixed(precision) : output;
3525
}
3526
3527
return parseFloat(output).toString();
3528
},
3529
"max":function(values, data, calcParams){
3530
var output = null,
3531
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
3532
3533
values.forEach(function(value){
3534
3535
value = Number(value);
3536
3537
if(value > output || output === null){
3538
output = value;
3539
}
3540
});
3541
3542
return output !== null ? (precision !== false ? output.toFixed(precision) : output) : "";
3543
},
3544
"min":function(values, data, calcParams){
3545
var output = null,
3546
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
3547
3548
values.forEach(function(value){
3549
3550
value = Number(value);
3551
3552
if(value < output || output === null){
3553
output = value;
3554
}
3555
});
3556
3557
return output !== null ? (precision !== false ? output.toFixed(precision) : output) : "";
3558
},
3559
"sum":function(values, data, calcParams){
3560
var output = 0,
3561
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
3562
3563
if(values.length){
3564
values.forEach(function(value){
3565
value = Number(value);
3566
3567
output += !isNaN(value) ? Number(value) : 0;
3568
});
3569
}
3570
3571
return precision !== false ? output.toFixed(precision) : output;
3572
},
3573
"concat":function(values, data, calcParams){
3574
var output = 0;
3575
3576
if(values.length){
3577
output = values.reduce(function(sum, value){
3578
return String(sum) + String(value);
3579
});
3580
}
3581
3582
return output;
3583
},
3584
"count":function(values, data, calcParams){
3585
var output = 0;
3586
3587
if(values.length){
3588
values.forEach(function(value){
3589
if(value){
3590
output ++;
3591
}
3592
});
3593
}
3594
3595
return output;
3596
},
3597
"unique":function(values, data, calcParams){
3598
var unique = values.filter((value, index) => {
3599
return (values || value === 0) && values.indexOf(value) === index;
3600
});
3601
3602
return unique.length;
3603
},
3604
};
3605
3606
class ColumnCalcs extends Module{
3607
3608
constructor(table){
3609
super(table);
3610
3611
this.topCalcs = [];
3612
this.botCalcs = [];
3613
this.genColumn = false;
3614
this.topElement = this.createElement();
3615
this.botElement = this.createElement();
3616
this.topRow = false;
3617
this.botRow = false;
3618
this.topInitialized = false;
3619
this.botInitialized = false;
3620
3621
this.blocked = false;
3622
this.recalcAfterBlock = false;
3623
3624
this.registerTableOption("columnCalcs", true);
3625
3626
this.registerColumnOption("topCalc");
3627
this.registerColumnOption("topCalcParams");
3628
this.registerColumnOption("topCalcFormatter");
3629
this.registerColumnOption("topCalcFormatterParams");
3630
this.registerColumnOption("bottomCalc");
3631
this.registerColumnOption("bottomCalcParams");
3632
this.registerColumnOption("bottomCalcFormatter");
3633
this.registerColumnOption("bottomCalcFormatterParams");
3634
}
3635
3636
createElement (){
3637
var el = document.createElement("div");
3638
el.classList.add("tabulator-calcs-holder");
3639
return el;
3640
}
3641
3642
initialize(){
3643
this.genColumn = new Column({field:"value"}, this);
3644
3645
this.subscribe("cell-value-changed", this.cellValueChanged.bind(this));
3646
this.subscribe("column-init", this.initializeColumnCheck.bind(this));
3647
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
3648
this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this));
3649
this.subscribe("row-added", this.rowsUpdated.bind(this));
3650
this.subscribe("column-moved", this.recalcActiveRows.bind(this));
3651
this.subscribe("column-add", this.recalcActiveRows.bind(this));
3652
this.subscribe("data-refreshed", this.recalcActiveRowsRefresh.bind(this));
3653
this.subscribe("table-redraw", this.tableRedraw.bind(this));
3654
this.subscribe("rows-visible", this.visibleRows.bind(this));
3655
this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this));
3656
3657
this.subscribe("redraw-blocked", this.blockRedraw.bind(this));
3658
this.subscribe("redraw-restored", this.restoreRedraw.bind(this));
3659
3660
this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this));
3661
this.subscribe("column-resized", this.resizeHolderWidth.bind(this));
3662
this.subscribe("column-show", this.resizeHolderWidth.bind(this));
3663
this.subscribe("column-hide", this.resizeHolderWidth.bind(this));
3664
3665
this.registerTableFunction("getCalcResults", this.getResults.bind(this));
3666
this.registerTableFunction("recalc", this.userRecalc.bind(this));
3667
3668
3669
this.resizeHolderWidth();
3670
}
3671
3672
resizeHolderWidth(){
3673
this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px";
3674
}
3675
3676
3677
tableRedraw(force){
3678
this.recalc(this.table.rowManager.activeRows);
3679
3680
if(force){
3681
this.redraw();
3682
}
3683
}
3684
3685
blockRedraw(){
3686
this.blocked = true;
3687
this.recalcAfterBlock = false;
3688
}
3689
3690
3691
restoreRedraw(){
3692
this.blocked = false;
3693
3694
if(this.recalcAfterBlock){
3695
this.recalcAfterBlock = false;
3696
this.recalcActiveRowsRefresh();
3697
}
3698
}
3699
3700
///////////////////////////////////
3701
///////// Table Functions /////////
3702
///////////////////////////////////
3703
userRecalc(){
3704
this.recalc(this.table.rowManager.activeRows);
3705
}
3706
3707
///////////////////////////////////
3708
///////// Internal Logic //////////
3709
///////////////////////////////////
3710
3711
blockCheck(){
3712
if(this.blocked){
3713
this.recalcAfterBlock = true;
3714
}
3715
3716
return this.blocked;
3717
}
3718
3719
visibleRows(viewable, rows){
3720
if(this.topRow){
3721
rows.unshift(this.topRow);
3722
}
3723
3724
if(this.botRow){
3725
rows.push(this.botRow);
3726
}
3727
3728
return rows;
3729
}
3730
3731
rowsUpdated(row){
3732
if(this.table.options.groupBy){
3733
this.recalcRowGroup(row);
3734
}else {
3735
this.recalcActiveRows();
3736
}
3737
}
3738
3739
recalcActiveRowsRefresh(){
3740
if(this.table.options.groupBy && this.table.options.dataTreeStartExpanded && this.table.options.dataTree){
3741
this.recalcAll();
3742
}else {
3743
this.recalcActiveRows();
3744
}
3745
}
3746
3747
recalcActiveRows(){
3748
this.recalc(this.table.rowManager.activeRows);
3749
}
3750
3751
cellValueChanged(cell){
3752
if(cell.column.definition.topCalc || cell.column.definition.bottomCalc){
3753
if(this.table.options.groupBy){
3754
if(this.table.options.columnCalcs == "table" || this.table.options.columnCalcs == "both"){
3755
this.recalcActiveRows();
3756
}
3757
3758
if(this.table.options.columnCalcs != "table"){
3759
this.recalcRowGroup(cell.row);
3760
}
3761
}else {
3762
this.recalcActiveRows();
3763
}
3764
}
3765
}
3766
3767
initializeColumnCheck(column){
3768
if(column.definition.topCalc || column.definition.bottomCalc){
3769
this.initializeColumn(column);
3770
}
3771
}
3772
3773
//initialize column calcs
3774
initializeColumn(column){
3775
var def = column.definition;
3776
3777
var config = {
3778
topCalcParams:def.topCalcParams || {},
3779
botCalcParams:def.bottomCalcParams || {},
3780
};
3781
3782
if(def.topCalc){
3783
3784
switch(typeof def.topCalc){
3785
case "string":
3786
if(ColumnCalcs.calculations[def.topCalc]){
3787
config.topCalc = ColumnCalcs.calculations[def.topCalc];
3788
}else {
3789
console.warn("Column Calc Error - No such calculation found, ignoring: ", def.topCalc);
3790
}
3791
break;
3792
3793
case "function":
3794
config.topCalc = def.topCalc;
3795
break;
3796
3797
}
3798
3799
if(config.topCalc){
3800
column.modules.columnCalcs = config;
3801
this.topCalcs.push(column);
3802
3803
if(this.table.options.columnCalcs != "group"){
3804
this.initializeTopRow();
3805
}
3806
}
3807
3808
}
3809
3810
if(def.bottomCalc){
3811
switch(typeof def.bottomCalc){
3812
case "string":
3813
if(ColumnCalcs.calculations[def.bottomCalc]){
3814
config.botCalc = ColumnCalcs.calculations[def.bottomCalc];
3815
}else {
3816
console.warn("Column Calc Error - No such calculation found, ignoring: ", def.bottomCalc);
3817
}
3818
break;
3819
3820
case "function":
3821
config.botCalc = def.bottomCalc;
3822
break;
3823
3824
}
3825
3826
if(config.botCalc){
3827
column.modules.columnCalcs = config;
3828
this.botCalcs.push(column);
3829
3830
if(this.table.options.columnCalcs != "group"){
3831
this.initializeBottomRow();
3832
}
3833
}
3834
}
3835
3836
}
3837
3838
//dummy functions to handle being mock column manager
3839
registerColumnField(){}
3840
3841
removeCalcs(){
3842
var changed = false;
3843
3844
if(this.topInitialized){
3845
this.topInitialized = false;
3846
this.topElement.parentNode.removeChild(this.topElement);
3847
changed = true;
3848
}
3849
3850
if(this.botInitialized){
3851
this.botInitialized = false;
3852
this.footerRemove(this.botElement);
3853
changed = true;
3854
}
3855
3856
if(changed){
3857
this.table.rowManager.adjustTableSize();
3858
}
3859
}
3860
3861
reinitializeCalcs(){
3862
if(this.topCalcs.length){
3863
this.initializeTopRow();
3864
}
3865
3866
if(this.botCalcs.length){
3867
this.initializeBottomRow();
3868
}
3869
}
3870
3871
initializeTopRow(){
3872
if(!this.topInitialized){
3873
this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling);
3874
this.topInitialized = true;
3875
}
3876
}
3877
3878
initializeBottomRow(){
3879
if(!this.botInitialized){
3880
this.footerPrepend(this.botElement);
3881
this.botInitialized = true;
3882
}
3883
}
3884
3885
scrollHorizontal(left){
3886
if(this.botInitialized && this.botRow){
3887
this.botElement.scrollLeft = left;
3888
}
3889
}
3890
3891
recalc(rows){
3892
var data, row;
3893
3894
if(!this.blockCheck()){
3895
if(this.topInitialized || this.botInitialized){
3896
data = this.rowsToData(rows);
3897
3898
if(this.topInitialized){
3899
if(this.topRow){
3900
this.topRow.deleteCells();
3901
}
3902
3903
row = this.generateRow("top", data);
3904
this.topRow = row;
3905
while(this.topElement.firstChild) this.topElement.removeChild(this.topElement.firstChild);
3906
this.topElement.appendChild(row.getElement());
3907
row.initialize(true);
3908
}
3909
3910
if(this.botInitialized){
3911
if(this.botRow){
3912
this.botRow.deleteCells();
3913
}
3914
3915
row = this.generateRow("bottom", data);
3916
this.botRow = row;
3917
while(this.botElement.firstChild) this.botElement.removeChild(this.botElement.firstChild);
3918
this.botElement.appendChild(row.getElement());
3919
row.initialize(true);
3920
}
3921
3922
this.table.rowManager.adjustTableSize();
3923
3924
//set resizable handles
3925
if(this.table.modExists("frozenColumns")){
3926
this.table.modules.frozenColumns.layout();
3927
}
3928
}
3929
}
3930
}
3931
3932
recalcRowGroup(row){
3933
this.recalcGroup(this.table.modules.groupRows.getRowGroup(row));
3934
}
3935
3936
recalcAll(){
3937
if(this.topCalcs.length || this.botCalcs.length){
3938
if(this.table.options.columnCalcs !== "group"){
3939
this.recalcActiveRows();
3940
}
3941
3942
if(this.table.options.groupBy && this.table.options.columnCalcs !== "table"){
3943
3944
var groups = this.table.modules.groupRows.getChildGroups();
3945
3946
groups.forEach((group) => {
3947
this.recalcGroup(group);
3948
});
3949
}
3950
}
3951
}
3952
3953
recalcGroup(group){
3954
var data, rowData;
3955
3956
if(!this.blockCheck()){
3957
if(group){
3958
if(group.calcs){
3959
if(group.calcs.bottom){
3960
data = this.rowsToData(group.rows);
3961
rowData = this.generateRowData("bottom", data);
3962
3963
group.calcs.bottom.updateData(rowData);
3964
group.calcs.bottom.reinitialize();
3965
}
3966
3967
if(group.calcs.top){
3968
data = this.rowsToData(group.rows);
3969
rowData = this.generateRowData("top", data);
3970
3971
group.calcs.top.updateData(rowData);
3972
group.calcs.top.reinitialize();
3973
}
3974
}
3975
}
3976
}
3977
}
3978
3979
//generate top stats row
3980
generateTopRow(rows){
3981
return this.generateRow("top", this.rowsToData(rows));
3982
}
3983
//generate bottom stats row
3984
generateBottomRow(rows){
3985
return this.generateRow("bottom", this.rowsToData(rows));
3986
}
3987
3988
rowsToData(rows){
3989
var data = [];
3990
3991
rows.forEach((row) => {
3992
data.push(row.getData());
3993
3994
if(this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs){
3995
if(row.modules.dataTree && row.modules.dataTree.open){
3996
var children = this.rowsToData(this.table.modules.dataTree.getFilteredTreeChildren(row));
3997
data = data.concat(children);
3998
}
3999
}
4000
});
4001
4002
return data;
4003
}
4004
4005
//generate stats row
4006
generateRow(pos, data){
4007
var rowData = this.generateRowData(pos, data),
4008
row;
4009
4010
if(this.table.modExists("mutator")){
4011
this.table.modules.mutator.disable();
4012
}
4013
4014
row = new Row(rowData, this, "calc");
4015
4016
if(this.table.modExists("mutator")){
4017
this.table.modules.mutator.enable();
4018
}
4019
4020
row.getElement().classList.add("tabulator-calcs", "tabulator-calcs-" + pos);
4021
4022
row.component = false;
4023
4024
row.getComponent = () => {
4025
if(!row.component){
4026
row.component = new CalcComponent(row);
4027
}
4028
4029
return row.component;
4030
};
4031
4032
row.generateCells = () => {
4033
4034
var cells = [];
4035
4036
this.table.columnManager.columnsByIndex.forEach((column) => {
4037
4038
//set field name of mock column
4039
this.genColumn.setField(column.getField());
4040
this.genColumn.hozAlign = column.hozAlign;
4041
4042
if(column.definition[pos + "CalcFormatter"] && this.table.modExists("format")){
4043
this.genColumn.modules.format = {
4044
formatter: this.table.modules.format.getFormatter(column.definition[pos + "CalcFormatter"]),
4045
params: column.definition[pos + "CalcFormatterParams"] || {},
4046
};
4047
}else {
4048
this.genColumn.modules.format = {
4049
formatter: this.table.modules.format.getFormatter("plaintext"),
4050
params:{}
4051
};
4052
}
4053
4054
//ensure css class definition is replicated to calculation cell
4055
this.genColumn.definition.cssClass = column.definition.cssClass;
4056
4057
//generate cell and assign to correct column
4058
var cell = new Cell(this.genColumn, row);
4059
cell.getElement();
4060
cell.column = column;
4061
cell.setWidth();
4062
4063
column.cells.push(cell);
4064
cells.push(cell);
4065
4066
if(!column.visible){
4067
cell.hide();
4068
}
4069
});
4070
4071
row.cells = cells;
4072
};
4073
4074
return row;
4075
}
4076
4077
//generate stats row
4078
generateRowData(pos, data){
4079
var rowData = {},
4080
calcs = pos == "top" ? this.topCalcs : this.botCalcs,
4081
type = pos == "top" ? "topCalc" : "botCalc",
4082
params, paramKey;
4083
4084
calcs.forEach(function(column){
4085
var values = [];
4086
4087
if(column.modules.columnCalcs && column.modules.columnCalcs[type]){
4088
data.forEach(function(item){
4089
values.push(column.getFieldValue(item));
4090
});
4091
4092
paramKey = type + "Params";
4093
params = typeof column.modules.columnCalcs[paramKey] === "function" ? column.modules.columnCalcs[paramKey](values, data) : column.modules.columnCalcs[paramKey];
4094
4095
column.setFieldValue(rowData, column.modules.columnCalcs[type](values, data, params));
4096
}
4097
});
4098
4099
return rowData;
4100
}
4101
4102
hasTopCalcs(){
4103
return !!(this.topCalcs.length);
4104
}
4105
4106
hasBottomCalcs(){
4107
return !!(this.botCalcs.length);
4108
}
4109
4110
//handle table redraw
4111
redraw(){
4112
if(this.topRow){
4113
this.topRow.normalizeHeight(true);
4114
}
4115
if(this.botRow){
4116
this.botRow.normalizeHeight(true);
4117
}
4118
}
4119
4120
//return the calculated
4121
getResults(){
4122
var results = {},
4123
groups;
4124
4125
if(this.table.options.groupBy && this.table.modExists("groupRows")){
4126
groups = this.table.modules.groupRows.getGroups(true);
4127
4128
groups.forEach((group) => {
4129
results[group.getKey()] = this.getGroupResults(group);
4130
});
4131
}else {
4132
results = {
4133
top: this.topRow ? this.topRow.getData() : {},
4134
bottom: this.botRow ? this.botRow.getData() : {},
4135
};
4136
}
4137
4138
return results;
4139
}
4140
4141
//get results from a group
4142
getGroupResults(group){
4143
var groupObj = group._getSelf(),
4144
subGroups = group.getSubGroups(),
4145
subGroupResults = {},
4146
results = {};
4147
4148
subGroups.forEach((subgroup) => {
4149
subGroupResults[subgroup.getKey()] = this.getGroupResults(subgroup);
4150
});
4151
4152
results = {
4153
top: groupObj.calcs.top ? groupObj.calcs.top.getData() : {},
4154
bottom: groupObj.calcs.bottom ? groupObj.calcs.bottom.getData() : {},
4155
groups: subGroupResults,
4156
};
4157
4158
return results;
4159
}
4160
4161
adjustForScrollbar(width){
4162
if(this.botRow){
4163
if(this.table.rtl){
4164
this.botElement.style.paddingLeft = width + "px";
4165
}else {
4166
this.botElement.style.paddingRight = width + "px";
4167
}
4168
}
4169
}
4170
}
4171
4172
ColumnCalcs.moduleName = "columnCalcs";
4173
4174
//load defaults
4175
ColumnCalcs.calculations = defaultCalculations;
4176
4177
class DataTree extends Module{
4178
4179
constructor(table){
4180
super(table);
4181
4182
this.indent = 10;
4183
this.field = "";
4184
this.collapseEl = null;
4185
this.expandEl = null;
4186
this.branchEl = null;
4187
this.elementField = false;
4188
4189
this.startOpen = function(){};
4190
4191
this.registerTableOption("dataTree", false); //enable data tree
4192
this.registerTableOption("dataTreeFilter", true); //filter child rows
4193
this.registerTableOption("dataTreeSort", true); //sort child rows
4194
this.registerTableOption("dataTreeElementColumn", false);
4195
this.registerTableOption("dataTreeBranchElement", true);//show data tree branch element
4196
this.registerTableOption("dataTreeChildIndent", 9); //data tree child indent in px
4197
this.registerTableOption("dataTreeChildField", "_children");//data tre column field to look for child rows
4198
this.registerTableOption("dataTreeCollapseElement", false);//data tree row collapse element
4199
this.registerTableOption("dataTreeExpandElement", false);//data tree row expand element
4200
this.registerTableOption("dataTreeStartExpanded", false);
4201
this.registerTableOption("dataTreeChildColumnCalcs", false);//include visible data tree rows in column calculations
4202
this.registerTableOption("dataTreeSelectPropagate", false);//selecting a parent row selects its children
4203
4204
//register component functions
4205
this.registerComponentFunction("row", "treeCollapse", this.collapseRow.bind(this));
4206
this.registerComponentFunction("row", "treeExpand", this.expandRow.bind(this));
4207
this.registerComponentFunction("row", "treeToggle", this.toggleRow.bind(this));
4208
this.registerComponentFunction("row", "getTreeParent", this.getTreeParent.bind(this));
4209
this.registerComponentFunction("row", "getTreeChildren", this.getRowChildren.bind(this));
4210
this.registerComponentFunction("row", "addTreeChild", this.addTreeChildRow.bind(this));
4211
this.registerComponentFunction("row", "isTreeExpanded", this.isRowExpanded.bind(this));
4212
}
4213
4214
initialize(){
4215
if(this.table.options.dataTree){
4216
var dummyEl = null,
4217
options = this.table.options;
4218
4219
this.field = options.dataTreeChildField;
4220
this.indent = options.dataTreeChildIndent;
4221
4222
if(this.options("movableRows")){
4223
console.warn("The movableRows option is not available with dataTree enabled, moving of child rows could result in unpredictable behavior");
4224
}
4225
4226
if(options.dataTreeBranchElement){
4227
4228
if(options.dataTreeBranchElement === true){
4229
this.branchEl = document.createElement("div");
4230
this.branchEl.classList.add("tabulator-data-tree-branch");
4231
}else {
4232
if(typeof options.dataTreeBranchElement === "string"){
4233
dummyEl = document.createElement("div");
4234
dummyEl.innerHTML = options.dataTreeBranchElement;
4235
this.branchEl = dummyEl.firstChild;
4236
}else {
4237
this.branchEl = options.dataTreeBranchElement;
4238
}
4239
}
4240
}else {
4241
this.branchEl = document.createElement("div");
4242
this.branchEl.classList.add("tabulator-data-tree-branch-empty");
4243
}
4244
4245
if(options.dataTreeCollapseElement){
4246
if(typeof options.dataTreeCollapseElement === "string"){
4247
dummyEl = document.createElement("div");
4248
dummyEl.innerHTML = options.dataTreeCollapseElement;
4249
this.collapseEl = dummyEl.firstChild;
4250
}else {
4251
this.collapseEl = options.dataTreeCollapseElement;
4252
}
4253
}else {
4254
this.collapseEl = document.createElement("div");
4255
this.collapseEl.classList.add("tabulator-data-tree-control");
4256
this.collapseEl.tabIndex = 0;
4257
this.collapseEl.innerHTML = "<div class='tabulator-data-tree-control-collapse'></div>";
4258
}
4259
4260
if(options.dataTreeExpandElement){
4261
if(typeof options.dataTreeExpandElement === "string"){
4262
dummyEl = document.createElement("div");
4263
dummyEl.innerHTML = options.dataTreeExpandElement;
4264
this.expandEl = dummyEl.firstChild;
4265
}else {
4266
this.expandEl = options.dataTreeExpandElement;
4267
}
4268
}else {
4269
this.expandEl = document.createElement("div");
4270
this.expandEl.classList.add("tabulator-data-tree-control");
4271
this.expandEl.tabIndex = 0;
4272
this.expandEl.innerHTML = "<div class='tabulator-data-tree-control-expand'></div>";
4273
}
4274
4275
4276
switch(typeof options.dataTreeStartExpanded){
4277
case "boolean":
4278
this.startOpen = function(row, index){
4279
return options.dataTreeStartExpanded;
4280
};
4281
break;
4282
4283
case "function":
4284
this.startOpen = options.dataTreeStartExpanded;
4285
break;
4286
4287
default:
4288
this.startOpen = function(row, index){
4289
return options.dataTreeStartExpanded[index];
4290
};
4291
break;
4292
}
4293
4294
this.subscribe("row-init", this.initializeRow.bind(this));
4295
this.subscribe("row-layout-after", this.layoutRow.bind(this));
4296
this.subscribe("row-deleted", this.rowDelete.bind(this),0);
4297
this.subscribe("row-data-changed", this.rowDataChanged.bind(this), 10);
4298
this.subscribe("cell-value-updated", this.cellValueChanged.bind(this));
4299
this.subscribe("edit-cancelled", this.cellValueChanged.bind(this));
4300
this.subscribe("column-moving-rows", this.columnMoving.bind(this));
4301
this.subscribe("table-built", this.initializeElementField.bind(this));
4302
this.subscribe("table-redrawing", this.tableRedrawing.bind(this));
4303
4304
this.registerDisplayHandler(this.getRows.bind(this), 30);
4305
}
4306
}
4307
4308
tableRedrawing(force){
4309
var rows;
4310
4311
if(force){
4312
rows = this.table.rowManager.getRows();
4313
4314
rows.forEach((row) => {
4315
this.reinitializeRowChildren(row);
4316
});
4317
}
4318
}
4319
4320
initializeElementField(){
4321
var firstCol = this.table.columnManager.getFirstVisibleColumn();
4322
4323
this.elementField = this.table.options.dataTreeElementColumn || (firstCol ? firstCol.field : false);
4324
}
4325
4326
getRowChildren(row){
4327
return this.getTreeChildren(row, true);
4328
}
4329
4330
columnMoving(){
4331
var rows = [];
4332
4333
this.table.rowManager.rows.forEach((row) => {
4334
rows = rows.concat(this.getTreeChildren(row, false, true));
4335
});
4336
4337
return rows;
4338
}
4339
4340
rowDataChanged(row, visible, updatedData){
4341
if(this.redrawNeeded(updatedData)){
4342
this.initializeRow(row);
4343
4344
if(visible){
4345
this.layoutRow(row);
4346
this.refreshData(true);
4347
}
4348
}
4349
}
4350
4351
cellValueChanged(cell){
4352
var field = cell.column.getField();
4353
4354
if(field === this.elementField){
4355
this.layoutRow(cell.row);
4356
}
4357
}
4358
4359
initializeRow(row){
4360
var childArray = row.getData()[this.field];
4361
var isArray = Array.isArray(childArray);
4362
4363
var children = isArray || (!isArray && typeof childArray === "object" && childArray !== null);
4364
4365
if(!children && row.modules.dataTree && row.modules.dataTree.branchEl){
4366
row.modules.dataTree.branchEl.parentNode.removeChild(row.modules.dataTree.branchEl);
4367
}
4368
4369
if(!children && row.modules.dataTree && row.modules.dataTree.controlEl){
4370
row.modules.dataTree.controlEl.parentNode.removeChild(row.modules.dataTree.controlEl);
4371
}
4372
4373
row.modules.dataTree = {
4374
index: row.modules.dataTree ? row.modules.dataTree.index : 0,
4375
open: children ? (row.modules.dataTree ? row.modules.dataTree.open : this.startOpen(row.getComponent(), 0)) : false,
4376
controlEl: row.modules.dataTree && children ? row.modules.dataTree.controlEl : false,
4377
branchEl: row.modules.dataTree && children ? row.modules.dataTree.branchEl : false,
4378
parent: row.modules.dataTree ? row.modules.dataTree.parent : false,
4379
children:children,
4380
};
4381
}
4382
4383
reinitializeRowChildren(row){
4384
var children = this.getTreeChildren(row, false, true);
4385
4386
children.forEach(function(child){
4387
child.reinitialize(true);
4388
});
4389
}
4390
4391
layoutRow(row){
4392
var cell = this.elementField ? row.getCell(this.elementField) : row.getCells()[0],
4393
el = cell.getElement(),
4394
config = row.modules.dataTree;
4395
4396
if(config.branchEl){
4397
if(config.branchEl.parentNode){
4398
config.branchEl.parentNode.removeChild(config.branchEl);
4399
}
4400
config.branchEl = false;
4401
}
4402
4403
if(config.controlEl){
4404
if(config.controlEl.parentNode){
4405
config.controlEl.parentNode.removeChild(config.controlEl);
4406
}
4407
config.controlEl = false;
4408
}
4409
4410
this.generateControlElement(row, el);
4411
4412
row.getElement().classList.add("tabulator-tree-level-" + config.index);
4413
4414
if(config.index){
4415
if(this.branchEl){
4416
config.branchEl = this.branchEl.cloneNode(true);
4417
el.insertBefore(config.branchEl, el.firstChild);
4418
4419
if(this.table.rtl){
4420
config.branchEl.style.marginRight = (((config.branchEl.offsetWidth + config.branchEl.style.marginLeft) * (config.index - 1)) + (config.index * this.indent)) + "px";
4421
}else {
4422
config.branchEl.style.marginLeft = (((config.branchEl.offsetWidth + config.branchEl.style.marginRight) * (config.index - 1)) + (config.index * this.indent)) + "px";
4423
}
4424
}else {
4425
4426
if(this.table.rtl){
4427
el.style.paddingRight = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-right')) + (config.index * this.indent) + "px";
4428
}else {
4429
el.style.paddingLeft = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-left')) + (config.index * this.indent) + "px";
4430
}
4431
}
4432
}
4433
}
4434
4435
generateControlElement(row, el){
4436
var config = row.modules.dataTree,
4437
oldControl = config.controlEl;
4438
4439
el = el || row.getCells()[0].getElement();
4440
4441
if(config.children !== false){
4442
4443
if(config.open){
4444
config.controlEl = this.collapseEl.cloneNode(true);
4445
config.controlEl.addEventListener("click", (e) => {
4446
e.stopPropagation();
4447
this.collapseRow(row);
4448
});
4449
}else {
4450
config.controlEl = this.expandEl.cloneNode(true);
4451
config.controlEl.addEventListener("click", (e) => {
4452
e.stopPropagation();
4453
this.expandRow(row);
4454
});
4455
}
4456
4457
config.controlEl.addEventListener("mousedown", (e) => {
4458
e.stopPropagation();
4459
});
4460
4461
if(oldControl && oldControl.parentNode === el){
4462
oldControl.parentNode.replaceChild(config.controlEl,oldControl);
4463
}else {
4464
el.insertBefore(config.controlEl, el.firstChild);
4465
}
4466
}
4467
}
4468
4469
getRows(rows){
4470
var output = [];
4471
4472
rows.forEach((row, i) => {
4473
var config, children;
4474
4475
output.push(row);
4476
4477
if(row instanceof Row){
4478
4479
row.create();
4480
4481
config = row.modules.dataTree;
4482
4483
if(!config.index && config.children !== false){
4484
children = this.getChildren(row);
4485
4486
children.forEach((child) => {
4487
child.create();
4488
output.push(child);
4489
});
4490
}
4491
}
4492
});
4493
4494
return output;
4495
}
4496
4497
getChildren(row, allChildren){
4498
var config = row.modules.dataTree,
4499
children = [],
4500
output = [];
4501
4502
if(config.children !== false && (config.open || allChildren)){
4503
if(!Array.isArray(config.children)){
4504
config.children = this.generateChildren(row);
4505
}
4506
4507
if(this.table.modExists("filter") && this.table.options.dataTreeFilter){
4508
children = this.table.modules.filter.filter(config.children);
4509
}else {
4510
children = config.children;
4511
}
4512
4513
if(this.table.modExists("sort") && this.table.options.dataTreeSort){
4514
this.table.modules.sort.sort(children);
4515
}
4516
4517
children.forEach((child) => {
4518
output.push(child);
4519
4520
var subChildren = this.getChildren(child);
4521
4522
subChildren.forEach((sub) => {
4523
output.push(sub);
4524
});
4525
});
4526
}
4527
4528
return output;
4529
}
4530
4531
generateChildren(row){
4532
var children = [];
4533
4534
var childArray = row.getData()[this.field];
4535
4536
if(!Array.isArray(childArray)){
4537
childArray = [childArray];
4538
}
4539
4540
childArray.forEach((childData) => {
4541
var childRow = new Row(childData || {}, this.table.rowManager);
4542
4543
childRow.create();
4544
4545
childRow.modules.dataTree.index = row.modules.dataTree.index + 1;
4546
childRow.modules.dataTree.parent = row;
4547
4548
if(childRow.modules.dataTree.children){
4549
childRow.modules.dataTree.open = this.startOpen(childRow.getComponent(), childRow.modules.dataTree.index);
4550
}
4551
children.push(childRow);
4552
});
4553
4554
return children;
4555
}
4556
4557
expandRow(row, silent){
4558
var config = row.modules.dataTree;
4559
4560
if(config.children !== false){
4561
config.open = true;
4562
4563
row.reinitialize();
4564
4565
this.refreshData(true);
4566
4567
this.dispatchExternal("dataTreeRowExpanded", row.getComponent(), row.modules.dataTree.index);
4568
}
4569
}
4570
4571
collapseRow(row){
4572
var config = row.modules.dataTree;
4573
4574
if(config.children !== false){
4575
config.open = false;
4576
4577
row.reinitialize();
4578
4579
this.refreshData(true);
4580
4581
this.dispatchExternal("dataTreeRowCollapsed", row.getComponent(), row.modules.dataTree.index);
4582
}
4583
}
4584
4585
toggleRow(row){
4586
var config = row.modules.dataTree;
4587
4588
if(config.children !== false){
4589
if(config.open){
4590
this.collapseRow(row);
4591
}else {
4592
this.expandRow(row);
4593
}
4594
}
4595
}
4596
4597
isRowExpanded(row){
4598
return row.modules.dataTree.open;
4599
}
4600
4601
getTreeParent(row){
4602
return row.modules.dataTree.parent ? row.modules.dataTree.parent.getComponent() : false;
4603
}
4604
4605
getTreeParentRoot(row){
4606
return row.modules.dataTree && row.modules.dataTree.parent ? this.getTreeParentRoot(row.modules.dataTree.parent) : row;
4607
}
4608
4609
getFilteredTreeChildren(row){
4610
var config = row.modules.dataTree,
4611
output = [], children;
4612
4613
if(config.children){
4614
4615
if(!Array.isArray(config.children)){
4616
config.children = this.generateChildren(row);
4617
}
4618
4619
if(this.table.modExists("filter") && this.table.options.dataTreeFilter){
4620
children = this.table.modules.filter.filter(config.children);
4621
}else {
4622
children = config.children;
4623
}
4624
4625
children.forEach((childRow) => {
4626
if(childRow instanceof Row){
4627
output.push(childRow);
4628
}
4629
});
4630
}
4631
4632
return output;
4633
}
4634
4635
rowDelete(row){
4636
var parent = row.modules.dataTree.parent,
4637
childIndex;
4638
4639
if(parent){
4640
childIndex = this.findChildIndex(row, parent);
4641
4642
if(childIndex !== false){
4643
parent.data[this.field].splice(childIndex, 1);
4644
}
4645
4646
if(!parent.data[this.field].length){
4647
delete parent.data[this.field];
4648
}
4649
4650
this.initializeRow(parent);
4651
this.layoutRow(parent);
4652
}
4653
4654
this.refreshData(true);
4655
}
4656
4657
addTreeChildRow(row, data, top, index){
4658
var childIndex = false;
4659
4660
if(typeof data === "string"){
4661
data = JSON.parse(data);
4662
}
4663
4664
if(!Array.isArray(row.data[this.field])){
4665
row.data[this.field] = [];
4666
4667
row.modules.dataTree.open = this.startOpen(row.getComponent(), row.modules.dataTree.index);
4668
}
4669
4670
if(typeof index !== "undefined"){
4671
childIndex = this.findChildIndex(index, row);
4672
4673
if(childIndex !== false){
4674
row.data[this.field].splice((top ? childIndex : childIndex + 1), 0, data);
4675
}
4676
}
4677
4678
if(childIndex === false){
4679
if(top){
4680
row.data[this.field].unshift(data);
4681
}else {
4682
row.data[this.field].push(data);
4683
}
4684
}
4685
4686
this.initializeRow(row);
4687
this.layoutRow(row);
4688
4689
this.refreshData(true);
4690
}
4691
4692
findChildIndex(subject, parent){
4693
var match = false;
4694
4695
if(typeof subject == "object"){
4696
4697
if(subject instanceof Row){
4698
//subject is row element
4699
match = subject.data;
4700
}else if(subject instanceof RowComponent){
4701
//subject is public row component
4702
match = subject._getSelf().data;
4703
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
4704
if(parent.modules.dataTree){
4705
match = parent.modules.dataTree.children.find((childRow) => {
4706
return childRow instanceof Row ? childRow.element === subject : false;
4707
});
4708
4709
if(match){
4710
match = match.data;
4711
}
4712
}
4713
}else if(subject === null){
4714
match = false;
4715
}
4716
4717
}else if(typeof subject == "undefined"){
4718
match = false;
4719
}else {
4720
//subject should be treated as the index of the row
4721
match = parent.data[this.field].find((row) => {
4722
return row.data[this.table.options.index] == subject;
4723
});
4724
}
4725
4726
if(match){
4727
4728
if(Array.isArray(parent.data[this.field])){
4729
match = parent.data[this.field].indexOf(match);
4730
}
4731
4732
if(match == -1){
4733
match = false;
4734
}
4735
}
4736
4737
//catch all for any other type of input
4738
4739
return match;
4740
}
4741
4742
getTreeChildren(row, component, recurse){
4743
var config = row.modules.dataTree,
4744
output = [];
4745
4746
if(config && config.children){
4747
4748
if(!Array.isArray(config.children)){
4749
config.children = this.generateChildren(row);
4750
}
4751
4752
config.children.forEach((childRow) => {
4753
if(childRow instanceof Row){
4754
output.push(component ? childRow.getComponent() : childRow);
4755
4756
if(recurse){
4757
output = output.concat(this.getTreeChildren(childRow, component, recurse));
4758
}
4759
}
4760
});
4761
}
4762
4763
return output;
4764
}
4765
4766
getChildField(){
4767
return this.field;
4768
}
4769
4770
redrawNeeded(data){
4771
return (this.field ? typeof data[this.field] !== "undefined" : false) || (this.elementField ? typeof data[this.elementField] !== "undefined" : false);
4772
}
4773
}
4774
4775
DataTree.moduleName = "dataTree";
4776
4777
function csv(list, options = {}, setFileContents){
4778
var delimiter = options.delimiter ? options.delimiter : ",",
4779
fileContents = [],
4780
headers = [];
4781
4782
list.forEach((row) => {
4783
var item = [];
4784
4785
switch(row.type){
4786
case "group":
4787
console.warn("Download Warning - CSV downloader cannot process row groups");
4788
break;
4789
4790
case "calc":
4791
console.warn("Download Warning - CSV downloader cannot process column calculations");
4792
break;
4793
4794
case "header":
4795
row.columns.forEach((col, i) => {
4796
if(col && col.depth === 1){
4797
headers[i] = typeof col.value == "undefined" || col.value === null ? "" : ('"' + String(col.value).split('"').join('""') + '"');
4798
}
4799
});
4800
break;
4801
4802
case "row":
4803
row.columns.forEach((col) => {
4804
4805
if(col){
4806
4807
switch(typeof col.value){
4808
case "object":
4809
col.value = col.value !== null ? JSON.stringify(col.value) : "";
4810
break;
4811
4812
case "undefined":
4813
col.value = "";
4814
break;
4815
}
4816
4817
item.push('"' + String(col.value).split('"').join('""') + '"');
4818
}
4819
});
4820
4821
fileContents.push(item.join(delimiter));
4822
break;
4823
}
4824
});
4825
4826
if(headers.length){
4827
fileContents.unshift(headers.join(delimiter));
4828
}
4829
4830
fileContents = fileContents.join("\n");
4831
4832
if(options.bom){
4833
fileContents = "\ufeff" + fileContents;
4834
}
4835
4836
setFileContents(fileContents, "text/csv");
4837
}
4838
4839
function json(list, options, setFileContents){
4840
var fileContents = [];
4841
4842
list.forEach((row) => {
4843
var item = {};
4844
4845
switch(row.type){
4846
case "header":
4847
break;
4848
4849
case "group":
4850
console.warn("Download Warning - JSON downloader cannot process row groups");
4851
break;
4852
4853
case "calc":
4854
console.warn("Download Warning - JSON downloader cannot process column calculations");
4855
break;
4856
4857
case "row":
4858
row.columns.forEach((col) => {
4859
if(col){
4860
item[col.component.getTitleDownload() || col.component.getField()] = col.value;
4861
}
4862
});
4863
4864
fileContents.push(item);
4865
break;
4866
}
4867
});
4868
4869
fileContents = JSON.stringify(fileContents, null, '\t');
4870
4871
setFileContents(fileContents, "application/json");
4872
}
4873
4874
function pdf(list, options = {}, setFileContents){
4875
var header = [],
4876
body = [],
4877
autoTableParams = {},
4878
rowGroupStyles = options.rowGroupStyles || {
4879
fontStyle: "bold",
4880
fontSize: 12,
4881
cellPadding: 6,
4882
fillColor: 220,
4883
},
4884
rowCalcStyles = options.rowCalcStyles || {
4885
fontStyle: "bold",
4886
fontSize: 10,
4887
cellPadding: 4,
4888
fillColor: 232,
4889
},
4890
jsPDFParams = options.jsPDF || {},
4891
title = options.title ? options.title : "";
4892
4893
if(!jsPDFParams.orientation){
4894
jsPDFParams.orientation = options.orientation || "landscape";
4895
}
4896
4897
if(!jsPDFParams.unit){
4898
jsPDFParams.unit = "pt";
4899
}
4900
4901
//parse row list
4902
list.forEach((row) => {
4903
switch(row.type){
4904
case "header":
4905
header.push(parseRow(row));
4906
break;
4907
4908
case "group":
4909
body.push(parseRow(row, rowGroupStyles));
4910
break;
4911
4912
case "calc":
4913
body.push(parseRow(row, rowCalcStyles));
4914
break;
4915
4916
case "row":
4917
body.push(parseRow(row));
4918
break;
4919
}
4920
});
4921
4922
function parseRow(row, styles){
4923
var rowData = [];
4924
4925
row.columns.forEach((col) =>{
4926
var cell;
4927
4928
if(col){
4929
switch(typeof col.value){
4930
case "object":
4931
col.value = col.value !== null ? JSON.stringify(col.value) : "";
4932
break;
4933
4934
case "undefined":
4935
col.value = "";
4936
break;
4937
}
4938
4939
cell = {
4940
content:col.value,
4941
colSpan:col.width,
4942
rowSpan:col.height,
4943
};
4944
4945
if(styles){
4946
cell.styles = styles;
4947
}
4948
4949
rowData.push(cell);
4950
}
4951
});
4952
4953
return rowData;
4954
}
4955
4956
4957
//configure PDF
4958
var doc = new jspdf.jsPDF(jsPDFParams); //set document to landscape, better for most tables
4959
4960
if(options.autoTable){
4961
if(typeof options.autoTable === "function"){
4962
autoTableParams = options.autoTable(doc) || {};
4963
}else {
4964
autoTableParams = options.autoTable;
4965
}
4966
}
4967
4968
if(title){
4969
autoTableParams.didDrawPage = function(data) {
4970
doc.text(title, 40, 30);
4971
};
4972
}
4973
4974
autoTableParams.head = header;
4975
autoTableParams.body = body;
4976
4977
doc.autoTable(autoTableParams);
4978
4979
if(options.documentProcessing){
4980
options.documentProcessing(doc);
4981
}
4982
4983
setFileContents(doc.output("arraybuffer"), "application/pdf");
4984
}
4985
4986
function xlsx(list, options, setFileContents){
4987
var self = this,
4988
sheetName = options.sheetName || "Sheet1",
4989
workbook = XLSX.utils.book_new(),
4990
tableFeatures = new CoreFeature(this),
4991
compression = 'compress' in options ? options.compress : true,
4992
writeOptions = options.writeOptions || {bookType:'xlsx', bookSST:true, compression},
4993
output;
4994
4995
writeOptions.type = 'binary';
4996
4997
workbook.SheetNames = [];
4998
workbook.Sheets = {};
4999
5000
function generateSheet(){
5001
var rows = [],
5002
merges = [],
5003
worksheet = {},
5004
range = {s: {c:0, r:0}, e: {c:(list[0] ? list[0].columns.reduce((a, b) => a + (b && b.width ? b.width : 1), 0) : 0), r:list.length }};
5005
5006
//parse row list
5007
list.forEach((row, i) => {
5008
var rowData = [];
5009
5010
row.columns.forEach(function(col, j){
5011
5012
if(col){
5013
rowData.push(!(col.value instanceof Date) && typeof col.value === "object" ? JSON.stringify(col.value) : col.value);
5014
5015
if(col.width > 1 || col.height > -1){
5016
if(col.height > 1 || col.width > 1){
5017
merges.push({s:{r:i,c:j},e:{r:i + col.height - 1,c:j + col.width - 1}});
5018
}
5019
}
5020
}else {
5021
rowData.push("");
5022
}
5023
});
5024
5025
rows.push(rowData);
5026
});
5027
5028
//convert rows to worksheet
5029
XLSX.utils.sheet_add_aoa(worksheet, rows);
5030
5031
worksheet['!ref'] = XLSX.utils.encode_range(range);
5032
5033
if(merges.length){
5034
worksheet["!merges"] = merges;
5035
}
5036
5037
return worksheet;
5038
}
5039
5040
if(options.sheetOnly){
5041
setFileContents(generateSheet());
5042
return;
5043
}
5044
5045
if(options.sheets){
5046
for(var sheet in options.sheets){
5047
5048
if(options.sheets[sheet] === true){
5049
workbook.SheetNames.push(sheet);
5050
workbook.Sheets[sheet] = generateSheet();
5051
}else {
5052
5053
workbook.SheetNames.push(sheet);
5054
5055
tableFeatures.commsSend(options.sheets[sheet], "download", "intercept",{
5056
type:"xlsx",
5057
options:{sheetOnly:true},
5058
active:self.active,
5059
intercept:function(data){
5060
workbook.Sheets[sheet] = data;
5061
}
5062
});
5063
}
5064
}
5065
}else {
5066
workbook.SheetNames.push(sheetName);
5067
workbook.Sheets[sheetName] = generateSheet();
5068
}
5069
5070
if(options.documentProcessing){
5071
workbook = options.documentProcessing(workbook);
5072
}
5073
5074
//convert workbook to binary array
5075
function s2ab(s) {
5076
var buf = new ArrayBuffer(s.length);
5077
var view = new Uint8Array(buf);
5078
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
5079
return buf;
5080
}
5081
5082
output = XLSX.write(workbook, writeOptions);
5083
5084
setFileContents(s2ab(output), "application/octet-stream");
5085
}
5086
5087
function html(list, options, setFileContents){
5088
if(this.modExists("export", true)){
5089
setFileContents(this.modules.export.generateHTMLTable(list), "text/html");
5090
}
5091
}
5092
5093
function jsonLines (list, options, setFileContents) {
5094
const fileContents = [];
5095
5096
list.forEach((row) => {
5097
const item = {};
5098
5099
switch (row.type) {
5100
case "header":
5101
break;
5102
5103
case "group":
5104
console.warn("Download Warning - JSON downloader cannot process row groups");
5105
break;
5106
5107
case "calc":
5108
console.warn("Download Warning - JSON downloader cannot process column calculations");
5109
break;
5110
5111
case "row":
5112
row.columns.forEach((col) => {
5113
if (col) {
5114
item[col.component.getTitleDownload() || col.component.getField()] = col.value;
5115
}
5116
});
5117
5118
fileContents.push(JSON.stringify(item));
5119
break;
5120
}
5121
});
5122
5123
setFileContents(fileContents.join("\n"), "application/x-ndjson");
5124
}
5125
5126
var defaultDownloaders = {
5127
csv:csv,
5128
json:json,
5129
jsonLines:jsonLines,
5130
pdf:pdf,
5131
xlsx:xlsx,
5132
html:html,
5133
};
5134
5135
class Download extends Module{
5136
5137
constructor(table){
5138
super(table);
5139
5140
this.registerTableOption("downloadEncoder", function(data, mimeType){
5141
return new Blob([data],{type:mimeType});
5142
}); //function to manipulate download data
5143
this.registerTableOption("downloadReady", undefined); //warn of function deprecation
5144
this.registerTableOption("downloadConfig", {}); //download config
5145
this.registerTableOption("downloadRowRange", "active"); //restrict download to active rows only
5146
5147
this.registerColumnOption("download");
5148
this.registerColumnOption("titleDownload");
5149
}
5150
5151
initialize(){
5152
this.deprecatedOptionsCheck();
5153
5154
this.registerTableFunction("download", this.download.bind(this));
5155
this.registerTableFunction("downloadToTab", this.downloadToTab.bind(this));
5156
}
5157
5158
deprecatedOptionsCheck(){
5159
this.deprecationCheck("downloadReady", "downloadEncoder");
5160
}
5161
5162
///////////////////////////////////
5163
///////// Table Functions /////////
5164
///////////////////////////////////
5165
5166
downloadToTab(type, filename, options, active){
5167
this.download(type, filename, options, active, true);
5168
}
5169
5170
///////////////////////////////////
5171
///////// Internal Logic //////////
5172
///////////////////////////////////
5173
5174
//trigger file download
5175
download(type, filename, options, range, interceptCallback){
5176
var downloadFunc = false;
5177
5178
function buildLink(data, mime){
5179
if(interceptCallback){
5180
if(interceptCallback === true){
5181
this.triggerDownload(data, mime, type, filename, true);
5182
}else {
5183
interceptCallback(data);
5184
}
5185
5186
}else {
5187
this.triggerDownload(data, mime, type, filename);
5188
}
5189
}
5190
5191
if(typeof type == "function"){
5192
downloadFunc = type;
5193
}else {
5194
if(Download.downloaders[type]){
5195
downloadFunc = Download.downloaders[type];
5196
}else {
5197
console.warn("Download Error - No such download type found: ", type);
5198
}
5199
}
5200
5201
if(downloadFunc){
5202
var list = this.generateExportList(range);
5203
5204
downloadFunc.call(this.table, list , options || {}, buildLink.bind(this));
5205
}
5206
}
5207
5208
generateExportList(range){
5209
var list = this.table.modules.export.generateExportList(this.table.options.downloadConfig, false, range || this.table.options.downloadRowRange, "download");
5210
5211
//assign group header formatter
5212
var groupHeader = this.table.options.groupHeaderDownload;
5213
5214
if(groupHeader && !Array.isArray(groupHeader)){
5215
groupHeader = [groupHeader];
5216
}
5217
5218
list.forEach((row) => {
5219
var group;
5220
5221
if(row.type === "group"){
5222
group = row.columns[0];
5223
5224
if(groupHeader && groupHeader[row.indent]){
5225
group.value = groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
5226
}
5227
}
5228
});
5229
5230
return list;
5231
}
5232
5233
triggerDownload(data, mime, type, filename, newTab){
5234
var element = document.createElement('a'),
5235
blob = this.table.options.downloadEncoder(data, mime);
5236
5237
if(blob){
5238
if(newTab){
5239
window.open(window.URL.createObjectURL(blob));
5240
}else {
5241
filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type);
5242
5243
if(navigator.msSaveOrOpenBlob){
5244
navigator.msSaveOrOpenBlob(blob, filename);
5245
}else {
5246
element.setAttribute('href', window.URL.createObjectURL(blob));
5247
5248
//set file title
5249
element.setAttribute('download', filename);
5250
5251
//trigger download
5252
element.style.display = 'none';
5253
document.body.appendChild(element);
5254
element.click();
5255
5256
//remove temporary link element
5257
document.body.removeChild(element);
5258
}
5259
}
5260
5261
this.dispatchExternal("downloadComplete");
5262
}
5263
}
5264
5265
commsReceived(table, action, data){
5266
switch(action){
5267
case "intercept":
5268
this.download(data.type, "", data.options, data.active, data.intercept);
5269
break;
5270
}
5271
}
5272
}
5273
5274
Download.moduleName = "download";
5275
5276
//load defaults
5277
Download.downloaders = defaultDownloaders;
5278
5279
function maskInput(el, options){
5280
var mask = options.mask,
5281
maskLetter = typeof options.maskLetterChar !== "undefined" ? options.maskLetterChar : "A",
5282
maskNumber = typeof options.maskNumberChar !== "undefined" ? options.maskNumberChar : "9",
5283
maskWildcard = typeof options.maskWildcardChar !== "undefined" ? options.maskWildcardChar : "*";
5284
5285
function fillSymbols(index){
5286
var symbol = mask[index];
5287
if(typeof symbol !== "undefined" && symbol !== maskWildcard && symbol !== maskLetter && symbol !== maskNumber){
5288
el.value = el.value + "" + symbol;
5289
fillSymbols(index+1);
5290
}
5291
}
5292
5293
el.addEventListener("keydown", (e) => {
5294
var index = el.value.length,
5295
char = e.key;
5296
5297
if(e.keyCode > 46 && !e.ctrlKey && !e.metaKey){
5298
if(index >= mask.length){
5299
e.preventDefault();
5300
e.stopPropagation();
5301
return false;
5302
}else {
5303
switch(mask[index]){
5304
case maskLetter:
5305
if(char.toUpperCase() == char.toLowerCase()){
5306
e.preventDefault();
5307
e.stopPropagation();
5308
return false;
5309
}
5310
break;
5311
5312
case maskNumber:
5313
if(isNaN(char)){
5314
e.preventDefault();
5315
e.stopPropagation();
5316
return false;
5317
}
5318
break;
5319
5320
case maskWildcard:
5321
break;
5322
5323
default:
5324
if(char !== mask[index]){
5325
e.preventDefault();
5326
e.stopPropagation();
5327
return false;
5328
}
5329
}
5330
}
5331
}
5332
5333
return;
5334
});
5335
5336
el.addEventListener("keyup", (e) => {
5337
if(e.keyCode > 46){
5338
if(options.maskAutoFill){
5339
fillSymbols(el.value.length);
5340
}
5341
}
5342
});
5343
5344
5345
if(!el.placeholder){
5346
el.placeholder = mask;
5347
}
5348
5349
if(options.maskAutoFill){
5350
fillSymbols(el.value.length);
5351
}
5352
}
5353
5354
//input element
5355
function input(cell, onRendered, success, cancel, editorParams){
5356
//create and style input
5357
var cellValue = cell.getValue(),
5358
input = document.createElement("input");
5359
5360
input.setAttribute("type", editorParams.search ? "search" : "text");
5361
5362
input.style.padding = "4px";
5363
input.style.width = "100%";
5364
input.style.boxSizing = "border-box";
5365
5366
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5367
for (let key in editorParams.elementAttributes){
5368
if(key.charAt(0) == "+"){
5369
key = key.slice(1);
5370
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5371
}else {
5372
input.setAttribute(key, editorParams.elementAttributes[key]);
5373
}
5374
}
5375
}
5376
5377
input.value = typeof cellValue !== "undefined" ? cellValue : "";
5378
5379
onRendered(function(){
5380
if(cell.getType() === "cell"){
5381
input.focus({preventScroll: true});
5382
input.style.height = "100%";
5383
5384
if(editorParams.selectContents){
5385
input.select();
5386
}
5387
}
5388
});
5389
5390
function onChange(e){
5391
if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){
5392
if(success(input.value)){
5393
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
5394
}
5395
}else {
5396
cancel();
5397
}
5398
}
5399
5400
//submit new value on blur or change
5401
input.addEventListener("change", onChange);
5402
input.addEventListener("blur", onChange);
5403
5404
//submit new value on enter
5405
input.addEventListener("keydown", function(e){
5406
switch(e.keyCode){
5407
// case 9:
5408
case 13:
5409
onChange();
5410
break;
5411
5412
case 27:
5413
cancel();
5414
break;
5415
5416
case 35:
5417
case 36:
5418
e.stopPropagation();
5419
break;
5420
}
5421
});
5422
5423
if(editorParams.mask){
5424
maskInput(input, editorParams);
5425
}
5426
5427
return input;
5428
}
5429
5430
//resizable text area element
5431
function textarea(cell, onRendered, success, cancel, editorParams){
5432
var cellValue = cell.getValue(),
5433
vertNav = editorParams.verticalNavigation || "hybrid",
5434
value = String(cellValue !== null && typeof cellValue !== "undefined" ? cellValue : ""),
5435
input = document.createElement("textarea"),
5436
scrollHeight = 0;
5437
5438
//create and style input
5439
input.style.display = "block";
5440
input.style.padding = "2px";
5441
input.style.height = "100%";
5442
input.style.width = "100%";
5443
input.style.boxSizing = "border-box";
5444
input.style.whiteSpace = "pre-wrap";
5445
input.style.resize = "none";
5446
5447
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5448
for (let key in editorParams.elementAttributes){
5449
if(key.charAt(0) == "+"){
5450
key = key.slice(1);
5451
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5452
}else {
5453
input.setAttribute(key, editorParams.elementAttributes[key]);
5454
}
5455
}
5456
}
5457
5458
input.value = value;
5459
5460
onRendered(function(){
5461
if(cell.getType() === "cell"){
5462
input.focus({preventScroll: true});
5463
input.style.height = "100%";
5464
5465
input.scrollHeight;
5466
input.style.height = input.scrollHeight + "px";
5467
cell.getRow().normalizeHeight();
5468
5469
if(editorParams.selectContents){
5470
input.select();
5471
}
5472
}
5473
});
5474
5475
function onChange(e){
5476
5477
if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){
5478
5479
if(success(input.value)){
5480
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
5481
}
5482
5483
setTimeout(function(){
5484
cell.getRow().normalizeHeight();
5485
},300);
5486
}else {
5487
cancel();
5488
}
5489
}
5490
5491
//submit new value on blur or change
5492
input.addEventListener("change", onChange);
5493
input.addEventListener("blur", onChange);
5494
5495
input.addEventListener("keyup", function(){
5496
5497
input.style.height = "";
5498
5499
var heightNow = input.scrollHeight;
5500
5501
input.style.height = heightNow + "px";
5502
5503
if(heightNow != scrollHeight){
5504
scrollHeight = heightNow;
5505
cell.getRow().normalizeHeight();
5506
}
5507
});
5508
5509
input.addEventListener("keydown", function(e){
5510
5511
switch(e.keyCode){
5512
5513
case 13:
5514
if(e.shiftKey && editorParams.shiftEnterSubmit){
5515
onChange();
5516
}
5517
break;
5518
5519
case 27:
5520
cancel();
5521
break;
5522
5523
case 38: //up arrow
5524
if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart)){
5525
e.stopImmediatePropagation();
5526
e.stopPropagation();
5527
}
5528
5529
break;
5530
5531
case 40: //down arrow
5532
if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart !== input.value.length)){
5533
e.stopImmediatePropagation();
5534
e.stopPropagation();
5535
}
5536
break;
5537
5538
case 35:
5539
case 36:
5540
e.stopPropagation();
5541
break;
5542
}
5543
});
5544
5545
if(editorParams.mask){
5546
maskInput(input, editorParams);
5547
}
5548
5549
return input;
5550
}
5551
5552
//input element with type of number
5553
function number(cell, onRendered, success, cancel, editorParams){
5554
var cellValue = cell.getValue(),
5555
vertNav = editorParams.verticalNavigation || "editor",
5556
input = document.createElement("input");
5557
5558
input.setAttribute("type", "number");
5559
5560
if(typeof editorParams.max != "undefined"){
5561
input.setAttribute("max", editorParams.max);
5562
}
5563
5564
if(typeof editorParams.min != "undefined"){
5565
input.setAttribute("min", editorParams.min);
5566
}
5567
5568
if(typeof editorParams.step != "undefined"){
5569
input.setAttribute("step", editorParams.step);
5570
}
5571
5572
//create and style input
5573
input.style.padding = "4px";
5574
input.style.width = "100%";
5575
input.style.boxSizing = "border-box";
5576
5577
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5578
for (let key in editorParams.elementAttributes){
5579
if(key.charAt(0) == "+"){
5580
key = key.slice(1);
5581
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5582
}else {
5583
input.setAttribute(key, editorParams.elementAttributes[key]);
5584
}
5585
}
5586
}
5587
5588
input.value = cellValue;
5589
5590
var blurFunc = function(e){
5591
onChange();
5592
};
5593
5594
onRendered(function () {
5595
if(cell.getType() === "cell"){
5596
//submit new value on blur
5597
input.removeEventListener("blur", blurFunc);
5598
5599
input.focus({preventScroll: true});
5600
input.style.height = "100%";
5601
5602
//submit new value on blur
5603
input.addEventListener("blur", blurFunc);
5604
5605
if(editorParams.selectContents){
5606
input.select();
5607
}
5608
}
5609
});
5610
5611
function onChange(){
5612
var value = input.value;
5613
5614
if(!isNaN(value) && value !==""){
5615
value = Number(value);
5616
}
5617
5618
if(value !== cellValue){
5619
if(success(value)){
5620
cellValue = value; //persist value if successfully validated incase editor is used as header filter
5621
}
5622
}else {
5623
cancel();
5624
}
5625
}
5626
5627
//submit new value on enter
5628
input.addEventListener("keydown", function(e){
5629
switch(e.keyCode){
5630
case 13:
5631
// case 9:
5632
onChange();
5633
break;
5634
5635
case 27:
5636
cancel();
5637
break;
5638
5639
case 38: //up arrow
5640
case 40: //down arrow
5641
if(vertNav == "editor"){
5642
e.stopImmediatePropagation();
5643
e.stopPropagation();
5644
}
5645
break;
5646
5647
case 35:
5648
case 36:
5649
e.stopPropagation();
5650
break;
5651
}
5652
});
5653
5654
if(editorParams.mask){
5655
maskInput(input, editorParams);
5656
}
5657
5658
return input;
5659
}
5660
5661
//input element with type of number
5662
function range(cell, onRendered, success, cancel, editorParams){
5663
var cellValue = cell.getValue(),
5664
input = document.createElement("input");
5665
5666
input.setAttribute("type", "range");
5667
5668
if (typeof editorParams.max != "undefined") {
5669
input.setAttribute("max", editorParams.max);
5670
}
5671
5672
if (typeof editorParams.min != "undefined") {
5673
input.setAttribute("min", editorParams.min);
5674
}
5675
5676
if (typeof editorParams.step != "undefined") {
5677
input.setAttribute("step", editorParams.step);
5678
}
5679
5680
//create and style input
5681
input.style.padding = "4px";
5682
input.style.width = "100%";
5683
input.style.boxSizing = "border-box";
5684
5685
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5686
for (let key in editorParams.elementAttributes){
5687
if(key.charAt(0) == "+"){
5688
key = key.slice(1);
5689
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5690
}else {
5691
input.setAttribute(key, editorParams.elementAttributes[key]);
5692
}
5693
}
5694
}
5695
5696
input.value = cellValue;
5697
5698
onRendered(function () {
5699
if(cell.getType() === "cell"){
5700
input.focus({preventScroll: true});
5701
input.style.height = "100%";
5702
}
5703
});
5704
5705
function onChange(){
5706
var value = input.value;
5707
5708
if(!isNaN(value) && value !==""){
5709
value = Number(value);
5710
}
5711
5712
if(value != cellValue){
5713
if(success(value)){
5714
cellValue = value; //persist value if successfully validated incase editor is used as header filter
5715
}
5716
}else {
5717
cancel();
5718
}
5719
}
5720
5721
//submit new value on blur
5722
input.addEventListener("blur", function(e){
5723
onChange();
5724
});
5725
5726
//submit new value on enter
5727
input.addEventListener("keydown", function(e){
5728
switch(e.keyCode){
5729
case 13:
5730
// case 9:
5731
onChange();
5732
break;
5733
5734
case 27:
5735
cancel();
5736
break;
5737
}
5738
});
5739
5740
return input;
5741
}
5742
5743
//input element
5744
function date(cell, onRendered, success, cancel, editorParams){
5745
var inputFormat = editorParams.format,
5746
vertNav = editorParams.verticalNavigation || "editor",
5747
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null;
5748
5749
//create and style input
5750
var cellValue = cell.getValue(),
5751
input = document.createElement("input");
5752
5753
function convertDate(value){
5754
var newDatetime;
5755
5756
if(DT.isDateTime(value)){
5757
newDatetime = value;
5758
}else if(inputFormat === "iso"){
5759
newDatetime = DT.fromISO(String(value));
5760
}else {
5761
newDatetime = DT.fromFormat(String(value), inputFormat);
5762
}
5763
5764
return newDatetime.toFormat("yyyy-MM-dd");
5765
}
5766
5767
input.type = "date";
5768
input.style.padding = "4px";
5769
input.style.width = "100%";
5770
input.style.boxSizing = "border-box";
5771
5772
if(editorParams.max){
5773
input.setAttribute("max", inputFormat ? convertDate(editorParams.max) : editorParams.max);
5774
}
5775
5776
if(editorParams.min){
5777
input.setAttribute("min", inputFormat ? convertDate(editorParams.min) : editorParams.min);
5778
}
5779
5780
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5781
for (let key in editorParams.elementAttributes){
5782
if(key.charAt(0) == "+"){
5783
key = key.slice(1);
5784
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5785
}else {
5786
input.setAttribute(key, editorParams.elementAttributes[key]);
5787
}
5788
}
5789
}
5790
5791
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
5792
5793
if(inputFormat){
5794
if(DT){
5795
cellValue = convertDate(cellValue);
5796
}else {
5797
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
5798
}
5799
}
5800
5801
input.value = cellValue;
5802
5803
onRendered(function(){
5804
if(cell.getType() === "cell"){
5805
input.focus({preventScroll: true});
5806
input.style.height = "100%";
5807
5808
if(editorParams.selectContents){
5809
input.select();
5810
}
5811
}
5812
});
5813
5814
function onChange(){
5815
var value = input.value,
5816
luxDate;
5817
5818
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
5819
5820
if(value && inputFormat){
5821
luxDate = DT.fromFormat(String(value), "yyyy-MM-dd");
5822
5823
switch(inputFormat){
5824
case true:
5825
value = luxDate;
5826
break;
5827
5828
case "iso":
5829
value = luxDate.toISO();
5830
break;
5831
5832
default:
5833
value = luxDate.toFormat(inputFormat);
5834
}
5835
}
5836
5837
if(success(value)){
5838
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
5839
}
5840
}else {
5841
cancel();
5842
}
5843
}
5844
5845
//submit new value on blur
5846
input.addEventListener("blur", function(e) {
5847
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
5848
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
5849
}
5850
});
5851
5852
//submit new value on enter
5853
input.addEventListener("keydown", function(e){
5854
switch(e.keyCode){
5855
// case 9:
5856
case 13:
5857
onChange();
5858
break;
5859
5860
case 27:
5861
cancel();
5862
break;
5863
5864
case 35:
5865
case 36:
5866
e.stopPropagation();
5867
break;
5868
5869
case 38: //up arrow
5870
case 40: //down arrow
5871
if(vertNav == "editor"){
5872
e.stopImmediatePropagation();
5873
e.stopPropagation();
5874
}
5875
break;
5876
}
5877
});
5878
5879
return input;
5880
}
5881
5882
//input element
5883
function time(cell, onRendered, success, cancel, editorParams){
5884
var inputFormat = editorParams.format,
5885
vertNav = editorParams.verticalNavigation || "editor",
5886
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null,
5887
newDatetime;
5888
5889
//create and style input
5890
var cellValue = cell.getValue(),
5891
input = document.createElement("input");
5892
5893
input.type = "time";
5894
input.style.padding = "4px";
5895
input.style.width = "100%";
5896
input.style.boxSizing = "border-box";
5897
5898
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
5899
for (let key in editorParams.elementAttributes){
5900
if(key.charAt(0) == "+"){
5901
key = key.slice(1);
5902
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
5903
}else {
5904
input.setAttribute(key, editorParams.elementAttributes[key]);
5905
}
5906
}
5907
}
5908
5909
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
5910
5911
if(inputFormat){
5912
if(DT){
5913
if(DT.isDateTime(cellValue)){
5914
newDatetime = cellValue;
5915
}else if(inputFormat === "iso"){
5916
newDatetime = DT.fromISO(String(cellValue));
5917
}else {
5918
newDatetime = DT.fromFormat(String(cellValue), inputFormat);
5919
}
5920
5921
cellValue = newDatetime.toFormat("hh:mm");
5922
5923
}else {
5924
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
5925
}
5926
}
5927
5928
input.value = cellValue;
5929
5930
onRendered(function(){
5931
if(cell.getType() == "cell"){
5932
input.focus({preventScroll: true});
5933
input.style.height = "100%";
5934
5935
if(editorParams.selectContents){
5936
input.select();
5937
}
5938
}
5939
});
5940
5941
function onChange(){
5942
var value = input.value,
5943
luxTime;
5944
5945
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
5946
5947
if(value && inputFormat){
5948
luxTime = DT.fromFormat(String(value), "hh:mm");
5949
5950
switch(inputFormat){
5951
case true:
5952
value = luxTime;
5953
break;
5954
5955
case "iso":
5956
value = luxTime.toISO();
5957
break;
5958
5959
default:
5960
value = luxTime.toFormat(inputFormat);
5961
}
5962
}
5963
5964
if(success(value)){
5965
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
5966
}
5967
}else {
5968
cancel();
5969
}
5970
}
5971
5972
//submit new value on blur
5973
input.addEventListener("blur", function(e) {
5974
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
5975
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
5976
}
5977
});
5978
5979
//submit new value on enter
5980
input.addEventListener("keydown", function(e){
5981
switch(e.keyCode){
5982
// case 9:
5983
case 13:
5984
onChange();
5985
break;
5986
5987
case 27:
5988
cancel();
5989
break;
5990
5991
case 35:
5992
case 36:
5993
e.stopPropagation();
5994
break;
5995
5996
case 38: //up arrow
5997
case 40: //down arrow
5998
if(vertNav == "editor"){
5999
e.stopImmediatePropagation();
6000
e.stopPropagation();
6001
}
6002
break;
6003
}
6004
});
6005
6006
return input;
6007
}
6008
6009
//input element
6010
function datetime(cell, onRendered, success, cancel, editorParams){
6011
var inputFormat = editorParams.format,
6012
vertNav = editorParams.verticalNavigation || "editor",
6013
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null,
6014
newDatetime;
6015
6016
//create and style input
6017
var cellValue = cell.getValue(),
6018
input = document.createElement("input");
6019
6020
input.type = "datetime-local";
6021
input.style.padding = "4px";
6022
input.style.width = "100%";
6023
input.style.boxSizing = "border-box";
6024
6025
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
6026
for (let key in editorParams.elementAttributes){
6027
if(key.charAt(0) == "+"){
6028
key = key.slice(1);
6029
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
6030
}else {
6031
input.setAttribute(key, editorParams.elementAttributes[key]);
6032
}
6033
}
6034
}
6035
6036
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
6037
6038
if(inputFormat){
6039
if(DT){
6040
if(DT.isDateTime(cellValue)){
6041
newDatetime = cellValue;
6042
}else if(inputFormat === "iso"){
6043
newDatetime = DT.fromISO(String(cellValue));
6044
}else {
6045
newDatetime = DT.fromFormat(String(cellValue), inputFormat);
6046
}
6047
6048
cellValue = newDatetime.toFormat("yyyy-MM-dd") + "T" + newDatetime.toFormat("hh:mm");
6049
}else {
6050
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
6051
}
6052
}
6053
6054
input.value = cellValue;
6055
6056
onRendered(function(){
6057
if(cell.getType() === "cell"){
6058
input.focus({preventScroll: true});
6059
input.style.height = "100%";
6060
6061
if(editorParams.selectContents){
6062
input.select();
6063
}
6064
}
6065
});
6066
6067
function onChange(){
6068
var value = input.value,
6069
luxDateTime;
6070
6071
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
6072
6073
if(value && inputFormat){
6074
luxDateTime = DT.fromISO(String(value));
6075
6076
switch(inputFormat){
6077
case true:
6078
value = luxDateTime;
6079
break;
6080
6081
case "iso":
6082
value = luxDateTime.toISO();
6083
break;
6084
6085
default:
6086
value = luxDateTime.toFormat(inputFormat);
6087
}
6088
}
6089
6090
if(success(value)){
6091
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
6092
}
6093
}else {
6094
cancel();
6095
}
6096
}
6097
6098
//submit new value on blur
6099
input.addEventListener("blur", function(e) {
6100
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
6101
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
6102
}
6103
});
6104
6105
//submit new value on enter
6106
input.addEventListener("keydown", function(e){
6107
switch(e.keyCode){
6108
// case 9:
6109
case 13:
6110
onChange();
6111
break;
6112
6113
case 27:
6114
cancel();
6115
break;
6116
6117
case 35:
6118
case 36:
6119
e.stopPropagation();
6120
break;
6121
6122
case 38: //up arrow
6123
case 40: //down arrow
6124
if(vertNav == "editor"){
6125
e.stopImmediatePropagation();
6126
e.stopPropagation();
6127
}
6128
break;
6129
}
6130
});
6131
6132
return input;
6133
}
6134
6135
class Edit{
6136
constructor(editor, cell, onRendered, success, cancel, editorParams){
6137
this.edit = editor;
6138
this.table = editor.table;
6139
this.cell = cell;
6140
this.params = this._initializeParams(editorParams);
6141
6142
this.data = [];
6143
this.displayItems = [];
6144
this.currentItems = [];
6145
this.focusedItem = null;
6146
6147
this.input = this._createInputElement();
6148
this.listEl = this._createListElement();
6149
6150
this.initialValues = null;
6151
6152
this.isFilter = cell.getType() === "header";
6153
6154
this.filterTimeout = null;
6155
this.filtered = false;
6156
this.typing = false;
6157
6158
this.values = [];
6159
this.popup = null;
6160
6161
this.listIteration = 0;
6162
6163
this.lastAction="";
6164
this.filterTerm="";
6165
6166
this.blurable = true;
6167
6168
this.actions = {
6169
success:success,
6170
cancel:cancel
6171
};
6172
6173
this._deprecatedOptionsCheck();
6174
this._initializeValue();
6175
6176
onRendered(this._onRendered.bind(this));
6177
}
6178
6179
_deprecatedOptionsCheck(){
6180
if(this.params.listItemFormatter){
6181
this.cell.getTable().deprecationAdvisor.msg("The listItemFormatter editor param has been deprecated, please see the latest editor documentation for updated options");
6182
}
6183
6184
if(this.params.sortValuesList){
6185
this.cell.getTable().deprecationAdvisor.msg("The sortValuesList editor param has been deprecated, please see the latest editor documentation for updated options");
6186
}
6187
6188
if(this.params.searchFunc){
6189
this.cell.getTable().deprecationAdvisor.msg("The searchFunc editor param has been deprecated, please see the latest editor documentation for updated options");
6190
}
6191
6192
if(this.params.searchingPlaceholder){
6193
this.cell.getTable().deprecationAdvisor.msg("The searchingPlaceholder editor param has been deprecated, please see the latest editor documentation for updated options");
6194
}
6195
}
6196
6197
_initializeValue(){
6198
var initialValue = this.cell.getValue();
6199
6200
if(typeof initialValue === "undefined" && typeof this.params.defaultValue !== "undefined"){
6201
initialValue = this.params.defaultValue;
6202
}
6203
6204
this.initialValues = this.params.multiselect ? initialValue : [initialValue];
6205
6206
if(this.isFilter){
6207
this.input.value = this.initialValues ? this.initialValues.join(",") : "";
6208
this.headerFilterInitialListGen();
6209
}
6210
}
6211
6212
_onRendered(){
6213
var cellEl = this.cell.getElement();
6214
6215
function clickStop(e){
6216
e.stopPropagation();
6217
}
6218
6219
if(!this.isFilter){
6220
this.input.style.height = "100%";
6221
this.input.focus({preventScroll: true});
6222
}
6223
6224
6225
cellEl.addEventListener("click", clickStop);
6226
6227
setTimeout(() => {
6228
cellEl.removeEventListener("click", clickStop);
6229
}, 1000);
6230
6231
this.input.addEventListener("mousedown", this._preventPopupBlur.bind(this));
6232
}
6233
6234
_createListElement(){
6235
var listEl = document.createElement("div");
6236
listEl.classList.add("tabulator-edit-list");
6237
6238
listEl.addEventListener("mousedown", this._preventBlur.bind(this));
6239
listEl.addEventListener("keydown", this._inputKeyDown.bind(this));
6240
6241
return listEl;
6242
}
6243
6244
_setListWidth(){
6245
var element = this.isFilter ? this.input : this.cell.getElement();
6246
6247
this.listEl.style.minWidth = element.offsetWidth + "px";
6248
6249
if(this.params.maxWidth){
6250
if(this.params.maxWidth === true){
6251
this.listEl.style.maxWidth = element.offsetWidth + "px";
6252
}else if(typeof this.params.maxWidth === "number"){
6253
this.listEl.style.maxWidth = this.params.maxWidth + "px";
6254
}else {
6255
this.listEl.style.maxWidth = this.params.maxWidth;
6256
}
6257
}
6258
6259
}
6260
6261
_createInputElement(){
6262
var attribs = this.params.elementAttributes;
6263
var input = document.createElement("input");
6264
6265
input.setAttribute("type", this.params.clearable ? "search" : "text");
6266
6267
input.style.padding = "4px";
6268
input.style.width = "100%";
6269
input.style.boxSizing = "border-box";
6270
6271
if(!this.params.autocomplete){
6272
input.style.cursor = "default";
6273
input.style.caretColor = "transparent";
6274
// input.readOnly = (this.edit.currentCell != false);
6275
}
6276
6277
if(attribs && typeof attribs == "object"){
6278
for (let key in attribs){
6279
if(key.charAt(0) == "+"){
6280
key = key.slice(1);
6281
input.setAttribute(key, input.getAttribute(key) + attribs["+" + key]);
6282
}else {
6283
input.setAttribute(key, attribs[key]);
6284
}
6285
}
6286
}
6287
6288
if(this.params.mask){
6289
maskInput(input, this.params);
6290
}
6291
6292
this._bindInputEvents(input);
6293
6294
return input;
6295
}
6296
6297
_initializeParams(params){
6298
var valueKeys = ["values", "valuesURL", "valuesLookup"],
6299
valueCheck;
6300
6301
params = Object.assign({}, params);
6302
6303
params.verticalNavigation = params.verticalNavigation || "editor";
6304
params.placeholderLoading = typeof params.placeholderLoading === "undefined" ? "Searching ..." : params.placeholderLoading;
6305
params.placeholderEmpty = typeof params.placeholderEmpty === "undefined" ? "No Results Found" : params.placeholderEmpty;
6306
params.filterDelay = typeof params.filterDelay === "undefined" ? 300 : params.filterDelay;
6307
6308
params.emptyValue = Object.keys(params).includes("emptyValue") ? params.emptyValue : "";
6309
6310
valueCheck = Object.keys(params).filter(key => valueKeys.includes(key)).length;
6311
6312
if(!valueCheck){
6313
console.warn("list editor config error - either the values, valuesURL, or valuesLookup option must be set");
6314
}else if(valueCheck > 1){
6315
console.warn("list editor config error - only one of the values, valuesURL, or valuesLookup options can be set on the same editor");
6316
}
6317
6318
if(params.autocomplete){
6319
if(params.multiselect){
6320
params.multiselect = false;
6321
console.warn("list editor config error - multiselect option is not available when autocomplete is enabled");
6322
}
6323
}else {
6324
if(params.freetext){
6325
params.freetext = false;
6326
console.warn("list editor config error - freetext option is only available when autocomplete is enabled");
6327
}
6328
6329
if(params.filterFunc){
6330
params.filterFunc = false;
6331
console.warn("list editor config error - filterFunc option is only available when autocomplete is enabled");
6332
}
6333
6334
if(params.filterRemote){
6335
params.filterRemote = false;
6336
console.warn("list editor config error - filterRemote option is only available when autocomplete is enabled");
6337
}
6338
6339
if(params.mask){
6340
params.mask = false;
6341
console.warn("list editor config error - mask option is only available when autocomplete is enabled");
6342
}
6343
6344
if(params.allowEmpty){
6345
params.allowEmpty = false;
6346
console.warn("list editor config error - allowEmpty option is only available when autocomplete is enabled");
6347
}
6348
6349
if(params.listOnEmpty){
6350
params.listOnEmpty = false;
6351
console.warn("list editor config error - listOnEmpty option is only available when autocomplete is enabled");
6352
}
6353
}
6354
6355
if(params.filterRemote && !(typeof params.valuesLookup === "function" || params.valuesURL)){
6356
params.filterRemote = false;
6357
console.warn("list editor config error - filterRemote option should only be used when values list is populated from a remote source");
6358
}
6359
return params;
6360
}
6361
//////////////////////////////////////
6362
////////// Event Handling ////////////
6363
//////////////////////////////////////
6364
6365
_bindInputEvents(input){
6366
input.addEventListener("focus", this._inputFocus.bind(this));
6367
input.addEventListener("click", this._inputClick.bind(this));
6368
input.addEventListener("blur", this._inputBlur.bind(this));
6369
input.addEventListener("keydown", this._inputKeyDown.bind(this));
6370
input.addEventListener("search", this._inputSearch.bind(this));
6371
6372
if(this.params.autocomplete){
6373
input.addEventListener("keyup", this._inputKeyUp.bind(this));
6374
}
6375
}
6376
6377
6378
_inputFocus(e){
6379
this.rebuildOptionsList();
6380
}
6381
6382
_filter(){
6383
if(this.params.filterRemote){
6384
clearTimeout(this.filterTimeout);
6385
6386
this.filterTimeout = setTimeout(() => {
6387
this.rebuildOptionsList();
6388
}, this.params.filterDelay);
6389
}else {
6390
this._filterList();
6391
}
6392
}
6393
6394
_inputClick(e){
6395
e.stopPropagation();
6396
}
6397
6398
_inputBlur(e){
6399
if(this.blurable){
6400
if(this.popup){
6401
this.popup.hide();
6402
}else {
6403
this._resolveValue(true);
6404
}
6405
}
6406
}
6407
6408
_inputSearch(){
6409
this._clearChoices();
6410
}
6411
6412
_inputKeyDown(e){
6413
switch(e.keyCode){
6414
6415
case 38: //up arrow
6416
this._keyUp(e);
6417
break;
6418
6419
case 40: //down arrow
6420
this._keyDown(e);
6421
break;
6422
6423
case 37: //left arrow
6424
case 39: //right arrow
6425
this._keySide(e);
6426
break;
6427
6428
case 13: //enter
6429
this._keyEnter();
6430
break;
6431
6432
case 27: //escape
6433
this._keyEsc();
6434
break;
6435
6436
case 36: //home
6437
case 35: //end
6438
this._keyHomeEnd(e);
6439
break;
6440
6441
case 9: //tab
6442
this._keyTab(e);
6443
break;
6444
6445
default:
6446
this._keySelectLetter(e);
6447
}
6448
}
6449
6450
_inputKeyUp(e){
6451
switch(e.keyCode){
6452
case 38: //up arrow
6453
case 37: //left arrow
6454
case 39: //up arrow
6455
case 40: //right arrow
6456
case 13: //enter
6457
case 27: //escape
6458
break;
6459
6460
default:
6461
this._keyAutoCompLetter(e);
6462
}
6463
}
6464
6465
_preventPopupBlur(){
6466
if(this.popup){
6467
this.popup.blockHide();
6468
}
6469
6470
setTimeout(() =>{
6471
if(this.popup){
6472
this.popup.restoreHide();
6473
}
6474
}, 10);
6475
}
6476
6477
_preventBlur(){
6478
this.blurable = false;
6479
6480
setTimeout(() =>{
6481
this.blurable = true;
6482
}, 10);
6483
}
6484
6485
//////////////////////////////////////
6486
//////// Keyboard Navigation /////////
6487
//////////////////////////////////////
6488
6489
_keyTab(e){
6490
if(this.params.autocomplete && this.lastAction === "typing"){
6491
this._resolveValue(true);
6492
}else {
6493
if(this.focusedItem){
6494
this._chooseItem(this.focusedItem, true);
6495
}
6496
}
6497
}
6498
6499
_keyUp(e){
6500
var index = this.displayItems.indexOf(this.focusedItem);
6501
6502
if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index)){
6503
e.stopImmediatePropagation();
6504
e.stopPropagation();
6505
e.preventDefault();
6506
6507
if(index > 0){
6508
this._focusItem(this.displayItems[index - 1]);
6509
}
6510
}
6511
}
6512
6513
_keyDown(e){
6514
var index = this.displayItems.indexOf(this.focusedItem);
6515
6516
if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index < this.displayItems.length - 1)){
6517
e.stopImmediatePropagation();
6518
e.stopPropagation();
6519
e.preventDefault();
6520
6521
if(index < this.displayItems.length - 1){
6522
if(index == -1){
6523
this._focusItem(this.displayItems[0]);
6524
}else {
6525
this._focusItem(this.displayItems[index + 1]);
6526
}
6527
}
6528
}
6529
}
6530
6531
_keySide(e){
6532
if(!this.params.autocomplete){
6533
e.stopImmediatePropagation();
6534
e.stopPropagation();
6535
e.preventDefault();
6536
}
6537
}
6538
6539
_keyEnter(e){
6540
if(this.params.autocomplete && this.lastAction === "typing"){
6541
this._resolveValue(true);
6542
}else {
6543
if(this.focusedItem){
6544
this._chooseItem(this.focusedItem);
6545
}
6546
}
6547
}
6548
6549
_keyEsc(e){
6550
this._cancel();
6551
}
6552
6553
_keyHomeEnd(e){
6554
if(this.params.autocomplete){
6555
//prevent table navigation while using input element
6556
e.stopImmediatePropagation();
6557
}
6558
}
6559
6560
_keySelectLetter(e){
6561
if(!this.params.autocomplete){
6562
// if(this.edit.currentCell === false){
6563
e.preventDefault();
6564
// }
6565
6566
if(e.keyCode >= 38 && e.keyCode <= 90){
6567
this._scrollToValue(e.keyCode);
6568
}
6569
}
6570
}
6571
6572
_keyAutoCompLetter(e){
6573
this._filter();
6574
this.lastAction = "typing";
6575
this.typing = true;
6576
}
6577
6578
6579
_scrollToValue(char){
6580
clearTimeout(this.filterTimeout);
6581
6582
var character = String.fromCharCode(char).toLowerCase();
6583
this.filterTerm += character.toLowerCase();
6584
6585
var match = this.displayItems.find((item) => {
6586
return typeof item.label !== "undefined" && item.label.toLowerCase().startsWith(this.filterTerm);
6587
});
6588
6589
if(match){
6590
this._focusItem(match);
6591
}
6592
6593
this.filterTimeout = setTimeout(() => {
6594
this.filterTerm = "";
6595
}, 800);
6596
}
6597
6598
_focusItem(item){
6599
this.lastAction = "focus";
6600
6601
if(this.focusedItem && this.focusedItem.element){
6602
this.focusedItem.element.classList.remove("focused");
6603
}
6604
6605
this.focusedItem = item;
6606
6607
if(item && item.element){
6608
item.element.classList.add("focused");
6609
item.element.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'});
6610
}
6611
}
6612
6613
6614
//////////////////////////////////////
6615
/////// Data List Generation /////////
6616
//////////////////////////////////////
6617
headerFilterInitialListGen(){
6618
this._generateOptions(true);
6619
}
6620
6621
rebuildOptionsList(){
6622
this._generateOptions()
6623
.then(this._sortOptions.bind(this))
6624
.then(this._buildList.bind(this))
6625
.then(this._showList.bind(this))
6626
.catch((e) => {
6627
if(!Number.isInteger(e)){
6628
console.error("List generation error", e);
6629
}
6630
});
6631
}
6632
6633
_filterList(){
6634
this._buildList(this._filterOptions());
6635
this._showList();
6636
}
6637
6638
_generateOptions(silent){
6639
var values = [];
6640
var iteration = ++ this.listIteration;
6641
6642
this.filtered = false;
6643
6644
if(this.params.values){
6645
values = this.params.values;
6646
}else if (this.params.valuesURL){
6647
values = this._ajaxRequest(this.params.valuesURL, this.input.value);
6648
}else {
6649
if(typeof this.params.valuesLookup === "function"){
6650
values = this.params.valuesLookup(this.cell, this.input.value);
6651
}else if(this.params.valuesLookup){
6652
values = this._uniqueColumnValues(this.params.valuesLookupField);
6653
}
6654
}
6655
6656
if(values instanceof Promise){
6657
if(!silent){
6658
this._addPlaceholder(this.params.placeholderLoading);
6659
}
6660
6661
return values.then()
6662
.then((responseValues) => {
6663
if(this.listIteration === iteration){
6664
return this._parseList(responseValues);
6665
}else {
6666
return Promise.reject(iteration);
6667
}
6668
});
6669
}else {
6670
return Promise.resolve(this._parseList(values));
6671
}
6672
}
6673
6674
_addPlaceholder(contents){
6675
var placeholder = document.createElement("div");
6676
6677
if(typeof contents === "function"){
6678
contents = contents(this.cell.getComponent(), this.listEl);
6679
}
6680
6681
if(contents){
6682
this._clearList();
6683
6684
if(contents instanceof HTMLElement){
6685
placeholder = contents;
6686
}else {
6687
placeholder.classList.add("tabulator-edit-list-placeholder");
6688
placeholder.innerHTML = contents;
6689
}
6690
6691
this.listEl.appendChild(placeholder);
6692
6693
this._showList();
6694
}
6695
}
6696
6697
_ajaxRequest(url, term){
6698
var params = this.params.filterRemote ? {term:term} : {};
6699
url = urlBuilder(url, {}, params);
6700
6701
return fetch(url)
6702
.then((response)=>{
6703
if(response.ok) {
6704
return response.json()
6705
.catch((error)=>{
6706
console.warn("List Ajax Load Error - Invalid JSON returned", error);
6707
return Promise.reject(error);
6708
});
6709
}else {
6710
console.error("List Ajax Load Error - Connection Error: " + response.status, response.statusText);
6711
return Promise.reject(response);
6712
}
6713
})
6714
.catch((error)=>{
6715
console.error("List Ajax Load Error - Connection Error: ", error);
6716
return Promise.reject(error);
6717
});
6718
}
6719
6720
_uniqueColumnValues(field){
6721
var output = {},
6722
data = this.table.getData(this.params.valuesLookup),
6723
column;
6724
6725
if(field){
6726
column = this.table.columnManager.getColumnByField(field);
6727
}else {
6728
column = this.cell.getColumn()._getSelf();
6729
}
6730
6731
if(column){
6732
data.forEach((row) => {
6733
var val = column.getFieldValue(row);
6734
6735
if(val !== null && typeof val !== "undefined" && val !== ""){
6736
output[val] = true;
6737
}
6738
});
6739
}else {
6740
console.warn("unable to find matching column to create select lookup list:", field);
6741
output = [];
6742
}
6743
6744
return Object.keys(output);
6745
}
6746
6747
6748
_parseList(inputValues){
6749
var data = [];
6750
6751
if(!Array.isArray(inputValues)){
6752
inputValues = Object.entries(inputValues).map(([key, value]) => {
6753
return {
6754
label:value,
6755
value:key,
6756
};
6757
});
6758
}
6759
6760
inputValues.forEach((value) => {
6761
if(typeof value !== "object"){
6762
value = {
6763
label:value,
6764
value:value,
6765
};
6766
}
6767
6768
this._parseListItem(value, data, 0);
6769
});
6770
6771
if(!this.currentItems.length && this.params.freetext){
6772
this.input.value = this.initialValues;
6773
this.typing = true;
6774
this.lastAction = "typing";
6775
}
6776
6777
this.data = data;
6778
6779
return data;
6780
}
6781
6782
_parseListItem(option, data, level){
6783
var item = {};
6784
6785
if(option.options){
6786
item = this._parseListGroup(option, level + 1);
6787
}else {
6788
item = {
6789
label:option.label,
6790
value:option.value,
6791
itemParams:option.itemParams,
6792
elementAttributes: option.elementAttributes,
6793
element:false,
6794
selected:false,
6795
visible:true,
6796
level:level,
6797
original:option,
6798
};
6799
6800
if(this.initialValues && this.initialValues.indexOf(option.value) > -1){
6801
this._chooseItem(item, true);
6802
}
6803
}
6804
6805
data.push(item);
6806
}
6807
6808
_parseListGroup(option, level){
6809
var item = {
6810
label:option.label,
6811
group:true,
6812
itemParams:option.itemParams,
6813
elementAttributes:option.elementAttributes,
6814
element:false,
6815
visible:true,
6816
level:level,
6817
options:[],
6818
original:option,
6819
};
6820
6821
option.options.forEach((child) => {
6822
this._parseListItem(child, item.options, level);
6823
});
6824
6825
return item;
6826
}
6827
6828
_sortOptions(options){
6829
var sorter;
6830
6831
if(this.params.sort){
6832
sorter = typeof this.params.sort === "function" ? this.params.sort : this._defaultSortFunction.bind(this);
6833
6834
this._sortGroup(sorter, options);
6835
}
6836
6837
return options;
6838
}
6839
6840
_sortGroup(sorter, options){
6841
options.sort((a,b) => {
6842
return sorter(a.label, b.label, a.value, b.value, a.original, b.original);
6843
});
6844
6845
options.forEach((option) => {
6846
if(option.group){
6847
this._sortGroup(sorter, option.options);
6848
}
6849
});
6850
}
6851
6852
_defaultSortFunction(as, bs){
6853
var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
6854
var emptyAlign = 0;
6855
6856
if(this.params.sort === "desc"){
6857
[as, bs] = [bs, as];
6858
}
6859
6860
//handle empty values
6861
if(!as && as!== 0){
6862
emptyAlign = !bs && bs!== 0 ? 0 : -1;
6863
}else if(!bs && bs!== 0){
6864
emptyAlign = 1;
6865
}else {
6866
if(isFinite(as) && isFinite(bs)) return as - bs;
6867
a = String(as).toLowerCase();
6868
b = String(bs).toLowerCase();
6869
if(a === b) return 0;
6870
if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
6871
a = a.match(rx);
6872
b = b.match(rx);
6873
L = a.length > b.length ? b.length : a.length;
6874
while(i < L){
6875
a1= a[i];
6876
b1= b[i++];
6877
if(a1 !== b1){
6878
if(isFinite(a1) && isFinite(b1)){
6879
if(a1.charAt(0) === "0") a1 = "." + a1;
6880
if(b1.charAt(0) === "0") b1 = "." + b1;
6881
return a1 - b1;
6882
}
6883
else return a1 > b1 ? 1 : -1;
6884
}
6885
}
6886
6887
return a.length > b.length;
6888
}
6889
6890
return emptyAlign;
6891
}
6892
6893
_filterOptions(){
6894
var filterFunc = this.params.filterFunc || this._defaultFilterFunc,
6895
term = this.input.value;
6896
6897
if(term){
6898
this.filtered = true;
6899
6900
this.data.forEach((item) => {
6901
this._filterItem(filterFunc, term, item);
6902
});
6903
}else {
6904
this.filtered = false;
6905
}
6906
6907
return this.data;
6908
}
6909
6910
_filterItem(func, term, item){
6911
var matches = false;
6912
6913
if(!item.group){
6914
item.visible = func(term, item.label, item.value, item.original);
6915
}else {
6916
item.options.forEach((option) => {
6917
if(this._filterItem(func, term, option)){
6918
matches = true;
6919
}
6920
});
6921
6922
item.visible = matches;
6923
}
6924
6925
return item.visible;
6926
}
6927
6928
_defaultFilterFunc(term, label, value, item){
6929
term = String(term).toLowerCase();
6930
6931
if(label !== null && typeof label !== "undefined"){
6932
if(String(label).toLowerCase().indexOf(term) > -1 || String(value).toLowerCase().indexOf(term) > -1){
6933
return true;
6934
}
6935
}
6936
6937
return false;
6938
}
6939
6940
//////////////////////////////////////
6941
/////////// Display List /////////////
6942
//////////////////////////////////////
6943
6944
_clearList(){
6945
while(this.listEl.firstChild) this.listEl.removeChild(this.listEl.firstChild);
6946
6947
this.displayItems = [];
6948
}
6949
6950
_buildList(data){
6951
this._clearList();
6952
6953
data.forEach((option) => {
6954
this._buildItem(option);
6955
});
6956
6957
if(!this.displayItems.length){
6958
this._addPlaceholder(this.params.placeholderEmpty);
6959
}
6960
}
6961
6962
_buildItem(item){
6963
var el = item.element,
6964
contents;
6965
6966
if(!this.filtered || item.visible){
6967
6968
if(!el){
6969
el = document.createElement("div");
6970
el.tabIndex = 0;
6971
6972
contents = this.params.itemFormatter ? this.params.itemFormatter(item.label, item.value, item.original, el) : item.label;
6973
6974
if(contents instanceof HTMLElement){
6975
el.appendChild(contents);
6976
}else {
6977
el.innerHTML = contents;
6978
}
6979
6980
if(item.group){
6981
el.classList.add("tabulator-edit-list-group");
6982
}else {
6983
el.classList.add("tabulator-edit-list-item");
6984
}
6985
6986
el.classList.add("tabulator-edit-list-group-level-" + item.level);
6987
6988
if(item.elementAttributes && typeof item.elementAttributes == "object"){
6989
for (let key in item.elementAttributes){
6990
if(key.charAt(0) == "+"){
6991
key = key.slice(1);
6992
el.setAttribute(key, this.input.getAttribute(key) + item.elementAttributes["+" + key]);
6993
}else {
6994
el.setAttribute(key, item.elementAttributes[key]);
6995
}
6996
}
6997
}
6998
6999
if(item.group){
7000
el.addEventListener("click", this._groupClick.bind(this, item));
7001
}else {
7002
el.addEventListener("click", this._itemClick.bind(this, item));
7003
}
7004
7005
el.addEventListener("mousedown", this._preventBlur.bind(this));
7006
7007
item.element = el;
7008
}
7009
7010
this._styleItem(item);
7011
7012
this.listEl.appendChild(el);
7013
7014
if(item.group){
7015
item.options.forEach((option) => {
7016
this._buildItem(option);
7017
});
7018
}else {
7019
this.displayItems.push(item);
7020
}
7021
}
7022
}
7023
7024
_showList(){
7025
var startVis = this.popup && this.popup.isVisible();
7026
7027
if(this.input.parentNode){
7028
if(this.params.autocomplete && this.input.value === "" && !this.params.listOnEmpty){
7029
if(this.popup){
7030
this.popup.hide(true);
7031
}
7032
return;
7033
}
7034
7035
this._setListWidth();
7036
7037
if(!this.popup){
7038
this.popup = this.edit.popup(this.listEl);
7039
}
7040
7041
this.popup.show(this.cell.getElement(), "bottom");
7042
7043
if(!startVis){
7044
setTimeout(() => {
7045
this.popup.hideOnBlur(this._resolveValue.bind(this, true));
7046
}, 10);
7047
}
7048
}
7049
}
7050
7051
_styleItem(item){
7052
if(item && item.element){
7053
if(item.selected){
7054
item.element.classList.add("active");
7055
}else {
7056
item.element.classList.remove("active");
7057
}
7058
}
7059
}
7060
7061
//////////////////////////////////////
7062
///////// User Interaction ///////////
7063
//////////////////////////////////////
7064
7065
_itemClick(item, e){
7066
e.stopPropagation();
7067
7068
this._chooseItem(item);
7069
}
7070
7071
_groupClick(item, e){
7072
e.stopPropagation();
7073
}
7074
7075
7076
//////////////////////////////////////
7077
////// Current Item Management ///////
7078
//////////////////////////////////////
7079
7080
_cancel(){
7081
this.popup.hide(true);
7082
this.actions.cancel();
7083
}
7084
7085
_clearChoices(){
7086
this.typing = true;
7087
7088
this.currentItems.forEach((item) => {
7089
item.selected = false;
7090
this._styleItem(item);
7091
});
7092
7093
this.currentItems = [];
7094
7095
this.focusedItem = null;
7096
}
7097
7098
_chooseItem(item, silent){
7099
var index;
7100
7101
this.typing = false;
7102
7103
if(this.params.multiselect){
7104
index = this.currentItems.indexOf(item);
7105
7106
if(index > -1){
7107
this.currentItems.splice(index, 1);
7108
item.selected = false;
7109
}else {
7110
this.currentItems.push(item);
7111
item.selected = true;
7112
}
7113
7114
this.input.value = this.currentItems.map(item => item.label).join(",");
7115
7116
this._styleItem(item);
7117
7118
}else {
7119
this.currentItems = [item];
7120
item.selected = true;
7121
7122
this.input.value = item.label;
7123
7124
this._styleItem(item);
7125
7126
if(!silent){
7127
this._resolveValue();
7128
}
7129
}
7130
7131
this._focusItem(item);
7132
}
7133
7134
_resolveValue(blur){
7135
var output, initialValue;
7136
7137
if(this.popup){
7138
this.popup.hide(true);
7139
}
7140
7141
if(this.params.multiselect){
7142
output = this.currentItems.map(item => item.value);
7143
}else {
7144
if(blur && this.params.autocomplete && this.typing){
7145
if(this.params.freetext || (this.params.allowEmpty && this.input.value === "")){
7146
output = this.input.value;
7147
}else {
7148
this.actions.cancel();
7149
return;
7150
}
7151
}else {
7152
if(this.currentItems[0]){
7153
output = this.currentItems[0].value;
7154
}else {
7155
initialValue = Array.isArray(this.initialValues) ? this.initialValues[0] : this.initialValues;
7156
7157
if(initialValue === null || typeof initialValue === "undefined" || initialValue === ""){
7158
output = initialValue;
7159
}else {
7160
output = this.params.emptyValue;
7161
}
7162
}
7163
7164
}
7165
}
7166
7167
if(output === ""){
7168
output = this.params.emptyValue;
7169
}
7170
7171
this.actions.success(output);
7172
7173
if(this.isFilter){
7174
this.initialValues = output && !Array.isArray(output) ? [output] : output;
7175
this.currentItems = [];
7176
}
7177
}
7178
7179
}
7180
7181
function select(cell, onRendered, success, cancel, editorParams){
7182
7183
this.deprecationMsg("The select editor has been deprecated, please use the new list editor");
7184
7185
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
7186
7187
return list.input;
7188
}
7189
7190
function list(cell, onRendered, success, cancel, editorParams){
7191
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
7192
7193
return list.input;
7194
}
7195
7196
function autocomplete(cell, onRendered, success, cancel, editorParams){
7197
7198
this.deprecationMsg("The autocomplete editor has been deprecated, please use the new list editor with the 'autocomplete' editorParam");
7199
7200
editorParams.autocomplete = true;
7201
7202
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
7203
7204
return list.input;
7205
}
7206
7207
//star rating
7208
function star(cell, onRendered, success, cancel, editorParams){
7209
var self = this,
7210
element = cell.getElement(),
7211
value = cell.getValue(),
7212
maxStars = element.getElementsByTagName("svg").length || 5,
7213
size = element.getElementsByTagName("svg")[0] ? element.getElementsByTagName("svg")[0].getAttribute("width") : 14,
7214
stars = [],
7215
starsHolder = document.createElement("div"),
7216
star = document.createElementNS('http://www.w3.org/2000/svg', "svg");
7217
7218
7219
//change star type
7220
function starChange(val){
7221
stars.forEach(function(star, i){
7222
if(i < val){
7223
if(self.table.browser == "ie"){
7224
star.setAttribute("class", "tabulator-star-active");
7225
}else {
7226
star.classList.replace("tabulator-star-inactive", "tabulator-star-active");
7227
}
7228
7229
star.innerHTML = '<polygon fill="#488CE9" stroke="#014AAE" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
7230
}else {
7231
if(self.table.browser == "ie"){
7232
star.setAttribute("class", "tabulator-star-inactive");
7233
}else {
7234
star.classList.replace("tabulator-star-active", "tabulator-star-inactive");
7235
}
7236
7237
star.innerHTML = '<polygon fill="#010155" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
7238
}
7239
});
7240
}
7241
7242
//build stars
7243
function buildStar(i){
7244
7245
var starHolder = document.createElement("span");
7246
var nextStar = star.cloneNode(true);
7247
7248
stars.push(nextStar);
7249
7250
starHolder.addEventListener("mouseenter", function(e){
7251
e.stopPropagation();
7252
e.stopImmediatePropagation();
7253
starChange(i);
7254
});
7255
7256
starHolder.addEventListener("mousemove", function(e){
7257
e.stopPropagation();
7258
e.stopImmediatePropagation();
7259
});
7260
7261
starHolder.addEventListener("click", function(e){
7262
e.stopPropagation();
7263
e.stopImmediatePropagation();
7264
success(i);
7265
element.blur();
7266
});
7267
7268
starHolder.appendChild(nextStar);
7269
starsHolder.appendChild(starHolder);
7270
7271
}
7272
7273
//handle keyboard navigation value change
7274
function changeValue(val){
7275
value = val;
7276
starChange(val);
7277
}
7278
7279
//style cell
7280
element.style.whiteSpace = "nowrap";
7281
element.style.overflow = "hidden";
7282
element.style.textOverflow = "ellipsis";
7283
7284
//style holding element
7285
starsHolder.style.verticalAlign = "middle";
7286
starsHolder.style.display = "inline-block";
7287
starsHolder.style.padding = "4px";
7288
7289
//style star
7290
star.setAttribute("width", size);
7291
star.setAttribute("height", size);
7292
star.setAttribute("viewBox", "0 0 512 512");
7293
star.setAttribute("xml:space", "preserve");
7294
star.style.padding = "0 1px";
7295
7296
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
7297
for (let key in editorParams.elementAttributes){
7298
if(key.charAt(0) == "+"){
7299
key = key.slice(1);
7300
starsHolder.setAttribute(key, starsHolder.getAttribute(key) + editorParams.elementAttributes["+" + key]);
7301
}else {
7302
starsHolder.setAttribute(key, editorParams.elementAttributes[key]);
7303
}
7304
}
7305
}
7306
7307
//create correct number of stars
7308
for(var i=1;i<= maxStars;i++){
7309
buildStar(i);
7310
}
7311
7312
//ensure value does not exceed number of stars
7313
value = Math.min(parseInt(value), maxStars);
7314
7315
// set initial styling of stars
7316
starChange(value);
7317
7318
starsHolder.addEventListener("mousemove", function(e){
7319
starChange(0);
7320
});
7321
7322
starsHolder.addEventListener("click", function(e){
7323
success(0);
7324
});
7325
7326
element.addEventListener("blur", function(e){
7327
cancel();
7328
});
7329
7330
//allow key based navigation
7331
element.addEventListener("keydown", function(e){
7332
switch(e.keyCode){
7333
case 39: //right arrow
7334
changeValue(value + 1);
7335
break;
7336
7337
case 37: //left arrow
7338
changeValue(value - 1);
7339
break;
7340
7341
case 13: //enter
7342
success(value);
7343
break;
7344
7345
case 27: //escape
7346
cancel();
7347
break;
7348
}
7349
});
7350
7351
return starsHolder;
7352
}
7353
7354
//draggable progress bar
7355
function progress(cell, onRendered, success, cancel, editorParams){
7356
var element = cell.getElement(),
7357
max = typeof editorParams.max === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("max")) || 100) : editorParams.max,
7358
min = typeof editorParams.min === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("min")) || 0) : editorParams.min,
7359
percent = (max - min) / 100,
7360
value = cell.getValue() || 0,
7361
handle = document.createElement("div"),
7362
bar = document.createElement("div"),
7363
mouseDrag, mouseDragWidth;
7364
7365
//set new value
7366
function updateValue(){
7367
var style = window.getComputedStyle(element, null);
7368
7369
var calcVal = (percent * Math.round(bar.offsetWidth / ((element.clientWidth - parseInt(style.getPropertyValue("padding-left")) - parseInt(style.getPropertyValue("padding-right")))/100))) + min;
7370
success(calcVal);
7371
element.setAttribute("aria-valuenow", calcVal);
7372
element.setAttribute("aria-label", value);
7373
}
7374
7375
//style handle
7376
handle.style.position = "absolute";
7377
handle.style.right = "0";
7378
handle.style.top = "0";
7379
handle.style.bottom = "0";
7380
handle.style.width = "5px";
7381
handle.classList.add("tabulator-progress-handle");
7382
7383
//style bar
7384
bar.style.display = "inline-block";
7385
bar.style.position = "relative";
7386
// bar.style.top = "8px";
7387
// bar.style.bottom = "8px";
7388
// bar.style.left = "4px";
7389
// bar.style.marginRight = "4px";
7390
bar.style.height = "100%";
7391
bar.style.backgroundColor = "#488CE9";
7392
bar.style.maxWidth = "100%";
7393
bar.style.minWidth = "0%";
7394
7395
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
7396
for (let key in editorParams.elementAttributes){
7397
if(key.charAt(0) == "+"){
7398
key = key.slice(1);
7399
bar.setAttribute(key, bar.getAttribute(key) + editorParams.elementAttributes["+" + key]);
7400
}else {
7401
bar.setAttribute(key, editorParams.elementAttributes[key]);
7402
}
7403
}
7404
}
7405
7406
//style cell
7407
element.style.padding = "4px 4px";
7408
7409
//make sure value is in range
7410
value = Math.min(parseFloat(value), max);
7411
value = Math.max(parseFloat(value), min);
7412
7413
//workout percentage
7414
value = Math.round((value - min) / percent);
7415
// bar.style.right = value + "%";
7416
bar.style.width = value + "%";
7417
7418
element.setAttribute("aria-valuemin", min);
7419
element.setAttribute("aria-valuemax", max);
7420
7421
bar.appendChild(handle);
7422
7423
handle.addEventListener("mousedown", function(e){
7424
mouseDrag = e.screenX;
7425
mouseDragWidth = bar.offsetWidth;
7426
});
7427
7428
handle.addEventListener("mouseover", function(){
7429
handle.style.cursor = "ew-resize";
7430
});
7431
7432
element.addEventListener("mousemove", function(e){
7433
if(mouseDrag){
7434
bar.style.width = (mouseDragWidth + e.screenX - mouseDrag) + "px";
7435
}
7436
});
7437
7438
element.addEventListener("mouseup", function(e){
7439
if(mouseDrag){
7440
e.stopPropagation();
7441
e.stopImmediatePropagation();
7442
7443
mouseDrag = false;
7444
mouseDragWidth = false;
7445
7446
updateValue();
7447
}
7448
});
7449
7450
//allow key based navigation
7451
element.addEventListener("keydown", function(e){
7452
switch(e.keyCode){
7453
case 39: //right arrow
7454
e.preventDefault();
7455
bar.style.width = (bar.clientWidth + element.clientWidth/100) + "px";
7456
break;
7457
7458
case 37: //left arrow
7459
e.preventDefault();
7460
bar.style.width = (bar.clientWidth - element.clientWidth/100) + "px";
7461
break;
7462
7463
case 9: //tab
7464
case 13: //enter
7465
updateValue();
7466
break;
7467
7468
case 27: //escape
7469
cancel();
7470
break;
7471
7472
}
7473
});
7474
7475
element.addEventListener("blur", function(){
7476
cancel();
7477
});
7478
7479
return bar;
7480
}
7481
7482
//checkbox
7483
function tickCross(cell, onRendered, success, cancel, editorParams){
7484
var value = cell.getValue(),
7485
input = document.createElement("input"),
7486
tristate = editorParams.tristate,
7487
indetermValue = typeof editorParams.indeterminateValue === "undefined" ? null : editorParams.indeterminateValue,
7488
indetermState = false,
7489
trueValueSet = Object.keys(editorParams).includes("trueValue"),
7490
falseValueSet = Object.keys(editorParams).includes("falseValue");
7491
7492
input.setAttribute("type", "checkbox");
7493
input.style.marginTop = "5px";
7494
input.style.boxSizing = "border-box";
7495
7496
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
7497
for (let key in editorParams.elementAttributes){
7498
if(key.charAt(0) == "+"){
7499
key = key.slice(1);
7500
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
7501
}else {
7502
input.setAttribute(key, editorParams.elementAttributes[key]);
7503
}
7504
}
7505
}
7506
7507
input.value = value;
7508
7509
if(tristate && (typeof value === "undefined" || value === indetermValue || value === "")){
7510
indetermState = true;
7511
input.indeterminate = true;
7512
}
7513
7514
if(this.table.browser != "firefox" && this.table.browser != "safari"){ //prevent blur issue on mac firefox
7515
onRendered(function(){
7516
if(cell.getType() === "cell"){
7517
input.focus({preventScroll: true});
7518
}
7519
});
7520
}
7521
7522
input.checked = trueValueSet ? value === editorParams.trueValue : (value === true || value === "true" || value === "True" || value === 1);
7523
7524
function setValue(blur){
7525
var checkedValue = input.checked;
7526
7527
if(trueValueSet && checkedValue){
7528
checkedValue = editorParams.trueValue;
7529
}else if(falseValueSet && !checkedValue){
7530
checkedValue = editorParams.falseValue;
7531
}
7532
7533
if(tristate){
7534
if(!blur){
7535
if(input.checked && !indetermState){
7536
input.checked = false;
7537
input.indeterminate = true;
7538
indetermState = true;
7539
return indetermValue;
7540
}else {
7541
indetermState = false;
7542
return checkedValue;
7543
}
7544
}else {
7545
if(indetermState){
7546
return indetermValue;
7547
}else {
7548
return checkedValue;
7549
}
7550
}
7551
}else {
7552
return checkedValue;
7553
}
7554
}
7555
7556
//submit new value on blur
7557
input.addEventListener("change", function(e){
7558
success(setValue());
7559
});
7560
7561
input.addEventListener("blur", function(e){
7562
success(setValue(true));
7563
});
7564
7565
//submit new value on enter
7566
input.addEventListener("keydown", function(e){
7567
if(e.keyCode == 13){
7568
success(setValue());
7569
}
7570
if(e.keyCode == 27){
7571
cancel();
7572
}
7573
});
7574
7575
return input;
7576
}
7577
7578
var defaultEditors = {
7579
input:input,
7580
textarea:textarea,
7581
number:number,
7582
range:range,
7583
date:date,
7584
time:time,
7585
datetime:datetime,
7586
select:select,
7587
list:list,
7588
autocomplete:autocomplete,
7589
star:star,
7590
progress:progress,
7591
tickCross:tickCross,
7592
};
7593
7594
class Edit$1 extends Module{
7595
7596
constructor(table){
7597
super(table);
7598
7599
this.currentCell = false; //hold currently editing cell
7600
this.mouseClick = false; //hold mousedown state to prevent click binding being overridden by editor opening
7601
this.recursionBlock = false; //prevent focus recursion
7602
this.invalidEdit = false;
7603
this.editedCells = [];
7604
7605
this.editors = Edit$1.editors;
7606
7607
this.registerColumnOption("editable");
7608
this.registerColumnOption("editor");
7609
this.registerColumnOption("editorParams");
7610
7611
this.registerColumnOption("cellEditing");
7612
this.registerColumnOption("cellEdited");
7613
this.registerColumnOption("cellEditCancelled");
7614
7615
this.registerTableFunction("getEditedCells", this.getEditedCells.bind(this));
7616
this.registerTableFunction("clearCellEdited", this.clearCellEdited.bind(this));
7617
this.registerTableFunction("navigatePrev", this.navigatePrev.bind(this));
7618
this.registerTableFunction("navigateNext", this.navigateNext.bind(this));
7619
this.registerTableFunction("navigateLeft", this.navigateLeft.bind(this));
7620
this.registerTableFunction("navigateRight", this.navigateRight.bind(this));
7621
this.registerTableFunction("navigateUp", this.navigateUp.bind(this));
7622
this.registerTableFunction("navigateDown", this.navigateDown.bind(this));
7623
7624
this.registerComponentFunction("cell", "isEdited", this.cellIsEdited.bind(this));
7625
this.registerComponentFunction("cell", "clearEdited", this.clearEdited.bind(this));
7626
this.registerComponentFunction("cell", "edit", this.editCell.bind(this));
7627
this.registerComponentFunction("cell", "cancelEdit", this.cellCancelEdit.bind(this));
7628
7629
this.registerComponentFunction("cell", "navigatePrev", this.navigatePrev.bind(this));
7630
this.registerComponentFunction("cell", "navigateNext", this.navigateNext.bind(this));
7631
this.registerComponentFunction("cell", "navigateLeft", this.navigateLeft.bind(this));
7632
this.registerComponentFunction("cell", "navigateRight", this.navigateRight.bind(this));
7633
this.registerComponentFunction("cell", "navigateUp", this.navigateUp.bind(this));
7634
this.registerComponentFunction("cell", "navigateDown", this.navigateDown.bind(this));
7635
}
7636
7637
initialize(){
7638
this.subscribe("cell-init", this.bindEditor.bind(this));
7639
this.subscribe("cell-delete", this.clearEdited.bind(this));
7640
this.subscribe("cell-value-changed", this.updateCellClass.bind(this));
7641
this.subscribe("column-layout", this.initializeColumnCheck.bind(this));
7642
this.subscribe("column-delete", this.columnDeleteCheck.bind(this));
7643
this.subscribe("row-deleting", this.rowDeleteCheck.bind(this));
7644
this.subscribe("row-layout", this.rowEditableCheck.bind(this));
7645
this.subscribe("data-refreshing", this.cancelEdit.bind(this));
7646
7647
this.subscribe("keybinding-nav-prev", this.navigatePrev.bind(this, undefined));
7648
this.subscribe("keybinding-nav-next", this.keybindingNavigateNext.bind(this));
7649
this.subscribe("keybinding-nav-left", this.navigateLeft.bind(this, undefined));
7650
this.subscribe("keybinding-nav-right", this.navigateRight.bind(this, undefined));
7651
this.subscribe("keybinding-nav-up", this.navigateUp.bind(this, undefined));
7652
this.subscribe("keybinding-nav-down", this.navigateDown.bind(this, undefined));
7653
}
7654
7655
7656
///////////////////////////////////
7657
////// Keybinding Functions ///////
7658
///////////////////////////////////
7659
7660
keybindingNavigateNext(e){
7661
var cell = this.currentCell,
7662
newRow = this.options("tabEndNewRow");
7663
7664
if(cell){
7665
if(!this.navigateNext(cell, e)){
7666
if(newRow){
7667
cell.getElement().firstChild.blur();
7668
7669
if(newRow === true){
7670
newRow = this.table.addRow({});
7671
}else {
7672
if(typeof newRow == "function"){
7673
newRow = this.table.addRow(newRow(cell.row.getComponent()));
7674
}else {
7675
newRow = this.table.addRow(Object.assign({}, newRow));
7676
}
7677
}
7678
7679
newRow.then(() => {
7680
setTimeout(() => {
7681
cell.getComponent().navigateNext();
7682
});
7683
});
7684
}
7685
}
7686
}
7687
}
7688
7689
///////////////////////////////////
7690
///////// Cell Functions //////////
7691
///////////////////////////////////
7692
7693
cellIsEdited(cell){
7694
return !! cell.modules.edit && cell.modules.edit.edited;
7695
}
7696
7697
cellCancelEdit(cell){
7698
if(cell === this.currentCell){
7699
this.table.modules.edit.cancelEdit();
7700
}else {
7701
console.warn("Cancel Editor Error - This cell is not currently being edited ");
7702
}
7703
}
7704
7705
7706
///////////////////////////////////
7707
///////// Table Functions /////////
7708
///////////////////////////////////
7709
updateCellClass(cell){
7710
if(this.allowEdit(cell)) {
7711
cell.getElement().classList.add("tabulator-editable");
7712
}
7713
else {
7714
cell.getElement().classList.remove("tabulator-editable");
7715
}
7716
}
7717
7718
clearCellEdited(cells){
7719
if(!cells){
7720
cells = this.table.modules.edit.getEditedCells();
7721
}
7722
7723
if(!Array.isArray(cells)){
7724
cells = [cells];
7725
}
7726
7727
cells.forEach((cell) => {
7728
this.table.modules.edit.clearEdited(cell._getSelf());
7729
});
7730
}
7731
7732
navigatePrev(cell = this.currentCell, e){
7733
var nextCell, prevRow;
7734
7735
if(cell){
7736
7737
if(e){
7738
e.preventDefault();
7739
}
7740
7741
nextCell = this.navigateLeft();
7742
7743
if(nextCell){
7744
return true;
7745
}else {
7746
prevRow = this.table.rowManager.prevDisplayRow(cell.row, true);
7747
7748
if(prevRow){
7749
nextCell = this.findPrevEditableCell(prevRow, prevRow.cells.length);
7750
7751
if(nextCell){
7752
nextCell.getComponent().edit();
7753
return true;
7754
}
7755
}
7756
}
7757
}
7758
7759
return false;
7760
}
7761
7762
navigateNext(cell = this.currentCell, e){
7763
var nextCell, nextRow;
7764
7765
if(cell){
7766
7767
if(e){
7768
e.preventDefault();
7769
}
7770
7771
nextCell = this.navigateRight();
7772
7773
if(nextCell){
7774
return true;
7775
}else {
7776
nextRow = this.table.rowManager.nextDisplayRow(cell.row, true);
7777
7778
if(nextRow){
7779
nextCell = this.findNextEditableCell(nextRow, -1);
7780
7781
if(nextCell){
7782
nextCell.getComponent().edit();
7783
return true;
7784
}
7785
}
7786
}
7787
}
7788
7789
return false;
7790
}
7791
7792
navigateLeft(cell = this.currentCell, e){
7793
var index, nextCell;
7794
7795
if(cell){
7796
7797
if(e){
7798
e.preventDefault();
7799
}
7800
7801
index = cell.getIndex();
7802
nextCell = this.findPrevEditableCell(cell.row, index);
7803
7804
if(nextCell){
7805
nextCell.getComponent().edit();
7806
return true;
7807
}
7808
}
7809
7810
return false;
7811
}
7812
7813
navigateRight(cell = this.currentCell, e){
7814
var index, nextCell;
7815
7816
if(cell){
7817
7818
if(e){
7819
e.preventDefault();
7820
}
7821
7822
index = cell.getIndex();
7823
nextCell = this.findNextEditableCell(cell.row, index);
7824
7825
if(nextCell){
7826
nextCell.getComponent().edit();
7827
return true;
7828
}
7829
}
7830
7831
return false;
7832
}
7833
7834
navigateUp(cell = this.currentCell, e){
7835
var index, nextRow;
7836
7837
if(cell){
7838
7839
if(e){
7840
e.preventDefault();
7841
}
7842
7843
index = cell.getIndex();
7844
nextRow = this.table.rowManager.prevDisplayRow(cell.row, true);
7845
7846
if(nextRow){
7847
nextRow.cells[index].getComponent().edit();
7848
return true;
7849
}
7850
}
7851
7852
return false;
7853
}
7854
7855
navigateDown(cell = this.currentCell, e){
7856
var index, nextRow;
7857
7858
if(cell){
7859
7860
if(e){
7861
e.preventDefault();
7862
}
7863
7864
index = cell.getIndex();
7865
nextRow = this.table.rowManager.nextDisplayRow(cell.row, true);
7866
7867
if(nextRow){
7868
nextRow.cells[index].getComponent().edit();
7869
return true;
7870
}
7871
}
7872
7873
return false;
7874
}
7875
7876
findNextEditableCell(row, index){
7877
var nextCell = false;
7878
7879
if(index < row.cells.length-1){
7880
for(var i = index+1; i < row.cells.length; i++){
7881
let cell = row.cells[i];
7882
7883
if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){
7884
let allowEdit = this.allowEdit(cell);
7885
7886
if(allowEdit){
7887
nextCell = cell;
7888
break;
7889
}
7890
}
7891
}
7892
}
7893
7894
return nextCell;
7895
}
7896
7897
findPrevEditableCell(row, index){
7898
var prevCell = false;
7899
7900
if(index > 0){
7901
for(var i = index-1; i >= 0; i--){
7902
let cell = row.cells[i];
7903
7904
if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){
7905
let allowEdit = this.allowEdit(cell);
7906
7907
if(allowEdit){
7908
prevCell = cell;
7909
break;
7910
}
7911
}
7912
}
7913
}
7914
7915
return prevCell;
7916
}
7917
7918
///////////////////////////////////
7919
///////// Internal Logic //////////
7920
///////////////////////////////////
7921
7922
initializeColumnCheck(column){
7923
if(typeof column.definition.editor !== "undefined"){
7924
this.initializeColumn(column);
7925
}
7926
}
7927
7928
columnDeleteCheck(column){
7929
if(this.currentCell && this.currentCell.column === column){
7930
this.cancelEdit();
7931
}
7932
}
7933
7934
rowDeleteCheck(row){
7935
if(this.currentCell && this.currentCell.row === row){
7936
this.cancelEdit();
7937
}
7938
}
7939
7940
rowEditableCheck(row){
7941
row.getCells().forEach((cell) => {
7942
if(cell.column.modules.edit && typeof cell.column.modules.edit.check === "function"){
7943
this.updateCellClass(cell);
7944
}
7945
});
7946
}
7947
7948
//initialize column editor
7949
initializeColumn(column){
7950
var config = {
7951
editor:false,
7952
blocked:false,
7953
check:column.definition.editable,
7954
params:column.definition.editorParams || {}
7955
};
7956
7957
//set column editor
7958
switch(typeof column.definition.editor){
7959
case "string":
7960
if(this.editors[column.definition.editor]){
7961
config.editor = this.editors[column.definition.editor];
7962
}else {
7963
console.warn("Editor Error - No such editor found: ", column.definition.editor);
7964
}
7965
break;
7966
7967
case "function":
7968
config.editor = column.definition.editor;
7969
break;
7970
7971
case "boolean":
7972
if(column.definition.editor === true){
7973
if(typeof column.definition.formatter !== "function"){
7974
if(this.editors[column.definition.formatter]){
7975
config.editor = this.editors[column.definition.formatter];
7976
}else {
7977
config.editor = this.editors["input"];
7978
}
7979
}else {
7980
console.warn("Editor Error - Cannot auto lookup editor for a custom formatter: ", column.definition.formatter);
7981
}
7982
}
7983
break;
7984
}
7985
7986
if(config.editor){
7987
column.modules.edit = config;
7988
}
7989
}
7990
7991
getCurrentCell(){
7992
return this.currentCell ? this.currentCell.getComponent() : false;
7993
}
7994
7995
clearEditor(cancel){
7996
var cell = this.currentCell,
7997
cellEl;
7998
7999
this.invalidEdit = false;
8000
8001
if(cell){
8002
this.currentCell = false;
8003
8004
cellEl = cell.getElement();
8005
8006
this.dispatch("edit-editor-clear", cell, cancel);
8007
8008
cellEl.classList.remove("tabulator-editing");
8009
8010
while(cellEl.firstChild) cellEl.removeChild(cellEl.firstChild);
8011
8012
cell.row.getElement().classList.remove("tabulator-editing");
8013
8014
cell.table.element.classList.remove("tabulator-editing");
8015
}
8016
}
8017
8018
cancelEdit(){
8019
if(this.currentCell){
8020
var cell = this.currentCell;
8021
var component = this.currentCell.getComponent();
8022
8023
this.clearEditor(true);
8024
cell.setValueActual(cell.getValue());
8025
cell.cellRendered();
8026
8027
if(cell.column.definition.editor == "textarea" || cell.column.definition.variableHeight){
8028
cell.row.normalizeHeight(true);
8029
}
8030
8031
if(cell.column.definition.cellEditCancelled){
8032
cell.column.definition.cellEditCancelled.call(this.table, component);
8033
}
8034
8035
this.dispatch("edit-cancelled", cell);
8036
this.dispatchExternal("cellEditCancelled", component);
8037
}
8038
}
8039
8040
//return a formatted value for a cell
8041
bindEditor(cell){
8042
if(cell.column.modules.edit){
8043
var self = this,
8044
element = cell.getElement(true);
8045
8046
this.updateCellClass(cell);
8047
element.setAttribute("tabindex", 0);
8048
8049
element.addEventListener("click", function(e){
8050
if(!element.classList.contains("tabulator-editing")){
8051
element.focus({preventScroll: true});
8052
}
8053
});
8054
8055
element.addEventListener("mousedown", function(e){
8056
if (e.button === 2) {
8057
e.preventDefault();
8058
}else {
8059
self.mouseClick = true;
8060
}
8061
});
8062
8063
element.addEventListener("focus", function(e){
8064
if(!self.recursionBlock){
8065
self.edit(cell, e, false);
8066
}
8067
});
8068
}
8069
}
8070
8071
focusCellNoEvent(cell, block){
8072
this.recursionBlock = true;
8073
8074
if(!(block && this.table.browser === "ie")){
8075
cell.getElement().focus({preventScroll: true});
8076
}
8077
8078
this.recursionBlock = false;
8079
}
8080
8081
editCell(cell, forceEdit){
8082
this.focusCellNoEvent(cell);
8083
this.edit(cell, false, forceEdit);
8084
}
8085
8086
focusScrollAdjust(cell){
8087
if(this.table.rowManager.getRenderMode() == "virtual"){
8088
var topEdge = this.table.rowManager.element.scrollTop,
8089
bottomEdge = this.table.rowManager.element.clientHeight + this.table.rowManager.element.scrollTop,
8090
rowEl = cell.row.getElement();
8091
8092
if(rowEl.offsetTop < topEdge){
8093
this.table.rowManager.element.scrollTop -= (topEdge - rowEl.offsetTop);
8094
}else {
8095
if(rowEl.offsetTop + rowEl.offsetHeight > bottomEdge){
8096
this.table.rowManager.element.scrollTop += (rowEl.offsetTop + rowEl.offsetHeight - bottomEdge);
8097
}
8098
}
8099
8100
var leftEdge = this.table.rowManager.element.scrollLeft,
8101
rightEdge = this.table.rowManager.element.clientWidth + this.table.rowManager.element.scrollLeft,
8102
cellEl = cell.getElement();
8103
8104
if(this.table.modExists("frozenColumns")){
8105
leftEdge += parseInt(this.table.modules.frozenColumns.leftMargin || 0);
8106
rightEdge -= parseInt(this.table.modules.frozenColumns.rightMargin || 0);
8107
}
8108
8109
if(this.table.options.renderHorizontal === "virtual"){
8110
leftEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft);
8111
rightEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft);
8112
}
8113
8114
if(cellEl.offsetLeft < leftEdge){
8115
this.table.rowManager.element.scrollLeft -= (leftEdge - cellEl.offsetLeft);
8116
}else {
8117
if(cellEl.offsetLeft + cellEl.offsetWidth > rightEdge){
8118
this.table.rowManager.element.scrollLeft += (cellEl.offsetLeft + cellEl.offsetWidth - rightEdge);
8119
}
8120
}
8121
}
8122
}
8123
8124
allowEdit(cell) {
8125
var check = cell.column.modules.edit ? true : false;
8126
8127
if(cell.column.modules.edit){
8128
switch(typeof cell.column.modules.edit.check){
8129
case "function":
8130
if(cell.row.initialized){
8131
check = cell.column.modules.edit.check(cell.getComponent());
8132
}
8133
break;
8134
8135
case "string":
8136
check = !!cell.row.data[cell.column.modules.edit.check];
8137
break;
8138
8139
case "boolean":
8140
check = cell.column.modules.edit.check;
8141
break;
8142
}
8143
}
8144
8145
return check;
8146
}
8147
8148
edit(cell, e, forceEdit){
8149
var self = this,
8150
allowEdit = true,
8151
rendered = function(){},
8152
element = cell.getElement(),
8153
cellEditor, component, params;
8154
8155
//prevent editing if another cell is refusing to leave focus (eg. validation fail)
8156
8157
if(this.currentCell){
8158
if(!this.invalidEdit && this.currentCell !== cell){
8159
this.cancelEdit();
8160
}
8161
return;
8162
}
8163
8164
//handle successful value change
8165
function success(value){
8166
if(self.currentCell === cell){
8167
var valid = self.chain("edit-success", [cell, value], true, true);
8168
8169
if(valid === true || self.table.options.validationMode === "highlight"){
8170
self.clearEditor();
8171
8172
8173
if(!cell.modules.edit){
8174
cell.modules.edit = {};
8175
}
8176
8177
cell.modules.edit.edited = true;
8178
8179
if(self.editedCells.indexOf(cell) == -1){
8180
self.editedCells.push(cell);
8181
}
8182
8183
cell.setValue(value, true);
8184
8185
return valid === true;
8186
}else {
8187
self.invalidEdit = true;
8188
self.focusCellNoEvent(cell, true);
8189
rendered();
8190
return false;
8191
}
8192
}
8193
}
8194
8195
//handle aborted edit
8196
function cancel(){
8197
if(self.currentCell === cell){
8198
self.cancelEdit();
8199
}
8200
}
8201
8202
function onRendered(callback){
8203
rendered = callback;
8204
}
8205
8206
if(!cell.column.modules.edit.blocked){
8207
if(e){
8208
e.stopPropagation();
8209
}
8210
8211
allowEdit = this.allowEdit(cell);
8212
8213
if(allowEdit || forceEdit){
8214
8215
self.cancelEdit();
8216
8217
self.currentCell = cell;
8218
8219
this.focusScrollAdjust(cell);
8220
8221
component = cell.getComponent();
8222
8223
if(this.mouseClick){
8224
this.mouseClick = false;
8225
8226
if(cell.column.definition.cellClick){
8227
cell.column.definition.cellClick.call(this.table, e, component);
8228
}
8229
}
8230
8231
if(cell.column.definition.cellEditing){
8232
cell.column.definition.cellEditing.call(this.table, component);
8233
}
8234
8235
this.dispatch("cell-editing", cell);
8236
this.dispatchExternal("cellEditing", component);
8237
8238
params = typeof cell.column.modules.edit.params === "function" ? cell.column.modules.edit.params(component) : cell.column.modules.edit.params;
8239
8240
cellEditor = cell.column.modules.edit.editor.call(self, component, onRendered, success, cancel, params);
8241
8242
//if editor returned, add to DOM, if false, abort edit
8243
if(this.currentCell && cellEditor !== false){
8244
if(cellEditor instanceof Node){
8245
element.classList.add("tabulator-editing");
8246
cell.row.getElement().classList.add("tabulator-editing");
8247
cell.table.element.classList.add("tabulator-editing");
8248
while(element.firstChild) element.removeChild(element.firstChild);
8249
element.appendChild(cellEditor);
8250
8251
//trigger onRendered Callback
8252
rendered();
8253
8254
//prevent editing from triggering rowClick event
8255
var children = element.children;
8256
8257
for (var i = 0; i < children.length; i++) {
8258
children[i].addEventListener("click", function(e){
8259
e.stopPropagation();
8260
});
8261
}
8262
}else {
8263
console.warn("Edit Error - Editor should return an instance of Node, the editor returned:", cellEditor);
8264
element.blur();
8265
return false;
8266
}
8267
}else {
8268
element.blur();
8269
return false;
8270
}
8271
8272
return true;
8273
}else {
8274
this.mouseClick = false;
8275
element.blur();
8276
return false;
8277
}
8278
}else {
8279
this.mouseClick = false;
8280
element.blur();
8281
return false;
8282
}
8283
}
8284
8285
getEditedCells(){
8286
var output = [];
8287
8288
this.editedCells.forEach((cell) => {
8289
output.push(cell.getComponent());
8290
});
8291
8292
return output;
8293
}
8294
8295
clearEdited(cell){
8296
var editIndex;
8297
8298
if(cell.modules.edit && cell.modules.edit.edited){
8299
cell.modules.edit.edited = false;
8300
8301
this.dispatch("edit-edited-clear", cell);
8302
}
8303
8304
editIndex = this.editedCells.indexOf(cell);
8305
8306
if(editIndex > -1){
8307
this.editedCells.splice(editIndex, 1);
8308
}
8309
}
8310
}
8311
8312
Edit$1.moduleName = "edit";
8313
8314
//load defaults
8315
Edit$1.editors = defaultEditors;
8316
8317
class ExportRow{
8318
constructor(type, columns, component, indent){
8319
this.type = type;
8320
this.columns = columns;
8321
this.component = component || false;
8322
this.indent = indent || 0;
8323
}
8324
}
8325
8326
class ExportColumn{
8327
constructor(value, component, width, height, depth){
8328
this.value = value;
8329
this.component = component || false;
8330
this.width = width;
8331
this.height = height;
8332
this.depth = depth;
8333
}
8334
}
8335
8336
class Export extends Module{
8337
8338
constructor(table){
8339
super(table);
8340
8341
this.config = {};
8342
this.cloneTableStyle = true;
8343
this.colVisProp = "";
8344
8345
this.registerTableOption("htmlOutputConfig", false); //html output config
8346
8347
this.registerColumnOption("htmlOutput");
8348
this.registerColumnOption("titleHtmlOutput");
8349
}
8350
8351
initialize(){
8352
this.registerTableFunction("getHtml", this.getHtml.bind(this));
8353
}
8354
8355
///////////////////////////////////
8356
///////// Table Functions /////////
8357
///////////////////////////////////
8358
8359
8360
///////////////////////////////////
8361
///////// Internal Logic //////////
8362
///////////////////////////////////
8363
8364
generateExportList(config, style, range, colVisProp){
8365
this.cloneTableStyle = style;
8366
this.config = config || {};
8367
this.colVisProp = colVisProp;
8368
8369
var headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders()) : [];
8370
var body = this.bodyToExportRows(this.rowLookup(range));
8371
8372
return headers.concat(body);
8373
}
8374
8375
generateTable(config, style, range, colVisProp){
8376
var list = this.generateExportList(config, style, range, colVisProp);
8377
8378
return this.generateTableElement(list);
8379
}
8380
8381
rowLookup(range){
8382
var rows = [];
8383
8384
if(typeof range == "function"){
8385
range.call(this.table).forEach((row) =>{
8386
row = this.table.rowManager.findRow(row);
8387
8388
if(row){
8389
rows.push(row);
8390
}
8391
});
8392
}else {
8393
switch(range){
8394
case true:
8395
case "visible":
8396
rows = this.table.rowManager.getVisibleRows(false, true);
8397
break;
8398
8399
case "all":
8400
rows = this.table.rowManager.rows;
8401
break;
8402
8403
case "selected":
8404
rows = this.table.modules.selectRow.selectedRows;
8405
break;
8406
8407
case "active":
8408
default:
8409
if(this.table.options.pagination){
8410
rows = this.table.rowManager.getDisplayRows(this.table.rowManager.displayRows.length - 2);
8411
}else {
8412
rows = this.table.rowManager.getDisplayRows();
8413
}
8414
}
8415
}
8416
8417
return Object.assign([], rows);
8418
}
8419
8420
generateColumnGroupHeaders(){
8421
var output = [];
8422
8423
var columns = this.config.columnGroups !== false ? this.table.columnManager.columns : this.table.columnManager.columnsByIndex;
8424
8425
columns.forEach((column) => {
8426
var colData = this.processColumnGroup(column);
8427
8428
if(colData){
8429
output.push(colData);
8430
}
8431
});
8432
8433
return output;
8434
}
8435
8436
processColumnGroup(column){
8437
var subGroups = column.columns,
8438
maxDepth = 0,
8439
title = column.definition["title" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))] || column.definition.title;
8440
8441
var groupData = {
8442
title:title,
8443
column:column,
8444
depth:1,
8445
};
8446
8447
if(subGroups.length){
8448
groupData.subGroups = [];
8449
groupData.width = 0;
8450
8451
subGroups.forEach((subGroup) => {
8452
var subGroupData = this.processColumnGroup(subGroup);
8453
8454
if(subGroupData){
8455
groupData.width += subGroupData.width;
8456
groupData.subGroups.push(subGroupData);
8457
8458
if(subGroupData.depth > maxDepth){
8459
maxDepth = subGroupData.depth;
8460
}
8461
}
8462
});
8463
8464
groupData.depth += maxDepth;
8465
8466
if(!groupData.width){
8467
return false;
8468
}
8469
}else {
8470
if(this.columnVisCheck(column)){
8471
groupData.width = 1;
8472
}else {
8473
return false;
8474
}
8475
}
8476
8477
return groupData;
8478
}
8479
8480
columnVisCheck(column){
8481
var visProp = column.definition[this.colVisProp];
8482
8483
if(typeof visProp === "function"){
8484
visProp = visProp.call(this.table, column.getComponent());
8485
}
8486
8487
return visProp !== false && (column.visible || (!column.visible && visProp));
8488
}
8489
8490
headersToExportRows(columns){
8491
var headers = [],
8492
headerDepth = 0,
8493
exportRows = [];
8494
8495
function parseColumnGroup(column, level){
8496
8497
var depth = headerDepth - level;
8498
8499
if(typeof headers[level] === "undefined"){
8500
headers[level] = [];
8501
}
8502
8503
column.height = column.subGroups ? 1 : (depth - column.depth) + 1;
8504
8505
headers[level].push(column);
8506
8507
if(column.height > 1){
8508
for(let i = 1; i < column.height; i ++){
8509
8510
if(typeof headers[level + i] === "undefined"){
8511
headers[level + i] = [];
8512
}
8513
8514
headers[level + i].push(false);
8515
}
8516
}
8517
8518
if(column.width > 1){
8519
for(let i = 1; i < column.width; i ++){
8520
headers[level].push(false);
8521
}
8522
}
8523
8524
if(column.subGroups){
8525
column.subGroups.forEach(function(subGroup){
8526
parseColumnGroup(subGroup, level+1);
8527
});
8528
}
8529
}
8530
8531
//calculate maximum header depth
8532
columns.forEach(function(column){
8533
if(column.depth > headerDepth){
8534
headerDepth = column.depth;
8535
}
8536
});
8537
8538
columns.forEach(function(column){
8539
parseColumnGroup(column,0);
8540
});
8541
8542
headers.forEach((header) => {
8543
var columns = [];
8544
8545
header.forEach((col) => {
8546
if(col){
8547
let title = typeof col.title === "undefined" ? "" : col.title;
8548
columns.push(new ExportColumn(title, col.column.getComponent(), col.width, col.height, col.depth));
8549
}else {
8550
columns.push(null);
8551
}
8552
});
8553
8554
exportRows.push(new ExportRow("header", columns));
8555
});
8556
8557
return exportRows;
8558
}
8559
8560
bodyToExportRows(rows){
8561
8562
var columns = [];
8563
var exportRows = [];
8564
8565
this.table.columnManager.columnsByIndex.forEach((column) => {
8566
if (this.columnVisCheck(column)) {
8567
columns.push(column.getComponent());
8568
}
8569
});
8570
8571
if(this.config.columnCalcs !== false && this.table.modExists("columnCalcs")){
8572
if(this.table.modules.columnCalcs.topInitialized){
8573
rows.unshift(this.table.modules.columnCalcs.topRow);
8574
}
8575
8576
if(this.table.modules.columnCalcs.botInitialized){
8577
rows.push(this.table.modules.columnCalcs.botRow);
8578
}
8579
}
8580
8581
rows = rows.filter((row) => {
8582
switch(row.type){
8583
case "group":
8584
return this.config.rowGroups !== false;
8585
8586
case "calc":
8587
return this.config.columnCalcs !== false;
8588
8589
case "row":
8590
return !(this.table.options.dataTree && this.config.dataTree === false && row.modules.dataTree.parent);
8591
}
8592
8593
return true;
8594
});
8595
8596
rows.forEach((row, i) => {
8597
var rowData = row.getData(this.colVisProp);
8598
var exportCols = [];
8599
var indent = 0;
8600
8601
switch(row.type){
8602
case "group":
8603
indent = row.level;
8604
exportCols.push(new ExportColumn(row.key, row.getComponent(), columns.length, 1));
8605
break;
8606
8607
case "calc" :
8608
case "row" :
8609
columns.forEach((col) => {
8610
exportCols.push(new ExportColumn(col._column.getFieldValue(rowData), col, 1, 1));
8611
});
8612
8613
if(this.table.options.dataTree && this.config.dataTree !== false){
8614
indent = row.modules.dataTree.index;
8615
}
8616
break;
8617
}
8618
8619
exportRows.push(new ExportRow(row.type, exportCols, row.getComponent(), indent));
8620
});
8621
8622
return exportRows;
8623
}
8624
8625
generateTableElement(list){
8626
var table = document.createElement("table"),
8627
headerEl = document.createElement("thead"),
8628
bodyEl = document.createElement("tbody"),
8629
styles = this.lookupTableStyles(),
8630
rowFormatter = this.table.options["rowFormatter" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))],
8631
setup = {};
8632
8633
setup.rowFormatter = rowFormatter !== null ? rowFormatter : this.table.options.rowFormatter;
8634
8635
if(this.table.options.dataTree &&this.config.dataTree !== false && this.table.modExists("columnCalcs")){
8636
setup.treeElementField = this.table.modules.dataTree.elementField;
8637
}
8638
8639
//assign group header formatter
8640
setup.groupHeader = this.table.options["groupHeader" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))];
8641
8642
if(setup.groupHeader && !Array.isArray(setup.groupHeader)){
8643
setup.groupHeader = [setup.groupHeader];
8644
}
8645
8646
table.classList.add("tabulator-print-table");
8647
8648
this.mapElementStyles(this.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
8649
8650
8651
if(list.length > 1000){
8652
console.warn("It may take a long time to render an HTML table with more than 1000 rows");
8653
}
8654
8655
list.forEach((row, i) => {
8656
let rowEl;
8657
8658
switch(row.type){
8659
case "header":
8660
headerEl.appendChild(this.generateHeaderElement(row, setup, styles));
8661
break;
8662
8663
case "group":
8664
bodyEl.appendChild(this.generateGroupElement(row, setup, styles));
8665
break;
8666
8667
case "calc":
8668
bodyEl.appendChild(this.generateCalcElement(row, setup, styles));
8669
break;
8670
8671
case "row":
8672
rowEl = this.generateRowElement(row, setup, styles);
8673
8674
this.mapElementStyles(((i % 2) && styles.evenRow) ? styles.evenRow : styles.oddRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
8675
bodyEl.appendChild(rowEl);
8676
break;
8677
}
8678
});
8679
8680
if(headerEl.innerHTML){
8681
table.appendChild(headerEl);
8682
}
8683
8684
table.appendChild(bodyEl);
8685
8686
8687
this.mapElementStyles(this.table.element, table, ["border-top", "border-left", "border-right", "border-bottom"]);
8688
return table;
8689
}
8690
8691
lookupTableStyles(){
8692
var styles = {};
8693
8694
//lookup row styles
8695
if(this.cloneTableStyle && window.getComputedStyle){
8696
styles.oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)");
8697
styles.evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)");
8698
styles.calcRow = this.table.element.querySelector(".tabulator-row.tabulator-calcs");
8699
styles.firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)");
8700
styles.firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0];
8701
8702
if(styles.firstRow){
8703
styles.styleCells = styles.firstRow.getElementsByClassName("tabulator-cell");
8704
styles.firstCell = styles.styleCells[0];
8705
styles.lastCell = styles.styleCells[styles.styleCells.length - 1];
8706
}
8707
}
8708
8709
return styles;
8710
}
8711
8712
generateHeaderElement(row, setup, styles){
8713
var rowEl = document.createElement("tr");
8714
8715
row.columns.forEach((column) => {
8716
if(column){
8717
var cellEl = document.createElement("th");
8718
var classNames = column.component._column.definition.cssClass ? column.component._column.definition.cssClass.split(" ") : [];
8719
8720
cellEl.colSpan = column.width;
8721
cellEl.rowSpan = column.height;
8722
8723
cellEl.innerHTML = column.value;
8724
8725
if(this.cloneTableStyle){
8726
cellEl.style.boxSizing = "border-box";
8727
}
8728
8729
classNames.forEach(function(className) {
8730
cellEl.classList.add(className);
8731
});
8732
8733
this.mapElementStyles(column.component.getElement(), cellEl, ["text-align", "border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
8734
this.mapElementStyles(column.component._column.contentElement, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);
8735
8736
if(column.component._column.visible){
8737
this.mapElementStyles(column.component.getElement(), cellEl, ["width"]);
8738
}else {
8739
if(column.component._column.definition.width){
8740
cellEl.style.width = column.component._column.definition.width + "px";
8741
}
8742
}
8743
8744
if(column.component._column.parent){
8745
this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]);
8746
}
8747
8748
rowEl.appendChild(cellEl);
8749
}
8750
});
8751
8752
return rowEl;
8753
}
8754
8755
generateGroupElement(row, setup, styles){
8756
8757
var rowEl = document.createElement("tr"),
8758
cellEl = document.createElement("td"),
8759
group = row.columns[0];
8760
8761
rowEl.classList.add("tabulator-print-table-row");
8762
8763
if(setup.groupHeader && setup.groupHeader[row.indent]){
8764
group.value = setup.groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
8765
}else {
8766
if(setup.groupHeader !== false){
8767
group.value = row.component._group.generator(group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
8768
}
8769
}
8770
8771
cellEl.colSpan = group.width;
8772
cellEl.innerHTML = group.value;
8773
8774
rowEl.classList.add("tabulator-print-table-group");
8775
rowEl.classList.add("tabulator-group-level-" + row.indent);
8776
8777
if(group.component.isVisible()){
8778
rowEl.classList.add("tabulator-group-visible");
8779
}
8780
8781
this.mapElementStyles(styles.firstGroup, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
8782
this.mapElementStyles(styles.firstGroup, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);
8783
8784
rowEl.appendChild(cellEl);
8785
8786
return rowEl;
8787
}
8788
8789
generateCalcElement(row, setup, styles){
8790
var rowEl = this.generateRowElement(row, setup, styles);
8791
8792
rowEl.classList.add("tabulator-print-table-calcs");
8793
this.mapElementStyles(styles.calcRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
8794
8795
return rowEl;
8796
}
8797
8798
generateRowElement(row, setup, styles){
8799
var rowEl = document.createElement("tr");
8800
8801
rowEl.classList.add("tabulator-print-table-row");
8802
8803
row.columns.forEach((col, i) => {
8804
if(col){
8805
var cellEl = document.createElement("td"),
8806
column = col.component._column,
8807
index = this.table.columnManager.findColumnIndex(column),
8808
value = col.value,
8809
cellStyle;
8810
8811
var cellWrapper = {
8812
modules:{},
8813
getValue:function(){
8814
return value;
8815
},
8816
getField:function(){
8817
return column.definition.field;
8818
},
8819
getElement:function(){
8820
return cellEl;
8821
},
8822
getType:function(){
8823
return "cell";
8824
},
8825
getColumn:function(){
8826
return column.getComponent();
8827
},
8828
getData:function(){
8829
return row.component.getData();
8830
},
8831
getRow:function(){
8832
return row.component;
8833
},
8834
getComponent:function(){
8835
return cellWrapper;
8836
},
8837
column:column,
8838
};
8839
8840
var classNames = column.definition.cssClass ? column.definition.cssClass.split(" ") : [];
8841
8842
classNames.forEach(function(className) {
8843
cellEl.classList.add(className);
8844
});
8845
8846
if(this.table.modExists("format") && this.config.formatCells !== false){
8847
value = this.table.modules.format.formatExportValue(cellWrapper, this.colVisProp);
8848
}else {
8849
switch(typeof value){
8850
case "object":
8851
value = value !== null ? JSON.stringify(value) : "";
8852
break;
8853
8854
case "undefined":
8855
value = "";
8856
break;
8857
}
8858
}
8859
8860
if(value instanceof Node){
8861
cellEl.appendChild(value);
8862
}else {
8863
cellEl.innerHTML = value;
8864
}
8865
8866
cellStyle = styles.styleCells && styles.styleCells[index] ? styles.styleCells[index] : styles.firstCell;
8867
8868
if(cellStyle){
8869
this.mapElementStyles(cellStyle, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom", "border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "text-align"]);
8870
8871
if(column.definition.align){
8872
cellEl.style.textAlign = column.definition.align;
8873
}
8874
}
8875
8876
if(this.table.options.dataTree && this.config.dataTree !== false){
8877
if((setup.treeElementField && setup.treeElementField == column.field) || (!setup.treeElementField && i == 0)){
8878
if(row.component._row.modules.dataTree.controlEl){
8879
cellEl.insertBefore(row.component._row.modules.dataTree.controlEl.cloneNode(true), cellEl.firstChild);
8880
}
8881
if(row.component._row.modules.dataTree.branchEl){
8882
cellEl.insertBefore(row.component._row.modules.dataTree.branchEl.cloneNode(true), cellEl.firstChild);
8883
}
8884
}
8885
}
8886
8887
rowEl.appendChild(cellEl);
8888
8889
if(cellWrapper.modules.format && cellWrapper.modules.format.renderedCallback){
8890
cellWrapper.modules.format.renderedCallback();
8891
}
8892
}
8893
});
8894
8895
if(setup.rowFormatter && row.type === "row" && this.config.formatCells !== false){
8896
let formatComponent = Object.assign(row.component);
8897
8898
formatComponent.getElement = function(){return rowEl;};
8899
8900
setup.rowFormatter(row.component);
8901
}
8902
8903
return rowEl;
8904
}
8905
8906
generateHTMLTable(list){
8907
var holder = document.createElement("div");
8908
8909
holder.appendChild(this.generateTableElement(list));
8910
8911
return holder.innerHTML;
8912
}
8913
8914
getHtml(visible, style, config, colVisProp){
8915
var list = this.generateExportList(config || this.table.options.htmlOutputConfig, style, visible, colVisProp || "htmlOutput");
8916
8917
return this.generateHTMLTable(list);
8918
}
8919
8920
mapElementStyles(from, to, props){
8921
if(this.cloneTableStyle && from && to){
8922
8923
var lookup = {
8924
"background-color" : "backgroundColor",
8925
"color" : "fontColor",
8926
"width" : "width",
8927
"font-weight" : "fontWeight",
8928
"font-family" : "fontFamily",
8929
"font-size" : "fontSize",
8930
"text-align" : "textAlign",
8931
"border-top" : "borderTop",
8932
"border-left" : "borderLeft",
8933
"border-right" : "borderRight",
8934
"border-bottom" : "borderBottom",
8935
"padding-top" : "paddingTop",
8936
"padding-left" : "paddingLeft",
8937
"padding-right" : "paddingRight",
8938
"padding-bottom" : "paddingBottom",
8939
};
8940
8941
if(window.getComputedStyle){
8942
var fromStyle = window.getComputedStyle(from);
8943
8944
props.forEach(function(prop){
8945
if(!to.style[lookup[prop]]){
8946
to.style[lookup[prop]] = fromStyle.getPropertyValue(prop);
8947
}
8948
});
8949
}
8950
}
8951
}
8952
}
8953
8954
Export.moduleName = "export";
8955
8956
var defaultFilters = {
8957
8958
//equal to
8959
"=":function(filterVal, rowVal, rowData, filterParams){
8960
return rowVal == filterVal ? true : false;
8961
},
8962
8963
//less than
8964
"<":function(filterVal, rowVal, rowData, filterParams){
8965
return rowVal < filterVal ? true : false;
8966
},
8967
8968
//less than or equal to
8969
"<=":function(filterVal, rowVal, rowData, filterParams){
8970
return rowVal <= filterVal ? true : false;
8971
},
8972
8973
//greater than
8974
">":function(filterVal, rowVal, rowData, filterParams){
8975
return rowVal > filterVal ? true : false;
8976
},
8977
8978
//greater than or equal to
8979
">=":function(filterVal, rowVal, rowData, filterParams){
8980
return rowVal >= filterVal ? true : false;
8981
},
8982
8983
//not equal to
8984
"!=":function(filterVal, rowVal, rowData, filterParams){
8985
return rowVal != filterVal ? true : false;
8986
},
8987
8988
"regex":function(filterVal, rowVal, rowData, filterParams){
8989
8990
if(typeof filterVal == "string"){
8991
filterVal = new RegExp(filterVal);
8992
}
8993
8994
return filterVal.test(rowVal);
8995
},
8996
8997
//contains the string
8998
"like":function(filterVal, rowVal, rowData, filterParams){
8999
if(filterVal === null || typeof filterVal === "undefined"){
9000
return rowVal === filterVal ? true : false;
9001
}else {
9002
if(typeof rowVal !== 'undefined' && rowVal !== null){
9003
return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1;
9004
}
9005
else {
9006
return false;
9007
}
9008
}
9009
},
9010
9011
//contains the keywords
9012
"keywords":function(filterVal, rowVal, rowData, filterParams){
9013
var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator),
9014
value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(),
9015
matches = [];
9016
9017
keywords.forEach((keyword) =>{
9018
if(value.includes(keyword)){
9019
matches.push(true);
9020
}
9021
});
9022
9023
return filterParams.matchAll ? matches.length === keywords.length : !!matches.length;
9024
},
9025
9026
//starts with the string
9027
"starts":function(filterVal, rowVal, rowData, filterParams){
9028
if(filterVal === null || typeof filterVal === "undefined"){
9029
return rowVal === filterVal ? true : false;
9030
}else {
9031
if(typeof rowVal !== 'undefined' && rowVal !== null){
9032
return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase());
9033
}
9034
else {
9035
return false;
9036
}
9037
}
9038
},
9039
9040
//ends with the string
9041
"ends":function(filterVal, rowVal, rowData, filterParams){
9042
if(filterVal === null || typeof filterVal === "undefined"){
9043
return rowVal === filterVal ? true : false;
9044
}else {
9045
if(typeof rowVal !== 'undefined' && rowVal !== null){
9046
return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase());
9047
}
9048
else {
9049
return false;
9050
}
9051
}
9052
},
9053
9054
//in array
9055
"in":function(filterVal, rowVal, rowData, filterParams){
9056
if(Array.isArray(filterVal)){
9057
return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true;
9058
}else {
9059
console.warn("Filter Error - filter value is not an array:", filterVal);
9060
return false;
9061
}
9062
},
9063
};
9064
9065
class Filter extends Module{
9066
9067
constructor(table){
9068
super(table);
9069
9070
this.filterList = []; //hold filter list
9071
this.headerFilters = {}; //hold column filters
9072
this.headerFilterColumns = []; //hold columns that use header filters
9073
9074
this.prevHeaderFilterChangeCheck = "";
9075
this.prevHeaderFilterChangeCheck = "{}";
9076
9077
this.changed = false; //has filtering changed since last render
9078
this.tableInitialized = false;
9079
9080
this.registerTableOption("filterMode", "local"); //local or remote filtering
9081
9082
this.registerTableOption("initialFilter", false); //initial filtering criteria
9083
this.registerTableOption("initialHeaderFilter", false); //initial header filtering criteria
9084
this.registerTableOption("headerFilterLiveFilterDelay", 300); //delay before updating column after user types in header filter
9085
this.registerTableOption("placeholderHeaderFilter", false); //placeholder when header filter is empty
9086
9087
this.registerColumnOption("headerFilter");
9088
this.registerColumnOption("headerFilterPlaceholder");
9089
this.registerColumnOption("headerFilterParams");
9090
this.registerColumnOption("headerFilterEmptyCheck");
9091
this.registerColumnOption("headerFilterFunc");
9092
this.registerColumnOption("headerFilterFuncParams");
9093
this.registerColumnOption("headerFilterLiveFilter");
9094
9095
this.registerTableFunction("searchRows", this.searchRows.bind(this));
9096
this.registerTableFunction("searchData", this.searchData.bind(this));
9097
9098
this.registerTableFunction("setFilter", this.userSetFilter.bind(this));
9099
this.registerTableFunction("refreshFilter", this.userRefreshFilter.bind(this));
9100
this.registerTableFunction("addFilter", this.userAddFilter.bind(this));
9101
this.registerTableFunction("getFilters", this.getFilters.bind(this));
9102
this.registerTableFunction("setHeaderFilterFocus", this.userSetHeaderFilterFocus.bind(this));
9103
this.registerTableFunction("getHeaderFilterValue", this.userGetHeaderFilterValue.bind(this));
9104
this.registerTableFunction("setHeaderFilterValue", this.userSetHeaderFilterValue.bind(this));
9105
this.registerTableFunction("getHeaderFilters", this.getHeaderFilters.bind(this));
9106
this.registerTableFunction("removeFilter", this.userRemoveFilter.bind(this));
9107
this.registerTableFunction("clearFilter", this.userClearFilter.bind(this));
9108
this.registerTableFunction("clearHeaderFilter", this.userClearHeaderFilter.bind(this));
9109
9110
this.registerComponentFunction("column", "headerFilterFocus", this.setHeaderFilterFocus.bind(this));
9111
this.registerComponentFunction("column", "reloadHeaderFilter", this.reloadHeaderFilter.bind(this));
9112
this.registerComponentFunction("column", "getHeaderFilterValue", this.getHeaderFilterValue.bind(this));
9113
this.registerComponentFunction("column", "setHeaderFilterValue", this.setHeaderFilterValue.bind(this));
9114
}
9115
9116
initialize(){
9117
this.subscribe("column-init", this.initializeColumnHeaderFilter.bind(this));
9118
this.subscribe("column-width-fit-before", this.hideHeaderFilterElements.bind(this));
9119
this.subscribe("column-width-fit-after", this.showHeaderFilterElements.bind(this));
9120
this.subscribe("table-built", this.tableBuilt.bind(this));
9121
this.subscribe("placeholder", this.generatePlaceholder.bind(this));
9122
9123
if(this.table.options.filterMode === "remote"){
9124
this.subscribe("data-params", this.remoteFilterParams.bind(this));
9125
}
9126
9127
this.registerDataHandler(this.filter.bind(this), 10);
9128
}
9129
9130
tableBuilt(){
9131
if(this.table.options.initialFilter){
9132
this.setFilter(this.table.options.initialFilter);
9133
}
9134
9135
if(this.table.options.initialHeaderFilter){
9136
this.table.options.initialHeaderFilter.forEach((item) => {
9137
9138
var column = this.table.columnManager.findColumn(item.field);
9139
9140
if(column){
9141
this.setHeaderFilterValue(column, item.value);
9142
}else {
9143
console.warn("Column Filter Error - No matching column found:", item.field);
9144
return false;
9145
}
9146
});
9147
}
9148
9149
this.tableInitialized = true;
9150
}
9151
9152
remoteFilterParams(data, config, silent, params){
9153
params.filter = this.getFilters(true, true);
9154
return params;
9155
}
9156
9157
generatePlaceholder(text){
9158
if(this.table.options.placeholderHeaderFilter && Object.keys(this.headerFilters).length){
9159
return this.table.options.placeholderHeaderFilter;
9160
}
9161
}
9162
9163
///////////////////////////////////
9164
///////// Table Functions /////////
9165
///////////////////////////////////
9166
9167
//set standard filters
9168
userSetFilter(field, type, value, params){
9169
this.setFilter(field, type, value, params);
9170
this.refreshFilter();
9171
}
9172
9173
//set standard filters
9174
userRefreshFilter(){
9175
this.refreshFilter();
9176
}
9177
9178
//add filter to array
9179
userAddFilter(field, type, value, params){
9180
this.addFilter(field, type, value, params);
9181
this.refreshFilter();
9182
}
9183
9184
userSetHeaderFilterFocus(field){
9185
var column = this.table.columnManager.findColumn(field);
9186
9187
if(column){
9188
this.setHeaderFilterFocus(column);
9189
}else {
9190
console.warn("Column Filter Focus Error - No matching column found:", field);
9191
return false;
9192
}
9193
}
9194
9195
userGetHeaderFilterValue(field) {
9196
var column = this.table.columnManager.findColumn(field);
9197
9198
if(column){
9199
return this.getHeaderFilterValue(column);
9200
}else {
9201
console.warn("Column Filter Error - No matching column found:", field);
9202
}
9203
}
9204
9205
userSetHeaderFilterValue(field, value){
9206
var column = this.table.columnManager.findColumn(field);
9207
9208
if(column){
9209
this.setHeaderFilterValue(column, value);
9210
}else {
9211
console.warn("Column Filter Error - No matching column found:", field);
9212
return false;
9213
}
9214
}
9215
9216
//remove filter from array
9217
userRemoveFilter(field, type, value){
9218
this.removeFilter(field, type, value);
9219
this.refreshFilter();
9220
}
9221
9222
//clear filters
9223
userClearFilter(all){
9224
this.clearFilter(all);
9225
this.refreshFilter();
9226
}
9227
9228
//clear header filters
9229
userClearHeaderFilter(){
9230
this.clearHeaderFilter();
9231
this.refreshFilter();
9232
}
9233
9234
9235
//search for specific row components
9236
searchRows(field, type, value){
9237
return this.search("rows", field, type, value);
9238
}
9239
9240
//search for specific data
9241
searchData(field, type, value){
9242
return this.search("data", field, type, value);
9243
}
9244
9245
///////////////////////////////////
9246
///////// Internal Logic //////////
9247
///////////////////////////////////
9248
9249
initializeColumnHeaderFilter(column){
9250
var def = column.definition;
9251
9252
if(def.headerFilter){
9253
this.initializeColumn(column);
9254
}
9255
}
9256
9257
//initialize column header filter
9258
initializeColumn(column, value){
9259
var self = this,
9260
field = column.getField();
9261
9262
//handle successfully value change
9263
function success(value){
9264
var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match",
9265
type = "",
9266
filterChangeCheck = "",
9267
filterFunc;
9268
9269
if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){
9270
9271
column.modules.filter.prevSuccess = value;
9272
9273
if(!column.modules.filter.emptyFunc(value)){
9274
column.modules.filter.value = value;
9275
9276
switch(typeof column.definition.headerFilterFunc){
9277
case "string":
9278
if(Filter.filters[column.definition.headerFilterFunc]){
9279
type = column.definition.headerFilterFunc;
9280
filterFunc = function(data){
9281
var params = column.definition.headerFilterFuncParams || {};
9282
var fieldVal = column.getFieldValue(data);
9283
9284
params = typeof params === "function" ? params(value, fieldVal, data) : params;
9285
9286
return Filter.filters[column.definition.headerFilterFunc](value, fieldVal, data, params);
9287
};
9288
}else {
9289
console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc);
9290
}
9291
break;
9292
9293
case "function":
9294
filterFunc = function(data){
9295
var params = column.definition.headerFilterFuncParams || {};
9296
var fieldVal = column.getFieldValue(data);
9297
9298
params = typeof params === "function" ? params(value, fieldVal, data) : params;
9299
9300
return column.definition.headerFilterFunc(value, fieldVal, data, params);
9301
};
9302
9303
type = filterFunc;
9304
break;
9305
}
9306
9307
if(!filterFunc){
9308
switch(filterType){
9309
case "partial":
9310
filterFunc = function(data){
9311
var colVal = column.getFieldValue(data);
9312
9313
if(typeof colVal !== 'undefined' && colVal !== null){
9314
return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1;
9315
}else {
9316
return false;
9317
}
9318
};
9319
type = "like";
9320
break;
9321
9322
default:
9323
filterFunc = function(data){
9324
return column.getFieldValue(data) == value;
9325
};
9326
type = "=";
9327
}
9328
}
9329
9330
self.headerFilters[field] = {value:value, func:filterFunc, type:type};
9331
}else {
9332
delete self.headerFilters[field];
9333
}
9334
9335
column.modules.filter.value = value;
9336
9337
filterChangeCheck = JSON.stringify(self.headerFilters);
9338
9339
if(self.prevHeaderFilterChangeCheck !== filterChangeCheck){
9340
self.prevHeaderFilterChangeCheck = filterChangeCheck;
9341
9342
self.trackChanges();
9343
self.refreshFilter();
9344
}
9345
}
9346
9347
return true;
9348
}
9349
9350
column.modules.filter = {
9351
success:success,
9352
attrType:false,
9353
tagType:false,
9354
emptyFunc:false,
9355
};
9356
9357
this.generateHeaderFilterElement(column);
9358
}
9359
9360
generateHeaderFilterElement(column, initialValue, reinitialize){
9361
var self = this,
9362
success = column.modules.filter.success,
9363
field = column.getField(),
9364
filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params, onRenderedCallback;
9365
9366
column.modules.filter.value = initialValue;
9367
9368
//handle aborted edit
9369
function cancel(){}
9370
9371
function onRendered(callback){
9372
onRenderedCallback = callback;
9373
}
9374
9375
if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){
9376
column.contentElement.removeChild(column.modules.filter.headerElement.parentNode);
9377
}
9378
9379
if(field){
9380
9381
//set empty value function
9382
column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){
9383
return !value && value !== 0;
9384
};
9385
9386
filterElement = document.createElement("div");
9387
filterElement.classList.add("tabulator-header-filter");
9388
9389
//set column editor
9390
switch(typeof column.definition.headerFilter){
9391
case "string":
9392
if(self.table.modules.edit.editors[column.definition.headerFilter]){
9393
editor = self.table.modules.edit.editors[column.definition.headerFilter];
9394
9395
if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){
9396
column.modules.filter.emptyFunc = function(value){
9397
return value !== true && value !== false;
9398
};
9399
}
9400
}else {
9401
console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor);
9402
}
9403
break;
9404
9405
case "function":
9406
editor = column.definition.headerFilter;
9407
break;
9408
9409
case "boolean":
9410
if(column.modules.edit && column.modules.edit.editor){
9411
editor = column.modules.edit.editor;
9412
}else {
9413
if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){
9414
editor = self.table.modules.edit.editors[column.definition.formatter];
9415
9416
if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){
9417
column.modules.filter.emptyFunc = function(value){
9418
return value !== true && value !== false;
9419
};
9420
}
9421
}else {
9422
editor = self.table.modules.edit.editors["input"];
9423
}
9424
}
9425
break;
9426
}
9427
9428
if(editor){
9429
9430
cellWrapper = {
9431
getValue:function(){
9432
return typeof initialValue !== "undefined" ? initialValue : "";
9433
},
9434
getField:function(){
9435
return column.definition.field;
9436
},
9437
getElement:function(){
9438
return filterElement;
9439
},
9440
getColumn:function(){
9441
return column.getComponent();
9442
},
9443
getTable:() => {
9444
return this.table;
9445
},
9446
getType:() => {
9447
return "header";
9448
},
9449
getRow:function(){
9450
return {
9451
normalizeHeight:function(){
9452
9453
}
9454
};
9455
}
9456
};
9457
9458
params = column.definition.headerFilterParams || {};
9459
9460
params = typeof params === "function" ? params.call(self.table, cellWrapper) : params;
9461
9462
editorElement = editor.call(this.table.modules.edit, cellWrapper, onRendered, success, cancel, params);
9463
9464
if(!editorElement){
9465
console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false");
9466
return;
9467
}
9468
9469
if(!(editorElement instanceof Node)){
9470
console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement);
9471
return;
9472
}
9473
9474
//set Placeholder Text
9475
self.langBind("headerFilters|columns|" + column.definition.field, function(value){
9476
editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : (column.definition.headerFilterPlaceholder || self.langText("headerFilters|default")));
9477
});
9478
9479
//focus on element on click
9480
editorElement.addEventListener("click", function(e){
9481
e.stopPropagation();
9482
editorElement.focus();
9483
});
9484
9485
editorElement.addEventListener("focus", (e) => {
9486
var left = this.table.columnManager.contentsElement.scrollLeft;
9487
9488
var headerPos = this.table.rowManager.element.scrollLeft;
9489
9490
if(left !== headerPos){
9491
this.table.rowManager.scrollHorizontal(left);
9492
this.table.columnManager.scrollHorizontal(left);
9493
}
9494
});
9495
9496
//live update filters as user types
9497
typingTimer = false;
9498
9499
searchTrigger = function(e){
9500
if(typingTimer){
9501
clearTimeout(typingTimer);
9502
}
9503
9504
typingTimer = setTimeout(function(){
9505
success(editorElement.value);
9506
},self.table.options.headerFilterLiveFilterDelay);
9507
};
9508
9509
column.modules.filter.headerElement = editorElement;
9510
column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ;
9511
column.modules.filter.tagType = editorElement.tagName.toLowerCase();
9512
9513
if(column.definition.headerFilterLiveFilter !== false){
9514
9515
if (
9516
!(
9517
column.definition.headerFilter === 'autocomplete' ||
9518
column.definition.headerFilter === 'tickCross' ||
9519
((column.definition.editor === 'autocomplete' ||
9520
column.definition.editor === 'tickCross') &&
9521
column.definition.headerFilter === true)
9522
)
9523
) {
9524
editorElement.addEventListener("keyup", searchTrigger);
9525
editorElement.addEventListener("search", searchTrigger);
9526
9527
9528
//update number filtered columns on change
9529
if(column.modules.filter.attrType == "number"){
9530
editorElement.addEventListener("change", function(e){
9531
success(editorElement.value);
9532
});
9533
}
9534
9535
//change text inputs to search inputs to allow for clearing of field
9536
if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){
9537
editorElement.setAttribute("type", "search");
9538
// editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click
9539
}
9540
9541
}
9542
9543
//prevent input and select elements from propagating click to column sorters etc
9544
if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){
9545
editorElement.addEventListener("mousedown",function(e){
9546
e.stopPropagation();
9547
});
9548
}
9549
}
9550
9551
filterElement.appendChild(editorElement);
9552
9553
column.contentElement.appendChild(filterElement);
9554
9555
if(!reinitialize){
9556
self.headerFilterColumns.push(column);
9557
}
9558
9559
if(onRenderedCallback){
9560
onRenderedCallback();
9561
}
9562
}
9563
}else {
9564
console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title);
9565
}
9566
}
9567
9568
//hide all header filter elements (used to ensure correct column widths in "fitData" layout mode)
9569
hideHeaderFilterElements(){
9570
this.headerFilterColumns.forEach(function(column){
9571
if(column.modules.filter && column.modules.filter.headerElement){
9572
column.modules.filter.headerElement.style.display = 'none';
9573
}
9574
});
9575
}
9576
9577
//show all header filter elements (used to ensure correct column widths in "fitData" layout mode)
9578
showHeaderFilterElements(){
9579
this.headerFilterColumns.forEach(function(column){
9580
if(column.modules.filter && column.modules.filter.headerElement){
9581
column.modules.filter.headerElement.style.display = '';
9582
}
9583
});
9584
}
9585
9586
//programmatically set focus of header filter
9587
setHeaderFilterFocus(column){
9588
if(column.modules.filter && column.modules.filter.headerElement){
9589
column.modules.filter.headerElement.focus();
9590
}else {
9591
console.warn("Column Filter Focus Error - No header filter set on column:", column.getField());
9592
}
9593
}
9594
9595
//programmatically get value of header filter
9596
getHeaderFilterValue(column){
9597
if(column.modules.filter && column.modules.filter.headerElement){
9598
return column.modules.filter.value;
9599
} else {
9600
console.warn("Column Filter Error - No header filter set on column:", column.getField());
9601
}
9602
}
9603
9604
//programmatically set value of header filter
9605
setHeaderFilterValue(column, value){
9606
if (column){
9607
if(column.modules.filter && column.modules.filter.headerElement){
9608
this.generateHeaderFilterElement(column, value, true);
9609
column.modules.filter.success(value);
9610
}else {
9611
console.warn("Column Filter Error - No header filter set on column:", column.getField());
9612
}
9613
}
9614
}
9615
9616
reloadHeaderFilter(column){
9617
if (column){
9618
if(column.modules.filter && column.modules.filter.headerElement){
9619
this.generateHeaderFilterElement(column, column.modules.filter.value, true);
9620
}else {
9621
console.warn("Column Filter Error - No header filter set on column:", column.getField());
9622
}
9623
}
9624
}
9625
9626
refreshFilter(){
9627
if(this.tableInitialized){
9628
if(this.table.options.filterMode === "remote"){
9629
this.reloadData(null, false, false);
9630
}else {
9631
this.refreshData(true);
9632
}
9633
}
9634
9635
//TODO - Persist left position of row manager
9636
// left = this.scrollLeft;
9637
// this.scrollHorizontal(left);
9638
}
9639
9640
//check if the filters has changed since last use
9641
trackChanges(){
9642
this.changed = true;
9643
this.dispatch("filter-changed");
9644
}
9645
9646
//check if the filters has changed since last use
9647
hasChanged(){
9648
var changed = this.changed;
9649
this.changed = false;
9650
return changed;
9651
}
9652
9653
//set standard filters
9654
setFilter(field, type, value, params){
9655
this.filterList = [];
9656
9657
if(!Array.isArray(field)){
9658
field = [{field:field, type:type, value:value, params:params}];
9659
}
9660
9661
this.addFilter(field);
9662
}
9663
9664
//add filter to array
9665
addFilter(field, type, value, params){
9666
var changed = false;
9667
9668
if(!Array.isArray(field)){
9669
field = [{field:field, type:type, value:value, params:params}];
9670
}
9671
9672
field.forEach((filter) => {
9673
filter = this.findFilter(filter);
9674
9675
if(filter){
9676
this.filterList.push(filter);
9677
changed = true;
9678
}
9679
});
9680
9681
if(changed){
9682
this.trackChanges();
9683
}
9684
}
9685
9686
findFilter(filter){
9687
var column;
9688
9689
if(Array.isArray(filter)){
9690
return this.findSubFilters(filter);
9691
}
9692
9693
var filterFunc = false;
9694
9695
if(typeof filter.field == "function"){
9696
filterFunc = function(data){
9697
return filter.field(data, filter.type || {});// pass params to custom filter function
9698
};
9699
}else {
9700
9701
if(Filter.filters[filter.type]){
9702
9703
column = this.table.columnManager.getColumnByField(filter.field);
9704
9705
if(column){
9706
filterFunc = function(data){
9707
return Filter.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {});
9708
};
9709
}else {
9710
filterFunc = function(data){
9711
return Filter.filters[filter.type](filter.value, data[filter.field], data, filter.params || {});
9712
};
9713
}
9714
9715
9716
}else {
9717
console.warn("Filter Error - No such filter type found, ignoring: ", filter.type);
9718
}
9719
}
9720
9721
filter.func = filterFunc;
9722
9723
return filter.func ? filter : false;
9724
}
9725
9726
findSubFilters(filters){
9727
var output = [];
9728
9729
filters.forEach((filter) => {
9730
filter = this.findFilter(filter);
9731
9732
if(filter){
9733
output.push(filter);
9734
}
9735
});
9736
9737
return output.length ? output : false;
9738
}
9739
9740
//get all filters
9741
getFilters(all, ajax){
9742
var output = [];
9743
9744
if(all){
9745
output = this.getHeaderFilters();
9746
}
9747
9748
if(ajax){
9749
output.forEach(function(item){
9750
if(typeof item.type == "function"){
9751
item.type = "function";
9752
}
9753
});
9754
}
9755
9756
output = output.concat(this.filtersToArray(this.filterList, ajax));
9757
9758
return output;
9759
}
9760
9761
//filter to Object
9762
filtersToArray(filterList, ajax){
9763
var output = [];
9764
9765
filterList.forEach((filter) => {
9766
var item;
9767
9768
if(Array.isArray(filter)){
9769
output.push(this.filtersToArray(filter, ajax));
9770
}else {
9771
item = {field:filter.field, type:filter.type, value:filter.value};
9772
9773
if(ajax){
9774
if(typeof item.type == "function"){
9775
item.type = "function";
9776
}
9777
}
9778
9779
output.push(item);
9780
}
9781
});
9782
9783
return output;
9784
}
9785
9786
//get all filters
9787
getHeaderFilters(){
9788
var output = [];
9789
9790
for(var key in this.headerFilters){
9791
output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value});
9792
}
9793
9794
return output;
9795
}
9796
9797
//remove filter from array
9798
removeFilter(field, type, value){
9799
if(!Array.isArray(field)){
9800
field = [{field:field, type:type, value:value}];
9801
}
9802
9803
field.forEach((filter) => {
9804
var index = -1;
9805
9806
if(typeof filter.field == "object"){
9807
index = this.filterList.findIndex((element) => {
9808
return filter === element;
9809
});
9810
}else {
9811
index = this.filterList.findIndex((element) => {
9812
return filter.field === element.field && filter.type === element.type && filter.value === element.value;
9813
});
9814
}
9815
9816
if(index > -1){
9817
this.filterList.splice(index, 1);
9818
}else {
9819
console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type);
9820
}
9821
});
9822
9823
this.trackChanges();
9824
}
9825
9826
//clear filters
9827
clearFilter(all){
9828
this.filterList = [];
9829
9830
if(all){
9831
this.clearHeaderFilter();
9832
}
9833
9834
this.trackChanges();
9835
}
9836
9837
//clear header filters
9838
clearHeaderFilter(){
9839
this.headerFilters = {};
9840
this.prevHeaderFilterChangeCheck = "{}";
9841
9842
this.headerFilterColumns.forEach((column) => {
9843
if(typeof column.modules.filter.value !== "undefined"){
9844
delete column.modules.filter.value;
9845
}
9846
column.modules.filter.prevSuccess = undefined;
9847
this.reloadHeaderFilter(column);
9848
});
9849
9850
this.trackChanges();
9851
}
9852
9853
//search data and return matching rows
9854
search (searchType, field, type, value){
9855
var activeRows = [],
9856
filterList = [];
9857
9858
if(!Array.isArray(field)){
9859
field = [{field:field, type:type, value:value}];
9860
}
9861
9862
field.forEach((filter) => {
9863
filter = this.findFilter(filter);
9864
9865
if(filter){
9866
filterList.push(filter);
9867
}
9868
});
9869
9870
this.table.rowManager.rows.forEach((row) => {
9871
var match = true;
9872
9873
filterList.forEach((filter) => {
9874
if(!this.filterRecurse(filter, row.getData())){
9875
match = false;
9876
}
9877
});
9878
9879
if(match){
9880
activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent());
9881
}
9882
9883
});
9884
9885
return activeRows;
9886
}
9887
9888
//filter row array
9889
filter(rowList, filters){
9890
var activeRows = [],
9891
activeRowComponents = [];
9892
9893
if(this.subscribedExternal("dataFiltering")){
9894
this.dispatchExternal("dataFiltering", this.getFilters(true));
9895
}
9896
9897
if(this.table.options.filterMode !== "remote" && (this.filterList.length || Object.keys(this.headerFilters).length)){
9898
9899
rowList.forEach((row) => {
9900
if(this.filterRow(row)){
9901
activeRows.push(row);
9902
}
9903
});
9904
9905
}else {
9906
activeRows = rowList.slice(0);
9907
}
9908
9909
if(this.subscribedExternal("dataFiltered")){
9910
9911
activeRows.forEach((row) => {
9912
activeRowComponents.push(row.getComponent());
9913
});
9914
9915
this.dispatchExternal("dataFiltered", this.getFilters(true), activeRowComponents);
9916
}
9917
9918
return activeRows;
9919
}
9920
9921
//filter individual row
9922
filterRow(row, filters){
9923
var match = true,
9924
data = row.getData();
9925
9926
this.filterList.forEach((filter) => {
9927
if(!this.filterRecurse(filter, data)){
9928
match = false;
9929
}
9930
});
9931
9932
9933
for(var field in this.headerFilters){
9934
if(!this.headerFilters[field].func(data)){
9935
match = false;
9936
}
9937
}
9938
9939
return match;
9940
}
9941
9942
filterRecurse(filter, data){
9943
var match = false;
9944
9945
if(Array.isArray(filter)){
9946
filter.forEach((subFilter) => {
9947
if(this.filterRecurse(subFilter, data)){
9948
match = true;
9949
}
9950
});
9951
}else {
9952
match = filter.func(data);
9953
}
9954
9955
return match;
9956
}
9957
}
9958
9959
Filter.moduleName = "filter";
9960
9961
//load defaults
9962
Filter.filters = defaultFilters;
9963
9964
function plaintext(cell, formatterParams, onRendered){
9965
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
9966
}
9967
9968
function html$1(cell, formatterParams, onRendered){
9969
return cell.getValue();
9970
}
9971
9972
function textarea$1(cell, formatterParams, onRendered){
9973
cell.getElement().style.whiteSpace = "pre-wrap";
9974
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
9975
}
9976
9977
function money(cell, formatterParams, onRendered){
9978
var floatVal = parseFloat(cell.getValue()),
9979
sign = "",
9980
number, integer, decimal, rgx, value;
9981
9982
var decimalSym = formatterParams.decimal || ".";
9983
var thousandSym = formatterParams.thousand || ",";
9984
var negativeSign = formatterParams.negativeSign || "-";
9985
var symbol = formatterParams.symbol || "";
9986
var after = !!formatterParams.symbolAfter;
9987
var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2;
9988
9989
if(isNaN(floatVal)){
9990
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
9991
}
9992
9993
if(floatVal < 0){
9994
floatVal = Math.abs(floatVal);
9995
sign = negativeSign;
9996
}
9997
9998
number = precision !== false ? floatVal.toFixed(precision) : floatVal;
9999
number = String(number).split(".");
10000
10001
integer = number[0];
10002
decimal = number.length > 1 ? decimalSym + number[1] : "";
10003
10004
if (formatterParams.thousand !== false) {
10005
rgx = /(\d+)(\d{3})/;
10006
10007
while (rgx.test(integer)){
10008
integer = integer.replace(rgx, "$1" + thousandSym + "$2");
10009
}
10010
}
10011
10012
value = integer + decimal;
10013
10014
if(sign === true){
10015
value = "(" + value + ")";
10016
return after ? value + symbol : symbol + value;
10017
}else {
10018
return after ? sign + value + symbol : sign + symbol + value;
10019
}
10020
}
10021
10022
function link(cell, formatterParams, onRendered){
10023
var value = cell.getValue(),
10024
urlPrefix = formatterParams.urlPrefix || "",
10025
download = formatterParams.download,
10026
label = value,
10027
el = document.createElement("a"),
10028
data;
10029
10030
function labelTraverse(path, data){
10031
var item = path.shift(),
10032
value = data[item];
10033
10034
if(path.length && typeof value === "object"){
10035
return labelTraverse(path, value);
10036
}
10037
10038
return value;
10039
}
10040
10041
if(formatterParams.labelField){
10042
data = cell.getData();
10043
label = labelTraverse(formatterParams.labelField.split(this.table.options.nestedFieldSeparator), data);
10044
}
10045
10046
if(formatterParams.label){
10047
switch(typeof formatterParams.label){
10048
case "string":
10049
label = formatterParams.label;
10050
break;
10051
10052
case "function":
10053
label = formatterParams.label(cell);
10054
break;
10055
}
10056
}
10057
10058
if(label){
10059
if(formatterParams.urlField){
10060
data = cell.getData();
10061
value = data[formatterParams.urlField];
10062
}
10063
10064
if(formatterParams.url){
10065
switch(typeof formatterParams.url){
10066
case "string":
10067
value = formatterParams.url;
10068
break;
10069
10070
case "function":
10071
value = formatterParams.url(cell);
10072
break;
10073
}
10074
}
10075
10076
el.setAttribute("href", urlPrefix + value);
10077
10078
if(formatterParams.target){
10079
el.setAttribute("target", formatterParams.target);
10080
}
10081
10082
if(formatterParams.download){
10083
10084
if(typeof download == "function"){
10085
download = download(cell);
10086
}else {
10087
download = download === true ? "" : download;
10088
}
10089
10090
el.setAttribute("download", download);
10091
}
10092
10093
el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label));
10094
10095
return el;
10096
}else {
10097
return "&nbsp;";
10098
}
10099
}
10100
10101
function image(cell, formatterParams, onRendered){
10102
var el = document.createElement("img"),
10103
src = cell.getValue();
10104
10105
if(formatterParams.urlPrefix){
10106
src = formatterParams.urlPrefix + cell.getValue();
10107
}
10108
10109
if(formatterParams.urlSuffix){
10110
src = src + formatterParams.urlSuffix;
10111
}
10112
10113
el.setAttribute("src", src);
10114
10115
switch(typeof formatterParams.height){
10116
case "number":
10117
el.style.height = formatterParams.height + "px";
10118
break;
10119
10120
case "string":
10121
el.style.height = formatterParams.height;
10122
break;
10123
}
10124
10125
switch(typeof formatterParams.width){
10126
case "number":
10127
el.style.width = formatterParams.width + "px";
10128
break;
10129
10130
case "string":
10131
el.style.width = formatterParams.width;
10132
break;
10133
}
10134
10135
el.addEventListener("load", function(){
10136
cell.getRow().normalizeHeight();
10137
});
10138
10139
return el;
10140
}
10141
10142
function tickCross$1(cell, formatterParams, onRendered){
10143
var value = cell.getValue(),
10144
element = cell.getElement(),
10145
empty = formatterParams.allowEmpty,
10146
truthy = formatterParams.allowTruthy,
10147
trueValueSet = Object.keys(formatterParams).includes("trueValue"),
10148
tick = typeof formatterParams.tickElement !== "undefined" ? formatterParams.tickElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>',
10149
cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';
10150
10151
if((trueValueSet && value === formatterParams.trueValue) || (!trueValueSet && ((truthy && value) || (value === true || value === "true" || value === "True" || value === 1 || value === "1")))){
10152
element.setAttribute("aria-checked", true);
10153
return tick || "";
10154
}else {
10155
if(empty && (value === "null" || value === "" || value === null || typeof value === "undefined")){
10156
element.setAttribute("aria-checked", "mixed");
10157
return "";
10158
}else {
10159
element.setAttribute("aria-checked", false);
10160
return cross || "";
10161
}
10162
}
10163
}
10164
10165
function datetime$1(cell, formatterParams, onRendered){
10166
var DT = window.DateTime || luxon.DateTime;
10167
var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss";
10168
var outputFormat = formatterParams.outputFormat || "dd/MM/yyyy HH:mm:ss";
10169
var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
10170
var value = cell.getValue();
10171
10172
if(typeof DT != "undefined"){
10173
var newDatetime;
10174
10175
if(DT.isDateTime(value)){
10176
newDatetime = value;
10177
}else if(inputFormat === "iso"){
10178
newDatetime = DT.fromISO(String(value));
10179
}else {
10180
newDatetime = DT.fromFormat(String(value), inputFormat);
10181
}
10182
10183
if(newDatetime.isValid){
10184
if(formatterParams.timezone){
10185
newDatetime = newDatetime.setZone(formatterParams.timezone);
10186
}
10187
10188
return newDatetime.toFormat(outputFormat);
10189
}else {
10190
if(invalid === true || !value){
10191
return value;
10192
}else if(typeof invalid === "function"){
10193
return invalid(value);
10194
}else {
10195
return invalid;
10196
}
10197
}
10198
}else {
10199
console.error("Format Error - 'datetime' formatter is dependant on luxon.js");
10200
}
10201
}
10202
10203
function datetimediff (cell, formatterParams, onRendered) {
10204
var DT = window.DateTime || luxon.DateTime;
10205
var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss";
10206
var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
10207
var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false;
10208
var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : "days";
10209
var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false;
10210
var date = typeof formatterParams.date !== "undefined" ? formatterParams.date : DT.now();
10211
var value = cell.getValue();
10212
10213
if(typeof DT != "undefined"){
10214
var newDatetime;
10215
10216
if(DT.isDateTime(value)){
10217
newDatetime = value;
10218
}else if(inputFormat === "iso"){
10219
newDatetime = DT.fromISO(String(value));
10220
}else {
10221
newDatetime = DT.fromFormat(String(value), inputFormat);
10222
}
10223
10224
if (newDatetime.isValid){
10225
if(humanize){
10226
return newDatetime.diff(date, unit).toHuman() + (suffix ? " " + suffix : "");
10227
}else {
10228
return parseInt(newDatetime.diff(date, unit)[unit]) + (suffix ? " " + suffix : "");
10229
}
10230
} else {
10231
10232
if (invalid === true) {
10233
return value;
10234
} else if (typeof invalid === "function") {
10235
return invalid(value);
10236
} else {
10237
return invalid;
10238
}
10239
}
10240
}else {
10241
console.error("Format Error - 'datetimediff' formatter is dependant on luxon.js");
10242
}
10243
}
10244
10245
function lookup (cell, formatterParams, onRendered) {
10246
var value = cell.getValue();
10247
10248
if (typeof formatterParams[value] === "undefined") {
10249
console.warn('Missing display value for ' + value);
10250
return value;
10251
}
10252
10253
return formatterParams[value];
10254
}
10255
10256
function star$1(cell, formatterParams, onRendered){
10257
var value = cell.getValue(),
10258
element = cell.getElement(),
10259
maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5,
10260
stars = document.createElement("span"),
10261
star = document.createElementNS('http://www.w3.org/2000/svg', "svg"),
10262
starActive = '<polygon fill="#FFEA00" stroke="#C1AB60" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>',
10263
starInactive = '<polygon fill="#D2D2D2" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
10264
10265
//style stars holder
10266
stars.style.verticalAlign = "middle";
10267
10268
//style star
10269
star.setAttribute("width", "14");
10270
star.setAttribute("height", "14");
10271
star.setAttribute("viewBox", "0 0 512 512");
10272
star.setAttribute("xml:space", "preserve");
10273
star.style.padding = "0 1px";
10274
10275
value = value && !isNaN(value) ? parseInt(value) : 0;
10276
10277
value = Math.max(0, Math.min(value, maxStars));
10278
10279
for(var i=1;i<= maxStars;i++){
10280
var nextStar = star.cloneNode(true);
10281
nextStar.innerHTML = i <= value ? starActive : starInactive;
10282
10283
stars.appendChild(nextStar);
10284
}
10285
10286
element.style.whiteSpace = "nowrap";
10287
element.style.overflow = "hidden";
10288
element.style.textOverflow = "ellipsis";
10289
10290
element.setAttribute("aria-label", value);
10291
10292
return stars;
10293
}
10294
10295
function traffic(cell, formatterParams, onRendered){
10296
var value = this.sanitizeHTML(cell.getValue()) || 0,
10297
el = document.createElement("span"),
10298
max = formatterParams && formatterParams.max ? formatterParams.max : 100,
10299
min = formatterParams && formatterParams.min ? formatterParams.min : 0,
10300
colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"],
10301
color = "#666666",
10302
percent, percentValue;
10303
10304
if(isNaN(value) || typeof cell.getValue() === "undefined"){
10305
return;
10306
}
10307
10308
el.classList.add("tabulator-traffic-light");
10309
10310
//make sure value is in range
10311
percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
10312
percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;
10313
10314
//workout percentage
10315
percent = (max - min) / 100;
10316
percentValue = Math.round((percentValue - min) / percent);
10317
10318
//set color
10319
switch(typeof colors){
10320
case "string":
10321
color = colors;
10322
break;
10323
case "function":
10324
color = colors(value);
10325
break;
10326
case "object":
10327
if(Array.isArray(colors)){
10328
var unit = 100 / colors.length;
10329
var index = Math.floor(percentValue / unit);
10330
10331
index = Math.min(index, colors.length - 1);
10332
index = Math.max(index, 0);
10333
color = colors[index];
10334
break;
10335
}
10336
}
10337
10338
el.style.backgroundColor = color;
10339
10340
return el;
10341
}
10342
10343
function progress$1(cell, formatterParams = {}, onRendered){ //progress bar
10344
var value = this.sanitizeHTML(cell.getValue()) || 0,
10345
element = cell.getElement(),
10346
max = formatterParams.max ? formatterParams.max : 100,
10347
min = formatterParams.min ? formatterParams.min : 0,
10348
legendAlign = formatterParams.legendAlign ? formatterParams.legendAlign : "center",
10349
percent, percentValue, color, legend, legendColor;
10350
10351
//make sure value is in range
10352
percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
10353
percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;
10354
10355
//workout percentage
10356
percent = (max - min) / 100;
10357
percentValue = Math.round((percentValue - min) / percent);
10358
10359
//set bar color
10360
switch(typeof formatterParams.color){
10361
case "string":
10362
color = formatterParams.color;
10363
break;
10364
case "function":
10365
color = formatterParams.color(value);
10366
break;
10367
case "object":
10368
if(Array.isArray(formatterParams.color)){
10369
let unit = 100 / formatterParams.color.length;
10370
let index = Math.floor(percentValue / unit);
10371
10372
index = Math.min(index, formatterParams.color.length - 1);
10373
index = Math.max(index, 0);
10374
color = formatterParams.color[index];
10375
break;
10376
}
10377
default:
10378
color = "#2DC214";
10379
}
10380
10381
//generate legend
10382
switch(typeof formatterParams.legend){
10383
case "string":
10384
legend = formatterParams.legend;
10385
break;
10386
case "function":
10387
legend = formatterParams.legend(value);
10388
break;
10389
case "boolean":
10390
legend = value;
10391
break;
10392
default:
10393
legend = false;
10394
}
10395
10396
//set legend color
10397
switch(typeof formatterParams.legendColor){
10398
case "string":
10399
legendColor = formatterParams.legendColor;
10400
break;
10401
case "function":
10402
legendColor = formatterParams.legendColor(value);
10403
break;
10404
case "object":
10405
if(Array.isArray(formatterParams.legendColor)){
10406
let unit = 100 / formatterParams.legendColor.length;
10407
let index = Math.floor(percentValue / unit);
10408
10409
index = Math.min(index, formatterParams.legendColor.length - 1);
10410
index = Math.max(index, 0);
10411
legendColor = formatterParams.legendColor[index];
10412
}
10413
break;
10414
default:
10415
legendColor = "#000";
10416
}
10417
10418
element.style.minWidth = "30px";
10419
element.style.position = "relative";
10420
10421
element.setAttribute("aria-label", percentValue);
10422
10423
var barEl = document.createElement("div");
10424
barEl.style.display = "inline-block";
10425
barEl.style.width = percentValue + "%";
10426
barEl.style.backgroundColor = color;
10427
barEl.style.height = "100%";
10428
10429
barEl.setAttribute('data-max', max);
10430
barEl.setAttribute('data-min', min);
10431
10432
var barContainer = document.createElement("div");
10433
barContainer.style.position = "relative";
10434
barContainer.style.width = "100%";
10435
barContainer.style.height = "100%";
10436
10437
if(legend){
10438
var legendEl = document.createElement("div");
10439
legendEl.style.position = "absolute";
10440
legendEl.style.top = 0;
10441
legendEl.style.left = 0;
10442
legendEl.style.textAlign = legendAlign;
10443
legendEl.style.width = "100%";
10444
legendEl.style.color = legendColor;
10445
legendEl.innerHTML = legend;
10446
}
10447
10448
onRendered(function(){
10449
10450
//handle custom element needed if formatter is to be included in printed/downloaded output
10451
if(!(cell instanceof CellComponent)){
10452
var holderEl = document.createElement("div");
10453
holderEl.style.position = "absolute";
10454
holderEl.style.top = "4px";
10455
holderEl.style.bottom = "4px";
10456
holderEl.style.left = "4px";
10457
holderEl.style.right = "4px";
10458
10459
element.appendChild(holderEl);
10460
10461
element = holderEl;
10462
}
10463
10464
element.appendChild(barContainer);
10465
barContainer.appendChild(barEl);
10466
10467
if(legend){
10468
barContainer.appendChild(legendEl);
10469
}
10470
});
10471
10472
return "";
10473
}
10474
10475
function color(cell, formatterParams, onRendered){
10476
cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue());
10477
return "";
10478
}
10479
10480
function buttonTick(cell, formatterParams, onRendered){
10481
return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>';
10482
}
10483
10484
function buttonCross(cell, formatterParams, onRendered){
10485
return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';
10486
}
10487
10488
function rownum(cell, formatterParams, onRendered){
10489
var content = document.createElement("span");
10490
var row = cell.getRow();
10491
10492
row.watchPosition((position) => {
10493
content.innerText = position;
10494
});
10495
10496
return content;
10497
}
10498
10499
function handle(cell, formatterParams, onRendered){
10500
cell.getElement().classList.add("tabulator-row-handle");
10501
return "<div class='tabulator-row-handle-box'><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div></div>";
10502
}
10503
10504
function responsiveCollapse(cell, formatterParams, onRendered){
10505
var el = document.createElement("div"),
10506
config = cell.getRow()._row.modules.responsiveLayout;
10507
10508
el.classList.add("tabulator-responsive-collapse-toggle");
10509
10510
el.innerHTML = `<svg class='tabulator-responsive-collapse-toggle-open' viewbox="0 0 24 24">
10511
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
10512
<line y1="7" x1="12" y2="17" x2="12" fill="none" stroke-width="3" stroke-linecap="round" />
10513
</svg>
10514
10515
<svg class='tabulator-responsive-collapse-toggle-close' viewbox="0 0 24 24">
10516
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
10517
</svg>`;
10518
10519
cell.getElement().classList.add("tabulator-row-handle");
10520
10521
function toggleList(isOpen){
10522
var collapseEl = config.element;
10523
10524
config.open = isOpen;
10525
10526
if(collapseEl){
10527
10528
if(config.open){
10529
el.classList.add("open");
10530
collapseEl.style.display = '';
10531
}else {
10532
el.classList.remove("open");
10533
collapseEl.style.display = 'none';
10534
}
10535
}
10536
}
10537
10538
el.addEventListener("click", function(e){
10539
e.stopImmediatePropagation();
10540
toggleList(!config.open);
10541
cell.getTable().rowManager.adjustTableSize();
10542
});
10543
10544
toggleList(config.open);
10545
10546
return el;
10547
}
10548
10549
function rowSelection(cell, formatterParams, onRendered){
10550
var checkbox = document.createElement("input");
10551
var blocked = false;
10552
10553
checkbox.type = 'checkbox';
10554
10555
checkbox.setAttribute("aria-label", "Select Row");
10556
10557
if(this.table.modExists("selectRow", true)){
10558
10559
checkbox.addEventListener("click", (e) => {
10560
e.stopPropagation();
10561
});
10562
10563
if(typeof cell.getRow == 'function'){
10564
var row = cell.getRow();
10565
10566
if(row instanceof RowComponent){
10567
10568
checkbox.addEventListener("change", (e) => {
10569
if(this.table.options.selectableRangeMode === "click"){
10570
if(!blocked){
10571
row.toggleSelect();
10572
}else {
10573
blocked = false;
10574
}
10575
}else {
10576
row.toggleSelect();
10577
}
10578
});
10579
10580
if(this.table.options.selectableRangeMode === "click"){
10581
checkbox.addEventListener("click", (e) => {
10582
blocked = true;
10583
this.table.modules.selectRow.handleComplexRowClick(row._row, e);
10584
});
10585
}
10586
10587
checkbox.checked = row.isSelected && row.isSelected();
10588
this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox);
10589
}else {
10590
checkbox = "";
10591
}
10592
}else {
10593
checkbox.addEventListener("change", (e) => {
10594
if(this.table.modules.selectRow.selectedRows.length){
10595
this.table.deselectRow();
10596
}else {
10597
this.table.selectRow(formatterParams.rowRange);
10598
}
10599
});
10600
10601
this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox);
10602
}
10603
}
10604
10605
return checkbox;
10606
}
10607
10608
var defaultFormatters = {
10609
plaintext:plaintext,
10610
html:html$1,
10611
textarea:textarea$1,
10612
money:money,
10613
link:link,
10614
image:image,
10615
tickCross:tickCross$1,
10616
datetime:datetime$1,
10617
datetimediff:datetimediff,
10618
lookup:lookup,
10619
star:star$1,
10620
traffic:traffic,
10621
progress:progress$1,
10622
color:color,
10623
buttonTick:buttonTick,
10624
buttonCross:buttonCross,
10625
rownum:rownum,
10626
handle:handle,
10627
responsiveCollapse:responsiveCollapse,
10628
rowSelection:rowSelection,
10629
};
10630
10631
class Format extends Module{
10632
10633
constructor(table){
10634
super(table);
10635
10636
this.registerColumnOption("formatter");
10637
this.registerColumnOption("formatterParams");
10638
10639
this.registerColumnOption("formatterPrint");
10640
this.registerColumnOption("formatterPrintParams");
10641
this.registerColumnOption("formatterClipboard");
10642
this.registerColumnOption("formatterClipboardParams");
10643
this.registerColumnOption("formatterHtmlOutput");
10644
this.registerColumnOption("formatterHtmlOutputParams");
10645
this.registerColumnOption("titleFormatter");
10646
this.registerColumnOption("titleFormatterParams");
10647
}
10648
10649
initialize(){
10650
this.subscribe("cell-format", this.formatValue.bind(this));
10651
this.subscribe("cell-rendered", this.cellRendered.bind(this));
10652
this.subscribe("column-layout", this.initializeColumn.bind(this));
10653
this.subscribe("column-format", this.formatHeader.bind(this));
10654
}
10655
10656
//initialize column formatter
10657
initializeColumn(column){
10658
column.modules.format = this.lookupFormatter(column, "");
10659
10660
if(typeof column.definition.formatterPrint !== "undefined"){
10661
column.modules.format.print = this.lookupFormatter(column, "Print");
10662
}
10663
10664
if(typeof column.definition.formatterClipboard !== "undefined"){
10665
column.modules.format.clipboard = this.lookupFormatter(column, "Clipboard");
10666
}
10667
10668
if(typeof column.definition.formatterHtmlOutput !== "undefined"){
10669
column.modules.format.htmlOutput = this.lookupFormatter(column, "HtmlOutput");
10670
}
10671
}
10672
10673
lookupFormatter(column, type){
10674
var config = {params:column.definition["formatter" + type + "Params"] || {}},
10675
formatter = column.definition["formatter" + type];
10676
10677
//set column formatter
10678
switch(typeof formatter){
10679
case "string":
10680
if(Format.formatters[formatter]){
10681
config.formatter = Format.formatters[formatter];
10682
}else {
10683
console.warn("Formatter Error - No such formatter found: ", formatter);
10684
config.formatter = Format.formatters.plaintext;
10685
}
10686
break;
10687
10688
case "function":
10689
config.formatter = formatter;
10690
break;
10691
10692
default:
10693
config.formatter = Format.formatters.plaintext;
10694
break;
10695
}
10696
10697
return config;
10698
}
10699
10700
cellRendered(cell){
10701
if(cell.modules.format && cell.modules.format.renderedCallback && !cell.modules.format.rendered){
10702
cell.modules.format.renderedCallback();
10703
cell.modules.format.rendered = true;
10704
}
10705
}
10706
10707
//return a formatted value for a column header
10708
formatHeader(column, title, el){
10709
var formatter, params, onRendered, mockCell;
10710
10711
if(column.definition.titleFormatter){
10712
formatter = this.getFormatter(column.definition.titleFormatter);
10713
10714
onRendered = (callback) => {
10715
column.titleFormatterRendered = callback;
10716
};
10717
10718
mockCell = {
10719
getValue:function(){
10720
return title;
10721
},
10722
getElement:function(){
10723
return el;
10724
},
10725
getType:function(){
10726
return "header";
10727
},
10728
getColumn:function(){
10729
return column.getComponent();
10730
},
10731
getTable:() => {
10732
return this.table;
10733
}
10734
};
10735
10736
params = column.definition.titleFormatterParams || {};
10737
10738
params = typeof params === "function" ? params() : params;
10739
10740
return formatter.call(this, mockCell, params, onRendered);
10741
}else {
10742
return title;
10743
}
10744
}
10745
10746
10747
//return a formatted value for a cell
10748
formatValue(cell){
10749
var component = cell.getComponent(),
10750
params = typeof cell.column.modules.format.params === "function" ? cell.column.modules.format.params(component) : cell.column.modules.format.params;
10751
10752
function onRendered(callback){
10753
if(!cell.modules.format){
10754
cell.modules.format = {};
10755
}
10756
10757
cell.modules.format.renderedCallback = callback;
10758
cell.modules.format.rendered = false;
10759
}
10760
10761
return cell.column.modules.format.formatter.call(this, component, params, onRendered);
10762
}
10763
10764
formatExportValue(cell, type){
10765
var formatter = cell.column.modules.format[type],
10766
params;
10767
10768
if(formatter){
10769
params = typeof formatter.params === "function" ? formatter.params(cell.getComponent()) : formatter.params;
10770
10771
function onRendered(callback){
10772
if(!cell.modules.format){
10773
cell.modules.format = {};
10774
}
10775
10776
cell.modules.format.renderedCallback = callback;
10777
cell.modules.format.rendered = false;
10778
}
10779
10780
return formatter.formatter.call(this, cell.getComponent(), params, onRendered);
10781
10782
}else {
10783
return this.formatValue(cell);
10784
}
10785
}
10786
10787
sanitizeHTML(value){
10788
if(value){
10789
var entityMap = {
10790
'&': '&amp;',
10791
'<': '&lt;',
10792
'>': '&gt;',
10793
'"': '&quot;',
10794
"'": '&#39;',
10795
'/': '&#x2F;',
10796
'`': '&#x60;',
10797
'=': '&#x3D;'
10798
};
10799
10800
return String(value).replace(/[&<>"'`=/]/g, function (s) {
10801
return entityMap[s];
10802
});
10803
}else {
10804
return value;
10805
}
10806
}
10807
10808
emptyToSpace(value){
10809
return value === null || typeof value === "undefined" || value === "" ? "&nbsp;" : value;
10810
}
10811
10812
//get formatter for cell
10813
getFormatter(formatter){
10814
switch(typeof formatter){
10815
case "string":
10816
if(Format.formatters[formatter]){
10817
formatter = Format.formatters[formatter];
10818
}else {
10819
console.warn("Formatter Error - No such formatter found: ", formatter);
10820
formatter = Format.formatters.plaintext;
10821
}
10822
break;
10823
10824
case "function":
10825
//Custom formatter Function, do nothing
10826
break;
10827
10828
default:
10829
formatter = Format.formatters.plaintext;
10830
break;
10831
}
10832
10833
return formatter;
10834
}
10835
}
10836
10837
Format.moduleName = "format";
10838
10839
//load defaults
10840
Format.formatters = defaultFormatters;
10841
10842
class FrozenColumns extends Module{
10843
10844
constructor(table){
10845
super(table);
10846
10847
this.leftColumns = [];
10848
this.rightColumns = [];
10849
this.initializationMode = "left";
10850
this.active = false;
10851
this.blocked = true;
10852
10853
this.registerColumnOption("frozen");
10854
}
10855
10856
//reset initial state
10857
reset(){
10858
this.initializationMode = "left";
10859
this.leftColumns = [];
10860
this.rightColumns = [];
10861
this.active = false;
10862
}
10863
10864
initialize(){
10865
this.subscribe("cell-layout", this.layoutCell.bind(this));
10866
this.subscribe("column-init", this.initializeColumn.bind(this));
10867
this.subscribe("column-width", this.layout.bind(this));
10868
this.subscribe("row-layout-after", this.layoutRow.bind(this));
10869
this.subscribe("table-layout", this.layout.bind(this));
10870
this.subscribe("columns-loading", this.reset.bind(this));
10871
10872
this.subscribe("column-add", this.reinitializeColumns.bind(this));
10873
this.subscribe("column-delete", this.reinitializeColumns.bind(this));
10874
10875
this.subscribe("table-redraw", this.layout.bind(this));
10876
this.subscribe("layout-refreshing", this.blockLayout.bind(this));
10877
this.subscribe("layout-refreshed", this.unblockLayout.bind(this));
10878
this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this));
10879
}
10880
10881
blockLayout(){
10882
this.blocked = true;
10883
}
10884
10885
unblockLayout(){
10886
this.blocked = false;
10887
}
10888
10889
layoutCell(cell){
10890
this.layoutElement(cell.element, cell.column);
10891
}
10892
10893
reinitializeColumns(){
10894
this.reset();
10895
10896
this.table.columnManager.columnsByIndex.forEach((column) => {
10897
this.initializeColumn(column);
10898
});
10899
}
10900
10901
//initialize specific column
10902
initializeColumn(column){
10903
var config = {margin:0, edge:false};
10904
10905
if(!column.isGroup){
10906
10907
if(this.frozenCheck(column)){
10908
10909
config.position = this.initializationMode;
10910
10911
if(this.initializationMode == "left"){
10912
this.leftColumns.push(column);
10913
}else {
10914
this.rightColumns.unshift(column);
10915
}
10916
10917
this.active = true;
10918
10919
column.modules.frozen = config;
10920
}else {
10921
this.initializationMode = "right";
10922
}
10923
}
10924
}
10925
10926
frozenCheck(column){
10927
if(column.parent.isGroup && column.definition.frozen){
10928
console.warn("Frozen Column Error - Parent column group must be frozen, not individual columns or sub column groups");
10929
}
10930
10931
if(column.parent.isGroup){
10932
return this.frozenCheck(column.parent);
10933
}else {
10934
return column.definition.frozen;
10935
}
10936
}
10937
10938
//layout calculation rows
10939
layoutCalcRows(){
10940
if(this.table.modExists("columnCalcs")){
10941
if(this.table.modules.columnCalcs.topInitialized && this.table.modules.columnCalcs.topRow){
10942
this.layoutRow(this.table.modules.columnCalcs.topRow);
10943
}
10944
10945
if(this.table.modules.columnCalcs.botInitialized && this.table.modules.columnCalcs.botRow){
10946
this.layoutRow(this.table.modules.columnCalcs.botRow);
10947
}
10948
10949
if(this.table.modExists("groupRows")){
10950
this.layoutGroupCalcs(this.table.modules.groupRows.getGroups());
10951
}
10952
}
10953
}
10954
10955
layoutGroupCalcs(groups){
10956
groups.forEach((group) => {
10957
if(group.calcs.top){
10958
this.layoutRow(group.calcs.top);
10959
}
10960
10961
if(group.calcs.bottom){
10962
this.layoutRow(group.calcs.bottom);
10963
}
10964
10965
if(group.groupList && group.groupList.length){
10966
this.layoutGroupCalcs(group.groupList);
10967
}
10968
});
10969
}
10970
10971
//calculate column positions and layout headers
10972
layoutColumnPosition(allCells){
10973
var leftParents = [];
10974
10975
var leftMargin = 0;
10976
var rightMargin = 0;
10977
10978
this.leftColumns.forEach((column, i) => {
10979
column.modules.frozen.marginValue = leftMargin;
10980
column.modules.frozen.margin = column.modules.frozen.marginValue + "px";
10981
10982
if(column.visible){
10983
leftMargin += column.getWidth();
10984
}
10985
10986
if(i == this.leftColumns.length - 1){
10987
column.modules.frozen.edge = true;
10988
}else {
10989
column.modules.frozen.edge = false;
10990
}
10991
10992
if(column.parent.isGroup){
10993
var parentEl = this.getColGroupParentElement(column);
10994
if(!leftParents.includes(parentEl)){
10995
this.layoutElement(parentEl, column);
10996
leftParents.push(parentEl);
10997
}
10998
10999
if(column.modules.frozen.edge){
11000
parentEl.classList.add("tabulator-frozen-" + column.modules.frozen.position);
11001
}
11002
}else {
11003
this.layoutElement(column.getElement(), column);
11004
}
11005
11006
if(allCells){
11007
column.cells.forEach((cell) => {
11008
this.layoutElement(cell.getElement(true), column);
11009
});
11010
}
11011
});
11012
11013
this.rightColumns.forEach((column, i) => {
11014
11015
column.modules.frozen.marginValue = rightMargin;
11016
column.modules.frozen.margin = column.modules.frozen.marginValue + "px";
11017
11018
if(column.visible){
11019
rightMargin += column.getWidth();
11020
}
11021
11022
if(i == this.rightColumns.length - 1){
11023
column.modules.frozen.edge = true;
11024
}else {
11025
column.modules.frozen.edge = false;
11026
}
11027
11028
if(column.parent.isGroup){
11029
this.layoutElement(this.getColGroupParentElement(column), column);
11030
}else {
11031
this.layoutElement(column.getElement(), column);
11032
}
11033
11034
if(allCells){
11035
column.cells.forEach((cell) => {
11036
this.layoutElement(cell.getElement(true), column);
11037
});
11038
}
11039
});
11040
}
11041
11042
getColGroupParentElement(column){
11043
return column.parent.isGroup ? this.getColGroupParentElement(column.parent) : column.getElement();
11044
}
11045
11046
//layout columns appropriately
11047
layout(){
11048
if(this.active && !this.blocked){
11049
11050
//calculate left columns
11051
this.layoutColumnPosition();
11052
11053
this.reinitializeRows();
11054
11055
this.layoutCalcRows();
11056
}
11057
}
11058
11059
reinitializeRows(){
11060
var visibleRows = this.table.rowManager.getVisibleRows(true);
11061
var otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row));
11062
11063
otherRows.forEach((row) =>{
11064
row.deinitialize();
11065
});
11066
11067
visibleRows.forEach((row) =>{
11068
if(row.type === "row"){
11069
this.layoutRow(row);
11070
}
11071
});
11072
}
11073
11074
layoutRow(row){
11075
if(this.table.options.layout === "fitDataFill" && this.rightColumns.length){
11076
this.table.rowManager.getTableElement().style.minWidth = "calc(100% - " + this.rightMargin + ")";
11077
}
11078
11079
this.leftColumns.forEach((column) => {
11080
var cell = row.getCell(column);
11081
11082
if(cell){
11083
this.layoutElement(cell.getElement(true), column);
11084
}
11085
});
11086
11087
this.rightColumns.forEach((column) => {
11088
var cell = row.getCell(column);
11089
11090
if(cell){
11091
this.layoutElement(cell.getElement(true), column);
11092
}
11093
});
11094
}
11095
11096
layoutElement(element, column){
11097
var position;
11098
11099
if(column.modules.frozen && element){
11100
element.style.position = "sticky";
11101
11102
if(this.table.rtl){
11103
position = column.modules.frozen.position === "left" ? "right" : "left";
11104
}else {
11105
position = column.modules.frozen.position;
11106
}
11107
11108
element.style[position] = column.modules.frozen.margin;
11109
11110
element.classList.add("tabulator-frozen");
11111
11112
if(column.modules.frozen.edge){
11113
element.classList.add("tabulator-frozen-" + column.modules.frozen.position);
11114
}
11115
}
11116
}
11117
11118
adjustForScrollbar(width){
11119
if(this.rightColumns.length){
11120
this.table.columnManager.getContentsElement().style.width = "calc(100% - " + width + "px)";
11121
}
11122
}
11123
11124
_calcSpace(columns, index){
11125
var width = 0;
11126
11127
for (let i = 0; i < index; i++){
11128
if(columns[i].visible){
11129
width += columns[i].getWidth();
11130
}
11131
}
11132
11133
return width;
11134
}
11135
}
11136
11137
FrozenColumns.moduleName = "frozenColumns";
11138
11139
class FrozenRows extends Module{
11140
11141
constructor(table){
11142
super(table);
11143
11144
this.topElement = document.createElement("div");
11145
this.rows = [];
11146
11147
//register component functions
11148
this.registerComponentFunction("row", "freeze", this.freezeRow.bind(this));
11149
this.registerComponentFunction("row", "unfreeze", this.unfreezeRow.bind(this));
11150
this.registerComponentFunction("row", "isFrozen", this.isRowFrozen.bind(this));
11151
11152
//register table options
11153
this.registerTableOption("frozenRowsField", "id"); //field to choose frozen rows by
11154
this.registerTableOption("frozenRows", false); //holder for frozen row identifiers
11155
}
11156
11157
initialize(){
11158
this.rows = [];
11159
11160
this.topElement.classList.add("tabulator-frozen-rows-holder");
11161
11162
// this.table.columnManager.element.append(this.topElement);
11163
this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling);
11164
11165
this.subscribe("row-deleting", this.detachRow.bind(this));
11166
this.subscribe("rows-visible", this.visibleRows.bind(this));
11167
11168
this.registerDisplayHandler(this.getRows.bind(this), 10);
11169
11170
if(this.table.options.frozenRows){
11171
this.subscribe("data-processed", this.initializeRows.bind(this));
11172
this.subscribe("row-added", this.initializeRow.bind(this));
11173
this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this));
11174
this.subscribe("column-resized", this.resizeHolderWidth.bind(this));
11175
this.subscribe("column-show", this.resizeHolderWidth.bind(this));
11176
this.subscribe("column-hide", this.resizeHolderWidth.bind(this));
11177
}
11178
11179
this.resizeHolderWidth();
11180
}
11181
11182
resizeHolderWidth(){
11183
this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px";
11184
}
11185
11186
initializeRows(){
11187
this.table.rowManager.getRows().forEach((row) => {
11188
this.initializeRow(row);
11189
});
11190
}
11191
11192
initializeRow(row){
11193
var frozenRows = this.table.options.frozenRows,
11194
rowType = typeof frozenRows;
11195
11196
if(rowType === "number"){
11197
if(row.getPosition() && (row.getPosition() + this.rows.length) <= frozenRows){
11198
this.freezeRow(row);
11199
}
11200
}else if(rowType === "function"){
11201
if(frozenRows.call(this.table, row.getComponent())){
11202
this.freezeRow(row);
11203
}
11204
}else if(Array.isArray(frozenRows)){
11205
if(frozenRows.includes(row.data[this.options("frozenRowsField")])){
11206
this.freezeRow(row);
11207
}
11208
}
11209
}
11210
11211
isRowFrozen(row){
11212
var index = this.rows.indexOf(row);
11213
return index > -1;
11214
}
11215
11216
isFrozen(){
11217
return !!this.rows.length;
11218
}
11219
11220
visibleRows(viewable, rows){
11221
this.rows.forEach((row) => {
11222
rows.push(row);
11223
});
11224
11225
return rows;
11226
}
11227
11228
//filter frozen rows out of display data
11229
getRows(rows){
11230
var output = rows.slice(0);
11231
11232
this.rows.forEach(function(row){
11233
var index = output.indexOf(row);
11234
11235
if(index > -1){
11236
output.splice(index, 1);
11237
}
11238
});
11239
11240
return output;
11241
}
11242
11243
freezeRow(row){
11244
if(!row.modules.frozen){
11245
row.modules.frozen = true;
11246
this.topElement.appendChild(row.getElement());
11247
row.initialize();
11248
row.normalizeHeight();
11249
11250
this.rows.push(row);
11251
11252
this.refreshData(false, "display");
11253
11254
this.table.rowManager.adjustTableSize();
11255
11256
this.styleRows();
11257
11258
}else {
11259
console.warn("Freeze Error - Row is already frozen");
11260
}
11261
}
11262
11263
unfreezeRow(row){
11264
if(row.modules.frozen){
11265
11266
row.modules.frozen = false;
11267
11268
this.detachRow(row);
11269
11270
this.table.rowManager.adjustTableSize();
11271
11272
this.refreshData(false, "display");
11273
11274
if(this.rows.length){
11275
this.styleRows();
11276
}
11277
11278
}else {
11279
console.warn("Freeze Error - Row is already unfrozen");
11280
}
11281
}
11282
11283
detachRow(row){
11284
var index = this.rows.indexOf(row);
11285
11286
if(index > -1){
11287
var rowEl = row.getElement();
11288
11289
if(rowEl.parentNode){
11290
rowEl.parentNode.removeChild(rowEl);
11291
}
11292
11293
this.rows.splice(index, 1);
11294
}
11295
}
11296
11297
styleRows(row){
11298
this.rows.forEach((row, i) => {
11299
this.table.rowManager.styleRow(row, i);
11300
});
11301
}
11302
}
11303
11304
FrozenRows.moduleName = "frozenRows";
11305
11306
//public group object
11307
class GroupComponent {
11308
constructor (group){
11309
this._group = group;
11310
this.type = "GroupComponent";
11311
11312
return new Proxy(this, {
11313
get: function(target, name, receiver) {
11314
if (typeof target[name] !== "undefined") {
11315
return target[name];
11316
}else {
11317
return target._group.groupManager.table.componentFunctionBinder.handle("group", target._group, name);
11318
}
11319
}
11320
});
11321
}
11322
11323
getKey(){
11324
return this._group.key;
11325
}
11326
11327
getField(){
11328
return this._group.field;
11329
}
11330
11331
getElement(){
11332
return this._group.element;
11333
}
11334
11335
getRows(){
11336
return this._group.getRows(true);
11337
}
11338
11339
getSubGroups(){
11340
return this._group.getSubGroups(true);
11341
}
11342
11343
getParentGroup(){
11344
return this._group.parent ? this._group.parent.getComponent() : false;
11345
}
11346
11347
isVisible(){
11348
return this._group.visible;
11349
}
11350
11351
show(){
11352
this._group.show();
11353
}
11354
11355
hide(){
11356
this._group.hide();
11357
}
11358
11359
toggle(){
11360
this._group.toggleVisibility();
11361
}
11362
11363
scrollTo(position, ifVisible){
11364
return this._group.groupManager.table.rowManager.scrollToRow(this._group, position, ifVisible);
11365
}
11366
11367
_getSelf(){
11368
return this._group;
11369
}
11370
11371
getTable(){
11372
return this._group.groupManager.table;
11373
}
11374
}
11375
11376
//Group functions
11377
class Group{
11378
11379
constructor(groupManager, parent, level, key, field, generator, oldGroup){
11380
this.groupManager = groupManager;
11381
this.parent = parent;
11382
this.key = key;
11383
this.level = level;
11384
this.field = field;
11385
this.hasSubGroups = level < (groupManager.groupIDLookups.length - 1);
11386
this.addRow = this.hasSubGroups ? this._addRowToGroup : this._addRow;
11387
this.type = "group"; //type of element
11388
this.old = oldGroup;
11389
this.rows = [];
11390
this.groups = [];
11391
this.groupList = [];
11392
this.generator = generator;
11393
this.element = false;
11394
this.elementContents = false;
11395
this.height = 0;
11396
this.outerHeight = 0;
11397
this.initialized = false;
11398
this.calcs = {};
11399
this.initialized = false;
11400
this.modules = {};
11401
this.arrowElement = false;
11402
11403
this.visible = oldGroup ? oldGroup.visible : (typeof groupManager.startOpen[level] !== "undefined" ? groupManager.startOpen[level] : groupManager.startOpen[0]);
11404
11405
this.component = null;
11406
11407
this.createElements();
11408
this.addBindings();
11409
11410
this.createValueGroups();
11411
}
11412
11413
wipe(elementsOnly){
11414
if(!elementsOnly){
11415
if(this.groupList.length){
11416
this.groupList.forEach(function(group){
11417
group.wipe();
11418
});
11419
}else {
11420
this.rows.forEach((row) => {
11421
if(row.modules){
11422
delete row.modules.group;
11423
}
11424
});
11425
}
11426
}
11427
11428
this.element = false;
11429
this.arrowElement = false;
11430
this.elementContents = false;
11431
}
11432
11433
createElements(){
11434
var arrow = document.createElement("div");
11435
arrow.classList.add("tabulator-arrow");
11436
11437
this.element = document.createElement("div");
11438
this.element.classList.add("tabulator-row");
11439
this.element.classList.add("tabulator-group");
11440
this.element.classList.add("tabulator-group-level-" + this.level);
11441
this.element.setAttribute("role", "rowgroup");
11442
11443
this.arrowElement = document.createElement("div");
11444
this.arrowElement.classList.add("tabulator-group-toggle");
11445
this.arrowElement.appendChild(arrow);
11446
11447
//setup movable rows
11448
if(this.groupManager.table.options.movableRows !== false && this.groupManager.table.modExists("moveRow")){
11449
this.groupManager.table.modules.moveRow.initializeGroupHeader(this);
11450
}
11451
}
11452
11453
createValueGroups(){
11454
var level = this.level + 1;
11455
if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){
11456
this.groupManager.allowedValues[level].forEach((value) => {
11457
this._createGroup(value, level);
11458
});
11459
}
11460
}
11461
11462
addBindings(){
11463
var toggleElement;
11464
11465
if(this.groupManager.table.options.groupToggleElement){
11466
toggleElement = this.groupManager.table.options.groupToggleElement == "arrow" ? this.arrowElement : this.element;
11467
11468
toggleElement.addEventListener("click", (e) => {
11469
if(this.groupManager.table.options.groupToggleElement === "arrow"){
11470
e.stopPropagation();
11471
e.stopImmediatePropagation();
11472
}
11473
11474
//allow click event to propagate before toggling visibility
11475
setTimeout(() => {
11476
this.toggleVisibility();
11477
});
11478
});
11479
}
11480
}
11481
11482
_createGroup(groupID, level){
11483
var groupKey = level + "_" + groupID;
11484
var group = new Group(this.groupManager, this, level, groupID, this.groupManager.groupIDLookups[level].field, this.groupManager.headerGenerator[level] || this.groupManager.headerGenerator[0], this.old ? this.old.groups[groupKey] : false);
11485
11486
this.groups[groupKey] = group;
11487
this.groupList.push(group);
11488
}
11489
11490
_addRowToGroup(row){
11491
11492
var level = this.level + 1;
11493
11494
if(this.hasSubGroups){
11495
var groupID = this.groupManager.groupIDLookups[level].func(row.getData()),
11496
groupKey = level + "_" + groupID;
11497
11498
if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){
11499
if(this.groups[groupKey]){
11500
this.groups[groupKey].addRow(row);
11501
}
11502
}else {
11503
if(!this.groups[groupKey]){
11504
this._createGroup(groupID, level);
11505
}
11506
11507
this.groups[groupKey].addRow(row);
11508
}
11509
}
11510
}
11511
11512
_addRow(row){
11513
this.rows.push(row);
11514
row.modules.group = this;
11515
}
11516
11517
insertRow(row, to, after){
11518
var data = this.conformRowData({});
11519
11520
row.updateData(data);
11521
11522
var toIndex = this.rows.indexOf(to);
11523
11524
if(toIndex > -1){
11525
if(after){
11526
this.rows.splice(toIndex+1, 0, row);
11527
}else {
11528
this.rows.splice(toIndex, 0, row);
11529
}
11530
}else {
11531
if(after){
11532
this.rows.push(row);
11533
}else {
11534
this.rows.unshift(row);
11535
}
11536
}
11537
11538
row.modules.group = this;
11539
11540
// this.generateGroupHeaderContents();
11541
11542
if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){
11543
this.groupManager.table.modules.columnCalcs.recalcGroup(this);
11544
}
11545
11546
this.groupManager.updateGroupRows(true);
11547
}
11548
11549
scrollHeader(left){
11550
if(this.arrowElement){
11551
this.arrowElement.style.marginLeft = left;
11552
11553
this.groupList.forEach(function(child){
11554
child.scrollHeader(left);
11555
});
11556
}
11557
}
11558
11559
getRowIndex(row){}
11560
11561
//update row data to match grouping constraints
11562
conformRowData(data){
11563
if(this.field){
11564
data[this.field] = this.key;
11565
}else {
11566
console.warn("Data Conforming Error - Cannot conform row data to match new group as groupBy is a function");
11567
}
11568
11569
if(this.parent){
11570
data = this.parent.conformRowData(data);
11571
}
11572
11573
return data;
11574
}
11575
11576
removeRow(row){
11577
var index = this.rows.indexOf(row);
11578
var el = row.getElement();
11579
11580
if(index > -1){
11581
this.rows.splice(index, 1);
11582
}
11583
11584
if(!this.groupManager.table.options.groupValues && !this.rows.length){
11585
if(this.parent){
11586
this.parent.removeGroup(this);
11587
}else {
11588
this.groupManager.removeGroup(this);
11589
}
11590
11591
this.groupManager.updateGroupRows(true);
11592
11593
}else {
11594
11595
if(el.parentNode){
11596
el.parentNode.removeChild(el);
11597
}
11598
11599
if(!this.groupManager.blockRedraw){
11600
this.generateGroupHeaderContents();
11601
11602
if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){
11603
this.groupManager.table.modules.columnCalcs.recalcGroup(this);
11604
}
11605
}
11606
11607
}
11608
}
11609
11610
removeGroup(group){
11611
var groupKey = group.level + "_" + group.key,
11612
index;
11613
11614
if(this.groups[groupKey]){
11615
delete this.groups[groupKey];
11616
11617
index = this.groupList.indexOf(group);
11618
11619
if(index > -1){
11620
this.groupList.splice(index, 1);
11621
}
11622
11623
if(!this.groupList.length){
11624
if(this.parent){
11625
this.parent.removeGroup(this);
11626
}else {
11627
this.groupManager.removeGroup(this);
11628
}
11629
}
11630
}
11631
}
11632
11633
getHeadersAndRows(){
11634
var output = [];
11635
11636
output.push(this);
11637
11638
this._visSet();
11639
11640
11641
if(this.calcs.top){
11642
this.calcs.top.detachElement();
11643
this.calcs.top.deleteCells();
11644
}
11645
11646
if(this.calcs.bottom){
11647
this.calcs.bottom.detachElement();
11648
this.calcs.bottom.deleteCells();
11649
}
11650
11651
11652
11653
if(this.visible){
11654
if(this.groupList.length){
11655
this.groupList.forEach(function(group){
11656
output = output.concat(group.getHeadersAndRows());
11657
});
11658
11659
}else {
11660
if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasTopCalcs()){
11661
this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows);
11662
output.push(this.calcs.top);
11663
}
11664
11665
output = output.concat(this.rows);
11666
11667
if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){
11668
this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows);
11669
output.push(this.calcs.bottom);
11670
}
11671
}
11672
}else {
11673
if(!this.groupList.length && this.groupManager.table.options.columnCalcs != "table"){
11674
11675
if(this.groupManager.table.modExists("columnCalcs")){
11676
if(this.groupManager.table.modules.columnCalcs.hasTopCalcs()){
11677
if(this.groupManager.table.options.groupClosedShowCalcs){
11678
this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows);
11679
output.push(this.calcs.top);
11680
}
11681
}
11682
11683
if(this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){
11684
if(this.groupManager.table.options.groupClosedShowCalcs){
11685
this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows);
11686
output.push(this.calcs.bottom);
11687
}
11688
}
11689
}
11690
}
11691
11692
}
11693
11694
return output;
11695
}
11696
11697
getData(visible, transform){
11698
var output = [];
11699
11700
this._visSet();
11701
11702
if(!visible || (visible && this.visible)){
11703
this.rows.forEach((row) => {
11704
output.push(row.getData(transform || "data"));
11705
});
11706
}
11707
11708
return output;
11709
}
11710
11711
getRowCount(){
11712
var count = 0;
11713
11714
if(this.groupList.length){
11715
this.groupList.forEach((group) => {
11716
count += group.getRowCount();
11717
});
11718
}else {
11719
count = this.rows.length;
11720
}
11721
return count;
11722
}
11723
11724
11725
toggleVisibility(){
11726
if(this.visible){
11727
this.hide();
11728
}else {
11729
this.show();
11730
}
11731
}
11732
11733
hide(){
11734
this.visible = false;
11735
11736
if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){
11737
11738
this.element.classList.remove("tabulator-group-visible");
11739
11740
if(this.groupList.length){
11741
this.groupList.forEach((group) => {
11742
11743
var rows = group.getHeadersAndRows();
11744
11745
rows.forEach((row) => {
11746
row.detachElement();
11747
});
11748
});
11749
11750
}else {
11751
this.rows.forEach((row) => {
11752
var rowEl = row.getElement();
11753
rowEl.parentNode.removeChild(rowEl);
11754
});
11755
}
11756
11757
this.groupManager.updateGroupRows(true);
11758
11759
}else {
11760
this.groupManager.updateGroupRows(true);
11761
}
11762
11763
this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), false);
11764
}
11765
11766
show(){
11767
this.visible = true;
11768
11769
if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){
11770
11771
this.element.classList.add("tabulator-group-visible");
11772
11773
var prev = this.generateElement();
11774
11775
if(this.groupList.length){
11776
this.groupList.forEach((group) => {
11777
var rows = group.getHeadersAndRows();
11778
11779
rows.forEach((row) => {
11780
var rowEl = row.getElement();
11781
prev.parentNode.insertBefore(rowEl, prev.nextSibling);
11782
row.initialize();
11783
prev = rowEl;
11784
});
11785
});
11786
11787
}else {
11788
this.rows.forEach((row) => {
11789
var rowEl = row.getElement();
11790
prev.parentNode.insertBefore(rowEl, prev.nextSibling);
11791
row.initialize();
11792
prev = rowEl;
11793
});
11794
}
11795
11796
this.groupManager.updateGroupRows(true);
11797
}else {
11798
this.groupManager.updateGroupRows(true);
11799
}
11800
11801
this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), true);
11802
}
11803
11804
_visSet(){
11805
var data = [];
11806
11807
if(typeof this.visible == "function"){
11808
11809
this.rows.forEach(function(row){
11810
data.push(row.getData());
11811
});
11812
11813
this.visible = this.visible(this.key, this.getRowCount(), data, this.getComponent());
11814
}
11815
}
11816
11817
getRowGroup(row){
11818
var match = false;
11819
if(this.groupList.length){
11820
this.groupList.forEach(function(group){
11821
var result = group.getRowGroup(row);
11822
11823
if(result){
11824
match = result;
11825
}
11826
});
11827
}else {
11828
if(this.rows.find(function(item){
11829
return item === row;
11830
})){
11831
match = this;
11832
}
11833
}
11834
11835
return match;
11836
}
11837
11838
getSubGroups(component){
11839
var output = [];
11840
11841
this.groupList.forEach(function(child){
11842
output.push(component ? child.getComponent() : child);
11843
});
11844
11845
return output;
11846
}
11847
11848
getRows(component, includeChildren){
11849
var output = [];
11850
11851
if(includeChildren && this.groupList.length){
11852
this.groupList.forEach((group) => {
11853
output = output.concat(group.getRows(component, includeChildren));
11854
});
11855
}else {
11856
this.rows.forEach(function(row){
11857
output.push(component ? row.getComponent() : row);
11858
});
11859
}
11860
11861
return output;
11862
}
11863
11864
generateGroupHeaderContents(){
11865
var data = [];
11866
11867
var rows = this.getRows(false, true);
11868
11869
rows.forEach(function(row){
11870
data.push(row.getData());
11871
});
11872
11873
this.elementContents = this.generator(this.key, this.getRowCount(), data, this.getComponent());
11874
11875
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
11876
11877
if(typeof this.elementContents === "string"){
11878
this.element.innerHTML = this.elementContents;
11879
}else {
11880
this.element.appendChild(this.elementContents);
11881
}
11882
11883
this.element.insertBefore(this.arrowElement, this.element.firstChild);
11884
}
11885
11886
getPath(path = []) {
11887
path.unshift(this.key);
11888
if(this.parent) {
11889
this.parent.getPath(path);
11890
}
11891
return path;
11892
}
11893
11894
////////////// Standard Row Functions //////////////
11895
11896
getElement(){
11897
return this.elementContents ? this.element : this.generateElement();
11898
}
11899
11900
generateElement(){
11901
this.addBindings = false;
11902
11903
this._visSet();
11904
11905
if(this.visible){
11906
this.element.classList.add("tabulator-group-visible");
11907
}else {
11908
this.element.classList.remove("tabulator-group-visible");
11909
}
11910
11911
for(var i = 0; i < this.element.childNodes.length; ++i){
11912
this.element.childNodes[i].parentNode.removeChild(this.element.childNodes[i]);
11913
}
11914
11915
this.generateGroupHeaderContents();
11916
11917
// this.addBindings();
11918
11919
return this.element;
11920
}
11921
11922
detachElement(){
11923
if (this.element && this.element.parentNode){
11924
this.element.parentNode.removeChild(this.element);
11925
}
11926
}
11927
11928
//normalize the height of elements in the row
11929
normalizeHeight(){
11930
this.setHeight(this.element.clientHeight);
11931
}
11932
11933
initialize(force){
11934
if(!this.initialized || force){
11935
this.normalizeHeight();
11936
this.initialized = true;
11937
}
11938
}
11939
11940
reinitialize(){
11941
this.initialized = false;
11942
this.height = 0;
11943
11944
if(Helpers.elVisible(this.element)){
11945
this.initialize(true);
11946
}
11947
}
11948
11949
setHeight(height){
11950
if(this.height != height){
11951
this.height = height;
11952
this.outerHeight = this.element.offsetHeight;
11953
}
11954
}
11955
11956
//return rows outer height
11957
getHeight(){
11958
return this.outerHeight;
11959
}
11960
11961
getGroup(){
11962
return this;
11963
}
11964
11965
reinitializeHeight(){}
11966
11967
calcHeight(){}
11968
11969
setCellHeight(){}
11970
11971
clearCellHeight(){}
11972
11973
deinitializeHeight(){}
11974
11975
rendered(){}
11976
11977
//////////////// Object Generation /////////////////
11978
getComponent(){
11979
if(!this.component){
11980
this.component = new GroupComponent(this);
11981
}
11982
11983
return this.component;
11984
}
11985
}
11986
11987
class GroupRows extends Module{
11988
11989
constructor(table){
11990
super(table);
11991
11992
this.groupIDLookups = false; //enable table grouping and set field to group by
11993
this.startOpen = [function(){return false;}]; //starting state of group
11994
this.headerGenerator = [function(){return "";}];
11995
this.groupList = []; //ordered list of groups
11996
this.allowedValues = false;
11997
this.groups = {}; //hold row groups
11998
11999
this.displayHandler = this.getRows.bind(this);
12000
12001
this.blockRedraw = false;
12002
12003
//register table options
12004
this.registerTableOption("groupBy", false); //enable table grouping and set field to group by
12005
this.registerTableOption("groupStartOpen", true); //starting state of group
12006
this.registerTableOption("groupValues", false);
12007
this.registerTableOption("groupUpdateOnCellEdit", false);
12008
this.registerTableOption("groupHeader", false); //header generation function
12009
this.registerTableOption("groupHeaderPrint", null);
12010
this.registerTableOption("groupHeaderClipboard", null);
12011
this.registerTableOption("groupHeaderHtmlOutput", null);
12012
this.registerTableOption("groupHeaderDownload", null);
12013
this.registerTableOption("groupToggleElement", "arrow");
12014
this.registerTableOption("groupClosedShowCalcs", false);
12015
12016
//register table functions
12017
this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this));
12018
this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this));
12019
this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this));
12020
this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this));
12021
this.registerTableFunction("getGroups", this.userGetGroups.bind(this));
12022
this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this));
12023
12024
//register component functions
12025
this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this));
12026
}
12027
12028
//initialize group configuration
12029
initialize(){
12030
this.subscribe("table-destroy", this._blockRedrawing.bind(this));
12031
this.subscribe("rows-wipe", this._blockRedrawing.bind(this));
12032
this.subscribe("rows-wiped", this._restore_redrawing.bind(this));
12033
12034
if(this.table.options.groupBy){
12035
if(this.table.options.groupUpdateOnCellEdit){
12036
this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
12037
this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0);
12038
}
12039
12040
this.subscribe("table-built", this.configureGroupSetup.bind(this));
12041
12042
this.subscribe("row-deleting", this.rowDeleting.bind(this));
12043
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
12044
this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this));
12045
this.subscribe("rows-wipe", this.wipe.bind(this));
12046
this.subscribe("rows-added", this.rowsUpdated.bind(this));
12047
this.subscribe("row-moving", this.rowMoving.bind(this));
12048
this.subscribe("row-adding-index", this.rowAddingIndex.bind(this));
12049
12050
this.subscribe("rows-sample", this.rowSample.bind(this));
12051
12052
this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this));
12053
12054
this.registerDisplayHandler(this.displayHandler, 20);
12055
12056
this.initialized = true;
12057
}
12058
}
12059
12060
_blockRedrawing(){
12061
this.blockRedraw = true;
12062
}
12063
12064
_restore_redrawing(){
12065
this.blockRedraw = false;
12066
}
12067
12068
configureGroupSetup(){
12069
if(this.table.options.groupBy){
12070
var groupBy = this.table.options.groupBy,
12071
startOpen = this.table.options.groupStartOpen,
12072
groupHeader = this.table.options.groupHeader;
12073
12074
this.allowedValues = this.table.options.groupValues;
12075
12076
if(Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length){
12077
console.warn("Error creating group headers, groupHeader array is shorter than groupBy array");
12078
}
12079
12080
this.headerGenerator = [function(){return "";}];
12081
this.startOpen = [function(){return false;}]; //starting state of group
12082
12083
this.langBind("groups|item", (langValue, lang) => {
12084
this.headerGenerator[0] = (value, count, data) => { //header layout function
12085
return (typeof value === "undefined" ? "" : value) + "<span>(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")</span>";
12086
};
12087
});
12088
12089
this.groupIDLookups = [];
12090
12091
if(groupBy){
12092
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both"){
12093
this.table.modules.columnCalcs.removeCalcs();
12094
}
12095
}else {
12096
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group"){
12097
12098
var cols = this.table.columnManager.getRealColumns();
12099
12100
cols.forEach((col) => {
12101
if(col.definition.topCalc){
12102
this.table.modules.columnCalcs.initializeTopRow();
12103
}
12104
12105
if(col.definition.bottomCalc){
12106
this.table.modules.columnCalcs.initializeBottomRow();
12107
}
12108
});
12109
}
12110
}
12111
12112
if(!Array.isArray(groupBy)){
12113
groupBy = [groupBy];
12114
}
12115
12116
groupBy.forEach((group, i) => {
12117
var lookupFunc, column;
12118
12119
if(typeof group == "function"){
12120
lookupFunc = group;
12121
}else {
12122
column = this.table.columnManager.getColumnByField(group);
12123
12124
if(column){
12125
lookupFunc = function(data){
12126
return column.getFieldValue(data);
12127
};
12128
}else {
12129
lookupFunc = function(data){
12130
return data[group];
12131
};
12132
}
12133
}
12134
12135
this.groupIDLookups.push({
12136
field: typeof group === "function" ? false : group,
12137
func:lookupFunc,
12138
values:this.allowedValues ? this.allowedValues[i] : false,
12139
});
12140
});
12141
12142
if(startOpen){
12143
if(!Array.isArray(startOpen)){
12144
startOpen = [startOpen];
12145
}
12146
12147
startOpen.forEach((level) => {
12148
});
12149
12150
this.startOpen = startOpen;
12151
}
12152
12153
if(groupHeader){
12154
this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader];
12155
}
12156
}else {
12157
this.groupList = [];
12158
this.groups = {};
12159
}
12160
}
12161
12162
rowSample(rows, prevValue){
12163
if(this.table.options.groupBy){
12164
var group = this.getGroups(false)[0];
12165
12166
prevValue.push(group.getRows(false)[0]);
12167
}
12168
12169
return prevValue;
12170
}
12171
12172
virtualRenderFill(){
12173
var el = this.table.rowManager.tableElement;
12174
var rows = this.table.rowManager.getVisibleRows();
12175
12176
if(this.table.options.groupBy){
12177
rows = rows.filter((row) => {
12178
return row.type !== "group";
12179
});
12180
12181
el.style.minWidth = !rows.length ? this.table.columnManager.getWidth() + "px" : "";
12182
}else {
12183
return rows;
12184
}
12185
}
12186
12187
rowAddingIndex(row, index, top){
12188
if(this.table.options.groupBy){
12189
this.assignRowToGroup(row);
12190
12191
var groupRows = row.modules.group.rows;
12192
12193
if(groupRows.length > 1){
12194
if(!index || (index && groupRows.indexOf(index) == -1)){
12195
if(top){
12196
if(groupRows[0] !== row){
12197
index = groupRows[0];
12198
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
12199
}
12200
}else {
12201
if(groupRows[groupRows.length -1] !== row){
12202
index = groupRows[groupRows.length -1];
12203
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
12204
}
12205
}
12206
}else {
12207
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
12208
}
12209
}
12210
12211
return index;
12212
}
12213
}
12214
12215
trackChanges(){
12216
this.dispatch("group-changed");
12217
}
12218
12219
///////////////////////////////////
12220
///////// Table Functions /////////
12221
///////////////////////////////////
12222
12223
setGroupBy(groups){
12224
this.table.options.groupBy = groups;
12225
12226
if(!this.initialized){
12227
this.initialize();
12228
}
12229
12230
this.configureGroupSetup();
12231
12232
if(!groups && this.table.modExists("columnCalcs") && this.table.options.columnCalcs === true){
12233
this.table.modules.columnCalcs.reinitializeCalcs();
12234
}
12235
12236
this.refreshData();
12237
12238
this.trackChanges();
12239
}
12240
12241
setGroupValues(groupValues){
12242
this.table.options.groupValues = groupValues;
12243
this.configureGroupSetup();
12244
this.refreshData();
12245
12246
this.trackChanges();
12247
}
12248
12249
setGroupStartOpen(values){
12250
this.table.options.groupStartOpen = values;
12251
this.configureGroupSetup();
12252
12253
if(this.table.options.groupBy){
12254
this.refreshData();
12255
12256
this.trackChanges();
12257
}else {
12258
console.warn("Grouping Update - cant refresh view, no groups have been set");
12259
}
12260
}
12261
12262
setGroupHeader(values){
12263
this.table.options.groupHeader = values;
12264
this.configureGroupSetup();
12265
12266
if(this.table.options.groupBy){
12267
this.refreshData();
12268
12269
this.trackChanges();
12270
}else {
12271
console.warn("Grouping Update - cant refresh view, no groups have been set");
12272
}
12273
}
12274
12275
userGetGroups(values){
12276
return this.getGroups(true);
12277
}
12278
12279
// get grouped table data in the same format as getData()
12280
userGetGroupedData(){
12281
return this.table.options.groupBy ? this.getGroupedData() : this.getData();
12282
}
12283
12284
12285
///////////////////////////////////////
12286
///////// Component Functions /////////
12287
///////////////////////////////////////
12288
12289
rowGetGroup(row){
12290
return row.modules.group ? row.modules.group.getComponent() : false;
12291
}
12292
12293
///////////////////////////////////
12294
///////// Internal Logic //////////
12295
///////////////////////////////////
12296
12297
rowMoving(from, to, after){
12298
if(this.table.options.groupBy){
12299
if(!after && to instanceof Group){
12300
to = this.table.rowManager.prevDisplayRow(from) || to;
12301
}
12302
12303
var toGroup = to instanceof Group ? to : to.modules.group;
12304
var fromGroup = from instanceof Group ? from : from.modules.group;
12305
12306
if(toGroup === fromGroup){
12307
this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after);
12308
}else {
12309
if(fromGroup){
12310
fromGroup.removeRow(from);
12311
}
12312
12313
toGroup.insertRow(from, to, after);
12314
}
12315
}
12316
}
12317
12318
12319
rowDeleting(row){
12320
//remove from group
12321
if(this.table.options.groupBy && row.modules.group){
12322
row.modules.group.removeRow(row);
12323
}
12324
}
12325
12326
rowsUpdated(row){
12327
if(this.table.options.groupBy){
12328
this.updateGroupRows(true);
12329
}
12330
}
12331
12332
cellUpdated(cell){
12333
if(this.table.options.groupBy){
12334
this.reassignRowToGroup(cell.row);
12335
}
12336
}
12337
12338
//return appropriate rows with group headers
12339
getRows(rows){
12340
if(this.table.options.groupBy && this.groupIDLookups.length){
12341
12342
this.dispatchExternal("dataGrouping");
12343
12344
this.generateGroups(rows);
12345
12346
if(this.subscribedExternal("dataGrouped")){
12347
this.dispatchExternal("dataGrouped", this.getGroups(true));
12348
}
12349
12350
return this.updateGroupRows();
12351
12352
}else {
12353
return rows.slice(0);
12354
}
12355
}
12356
12357
getGroups(component){
12358
var groupComponents = [];
12359
12360
this.groupList.forEach(function(group){
12361
groupComponents.push(component ? group.getComponent() : group);
12362
});
12363
12364
return groupComponents;
12365
}
12366
12367
getChildGroups(group){
12368
var groupComponents = [];
12369
12370
if(!group){
12371
group = this;
12372
}
12373
12374
group.groupList.forEach((child) => {
12375
if(child.groupList.length){
12376
groupComponents = groupComponents.concat(this.getChildGroups(child));
12377
}else {
12378
groupComponents.push(child);
12379
}
12380
});
12381
12382
return groupComponents;
12383
}
12384
12385
wipe(){
12386
if(this.table.options.groupBy){
12387
this.groupList.forEach(function(group){
12388
group.wipe();
12389
});
12390
12391
this.groupList = [];
12392
this.groups = {};
12393
}
12394
}
12395
12396
pullGroupListData(groupList) {
12397
var groupListData = [];
12398
12399
groupList.forEach((group) => {
12400
var groupHeader = {};
12401
groupHeader.level = 0;
12402
groupHeader.rowCount = 0;
12403
groupHeader.headerContent = "";
12404
var childData = [];
12405
12406
if (group.hasSubGroups) {
12407
childData = this.pullGroupListData(group.groupList);
12408
12409
groupHeader.level = group.level;
12410
groupHeader.rowCount = childData.length - group.groupList.length; // data length minus number of sub-headers
12411
groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group);
12412
12413
groupListData.push(groupHeader);
12414
groupListData = groupListData.concat(childData);
12415
}
12416
12417
else {
12418
groupHeader.level = group.level;
12419
groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group);
12420
groupHeader.rowCount = group.getRows().length;
12421
12422
groupListData.push(groupHeader);
12423
12424
group.getRows().forEach((row) => {
12425
groupListData.push(row.getData("data"));
12426
});
12427
}
12428
});
12429
12430
return groupListData;
12431
}
12432
12433
getGroupedData(){
12434
12435
return this.pullGroupListData(this.groupList);
12436
}
12437
12438
getRowGroup(row){
12439
var match = false;
12440
12441
if(this.options("dataTree")){
12442
row = this.table.modules.dataTree.getTreeParentRoot(row);
12443
}
12444
12445
this.groupList.forEach((group) => {
12446
var result = group.getRowGroup(row);
12447
12448
if(result){
12449
match = result;
12450
}
12451
});
12452
12453
return match;
12454
}
12455
12456
countGroups(){
12457
return this.groupList.length;
12458
}
12459
12460
generateGroups(rows){
12461
var oldGroups = this.groups;
12462
12463
this.groups = {};
12464
this.groupList = [];
12465
12466
if(this.allowedValues && this.allowedValues[0]){
12467
this.allowedValues[0].forEach((value) => {
12468
this.createGroup(value, 0, oldGroups);
12469
});
12470
12471
rows.forEach((row) => {
12472
this.assignRowToExistingGroup(row, oldGroups);
12473
});
12474
}else {
12475
rows.forEach((row) => {
12476
this.assignRowToGroup(row, oldGroups);
12477
});
12478
}
12479
12480
Object.values(oldGroups).forEach((group) => {
12481
group.wipe(true);
12482
});
12483
}
12484
12485
12486
createGroup(groupID, level, oldGroups){
12487
var groupKey = level + "_" + groupID,
12488
group;
12489
12490
oldGroups = oldGroups || [];
12491
12492
group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]);
12493
12494
this.groups[groupKey] = group;
12495
this.groupList.push(group);
12496
}
12497
12498
assignRowToExistingGroup(row, oldGroups){
12499
var groupID = this.groupIDLookups[0].func(row.getData()),
12500
groupKey = "0_" + groupID;
12501
12502
if(this.groups[groupKey]){
12503
this.groups[groupKey].addRow(row);
12504
}
12505
}
12506
12507
assignRowToGroup(row, oldGroups){
12508
var groupID = this.groupIDLookups[0].func(row.getData()),
12509
newGroupNeeded = !this.groups["0_" + groupID];
12510
12511
if(newGroupNeeded){
12512
this.createGroup(groupID, 0, oldGroups);
12513
}
12514
12515
this.groups["0_" + groupID].addRow(row);
12516
12517
return !newGroupNeeded;
12518
}
12519
12520
reassignRowToGroup(row){
12521
if(row.type === "row"){
12522
var oldRowGroup = row.modules.group,
12523
oldGroupPath = oldRowGroup.getPath(),
12524
newGroupPath = this.getExpectedPath(row),
12525
samePath;
12526
12527
// figure out if new group path is the same as old group path
12528
samePath = (oldGroupPath.length == newGroupPath.length) && oldGroupPath.every((element, index) => {
12529
return element === newGroupPath[index];
12530
});
12531
12532
// refresh if they new path and old path aren't the same (aka the row's groupings have changed)
12533
if(!samePath) {
12534
oldRowGroup.removeRow(row);
12535
this.assignRowToGroup(row, this.groups);
12536
this.refreshData(true);
12537
}
12538
}
12539
}
12540
12541
getExpectedPath(row) {
12542
var groupPath = [], rowData = row.getData();
12543
12544
this.groupIDLookups.forEach((groupId) => {
12545
groupPath.push(groupId.func(rowData));
12546
});
12547
12548
return groupPath;
12549
}
12550
12551
updateGroupRows(force){
12552
var output = [];
12553
12554
if(!this.blockRedraw){
12555
this.groupList.forEach((group) => {
12556
output = output.concat(group.getHeadersAndRows());
12557
});
12558
12559
if(force){
12560
this.refreshData(true);
12561
}
12562
}
12563
12564
return output;
12565
}
12566
12567
scrollHeaders(left){
12568
if(this.table.options.groupBy){
12569
if(this.table.options.renderHorizontal === "virtual"){
12570
left -= this.table.columnManager.renderer.vDomPadLeft;
12571
}
12572
12573
left = left + "px";
12574
12575
this.groupList.forEach((group) => {
12576
group.scrollHeader(left);
12577
});
12578
}
12579
}
12580
12581
removeGroup(group){
12582
var groupKey = group.level + "_" + group.key,
12583
index;
12584
12585
if(this.groups[groupKey]){
12586
delete this.groups[groupKey];
12587
12588
index = this.groupList.indexOf(group);
12589
12590
if(index > -1){
12591
this.groupList.splice(index, 1);
12592
}
12593
}
12594
}
12595
12596
checkBasicModeGroupHeaderWidth(){
12597
var element = this.table.rowManager.tableElement,
12598
onlyGroupHeaders = true;
12599
12600
this.table.rowManager.getDisplayRows().forEach((row, index) =>{
12601
this.table.rowManager.styleRow(row, index);
12602
element.appendChild(row.getElement());
12603
row.initialize(true);
12604
12605
if(row.type !== "group"){
12606
onlyGroupHeaders = false;
12607
}
12608
});
12609
12610
if(onlyGroupHeaders){
12611
element.style.minWidth = this.table.columnManager.getWidth() + "px";
12612
}else {
12613
element.style.minWidth = "";
12614
}
12615
}
12616
12617
}
12618
12619
GroupRows.moduleName = "groupRows";
12620
12621
var defaultUndoers = {
12622
cellEdit: function(action){
12623
action.component.setValueProcessData(action.data.oldValue);
12624
action.component.cellRendered();
12625
},
12626
12627
rowAdd: function(action){
12628
action.component.deleteActual();
12629
},
12630
12631
rowDelete: function(action){
12632
var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
12633
12634
if(this.table.options.groupBy && this.table.modExists("groupRows")){
12635
this.table.modules.groupRows.updateGroupRows(true);
12636
}
12637
12638
this._rebindRow(action.component, newRow);
12639
},
12640
12641
rowMove: function(action){
12642
var after = (action.data.posFrom - action.data.posTo) > 0;
12643
12644
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posFrom), after);
12645
12646
this.table.rowManager.regenerateRowPositions();
12647
this.table.rowManager.reRenderInPosition();
12648
},
12649
};
12650
12651
var defaultRedoers = {
12652
cellEdit: function(action){
12653
action.component.setValueProcessData(action.data.newValue);
12654
action.component.cellRendered();
12655
},
12656
12657
rowAdd: function(action){
12658
var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
12659
12660
if(this.table.options.groupBy && this.table.modExists("groupRows")){
12661
this.table.modules.groupRows.updateGroupRows(true);
12662
}
12663
12664
this._rebindRow(action.component, newRow);
12665
},
12666
12667
rowDelete:function(action){
12668
action.component.deleteActual();
12669
},
12670
12671
rowMove: function(action){
12672
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posTo), action.data.after);
12673
12674
this.table.rowManager.regenerateRowPositions();
12675
this.table.rowManager.reRenderInPosition();
12676
},
12677
};
12678
12679
class History extends Module{
12680
12681
constructor(table){
12682
super(table);
12683
12684
this.history = [];
12685
this.index = -1;
12686
12687
this.registerTableOption("history", false); //enable edit history
12688
}
12689
12690
initialize(){
12691
if(this.table.options.history){
12692
this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
12693
this.subscribe("cell-delete", this.clearComponentHistory.bind(this));
12694
this.subscribe("row-delete", this.rowDeleted.bind(this));
12695
this.subscribe("rows-wipe", this.clear.bind(this));
12696
this.subscribe("row-added", this.rowAdded.bind(this));
12697
this.subscribe("row-move", this.rowMoved.bind(this));
12698
}
12699
12700
this.registerTableFunction("undo", this.undo.bind(this));
12701
this.registerTableFunction("redo", this.redo.bind(this));
12702
this.registerTableFunction("getHistoryUndoSize", this.getHistoryUndoSize.bind(this));
12703
this.registerTableFunction("getHistoryRedoSize", this.getHistoryRedoSize.bind(this));
12704
this.registerTableFunction("clearHistory", this.clear.bind(this));
12705
}
12706
12707
rowMoved(from, to, after){
12708
this.action("rowMove", from, {posFrom:from.getPosition(), posTo:to.getPosition(), to:to, after:after});
12709
}
12710
12711
rowAdded(row, data, pos, index){
12712
this.action("rowAdd", row, {data:data, pos:pos, index:index});
12713
}
12714
12715
rowDeleted(row){
12716
var index, rows;
12717
12718
if(this.table.options.groupBy){
12719
12720
rows = row.getComponent().getGroup()._getSelf().rows;
12721
index = rows.indexOf(row);
12722
12723
if(index){
12724
index = rows[index-1];
12725
}
12726
}else {
12727
index = row.table.rowManager.getRowIndex(row);
12728
12729
if(index){
12730
index = row.table.rowManager.rows[index-1];
12731
}
12732
}
12733
12734
this.action("rowDelete", row, {data:row.getData(), pos:!index, index:index});
12735
}
12736
12737
cellUpdated(cell){
12738
this.action("cellEdit", cell, {oldValue:cell.oldValue, newValue:cell.value});
12739
}
12740
12741
clear(){
12742
this.history = [];
12743
this.index = -1;
12744
}
12745
12746
action(type, component, data){
12747
this.history = this.history.slice(0, this.index + 1);
12748
12749
this.history.push({
12750
type:type,
12751
component:component,
12752
data:data,
12753
});
12754
12755
this.index ++;
12756
}
12757
12758
getHistoryUndoSize(){
12759
return this.index + 1;
12760
}
12761
12762
getHistoryRedoSize(){
12763
return this.history.length - (this.index + 1);
12764
}
12765
12766
clearComponentHistory(component){
12767
var index = this.history.findIndex(function(item){
12768
return item.component === component;
12769
});
12770
12771
if(index > -1){
12772
this.history.splice(index, 1);
12773
if(index <= this.index){
12774
this.index--;
12775
}
12776
12777
this.clearComponentHistory(component);
12778
}
12779
}
12780
12781
undo(){
12782
if(this.index > -1){
12783
let action = this.history[this.index];
12784
12785
History.undoers[action.type].call(this, action);
12786
12787
this.index--;
12788
12789
this.dispatchExternal("historyUndo", action.type, action.component.getComponent(), action.data);
12790
12791
return true;
12792
}else {
12793
console.warn("History Undo Error - No more history to undo");
12794
return false;
12795
}
12796
}
12797
12798
redo(){
12799
if(this.history.length-1 > this.index){
12800
12801
this.index++;
12802
12803
let action = this.history[this.index];
12804
12805
History.redoers[action.type].call(this, action);
12806
12807
this.dispatchExternal("historyRedo", action.type, action.component.getComponent(), action.data);
12808
12809
return true;
12810
}else {
12811
console.warn("History Redo Error - No more history to redo");
12812
return false;
12813
}
12814
}
12815
12816
//rebind rows to new element after deletion
12817
_rebindRow(oldRow, newRow){
12818
this.history.forEach(function(action){
12819
if(action.component instanceof Row){
12820
if(action.component === oldRow){
12821
action.component = newRow;
12822
}
12823
}else if(action.component instanceof Cell){
12824
if(action.component.row === oldRow){
12825
var field = action.component.column.getField();
12826
12827
if(field){
12828
action.component = newRow.getCell(field);
12829
}
12830
12831
}
12832
}
12833
});
12834
}
12835
}
12836
12837
History.moduleName = "history";
12838
12839
//load defaults
12840
History.undoers = defaultUndoers;
12841
History.redoers = defaultRedoers;
12842
12843
class HtmlTableImport extends Module{
12844
12845
constructor(table){
12846
super(table);
12847
12848
this.fieldIndex = [];
12849
this.hasIndex = false;
12850
}
12851
12852
initialize(){
12853
this.tableElementCheck();
12854
}
12855
12856
tableElementCheck(){
12857
if(this.table.originalElement && this.table.originalElement.tagName === "TABLE"){
12858
if(this.table.originalElement.childNodes.length){
12859
this.parseTable();
12860
}else {
12861
console.warn("Unable to parse data from empty table tag, Tabulator should be initialized on a div tag unless importing data from a table element.");
12862
}
12863
}
12864
}
12865
12866
parseTable(){
12867
var element = this.table.originalElement,
12868
options = this.table.options,
12869
headers = element.getElementsByTagName("th"),
12870
rows = element.getElementsByTagName("tbody")[0],
12871
data = [];
12872
12873
this.hasIndex = false;
12874
12875
this.dispatchExternal("htmlImporting");
12876
12877
rows = rows ? rows.getElementsByTagName("tr") : [];
12878
12879
//check for Tabulator inline options
12880
this._extractOptions(element, options);
12881
12882
if(headers.length){
12883
this._extractHeaders(headers, rows);
12884
}else {
12885
this._generateBlankHeaders(headers, rows);
12886
}
12887
12888
//iterate through table rows and build data set
12889
for(var index = 0; index < rows.length; index++){
12890
var row = rows[index],
12891
cells = row.getElementsByTagName("td"),
12892
item = {};
12893
12894
//create index if the don't exist in table
12895
if(!this.hasIndex){
12896
item[options.index] = index;
12897
}
12898
12899
for(var i = 0; i < cells.length; i++){
12900
var cell = cells[i];
12901
if(typeof this.fieldIndex[i] !== "undefined"){
12902
item[this.fieldIndex[i]] = cell.innerHTML;
12903
}
12904
}
12905
12906
//add row data to item
12907
data.push(item);
12908
}
12909
12910
options.data = data;
12911
12912
this.dispatchExternal("htmlImported");
12913
}
12914
12915
//extract tabulator attribute options
12916
_extractOptions(element, options, defaultOptions){
12917
var attributes = element.attributes;
12918
var optionsArr = defaultOptions ? Object.keys(defaultOptions) : Object.keys(options);
12919
var optionsList = {};
12920
12921
optionsArr.forEach((item) => {
12922
optionsList[item.toLowerCase()] = item;
12923
});
12924
12925
for(var index in attributes){
12926
var attrib = attributes[index];
12927
var name;
12928
12929
if(attrib && typeof attrib == "object" && attrib.name && attrib.name.indexOf("tabulator-") === 0){
12930
name = attrib.name.replace("tabulator-", "");
12931
12932
if(typeof optionsList[name] !== "undefined"){
12933
options[optionsList[name]] = this._attribValue(attrib.value);
12934
}
12935
}
12936
}
12937
}
12938
12939
//get value of attribute
12940
_attribValue(value){
12941
if(value === "true"){
12942
return true;
12943
}
12944
12945
if(value === "false"){
12946
return false;
12947
}
12948
12949
return value;
12950
}
12951
12952
//find column if it has already been defined
12953
_findCol(title){
12954
var match = this.table.options.columns.find((column) => {
12955
return column.title === title;
12956
});
12957
12958
return match || false;
12959
}
12960
12961
//extract column from headers
12962
_extractHeaders(headers, rows){
12963
for(var index = 0; index < headers.length; index++){
12964
var header = headers[index],
12965
exists = false,
12966
col = this._findCol(header.textContent),
12967
width;
12968
12969
if(col){
12970
exists = true;
12971
}else {
12972
col = {title:header.textContent.trim()};
12973
}
12974
12975
if(!col.field) {
12976
col.field = header.textContent.trim().toLowerCase().replaceAll(" ", "_");
12977
}
12978
12979
width = header.getAttribute("width");
12980
12981
if(width && !col.width) {
12982
col.width = width;
12983
}
12984
12985
//check for Tabulator inline options
12986
this._extractOptions(header, col, this.table.columnManager.optionsList.registeredDefaults);
12987
12988
this.fieldIndex[index] = col.field;
12989
12990
if(col.field == this.table.options.index){
12991
this.hasIndex = true;
12992
}
12993
12994
if(!exists){
12995
this.table.options.columns.push(col);
12996
}
12997
12998
}
12999
}
13000
13001
//generate blank headers
13002
_generateBlankHeaders(headers, rows){
13003
for(var index = 0; index < headers.length; index++){
13004
var header = headers[index],
13005
col = {title:"", field:"col" + index};
13006
13007
this.fieldIndex[index] = col.field;
13008
13009
var width = header.getAttribute("width");
13010
13011
if(width){
13012
col.width = width;
13013
}
13014
13015
this.table.options.columns.push(col);
13016
}
13017
}
13018
}
13019
13020
HtmlTableImport.moduleName = "htmlTableImport";
13021
13022
function csvImporter(input){
13023
var data = [],
13024
row = 0,
13025
col = 0,
13026
inQuote = false;
13027
13028
//Iterate over each character
13029
for (let index = 0; index < input.length; index++) {
13030
let char = input[index],
13031
nextChar = input[index+1];
13032
13033
//Initialize empty row
13034
if(!data[row]){
13035
data[row] = [];
13036
}
13037
13038
//Initialize empty column
13039
if(!data[row][col]){
13040
data[row][col] = "";
13041
}
13042
13043
//Handle quotation mark inside string
13044
if (char == '"' && inQuote && nextChar == '"') {
13045
data[row][col] += char;
13046
index++;
13047
continue;
13048
}
13049
13050
//Begin / End Quote
13051
if (char == '"') {
13052
inQuote = !inQuote;
13053
continue;
13054
}
13055
13056
//Next column (if not in quote)
13057
if (char == ',' && !inQuote) {
13058
col++;
13059
continue;
13060
}
13061
13062
//New row if new line and not in quote (CRLF)
13063
if (char == '\r' && nextChar == '\n' && !inQuote) {
13064
col = 0;
13065
row++;
13066
index++;
13067
continue;
13068
}
13069
13070
//New row if new line and not in quote (CR or LF)
13071
if ((char == '\r' || char == '\n') && !inQuote) {
13072
col = 0;
13073
row++;
13074
continue;
13075
}
13076
13077
//Normal Character, append to column
13078
data[row][col] += char;
13079
}
13080
13081
return data;
13082
}
13083
13084
function json$1(input){
13085
try {
13086
return JSON.parse(input);
13087
} catch(e) {
13088
console.warn("JSON Import Error - File contents is invalid JSON", e);
13089
return Promise.reject();
13090
}
13091
}
13092
13093
function arrayImporter(input){
13094
return input;
13095
}
13096
13097
var defaultImporters = {
13098
csv:csvImporter,
13099
json:json$1,
13100
array:arrayImporter,
13101
};
13102
13103
class Import extends Module{
13104
13105
constructor(table){
13106
super(table);
13107
13108
this.registerTableOption("importFormat");
13109
this.registerTableOption("importReader", "text");
13110
}
13111
13112
initialize(){
13113
this.registerTableFunction("import", this.importFromFile.bind(this));
13114
13115
if(this.table.options.importFormat){
13116
this.subscribe("data-loading", this.loadDataCheck.bind(this), 10);
13117
this.subscribe("data-load", this.loadData.bind(this), 10);
13118
}
13119
}
13120
13121
loadDataCheck(data){
13122
return this.table.options.importFormat && (typeof data === "string" || (Array.isArray(data) && data.length && Array.isArray(data)));
13123
}
13124
13125
loadData(data, params, config, silent, previousData){
13126
return this.importData(this.lookupImporter(), data)
13127
.then(this.structureData.bind(this))
13128
.catch((err) => {
13129
console.error("Import Error:", err || "Unable to import data");
13130
return Promise.reject(err);
13131
});
13132
}
13133
13134
lookupImporter(importFormat){
13135
var importer;
13136
13137
if(!importFormat){
13138
importFormat = this.table.options.importFormat;
13139
}
13140
13141
if(typeof importFormat === "string"){
13142
importer = Import.importers[importFormat];
13143
}else {
13144
importer = importFormat;
13145
}
13146
13147
if(!importer){
13148
console.error("Import Error - Importer not found:", importFormat);
13149
}
13150
13151
return importer;
13152
}
13153
13154
importFromFile(importFormat, extension){
13155
var importer = this.lookupImporter(importFormat);
13156
13157
if(importer){
13158
return this.pickFile(extension)
13159
.then(this.importData.bind(this, importer))
13160
.then(this.structureData.bind(this))
13161
.then(this.setData.bind(this))
13162
.catch((err) => {
13163
console.error("Import Error:", err || "Unable to import file");
13164
return Promise.reject(err);
13165
});
13166
}
13167
}
13168
13169
pickFile(extensions){
13170
return new Promise((resolve, reject) => {
13171
var input = document.createElement("input");
13172
input.type = "file";
13173
input.accept = extensions;
13174
13175
input.addEventListener("change", (e) => {
13176
var file = input.files[0],
13177
reader = new FileReader();
13178
13179
switch(this.table.options.importReader){
13180
case "buffer":
13181
reader.readAsArrayBuffer(file);
13182
break;
13183
13184
case "binary":
13185
reader.readAsBinaryString(file);
13186
break;
13187
13188
case "url":
13189
reader.readAsDataURL(file);
13190
break;
13191
13192
case "text":
13193
default:
13194
reader.readAsText(file);
13195
}
13196
13197
reader.onload = (e) => {
13198
resolve(reader.result);
13199
};
13200
13201
reader.onerror = (e) => {
13202
console.warn("File Load Error - Unable to read file");
13203
reject();
13204
};
13205
});
13206
13207
input.click();
13208
});
13209
}
13210
13211
importData(importer, fileContents){
13212
var data = importer.call(this.table, fileContents);
13213
13214
if(data instanceof Promise){
13215
return data;
13216
}else {
13217
return data ? Promise.resolve(data) : Promise.reject();
13218
}
13219
}
13220
13221
structureData(parsedData){
13222
var data = [];
13223
13224
if(Array.isArray(parsedData) && parsedData.length && Array.isArray(parsedData[0])){
13225
if(this.table.options.autoColumns){
13226
data = this.structureArrayToObject(parsedData);
13227
}else {
13228
data = this.structureArrayToColumns(parsedData);
13229
}
13230
13231
return data;
13232
}else {
13233
return parsedData;
13234
}
13235
}
13236
13237
structureArrayToObject(parsedData){
13238
var columns = parsedData.shift();
13239
13240
var data = parsedData.map((values) => {
13241
var row = {};
13242
13243
columns.forEach((key, i) => {
13244
row[key] = values[i];
13245
});
13246
13247
return row;
13248
});
13249
13250
return data;
13251
}
13252
13253
structureArrayToColumns(parsedData){
13254
var data = [],
13255
columns = this.table.getColumns();
13256
13257
//remove first row if it is the column names
13258
if(columns[0] && parsedData[0][0]){
13259
if(columns[0].getDefinition().title === parsedData[0][0]){
13260
parsedData.shift();
13261
}
13262
}
13263
13264
//convert row arrays to objects
13265
parsedData.forEach((rowData) => {
13266
var row = {};
13267
13268
rowData.forEach((value, index) => {
13269
var column = columns[index];
13270
13271
if(column){
13272
row[column.getField()] = value;
13273
}
13274
});
13275
13276
data.push(row);
13277
});
13278
13279
return data;
13280
}
13281
13282
setData(data){
13283
return this.table.setData(data);
13284
}
13285
}
13286
13287
Import.moduleName = "import";
13288
13289
//load defaults
13290
Import.importers = defaultImporters;
13291
13292
class Interaction extends Module{
13293
13294
constructor(table){
13295
super(table);
13296
13297
this.eventMap = {
13298
//row events
13299
rowClick:"row-click",
13300
rowDblClick:"row-dblclick",
13301
rowContext:"row-contextmenu",
13302
rowMouseEnter:"row-mouseenter",
13303
rowMouseLeave:"row-mouseleave",
13304
rowMouseOver:"row-mouseover",
13305
rowMouseOut:"row-mouseout",
13306
rowMouseMove:"row-mousemove",
13307
rowMouseDown:"row-mousedown",
13308
rowMouseUp:"row-mouseup",
13309
rowTap:"row",
13310
rowDblTap:"row",
13311
rowTapHold:"row",
13312
13313
//cell events
13314
cellClick:"cell-click",
13315
cellDblClick:"cell-dblclick",
13316
cellContext:"cell-contextmenu",
13317
cellMouseEnter:"cell-mouseenter",
13318
cellMouseLeave:"cell-mouseleave",
13319
cellMouseOver:"cell-mouseover",
13320
cellMouseOut:"cell-mouseout",
13321
cellMouseMove:"cell-mousemove",
13322
cellMouseDown:"cell-mousedown",
13323
cellMouseUp:"cell-mouseup",
13324
cellTap:"cell",
13325
cellDblTap:"cell",
13326
cellTapHold:"cell",
13327
13328
//column header events
13329
headerClick:"column-click",
13330
headerDblClick:"column-dblclick",
13331
headerContext:"column-contextmenu",
13332
headerMouseEnter:"column-mouseenter",
13333
headerMouseLeave:"column-mouseleave",
13334
headerMouseOver:"column-mouseover",
13335
headerMouseOut:"column-mouseout",
13336
headerMouseMove:"column-mousemove",
13337
headerMouseDown:"column-mousedown",
13338
headerMouseUp:"column-mouseup",
13339
headerTap:"column",
13340
headerDblTap:"column",
13341
headerTapHold:"column",
13342
13343
//group header
13344
groupClick:"group-click",
13345
groupDblClick:"group-dblclick",
13346
groupContext:"group-contextmenu",
13347
groupMouseEnter:"group-mouseenter",
13348
groupMouseLeave:"group-mouseleave",
13349
groupMouseOver:"group-mouseover",
13350
groupMouseOut:"group-mouseout",
13351
groupMouseMove:"group-mousemove",
13352
groupMouseDown:"group-mousedown",
13353
groupMouseUp:"group-mouseup",
13354
groupTap:"group",
13355
groupDblTap:"group",
13356
groupTapHold:"group",
13357
};
13358
13359
this.subscribers = {};
13360
13361
this.touchSubscribers = {};
13362
13363
this.columnSubscribers = {};
13364
13365
this.touchWatchers = {
13366
row:{
13367
tap:null,
13368
tapDbl:null,
13369
tapHold:null,
13370
},
13371
cell:{
13372
tap:null,
13373
tapDbl:null,
13374
tapHold:null,
13375
},
13376
column:{
13377
tap:null,
13378
tapDbl:null,
13379
tapHold:null,
13380
},
13381
group:{
13382
tap:null,
13383
tapDbl:null,
13384
tapHold:null,
13385
}
13386
};
13387
13388
this.registerColumnOption("headerClick");
13389
this.registerColumnOption("headerDblClick");
13390
this.registerColumnOption("headerContext");
13391
this.registerColumnOption("headerMouseEnter");
13392
this.registerColumnOption("headerMouseLeave");
13393
this.registerColumnOption("headerMouseOver");
13394
this.registerColumnOption("headerMouseOut");
13395
this.registerColumnOption("headerMouseMove");
13396
this.registerColumnOption("headerMouseDown");
13397
this.registerColumnOption("headerMouseUp");
13398
this.registerColumnOption("headerTap");
13399
this.registerColumnOption("headerDblTap");
13400
this.registerColumnOption("headerTapHold");
13401
13402
this.registerColumnOption("cellClick");
13403
this.registerColumnOption("cellDblClick");
13404
this.registerColumnOption("cellContext");
13405
this.registerColumnOption("cellMouseEnter");
13406
this.registerColumnOption("cellMouseLeave");
13407
this.registerColumnOption("cellMouseOver");
13408
this.registerColumnOption("cellMouseOut");
13409
this.registerColumnOption("cellMouseMove");
13410
this.registerColumnOption("cellMouseDown");
13411
this.registerColumnOption("cellMouseUp");
13412
this.registerColumnOption("cellTap");
13413
this.registerColumnOption("cellDblTap");
13414
this.registerColumnOption("cellTapHold");
13415
13416
}
13417
13418
initialize(){
13419
this.initializeExternalEvents();
13420
13421
this.subscribe("column-init", this.initializeColumn.bind(this));
13422
this.subscribe("cell-dblclick", this.cellContentsSelectionFixer.bind(this));
13423
this.subscribe("scroll-horizontal", this.clearTouchWatchers.bind(this));
13424
this.subscribe("scroll-vertical", this.clearTouchWatchers.bind(this));
13425
}
13426
13427
clearTouchWatchers(){
13428
var types = Object.values(this.touchWatchers);
13429
13430
types.forEach((type) => {
13431
for(let key in type){
13432
type[key] = null;
13433
}
13434
});
13435
}
13436
13437
cellContentsSelectionFixer(e, cell){
13438
var range;
13439
13440
if(this.table.modExists("edit")){
13441
if (this.table.modules.edit.currentCell === cell){
13442
return; //prevent instant selection of editor content
13443
}
13444
}
13445
13446
e.preventDefault();
13447
13448
try{
13449
if (document.selection) { // IE
13450
range = document.body.createTextRange();
13451
range.moveToElementText(cell.getElement());
13452
range.select();
13453
} else if (window.getSelection) {
13454
range = document.createRange();
13455
range.selectNode(cell.getElement());
13456
window.getSelection().removeAllRanges();
13457
window.getSelection().addRange(range);
13458
}
13459
}catch(e){}
13460
}
13461
13462
initializeExternalEvents(){
13463
for(let key in this.eventMap){
13464
this.subscriptionChangeExternal(key, this.subscriptionChanged.bind(this, key));
13465
}
13466
}
13467
13468
subscriptionChanged(key, added){
13469
if(added){
13470
if(!this.subscribers[key]){
13471
if(this.eventMap[key].includes("-")){
13472
this.subscribers[key] = this.handle.bind(this, key);
13473
this.subscribe(this.eventMap[key], this.subscribers[key]);
13474
}else {
13475
this.subscribeTouchEvents(key);
13476
}
13477
}
13478
}else {
13479
if(this.eventMap[key].includes("-")){
13480
if(this.subscribers[key] && !this.columnSubscribers[key] && !this.subscribedExternal(key)){
13481
this.unsubscribe(this.eventMap[key], this.subscribers[key]);
13482
delete this.subscribers[key];
13483
}
13484
}else {
13485
this.unsubscribeTouchEvents(key);
13486
}
13487
}
13488
}
13489
13490
13491
subscribeTouchEvents(key){
13492
var type = this.eventMap[key];
13493
13494
if(!this.touchSubscribers[type + "-touchstart"]){
13495
this.touchSubscribers[type + "-touchstart"] = this.handleTouch.bind(this, type, "start");
13496
this.touchSubscribers[type + "-touchend"] = this.handleTouch.bind(this, type, "end");
13497
13498
this.subscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]);
13499
this.subscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]);
13500
}
13501
13502
this.subscribers[key] = true;
13503
}
13504
13505
unsubscribeTouchEvents(key){
13506
var noTouch = true,
13507
type = this.eventMap[key];
13508
13509
if(this.subscribers[key] && !this.subscribedExternal(key)){
13510
delete this.subscribers[key];
13511
13512
for(let i in this.eventMap){
13513
if(this.eventMap[i] === type){
13514
if(this.subscribers[i]){
13515
noTouch = false;
13516
}
13517
}
13518
}
13519
13520
if(noTouch){
13521
this.unsubscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]);
13522
this.unsubscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]);
13523
13524
delete this.touchSubscribers[type + "-touchstart"];
13525
delete this.touchSubscribers[type + "-touchend"];
13526
}
13527
}
13528
}
13529
13530
initializeColumn(column){
13531
var def = column.definition;
13532
13533
for(let key in this.eventMap){
13534
if(def[key]){
13535
this.subscriptionChanged(key, true);
13536
13537
if(!this.columnSubscribers[key]){
13538
this.columnSubscribers[key] = [];
13539
}
13540
13541
this.columnSubscribers[key].push(column);
13542
}
13543
}
13544
}
13545
13546
handle(action, e, component){
13547
this.dispatchEvent(action, e, component);
13548
}
13549
13550
handleTouch(type, action, e, component){
13551
var watchers = this.touchWatchers[type];
13552
13553
if(type === "column"){
13554
type = "header";
13555
}
13556
13557
switch(action){
13558
case "start":
13559
watchers.tap = true;
13560
13561
clearTimeout(watchers.tapHold);
13562
13563
watchers.tapHold = setTimeout(() => {
13564
clearTimeout(watchers.tapHold);
13565
watchers.tapHold = null;
13566
13567
watchers.tap = null;
13568
clearTimeout(watchers.tapDbl);
13569
watchers.tapDbl = null;
13570
13571
this.dispatchEvent(type + "TapHold", e, component);
13572
}, 1000);
13573
break;
13574
13575
case "end":
13576
if(watchers.tap){
13577
13578
watchers.tap = null;
13579
this.dispatchEvent(type + "Tap", e, component);
13580
}
13581
13582
if(watchers.tapDbl){
13583
clearTimeout(watchers.tapDbl);
13584
watchers.tapDbl = null;
13585
13586
this.dispatchEvent(type + "DblTap", e, component);
13587
}else {
13588
watchers.tapDbl = setTimeout(() => {
13589
clearTimeout(watchers.tapDbl);
13590
watchers.tapDbl = null;
13591
}, 300);
13592
}
13593
13594
clearTimeout(watchers.tapHold);
13595
watchers.tapHold = null;
13596
break;
13597
}
13598
}
13599
13600
dispatchEvent(action, e, component){
13601
var componentObj = component.getComponent(),
13602
callback;
13603
13604
if(this.columnSubscribers[action]){
13605
13606
if(component instanceof Cell){
13607
callback = component.column.definition[action];
13608
}else if(component instanceof Column){
13609
callback = component.definition[action];
13610
}
13611
13612
if(callback){
13613
callback(e, componentObj);
13614
}
13615
}
13616
13617
this.dispatchExternal(action, e, componentObj);
13618
}
13619
}
13620
13621
Interaction.moduleName = "interaction";
13622
13623
var defaultBindings = {
13624
navPrev:"shift + 9",
13625
navNext:9,
13626
navUp:38,
13627
navDown:40,
13628
scrollPageUp:33,
13629
scrollPageDown:34,
13630
scrollToStart:36,
13631
scrollToEnd:35,
13632
undo:["ctrl + 90", "meta + 90"],
13633
redo:["ctrl + 89", "meta + 89"],
13634
copyToClipboard:["ctrl + 67", "meta + 67"],
13635
};
13636
13637
var defaultActions = {
13638
keyBlock:function(e){
13639
e.stopPropagation();
13640
e.preventDefault();
13641
},
13642
scrollPageUp:function(e){
13643
var rowManager = this.table.rowManager,
13644
newPos = rowManager.scrollTop - rowManager.element.clientHeight;
13645
13646
e.preventDefault();
13647
13648
if(rowManager.displayRowsCount){
13649
if(newPos >= 0){
13650
rowManager.element.scrollTop = newPos;
13651
}else {
13652
rowManager.scrollToRow(rowManager.getDisplayRows()[0]);
13653
}
13654
}
13655
13656
this.table.element.focus();
13657
},
13658
scrollPageDown:function(e){
13659
var rowManager = this.table.rowManager,
13660
newPos = rowManager.scrollTop + rowManager.element.clientHeight,
13661
scrollMax = rowManager.element.scrollHeight;
13662
13663
e.preventDefault();
13664
13665
if(rowManager.displayRowsCount){
13666
if(newPos <= scrollMax){
13667
rowManager.element.scrollTop = newPos;
13668
}else {
13669
rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]);
13670
}
13671
}
13672
13673
this.table.element.focus();
13674
13675
},
13676
scrollToStart:function(e){
13677
var rowManager = this.table.rowManager;
13678
13679
e.preventDefault();
13680
13681
if(rowManager.displayRowsCount){
13682
rowManager.scrollToRow(rowManager.getDisplayRows()[0]);
13683
}
13684
13685
this.table.element.focus();
13686
},
13687
scrollToEnd:function(e){
13688
var rowManager = this.table.rowManager;
13689
13690
e.preventDefault();
13691
13692
if(rowManager.displayRowsCount){
13693
rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]);
13694
}
13695
13696
this.table.element.focus();
13697
},
13698
navPrev:function(e){
13699
this.dispatch("keybinding-nav-prev", e);
13700
},
13701
13702
navNext:function(e){
13703
this.dispatch("keybinding-nav-next", e);
13704
},
13705
13706
navLeft:function(e){
13707
this.dispatch("keybinding-nav-left", e);
13708
},
13709
13710
navRight:function(e){
13711
this.dispatch("keybinding-nav-right", e);
13712
},
13713
13714
navUp:function(e){
13715
this.dispatch("keybinding-nav-up", e);
13716
},
13717
13718
navDown:function(e){
13719
this.dispatch("keybinding-nav-down", e);
13720
},
13721
13722
undo:function(e){
13723
var cell = false;
13724
if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){
13725
13726
cell = this.table.modules.edit.currentCell;
13727
13728
if(!cell){
13729
e.preventDefault();
13730
this.table.modules.history.undo();
13731
}
13732
}
13733
},
13734
13735
redo:function(e){
13736
var cell = false;
13737
if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){
13738
13739
cell = this.table.modules.edit.currentCell;
13740
13741
if(!cell){
13742
e.preventDefault();
13743
this.table.modules.history.redo();
13744
}
13745
}
13746
},
13747
13748
copyToClipboard:function(e){
13749
if(!this.table.modules.edit.currentCell){
13750
if(this.table.modExists("clipboard", true)){
13751
this.table.modules.clipboard.copy(false, true);
13752
}
13753
}
13754
},
13755
};
13756
13757
class Keybindings extends Module{
13758
13759
constructor(table){
13760
super(table);
13761
13762
this.watchKeys = null;
13763
this.pressedKeys = null;
13764
this.keyupBinding = false;
13765
this.keydownBinding = false;
13766
13767
this.registerTableOption("keybindings", {}); //array for keybindings
13768
this.registerTableOption("tabEndNewRow", false); //create new row when tab to end of table
13769
}
13770
13771
initialize(){
13772
var bindings = this.table.options.keybindings,
13773
mergedBindings = {};
13774
13775
this.watchKeys = {};
13776
this.pressedKeys = [];
13777
13778
if(bindings !== false){
13779
Object.assign(mergedBindings, Keybindings.bindings);
13780
Object.assign(mergedBindings, bindings);
13781
13782
this.mapBindings(mergedBindings);
13783
this.bindEvents();
13784
}
13785
13786
this.subscribe("table-destroy", this.clearBindings.bind(this));
13787
}
13788
13789
mapBindings(bindings){
13790
for(let key in bindings){
13791
if(Keybindings.actions[key]){
13792
if(bindings[key]){
13793
if(typeof bindings[key] !== "object"){
13794
bindings[key] = [bindings[key]];
13795
}
13796
13797
bindings[key].forEach((binding) => {
13798
var bindingList = Array.isArray(binding) ? binding : [binding];
13799
13800
bindingList.forEach((item) => {
13801
this.mapBinding(key, item);
13802
});
13803
});
13804
}
13805
}else {
13806
console.warn("Key Binding Error - no such action:", key);
13807
}
13808
}
13809
}
13810
13811
mapBinding(action, symbolsList){
13812
var binding = {
13813
action: Keybindings.actions[action],
13814
keys: [],
13815
ctrl: false,
13816
shift: false,
13817
meta: false,
13818
};
13819
13820
var symbols = symbolsList.toString().toLowerCase().split(" ").join("").split("+");
13821
13822
symbols.forEach((symbol) => {
13823
switch(symbol){
13824
case "ctrl":
13825
binding.ctrl = true;
13826
break;
13827
13828
case "shift":
13829
binding.shift = true;
13830
break;
13831
13832
case "meta":
13833
binding.meta = true;
13834
break;
13835
13836
default:
13837
symbol = isNaN(symbol) ? symbol.toUpperCase().charCodeAt(0) : parseInt(symbol);
13838
binding.keys.push(symbol);
13839
13840
if(!this.watchKeys[symbol]){
13841
this.watchKeys[symbol] = [];
13842
}
13843
13844
this.watchKeys[symbol].push(binding);
13845
}
13846
});
13847
}
13848
13849
bindEvents(){
13850
var self = this;
13851
13852
this.keyupBinding = function(e){
13853
var code = e.keyCode;
13854
var bindings = self.watchKeys[code];
13855
13856
if(bindings){
13857
13858
self.pressedKeys.push(code);
13859
13860
bindings.forEach(function(binding){
13861
self.checkBinding(e, binding);
13862
});
13863
}
13864
};
13865
13866
this.keydownBinding = function(e){
13867
var code = e.keyCode;
13868
var bindings = self.watchKeys[code];
13869
13870
if(bindings){
13871
13872
var index = self.pressedKeys.indexOf(code);
13873
13874
if(index > -1){
13875
self.pressedKeys.splice(index, 1);
13876
}
13877
}
13878
};
13879
13880
this.table.element.addEventListener("keydown", this.keyupBinding);
13881
13882
this.table.element.addEventListener("keyup", this.keydownBinding);
13883
}
13884
13885
clearBindings(){
13886
if(this.keyupBinding){
13887
this.table.element.removeEventListener("keydown", this.keyupBinding);
13888
}
13889
13890
if(this.keydownBinding){
13891
this.table.element.removeEventListener("keyup", this.keydownBinding);
13892
}
13893
}
13894
13895
checkBinding(e, binding){
13896
var match = true;
13897
13898
if(e.ctrlKey == binding.ctrl && e.shiftKey == binding.shift && e.metaKey == binding.meta){
13899
binding.keys.forEach((key) => {
13900
var index = this.pressedKeys.indexOf(key);
13901
13902
if(index == -1){
13903
match = false;
13904
}
13905
});
13906
13907
if(match){
13908
binding.action.call(this, e);
13909
}
13910
13911
return true;
13912
}
13913
13914
return false;
13915
}
13916
}
13917
13918
Keybindings.moduleName = "keybindings";
13919
13920
//load defaults
13921
Keybindings.bindings = defaultBindings;
13922
Keybindings.actions = defaultActions;
13923
13924
class Menu extends Module{
13925
13926
constructor(table){
13927
super(table);
13928
13929
this.menuContainer = null;
13930
this.nestedMenuBlock = false;
13931
13932
this.currentComponent = null;
13933
this.rootPopup = null;
13934
13935
this.columnSubscribers = {};
13936
13937
this.registerTableOption("menuContainer", undefined); //deprecated
13938
13939
this.registerTableOption("rowContextMenu", false);
13940
this.registerTableOption("rowClickMenu", false);
13941
this.registerTableOption("rowDblClickMenu", false);
13942
this.registerTableOption("groupContextMenu", false);
13943
this.registerTableOption("groupClickMenu", false);
13944
this.registerTableOption("groupDblClickMenu", false);
13945
13946
this.registerColumnOption("headerContextMenu");
13947
this.registerColumnOption("headerClickMenu");
13948
this.registerColumnOption("headerDblClickMenu");
13949
this.registerColumnOption("headerMenu");
13950
this.registerColumnOption("headerMenuIcon");
13951
this.registerColumnOption("contextMenu");
13952
this.registerColumnOption("clickMenu");
13953
this.registerColumnOption("dblClickMenu");
13954
13955
}
13956
13957
initialize(){
13958
this.deprecatedOptionsCheck();
13959
this.initializeRowWatchers();
13960
this.initializeGroupWatchers();
13961
13962
this.subscribe("column-init", this.initializeColumn.bind(this));
13963
}
13964
13965
deprecatedOptionsCheck(){
13966
if(!this.deprecationCheck("menuContainer", "popupContainer")){
13967
this.table.options.popupContainer = this.table.options.menuContainer;
13968
}
13969
}
13970
13971
initializeRowWatchers(){
13972
if(this.table.options.rowContextMenu){
13973
this.subscribe("row-contextmenu", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu));
13974
this.table.on("rowTapHold", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu));
13975
}
13976
13977
if(this.table.options.rowClickMenu){
13978
this.subscribe("row-click", this.loadMenuEvent.bind(this, this.table.options.rowClickMenu));
13979
}
13980
13981
if(this.table.options.rowDblClickMenu){
13982
this.subscribe("row-dblclick", this.loadMenuEvent.bind(this, this.table.options.rowDblClickMenu));
13983
}
13984
}
13985
13986
initializeGroupWatchers(){
13987
if(this.table.options.groupContextMenu){
13988
this.subscribe("group-contextmenu", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu));
13989
this.table.on("groupTapHold", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu));
13990
}
13991
13992
if(this.table.options.groupClickMenu){
13993
this.subscribe("group-click", this.loadMenuEvent.bind(this, this.table.options.groupClickMenu));
13994
}
13995
13996
if(this.table.options.groupDblClickMenu){
13997
this.subscribe("group-dblclick", this.loadMenuEvent.bind(this, this.table.options.groupDblClickMenu));
13998
}
13999
}
14000
14001
initializeColumn(column){
14002
var def = column.definition;
14003
14004
//handle column events
14005
if(def.headerContextMenu && !this.columnSubscribers.headerContextMenu){
14006
this.columnSubscribers.headerContextMenu = this.loadMenuTableColumnEvent.bind(this, "headerContextMenu");
14007
this.subscribe("column-contextmenu", this.columnSubscribers.headerContextMenu);
14008
this.table.on("headerTapHold", this.loadMenuTableColumnEvent.bind(this, "headerContextMenu"));
14009
}
14010
14011
if(def.headerClickMenu && !this.columnSubscribers.headerClickMenu){
14012
this.columnSubscribers.headerClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerClickMenu");
14013
this.subscribe("column-click", this.columnSubscribers.headerClickMenu);
14014
}
14015
14016
if(def.headerDblClickMenu && !this.columnSubscribers.headerDblClickMenu){
14017
this.columnSubscribers.headerDblClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerDblClickMenu");
14018
this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickMenu);
14019
}
14020
14021
if(def.headerMenu){
14022
this.initializeColumnHeaderMenu(column);
14023
}
14024
14025
//handle cell events
14026
if(def.contextMenu && !this.columnSubscribers.contextMenu){
14027
this.columnSubscribers.contextMenu = this.loadMenuTableCellEvent.bind(this, "contextMenu");
14028
this.subscribe("cell-contextmenu", this.columnSubscribers.contextMenu);
14029
this.table.on("cellTapHold", this.loadMenuTableCellEvent.bind(this, "contextMenu"));
14030
}
14031
14032
if(def.clickMenu && !this.columnSubscribers.clickMenu){
14033
this.columnSubscribers.clickMenu = this.loadMenuTableCellEvent.bind(this, "clickMenu");
14034
this.subscribe("cell-click", this.columnSubscribers.clickMenu);
14035
}
14036
14037
if(def.dblClickMenu && !this.columnSubscribers.dblClickMenu){
14038
this.columnSubscribers.dblClickMenu = this.loadMenuTableCellEvent.bind(this, "dblClickMenu");
14039
this.subscribe("cell-dblclick", this.columnSubscribers.dblClickMenu);
14040
}
14041
}
14042
14043
initializeColumnHeaderMenu(column){
14044
var icon = column.definition.headerMenuIcon,
14045
headerMenuEl;
14046
14047
headerMenuEl = document.createElement("span");
14048
headerMenuEl.classList.add("tabulator-header-popup-button");
14049
14050
if(icon){
14051
if(typeof icon === "function"){
14052
icon = icon(column.getComponent());
14053
}
14054
14055
if(icon instanceof HTMLElement){
14056
headerMenuEl.appendChild(icon);
14057
}else {
14058
headerMenuEl.innerHTML = icon;
14059
}
14060
}else {
14061
headerMenuEl.innerHTML = "&vellip;";
14062
}
14063
14064
headerMenuEl.addEventListener("click", (e) => {
14065
e.stopPropagation();
14066
e.preventDefault();
14067
14068
this.loadMenuEvent(column.definition.headerMenu, e, column);
14069
});
14070
14071
column.titleElement.insertBefore(headerMenuEl, column.titleElement.firstChild);
14072
}
14073
14074
loadMenuTableCellEvent(option, e, cell){
14075
if(cell._cell){
14076
cell = cell._cell;
14077
}
14078
14079
if(cell.column.definition[option]){
14080
this.loadMenuEvent(cell.column.definition[option], e, cell);
14081
}
14082
}
14083
14084
loadMenuTableColumnEvent(option, e, column){
14085
if(column._column){
14086
column = column._column;
14087
}
14088
14089
if(column.definition[option]){
14090
this.loadMenuEvent(column.definition[option], e, column);
14091
}
14092
}
14093
14094
loadMenuEvent(menu, e, component){
14095
if(component._group){
14096
component = component._group;
14097
}else if(component._row){
14098
component = component._row;
14099
}
14100
14101
menu = typeof menu == "function" ? menu.call(this.table, e, component.getComponent()) : menu;
14102
14103
this.loadMenu(e, component, menu);
14104
}
14105
14106
loadMenu(e, component, menu, parentEl, parentPopup){
14107
var touch = !(e instanceof MouseEvent),
14108
menuEl = document.createElement("div"),
14109
popup;
14110
14111
menuEl.classList.add("tabulator-menu");
14112
14113
if(!touch){
14114
e.preventDefault();
14115
}
14116
14117
//abort if no menu set
14118
if(!menu || !menu.length){
14119
return;
14120
}
14121
14122
if(!parentEl){
14123
if(this.nestedMenuBlock){
14124
//abort if child menu already open
14125
if(this.rootPopup){
14126
return;
14127
}
14128
}else {
14129
this.nestedMenuBlock = setTimeout(() => {
14130
this.nestedMenuBlock = false;
14131
}, 100);
14132
}
14133
14134
if(this.rootPopup){
14135
this.rootPopup.hide();
14136
}
14137
14138
this.rootPopup = popup = this.popup(menuEl);
14139
14140
}else {
14141
popup = parentPopup.child(menuEl);
14142
}
14143
14144
menu.forEach((item) => {
14145
var itemEl = document.createElement("div"),
14146
label = item.label,
14147
disabled = item.disabled;
14148
14149
if(item.separator){
14150
itemEl.classList.add("tabulator-menu-separator");
14151
}else {
14152
itemEl.classList.add("tabulator-menu-item");
14153
14154
if(typeof label == "function"){
14155
label = label.call(this.table, component.getComponent());
14156
}
14157
14158
if(label instanceof Node){
14159
itemEl.appendChild(label);
14160
}else {
14161
itemEl.innerHTML = label;
14162
}
14163
14164
if(typeof disabled == "function"){
14165
disabled = disabled.call(this.table, component.getComponent());
14166
}
14167
14168
if(disabled){
14169
itemEl.classList.add("tabulator-menu-item-disabled");
14170
itemEl.addEventListener("click", (e) => {
14171
e.stopPropagation();
14172
});
14173
}else {
14174
if(item.menu && item.menu.length){
14175
itemEl.addEventListener("click", (e) => {
14176
e.stopPropagation();
14177
this.loadMenu(e, component, item.menu, itemEl, popup);
14178
});
14179
}else {
14180
if(item.action){
14181
itemEl.addEventListener("click", (e) => {
14182
item.action(e, component.getComponent());
14183
});
14184
}
14185
}
14186
}
14187
14188
if(item.menu && item.menu.length){
14189
itemEl.classList.add("tabulator-menu-item-submenu");
14190
}
14191
}
14192
14193
menuEl.appendChild(itemEl);
14194
});
14195
14196
menuEl.addEventListener("click", (e) => {
14197
if(this.rootPopup){
14198
this.rootPopup.hide();
14199
}
14200
});
14201
14202
popup.show(parentEl || e);
14203
14204
if(popup === this.rootPopup){
14205
this.rootPopup.hideOnBlur(() => {
14206
this.rootPopup = null;
14207
14208
if(this.currentComponent){
14209
this.dispatchExternal("menuClosed", this.currentComponent.getComponent());
14210
this.currentComponent = null;
14211
}
14212
});
14213
14214
this.currentComponent = component;
14215
14216
this.dispatchExternal("menuOpened", component.getComponent());
14217
}
14218
}
14219
}
14220
14221
Menu.moduleName = "menu";
14222
14223
class MoveColumns extends Module{
14224
14225
constructor(table){
14226
super(table);
14227
14228
this.placeholderElement = this.createPlaceholderElement();
14229
this.hoverElement = false; //floating column header element
14230
this.checkTimeout = false; //click check timeout holder
14231
this.checkPeriod = 250; //period to wait on mousedown to consider this a move and not a click
14232
this.moving = false; //currently moving column
14233
this.toCol = false; //destination column
14234
this.toColAfter = false; //position of moving column relative to the destination column
14235
this.startX = 0; //starting position within header element
14236
this.autoScrollMargin = 40; //auto scroll on edge when within margin
14237
this.autoScrollStep = 5; //auto scroll distance in pixels
14238
this.autoScrollTimeout = false; //auto scroll timeout
14239
this.touchMove = false;
14240
14241
this.moveHover = this.moveHover.bind(this);
14242
this.endMove = this.endMove.bind(this);
14243
14244
this.registerTableOption("movableColumns", false); //enable movable columns
14245
}
14246
14247
createPlaceholderElement(){
14248
var el = document.createElement("div");
14249
14250
el.classList.add("tabulator-col");
14251
el.classList.add("tabulator-col-placeholder");
14252
14253
return el;
14254
}
14255
14256
initialize(){
14257
if(this.table.options.movableColumns){
14258
this.subscribe("column-init", this.initializeColumn.bind(this));
14259
}
14260
}
14261
14262
initializeColumn(column){
14263
var self = this,
14264
config = {},
14265
colEl;
14266
14267
if(!column.modules.frozen && !column.isGroup){
14268
colEl = column.getElement();
14269
14270
config.mousemove = function(e){
14271
if(column.parent === self.moving.parent){
14272
if((((self.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(colEl).left) + self.table.columnManager.contentsElement.scrollLeft) > (column.getWidth() / 2)){
14273
if(self.toCol !== column || !self.toColAfter){
14274
colEl.parentNode.insertBefore(self.placeholderElement, colEl.nextSibling);
14275
self.moveColumn(column, true);
14276
}
14277
}else {
14278
if(self.toCol !== column || self.toColAfter){
14279
colEl.parentNode.insertBefore(self.placeholderElement, colEl);
14280
self.moveColumn(column, false);
14281
}
14282
}
14283
}
14284
}.bind(self);
14285
14286
colEl.addEventListener("mousedown", function(e){
14287
self.touchMove = false;
14288
if(e.which === 1){
14289
self.checkTimeout = setTimeout(function(){
14290
self.startMove(e, column);
14291
}, self.checkPeriod);
14292
}
14293
});
14294
14295
colEl.addEventListener("mouseup", function(e){
14296
if(e.which === 1){
14297
if(self.checkTimeout){
14298
clearTimeout(self.checkTimeout);
14299
}
14300
}
14301
});
14302
14303
self.bindTouchEvents(column);
14304
}
14305
14306
column.modules.moveColumn = config;
14307
}
14308
14309
bindTouchEvents(column){
14310
var colEl = column.getElement(),
14311
startXMove = false, //shifting center position of the cell
14312
nextCol, prevCol, nextColWidth, prevColWidth, nextColWidthLast, prevColWidthLast;
14313
14314
colEl.addEventListener("touchstart", (e) => {
14315
this.checkTimeout = setTimeout(() => {
14316
this.touchMove = true;
14317
nextCol = column.nextColumn();
14318
nextColWidth = nextCol ? nextCol.getWidth()/2 : 0;
14319
prevCol = column.prevColumn();
14320
prevColWidth = prevCol ? prevCol.getWidth()/2 : 0;
14321
nextColWidthLast = 0;
14322
prevColWidthLast = 0;
14323
startXMove = false;
14324
14325
this.startMove(e, column);
14326
}, this.checkPeriod);
14327
}, {passive: true});
14328
14329
colEl.addEventListener("touchmove", (e) => {
14330
var diff, moveToCol;
14331
14332
if(this.moving){
14333
this.moveHover(e);
14334
14335
if(!startXMove){
14336
startXMove = e.touches[0].pageX;
14337
}
14338
14339
diff = e.touches[0].pageX - startXMove;
14340
14341
if(diff > 0){
14342
if(nextCol && diff - nextColWidthLast > nextColWidth){
14343
moveToCol = nextCol;
14344
14345
if(moveToCol !== column){
14346
startXMove = e.touches[0].pageX;
14347
moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement().nextSibling);
14348
this.moveColumn(moveToCol, true);
14349
}
14350
}
14351
}else {
14352
if(prevCol && -diff - prevColWidthLast > prevColWidth){
14353
moveToCol = prevCol;
14354
14355
if(moveToCol !== column){
14356
startXMove = e.touches[0].pageX;
14357
moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement());
14358
this.moveColumn(moveToCol, false);
14359
}
14360
}
14361
}
14362
14363
if(moveToCol){
14364
nextCol = moveToCol.nextColumn();
14365
nextColWidthLast = nextColWidth;
14366
nextColWidth = nextCol ? nextCol.getWidth() / 2 : 0;
14367
prevCol = moveToCol.prevColumn();
14368
prevColWidthLast = prevColWidth;
14369
prevColWidth = prevCol ? prevCol.getWidth() / 2 : 0;
14370
}
14371
}
14372
}, {passive: true});
14373
14374
colEl.addEventListener("touchend", (e) => {
14375
if(this.checkTimeout){
14376
clearTimeout(this.checkTimeout);
14377
}
14378
if(this.moving){
14379
this.endMove(e);
14380
}
14381
});
14382
}
14383
14384
startMove(e, column){
14385
var element = column.getElement(),
14386
headerElement = this.table.columnManager.getContentsElement(),
14387
headersElement = this.table.columnManager.getHeadersElement();
14388
14389
this.moving = column;
14390
this.startX = (this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(element).left;
14391
14392
this.table.element.classList.add("tabulator-block-select");
14393
14394
//create placeholder
14395
this.placeholderElement.style.width = column.getWidth() + "px";
14396
this.placeholderElement.style.height = column.getHeight() + "px";
14397
14398
element.parentNode.insertBefore(this.placeholderElement, element);
14399
element.parentNode.removeChild(element);
14400
14401
//create hover element
14402
this.hoverElement = element.cloneNode(true);
14403
this.hoverElement.classList.add("tabulator-moving");
14404
14405
headerElement.appendChild(this.hoverElement);
14406
14407
this.hoverElement.style.left = "0";
14408
this.hoverElement.style.bottom = (headerElement.clientHeight - headersElement.offsetHeight) + "px";
14409
14410
if(!this.touchMove){
14411
this._bindMouseMove();
14412
14413
document.body.addEventListener("mousemove", this.moveHover);
14414
document.body.addEventListener("mouseup", this.endMove);
14415
}
14416
14417
this.moveHover(e);
14418
}
14419
14420
_bindMouseMove(){
14421
this.table.columnManager.columnsByIndex.forEach(function(column){
14422
if(column.modules.moveColumn.mousemove){
14423
column.getElement().addEventListener("mousemove", column.modules.moveColumn.mousemove);
14424
}
14425
});
14426
}
14427
14428
_unbindMouseMove(){
14429
this.table.columnManager.columnsByIndex.forEach(function(column){
14430
if(column.modules.moveColumn.mousemove){
14431
column.getElement().removeEventListener("mousemove", column.modules.moveColumn.mousemove);
14432
}
14433
});
14434
}
14435
14436
moveColumn(column, after){
14437
var movingCells = this.moving.getCells();
14438
14439
this.toCol = column;
14440
this.toColAfter = after;
14441
14442
if(after){
14443
column.getCells().forEach(function(cell, i){
14444
var cellEl = cell.getElement(true);
14445
14446
if(cellEl.parentNode && movingCells[i]){
14447
cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl.nextSibling);
14448
}
14449
});
14450
}else {
14451
column.getCells().forEach(function(cell, i){
14452
var cellEl = cell.getElement(true);
14453
14454
if(cellEl.parentNode && movingCells[i]){
14455
cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl);
14456
}
14457
});
14458
}
14459
}
14460
14461
endMove(e){
14462
if(e.which === 1 || this.touchMove){
14463
this._unbindMouseMove();
14464
14465
this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling);
14466
this.placeholderElement.parentNode.removeChild(this.placeholderElement);
14467
this.hoverElement.parentNode.removeChild(this.hoverElement);
14468
14469
this.table.element.classList.remove("tabulator-block-select");
14470
14471
if(this.toCol){
14472
this.table.columnManager.moveColumnActual(this.moving, this.toCol, this.toColAfter);
14473
}
14474
14475
this.moving = false;
14476
this.toCol = false;
14477
this.toColAfter = false;
14478
14479
if(!this.touchMove){
14480
document.body.removeEventListener("mousemove", this.moveHover);
14481
document.body.removeEventListener("mouseup", this.endMove);
14482
}
14483
}
14484
}
14485
14486
moveHover(e){
14487
var columnHolder = this.table.columnManager.getContentsElement(),
14488
scrollLeft = columnHolder.scrollLeft,
14489
xPos = ((this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(columnHolder).left) + scrollLeft,
14490
scrollPos;
14491
14492
this.hoverElement.style.left = (xPos - this.startX) + "px";
14493
14494
if(xPos - scrollLeft < this.autoScrollMargin){
14495
if(!this.autoScrollTimeout){
14496
this.autoScrollTimeout = setTimeout(() => {
14497
scrollPos = Math.max(0,scrollLeft-5);
14498
this.table.rowManager.getElement().scrollLeft = scrollPos;
14499
this.autoScrollTimeout = false;
14500
}, 1);
14501
}
14502
}
14503
14504
if(scrollLeft + columnHolder.clientWidth - xPos < this.autoScrollMargin){
14505
if(!this.autoScrollTimeout){
14506
this.autoScrollTimeout = setTimeout(() => {
14507
scrollPos = Math.min(columnHolder.clientWidth, scrollLeft+5);
14508
this.table.rowManager.getElement().scrollLeft = scrollPos;
14509
this.autoScrollTimeout = false;
14510
}, 1);
14511
}
14512
}
14513
}
14514
}
14515
14516
MoveColumns.moduleName = "moveColumn";
14517
14518
class MoveRows extends Module{
14519
14520
constructor(table){
14521
super(table);
14522
14523
this.placeholderElement = this.createPlaceholderElement();
14524
this.hoverElement = false; //floating row header element
14525
this.checkTimeout = false; //click check timeout holder
14526
this.checkPeriod = 150; //period to wait on mousedown to consider this a move and not a click
14527
this.moving = false; //currently moving row
14528
this.toRow = false; //destination row
14529
this.toRowAfter = false; //position of moving row relative to the destination row
14530
this.hasHandle = false; //row has handle instead of fully movable row
14531
this.startY = 0; //starting Y position within header element
14532
this.startX = 0; //starting X position within header element
14533
14534
this.moveHover = this.moveHover.bind(this);
14535
this.endMove = this.endMove.bind(this);
14536
this.tableRowDropEvent = false;
14537
14538
this.touchMove = false;
14539
14540
this.connection = false;
14541
this.connectionSelectorsTables = false;
14542
this.connectionSelectorsElements = false;
14543
this.connectionElements = [];
14544
this.connections = [];
14545
14546
this.connectedTable = false;
14547
this.connectedRow = false;
14548
14549
this.registerTableOption("movableRows", false); //enable movable rows
14550
this.registerTableOption("movableRowsConnectedTables", false); //tables for movable rows to be connected to
14551
this.registerTableOption("movableRowsConnectedElements", false); //other elements for movable rows to be connected to
14552
this.registerTableOption("movableRowsSender", false);
14553
this.registerTableOption("movableRowsReceiver", "insert");
14554
14555
this.registerColumnOption("rowHandle");
14556
}
14557
14558
createPlaceholderElement(){
14559
var el = document.createElement("div");
14560
14561
el.classList.add("tabulator-row");
14562
el.classList.add("tabulator-row-placeholder");
14563
14564
return el;
14565
}
14566
14567
initialize(){
14568
if(this.table.options.movableRows){
14569
this.connectionSelectorsTables = this.table.options.movableRowsConnectedTables;
14570
this.connectionSelectorsElements = this.table.options.movableRowsConnectedElements;
14571
14572
this.connection = this.connectionSelectorsTables || this.connectionSelectorsElements;
14573
14574
this.subscribe("cell-init", this.initializeCell.bind(this));
14575
this.subscribe("column-init", this.initializeColumn.bind(this));
14576
this.subscribe("row-init", this.initializeRow.bind(this));
14577
}
14578
}
14579
14580
initializeGroupHeader(group){
14581
var self = this,
14582
config = {};
14583
14584
//inter table drag drop
14585
config.mouseup = function(e){
14586
self.tableRowDrop(e, group);
14587
}.bind(self);
14588
14589
//same table drag drop
14590
config.mousemove = function(e){
14591
var rowEl;
14592
14593
if(((e.pageY - Helpers.elOffset(group.element).top) + self.table.rowManager.element.scrollTop) > (group.getHeight() / 2)){
14594
if(self.toRow !== group || !self.toRowAfter){
14595
rowEl = group.getElement();
14596
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
14597
self.moveRow(group, true);
14598
}
14599
}else {
14600
if(self.toRow !== group || self.toRowAfter){
14601
rowEl = group.getElement();
14602
if(rowEl.previousSibling){
14603
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
14604
self.moveRow(group, false);
14605
}
14606
}
14607
}
14608
}.bind(self);
14609
14610
group.modules.moveRow = config;
14611
}
14612
14613
initializeRow(row){
14614
var self = this,
14615
config = {},
14616
rowEl;
14617
14618
//inter table drag drop
14619
config.mouseup = function(e){
14620
self.tableRowDrop(e, row);
14621
}.bind(self);
14622
14623
//same table drag drop
14624
config.mousemove = function(e){
14625
var rowEl = row.getElement();
14626
14627
if(((e.pageY - Helpers.elOffset(rowEl).top) + self.table.rowManager.element.scrollTop) > (row.getHeight() / 2)){
14628
if(self.toRow !== row || !self.toRowAfter){
14629
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
14630
self.moveRow(row, true);
14631
}
14632
}else {
14633
if(self.toRow !== row || self.toRowAfter){
14634
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
14635
self.moveRow(row, false);
14636
}
14637
}
14638
}.bind(self);
14639
14640
14641
if(!this.hasHandle){
14642
14643
rowEl = row.getElement();
14644
14645
rowEl.addEventListener("mousedown", function(e){
14646
if(e.which === 1){
14647
self.checkTimeout = setTimeout(function(){
14648
self.startMove(e, row);
14649
}, self.checkPeriod);
14650
}
14651
});
14652
14653
rowEl.addEventListener("mouseup", function(e){
14654
if(e.which === 1){
14655
if(self.checkTimeout){
14656
clearTimeout(self.checkTimeout);
14657
}
14658
}
14659
});
14660
14661
this.bindTouchEvents(row, row.getElement());
14662
}
14663
14664
row.modules.moveRow = config;
14665
}
14666
14667
initializeColumn(column){
14668
if(column.definition.rowHandle && this.table.options.movableRows !== false){
14669
this.hasHandle = true;
14670
}
14671
}
14672
14673
initializeCell(cell){
14674
if(cell.column.definition.rowHandle && this.table.options.movableRows !== false){
14675
var self = this,
14676
cellEl = cell.getElement(true);
14677
14678
cellEl.addEventListener("mousedown", function(e){
14679
if(e.which === 1){
14680
self.checkTimeout = setTimeout(function(){
14681
self.startMove(e, cell.row);
14682
}, self.checkPeriod);
14683
}
14684
});
14685
14686
cellEl.addEventListener("mouseup", function(e){
14687
if(e.which === 1){
14688
if(self.checkTimeout){
14689
clearTimeout(self.checkTimeout);
14690
}
14691
}
14692
});
14693
14694
this.bindTouchEvents(cell.row, cellEl);
14695
}
14696
}
14697
14698
bindTouchEvents(row, element){
14699
var startYMove = false, //shifting center position of the cell
14700
nextRow, prevRow, nextRowHeight, prevRowHeight, nextRowHeightLast, prevRowHeightLast;
14701
14702
element.addEventListener("touchstart", (e) => {
14703
this.checkTimeout = setTimeout(() => {
14704
this.touchMove = true;
14705
nextRow = row.nextRow();
14706
nextRowHeight = nextRow ? nextRow.getHeight()/2 : 0;
14707
prevRow = row.prevRow();
14708
prevRowHeight = prevRow ? prevRow.getHeight()/2 : 0;
14709
nextRowHeightLast = 0;
14710
prevRowHeightLast = 0;
14711
startYMove = false;
14712
14713
this.startMove(e, row);
14714
}, this.checkPeriod);
14715
}, {passive: true});
14716
this.moving, this.toRow, this.toRowAfter;
14717
element.addEventListener("touchmove", (e) => {
14718
14719
var diff, moveToRow;
14720
14721
if(this.moving){
14722
e.preventDefault();
14723
14724
this.moveHover(e);
14725
14726
if(!startYMove){
14727
startYMove = e.touches[0].pageY;
14728
}
14729
14730
diff = e.touches[0].pageY - startYMove;
14731
14732
if(diff > 0){
14733
if(nextRow && diff - nextRowHeightLast > nextRowHeight){
14734
moveToRow = nextRow;
14735
14736
if(moveToRow !== row){
14737
startYMove = e.touches[0].pageY;
14738
moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement().nextSibling);
14739
this.moveRow(moveToRow, true);
14740
}
14741
}
14742
}else {
14743
if(prevRow && -diff - prevRowHeightLast > prevRowHeight){
14744
moveToRow = prevRow;
14745
14746
if(moveToRow !== row){
14747
startYMove = e.touches[0].pageY;
14748
moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement());
14749
this.moveRow(moveToRow, false);
14750
}
14751
}
14752
}
14753
14754
if(moveToRow){
14755
nextRow = moveToRow.nextRow();
14756
nextRowHeightLast = nextRowHeight;
14757
nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0;
14758
prevRow = moveToRow.prevRow();
14759
prevRowHeightLast = prevRowHeight;
14760
prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0;
14761
}
14762
}
14763
});
14764
14765
element.addEventListener("touchend", (e) => {
14766
if(this.checkTimeout){
14767
clearTimeout(this.checkTimeout);
14768
}
14769
if(this.moving){
14770
this.endMove(e);
14771
this.touchMove = false;
14772
}
14773
});
14774
}
14775
14776
_bindMouseMove(){
14777
this.table.rowManager.getDisplayRows().forEach((row) => {
14778
if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){
14779
row.getElement().addEventListener("mousemove", row.modules.moveRow.mousemove);
14780
}
14781
});
14782
}
14783
14784
_unbindMouseMove(){
14785
this.table.rowManager.getDisplayRows().forEach((row) => {
14786
if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){
14787
row.getElement().removeEventListener("mousemove", row.modules.moveRow.mousemove);
14788
}
14789
});
14790
}
14791
14792
startMove(e, row){
14793
var element = row.getElement();
14794
14795
this.setStartPosition(e, row);
14796
14797
this.moving = row;
14798
14799
this.table.element.classList.add("tabulator-block-select");
14800
14801
//create placeholder
14802
this.placeholderElement.style.width = row.getWidth() + "px";
14803
this.placeholderElement.style.height = row.getHeight() + "px";
14804
14805
if(!this.connection){
14806
element.parentNode.insertBefore(this.placeholderElement, element);
14807
element.parentNode.removeChild(element);
14808
}else {
14809
this.table.element.classList.add("tabulator-movingrow-sending");
14810
this.connectToTables(row);
14811
}
14812
14813
//create hover element
14814
this.hoverElement = element.cloneNode(true);
14815
this.hoverElement.classList.add("tabulator-moving");
14816
14817
if(this.connection){
14818
document.body.appendChild(this.hoverElement);
14819
this.hoverElement.style.left = "0";
14820
this.hoverElement.style.top = "0";
14821
this.hoverElement.style.width = this.table.element.clientWidth + "px";
14822
this.hoverElement.style.whiteSpace = "nowrap";
14823
this.hoverElement.style.overflow = "hidden";
14824
this.hoverElement.style.pointerEvents = "none";
14825
}else {
14826
this.table.rowManager.getTableElement().appendChild(this.hoverElement);
14827
14828
this.hoverElement.style.left = "0";
14829
this.hoverElement.style.top = "0";
14830
14831
this._bindMouseMove();
14832
}
14833
14834
document.body.addEventListener("mousemove", this.moveHover);
14835
document.body.addEventListener("mouseup", this.endMove);
14836
14837
this.dispatchExternal("rowMoving", row.getComponent());
14838
14839
this.moveHover(e);
14840
}
14841
14842
setStartPosition(e, row){
14843
var pageX = this.touchMove ? e.touches[0].pageX : e.pageX,
14844
pageY = this.touchMove ? e.touches[0].pageY : e.pageY,
14845
element, position;
14846
14847
element = row.getElement();
14848
if(this.connection){
14849
position = element.getBoundingClientRect();
14850
14851
this.startX = position.left - pageX + window.pageXOffset;
14852
this.startY = position.top - pageY + window.pageYOffset;
14853
}else {
14854
this.startY = (pageY - element.getBoundingClientRect().top);
14855
}
14856
}
14857
14858
endMove(e){
14859
if(!e || e.which === 1 || this.touchMove){
14860
this._unbindMouseMove();
14861
14862
if(!this.connection){
14863
this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling);
14864
this.placeholderElement.parentNode.removeChild(this.placeholderElement);
14865
}
14866
14867
this.hoverElement.parentNode.removeChild(this.hoverElement);
14868
14869
this.table.element.classList.remove("tabulator-block-select");
14870
14871
if(this.toRow){
14872
this.table.rowManager.moveRow(this.moving, this.toRow, this.toRowAfter);
14873
}else {
14874
this.dispatchExternal("rowMoveCancelled", this.moving.getComponent());
14875
}
14876
14877
this.moving = false;
14878
this.toRow = false;
14879
this.toRowAfter = false;
14880
14881
document.body.removeEventListener("mousemove", this.moveHover);
14882
document.body.removeEventListener("mouseup", this.endMove);
14883
14884
if(this.connection){
14885
this.table.element.classList.remove("tabulator-movingrow-sending");
14886
this.disconnectFromTables();
14887
}
14888
}
14889
}
14890
14891
moveRow(row, after){
14892
this.toRow = row;
14893
this.toRowAfter = after;
14894
}
14895
14896
moveHover(e){
14897
if(this.connection){
14898
this.moveHoverConnections.call(this, e);
14899
}else {
14900
this.moveHoverTable.call(this, e);
14901
}
14902
}
14903
14904
moveHoverTable(e){
14905
var rowHolder = this.table.rowManager.getElement(),
14906
scrollTop = rowHolder.scrollTop,
14907
yPos = ((this.touchMove ? e.touches[0].pageY : e.pageY) - rowHolder.getBoundingClientRect().top) + scrollTop;
14908
14909
this.hoverElement.style.top = Math.min(yPos - this.startY, this.table.rowManager.element.scrollHeight - this.hoverElement.offsetHeight) + "px";
14910
}
14911
14912
moveHoverConnections(e){
14913
this.hoverElement.style.left = (this.startX + (this.touchMove ? e.touches[0].pageX : e.pageX)) + "px";
14914
this.hoverElement.style.top = (this.startY + (this.touchMove ? e.touches[0].pageY : e.pageY)) + "px";
14915
}
14916
14917
elementRowDrop(e, element, row){
14918
this.dispatchExternal("movableRowsElementDrop", e, element, row ? row.getComponent() : false);
14919
}
14920
14921
//establish connection with other tables
14922
connectToTables(row){
14923
var connectionTables;
14924
14925
if(this.connectionSelectorsTables){
14926
connectionTables = this.commsConnections(this.connectionSelectorsTables);
14927
14928
this.dispatchExternal("movableRowsSendingStart", connectionTables);
14929
14930
this.commsSend(this.connectionSelectorsTables, "moveRow", "connect", {
14931
row:row,
14932
});
14933
}
14934
14935
if(this.connectionSelectorsElements){
14936
14937
this.connectionElements = [];
14938
14939
if(!Array.isArray(this.connectionSelectorsElements)){
14940
this.connectionSelectorsElements = [this.connectionSelectorsElements];
14941
}
14942
14943
this.connectionSelectorsElements.forEach((query) => {
14944
if(typeof query === "string"){
14945
this.connectionElements = this.connectionElements.concat(Array.prototype.slice.call(document.querySelectorAll(query)));
14946
}else {
14947
this.connectionElements.push(query);
14948
}
14949
});
14950
14951
this.connectionElements.forEach((element) => {
14952
var dropEvent = (e) => {
14953
this.elementRowDrop(e, element, this.moving);
14954
};
14955
14956
element.addEventListener("mouseup", dropEvent);
14957
element.tabulatorElementDropEvent = dropEvent;
14958
14959
element.classList.add("tabulator-movingrow-receiving");
14960
});
14961
}
14962
}
14963
14964
//disconnect from other tables
14965
disconnectFromTables(){
14966
var connectionTables;
14967
14968
if(this.connectionSelectorsTables){
14969
connectionTables = this.commsConnections(this.connectionSelectorsTables);
14970
14971
this.dispatchExternal("movableRowsSendingStop", connectionTables);
14972
14973
this.commsSend(this.connectionSelectorsTables, "moveRow", "disconnect");
14974
}
14975
14976
this.connectionElements.forEach((element) => {
14977
element.classList.remove("tabulator-movingrow-receiving");
14978
element.removeEventListener("mouseup", element.tabulatorElementDropEvent);
14979
delete element.tabulatorElementDropEvent;
14980
});
14981
}
14982
14983
//accept incomming connection
14984
connect(table, row){
14985
if(!this.connectedTable){
14986
this.connectedTable = table;
14987
this.connectedRow = row;
14988
14989
this.table.element.classList.add("tabulator-movingrow-receiving");
14990
14991
this.table.rowManager.getDisplayRows().forEach((row) => {
14992
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
14993
row.getElement().addEventListener("mouseup", row.modules.moveRow.mouseup);
14994
}
14995
});
14996
14997
this.tableRowDropEvent = this.tableRowDrop.bind(this);
14998
14999
this.table.element.addEventListener("mouseup", this.tableRowDropEvent);
15000
15001
this.dispatchExternal("movableRowsReceivingStart", row, table);
15002
15003
return true;
15004
}else {
15005
console.warn("Move Row Error - Table cannot accept connection, already connected to table:", this.connectedTable);
15006
return false;
15007
}
15008
}
15009
15010
//close incoming connection
15011
disconnect(table){
15012
if(table === this.connectedTable){
15013
this.connectedTable = false;
15014
this.connectedRow = false;
15015
15016
this.table.element.classList.remove("tabulator-movingrow-receiving");
15017
15018
this.table.rowManager.getDisplayRows().forEach((row) =>{
15019
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
15020
row.getElement().removeEventListener("mouseup", row.modules.moveRow.mouseup);
15021
}
15022
});
15023
15024
this.table.element.removeEventListener("mouseup", this.tableRowDropEvent);
15025
15026
this.dispatchExternal("movableRowsReceivingStop", table);
15027
}else {
15028
console.warn("Move Row Error - trying to disconnect from non connected table");
15029
}
15030
}
15031
15032
dropComplete(table, row, success){
15033
var sender = false;
15034
15035
if(success){
15036
15037
switch(typeof this.table.options.movableRowsSender){
15038
case "string":
15039
sender = this.senders[this.table.options.movableRowsSender];
15040
break;
15041
15042
case "function":
15043
sender = this.table.options.movableRowsSender;
15044
break;
15045
}
15046
15047
if(sender){
15048
sender.call(this, this.moving ? this.moving.getComponent() : undefined, row ? row.getComponent() : undefined, table);
15049
}else {
15050
if(this.table.options.movableRowsSender){
15051
console.warn("Mover Row Error - no matching sender found:", this.table.options.movableRowsSender);
15052
}
15053
}
15054
15055
this.dispatchExternal("movableRowsSent", this.moving.getComponent(), row ? row.getComponent() : undefined, table);
15056
}else {
15057
this.dispatchExternal("movableRowsSentFailed", this.moving.getComponent(), row ? row.getComponent() : undefined, table);
15058
}
15059
15060
this.endMove();
15061
}
15062
15063
tableRowDrop(e, row){
15064
var receiver = false,
15065
success = false;
15066
15067
e.stopImmediatePropagation();
15068
15069
switch(typeof this.table.options.movableRowsReceiver){
15070
case "string":
15071
receiver = this.receivers[this.table.options.movableRowsReceiver];
15072
break;
15073
15074
case "function":
15075
receiver = this.table.options.movableRowsReceiver;
15076
break;
15077
}
15078
15079
if(receiver){
15080
success = receiver.call(this, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
15081
}else {
15082
console.warn("Mover Row Error - no matching receiver found:", this.table.options.movableRowsReceiver);
15083
}
15084
15085
if(success){
15086
this.dispatchExternal("movableRowsReceived", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
15087
}else {
15088
this.dispatchExternal("movableRowsReceivedFailed", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
15089
}
15090
15091
this.commsSend(this.connectedTable, "moveRow", "dropcomplete", {
15092
row:row,
15093
success:success,
15094
});
15095
}
15096
15097
commsReceived(table, action, data){
15098
switch(action){
15099
case "connect":
15100
return this.connect(table, data.row);
15101
15102
case "disconnect":
15103
return this.disconnect(table);
15104
15105
case "dropcomplete":
15106
return this.dropComplete(table, data.row, data.success);
15107
}
15108
}
15109
}
15110
15111
MoveRows.prototype.receivers = {
15112
insert:function(fromRow, toRow, fromTable){
15113
this.table.addRow(fromRow.getData(), undefined, toRow);
15114
return true;
15115
},
15116
15117
add:function(fromRow, toRow, fromTable){
15118
this.table.addRow(fromRow.getData());
15119
return true;
15120
},
15121
15122
update:function(fromRow, toRow, fromTable){
15123
if(toRow){
15124
toRow.update(fromRow.getData());
15125
return true;
15126
}
15127
15128
return false;
15129
},
15130
15131
replace:function(fromRow, toRow, fromTable){
15132
if(toRow){
15133
this.table.addRow(fromRow.getData(), undefined, toRow);
15134
toRow.delete();
15135
return true;
15136
}
15137
15138
return false;
15139
},
15140
};
15141
15142
MoveRows.prototype.senders = {
15143
delete:function(fromRow, toRow, toTable){
15144
fromRow.delete();
15145
}
15146
};
15147
15148
MoveRows.moduleName = "moveRow";
15149
15150
var defaultMutators = {};
15151
15152
class Mutator extends Module{
15153
15154
constructor(table){
15155
super(table);
15156
15157
this.allowedTypes = ["", "data", "edit", "clipboard"]; //list of mutation types
15158
this.enabled = true;
15159
15160
this.registerColumnOption("mutator");
15161
this.registerColumnOption("mutatorParams");
15162
this.registerColumnOption("mutatorData");
15163
this.registerColumnOption("mutatorDataParams");
15164
this.registerColumnOption("mutatorEdit");
15165
this.registerColumnOption("mutatorEditParams");
15166
this.registerColumnOption("mutatorClipboard");
15167
this.registerColumnOption("mutatorClipboardParams");
15168
this.registerColumnOption("mutateLink");
15169
}
15170
15171
initialize(){
15172
this.subscribe("cell-value-changing", this.transformCell.bind(this));
15173
this.subscribe("cell-value-changed", this.mutateLink.bind(this));
15174
this.subscribe("column-layout", this.initializeColumn.bind(this));
15175
this.subscribe("row-data-init-before", this.rowDataChanged.bind(this));
15176
this.subscribe("row-data-changing", this.rowDataChanged.bind(this));
15177
}
15178
15179
rowDataChanged(row, tempData, updatedData){
15180
return this.transformRow(tempData, "data", updatedData);
15181
}
15182
15183
//initialize column mutator
15184
initializeColumn(column){
15185
var match = false,
15186
config = {};
15187
15188
this.allowedTypes.forEach((type) => {
15189
var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)),
15190
mutator;
15191
15192
if(column.definition[key]){
15193
mutator = this.lookupMutator(column.definition[key]);
15194
15195
if(mutator){
15196
match = true;
15197
15198
config[key] = {
15199
mutator:mutator,
15200
params: column.definition[key + "Params"] || {},
15201
};
15202
}
15203
}
15204
});
15205
15206
if(match){
15207
column.modules.mutate = config;
15208
}
15209
}
15210
15211
lookupMutator(value){
15212
var mutator = false;
15213
15214
//set column mutator
15215
switch(typeof value){
15216
case "string":
15217
if(Mutator.mutators[value]){
15218
mutator = Mutator.mutators[value];
15219
}else {
15220
console.warn("Mutator Error - No such mutator found, ignoring: ", value);
15221
}
15222
break;
15223
15224
case "function":
15225
mutator = value;
15226
break;
15227
}
15228
15229
return mutator;
15230
}
15231
15232
//apply mutator to row
15233
transformRow(data, type, updatedData){
15234
var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)),
15235
value;
15236
15237
if(this.enabled){
15238
15239
this.table.columnManager.traverse((column) => {
15240
var mutator, params, component;
15241
15242
if(column.modules.mutate){
15243
mutator = column.modules.mutate[key] || column.modules.mutate.mutator || false;
15244
15245
if(mutator){
15246
value = column.getFieldValue(typeof updatedData !== "undefined" ? updatedData : data);
15247
15248
if((type == "data" && !updatedData)|| typeof value !== "undefined"){
15249
component = column.getComponent();
15250
params = typeof mutator.params === "function" ? mutator.params(value, data, type, component) : mutator.params;
15251
column.setFieldValue(data, mutator.mutator(value, data, type, params, component));
15252
}
15253
}
15254
}
15255
});
15256
}
15257
15258
return data;
15259
}
15260
15261
//apply mutator to new cell value
15262
transformCell(cell, value){
15263
if(cell.column.modules.mutate){
15264
var mutator = cell.column.modules.mutate.mutatorEdit || cell.column.modules.mutate.mutator || false,
15265
tempData = {};
15266
15267
if(mutator){
15268
tempData = Object.assign(tempData, cell.row.getData());
15269
cell.column.setFieldValue(tempData, value);
15270
return mutator.mutator(value, tempData, "edit", mutator.params, cell.getComponent());
15271
}
15272
}
15273
15274
return value;
15275
}
15276
15277
mutateLink(cell){
15278
var links = cell.column.definition.mutateLink;
15279
15280
if(links){
15281
if(!Array.isArray(links)){
15282
links = [links];
15283
}
15284
15285
links.forEach((link) => {
15286
var linkCell = cell.row.getCell(link);
15287
15288
if(linkCell){
15289
linkCell.setValue(linkCell.getValue(), true, true);
15290
}
15291
});
15292
}
15293
}
15294
15295
enable(){
15296
this.enabled = true;
15297
}
15298
15299
disable(){
15300
this.enabled = false;
15301
}
15302
}
15303
15304
Mutator.moduleName = "mutator";
15305
15306
//load defaults
15307
Mutator.mutators = defaultMutators;
15308
15309
function rows(pageSize, currentRow, currentPage, totalRows, totalPages){
15310
var el = document.createElement("span"),
15311
showingEl = document.createElement("span"),
15312
valueEl = document.createElement("span"),
15313
ofEl = document.createElement("span"),
15314
totalEl = document.createElement("span"),
15315
rowsEl = document.createElement("span");
15316
15317
this.table.modules.localize.langBind("pagination|counter|showing", (value) => {
15318
showingEl.innerHTML = value;
15319
});
15320
15321
this.table.modules.localize.langBind("pagination|counter|of", (value) => {
15322
ofEl.innerHTML = value;
15323
});
15324
15325
this.table.modules.localize.langBind("pagination|counter|rows", (value) => {
15326
rowsEl.innerHTML = value;
15327
});
15328
15329
if(totalRows){
15330
valueEl.innerHTML = " " + currentRow + "-" + Math.min((currentRow + pageSize - 1), totalRows) + " ";
15331
15332
totalEl.innerHTML = " " + totalRows + " ";
15333
15334
el.appendChild(showingEl);
15335
el.appendChild(valueEl);
15336
el.appendChild(ofEl);
15337
el.appendChild(totalEl);
15338
el.appendChild(rowsEl);
15339
}else {
15340
valueEl.innerHTML = " 0 ";
15341
15342
el.appendChild(showingEl);
15343
el.appendChild(valueEl);
15344
el.appendChild(rowsEl);
15345
}
15346
15347
return el;
15348
}
15349
15350
function pages(pageSize, currentRow, currentPage, totalRows, totalPages){
15351
15352
var el = document.createElement("span"),
15353
showingEl = document.createElement("span"),
15354
valueEl = document.createElement("span"),
15355
ofEl = document.createElement("span"),
15356
totalEl = document.createElement("span"),
15357
rowsEl = document.createElement("span");
15358
15359
this.table.modules.localize.langBind("pagination|counter|showing", (value) => {
15360
showingEl.innerHTML = value;
15361
});
15362
15363
valueEl.innerHTML = " " + currentPage + " ";
15364
15365
this.table.modules.localize.langBind("pagination|counter|of", (value) => {
15366
ofEl.innerHTML = value;
15367
});
15368
15369
totalEl.innerHTML = " " + totalPages + " ";
15370
15371
this.table.modules.localize.langBind("pagination|counter|pages", (value) => {
15372
rowsEl.innerHTML = value;
15373
});
15374
15375
el.appendChild(showingEl);
15376
el.appendChild(valueEl);
15377
el.appendChild(ofEl);
15378
el.appendChild(totalEl);
15379
el.appendChild(rowsEl);
15380
15381
return el;
15382
}
15383
15384
var defaultPageCounters = {
15385
rows:rows,
15386
pages:pages,
15387
};
15388
15389
class Page extends Module{
15390
15391
constructor(table){
15392
super(table);
15393
15394
this.mode = "local";
15395
this.progressiveLoad = false;
15396
15397
this.element = null;
15398
this.pageCounterElement = null;
15399
this.pageCounter = null;
15400
15401
this.size = 0;
15402
this.page = 1;
15403
this.count = 5;
15404
this.max = 1;
15405
15406
this.remoteRowCountEstimate = null;
15407
15408
this.initialLoad = true;
15409
this.dataChanging = false; //flag to check if data is being changed by this module
15410
15411
this.pageSizes = [];
15412
15413
this.registerTableOption("pagination", false); //set pagination type
15414
this.registerTableOption("paginationMode", "local"); //local or remote pagination
15415
this.registerTableOption("paginationSize", false); //set number of rows to a page
15416
this.registerTableOption("paginationInitialPage", 1); //initial page to show on load
15417
this.registerTableOption("paginationCounter", false); // set pagination counter
15418
this.registerTableOption("paginationCounterElement", false); // set pagination counter
15419
this.registerTableOption("paginationButtonCount", 5); // set count of page button
15420
this.registerTableOption("paginationSizeSelector", false); //add pagination size selector element
15421
this.registerTableOption("paginationElement", false); //element to hold pagination numbers
15422
// this.registerTableOption("paginationDataSent", {}); //pagination data sent to the server
15423
// this.registerTableOption("paginationDataReceived", {}); //pagination data received from the server
15424
this.registerTableOption("paginationAddRow", "page"); //add rows on table or page
15425
15426
this.registerTableOption("progressiveLoad", false); //progressive loading
15427
this.registerTableOption("progressiveLoadDelay", 0); //delay between requests
15428
this.registerTableOption("progressiveLoadScrollMargin", 0); //margin before scroll begins
15429
15430
this.registerTableFunction("setMaxPage", this.setMaxPage.bind(this));
15431
this.registerTableFunction("setPage", this.setPage.bind(this));
15432
this.registerTableFunction("setPageToRow", this.userSetPageToRow.bind(this));
15433
this.registerTableFunction("setPageSize", this.userSetPageSize.bind(this));
15434
this.registerTableFunction("getPageSize", this.getPageSize.bind(this));
15435
this.registerTableFunction("previousPage", this.previousPage.bind(this));
15436
this.registerTableFunction("nextPage", this.nextPage.bind(this));
15437
this.registerTableFunction("getPage", this.getPage.bind(this));
15438
this.registerTableFunction("getPageMax", this.getPageMax.bind(this));
15439
15440
//register component functions
15441
this.registerComponentFunction("row", "pageTo", this.setPageToRow.bind(this));
15442
}
15443
15444
initialize(){
15445
if(this.table.options.pagination){
15446
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
15447
this.subscribe("row-added", this.rowsUpdated.bind(this));
15448
this.subscribe("data-processed", this.initialLoadComplete.bind(this));
15449
this.subscribe("table-built", this.calculatePageSizes.bind(this));
15450
this.subscribe("footer-redraw", this.footerRedraw.bind(this));
15451
15452
if(this.table.options.paginationAddRow == "page"){
15453
this.subscribe("row-adding-position", this.rowAddingPosition.bind(this));
15454
}
15455
15456
if(this.table.options.paginationMode === "remote"){
15457
this.subscribe("data-params", this.remotePageParams.bind(this));
15458
this.subscribe("data-loaded", this._parseRemoteData.bind(this));
15459
}
15460
15461
if(this.table.options.progressiveLoad){
15462
console.error("Progressive Load Error - Pagination and progressive load cannot be used at the same time");
15463
}
15464
15465
this.registerDisplayHandler(this.restOnRenderBefore.bind(this), 40);
15466
this.registerDisplayHandler(this.getRows.bind(this), 50);
15467
15468
this.createElements();
15469
this.initializePageCounter();
15470
this.initializePaginator();
15471
}else if(this.table.options.progressiveLoad){
15472
this.subscribe("data-params", this.remotePageParams.bind(this));
15473
this.subscribe("data-loaded", this._parseRemoteData.bind(this));
15474
this.subscribe("table-built", this.calculatePageSizes.bind(this));
15475
this.subscribe("data-processed", this.initialLoadComplete.bind(this));
15476
15477
this.initializeProgressive(this.table.options.progressiveLoad);
15478
15479
if(this.table.options.progressiveLoad === "scroll"){
15480
this.subscribe("scroll-vertical", this.scrollVertical.bind(this));
15481
}
15482
}
15483
}
15484
15485
rowAddingPosition(row, top){
15486
var rowManager = this.table.rowManager,
15487
displayRows = rowManager.getDisplayRows(),
15488
index;
15489
15490
if(top){
15491
if(displayRows.length){
15492
index = displayRows[0];
15493
}else {
15494
if(rowManager.activeRows.length){
15495
index = rowManager.activeRows[rowManager.activeRows.length-1];
15496
top = false;
15497
}
15498
}
15499
}else {
15500
if(displayRows.length){
15501
index = displayRows[displayRows.length - 1];
15502
top = displayRows.length < this.size ? false : true;
15503
}
15504
}
15505
15506
return {index, top};
15507
}
15508
15509
calculatePageSizes(){
15510
var testElRow, testElCell;
15511
15512
if(this.table.options.paginationSize){
15513
this.size = this.table.options.paginationSize;
15514
}else {
15515
testElRow = document.createElement("div");
15516
testElRow.classList.add("tabulator-row");
15517
testElRow.style.visibility = "hidden";
15518
15519
testElCell = document.createElement("div");
15520
testElCell.classList.add("tabulator-cell");
15521
testElCell.innerHTML = "Page Row Test";
15522
15523
testElRow.appendChild(testElCell);
15524
15525
this.table.rowManager.getTableElement().appendChild(testElRow);
15526
15527
this.size = Math.floor(this.table.rowManager.getElement().clientHeight / testElRow.offsetHeight);
15528
15529
this.table.rowManager.getTableElement().removeChild(testElRow);
15530
}
15531
15532
this.dispatchExternal("pageSizeChanged", this.size);
15533
15534
this.generatePageSizeSelectList();
15535
}
15536
15537
initialLoadComplete(){
15538
this.initialLoad = false;
15539
}
15540
15541
remotePageParams(data, config, silent, params){
15542
if(!this.initialLoad){
15543
if((this.progressiveLoad && !silent) || (!this.progressiveLoad && !this.dataChanging)){
15544
this.reset(true);
15545
}
15546
}
15547
15548
//configure request params
15549
params.page = this.page;
15550
15551
//set page size if defined
15552
if(this.size){
15553
params.size = this.size;
15554
}
15555
15556
return params;
15557
}
15558
15559
///////////////////////////////////
15560
///////// Table Functions /////////
15561
///////////////////////////////////
15562
15563
userSetPageToRow(row){
15564
if(this.table.options.pagination){
15565
row = this.rowManager.findRow(row);
15566
15567
if(row){
15568
return this.setPageToRow(row);
15569
}
15570
}
15571
15572
return Promise.reject();
15573
}
15574
15575
userSetPageSize(size){
15576
if(this.table.options.pagination){
15577
this.setPageSize(size);
15578
return this.setPage(1);
15579
}else {
15580
return false;
15581
}
15582
}
15583
///////////////////////////////////
15584
///////// Internal Logic //////////
15585
///////////////////////////////////
15586
15587
scrollVertical(top, dir){
15588
var element, diff, margin;
15589
if(!dir && !this.table.dataLoader.loading){
15590
element = this.table.rowManager.getElement();
15591
diff = element.scrollHeight - element.clientHeight - top;
15592
margin = this.table.options.progressiveLoadScrollMargin || (element.clientHeight * 2);
15593
15594
if(diff < margin){
15595
this.nextPage()
15596
.catch(() => {}); //consume the exception thrown when on the last page
15597
}
15598
}
15599
}
15600
15601
restOnRenderBefore(rows, renderInPosition){
15602
if(!renderInPosition){
15603
if(this.mode === "local"){
15604
this.reset();
15605
}
15606
}
15607
15608
return rows;
15609
}
15610
15611
rowsUpdated(){
15612
this.refreshData(true, "all");
15613
}
15614
15615
createElements(){
15616
var button;
15617
15618
this.element = document.createElement("span");
15619
this.element.classList.add("tabulator-paginator");
15620
15621
this.pagesElement = document.createElement("span");
15622
this.pagesElement.classList.add("tabulator-pages");
15623
15624
button = document.createElement("button");
15625
button.classList.add("tabulator-page");
15626
button.setAttribute("type", "button");
15627
button.setAttribute("role", "button");
15628
button.setAttribute("aria-label", "");
15629
button.setAttribute("title", "");
15630
15631
this.firstBut = button.cloneNode(true);
15632
this.firstBut.setAttribute("data-page", "first");
15633
15634
this.prevBut = button.cloneNode(true);
15635
this.prevBut.setAttribute("data-page", "prev");
15636
15637
this.nextBut = button.cloneNode(true);
15638
this.nextBut.setAttribute("data-page", "next");
15639
15640
this.lastBut = button.cloneNode(true);
15641
this.lastBut.setAttribute("data-page", "last");
15642
15643
if(this.table.options.paginationSizeSelector){
15644
this.pageSizeSelect = document.createElement("select");
15645
this.pageSizeSelect.classList.add("tabulator-page-size");
15646
}
15647
}
15648
15649
generatePageSizeSelectList(){
15650
var pageSizes = [];
15651
15652
if(this.pageSizeSelect){
15653
15654
if(Array.isArray(this.table.options.paginationSizeSelector)){
15655
pageSizes = this.table.options.paginationSizeSelector;
15656
this.pageSizes = pageSizes;
15657
15658
if(this.pageSizes.indexOf(this.size) == -1){
15659
pageSizes.unshift(this.size);
15660
}
15661
}else {
15662
15663
if(this.pageSizes.indexOf(this.size) == -1){
15664
pageSizes = [];
15665
15666
for (let i = 1; i < 5; i++){
15667
pageSizes.push(this.size * i);
15668
}
15669
15670
this.pageSizes = pageSizes;
15671
}else {
15672
pageSizes = this.pageSizes;
15673
}
15674
}
15675
15676
while(this.pageSizeSelect.firstChild) this.pageSizeSelect.removeChild(this.pageSizeSelect.firstChild);
15677
15678
pageSizes.forEach((item) => {
15679
var itemEl = document.createElement("option");
15680
itemEl.value = item;
15681
15682
if(item === true){
15683
this.langBind("pagination|all", function(value){
15684
itemEl.innerHTML = value;
15685
});
15686
}else {
15687
itemEl.innerHTML = item;
15688
}
15689
15690
15691
15692
this.pageSizeSelect.appendChild(itemEl);
15693
});
15694
15695
this.pageSizeSelect.value = this.size;
15696
}
15697
}
15698
15699
initializePageCounter(){
15700
var counter = this.table.options.paginationCounter,
15701
pageCounter = null;
15702
15703
if(counter){
15704
if(typeof counter === "function"){
15705
pageCounter = counter;
15706
}else {
15707
pageCounter = Page.pageCounters[counter];
15708
}
15709
15710
if(pageCounter){
15711
this.pageCounter = pageCounter;
15712
15713
this.pageCounterElement = document.createElement("span");
15714
this.pageCounterElement.classList.add("tabulator-page-counter");
15715
}else {
15716
console.warn("Pagination Error - No such page counter found: ", counter);
15717
}
15718
}
15719
}
15720
15721
//setup pagination
15722
initializePaginator(hidden){
15723
var pageSelectLabel, paginationCounterHolder;
15724
15725
if(!hidden){
15726
//build pagination element
15727
15728
//bind localizations
15729
this.langBind("pagination|first", (value) => {
15730
this.firstBut.innerHTML = value;
15731
});
15732
15733
this.langBind("pagination|first_title", (value) => {
15734
this.firstBut.setAttribute("aria-label", value);
15735
this.firstBut.setAttribute("title", value);
15736
});
15737
15738
this.langBind("pagination|prev", (value) => {
15739
this.prevBut.innerHTML = value;
15740
});
15741
15742
this.langBind("pagination|prev_title", (value) => {
15743
this.prevBut.setAttribute("aria-label", value);
15744
this.prevBut.setAttribute("title", value);
15745
});
15746
15747
this.langBind("pagination|next", (value) => {
15748
this.nextBut.innerHTML = value;
15749
});
15750
15751
this.langBind("pagination|next_title", (value) => {
15752
this.nextBut.setAttribute("aria-label", value);
15753
this.nextBut.setAttribute("title", value);
15754
});
15755
15756
this.langBind("pagination|last", (value) => {
15757
this.lastBut.innerHTML = value;
15758
});
15759
15760
this.langBind("pagination|last_title", (value) => {
15761
this.lastBut.setAttribute("aria-label", value);
15762
this.lastBut.setAttribute("title", value);
15763
});
15764
15765
//click bindings
15766
this.firstBut.addEventListener("click", () => {
15767
this.setPage(1);
15768
});
15769
15770
this.prevBut.addEventListener("click", () => {
15771
this.previousPage();
15772
});
15773
15774
this.nextBut.addEventListener("click", () => {
15775
this.nextPage();
15776
});
15777
15778
this.lastBut.addEventListener("click", () => {
15779
this.setPage(this.max);
15780
});
15781
15782
if(this.table.options.paginationElement){
15783
this.element = this.table.options.paginationElement;
15784
}
15785
15786
if(this.pageSizeSelect){
15787
pageSelectLabel = document.createElement("label");
15788
15789
this.langBind("pagination|page_size", (value) => {
15790
this.pageSizeSelect.setAttribute("aria-label", value);
15791
this.pageSizeSelect.setAttribute("title", value);
15792
pageSelectLabel.innerHTML = value;
15793
});
15794
15795
this.element.appendChild(pageSelectLabel);
15796
this.element.appendChild(this.pageSizeSelect);
15797
15798
this.pageSizeSelect.addEventListener("change", (e) => {
15799
this.setPageSize(this.pageSizeSelect.value == "true" ? true : this.pageSizeSelect.value);
15800
this.setPage(1);
15801
});
15802
}
15803
15804
//append to DOM
15805
this.element.appendChild(this.firstBut);
15806
this.element.appendChild(this.prevBut);
15807
this.element.appendChild(this.pagesElement);
15808
this.element.appendChild(this.nextBut);
15809
this.element.appendChild(this.lastBut);
15810
15811
if(!this.table.options.paginationElement){
15812
if(this.table.options.paginationCounter){
15813
15814
if(this.table.options.paginationCounterElement){
15815
if(this.table.options.paginationCounterElement instanceof HTMLElement){
15816
this.table.options.paginationCounterElement.appendChild(this.pageCounterElement);
15817
}else if(typeof this.table.options.paginationCounterElement === "string"){
15818
paginationCounterHolder = document.querySelector(this.table.options.paginationCounterElement);
15819
15820
if(paginationCounterHolder){
15821
paginationCounterHolder.appendChild(this.pageCounterElement);
15822
}else {
15823
console.warn("Pagination Error - Unable to find element matching paginationCounterElement selector:", this.table.options.paginationCounterElement);
15824
}
15825
}
15826
}else {
15827
this.footerAppend(this.pageCounterElement);
15828
}
15829
15830
}
15831
15832
this.footerAppend(this.element);
15833
}
15834
15835
this.page = this.table.options.paginationInitialPage;
15836
this.count = this.table.options.paginationButtonCount;
15837
}
15838
15839
//set default values
15840
this.mode = this.table.options.paginationMode;
15841
}
15842
15843
initializeProgressive(mode){
15844
this.initializePaginator(true);
15845
this.mode = "progressive_" + mode;
15846
this.progressiveLoad = true;
15847
}
15848
15849
trackChanges(){
15850
this.dispatch("page-changed");
15851
}
15852
15853
//calculate maximum page from number of rows
15854
setMaxRows(rowCount){
15855
if(!rowCount){
15856
this.max = 1;
15857
}else {
15858
this.max = this.size === true ? 1 : Math.ceil(rowCount/this.size);
15859
}
15860
15861
if(this.page > this.max){
15862
this.page = this.max;
15863
}
15864
}
15865
15866
//reset to first page without triggering action
15867
reset(force){
15868
if(!this.initialLoad){
15869
if(this.mode == "local" || force){
15870
this.page = 1;
15871
this.trackChanges();
15872
}
15873
}
15874
}
15875
15876
//set the maximum page
15877
setMaxPage(max){
15878
15879
max = parseInt(max);
15880
15881
this.max = max || 1;
15882
15883
if(this.page > this.max){
15884
this.page = this.max;
15885
this.trigger();
15886
}
15887
}
15888
15889
//set current page number
15890
setPage(page){
15891
switch(page){
15892
case "first":
15893
return this.setPage(1);
15894
15895
case "prev":
15896
return this.previousPage();
15897
15898
case "next":
15899
return this.nextPage();
15900
15901
case "last":
15902
return this.setPage(this.max);
15903
}
15904
15905
page = parseInt(page);
15906
15907
if((page > 0 && page <= this.max) || this.mode !== "local"){
15908
this.page = page;
15909
15910
this.trackChanges();
15911
15912
return this.trigger();
15913
}else {
15914
console.warn("Pagination Error - Requested page is out of range of 1 - " + this.max + ":", page);
15915
return Promise.reject();
15916
}
15917
}
15918
15919
setPageToRow(row){
15920
var rows = this.displayRows(-1);
15921
var index = rows.indexOf(row);
15922
15923
if(index > -1){
15924
var page = this.size === true ? 1 : Math.ceil((index + 1) / this.size);
15925
15926
return this.setPage(page);
15927
}else {
15928
console.warn("Pagination Error - Requested row is not visible");
15929
return Promise.reject();
15930
}
15931
}
15932
15933
setPageSize(size){
15934
if(size !== true){
15935
size = parseInt(size);
15936
}
15937
15938
if(size > 0){
15939
this.size = size;
15940
this.dispatchExternal("pageSizeChanged", size);
15941
}
15942
15943
if(this.pageSizeSelect){
15944
// this.pageSizeSelect.value = size;
15945
this.generatePageSizeSelectList();
15946
}
15947
15948
this.trackChanges();
15949
}
15950
15951
_setPageCounter(totalRows, size, currentRow){
15952
var content;
15953
15954
if(this.pageCounter){
15955
15956
if(this.mode === "remote"){
15957
size = this.size;
15958
currentRow = ((this.page - 1) * this.size) + 1;
15959
totalRows = this.remoteRowCountEstimate;
15960
}
15961
15962
content = this.pageCounter.call(this, size, currentRow, this.page, totalRows, this.max);
15963
15964
switch(typeof content){
15965
case "object":
15966
if(content instanceof Node){
15967
15968
//clear previous cell contents
15969
while(this.pageCounterElement.firstChild) this.pageCounterElement.removeChild(this.pageCounterElement.firstChild);
15970
15971
this.pageCounterElement.appendChild(content);
15972
}else {
15973
this.pageCounterElement.innerHTML = "";
15974
15975
if(content != null){
15976
console.warn("Page Counter Error - Page Counter has returned a type of object, the only valid page counter object return is an instance of Node, the page counter returned:", content);
15977
}
15978
}
15979
break;
15980
case "undefined":
15981
this.pageCounterElement.innerHTML = "";
15982
break;
15983
default:
15984
this.pageCounterElement.innerHTML = content;
15985
}
15986
}
15987
}
15988
15989
//setup the pagination buttons
15990
_setPageButtons(){
15991
let leftSize = Math.floor((this.count-1) / 2);
15992
let rightSize = Math.ceil((this.count-1) / 2);
15993
let min = this.max - this.page + leftSize + 1 < this.count ? this.max-this.count+1: Math.max(this.page-leftSize,1);
15994
let max = this.page <= rightSize? Math.min(this.count, this.max) :Math.min(this.page+rightSize, this.max);
15995
15996
while(this.pagesElement.firstChild) this.pagesElement.removeChild(this.pagesElement.firstChild);
15997
15998
if(this.page == 1){
15999
this.firstBut.disabled = true;
16000
this.prevBut.disabled = true;
16001
}else {
16002
this.firstBut.disabled = false;
16003
this.prevBut.disabled = false;
16004
}
16005
16006
if(this.page == this.max){
16007
this.lastBut.disabled = true;
16008
this.nextBut.disabled = true;
16009
}else {
16010
this.lastBut.disabled = false;
16011
this.nextBut.disabled = false;
16012
}
16013
16014
for(let i = min; i <= max; i++){
16015
if(i>0 && i <= this.max){
16016
this.pagesElement.appendChild(this._generatePageButton(i));
16017
}
16018
}
16019
16020
this.footerRedraw();
16021
}
16022
16023
_generatePageButton(page){
16024
var button = document.createElement("button");
16025
16026
button.classList.add("tabulator-page");
16027
if(page == this.page){
16028
button.classList.add("active");
16029
}
16030
16031
button.setAttribute("type", "button");
16032
button.setAttribute("role", "button");
16033
16034
this.langBind("pagination|page_title", (value) => {
16035
button.setAttribute("aria-label", value + " " + page);
16036
button.setAttribute("title", value + " " + page);
16037
});
16038
16039
button.setAttribute("data-page", page);
16040
button.textContent = page;
16041
16042
button.addEventListener("click", (e) => {
16043
this.setPage(page);
16044
});
16045
16046
return button;
16047
}
16048
16049
//previous page
16050
previousPage(){
16051
if(this.page > 1){
16052
this.page--;
16053
16054
this.trackChanges();
16055
16056
return this.trigger();
16057
16058
}else {
16059
console.warn("Pagination Error - Previous page would be less than page 1:", 0);
16060
return Promise.reject();
16061
}
16062
}
16063
16064
//next page
16065
nextPage(){
16066
if(this.page < this.max){
16067
this.page++;
16068
16069
this.trackChanges();
16070
16071
return this.trigger();
16072
16073
}else {
16074
if(!this.progressiveLoad){
16075
console.warn("Pagination Error - Next page would be greater than maximum page of " + this.max + ":", this.max + 1);
16076
}
16077
return Promise.reject();
16078
}
16079
}
16080
16081
//return current page number
16082
getPage(){
16083
return this.page;
16084
}
16085
16086
//return max page number
16087
getPageMax(){
16088
return this.max;
16089
}
16090
16091
getPageSize(size){
16092
return this.size;
16093
}
16094
16095
getMode(){
16096
return this.mode;
16097
}
16098
16099
//return appropriate rows for current page
16100
getRows(data){
16101
var actualRowPageSize = 0,
16102
output, start, end, actualStartRow;
16103
16104
var actualRows = data.filter((row) => {
16105
return row.type === "row";
16106
});
16107
16108
if(this.mode == "local"){
16109
output = [];
16110
16111
this.setMaxRows(data.length);
16112
16113
if(this.size === true){
16114
start = 0;
16115
end = data.length;
16116
}else {
16117
start = this.size * (this.page - 1);
16118
end = start + parseInt(this.size);
16119
}
16120
16121
this._setPageButtons();
16122
16123
for(let i = start; i < end; i++){
16124
let row = data[i];
16125
16126
if(row){
16127
output.push(row);
16128
16129
if(row.type === "row"){
16130
if(!actualStartRow){
16131
actualStartRow = row;
16132
}
16133
16134
actualRowPageSize++;
16135
}
16136
}
16137
}
16138
16139
this._setPageCounter(actualRows.length, actualRowPageSize, actualStartRow ? (actualRows.indexOf(actualStartRow) + 1) : 0);
16140
16141
return output;
16142
}else {
16143
this._setPageButtons();
16144
this._setPageCounter(actualRows.length);
16145
16146
return data.slice(0);
16147
}
16148
}
16149
16150
trigger(){
16151
var left;
16152
16153
switch(this.mode){
16154
case "local":
16155
left = this.table.rowManager.scrollLeft;
16156
16157
this.refreshData();
16158
this.table.rowManager.scrollHorizontal(left);
16159
16160
this.dispatchExternal("pageLoaded", this.getPage());
16161
16162
return Promise.resolve();
16163
16164
case "remote":
16165
this.dataChanging = true;
16166
return this.reloadData(null)
16167
.finally(() => {
16168
this.dataChanging = false;
16169
});
16170
16171
case "progressive_load":
16172
case "progressive_scroll":
16173
return this.reloadData(null, true);
16174
16175
default:
16176
console.warn("Pagination Error - no such pagination mode:", this.mode);
16177
return Promise.reject();
16178
}
16179
}
16180
16181
_parseRemoteData(data){
16182
var margin;
16183
16184
if(typeof data.last_page === "undefined"){
16185
console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").last_page || "last_page") + "' property");
16186
}
16187
16188
if(data.data){
16189
this.max = parseInt(data.last_page) || 1;
16190
16191
this.remoteRowCountEstimate = typeof data.last_row !== "undefined" ? data.last_row : (data.last_page * this.size - (this.page == data.last_page ? (this.size - data.data.length) : 0));
16192
16193
if(this.progressiveLoad){
16194
switch(this.mode){
16195
case "progressive_load":
16196
16197
if(this.page == 1){
16198
this.table.rowManager.setData(data.data, false, this.page == 1);
16199
}else {
16200
this.table.rowManager.addRows(data.data);
16201
}
16202
16203
if(this.page < this.max){
16204
setTimeout(() => {
16205
this.nextPage();
16206
}, this.table.options.progressiveLoadDelay);
16207
}
16208
break;
16209
16210
case "progressive_scroll":
16211
data = this.page === 1 ? data.data : this.table.rowManager.getData().concat(data.data);
16212
16213
this.table.rowManager.setData(data, this.page !== 1, this.page == 1);
16214
16215
margin = this.table.options.progressiveLoadScrollMargin || (this.table.rowManager.element.clientHeight * 2);
16216
16217
if(this.table.rowManager.element.scrollHeight <= (this.table.rowManager.element.clientHeight + margin)){
16218
if(this.page < this.max){
16219
setTimeout(() => {
16220
this.nextPage();
16221
});
16222
}
16223
}
16224
break;
16225
}
16226
16227
return false;
16228
}else {
16229
// left = this.table.rowManager.scrollLeft;
16230
this.dispatchExternal("pageLoaded", this.getPage());
16231
// this.table.rowManager.scrollHorizontal(left);
16232
// this.table.columnManager.scrollHorizontal(left);
16233
}
16234
16235
}else {
16236
console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").data || "data") + "' property");
16237
}
16238
16239
return data.data;
16240
}
16241
16242
//handle the footer element being redrawn
16243
footerRedraw(){
16244
var footer = this.table.footerManager.containerElement;
16245
16246
if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){
16247
this.pagesElement.style.display = 'none';
16248
}else {
16249
this.pagesElement.style.display = '';
16250
16251
if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){
16252
this.pagesElement.style.display = 'none';
16253
}
16254
}
16255
}
16256
}
16257
16258
Page.moduleName = "page";
16259
16260
//load defaults
16261
Page.pageCounters = defaultPageCounters;
16262
16263
// read persistance information from storage
16264
var defaultReaders = {
16265
local:function(id, type){
16266
var data = localStorage.getItem(id + "-" + type);
16267
16268
return data ? JSON.parse(data) : false;
16269
},
16270
cookie:function(id, type){
16271
var cookie = document.cookie,
16272
key = id + "-" + type,
16273
cookiePos = cookie.indexOf(key + "="),
16274
end, data;
16275
16276
//if cookie exists, decode and load column data into tabulator
16277
if(cookiePos > -1){
16278
cookie = cookie.slice(cookiePos);
16279
16280
end = cookie.indexOf(";");
16281
16282
if(end > -1){
16283
cookie = cookie.slice(0, end);
16284
}
16285
16286
data = cookie.replace(key + "=", "");
16287
}
16288
16289
return data ? JSON.parse(data) : false;
16290
}
16291
};
16292
16293
//write persistence information to storage
16294
var defaultWriters = {
16295
local:function(id, type, data){
16296
localStorage.setItem(id + "-" + type, JSON.stringify(data));
16297
},
16298
cookie:function(id, type, data){
16299
var expireDate = new Date();
16300
16301
expireDate.setDate(expireDate.getDate() + 10000);
16302
16303
document.cookie = id + "-" + type + "=" + JSON.stringify(data) + "; expires=" + expireDate.toUTCString();
16304
}
16305
};
16306
16307
class Persistence extends Module{
16308
16309
constructor(table){
16310
super(table);
16311
16312
this.mode = "";
16313
this.id = "";
16314
// this.persistProps = ["field", "width", "visible"];
16315
this.defWatcherBlock = false;
16316
this.config = {};
16317
this.readFunc = false;
16318
this.writeFunc = false;
16319
16320
this.registerTableOption("persistence", false);
16321
this.registerTableOption("persistenceID", ""); //key for persistent storage
16322
this.registerTableOption("persistenceMode", true); //mode for storing persistence information
16323
this.registerTableOption("persistenceReaderFunc", false); //function for handling persistence data reading
16324
this.registerTableOption("persistenceWriterFunc", false); //function for handling persistence data writing
16325
}
16326
16327
// Test for whether localStorage is available for use.
16328
localStorageTest() {
16329
var testKey = "_tabulator_test";
16330
16331
try {
16332
window.localStorage.setItem( testKey, testKey);
16333
window.localStorage.removeItem( testKey );
16334
return true;
16335
} catch(e) {
16336
return false;
16337
}
16338
}
16339
16340
//setup parameters
16341
initialize(){
16342
if(this.table.options.persistence){
16343
//determine persistent layout storage type
16344
var mode = this.table.options.persistenceMode,
16345
id = this.table.options.persistenceID,
16346
retrievedData;
16347
16348
this.mode = mode !== true ? mode : (this.localStorageTest() ? "local" : "cookie");
16349
16350
if(this.table.options.persistenceReaderFunc){
16351
if(typeof this.table.options.persistenceReaderFunc === "function"){
16352
this.readFunc = this.table.options.persistenceReaderFunc;
16353
}else {
16354
if(Persistence.readers[this.table.options.persistenceReaderFunc]){
16355
this.readFunc = Persistence.readers[this.table.options.persistenceReaderFunc];
16356
}else {
16357
console.warn("Persistence Read Error - invalid reader set", this.table.options.persistenceReaderFunc);
16358
}
16359
}
16360
}else {
16361
if(Persistence.readers[this.mode]){
16362
this.readFunc = Persistence.readers[this.mode];
16363
}else {
16364
console.warn("Persistence Read Error - invalid reader set", this.mode);
16365
}
16366
}
16367
16368
if(this.table.options.persistenceWriterFunc){
16369
if(typeof this.table.options.persistenceWriterFunc === "function"){
16370
this.writeFunc = this.table.options.persistenceWriterFunc;
16371
}else {
16372
if(Persistence.writers[this.table.options.persistenceWriterFunc]){
16373
this.writeFunc = Persistence.writers[this.table.options.persistenceWriterFunc];
16374
}else {
16375
console.warn("Persistence Write Error - invalid reader set", this.table.options.persistenceWriterFunc);
16376
}
16377
}
16378
}else {
16379
if(Persistence.writers[this.mode]){
16380
this.writeFunc = Persistence.writers[this.mode];
16381
}else {
16382
console.warn("Persistence Write Error - invalid writer set", this.mode);
16383
}
16384
}
16385
16386
//set storage tag
16387
this.id = "tabulator-" + (id || (this.table.element.getAttribute("id") || ""));
16388
16389
this.config = {
16390
sort:this.table.options.persistence === true || this.table.options.persistence.sort,
16391
filter:this.table.options.persistence === true || this.table.options.persistence.filter,
16392
headerFilter:this.table.options.persistence === true || this.table.options.persistence.headerFilter,
16393
group:this.table.options.persistence === true || this.table.options.persistence.group,
16394
page:this.table.options.persistence === true || this.table.options.persistence.page,
16395
columns:this.table.options.persistence === true ? ["title", "width", "visible"] : this.table.options.persistence.columns,
16396
};
16397
16398
//load pagination data if needed
16399
if(this.config.page){
16400
retrievedData = this.retrieveData("page");
16401
16402
if(retrievedData){
16403
if(typeof retrievedData.paginationSize !== "undefined" && (this.config.page === true || this.config.page.size)){
16404
this.table.options.paginationSize = retrievedData.paginationSize;
16405
}
16406
16407
if(typeof retrievedData.paginationInitialPage !== "undefined" && (this.config.page === true || this.config.page.page)){
16408
this.table.options.paginationInitialPage = retrievedData.paginationInitialPage;
16409
}
16410
}
16411
}
16412
16413
//load group data if needed
16414
if(this.config.group){
16415
retrievedData = this.retrieveData("group");
16416
16417
if(retrievedData){
16418
if(typeof retrievedData.groupBy !== "undefined" && (this.config.group === true || this.config.group.groupBy)){
16419
this.table.options.groupBy = retrievedData.groupBy;
16420
}
16421
if(typeof retrievedData.groupStartOpen !== "undefined" && (this.config.group === true || this.config.group.groupStartOpen)){
16422
this.table.options.groupStartOpen = retrievedData.groupStartOpen;
16423
}
16424
if(typeof retrievedData.groupHeader !== "undefined" && (this.config.group === true || this.config.group.groupHeader)){
16425
this.table.options.groupHeader = retrievedData.groupHeader;
16426
}
16427
}
16428
}
16429
16430
if(this.config.columns){
16431
this.table.options.columns = this.load("columns", this.table.options.columns);
16432
this.subscribe("column-init", this.initializeColumn.bind(this));
16433
this.subscribe("column-show", this.save.bind(this, "columns"));
16434
this.subscribe("column-hide", this.save.bind(this, "columns"));
16435
this.subscribe("column-moved", this.save.bind(this, "columns"));
16436
}
16437
16438
this.subscribe("table-built", this.tableBuilt.bind(this), 0);
16439
16440
this.subscribe("table-redraw", this.tableRedraw.bind(this));
16441
16442
this.subscribe("filter-changed", this.eventSave.bind(this, "filter"));
16443
this.subscribe("filter-changed", this.eventSave.bind(this, "headerFilter"));
16444
this.subscribe("sort-changed", this.eventSave.bind(this, "sort"));
16445
this.subscribe("group-changed", this.eventSave.bind(this, "group"));
16446
this.subscribe("page-changed", this.eventSave.bind(this, "page"));
16447
this.subscribe("column-resized", this.eventSave.bind(this, "columns"));
16448
this.subscribe("column-width", this.eventSave.bind(this, "columns"));
16449
this.subscribe("layout-refreshed", this.eventSave.bind(this, "columns"));
16450
}
16451
16452
this.registerTableFunction("getColumnLayout", this.getColumnLayout.bind(this));
16453
this.registerTableFunction("setColumnLayout", this.setColumnLayout.bind(this));
16454
}
16455
16456
eventSave(type){
16457
if(this.config[type]){
16458
this.save(type);
16459
}
16460
}
16461
16462
tableBuilt(){
16463
var sorters, filters, headerFilters;
16464
16465
if(this.config.sort){
16466
sorters = this.load("sort");
16467
16468
if(!sorters === false){
16469
this.table.options.initialSort = sorters;
16470
}
16471
}
16472
16473
if(this.config.filter){
16474
filters = this.load("filter");
16475
16476
if(!filters === false){
16477
this.table.options.initialFilter = filters;
16478
}
16479
}
16480
if(this.config.headerFilter){
16481
headerFilters = this.load("headerFilter");
16482
16483
if(!headerFilters === false){
16484
this.table.options.initialHeaderFilter = headerFilters;
16485
}
16486
}
16487
16488
}
16489
16490
tableRedraw(force){
16491
if(force && this.config.columns){
16492
this.save("columns");
16493
}
16494
}
16495
16496
///////////////////////////////////
16497
///////// Table Functions /////////
16498
///////////////////////////////////
16499
16500
getColumnLayout(){
16501
return this.parseColumns(this.table.columnManager.getColumns());
16502
}
16503
16504
setColumnLayout(layout){
16505
this.table.columnManager.setColumns(this.mergeDefinition(this.table.options.columns, layout));
16506
return true;
16507
}
16508
16509
///////////////////////////////////
16510
///////// Internal Logic //////////
16511
///////////////////////////////////
16512
16513
initializeColumn(column){
16514
var def, keys;
16515
16516
if(this.config.columns){
16517
this.defWatcherBlock = true;
16518
16519
def = column.getDefinition();
16520
16521
keys = this.config.columns === true ? Object.keys(def) : this.config.columns;
16522
16523
keys.forEach((key)=>{
16524
var props = Object.getOwnPropertyDescriptor(def, key);
16525
var value = def[key];
16526
16527
if(props){
16528
Object.defineProperty(def, key, {
16529
set: (newValue) => {
16530
value = newValue;
16531
16532
if(!this.defWatcherBlock){
16533
this.save("columns");
16534
}
16535
16536
if(props.set){
16537
props.set(newValue);
16538
}
16539
},
16540
get:() => {
16541
if(props.get){
16542
props.get();
16543
}
16544
return value;
16545
}
16546
});
16547
}
16548
});
16549
16550
this.defWatcherBlock = false;
16551
}
16552
}
16553
16554
//load saved definitions
16555
load(type, current){
16556
var data = this.retrieveData(type);
16557
16558
if(current){
16559
data = data ? this.mergeDefinition(current, data) : current;
16560
}
16561
16562
return data;
16563
}
16564
16565
//retrieve data from memory
16566
retrieveData(type){
16567
return this.readFunc ? this.readFunc(this.id, type) : false;
16568
}
16569
16570
//merge old and new column definitions
16571
mergeDefinition(oldCols, newCols){
16572
var output = [];
16573
16574
newCols = newCols || [];
16575
16576
newCols.forEach((column, to) => {
16577
var from = this._findColumn(oldCols, column),
16578
keys;
16579
16580
if(from){
16581
if(this.config.columns === true || this.config.columns == undefined){
16582
keys = Object.keys(from);
16583
keys.push("width");
16584
}else {
16585
keys = this.config.columns;
16586
}
16587
16588
keys.forEach((key)=>{
16589
if(key !== "columns" && typeof column[key] !== "undefined"){
16590
from[key] = column[key];
16591
}
16592
});
16593
16594
if(from.columns){
16595
from.columns = this.mergeDefinition(from.columns, column.columns);
16596
}
16597
16598
output.push(from);
16599
}
16600
});
16601
16602
oldCols.forEach((column, i) => {
16603
var from = this._findColumn(newCols, column);
16604
16605
if (!from) {
16606
if(output.length>i){
16607
output.splice(i, 0, column);
16608
}else {
16609
output.push(column);
16610
}
16611
}
16612
});
16613
16614
return output;
16615
}
16616
16617
//find matching columns
16618
_findColumn(columns, subject){
16619
var type = subject.columns ? "group" : (subject.field ? "field" : "object");
16620
16621
return columns.find(function(col){
16622
switch(type){
16623
case "group":
16624
return col.title === subject.title && col.columns.length === subject.columns.length;
16625
16626
case "field":
16627
return col.field === subject.field;
16628
16629
case "object":
16630
return col === subject;
16631
}
16632
});
16633
}
16634
16635
//save data
16636
save(type){
16637
var data = {};
16638
16639
switch(type){
16640
case "columns":
16641
data = this.parseColumns(this.table.columnManager.getColumns());
16642
break;
16643
16644
case "filter":
16645
data = this.table.modules.filter.getFilters();
16646
break;
16647
16648
case "headerFilter":
16649
data = this.table.modules.filter.getHeaderFilters();
16650
break;
16651
16652
case "sort":
16653
data = this.validateSorters(this.table.modules.sort.getSort());
16654
break;
16655
16656
case "group":
16657
data = this.getGroupConfig();
16658
break;
16659
16660
case "page":
16661
data = this.getPageConfig();
16662
break;
16663
}
16664
16665
if(this.writeFunc){
16666
this.writeFunc(this.id, type, data);
16667
}
16668
16669
}
16670
16671
//ensure sorters contain no function data
16672
validateSorters(data){
16673
data.forEach(function(item){
16674
item.column = item.field;
16675
delete item.field;
16676
});
16677
16678
return data;
16679
}
16680
16681
getGroupConfig(){
16682
var data = {};
16683
16684
if(this.config.group){
16685
if(this.config.group === true || this.config.group.groupBy){
16686
data.groupBy = this.table.options.groupBy;
16687
}
16688
16689
if(this.config.group === true || this.config.group.groupStartOpen){
16690
data.groupStartOpen = this.table.options.groupStartOpen;
16691
}
16692
16693
if(this.config.group === true || this.config.group.groupHeader){
16694
data.groupHeader = this.table.options.groupHeader;
16695
}
16696
}
16697
16698
return data;
16699
}
16700
16701
getPageConfig(){
16702
var data = {};
16703
16704
if(this.config.page){
16705
if(this.config.page === true || this.config.page.size){
16706
data.paginationSize = this.table.modules.page.getPageSize();
16707
}
16708
16709
if(this.config.page === true || this.config.page.page){
16710
data.paginationInitialPage = this.table.modules.page.getPage();
16711
}
16712
}
16713
16714
return data;
16715
}
16716
16717
16718
//parse columns for data to store
16719
parseColumns(columns){
16720
var definitions = [],
16721
excludedKeys = ["headerContextMenu", "headerMenu", "contextMenu", "clickMenu"];
16722
16723
columns.forEach((column) => {
16724
var defStore = {},
16725
colDef = column.getDefinition(),
16726
keys;
16727
16728
if(column.isGroup){
16729
defStore.title = colDef.title;
16730
defStore.columns = this.parseColumns(column.getColumns());
16731
}else {
16732
defStore.field = column.getField();
16733
16734
if(this.config.columns === true || this.config.columns == undefined){
16735
keys = Object.keys(colDef);
16736
keys.push("width");
16737
keys.push("visible");
16738
}else {
16739
keys = this.config.columns;
16740
}
16741
16742
keys.forEach((key)=>{
16743
switch(key){
16744
case "width":
16745
defStore.width = column.getWidth();
16746
break;
16747
case "visible":
16748
defStore.visible = column.visible;
16749
break;
16750
16751
default:
16752
if(typeof colDef[key] !== "function" && excludedKeys.indexOf(key) === -1){
16753
defStore[key] = colDef[key];
16754
}
16755
}
16756
});
16757
}
16758
16759
definitions.push(defStore);
16760
});
16761
16762
return definitions;
16763
}
16764
}
16765
16766
Persistence.moduleName = "persistence";
16767
16768
Persistence.moduleInitOrder = -10;
16769
16770
//load defaults
16771
Persistence.readers = defaultReaders;
16772
Persistence.writers = defaultWriters;
16773
16774
class Popup$1 extends Module{
16775
16776
constructor(table){
16777
super(table);
16778
16779
this.columnSubscribers = {};
16780
16781
this.registerTableOption("rowContextPopup", false);
16782
this.registerTableOption("rowClickPopup", false);
16783
this.registerTableOption("rowDblClickPopup", false);
16784
this.registerTableOption("groupContextPopup", false);
16785
this.registerTableOption("groupClickPopup", false);
16786
this.registerTableOption("groupDblClickPopup", false);
16787
16788
this.registerColumnOption("headerContextPopup");
16789
this.registerColumnOption("headerClickPopup");
16790
this.registerColumnOption("headerDblClickPopup");
16791
this.registerColumnOption("headerPopup");
16792
this.registerColumnOption("headerPopupIcon");
16793
this.registerColumnOption("contextPopup");
16794
this.registerColumnOption("clickPopup");
16795
this.registerColumnOption("dblClickPopup");
16796
16797
this.registerComponentFunction("cell", "popup", this._componentPopupCall.bind(this));
16798
this.registerComponentFunction("column", "popup", this._componentPopupCall.bind(this));
16799
this.registerComponentFunction("row", "popup", this._componentPopupCall.bind(this));
16800
this.registerComponentFunction("group", "popup", this._componentPopupCall.bind(this));
16801
16802
}
16803
16804
initialize(){
16805
this.initializeRowWatchers();
16806
this.initializeGroupWatchers();
16807
16808
this.subscribe("column-init", this.initializeColumn.bind(this));
16809
}
16810
16811
_componentPopupCall(component, contents, position){
16812
this.loadPopupEvent(contents, null, component, position);
16813
}
16814
16815
initializeRowWatchers(){
16816
if(this.table.options.rowContextPopup){
16817
this.subscribe("row-contextmenu", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup));
16818
this.table.on("rowTapHold", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup));
16819
}
16820
16821
if(this.table.options.rowClickPopup){
16822
this.subscribe("row-click", this.loadPopupEvent.bind(this, this.table.options.rowClickPopup));
16823
}
16824
16825
if(this.table.options.rowDblClickPopup){
16826
this.subscribe("row-dblclick", this.loadPopupEvent.bind(this, this.table.options.rowDblClickPopup));
16827
}
16828
}
16829
16830
initializeGroupWatchers(){
16831
if(this.table.options.groupContextPopup){
16832
this.subscribe("group-contextmenu", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup));
16833
this.table.on("groupTapHold", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup));
16834
}
16835
16836
if(this.table.options.groupClickPopup){
16837
this.subscribe("group-click", this.loadPopupEvent.bind(this, this.table.options.groupClickPopup));
16838
}
16839
16840
if(this.table.options.groupDblClickPopup){
16841
this.subscribe("group-dblclick", this.loadPopupEvent.bind(this, this.table.options.groupDblClickPopup));
16842
}
16843
}
16844
16845
initializeColumn(column){
16846
var def = column.definition;
16847
16848
//handle column events
16849
if(def.headerContextPopup && !this.columnSubscribers.headerContextPopup){
16850
this.columnSubscribers.headerContextPopup = this.loadPopupTableColumnEvent.bind(this, "headerContextPopup");
16851
this.subscribe("column-contextmenu", this.columnSubscribers.headerContextPopup);
16852
this.table.on("headerTapHold", this.loadPopupTableColumnEvent.bind(this, "headerContextPopup"));
16853
}
16854
16855
if(def.headerClickPopup && !this.columnSubscribers.headerClickPopup){
16856
this.columnSubscribers.headerClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerClickPopup");
16857
this.subscribe("column-click", this.columnSubscribers.headerClickPopup);
16858
16859
16860
}if(def.headerDblClickPopup && !this.columnSubscribers.headerDblClickPopup){
16861
this.columnSubscribers.headerDblClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerDblClickPopup");
16862
this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickPopup);
16863
}
16864
16865
if(def.headerPopup){
16866
this.initializeColumnHeaderPopup(column);
16867
}
16868
16869
//handle cell events
16870
if(def.contextPopup && !this.columnSubscribers.contextPopup){
16871
this.columnSubscribers.contextPopup = this.loadPopupTableCellEvent.bind(this, "contextPopup");
16872
this.subscribe("cell-contextmenu", this.columnSubscribers.contextPopup);
16873
this.table.on("cellTapHold", this.loadPopupTableCellEvent.bind(this, "contextPopup"));
16874
}
16875
16876
if(def.clickPopup && !this.columnSubscribers.clickPopup){
16877
this.columnSubscribers.clickPopup = this.loadPopupTableCellEvent.bind(this, "clickPopup");
16878
this.subscribe("cell-click", this.columnSubscribers.clickPopup);
16879
}
16880
16881
if(def.dblClickPopup && !this.columnSubscribers.dblClickPopup){
16882
this.columnSubscribers.dblClickPopup = this.loadPopupTableCellEvent.bind(this, "dblClickPopup");
16883
this.subscribe("cell-click", this.columnSubscribers.dblClickPopup);
16884
}
16885
}
16886
16887
initializeColumnHeaderPopup(column){
16888
var icon = column.definition.headerPopupIcon,
16889
headerPopupEl;
16890
16891
headerPopupEl = document.createElement("span");
16892
headerPopupEl.classList.add("tabulator-header-popup-button");
16893
16894
if(icon){
16895
if(typeof icon === "function"){
16896
icon = icon(column.getComponent());
16897
}
16898
16899
if(icon instanceof HTMLElement){
16900
headerPopupEl.appendChild(icon);
16901
}else {
16902
headerPopupEl.innerHTML = icon;
16903
}
16904
}else {
16905
headerPopupEl.innerHTML = "&vellip;";
16906
}
16907
16908
headerPopupEl.addEventListener("click", (e) => {
16909
e.stopPropagation();
16910
e.preventDefault();
16911
16912
this.loadPopupEvent(column.definition.headerPopup, e, column);
16913
});
16914
16915
column.titleElement.insertBefore(headerPopupEl, column.titleElement.firstChild);
16916
}
16917
16918
loadPopupTableCellEvent(option, e, cell){
16919
if(cell._cell){
16920
cell = cell._cell;
16921
}
16922
16923
if(cell.column.definition[option]){
16924
this.loadPopupEvent(cell.column.definition[option], e, cell);
16925
}
16926
}
16927
16928
loadPopupTableColumnEvent(option, e, column){
16929
if(column._column){
16930
column = column._column;
16931
}
16932
16933
if(column.definition[option]){
16934
this.loadPopupEvent(column.definition[option], e, column);
16935
}
16936
}
16937
16938
loadPopupEvent(contents, e, component, position){
16939
var renderedCallback;
16940
16941
function onRendered(callback){
16942
renderedCallback = callback;
16943
}
16944
16945
if(component._group){
16946
component = component._group;
16947
}else if(component._row){
16948
component = component._row;
16949
}
16950
16951
contents = typeof contents == "function" ? contents.call(this.table, e, component.getComponent(), onRendered) : contents;
16952
16953
this.loadPopup(e, component, contents, renderedCallback, position);
16954
}
16955
16956
loadPopup(e, component, contents, renderedCallback, position){
16957
var touch = !(e instanceof MouseEvent),
16958
contentsEl, popup;
16959
16960
if(contents instanceof HTMLElement){
16961
contentsEl = contents;
16962
}else {
16963
contentsEl = document.createElement("div");
16964
contentsEl.innerHTML = contents;
16965
}
16966
16967
contentsEl.classList.add("tabulator-popup");
16968
16969
contentsEl.addEventListener("click", (e) =>{
16970
e.stopPropagation();
16971
});
16972
16973
if(!touch){
16974
e.preventDefault();
16975
}
16976
16977
popup = this.popup(contentsEl);
16978
16979
if(typeof renderedCallback === "function"){
16980
popup.renderCallback(renderedCallback);
16981
}
16982
16983
if(e){
16984
popup.show(e);
16985
}else {
16986
popup.show(component.getElement(), position || "center");
16987
}
16988
16989
16990
popup.hideOnBlur(() => {
16991
this.dispatchExternal("popupClosed", component.getComponent());
16992
});
16993
16994
16995
16996
this.dispatchExternal("popupOpened", component.getComponent());
16997
}
16998
}
16999
17000
Popup$1.moduleName = "popup";
17001
17002
class Print extends Module{
17003
17004
constructor(table){
17005
super(table);
17006
17007
this.element = false;
17008
this.manualBlock = false;
17009
this.beforeprintEventHandler = null;
17010
this.afterprintEventHandler = null;
17011
17012
this.registerTableOption("printAsHtml", false); //enable print as html
17013
this.registerTableOption("printFormatter", false); //printing page formatter
17014
this.registerTableOption("printHeader", false); //page header contents
17015
this.registerTableOption("printFooter", false); //page footer contents
17016
this.registerTableOption("printStyled", true); //enable print as html styling
17017
this.registerTableOption("printRowRange", "visible"); //restrict print to visible rows only
17018
this.registerTableOption("printConfig", {}); //print config options
17019
17020
this.registerColumnOption("print");
17021
this.registerColumnOption("titlePrint");
17022
}
17023
17024
initialize(){
17025
if(this.table.options.printAsHtml){
17026
this.beforeprintEventHandler = this.replaceTable.bind(this);
17027
this.afterprintEventHandler = this.cleanup.bind(this);
17028
17029
window.addEventListener("beforeprint", this.beforeprintEventHandler );
17030
window.addEventListener("afterprint", this.afterprintEventHandler);
17031
this.subscribe("table-destroy", this.destroy.bind(this));
17032
}
17033
17034
this.registerTableFunction("print", this.printFullscreen.bind(this));
17035
}
17036
17037
destroy(){
17038
if(this.table.options.printAsHtml){
17039
window.removeEventListener( "beforeprint", this.beforeprintEventHandler );
17040
window.removeEventListener( "afterprint", this.afterprintEventHandler );
17041
}
17042
}
17043
17044
///////////////////////////////////
17045
///////// Table Functions /////////
17046
///////////////////////////////////
17047
17048
///////////////////////////////////
17049
///////// Internal Logic //////////
17050
///////////////////////////////////
17051
17052
replaceTable(){
17053
if(!this.manualBlock){
17054
this.element = document.createElement("div");
17055
this.element.classList.add("tabulator-print-table");
17056
17057
this.element.appendChild(this.table.modules.export.generateTable(this.table.options.printConfig, this.table.options.printStyled, this.table.options.printRowRange, "print"));
17058
17059
this.table.element.style.display = "none";
17060
17061
this.table.element.parentNode.insertBefore(this.element, this.table.element);
17062
}
17063
}
17064
17065
cleanup(){
17066
document.body.classList.remove("tabulator-print-fullscreen-hide");
17067
17068
if(this.element && this.element.parentNode){
17069
this.element.parentNode.removeChild(this.element);
17070
this.table.element.style.display = "";
17071
}
17072
}
17073
17074
printFullscreen(visible, style, config){
17075
var scrollX = window.scrollX,
17076
scrollY = window.scrollY,
17077
headerEl = document.createElement("div"),
17078
footerEl = document.createElement("div"),
17079
tableEl = this.table.modules.export.generateTable(typeof config != "undefined" ? config : this.table.options.printConfig, typeof style != "undefined" ? style : this.table.options.printStyled, visible || this.table.options.printRowRange, "print"),
17080
headerContent, footerContent;
17081
17082
this.manualBlock = true;
17083
17084
this.element = document.createElement("div");
17085
this.element.classList.add("tabulator-print-fullscreen");
17086
17087
if(this.table.options.printHeader){
17088
headerEl.classList.add("tabulator-print-header");
17089
17090
headerContent = typeof this.table.options.printHeader == "function" ? this.table.options.printHeader.call(this.table) : this.table.options.printHeader;
17091
17092
if(typeof headerContent == "string"){
17093
headerEl.innerHTML = headerContent;
17094
}else {
17095
headerEl.appendChild(headerContent);
17096
}
17097
17098
this.element.appendChild(headerEl);
17099
}
17100
17101
this.element.appendChild(tableEl);
17102
17103
if(this.table.options.printFooter){
17104
footerEl.classList.add("tabulator-print-footer");
17105
17106
footerContent = typeof this.table.options.printFooter == "function" ? this.table.options.printFooter.call(this.table) : this.table.options.printFooter;
17107
17108
17109
if(typeof footerContent == "string"){
17110
footerEl.innerHTML = footerContent;
17111
}else {
17112
footerEl.appendChild(footerContent);
17113
}
17114
17115
this.element.appendChild(footerEl);
17116
}
17117
17118
document.body.classList.add("tabulator-print-fullscreen-hide");
17119
document.body.appendChild(this.element);
17120
17121
if(this.table.options.printFormatter){
17122
this.table.options.printFormatter(this.element, tableEl);
17123
}
17124
17125
window.print();
17126
17127
this.cleanup();
17128
17129
window.scrollTo(scrollX, scrollY);
17130
17131
this.manualBlock = false;
17132
}
17133
}
17134
17135
Print.moduleName = "print";
17136
17137
class ReactiveData extends Module{
17138
17139
constructor(table){
17140
super(table);
17141
17142
this.data = false;
17143
this.blocked = false; //block reactivity while performing update
17144
this.origFuncs = {}; // hold original data array functions to allow replacement after data is done with
17145
this.currentVersion = 0;
17146
17147
this.registerTableOption("reactiveData", false); //enable data reactivity
17148
}
17149
17150
initialize(){
17151
if(this.table.options.reactiveData){
17152
this.subscribe("cell-value-save-before", this.block.bind(this, "cellsave"));
17153
this.subscribe("cell-value-save-after", this.unblock.bind(this, "cellsave"));
17154
this.subscribe("row-data-save-before", this.block.bind(this, "rowsave"));
17155
this.subscribe("row-data-save-after", this.unblock.bind(this, "rowsave"));
17156
this.subscribe("row-data-init-after", this.watchRow.bind(this));
17157
this.subscribe("data-processing", this.watchData.bind(this));
17158
this.subscribe("table-destroy", this.unwatchData.bind(this));
17159
}
17160
}
17161
17162
watchData(data){
17163
var self = this,
17164
version;
17165
17166
this.currentVersion ++;
17167
17168
version = this.currentVersion;
17169
17170
this.unwatchData();
17171
17172
this.data = data;
17173
17174
//override array push function
17175
this.origFuncs.push = data.push;
17176
17177
Object.defineProperty(this.data, "push", {
17178
enumerable: false,
17179
configurable: true,
17180
value: function(){
17181
var args = Array.from(arguments),
17182
result;
17183
17184
if(!self.blocked && version === self.currentVersion){
17185
self.block("data-push");
17186
17187
args.forEach((arg) => {
17188
self.table.rowManager.addRowActual(arg, false);
17189
});
17190
17191
result = self.origFuncs.push.apply(data, arguments);
17192
17193
self.unblock("data-push");
17194
}
17195
17196
return result;
17197
}
17198
});
17199
17200
//override array unshift function
17201
this.origFuncs.unshift = data.unshift;
17202
17203
Object.defineProperty(this.data, "unshift", {
17204
enumerable: false,
17205
configurable: true,
17206
value: function(){
17207
var args = Array.from(arguments),
17208
result;
17209
17210
if(!self.blocked && version === self.currentVersion){
17211
self.block("data-unshift");
17212
17213
args.forEach((arg) => {
17214
self.table.rowManager.addRowActual(arg, true);
17215
});
17216
17217
result = self.origFuncs.unshift.apply(data, arguments);
17218
17219
self.unblock("data-unshift");
17220
}
17221
17222
return result;
17223
}
17224
});
17225
17226
17227
//override array shift function
17228
this.origFuncs.shift = data.shift;
17229
17230
Object.defineProperty(this.data, "shift", {
17231
enumerable: false,
17232
configurable: true,
17233
value: function(){
17234
var row, result;
17235
17236
if(!self.blocked && version === self.currentVersion){
17237
self.block("data-shift");
17238
17239
if(self.data.length){
17240
row = self.table.rowManager.getRowFromDataObject(self.data[0]);
17241
17242
if(row){
17243
row.deleteActual();
17244
}
17245
}
17246
17247
result = self.origFuncs.shift.call(data);
17248
17249
self.unblock("data-shift");
17250
}
17251
17252
return result;
17253
}
17254
});
17255
17256
//override array pop function
17257
this.origFuncs.pop = data.pop;
17258
17259
Object.defineProperty(this.data, "pop", {
17260
enumerable: false,
17261
configurable: true,
17262
value: function(){
17263
var row, result;
17264
17265
if(!self.blocked && version === self.currentVersion){
17266
self.block("data-pop");
17267
17268
if(self.data.length){
17269
row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]);
17270
17271
if(row){
17272
row.deleteActual();
17273
}
17274
}
17275
17276
result = self.origFuncs.pop.call(data);
17277
17278
self.unblock("data-pop");
17279
}
17280
17281
return result;
17282
}
17283
});
17284
17285
17286
//override array splice function
17287
this.origFuncs.splice = data.splice;
17288
17289
Object.defineProperty(this.data, "splice", {
17290
enumerable: false,
17291
configurable: true,
17292
value: function(){
17293
var args = Array.from(arguments),
17294
start = args[0] < 0 ? data.length + args[0] : args[0],
17295
end = args[1],
17296
newRows = args[2] ? args.slice(2) : false,
17297
startRow, result;
17298
17299
if(!self.blocked && version === self.currentVersion){
17300
self.block("data-splice");
17301
//add new rows
17302
if(newRows){
17303
startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false;
17304
17305
if(startRow){
17306
newRows.forEach((rowData) => {
17307
self.table.rowManager.addRowActual(rowData, true, startRow, true);
17308
});
17309
}else {
17310
newRows = newRows.slice().reverse();
17311
17312
newRows.forEach((rowData) => {
17313
self.table.rowManager.addRowActual(rowData, true, false, true);
17314
});
17315
}
17316
}
17317
17318
//delete removed rows
17319
if(end !== 0){
17320
var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end);
17321
17322
oldRows.forEach((rowData, i) => {
17323
var row = self.table.rowManager.getRowFromDataObject(rowData);
17324
17325
if(row){
17326
row.deleteActual(i !== oldRows.length - 1);
17327
}
17328
});
17329
}
17330
17331
if(newRows || end !== 0){
17332
self.table.rowManager.reRenderInPosition();
17333
}
17334
17335
result = self.origFuncs.splice.apply(data, arguments);
17336
17337
self.unblock("data-splice");
17338
}
17339
17340
return result ;
17341
}
17342
});
17343
}
17344
17345
unwatchData(){
17346
if(this.data !== false){
17347
for(var key in this.origFuncs){
17348
Object.defineProperty(this.data, key, {
17349
enumerable: true,
17350
configurable:true,
17351
writable:true,
17352
value: this.origFuncs.key,
17353
});
17354
}
17355
}
17356
}
17357
17358
watchRow(row){
17359
var data = row.getData();
17360
17361
for(var key in data){
17362
this.watchKey(row, data, key);
17363
}
17364
17365
if(this.table.options.dataTree){
17366
this.watchTreeChildren(row);
17367
}
17368
}
17369
17370
watchTreeChildren (row){
17371
var self = this,
17372
childField = row.getData()[this.table.options.dataTreeChildField],
17373
origFuncs = {};
17374
17375
if(childField){
17376
17377
origFuncs.push = childField.push;
17378
17379
Object.defineProperty(childField, "push", {
17380
enumerable: false,
17381
configurable: true,
17382
value: () => {
17383
if(!self.blocked){
17384
self.block("tree-push");
17385
17386
var result = origFuncs.push.apply(childField, arguments);
17387
this.rebuildTree(row);
17388
17389
self.unblock("tree-push");
17390
}
17391
17392
return result;
17393
}
17394
});
17395
17396
origFuncs.unshift = childField.unshift;
17397
17398
Object.defineProperty(childField, "unshift", {
17399
enumerable: false,
17400
configurable: true,
17401
value: () => {
17402
if(!self.blocked){
17403
self.block("tree-unshift");
17404
17405
var result = origFuncs.unshift.apply(childField, arguments);
17406
this.rebuildTree(row);
17407
17408
self.unblock("tree-unshift");
17409
}
17410
17411
return result;
17412
}
17413
});
17414
17415
origFuncs.shift = childField.shift;
17416
17417
Object.defineProperty(childField, "shift", {
17418
enumerable: false,
17419
configurable: true,
17420
value: () => {
17421
if(!self.blocked){
17422
self.block("tree-shift");
17423
17424
var result = origFuncs.shift.call(childField);
17425
this.rebuildTree(row);
17426
17427
self.unblock("tree-shift");
17428
}
17429
17430
return result;
17431
}
17432
});
17433
17434
origFuncs.pop = childField.pop;
17435
17436
Object.defineProperty(childField, "pop", {
17437
enumerable: false,
17438
configurable: true,
17439
value: () => {
17440
if(!self.blocked){
17441
self.block("tree-pop");
17442
17443
var result = origFuncs.pop.call(childField);
17444
this.rebuildTree(row);
17445
17446
self.unblock("tree-pop");
17447
}
17448
17449
return result;
17450
}
17451
});
17452
17453
origFuncs.splice = childField.splice;
17454
17455
Object.defineProperty(childField, "splice", {
17456
enumerable: false,
17457
configurable: true,
17458
value: () => {
17459
if(!self.blocked){
17460
self.block("tree-splice");
17461
17462
var result = origFuncs.splice.apply(childField, arguments);
17463
this.rebuildTree(row);
17464
17465
self.unblock("tree-splice");
17466
}
17467
17468
return result;
17469
}
17470
});
17471
}
17472
}
17473
17474
rebuildTree(row){
17475
this.table.modules.dataTree.initializeRow(row);
17476
this.table.modules.dataTree.layoutRow(row);
17477
this.table.rowManager.refreshActiveData("tree", false, true);
17478
}
17479
17480
watchKey(row, data, key){
17481
var self = this,
17482
props = Object.getOwnPropertyDescriptor(data, key),
17483
value = data[key],
17484
version = this.currentVersion;
17485
17486
Object.defineProperty(data, key, {
17487
set: (newValue) => {
17488
value = newValue;
17489
if(!self.blocked && version === self.currentVersion){
17490
self.block("key");
17491
17492
var update = {};
17493
update[key] = newValue;
17494
row.updateData(update);
17495
17496
self.unblock("key");
17497
}
17498
17499
if(props.set){
17500
props.set(newValue);
17501
}
17502
},
17503
get:() => {
17504
17505
if(props.get){
17506
props.get();
17507
}
17508
17509
return value;
17510
}
17511
});
17512
}
17513
17514
unwatchRow(row){
17515
var data = row.getData();
17516
17517
for(var key in data){
17518
Object.defineProperty(data, key, {
17519
value:data[key],
17520
});
17521
}
17522
}
17523
17524
block(key){
17525
if(!this.blocked){
17526
this.blocked = key;
17527
}
17528
}
17529
17530
unblock(key){
17531
if(this.blocked === key){
17532
this.blocked = false;
17533
}
17534
}
17535
}
17536
17537
ReactiveData.moduleName = "reactiveData";
17538
17539
class ResizeColumns extends Module{
17540
17541
constructor(table){
17542
super(table);
17543
17544
this.startColumn = false;
17545
this.startX = false;
17546
this.startWidth = false;
17547
this.latestX = false;
17548
this.handle = null;
17549
this.initialNextColumn = null;
17550
this.nextColumn = null;
17551
17552
this.initialized = false;
17553
this.registerColumnOption("resizable", true);
17554
this.registerTableOption("resizableColumnFit", false);
17555
}
17556
17557
initialize(){
17558
this.subscribe("column-rendered", this.layoutColumnHeader.bind(this));
17559
}
17560
17561
initializeEventWatchers(){
17562
if(!this.initialized){
17563
17564
this.subscribe("cell-rendered", this.layoutCellHandles.bind(this));
17565
this.subscribe("cell-delete", this.deInitializeComponent.bind(this));
17566
17567
this.subscribe("cell-height", this.resizeHandle.bind(this));
17568
this.subscribe("column-moved", this.columnLayoutUpdated.bind(this));
17569
17570
this.subscribe("column-hide", this.deInitializeColumn.bind(this));
17571
this.subscribe("column-show", this.columnLayoutUpdated.bind(this));
17572
this.subscribe("column-width", this.columnWidthUpdated.bind(this));
17573
17574
this.subscribe("column-delete", this.deInitializeComponent.bind(this));
17575
this.subscribe("column-height", this.resizeHandle.bind(this));
17576
17577
this.initialized = true;
17578
}
17579
}
17580
17581
17582
layoutCellHandles(cell){
17583
if(cell.row.type === "row"){
17584
this.deInitializeComponent(cell);
17585
this.initializeColumn("cell", cell, cell.column, cell.element);
17586
}
17587
}
17588
17589
layoutColumnHeader(column){
17590
if(column.definition.resizable){
17591
this.initializeEventWatchers();
17592
this.deInitializeComponent(column);
17593
this.initializeColumn("header", column, column, column.element);
17594
}
17595
}
17596
17597
columnLayoutUpdated(column){
17598
var prev = column.prevColumn();
17599
17600
this.reinitializeColumn(column);
17601
17602
if(prev){
17603
this.reinitializeColumn(prev);
17604
}
17605
}
17606
17607
columnWidthUpdated(column){
17608
if(column.modules.frozen){
17609
if(this.table.modules.frozenColumns.leftColumns.includes(column)){
17610
this.table.modules.frozenColumns.leftColumns.forEach((col) => {
17611
this.reinitializeColumn(col);
17612
});
17613
}else if(this.table.modules.frozenColumns.rightColumns.includes(column)){
17614
this.table.modules.frozenColumns.rightColumns.forEach((col) => {
17615
this.reinitializeColumn(col);
17616
});
17617
}
17618
}
17619
}
17620
17621
frozenColumnOffset(column){
17622
var offset = false;
17623
17624
if(column.modules.frozen){
17625
offset = column.modules.frozen.marginValue;
17626
17627
if(column.modules.frozen.position === "left"){
17628
offset += column.getWidth() - 3;
17629
}else {
17630
if(offset){
17631
offset -= 3;
17632
}
17633
}
17634
}
17635
17636
return offset !== false ? offset + "px" : false;
17637
}
17638
17639
reinitializeColumn(column){
17640
var frozenOffset = this.frozenColumnOffset(column);
17641
17642
column.cells.forEach((cell) => {
17643
if(cell.modules.resize && cell.modules.resize.handleEl){
17644
if(frozenOffset){
17645
cell.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset;
17646
cell.modules.resize.handleEl.style["z-index"] = 11;
17647
}
17648
17649
cell.element.after(cell.modules.resize.handleEl);
17650
}
17651
});
17652
17653
if(column.modules.resize && column.modules.resize.handleEl){
17654
if(frozenOffset){
17655
column.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset;
17656
}
17657
17658
column.element.after(column.modules.resize.handleEl);
17659
}
17660
}
17661
17662
initializeColumn(type, component, column, element){
17663
var self = this,
17664
variableHeight = false,
17665
mode = column.definition.resizable,
17666
config = {},
17667
nearestColumn = column.getLastColumn();
17668
17669
//set column resize mode
17670
if(type === "header"){
17671
variableHeight = column.definition.formatter == "textarea" || column.definition.variableHeight;
17672
config = {variableHeight:variableHeight};
17673
}
17674
17675
if((mode === true || mode == type) && this._checkResizability(nearestColumn)){
17676
17677
var handle = document.createElement('span');
17678
handle.className = "tabulator-col-resize-handle";
17679
17680
handle.addEventListener("click", function(e){
17681
e.stopPropagation();
17682
});
17683
17684
var handleDown = function(e){
17685
self.startColumn = column;
17686
self.initialNextColumn = self.nextColumn = nearestColumn.nextColumn();
17687
self._mouseDown(e, nearestColumn, handle);
17688
};
17689
17690
handle.addEventListener("mousedown", handleDown);
17691
handle.addEventListener("touchstart", handleDown, {passive: true});
17692
17693
//resize column on double click
17694
handle.addEventListener("dblclick", (e) => {
17695
var oldWidth = nearestColumn.getWidth();
17696
17697
e.stopPropagation();
17698
nearestColumn.reinitializeWidth(true);
17699
17700
if(oldWidth !== nearestColumn.getWidth()){
17701
self.dispatch("column-resized", nearestColumn);
17702
self.table.externalEvents.dispatch("columnResized", nearestColumn.getComponent());
17703
}
17704
});
17705
17706
if(column.modules.frozen){
17707
handle.style.position = "sticky";
17708
handle.style[column.modules.frozen.position] = this.frozenColumnOffset(column);
17709
}
17710
17711
config.handleEl = handle;
17712
17713
if(element.parentNode && column.visible){
17714
element.after(handle);
17715
}
17716
}
17717
17718
component.modules.resize = config;
17719
}
17720
17721
deInitializeColumn(column){
17722
this.deInitializeComponent(column);
17723
17724
column.cells.forEach((cell) => {
17725
this.deInitializeComponent(cell);
17726
});
17727
}
17728
17729
deInitializeComponent(component){
17730
var handleEl;
17731
17732
if(component.modules.resize){
17733
handleEl = component.modules.resize.handleEl;
17734
17735
if(handleEl && handleEl.parentElement){
17736
handleEl.parentElement.removeChild(handleEl);
17737
}
17738
}
17739
}
17740
17741
resizeHandle(component, height){
17742
if(component.modules.resize && component.modules.resize.handleEl){
17743
component.modules.resize.handleEl.style.height = height;
17744
}
17745
}
17746
17747
_checkResizability(column){
17748
return column.definition.resizable;
17749
}
17750
17751
_mouseDown(e, column, handle){
17752
var self = this;
17753
17754
self.table.element.classList.add("tabulator-block-select");
17755
17756
function mouseMove(e){
17757
var x = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX,
17758
startDiff = x - self.startX,
17759
moveDiff = x - self.latestX,
17760
blockedBefore, blockedAfter;
17761
17762
self.latestX = x;
17763
17764
if(self.table.rtl){
17765
startDiff = -startDiff;
17766
moveDiff = -moveDiff;
17767
}
17768
17769
blockedBefore = column.width == column.minWidth || column.width == column.maxWidth;
17770
17771
column.setWidth(self.startWidth + startDiff);
17772
17773
blockedAfter = column.width == column.minWidth || column.width == column.maxWidth;
17774
17775
if(moveDiff < 0){
17776
self.nextColumn = self.initialNextColumn;
17777
}
17778
17779
if(self.table.options.resizableColumnFit && self.nextColumn && !(blockedBefore && blockedAfter)){
17780
let colWidth = self.nextColumn.getWidth();
17781
17782
if(moveDiff > 0){
17783
if(colWidth <= self.nextColumn.minWidth){
17784
self.nextColumn = self.nextColumn.nextColumn();
17785
}
17786
}
17787
17788
if(self.nextColumn){
17789
self.nextColumn.setWidth(self.nextColumn.getWidth() - moveDiff);
17790
}
17791
}
17792
17793
self.table.columnManager.rerenderColumns(true);
17794
17795
if(!self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){
17796
column.checkCellHeights();
17797
}
17798
}
17799
17800
function mouseUp(e){
17801
17802
//block editor from taking action while resizing is taking place
17803
if(self.startColumn.modules.edit){
17804
self.startColumn.modules.edit.blocked = false;
17805
}
17806
17807
if(self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){
17808
column.checkCellHeights();
17809
}
17810
17811
document.body.removeEventListener("mouseup", mouseUp);
17812
document.body.removeEventListener("mousemove", mouseMove);
17813
17814
handle.removeEventListener("touchmove", mouseMove);
17815
handle.removeEventListener("touchend", mouseUp);
17816
17817
self.table.element.classList.remove("tabulator-block-select");
17818
17819
if(self.startWidth !== column.getWidth()){
17820
self.table.columnManager.verticalAlignHeaders();
17821
17822
self.dispatch("column-resized", column);
17823
self.table.externalEvents.dispatch("columnResized", column.getComponent());
17824
}
17825
}
17826
17827
e.stopPropagation(); //prevent resize from interfering with movable columns
17828
17829
//block editor from taking action while resizing is taking place
17830
if(self.startColumn.modules.edit){
17831
self.startColumn.modules.edit.blocked = true;
17832
}
17833
17834
self.startX = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX;
17835
self.latestX = self.startX;
17836
self.startWidth = column.getWidth();
17837
17838
document.body.addEventListener("mousemove", mouseMove);
17839
document.body.addEventListener("mouseup", mouseUp);
17840
handle.addEventListener("touchmove", mouseMove, {passive: true});
17841
handle.addEventListener("touchend", mouseUp);
17842
}
17843
}
17844
17845
ResizeColumns.moduleName = "resizeColumns";
17846
17847
class ResizeRows extends Module{
17848
17849
constructor(table){
17850
super(table);
17851
17852
this.startColumn = false;
17853
this.startY = false;
17854
this.startHeight = false;
17855
this.handle = null;
17856
this.prevHandle = null;
17857
17858
this.registerTableOption("resizableRows", false); //resizable rows
17859
}
17860
17861
initialize(){
17862
if(this.table.options.resizableRows){
17863
this.subscribe("row-layout-after", this.initializeRow.bind(this));
17864
}
17865
}
17866
17867
initializeRow(row){
17868
var self = this,
17869
rowEl = row.getElement();
17870
17871
var handle = document.createElement('div');
17872
handle.className = "tabulator-row-resize-handle";
17873
17874
var prevHandle = document.createElement('div');
17875
prevHandle.className = "tabulator-row-resize-handle prev";
17876
17877
handle.addEventListener("click", function(e){
17878
e.stopPropagation();
17879
});
17880
17881
var handleDown = function(e){
17882
self.startRow = row;
17883
self._mouseDown(e, row, handle);
17884
};
17885
17886
handle.addEventListener("mousedown", handleDown);
17887
handle.addEventListener("touchstart", handleDown, {passive: true});
17888
17889
prevHandle.addEventListener("click", function(e){
17890
e.stopPropagation();
17891
});
17892
17893
var prevHandleDown = function(e){
17894
var prevRow = self.table.rowManager.prevDisplayRow(row);
17895
17896
if(prevRow){
17897
self.startRow = prevRow;
17898
self._mouseDown(e, prevRow, prevHandle);
17899
}
17900
};
17901
17902
prevHandle.addEventListener("mousedown",prevHandleDown);
17903
prevHandle.addEventListener("touchstart",prevHandleDown, {passive: true});
17904
17905
rowEl.appendChild(handle);
17906
rowEl.appendChild(prevHandle);
17907
}
17908
17909
_mouseDown(e, row, handle){
17910
var self = this;
17911
17912
self.table.element.classList.add("tabulator-block-select");
17913
17914
function mouseMove(e){
17915
row.setHeight(self.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - self.startY));
17916
}
17917
17918
function mouseUp(e){
17919
17920
// //block editor from taking action while resizing is taking place
17921
// if(self.startColumn.modules.edit){
17922
// self.startColumn.modules.edit.blocked = false;
17923
// }
17924
17925
document.body.removeEventListener("mouseup", mouseMove);
17926
document.body.removeEventListener("mousemove", mouseMove);
17927
17928
handle.removeEventListener("touchmove", mouseMove);
17929
handle.removeEventListener("touchend", mouseUp);
17930
17931
self.table.element.classList.remove("tabulator-block-select");
17932
17933
self.dispatchExternal("rowResized", row.getComponent());
17934
}
17935
17936
e.stopPropagation(); //prevent resize from interfering with movable columns
17937
17938
//block editor from taking action while resizing is taking place
17939
// if(self.startColumn.modules.edit){
17940
// self.startColumn.modules.edit.blocked = true;
17941
// }
17942
17943
self.startY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY;
17944
self.startHeight = row.getHeight();
17945
17946
document.body.addEventListener("mousemove", mouseMove);
17947
document.body.addEventListener("mouseup", mouseUp);
17948
17949
handle.addEventListener("touchmove", mouseMove, {passive: true});
17950
handle.addEventListener("touchend", mouseUp);
17951
}
17952
}
17953
17954
ResizeRows.moduleName = "resizeRows";
17955
17956
class ResizeTable extends Module{
17957
17958
constructor(table){
17959
super(table);
17960
17961
this.binding = false;
17962
this.visibilityObserver = false;
17963
this.resizeObserver = false;
17964
this.containerObserver = false;
17965
17966
this.tableHeight = 0;
17967
this.tableWidth = 0;
17968
this.containerHeight = 0;
17969
this.containerWidth = 0;
17970
17971
this.autoResize = false;
17972
17973
this.visible = false;
17974
17975
this.initialized = false;
17976
this.initialRedraw = false;
17977
17978
this.registerTableOption("autoResize", true); //auto resize table
17979
}
17980
17981
initialize(){
17982
if(this.table.options.autoResize){
17983
var table = this.table,
17984
tableStyle;
17985
17986
this.tableHeight = table.element.clientHeight;
17987
this.tableWidth = table.element.clientWidth;
17988
17989
if(table.element.parentNode){
17990
this.containerHeight = table.element.parentNode.clientHeight;
17991
this.containerWidth = table.element.parentNode.clientWidth;
17992
}
17993
17994
if(typeof IntersectionObserver !== "undefined" && typeof ResizeObserver !== "undefined" && table.rowManager.getRenderMode() === "virtual"){
17995
17996
this.initializeVisibilityObserver();
17997
17998
this.autoResize = true;
17999
18000
this.resizeObserver = new ResizeObserver((entry) => {
18001
if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){
18002
18003
var nodeHeight = Math.floor(entry[0].contentRect.height);
18004
var nodeWidth = Math.floor(entry[0].contentRect.width);
18005
18006
if(this.tableHeight != nodeHeight || this.tableWidth != nodeWidth){
18007
this.tableHeight = nodeHeight;
18008
this.tableWidth = nodeWidth;
18009
18010
if(table.element.parentNode){
18011
this.containerHeight = table.element.parentNode.clientHeight;
18012
this.containerWidth = table.element.parentNode.clientWidth;
18013
}
18014
18015
this.redrawTable();
18016
}
18017
}
18018
});
18019
18020
this.resizeObserver.observe(table.element);
18021
18022
tableStyle = window.getComputedStyle(table.element);
18023
18024
if(this.table.element.parentNode && !this.table.rowManager.fixedHeight && (tableStyle.getPropertyValue("max-height") || tableStyle.getPropertyValue("min-height"))){
18025
18026
this.containerObserver = new ResizeObserver((entry) => {
18027
if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){
18028
18029
var nodeHeight = Math.floor(entry[0].contentRect.height);
18030
var nodeWidth = Math.floor(entry[0].contentRect.width);
18031
18032
if(this.containerHeight != nodeHeight || this.containerWidth != nodeWidth){
18033
this.containerHeight = nodeHeight;
18034
this.containerWidth = nodeWidth;
18035
this.tableHeight = table.element.clientHeight;
18036
this.tableWidth = table.element.clientWidth;
18037
}
18038
18039
this.redrawTable();
18040
}
18041
});
18042
18043
this.containerObserver.observe(this.table.element.parentNode);
18044
}
18045
18046
this.subscribe("table-resize", this.tableResized.bind(this));
18047
18048
}else {
18049
this.binding = function(){
18050
if(!table.browserMobile || (table.browserMobile && !table.modules.edit.currentCell)){
18051
table.columnManager.rerenderColumns(true);
18052
table.redraw();
18053
}
18054
};
18055
18056
window.addEventListener("resize", this.binding);
18057
}
18058
18059
this.subscribe("table-destroy", this.clearBindings.bind(this));
18060
}
18061
}
18062
18063
initializeVisibilityObserver(){
18064
this.visibilityObserver = new IntersectionObserver((entries) => {
18065
this.visible = entries[0].isIntersecting;
18066
18067
if(!this.initialized){
18068
this.initialized = true;
18069
this.initialRedraw = !this.visible;
18070
}else {
18071
if(this.visible){
18072
this.redrawTable(this.initialRedraw);
18073
this.initialRedraw = false;
18074
}
18075
}
18076
});
18077
18078
this.visibilityObserver.observe(this.table.element);
18079
}
18080
18081
redrawTable(force){
18082
if(this.initialized && this.visible){
18083
this.table.columnManager.rerenderColumns(true);
18084
this.table.redraw(force);
18085
}
18086
}
18087
18088
tableResized(){
18089
this.table.rowManager.redraw();
18090
}
18091
18092
clearBindings(){
18093
if(this.binding){
18094
window.removeEventListener("resize", this.binding);
18095
}
18096
18097
if(this.resizeObserver){
18098
this.resizeObserver.unobserve(this.table.element);
18099
}
18100
18101
if(this.visibilityObserver){
18102
this.visibilityObserver.unobserve(this.table.element);
18103
}
18104
18105
if(this.containerObserver){
18106
this.containerObserver.unobserve(this.table.element.parentNode);
18107
}
18108
}
18109
}
18110
18111
ResizeTable.moduleName = "resizeTable";
18112
18113
class ResponsiveLayout extends Module{
18114
18115
constructor(table){
18116
super(table);
18117
18118
this.columns = [];
18119
this.hiddenColumns = [];
18120
this.mode = "";
18121
this.index = 0;
18122
this.collapseFormatter = [];
18123
this.collapseStartOpen = true;
18124
this.collapseHandleColumn = false;
18125
18126
this.registerTableOption("responsiveLayout", false); //responsive layout flags
18127
this.registerTableOption("responsiveLayoutCollapseStartOpen", true); //start showing collapsed data
18128
this.registerTableOption("responsiveLayoutCollapseUseFormatters", true); //responsive layout collapse formatter
18129
this.registerTableOption("responsiveLayoutCollapseFormatter", false); //responsive layout collapse formatter
18130
18131
this.registerColumnOption("responsive");
18132
}
18133
18134
//generate responsive columns list
18135
initialize(){
18136
if(this.table.options.responsiveLayout){
18137
this.subscribe("column-layout", this.initializeColumn.bind(this));
18138
this.subscribe("column-show", this.updateColumnVisibility.bind(this));
18139
this.subscribe("column-hide", this.updateColumnVisibility.bind(this));
18140
this.subscribe("columns-loaded", this.initializeResponsivity.bind(this));
18141
this.subscribe("column-moved", this.initializeResponsivity.bind(this));
18142
this.subscribe("column-add", this.initializeResponsivity.bind(this));
18143
this.subscribe("column-delete", this.initializeResponsivity.bind(this));
18144
18145
this.subscribe("table-redrawing", this.tableRedraw.bind(this));
18146
18147
if(this.table.options.responsiveLayout === "collapse"){
18148
this.subscribe("row-data-changed", this.generateCollapsedRowContent.bind(this));
18149
this.subscribe("row-init", this.initializeRow.bind(this));
18150
this.subscribe("row-layout", this.layoutRow.bind(this));
18151
}
18152
}
18153
}
18154
18155
tableRedraw(force){
18156
if(["fitColumns", "fitDataStretch"].indexOf(this.layoutMode()) === -1){
18157
if(!force){
18158
this.update();
18159
}
18160
}
18161
}
18162
18163
initializeResponsivity(){
18164
var columns = [];
18165
18166
this.mode = this.table.options.responsiveLayout;
18167
this.collapseFormatter = this.table.options.responsiveLayoutCollapseFormatter || this.formatCollapsedData;
18168
this.collapseStartOpen = this.table.options.responsiveLayoutCollapseStartOpen;
18169
this.hiddenColumns = [];
18170
18171
//determine level of responsivity for each column
18172
this.table.columnManager.columnsByIndex.forEach((column, i) => {
18173
if(column.modules.responsive){
18174
if(column.modules.responsive.order && column.modules.responsive.visible){
18175
column.modules.responsive.index = i;
18176
columns.push(column);
18177
18178
if(!column.visible && this.mode === "collapse"){
18179
this.hiddenColumns.push(column);
18180
}
18181
}
18182
}
18183
});
18184
18185
//sort list by responsivity
18186
columns = columns.reverse();
18187
columns = columns.sort((a, b) => {
18188
var diff = b.modules.responsive.order - a.modules.responsive.order;
18189
return diff || (b.modules.responsive.index - a.modules.responsive.index);
18190
});
18191
18192
this.columns = columns;
18193
18194
if(this.mode === "collapse"){
18195
this.generateCollapsedContent();
18196
}
18197
18198
//assign collapse column
18199
for (let col of this.table.columnManager.columnsByIndex){
18200
if(col.definition.formatter == "responsiveCollapse"){
18201
this.collapseHandleColumn = col;
18202
break;
18203
}
18204
}
18205
18206
if(this.collapseHandleColumn){
18207
if(this.hiddenColumns.length){
18208
this.collapseHandleColumn.show();
18209
}else {
18210
this.collapseHandleColumn.hide();
18211
}
18212
}
18213
}
18214
18215
//define layout information
18216
initializeColumn(column){
18217
var def = column.getDefinition();
18218
18219
column.modules.responsive = {order: typeof def.responsive === "undefined" ? 1 : def.responsive, visible:def.visible === false ? false : true};
18220
}
18221
18222
initializeRow(row){
18223
var el;
18224
18225
if(row.type !== "calc"){
18226
el = document.createElement("div");
18227
el.classList.add("tabulator-responsive-collapse");
18228
18229
row.modules.responsiveLayout = {
18230
element:el,
18231
open:this.collapseStartOpen,
18232
};
18233
18234
if(!this.collapseStartOpen){
18235
el.style.display = 'none';
18236
}
18237
}
18238
}
18239
18240
layoutRow(row){
18241
var rowEl = row.getElement();
18242
18243
if(row.modules.responsiveLayout){
18244
rowEl.appendChild(row.modules.responsiveLayout.element);
18245
this.generateCollapsedRowContent(row);
18246
}
18247
}
18248
18249
//update column visibility
18250
updateColumnVisibility(column, responsiveToggle){
18251
if(!responsiveToggle && column.modules.responsive){
18252
column.modules.responsive.visible = column.visible;
18253
this.initializeResponsivity();
18254
}
18255
}
18256
18257
hideColumn(column){
18258
var colCount = this.hiddenColumns.length;
18259
18260
column.hide(false, true);
18261
18262
if(this.mode === "collapse"){
18263
this.hiddenColumns.unshift(column);
18264
this.generateCollapsedContent();
18265
18266
if(this.collapseHandleColumn && !colCount){
18267
this.collapseHandleColumn.show();
18268
}
18269
}
18270
}
18271
18272
showColumn(column){
18273
var index;
18274
18275
column.show(false, true);
18276
//set column width to prevent calculation loops on uninitialized columns
18277
column.setWidth(column.getWidth());
18278
18279
if(this.mode === "collapse"){
18280
index = this.hiddenColumns.indexOf(column);
18281
18282
if(index > -1){
18283
this.hiddenColumns.splice(index, 1);
18284
}
18285
18286
this.generateCollapsedContent();
18287
18288
if(this.collapseHandleColumn && !this.hiddenColumns.length){
18289
this.collapseHandleColumn.hide();
18290
}
18291
}
18292
}
18293
18294
//redraw columns to fit space
18295
update(){
18296
var working = true;
18297
18298
while(working){
18299
18300
let width = this.table.modules.layout.getMode() == "fitColumns" ? this.table.columnManager.getFlexBaseWidth() : this.table.columnManager.getWidth();
18301
18302
let diff = (this.table.options.headerVisible ? this.table.columnManager.element.clientWidth : this.table.element.clientWidth) - width;
18303
18304
if(diff < 0){
18305
//table is too wide
18306
let column = this.columns[this.index];
18307
18308
if(column){
18309
this.hideColumn(column);
18310
this.index ++;
18311
}else {
18312
working = false;
18313
}
18314
18315
}else {
18316
18317
//table has spare space
18318
let column = this.columns[this.index -1];
18319
18320
if(column){
18321
if(diff > 0){
18322
if(diff >= column.getWidth()){
18323
this.showColumn(column);
18324
this.index --;
18325
}else {
18326
working = false;
18327
}
18328
}else {
18329
working = false;
18330
}
18331
}else {
18332
working = false;
18333
}
18334
}
18335
18336
if(!this.table.rowManager.activeRowsCount){
18337
this.table.rowManager.renderEmptyScroll();
18338
}
18339
}
18340
}
18341
18342
generateCollapsedContent(){
18343
var rows = this.table.rowManager.getDisplayRows();
18344
18345
rows.forEach((row) => {
18346
this.generateCollapsedRowContent(row);
18347
});
18348
}
18349
18350
generateCollapsedRowContent(row){
18351
var el, contents;
18352
18353
if(row.modules.responsiveLayout){
18354
el = row.modules.responsiveLayout.element;
18355
18356
while(el.firstChild) el.removeChild(el.firstChild);
18357
18358
contents = this.collapseFormatter(this.generateCollapsedRowData(row));
18359
if(contents){
18360
el.appendChild(contents);
18361
}
18362
}
18363
}
18364
18365
generateCollapsedRowData(row){
18366
var data = row.getData(),
18367
output = [],
18368
mockCellComponent;
18369
18370
this.hiddenColumns.forEach((column) => {
18371
var value = column.getFieldValue(data);
18372
18373
if(column.definition.title && column.field){
18374
if(column.modules.format && this.table.options.responsiveLayoutCollapseUseFormatters){
18375
18376
mockCellComponent = {
18377
value:false,
18378
data:{},
18379
getValue:function(){
18380
return value;
18381
},
18382
getData:function(){
18383
return data;
18384
},
18385
getType:function(){
18386
return "cell";
18387
},
18388
getElement:function(){
18389
return document.createElement("div");
18390
},
18391
getRow:function(){
18392
return row.getComponent();
18393
},
18394
getColumn:function(){
18395
return column.getComponent();
18396
},
18397
getTable:() => {
18398
return this.table;
18399
},
18400
};
18401
18402
function onRendered(callback){
18403
callback();
18404
}
18405
18406
output.push({
18407
field: column.field,
18408
title: column.definition.title,
18409
value: column.modules.format.formatter.call(this.table.modules.format, mockCellComponent, column.modules.format.params, onRendered)
18410
});
18411
}else {
18412
output.push({
18413
field: column.field,
18414
title: column.definition.title,
18415
value: value
18416
});
18417
}
18418
}
18419
});
18420
18421
return output;
18422
}
18423
18424
formatCollapsedData(data){
18425
var list = document.createElement("table");
18426
18427
data.forEach(function(item){
18428
var row = document.createElement("tr");
18429
var titleData = document.createElement("td");
18430
var valueData = document.createElement("td");
18431
var node_content;
18432
18433
var titleHighlight = document.createElement("strong");
18434
titleData.appendChild(titleHighlight);
18435
this.langBind("columns|" + item.field, function(text){
18436
titleHighlight.innerHTML = text || item.title;
18437
});
18438
18439
if(item.value instanceof Node){
18440
node_content = document.createElement("div");
18441
node_content.appendChild(item.value);
18442
valueData.appendChild(node_content);
18443
}else {
18444
valueData.innerHTML = item.value;
18445
}
18446
18447
row.appendChild(titleData);
18448
row.appendChild(valueData);
18449
list.appendChild(row);
18450
}, this);
18451
18452
return Object.keys(data).length ? list : "";
18453
}
18454
}
18455
18456
ResponsiveLayout.moduleName = "responsiveLayout";
18457
18458
class SelectRow extends Module{
18459
18460
constructor(table){
18461
super(table);
18462
18463
this.selecting = false; //flag selecting in progress
18464
this.lastClickedRow = false; //last clicked row
18465
this.selectPrev = []; //hold previously selected element for drag drop selection
18466
this.selectedRows = []; //hold selected rows
18467
this.headerCheckboxElement = null; // hold header select element
18468
18469
this.registerTableOption("selectable", "highlight"); //highlight rows on hover
18470
this.registerTableOption("selectableRangeMode", "drag"); //highlight rows on hover
18471
this.registerTableOption("selectableRollingSelection", true); //roll selection once maximum number of selectable rows is reached
18472
this.registerTableOption("selectablePersistence", true); // maintain selection when table view is updated
18473
this.registerTableOption("selectableCheck", function(data, row){return true;}); //check whether row is selectable
18474
18475
this.registerTableFunction("selectRow", this.selectRows.bind(this));
18476
this.registerTableFunction("deselectRow", this.deselectRows.bind(this));
18477
this.registerTableFunction("toggleSelectRow", this.toggleRow.bind(this));
18478
this.registerTableFunction("getSelectedRows", this.getSelectedRows.bind(this));
18479
this.registerTableFunction("getSelectedData", this.getSelectedData.bind(this));
18480
18481
//register component functions
18482
this.registerComponentFunction("row", "select", this.selectRows.bind(this));
18483
this.registerComponentFunction("row", "deselect", this.deselectRows.bind(this));
18484
this.registerComponentFunction("row", "toggleSelect", this.toggleRow.bind(this));
18485
this.registerComponentFunction("row", "isSelected", this.isRowSelected.bind(this));
18486
}
18487
18488
initialize(){
18489
if(this.table.options.selectable !== false){
18490
this.subscribe("row-init", this.initializeRow.bind(this));
18491
this.subscribe("row-deleting", this.rowDeleted.bind(this));
18492
this.subscribe("rows-wipe", this.clearSelectionData.bind(this));
18493
this.subscribe("rows-retrieve", this.rowRetrieve.bind(this));
18494
18495
if(this.table.options.selectable && !this.table.options.selectablePersistence){
18496
this.subscribe("data-refreshing", this.deselectRows.bind(this));
18497
}
18498
}
18499
}
18500
18501
rowRetrieve(type, prevValue){
18502
return type === "selected" ? this.selectedRows : prevValue;
18503
}
18504
18505
rowDeleted(row){
18506
this._deselectRow(row, true);
18507
}
18508
18509
clearSelectionData(silent){
18510
var prevSelected = this.selectedRows.length;
18511
18512
this.selecting = false;
18513
this.lastClickedRow = false;
18514
this.selectPrev = [];
18515
this.selectedRows = [];
18516
18517
if(prevSelected && silent !== true){
18518
this._rowSelectionChanged();
18519
}
18520
}
18521
18522
initializeRow(row){
18523
var self = this,
18524
element = row.getElement();
18525
18526
// trigger end of row selection
18527
var endSelect = function(){
18528
18529
setTimeout(function(){
18530
self.selecting = false;
18531
}, 50);
18532
18533
document.body.removeEventListener("mouseup", endSelect);
18534
};
18535
18536
row.modules.select = {selected:false};
18537
18538
//set row selection class
18539
if(self.checkRowSelectability(row)){
18540
element.classList.add("tabulator-selectable");
18541
element.classList.remove("tabulator-unselectable");
18542
18543
if(self.table.options.selectable && self.table.options.selectable != "highlight"){
18544
if(self.table.options.selectableRangeMode === "click"){
18545
element.addEventListener("click", this.handleComplexRowClick.bind(this, row));
18546
}else {
18547
element.addEventListener("click", function(e){
18548
if(!self.table.modExists("edit") || !self.table.modules.edit.getCurrentCell()){
18549
self.table._clearSelection();
18550
}
18551
18552
if(!self.selecting){
18553
self.toggleRow(row);
18554
}
18555
});
18556
18557
element.addEventListener("mousedown", function(e){
18558
if(e.shiftKey){
18559
self.table._clearSelection();
18560
18561
self.selecting = true;
18562
18563
self.selectPrev = [];
18564
18565
document.body.addEventListener("mouseup", endSelect);
18566
document.body.addEventListener("keyup", endSelect);
18567
18568
self.toggleRow(row);
18569
18570
return false;
18571
}
18572
});
18573
18574
element.addEventListener("mouseenter", function(e){
18575
if(self.selecting){
18576
self.table._clearSelection();
18577
self.toggleRow(row);
18578
18579
if(self.selectPrev[1] == row){
18580
self.toggleRow(self.selectPrev[0]);
18581
}
18582
}
18583
});
18584
18585
element.addEventListener("mouseout", function(e){
18586
if(self.selecting){
18587
self.table._clearSelection();
18588
self.selectPrev.unshift(row);
18589
}
18590
});
18591
}
18592
}
18593
18594
}else {
18595
element.classList.add("tabulator-unselectable");
18596
element.classList.remove("tabulator-selectable");
18597
}
18598
}
18599
18600
handleComplexRowClick(row, e){
18601
if(e.shiftKey){
18602
this.table._clearSelection();
18603
this.lastClickedRow = this.lastClickedRow || row;
18604
18605
var lastClickedRowIdx = this.table.rowManager.getDisplayRowIndex(this.lastClickedRow);
18606
var rowIdx = this.table.rowManager.getDisplayRowIndex(row);
18607
18608
var fromRowIdx = lastClickedRowIdx <= rowIdx ? lastClickedRowIdx : rowIdx;
18609
var toRowIdx = lastClickedRowIdx >= rowIdx ? lastClickedRowIdx : rowIdx;
18610
18611
var rows = this.table.rowManager.getDisplayRows().slice(0);
18612
var toggledRows = rows.splice(fromRowIdx, toRowIdx - fromRowIdx + 1);
18613
18614
if(e.ctrlKey || e.metaKey){
18615
toggledRows.forEach((toggledRow)=>{
18616
if(toggledRow !== this.lastClickedRow){
18617
18618
if(this.table.options.selectable !== true && !this.isRowSelected(row)){
18619
if(this.selectedRows.length < this.table.options.selectable){
18620
this.toggleRow(toggledRow);
18621
}
18622
}else {
18623
this.toggleRow(toggledRow);
18624
}
18625
}
18626
});
18627
this.lastClickedRow = row;
18628
}else {
18629
this.deselectRows(undefined, true);
18630
18631
if(this.table.options.selectable !== true){
18632
if(toggledRows.length > this.table.options.selectable){
18633
toggledRows = toggledRows.slice(0, this.table.options.selectable);
18634
}
18635
}
18636
18637
this.selectRows(toggledRows);
18638
}
18639
this.table._clearSelection();
18640
}
18641
else if(e.ctrlKey || e.metaKey){
18642
this.toggleRow(row);
18643
this.lastClickedRow = row;
18644
}else {
18645
this.deselectRows(undefined, true);
18646
this.selectRows(row);
18647
this.lastClickedRow = row;
18648
}
18649
}
18650
18651
checkRowSelectability(row){
18652
if(row && row.type === "row"){
18653
return this.table.options.selectableCheck.call(this.table, row.getComponent());
18654
}
18655
18656
return false;
18657
}
18658
18659
//toggle row selection
18660
toggleRow(row){
18661
if(this.checkRowSelectability(row)){
18662
if(row.modules.select && row.modules.select.selected){
18663
this._deselectRow(row);
18664
}else {
18665
this._selectRow(row);
18666
}
18667
}
18668
}
18669
18670
//select a number of rows
18671
selectRows(rows){
18672
var changes = [],
18673
rowMatch, change;
18674
18675
switch(typeof rows){
18676
case "undefined":
18677
rowMatch = this.table.rowManager.rows;
18678
break;
18679
18680
case "string":
18681
rowMatch = this.table.rowManager.findRow(rows);
18682
18683
if(!rowMatch){
18684
rowMatch = this.table.rowManager.getRows(rows);
18685
}
18686
break;
18687
18688
default:
18689
rowMatch = rows;
18690
break;
18691
}
18692
18693
if(Array.isArray(rowMatch)){
18694
if(rowMatch.length){
18695
rowMatch.forEach((row) => {
18696
change = this._selectRow(row, true, true);
18697
18698
if(change){
18699
changes.push(change);
18700
}
18701
});
18702
18703
this._rowSelectionChanged(false, changes);
18704
}
18705
}else {
18706
if(rowMatch){
18707
this._selectRow(rowMatch, false, true);
18708
}
18709
}
18710
}
18711
18712
//select an individual row
18713
_selectRow(rowInfo, silent, force){
18714
//handle max row count
18715
if(!isNaN(this.table.options.selectable) && this.table.options.selectable !== true && !force){
18716
if(this.selectedRows.length >= this.table.options.selectable){
18717
if(this.table.options.selectableRollingSelection){
18718
this._deselectRow(this.selectedRows[0]);
18719
}else {
18720
return false;
18721
}
18722
}
18723
}
18724
18725
var row = this.table.rowManager.findRow(rowInfo);
18726
18727
if(row){
18728
if(this.selectedRows.indexOf(row) == -1){
18729
row.getElement().classList.add("tabulator-selected");
18730
if(!row.modules.select){
18731
row.modules.select = {};
18732
}
18733
18734
row.modules.select.selected = true;
18735
if(row.modules.select.checkboxEl){
18736
row.modules.select.checkboxEl.checked = true;
18737
}
18738
18739
this.selectedRows.push(row);
18740
18741
if(this.table.options.dataTreeSelectPropagate){
18742
this.childRowSelection(row, true);
18743
}
18744
18745
this.dispatchExternal("rowSelected", row.getComponent());
18746
18747
this._rowSelectionChanged(silent, row);
18748
18749
return row;
18750
}
18751
}else {
18752
if(!silent){
18753
console.warn("Selection Error - No such row found, ignoring selection:" + rowInfo);
18754
}
18755
}
18756
}
18757
18758
isRowSelected(row){
18759
return this.selectedRows.indexOf(row) !== -1;
18760
}
18761
18762
//deselect a number of rows
18763
deselectRows(rows, silent){
18764
var changes = [],
18765
rowMatch, change;
18766
18767
switch(typeof rows){
18768
case "undefined":
18769
rowMatch = Object.assign([], this.selectedRows);
18770
break;
18771
18772
case "string":
18773
rowMatch = this.table.rowManager.findRow(rows);
18774
18775
if(!rowMatch){
18776
rowMatch = this.table.rowManager.getRows(rows);
18777
}
18778
break;
18779
18780
default:
18781
rowMatch = rows;
18782
break;
18783
}
18784
18785
if(Array.isArray(rowMatch)){
18786
if(rowMatch.length){
18787
rowMatch.forEach((row) => {
18788
change = this._deselectRow(row, true, true);
18789
18790
if(change){
18791
changes.push(change);
18792
}
18793
});
18794
18795
this._rowSelectionChanged(silent, [], changes);
18796
}
18797
}else {
18798
if(rowMatch){
18799
this._deselectRow(rowMatch, silent, true);
18800
}
18801
}
18802
}
18803
18804
//deselect an individual row
18805
_deselectRow(rowInfo, silent){
18806
var self = this,
18807
row = self.table.rowManager.findRow(rowInfo),
18808
index, element;
18809
18810
if(row){
18811
index = self.selectedRows.findIndex(function(selectedRow){
18812
return selectedRow == row;
18813
});
18814
18815
if(index > -1){
18816
18817
element = row.getElement();
18818
18819
if(element){
18820
element.classList.remove("tabulator-selected");
18821
}
18822
18823
if(!row.modules.select){
18824
row.modules.select = {};
18825
}
18826
18827
row.modules.select.selected = false;
18828
if(row.modules.select.checkboxEl){
18829
row.modules.select.checkboxEl.checked = false;
18830
}
18831
self.selectedRows.splice(index, 1);
18832
18833
if(this.table.options.dataTreeSelectPropagate){
18834
this.childRowSelection(row, false);
18835
}
18836
18837
this.dispatchExternal("rowDeselected", row.getComponent());
18838
18839
self._rowSelectionChanged(silent, undefined, row);
18840
18841
return row;
18842
}
18843
}else {
18844
if(!silent){
18845
console.warn("Deselection Error - No such row found, ignoring selection:" + rowInfo);
18846
}
18847
}
18848
}
18849
18850
getSelectedData(){
18851
var data = [];
18852
18853
this.selectedRows.forEach(function(row){
18854
data.push(row.getData());
18855
});
18856
18857
return data;
18858
}
18859
18860
getSelectedRows(){
18861
var rows = [];
18862
18863
this.selectedRows.forEach(function(row){
18864
rows.push(row.getComponent());
18865
});
18866
18867
return rows;
18868
}
18869
18870
_rowSelectionChanged(silent, selected = [], deselected = []){
18871
if(this.headerCheckboxElement){
18872
if(this.selectedRows.length === 0){
18873
this.headerCheckboxElement.checked = false;
18874
this.headerCheckboxElement.indeterminate = false;
18875
} else if(this.table.rowManager.rows.length === this.selectedRows.length){
18876
this.headerCheckboxElement.checked = true;
18877
this.headerCheckboxElement.indeterminate = false;
18878
} else {
18879
this.headerCheckboxElement.indeterminate = true;
18880
this.headerCheckboxElement.checked = false;
18881
}
18882
}
18883
18884
if(!silent){
18885
if(!Array.isArray(selected)){
18886
selected = [selected];
18887
}
18888
18889
selected = selected.map(row => row.getComponent());
18890
18891
if(!Array.isArray(deselected)){
18892
deselected = [deselected];
18893
}
18894
18895
deselected = deselected.map(row => row.getComponent());
18896
18897
this.dispatchExternal("rowSelectionChanged", this.getSelectedData(), this.getSelectedRows(), selected, deselected);
18898
}
18899
}
18900
18901
registerRowSelectCheckbox (row, element) {
18902
if(!row._row.modules.select){
18903
row._row.modules.select = {};
18904
}
18905
18906
row._row.modules.select.checkboxEl = element;
18907
}
18908
18909
registerHeaderSelectCheckbox (element) {
18910
this.headerCheckboxElement = element;
18911
}
18912
18913
childRowSelection(row, select){
18914
var children = this.table.modules.dataTree.getChildren(row, true);
18915
18916
if(select){
18917
for(let child of children){
18918
this._selectRow(child, true);
18919
}
18920
}else {
18921
for(let child of children){
18922
this._deselectRow(child, true);
18923
}
18924
}
18925
}
18926
}
18927
18928
SelectRow.moduleName = "selectRow";
18929
18930
//sort numbers
18931
function number$1(a, b, aRow, bRow, column, dir, params){
18932
var alignEmptyValues = params.alignEmptyValues;
18933
var decimal = params.decimalSeparator;
18934
var thousand = params.thousandSeparator;
18935
var emptyAlign = 0;
18936
18937
a = String(a);
18938
b = String(b);
18939
18940
if(thousand){
18941
a = a.split(thousand).join("");
18942
b = b.split(thousand).join("");
18943
}
18944
18945
if(decimal){
18946
a = a.split(decimal).join(".");
18947
b = b.split(decimal).join(".");
18948
}
18949
18950
a = parseFloat(a);
18951
b = parseFloat(b);
18952
18953
//handle non numeric values
18954
if(isNaN(a)){
18955
emptyAlign = isNaN(b) ? 0 : -1;
18956
}else if(isNaN(b)){
18957
emptyAlign = 1;
18958
}else {
18959
//compare valid values
18960
return a - b;
18961
}
18962
18963
//fix empty values in position
18964
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
18965
emptyAlign *= -1;
18966
}
18967
18968
return emptyAlign;
18969
}
18970
18971
//sort strings
18972
function string(a, b, aRow, bRow, column, dir, params){
18973
var alignEmptyValues = params.alignEmptyValues;
18974
var emptyAlign = 0;
18975
var locale;
18976
18977
//handle empty values
18978
if(!a){
18979
emptyAlign = !b ? 0 : -1;
18980
}else if(!b){
18981
emptyAlign = 1;
18982
}else {
18983
//compare valid values
18984
switch(typeof params.locale){
18985
case "boolean":
18986
if(params.locale){
18987
locale = this.langLocale();
18988
}
18989
break;
18990
case "string":
18991
locale = params.locale;
18992
break;
18993
}
18994
18995
return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale);
18996
}
18997
18998
//fix empty values in position
18999
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
19000
emptyAlign *= -1;
19001
}
19002
19003
return emptyAlign;
19004
}
19005
19006
//sort datetime
19007
function datetime$2(a, b, aRow, bRow, column, dir, params){
19008
var DT = window.DateTime || luxon.DateTime;
19009
var format = params.format || "dd/MM/yyyy HH:mm:ss",
19010
alignEmptyValues = params.alignEmptyValues,
19011
emptyAlign = 0;
19012
19013
if(typeof DT != "undefined"){
19014
if(!DT.isDateTime(a)){
19015
if(format === "iso"){
19016
a = DT.fromISO(String(a));
19017
}else {
19018
a = DT.fromFormat(String(a), format);
19019
}
19020
}
19021
19022
if(!DT.isDateTime(b)){
19023
if(format === "iso"){
19024
b = DT.fromISO(String(b));
19025
}else {
19026
b = DT.fromFormat(String(b), format);
19027
}
19028
}
19029
19030
if(!a.isValid){
19031
emptyAlign = !b.isValid ? 0 : -1;
19032
}else if(!b.isValid){
19033
emptyAlign = 1;
19034
}else {
19035
//compare valid values
19036
return a - b;
19037
}
19038
19039
//fix empty values in position
19040
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
19041
emptyAlign *= -1;
19042
}
19043
19044
return emptyAlign;
19045
19046
}else {
19047
console.error("Sort Error - 'datetime' sorter is dependant on luxon.js");
19048
}
19049
}
19050
19051
//sort date
19052
function date$1(a, b, aRow, bRow, column, dir, params){
19053
if(!params.format){
19054
params.format = "dd/MM/yyyy";
19055
}
19056
19057
return datetime$2.call(this, a, b, aRow, bRow, column, dir, params);
19058
}
19059
19060
//sort times
19061
function time$1(a, b, aRow, bRow, column, dir, params){
19062
if(!params.format){
19063
params.format = "HH:mm";
19064
}
19065
19066
return datetime$2.call(this, a, b, aRow, bRow, column, dir, params);
19067
}
19068
19069
//sort booleans
19070
function boolean(a, b, aRow, bRow, column, dir, params){
19071
var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0;
19072
var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0;
19073
19074
return el1 - el2;
19075
}
19076
19077
//sort if element contains any data
19078
function array(a, b, aRow, bRow, column, dir, params){
19079
var type = params.type || "length",
19080
alignEmptyValues = params.alignEmptyValues,
19081
emptyAlign = 0;
19082
19083
function calc(value){
19084
var result;
19085
19086
switch(type){
19087
case "length":
19088
result = value.length;
19089
break;
19090
19091
case "sum":
19092
result = value.reduce(function(c, d){
19093
return c + d;
19094
});
19095
break;
19096
19097
case "max":
19098
result = Math.max.apply(null, value) ;
19099
break;
19100
19101
case "min":
19102
result = Math.min.apply(null, value) ;
19103
break;
19104
19105
case "avg":
19106
result = value.reduce(function(c, d){
19107
return c + d;
19108
}) / value.length;
19109
break;
19110
}
19111
19112
return result;
19113
}
19114
19115
//handle non array values
19116
if(!Array.isArray(a)){
19117
emptyAlign = !Array.isArray(b) ? 0 : -1;
19118
}else if(!Array.isArray(b)){
19119
emptyAlign = 1;
19120
}else {
19121
return calc(b) - calc(a);
19122
}
19123
19124
//fix empty values in position
19125
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
19126
emptyAlign *= -1;
19127
}
19128
19129
return emptyAlign;
19130
}
19131
19132
//sort if element contains any data
19133
function exists(a, b, aRow, bRow, column, dir, params){
19134
var el1 = typeof a == "undefined" ? 0 : 1;
19135
var el2 = typeof b == "undefined" ? 0 : 1;
19136
19137
return el1 - el2;
19138
}
19139
19140
//sort alpha numeric strings
19141
function alphanum(as, bs, aRow, bRow, column, dir, params){
19142
var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
19143
var alignEmptyValues = params.alignEmptyValues;
19144
var emptyAlign = 0;
19145
19146
//handle empty values
19147
if(!as && as!== 0){
19148
emptyAlign = !bs && bs!== 0 ? 0 : -1;
19149
}else if(!bs && bs!== 0){
19150
emptyAlign = 1;
19151
}else {
19152
19153
if(isFinite(as) && isFinite(bs)) return as - bs;
19154
a = String(as).toLowerCase();
19155
b = String(bs).toLowerCase();
19156
if(a === b) return 0;
19157
if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
19158
a = a.match(rx);
19159
b = b.match(rx);
19160
L = a.length > b.length ? b.length : a.length;
19161
while(i < L){
19162
a1= a[i];
19163
b1= b[i++];
19164
if(a1 !== b1){
19165
if(isFinite(a1) && isFinite(b1)){
19166
if(a1.charAt(0) === "0") a1 = "." + a1;
19167
if(b1.charAt(0) === "0") b1 = "." + b1;
19168
return a1 - b1;
19169
}
19170
else return a1 > b1 ? 1 : -1;
19171
}
19172
}
19173
19174
return a.length > b.length;
19175
}
19176
19177
//fix empty values in position
19178
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
19179
emptyAlign *= -1;
19180
}
19181
19182
return emptyAlign;
19183
}
19184
19185
var defaultSorters = {
19186
number:number$1,
19187
string:string,
19188
date:date$1,
19189
time:time$1,
19190
datetime:datetime$2,
19191
boolean:boolean,
19192
array:array,
19193
exists:exists,
19194
alphanum:alphanum
19195
};
19196
19197
class Sort extends Module{
19198
19199
constructor(table){
19200
super(table);
19201
19202
this.sortList = []; //holder current sort
19203
this.changed = false; //has the sort changed since last render
19204
19205
this.registerTableOption("sortMode", "local"); //local or remote sorting
19206
19207
this.registerTableOption("initialSort", false); //initial sorting criteria
19208
this.registerTableOption("columnHeaderSortMulti", true); //multiple or single column sorting
19209
this.registerTableOption("sortOrderReverse", false); //reverse internal sort ordering
19210
this.registerTableOption("headerSortElement", "<div class='tabulator-arrow'></div>"); //header sort element
19211
this.registerTableOption("headerSortClickElement", "header"); //element which triggers sort when clicked
19212
19213
this.registerColumnOption("sorter");
19214
this.registerColumnOption("sorterParams");
19215
19216
this.registerColumnOption("headerSort", true);
19217
this.registerColumnOption("headerSortStartingDir");
19218
this.registerColumnOption("headerSortTristate");
19219
19220
}
19221
19222
initialize(){
19223
this.subscribe("column-layout", this.initializeColumn.bind(this));
19224
this.subscribe("table-built", this.tableBuilt.bind(this));
19225
this.registerDataHandler(this.sort.bind(this), 20);
19226
19227
this.registerTableFunction("setSort", this.userSetSort.bind(this));
19228
this.registerTableFunction("getSorters", this.getSort.bind(this));
19229
this.registerTableFunction("clearSort", this.clearSort.bind(this));
19230
19231
if(this.table.options.sortMode === "remote"){
19232
this.subscribe("data-params", this.remoteSortParams.bind(this));
19233
}
19234
}
19235
19236
tableBuilt(){
19237
if(this.table.options.initialSort){
19238
this.setSort(this.table.options.initialSort);
19239
}
19240
}
19241
19242
remoteSortParams(data, config, silent, params){
19243
var sorters = this.getSort();
19244
19245
sorters.forEach((item) => {
19246
delete item.column;
19247
});
19248
19249
params.sort = sorters;
19250
19251
return params;
19252
}
19253
19254
19255
///////////////////////////////////
19256
///////// Table Functions /////////
19257
///////////////////////////////////
19258
19259
userSetSort(sortList, dir){
19260
this.setSort(sortList, dir);
19261
// this.table.rowManager.sorterRefresh();
19262
this.refreshSort();
19263
}
19264
19265
clearSort(){
19266
this.clear();
19267
// this.table.rowManager.sorterRefresh();
19268
this.refreshSort();
19269
}
19270
19271
19272
///////////////////////////////////
19273
///////// Internal Logic //////////
19274
///////////////////////////////////
19275
19276
//initialize column header for sorting
19277
initializeColumn(column){
19278
var sorter = false,
19279
colEl,
19280
arrowEl;
19281
19282
switch(typeof column.definition.sorter){
19283
case "string":
19284
if(Sort.sorters[column.definition.sorter]){
19285
sorter = Sort.sorters[column.definition.sorter];
19286
}else {
19287
console.warn("Sort Error - No such sorter found: ", column.definition.sorter);
19288
}
19289
break;
19290
19291
case "function":
19292
sorter = column.definition.sorter;
19293
break;
19294
}
19295
19296
column.modules.sort = {
19297
sorter:sorter, dir:"none",
19298
params:column.definition.sorterParams || {},
19299
startingDir:column.definition.headerSortStartingDir || "asc",
19300
tristate: column.definition.headerSortTristate,
19301
};
19302
19303
if(column.definition.headerSort !== false){
19304
19305
colEl = column.getElement();
19306
19307
colEl.classList.add("tabulator-sortable");
19308
19309
arrowEl = document.createElement("div");
19310
arrowEl.classList.add("tabulator-col-sorter");
19311
19312
switch(this.table.options.headerSortClickElement){
19313
case "icon":
19314
arrowEl.classList.add("tabulator-col-sorter-element");
19315
break;
19316
case "header":
19317
colEl.classList.add("tabulator-col-sorter-element");
19318
break;
19319
default:
19320
colEl.classList.add("tabulator-col-sorter-element");
19321
break;
19322
}
19323
19324
switch(this.table.options.headerSortElement){
19325
case "function":
19326
//do nothing
19327
break;
19328
19329
case "object":
19330
arrowEl.appendChild(this.table.options.headerSortElement);
19331
break;
19332
19333
default:
19334
arrowEl.innerHTML = this.table.options.headerSortElement;
19335
}
19336
19337
//create sorter arrow
19338
column.titleHolderElement.appendChild(arrowEl);
19339
19340
column.modules.sort.element = arrowEl;
19341
19342
this.setColumnHeaderSortIcon(column, "none");
19343
19344
//sort on click
19345
(this.table.options.headerSortClickElement === "icon" ? arrowEl : colEl).addEventListener("click", (e) => {
19346
var dir = "",
19347
sorters=[],
19348
match = false;
19349
19350
if(column.modules.sort){
19351
if(column.modules.sort.tristate){
19352
if(column.modules.sort.dir == "none"){
19353
dir = column.modules.sort.startingDir;
19354
}else {
19355
if(column.modules.sort.dir == column.modules.sort.startingDir){
19356
dir = column.modules.sort.dir == "asc" ? "desc" : "asc";
19357
}else {
19358
dir = "none";
19359
}
19360
}
19361
}else {
19362
switch(column.modules.sort.dir){
19363
case "asc":
19364
dir = "desc";
19365
break;
19366
19367
case "desc":
19368
dir = "asc";
19369
break;
19370
19371
default:
19372
dir = column.modules.sort.startingDir;
19373
}
19374
}
19375
19376
if (this.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) {
19377
sorters = this.getSort();
19378
19379
match = sorters.findIndex((sorter) => {
19380
return sorter.field === column.getField();
19381
});
19382
19383
if(match > -1){
19384
sorters[match].dir = dir;
19385
19386
match = sorters.splice(match, 1)[0];
19387
if(dir != "none"){
19388
sorters.push(match);
19389
}
19390
}else {
19391
if(dir != "none"){
19392
sorters.push({column:column, dir:dir});
19393
}
19394
}
19395
19396
//add to existing sort
19397
this.setSort(sorters);
19398
}else {
19399
if(dir == "none"){
19400
this.clear();
19401
}else {
19402
//sort by column only
19403
this.setSort(column, dir);
19404
}
19405
19406
}
19407
19408
// this.table.rowManager.sorterRefresh(!this.sortList.length);
19409
this.refreshSort();
19410
}
19411
});
19412
}
19413
}
19414
19415
refreshSort(){
19416
if(this.table.options.sortMode === "remote"){
19417
this.reloadData(null, false, false);
19418
}else {
19419
this.refreshData(true);
19420
}
19421
19422
//TODO - Persist left position of row manager
19423
// left = this.scrollLeft;
19424
// this.scrollHorizontal(left);
19425
}
19426
19427
//check if the sorters have changed since last use
19428
hasChanged(){
19429
var changed = this.changed;
19430
this.changed = false;
19431
return changed;
19432
}
19433
19434
//return current sorters
19435
getSort(){
19436
var self = this,
19437
sorters = [];
19438
19439
self.sortList.forEach(function(item){
19440
if(item.column){
19441
sorters.push({column:item.column.getComponent(), field:item.column.getField(), dir:item.dir});
19442
}
19443
});
19444
19445
return sorters;
19446
}
19447
19448
//change sort list and trigger sort
19449
setSort(sortList, dir){
19450
var self = this,
19451
newSortList = [];
19452
19453
if(!Array.isArray(sortList)){
19454
sortList = [{column: sortList, dir:dir}];
19455
}
19456
19457
sortList.forEach(function(item){
19458
var column;
19459
19460
column = self.table.columnManager.findColumn(item.column);
19461
19462
if(column){
19463
item.column = column;
19464
newSortList.push(item);
19465
self.changed = true;
19466
}else {
19467
console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column);
19468
}
19469
19470
});
19471
19472
self.sortList = newSortList;
19473
19474
this.dispatch("sort-changed");
19475
}
19476
19477
//clear sorters
19478
clear(){
19479
this.setSort([]);
19480
}
19481
19482
//find appropriate sorter for column
19483
findSorter(column){
19484
var row = this.table.rowManager.activeRows[0],
19485
sorter = "string",
19486
field, value;
19487
19488
if(row){
19489
row = row.getData();
19490
field = column.getField();
19491
19492
if(field){
19493
19494
value = column.getFieldValue(row);
19495
19496
switch(typeof value){
19497
case "undefined":
19498
sorter = "string";
19499
break;
19500
19501
case "boolean":
19502
sorter = "boolean";
19503
break;
19504
19505
default:
19506
if(!isNaN(value) && value !== ""){
19507
sorter = "number";
19508
}else {
19509
if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
19510
sorter = "alphanum";
19511
}
19512
}
19513
break;
19514
}
19515
}
19516
}
19517
19518
return Sort.sorters[sorter];
19519
}
19520
19521
//work through sort list sorting data
19522
sort(data){
19523
var self = this,
19524
sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList,
19525
sortListActual = [],
19526
rowComponents = [];
19527
19528
if(this.subscribedExternal("dataSorting")){
19529
this.dispatchExternal("dataSorting", self.getSort());
19530
}
19531
19532
self.clearColumnHeaders();
19533
19534
if(this.table.options.sortMode !== "remote"){
19535
19536
//build list of valid sorters and trigger column specific callbacks before sort begins
19537
sortList.forEach(function(item, i){
19538
var sortObj;
19539
19540
if(item.column){
19541
sortObj = item.column.modules.sort;
19542
19543
if(sortObj){
19544
19545
//if no sorter has been defined, take a guess
19546
if(!sortObj.sorter){
19547
sortObj.sorter = self.findSorter(item.column);
19548
}
19549
19550
item.params = typeof sortObj.params === "function" ? sortObj.params(item.column.getComponent(), item.dir) : sortObj.params;
19551
19552
sortListActual.push(item);
19553
}
19554
19555
self.setColumnHeader(item.column, item.dir);
19556
}
19557
});
19558
19559
//sort data
19560
if (sortListActual.length) {
19561
self._sortItems(data, sortListActual);
19562
}
19563
19564
}else {
19565
sortList.forEach(function(item, i){
19566
self.setColumnHeader(item.column, item.dir);
19567
});
19568
}
19569
19570
if(this.subscribedExternal("dataSorted")){
19571
data.forEach((row) => {
19572
rowComponents.push(row.getComponent());
19573
});
19574
19575
this.dispatchExternal("dataSorted", self.getSort(), rowComponents);
19576
}
19577
19578
return data;
19579
}
19580
19581
//clear sort arrows on columns
19582
clearColumnHeaders(){
19583
this.table.columnManager.getRealColumns().forEach((column) => {
19584
if(column.modules.sort){
19585
column.modules.sort.dir = "none";
19586
column.getElement().setAttribute("aria-sort", "none");
19587
this.setColumnHeaderSortIcon(column, "none");
19588
}
19589
});
19590
}
19591
19592
//set the column header sort direction
19593
setColumnHeader(column, dir){
19594
column.modules.sort.dir = dir;
19595
column.getElement().setAttribute("aria-sort", dir === "asc" ? "ascending" : "descending");
19596
this.setColumnHeaderSortIcon(column, dir);
19597
}
19598
19599
setColumnHeaderSortIcon(column, dir){
19600
var sortEl = column.modules.sort.element,
19601
arrowEl;
19602
19603
if(column.definition.headerSort && typeof this.table.options.headerSortElement === "function"){
19604
while(sortEl.firstChild) sortEl.removeChild(sortEl.firstChild);
19605
19606
arrowEl = this.table.options.headerSortElement.call(this.table, column.getComponent(), dir);
19607
19608
if(typeof arrowEl === "object"){
19609
sortEl.appendChild(arrowEl);
19610
}else {
19611
sortEl.innerHTML = arrowEl;
19612
}
19613
}
19614
}
19615
19616
//sort each item in sort list
19617
_sortItems(data, sortList){
19618
var sorterCount = sortList.length - 1;
19619
19620
data.sort((a, b) => {
19621
var result;
19622
19623
for(var i = sorterCount; i>= 0; i--){
19624
let sortItem = sortList[i];
19625
19626
result = this._sortRow(a, b, sortItem.column, sortItem.dir, sortItem.params);
19627
19628
if(result !== 0){
19629
break;
19630
}
19631
}
19632
19633
return result;
19634
});
19635
}
19636
19637
//process individual rows for a sort function on active data
19638
_sortRow(a, b, column, dir, params){
19639
var el1Comp, el2Comp;
19640
19641
//switch elements depending on search direction
19642
var el1 = dir == "asc" ? a : b;
19643
var el2 = dir == "asc" ? b : a;
19644
19645
a = column.getFieldValue(el1.getData());
19646
b = column.getFieldValue(el2.getData());
19647
19648
a = typeof a !== "undefined" ? a : "";
19649
b = typeof b !== "undefined" ? b : "";
19650
19651
el1Comp = el1.getComponent();
19652
el2Comp = el2.getComponent();
19653
19654
return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params);
19655
}
19656
}
19657
19658
Sort.moduleName = "sort";
19659
19660
//load defaults
19661
Sort.sorters = defaultSorters;
19662
19663
class Tooltip extends Module{
19664
19665
constructor(table){
19666
super(table);
19667
19668
this.tooltipSubscriber = null,
19669
this.headerSubscriber = null,
19670
19671
this.timeout = null;
19672
this.popupInstance = null;
19673
19674
this.registerTableOption("tooltipGenerationMode", undefined); //deprecated
19675
this.registerTableOption("tooltipDelay", 300);
19676
19677
this.registerColumnOption("tooltip");
19678
this.registerColumnOption("headerTooltip");
19679
}
19680
19681
initialize(){
19682
this.deprecatedOptionsCheck();
19683
19684
this.subscribe("column-init", this.initializeColumn.bind(this));
19685
}
19686
19687
deprecatedOptionsCheck(){
19688
this.deprecationCheckMsg("tooltipGenerationMode", "This option is no longer needed as tooltips are always generated on hover now");
19689
}
19690
19691
initializeColumn(column){
19692
if(column.definition.headerTooltip && !this.headerSubscriber){
19693
this.headerSubscriber = true;
19694
19695
this.subscribe("column-mousemove", this.mousemoveCheck.bind(this, "headerTooltip"));
19696
this.subscribe("column-mouseout", this.mouseoutCheck.bind(this, "headerTooltip"));
19697
}
19698
19699
if(column.definition.tooltip && !this.tooltipSubscriber){
19700
this.tooltipSubscriber = true;
19701
19702
this.subscribe("cell-mousemove", this.mousemoveCheck.bind(this, "tooltip"));
19703
this.subscribe("cell-mouseout", this.mouseoutCheck.bind(this, "tooltip"));
19704
}
19705
}
19706
19707
mousemoveCheck(action, e, component){
19708
var tooltip = action === "tooltip" ? component.column.definition.tooltip : component.definition.headerTooltip;
19709
19710
if(tooltip){
19711
this.clearPopup();
19712
this.timeout = setTimeout(this.loadTooltip.bind(this, e, component, tooltip), this.table.options.tooltipDelay);
19713
}
19714
}
19715
19716
mouseoutCheck(action, e, component){
19717
if(!this.popupInstance){
19718
this.clearPopup();
19719
}
19720
}
19721
19722
clearPopup(action, e, component){
19723
clearTimeout(this.timeout);
19724
this.timeout = null;
19725
19726
if(this.popupInstance){
19727
this.popupInstance.hide();
19728
}
19729
}
19730
19731
loadTooltip(e, component, tooltip){
19732
var contentsEl, renderedCallback, coords;
19733
19734
function onRendered(callback){
19735
renderedCallback = callback;
19736
}
19737
19738
if(typeof tooltip === "function"){
19739
tooltip = tooltip(e, component.getComponent(), onRendered);
19740
}
19741
19742
if(tooltip instanceof HTMLElement){
19743
contentsEl = tooltip;
19744
}else {
19745
contentsEl = document.createElement("div");
19746
19747
if(tooltip === true){
19748
if(component instanceof Cell){
19749
tooltip = component.value;
19750
}else {
19751
if(component.definition.field){
19752
this.langBind("columns|" + component.definition.field, (value) => {
19753
contentsEl.innerHTML = tooltip = value || component.definition.title;
19754
});
19755
}else {
19756
tooltip = component.definition.title;
19757
}
19758
}
19759
}
19760
19761
contentsEl.innerHTML = tooltip;
19762
}
19763
19764
if(tooltip || tooltip === 0 || tooltip === false){
19765
contentsEl.classList.add("tabulator-tooltip");
19766
19767
contentsEl.addEventListener("mousemove", e => e.preventDefault());
19768
19769
this.popupInstance = this.popup(contentsEl);
19770
19771
if(typeof renderedCallback === "function"){
19772
this.popupInstance.renderCallback(renderedCallback);
19773
}
19774
19775
coords = this.popupInstance.containerEventCoords(e);
19776
19777
this.popupInstance.show(coords.x + 15, coords.y + 15).hideOnBlur(() => {
19778
this.dispatchExternal("TooltipClosed", component.getComponent());
19779
this.popupInstance = null;
19780
});
19781
19782
this.dispatchExternal("TooltipOpened", component.getComponent());
19783
}
19784
}
19785
}
19786
19787
Tooltip.moduleName = "tooltip";
19788
19789
var defaultValidators = {
19790
//is integer
19791
integer: function(cell, value, parameters){
19792
if(value === "" || value === null || typeof value === "undefined"){
19793
return true;
19794
}
19795
19796
value = Number(value);
19797
19798
return !isNaN(value) && isFinite(value) && Math.floor(value) === value;
19799
},
19800
19801
//is float
19802
float: function(cell, value, parameters){
19803
if(value === "" || value === null || typeof value === "undefined"){
19804
return true;
19805
}
19806
19807
value = Number(value);
19808
19809
return !isNaN(value) && isFinite(value) && value % 1 !== 0;
19810
},
19811
19812
//must be a number
19813
numeric: function(cell, value, parameters){
19814
if(value === "" || value === null || typeof value === "undefined"){
19815
return true;
19816
}
19817
return !isNaN(value);
19818
},
19819
19820
//must be a string
19821
string: function(cell, value, parameters){
19822
if(value === "" || value === null || typeof value === "undefined"){
19823
return true;
19824
}
19825
return isNaN(value);
19826
},
19827
19828
//maximum value
19829
max: function(cell, value, parameters){
19830
if(value === "" || value === null || typeof value === "undefined"){
19831
return true;
19832
}
19833
return parseFloat(value) <= parameters;
19834
},
19835
19836
//minimum value
19837
min: function(cell, value, parameters){
19838
if(value === "" || value === null || typeof value === "undefined"){
19839
return true;
19840
}
19841
return parseFloat(value) >= parameters;
19842
},
19843
19844
//starts with value
19845
starts: function(cell, value, parameters){
19846
if(value === "" || value === null || typeof value === "undefined"){
19847
return true;
19848
}
19849
return String(value).toLowerCase().startsWith(String(parameters).toLowerCase());
19850
},
19851
19852
//ends with value
19853
ends: function(cell, value, parameters){
19854
if(value === "" || value === null || typeof value === "undefined"){
19855
return true;
19856
}
19857
return String(value).toLowerCase().endsWith(String(parameters).toLowerCase());
19858
},
19859
19860
19861
//minimum string length
19862
minLength: function(cell, value, parameters){
19863
if(value === "" || value === null || typeof value === "undefined"){
19864
return true;
19865
}
19866
return String(value).length >= parameters;
19867
},
19868
19869
//maximum string length
19870
maxLength: function(cell, value, parameters){
19871
if(value === "" || value === null || typeof value === "undefined"){
19872
return true;
19873
}
19874
return String(value).length <= parameters;
19875
},
19876
19877
//in provided value list
19878
in: function(cell, value, parameters){
19879
if(value === "" || value === null || typeof value === "undefined"){
19880
return true;
19881
}
19882
19883
if(typeof parameters == "string"){
19884
parameters = parameters.split("|");
19885
}
19886
19887
return parameters.indexOf(value) > -1;
19888
},
19889
19890
//must match provided regex
19891
regex: function(cell, value, parameters){
19892
if(value === "" || value === null || typeof value === "undefined"){
19893
return true;
19894
}
19895
var reg = new RegExp(parameters);
19896
19897
return reg.test(value);
19898
},
19899
19900
//value must be unique in this column
19901
unique: function(cell, value, parameters){
19902
if(value === "" || value === null || typeof value === "undefined"){
19903
return true;
19904
}
19905
var unique = true;
19906
19907
var cellData = cell.getData();
19908
var column = cell.getColumn()._getSelf();
19909
19910
this.table.rowManager.rows.forEach(function(row){
19911
var data = row.getData();
19912
19913
if(data !== cellData){
19914
if(value == column.getFieldValue(data)){
19915
unique = false;
19916
}
19917
}
19918
});
19919
19920
return unique;
19921
},
19922
19923
//must have a value
19924
required:function(cell, value, parameters){
19925
return value !== "" && value !== null && typeof value !== "undefined";
19926
},
19927
};
19928
19929
class Validate extends Module{
19930
19931
constructor(table){
19932
super(table);
19933
19934
this.invalidCells = [];
19935
19936
this.registerTableOption("validationMode", "blocking");
19937
19938
this.registerColumnOption("validator");
19939
19940
this.registerTableFunction("getInvalidCells", this.getInvalidCells.bind(this));
19941
this.registerTableFunction("clearCellValidation", this.userClearCellValidation.bind(this));
19942
this.registerTableFunction("validate", this.userValidate.bind(this));
19943
19944
this.registerComponentFunction("cell", "isValid", this.cellIsValid.bind(this));
19945
this.registerComponentFunction("cell", "clearValidation", this.clearValidation.bind(this));
19946
this.registerComponentFunction("cell", "validate", this.cellValidate.bind(this));
19947
19948
this.registerComponentFunction("column", "validate", this.columnValidate.bind(this));
19949
this.registerComponentFunction("row", "validate", this.rowValidate.bind(this));
19950
}
19951
19952
19953
initialize(){
19954
this.subscribe("cell-delete", this.clearValidation.bind(this));
19955
this.subscribe("column-layout", this.initializeColumnCheck.bind(this));
19956
19957
this.subscribe("edit-success", this.editValidate.bind(this));
19958
this.subscribe("edit-editor-clear", this.editorClear.bind(this));
19959
this.subscribe("edit-edited-clear", this.editedClear.bind(this));
19960
}
19961
19962
///////////////////////////////////
19963
///////// Event Handling //////////
19964
///////////////////////////////////
19965
19966
editValidate(cell, value, previousValue){
19967
var valid = this.table.options.validationMode !== "manual" ? this.validate(cell.column.modules.validate, cell, value) : true;
19968
19969
// allow time for editor to make render changes then style cell
19970
if(valid !== true){
19971
setTimeout(() => {
19972
cell.getElement().classList.add("tabulator-validation-fail");
19973
this.dispatchExternal("validationFailed", cell.getComponent(), value, valid);
19974
});
19975
}
19976
19977
return valid;
19978
}
19979
19980
editorClear(cell, cancelled){
19981
if(cancelled){
19982
if(cell.column.modules.validate){
19983
this.cellValidate(cell);
19984
}
19985
}
19986
19987
cell.getElement().classList.remove("tabulator-validation-fail");
19988
}
19989
19990
editedClear(cell){
19991
if(cell.modules.validate){
19992
cell.modules.validate.invalid = false;
19993
}
19994
}
19995
19996
///////////////////////////////////
19997
////////// Cell Functions /////////
19998
///////////////////////////////////
19999
20000
cellIsValid(cell){
20001
return cell.modules.validate ? (cell.modules.validate.invalid || true) : true;
20002
}
20003
20004
cellValidate(cell){
20005
return this.validate(cell.column.modules.validate, cell, cell.getValue());
20006
}
20007
20008
///////////////////////////////////
20009
///////// Column Functions ////////
20010
///////////////////////////////////
20011
20012
columnValidate(column){
20013
var invalid = [];
20014
20015
column.cells.forEach((cell) => {
20016
if(this.cellValidate(cell) !== true){
20017
invalid.push(cell.getComponent());
20018
}
20019
});
20020
20021
return invalid.length ? invalid : true;
20022
}
20023
20024
///////////////////////////////////
20025
////////// Row Functions //////////
20026
///////////////////////////////////
20027
20028
rowValidate(row){
20029
var invalid = [];
20030
20031
row.cells.forEach((cell) => {
20032
if(this.cellValidate(cell) !== true){
20033
invalid.push(cell.getComponent());
20034
}
20035
});
20036
20037
return invalid.length ? invalid : true;
20038
}
20039
20040
///////////////////////////////////
20041
///////// Table Functions /////////
20042
///////////////////////////////////
20043
20044
20045
userClearCellValidation(cells){
20046
if(!cells){
20047
cells = this.getInvalidCells();
20048
}
20049
20050
if(!Array.isArray(cells)){
20051
cells = [cells];
20052
}
20053
20054
cells.forEach((cell) => {
20055
this.clearValidation(cell._getSelf());
20056
});
20057
}
20058
20059
userValidate(cells){
20060
var output = [];
20061
20062
//clear row data
20063
this.table.rowManager.rows.forEach((row) => {
20064
row = row.getComponent();
20065
20066
var valid = row.validate();
20067
20068
if(valid !== true){
20069
output = output.concat(valid);
20070
}
20071
});
20072
20073
return output.length ? output : true;
20074
}
20075
20076
///////////////////////////////////
20077
///////// Internal Logic //////////
20078
///////////////////////////////////
20079
20080
initializeColumnCheck(column){
20081
if(typeof column.definition.validator !== "undefined"){
20082
this.initializeColumn(column);
20083
}
20084
}
20085
20086
//validate
20087
initializeColumn(column){
20088
var self = this,
20089
config = [],
20090
validator;
20091
20092
if(column.definition.validator){
20093
20094
if(Array.isArray(column.definition.validator)){
20095
column.definition.validator.forEach((item) => {
20096
validator = self._extractValidator(item);
20097
20098
if(validator){
20099
config.push(validator);
20100
}
20101
});
20102
20103
}else {
20104
validator = this._extractValidator(column.definition.validator);
20105
20106
if(validator){
20107
config.push(validator);
20108
}
20109
}
20110
20111
column.modules.validate = config.length ? config : false;
20112
}
20113
}
20114
20115
_extractValidator(value){
20116
var type, params, pos;
20117
20118
switch(typeof value){
20119
case "string":
20120
pos = value.indexOf(':');
20121
20122
if(pos > -1){
20123
type = value.substring(0,pos);
20124
params = value.substring(pos+1);
20125
}else {
20126
type = value;
20127
}
20128
20129
return this._buildValidator(type, params);
20130
20131
case "function":
20132
return this._buildValidator(value);
20133
20134
case "object":
20135
return this._buildValidator(value.type, value.parameters);
20136
}
20137
}
20138
20139
_buildValidator(type, params){
20140
20141
var func = typeof type == "function" ? type : Validate.validators[type];
20142
20143
if(!func){
20144
console.warn("Validator Setup Error - No matching validator found:", type);
20145
return false;
20146
}else {
20147
return {
20148
type:typeof type == "function" ? "function" : type,
20149
func:func,
20150
params:params,
20151
};
20152
}
20153
}
20154
20155
validate(validators, cell, value){
20156
var self = this,
20157
failedValidators = [],
20158
invalidIndex = this.invalidCells.indexOf(cell);
20159
20160
if(validators){
20161
validators.forEach((item) => {
20162
if(!item.func.call(self, cell.getComponent(), value, item.params)){
20163
failedValidators.push({
20164
type:item.type,
20165
parameters:item.params
20166
});
20167
}
20168
});
20169
}
20170
20171
if(!cell.modules.validate){
20172
cell.modules.validate = {};
20173
}
20174
20175
if(!failedValidators.length){
20176
cell.modules.validate.invalid = false;
20177
cell.getElement().classList.remove("tabulator-validation-fail");
20178
20179
if(invalidIndex > -1){
20180
this.invalidCells.splice(invalidIndex, 1);
20181
}
20182
}else {
20183
cell.modules.validate.invalid = failedValidators;
20184
20185
if(this.table.options.validationMode !== "manual"){
20186
cell.getElement().classList.add("tabulator-validation-fail");
20187
}
20188
20189
if(invalidIndex == -1){
20190
this.invalidCells.push(cell);
20191
}
20192
}
20193
20194
return failedValidators.length ? failedValidators : true;
20195
}
20196
20197
getInvalidCells(){
20198
var output = [];
20199
20200
this.invalidCells.forEach((cell) => {
20201
output.push(cell.getComponent());
20202
});
20203
20204
return output;
20205
}
20206
20207
clearValidation(cell){
20208
var invalidIndex;
20209
20210
if(cell.modules.validate && cell.modules.validate.invalid){
20211
20212
cell.getElement().classList.remove("tabulator-validation-fail");
20213
cell.modules.validate.invalid = false;
20214
20215
invalidIndex = this.invalidCells.indexOf(cell);
20216
20217
if(invalidIndex > -1){
20218
this.invalidCells.splice(invalidIndex, 1);
20219
}
20220
}
20221
}
20222
}
20223
20224
Validate.moduleName = "validate";
20225
20226
//load defaults
20227
Validate.validators = defaultValidators;
20228
20229
var modules = /*#__PURE__*/Object.freeze({
20230
__proto__: null,
20231
AccessorModule: Accessor,
20232
AjaxModule: Ajax,
20233
ClipboardModule: Clipboard,
20234
ColumnCalcsModule: ColumnCalcs,
20235
DataTreeModule: DataTree,
20236
DownloadModule: Download,
20237
EditModule: Edit$1,
20238
ExportModule: Export,
20239
FilterModule: Filter,
20240
FormatModule: Format,
20241
FrozenColumnsModule: FrozenColumns,
20242
FrozenRowsModule: FrozenRows,
20243
GroupRowsModule: GroupRows,
20244
HistoryModule: History,
20245
HtmlTableImportModule: HtmlTableImport,
20246
ImportModule: Import,
20247
InteractionModule: Interaction,
20248
KeybindingsModule: Keybindings,
20249
MenuModule: Menu,
20250
MoveColumnsModule: MoveColumns,
20251
MoveRowsModule: MoveRows,
20252
MutatorModule: Mutator,
20253
PageModule: Page,
20254
PersistenceModule: Persistence,
20255
PopupModule: Popup$1,
20256
PrintModule: Print,
20257
ReactiveDataModule: ReactiveData,
20258
ResizeColumnsModule: ResizeColumns,
20259
ResizeRowsModule: ResizeRows,
20260
ResizeTableModule: ResizeTable,
20261
ResponsiveLayoutModule: ResponsiveLayout,
20262
SelectRowModule: SelectRow,
20263
SortModule: Sort,
20264
TooltipModule: Tooltip,
20265
ValidateModule: Validate
20266
});
20267
20268
var defaultOptions = {
20269
20270
debugEventsExternal:false, //flag to console log events
20271
debugEventsInternal:false, //flag to console log events
20272
debugInvalidOptions:true, //allow toggling of invalid option warnings
20273
debugInvalidComponentFuncs:true, //allow toggling of invalid component warnings
20274
debugInitialization:true, //allow toggling of pre initialization function call warnings
20275
debugDeprecation:true, //allow toggling of deprecation warnings
20276
20277
height:false, //height of tabulator
20278
minHeight:false, //minimum height of tabulator
20279
maxHeight:false, //maximum height of tabulator
20280
20281
columnHeaderVertAlign:"top", //vertical alignment of column headers
20282
20283
popupContainer:false,
20284
20285
columns:[],//store for colum header info
20286
columnDefaults:{}, //store column default props
20287
20288
data:false, //default starting data
20289
20290
autoColumns:false, //build columns from data row structure
20291
autoColumnsDefinitions:false,
20292
20293
nestedFieldSeparator:".", //separator for nested data
20294
20295
footerElement:false, //hold footer element
20296
20297
index:"id", //filed for row index
20298
20299
textDirection:"auto",
20300
20301
addRowPos:"bottom", //position to insert blank rows, top|bottom
20302
20303
headerVisible:true, //hide header
20304
20305
renderVertical:"virtual",
20306
renderHorizontal:"basic",
20307
renderVerticalBuffer:0, // set virtual DOM buffer size
20308
20309
scrollToRowPosition:"top",
20310
scrollToRowIfVisible:true,
20311
20312
scrollToColumnPosition:"left",
20313
scrollToColumnIfVisible:true,
20314
20315
rowFormatter:false,
20316
rowFormatterPrint:null,
20317
rowFormatterClipboard:null,
20318
rowFormatterHtmlOutput:null,
20319
20320
rowHeight:null,
20321
20322
placeholder:false,
20323
20324
dataLoader:true,
20325
dataLoaderLoading:false,
20326
dataLoaderError:false,
20327
dataLoaderErrorTimeout:3000,
20328
20329
dataSendParams:{},
20330
20331
dataReceiveParams:{},
20332
};
20333
20334
class OptionsList {
20335
constructor(table, msgType, defaults = {}){
20336
this.table = table;
20337
this.msgType = msgType;
20338
this.registeredDefaults = Object.assign({}, defaults);
20339
}
20340
20341
register(option, value){
20342
this.registeredDefaults[option] = value;
20343
}
20344
20345
generate(defaultOptions, userOptions = {}){
20346
var output = Object.assign({}, this.registeredDefaults),
20347
warn = this.table.options.debugInvalidOptions || userOptions.debugInvalidOptions === true;
20348
20349
Object.assign(output, defaultOptions);
20350
20351
for (let key in userOptions){
20352
if(!output.hasOwnProperty(key)){
20353
if(warn){
20354
console.warn("Invalid " + this.msgType + " option:", key);
20355
}
20356
20357
output[key] = userOptions.key;
20358
}
20359
}
20360
20361
20362
for (let key in output){
20363
if(key in userOptions){
20364
output[key] = userOptions[key];
20365
}else {
20366
if(Array.isArray(output[key])){
20367
output[key] = Object.assign([], output[key]);
20368
}else if(typeof output[key] === "object" && output[key] !== null){
20369
output[key] = Object.assign({}, output[key]);
20370
}else if (typeof output[key] === "undefined"){
20371
delete output[key];
20372
}
20373
}
20374
}
20375
20376
return output;
20377
}
20378
}
20379
20380
class Renderer extends CoreFeature{
20381
constructor(table){
20382
super(table);
20383
20384
this.elementVertical = table.rowManager.element;
20385
this.elementHorizontal = table.columnManager.element;
20386
this.tableElement = table.rowManager.tableElement;
20387
20388
this.verticalFillMode = "fit"; // used by row manager to determine how to size the render area ("fit" - fits container to the contents, "fill" - fills the container without resizing it)
20389
}
20390
20391
20392
///////////////////////////////////
20393
/////// Internal Bindings /////////
20394
///////////////////////////////////
20395
20396
initialize(){
20397
//initialize core functionality
20398
}
20399
20400
clearRows(){
20401
//clear down existing rows layout
20402
}
20403
20404
clearColumns(){
20405
//clear down existing columns layout
20406
}
20407
20408
20409
reinitializeColumnWidths(columns){
20410
//resize columns to fit data
20411
}
20412
20413
20414
renderRows(){
20415
//render rows from a clean slate
20416
}
20417
20418
renderColumns(){
20419
//render columns from a clean slate
20420
}
20421
20422
rerenderRows(callback){
20423
// rerender rows and keep position
20424
if(callback){
20425
callback();
20426
}
20427
}
20428
20429
rerenderColumns(update, blockRedraw){
20430
//rerender columns
20431
}
20432
20433
renderRowCells(row){
20434
//render the cells in a row
20435
}
20436
20437
rerenderRowCells(row, force){
20438
//rerender the cells in a row
20439
}
20440
20441
scrollColumns(left, dir){
20442
//handle horizontal scrolling
20443
}
20444
20445
scrollRows(top, dir){
20446
//handle vertical scrolling
20447
}
20448
20449
resize(){
20450
//container has resized, carry out any needed recalculations (DO NOT RERENDER IN THIS FUNCTION)
20451
}
20452
20453
scrollToRow(row){
20454
//scroll to a specific row
20455
}
20456
20457
scrollToRowNearestTop(row){
20458
//determine weather the row is nearest the top or bottom of the table, return true for top or false for bottom
20459
}
20460
20461
visibleRows(includingBuffer){
20462
//return the visible rows
20463
return [];
20464
}
20465
20466
///////////////////////////////////
20467
//////// Helper Functions /////////
20468
///////////////////////////////////
20469
20470
rows(){
20471
return this.table.rowManager.getDisplayRows();
20472
}
20473
20474
styleRow(row, index){
20475
var rowEl = row.getElement();
20476
20477
if(index % 2){
20478
rowEl.classList.add("tabulator-row-even");
20479
rowEl.classList.remove("tabulator-row-odd");
20480
}else {
20481
rowEl.classList.add("tabulator-row-odd");
20482
rowEl.classList.remove("tabulator-row-even");
20483
}
20484
}
20485
20486
///////////////////////////////////
20487
/////// External Triggers /////////
20488
/////// (DO NOT OVERRIDE) /////////
20489
///////////////////////////////////
20490
20491
clear(){
20492
//clear down existing layout
20493
this.clearRows();
20494
this.clearColumns();
20495
}
20496
20497
render(){
20498
//render from a clean slate
20499
this.renderRows();
20500
this.renderColumns();
20501
}
20502
20503
rerender(callback){
20504
// rerender and keep position
20505
this.rerenderRows();
20506
this.rerenderColumns();
20507
}
20508
20509
scrollToRowPosition(row, position, ifVisible){
20510
var rowIndex = this.rows().indexOf(row),
20511
rowEl = row.getElement(),
20512
offset = 0;
20513
20514
return new Promise((resolve, reject) => {
20515
if(rowIndex > -1){
20516
20517
if(typeof ifVisible === "undefined"){
20518
ifVisible = this.table.options.scrollToRowIfVisible;
20519
}
20520
20521
//check row visibility
20522
if(!ifVisible){
20523
if(Helpers.elVisible(rowEl)){
20524
offset = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top;
20525
20526
if(offset > 0 && offset < this.elementVertical.clientHeight - rowEl.offsetHeight){
20527
resolve();
20528
return false;
20529
}
20530
}
20531
}
20532
20533
if(typeof position === "undefined"){
20534
position = this.table.options.scrollToRowPosition;
20535
}
20536
20537
if(position === "nearest"){
20538
position = this.scrollToRowNearestTop(row) ? "top" : "bottom";
20539
}
20540
20541
//scroll to row
20542
this.scrollToRow(row);
20543
20544
//align to correct position
20545
switch(position){
20546
case "middle":
20547
case "center":
20548
20549
if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){
20550
this.elementVertical.scrollTop = this.elementVertical.scrollTop + (rowEl.offsetTop - this.elementVertical.scrollTop) - ((this.elementVertical.scrollHeight - rowEl.offsetTop) / 2);
20551
}else {
20552
this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.clientHeight / 2);
20553
}
20554
20555
break;
20556
20557
case "bottom":
20558
20559
if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){
20560
this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.scrollHeight - rowEl.offsetTop) + rowEl.offsetHeight;
20561
}else {
20562
this.elementVertical.scrollTop = this.elementVertical.scrollTop - this.elementVertical.clientHeight + rowEl.offsetHeight;
20563
}
20564
20565
break;
20566
20567
case "top":
20568
this.elementVertical.scrollTop = rowEl.offsetTop;
20569
break;
20570
}
20571
20572
resolve();
20573
20574
}else {
20575
console.warn("Scroll Error - Row not visible");
20576
reject("Scroll Error - Row not visible");
20577
}
20578
});
20579
}
20580
}
20581
20582
class BasicHorizontal extends Renderer{
20583
constructor(table){
20584
super(table);
20585
}
20586
20587
renderRowCells(row, inFragment) {
20588
const rowFrag = document.createDocumentFragment();
20589
row.cells.forEach((cell) => {
20590
rowFrag.appendChild(cell.getElement());
20591
});
20592
row.element.appendChild(rowFrag);
20593
20594
if(!inFragment){
20595
row.cells.forEach((cell) => {
20596
cell.cellRendered();
20597
});
20598
}
20599
}
20600
20601
reinitializeColumnWidths(columns){
20602
columns.forEach(function(column){
20603
column.reinitializeWidth();
20604
});
20605
}
20606
}
20607
20608
class VirtualDomHorizontal extends Renderer{
20609
constructor(table){
20610
super(table);
20611
20612
this.leftCol = 0;
20613
this.rightCol = 0;
20614
this.scrollLeft = 0;
20615
20616
this.vDomScrollPosLeft = 0;
20617
this.vDomScrollPosRight = 0;
20618
20619
this.vDomPadLeft = 0;
20620
this.vDomPadRight = 0;
20621
20622
this.fitDataColAvg = 0;
20623
20624
this.windowBuffer = 200; //pixel margin to make column visible before it is shown on screen
20625
20626
this.visibleRows = null;
20627
20628
this.initialized = false;
20629
this.isFitData = false;
20630
20631
this.columns = [];
20632
}
20633
20634
initialize(){
20635
this.compatibilityCheck();
20636
this.layoutCheck();
20637
this.vertScrollListen();
20638
}
20639
20640
compatibilityCheck(){
20641
if(this.options("layout") == "fitDataTable"){
20642
console.warn("Horizontal Virtual DOM is not compatible with fitDataTable layout mode");
20643
}
20644
20645
if(this.options("responsiveLayout")){
20646
console.warn("Horizontal Virtual DOM is not compatible with responsive columns");
20647
}
20648
20649
if(this.options("rtl")){
20650
console.warn("Horizontal Virtual DOM is not currently compatible with RTL text direction");
20651
}
20652
}
20653
20654
layoutCheck(){
20655
this.isFitData = this.options("layout").startsWith('fitData');
20656
}
20657
20658
vertScrollListen(){
20659
this.subscribe("scroll-vertical", this.clearVisRowCache.bind(this));
20660
this.subscribe("data-refreshed", this.clearVisRowCache.bind(this));
20661
}
20662
20663
clearVisRowCache(){
20664
this.visibleRows = null;
20665
}
20666
20667
//////////////////////////////////////
20668
///////// Public Functions ///////////
20669
//////////////////////////////////////
20670
20671
renderColumns(row, force){
20672
this.dataChange();
20673
}
20674
20675
20676
scrollColumns(left, dir){
20677
if(this.scrollLeft != left){
20678
this.scrollLeft = left;
20679
20680
this.scroll(left - (this.vDomScrollPosLeft + this.windowBuffer));
20681
}
20682
}
20683
20684
calcWindowBuffer(){
20685
var buffer = this.elementVertical.clientWidth;
20686
20687
this.table.columnManager.columnsByIndex.forEach((column) => {
20688
if(column.visible){
20689
var width = column.getWidth();
20690
20691
if(width > buffer){
20692
buffer = width;
20693
}
20694
}
20695
});
20696
20697
this.windowBuffer = buffer * 2;
20698
}
20699
20700
rerenderColumns(update, blockRedraw){
20701
var old = {
20702
cols:this.columns,
20703
leftCol:this.leftCol,
20704
rightCol:this.rightCol,
20705
},
20706
colPos = 0;
20707
20708
if(update && !this.initialized){
20709
return;
20710
}
20711
20712
this.clear();
20713
20714
this.calcWindowBuffer();
20715
20716
this.scrollLeft = this.elementVertical.scrollLeft;
20717
20718
this.vDomScrollPosLeft = this.scrollLeft - this.windowBuffer;
20719
this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer;
20720
20721
this.table.columnManager.columnsByIndex.forEach((column) => {
20722
var config = {},
20723
width;
20724
20725
if(column.visible){
20726
if(!column.modules.frozen){
20727
width = column.getWidth();
20728
20729
config.leftPos = colPos;
20730
config.rightPos = colPos + width;
20731
20732
config.width = width;
20733
20734
if (this.isFitData) {
20735
config.fitDataCheck = column.modules.vdomHoz ? column.modules.vdomHoz.fitDataCheck : true;
20736
}
20737
20738
if((colPos + width > this.vDomScrollPosLeft) && (colPos < this.vDomScrollPosRight)){
20739
//column is visible
20740
20741
if(this.leftCol == -1){
20742
this.leftCol = this.columns.length;
20743
this.vDomPadLeft = colPos;
20744
}
20745
20746
this.rightCol = this.columns.length;
20747
}else {
20748
// column is hidden
20749
if(this.leftCol !== -1){
20750
this.vDomPadRight += width;
20751
}
20752
}
20753
20754
this.columns.push(column);
20755
20756
column.modules.vdomHoz = config;
20757
20758
colPos += width;
20759
}
20760
}
20761
});
20762
20763
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
20764
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
20765
20766
this.initialized = true;
20767
20768
if(!blockRedraw){
20769
if(!update || this.reinitChanged(old)){
20770
this.reinitializeRows();
20771
}
20772
}
20773
20774
this.elementVertical.scrollLeft = this.scrollLeft;
20775
}
20776
20777
renderRowCells(row){
20778
if(this.initialized){
20779
this.initializeRow(row);
20780
}else {
20781
const rowFrag = document.createDocumentFragment();
20782
row.cells.forEach((cell) => {
20783
rowFrag.appendChild(cell.getElement());
20784
});
20785
row.element.appendChild(rowFrag);
20786
20787
row.cells.forEach((cell) => {
20788
cell.cellRendered();
20789
});
20790
}
20791
}
20792
20793
rerenderRowCells(row, force){
20794
this.reinitializeRow(row, force);
20795
}
20796
20797
reinitializeColumnWidths(columns){
20798
for(let i = this.leftCol; i <= this.rightCol; i++){
20799
this.columns[i].reinitializeWidth();
20800
}
20801
}
20802
20803
//////////////////////////////////////
20804
//////// Internal Rendering //////////
20805
//////////////////////////////////////
20806
20807
deinitialize(){
20808
this.initialized = false;
20809
}
20810
20811
clear(){
20812
this.columns = [];
20813
20814
this.leftCol = -1;
20815
this.rightCol = 0;
20816
20817
this.vDomScrollPosLeft = 0;
20818
this.vDomScrollPosRight = 0;
20819
this.vDomPadLeft = 0;
20820
this.vDomPadRight = 0;
20821
}
20822
20823
dataChange(){
20824
var change = false,
20825
row, rowEl;
20826
20827
if(this.isFitData){
20828
this.table.columnManager.columnsByIndex.forEach((column) => {
20829
if(!column.definition.width && column.visible){
20830
change = true;
20831
}
20832
});
20833
20834
if(change && this.table.rowManager.getDisplayRows().length){
20835
this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer;
20836
20837
row = this.chain("rows-sample", [1], [], () => {
20838
return this.table.rowManager.getDisplayRows();
20839
})[0];
20840
20841
if(row){
20842
rowEl = row.getElement();
20843
20844
row.generateCells();
20845
20846
this.tableElement.appendChild(rowEl);
20847
20848
for(let colEnd = 0; colEnd < row.cells.length; colEnd++){
20849
let cell = row.cells[colEnd];
20850
rowEl.appendChild(cell.getElement());
20851
20852
cell.column.reinitializeWidth();
20853
}
20854
20855
rowEl.parentNode.removeChild(rowEl);
20856
20857
this.rerenderColumns(false, true);
20858
}
20859
}
20860
}else {
20861
if(this.options("layout") === "fitColumns"){
20862
this.layoutRefresh();
20863
this.rerenderColumns(false, true);
20864
}
20865
}
20866
}
20867
20868
reinitChanged(old){
20869
var match = true;
20870
20871
if(old.cols.length !== this.columns.length || old.leftCol !== this.leftCol || old.rightCol !== this.rightCol){
20872
return true;
20873
}
20874
20875
old.cols.forEach((col, i) => {
20876
if(col !== this.columns[i]){
20877
match = false;
20878
}
20879
});
20880
20881
return !match;
20882
}
20883
20884
reinitializeRows(){
20885
var visibleRows = this.getVisibleRows(),
20886
otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row));
20887
20888
visibleRows.forEach((row) => {
20889
this.reinitializeRow(row, true);
20890
});
20891
20892
otherRows.forEach((row) =>{
20893
row.deinitialize();
20894
});
20895
}
20896
20897
getVisibleRows(){
20898
if (!this.visibleRows){
20899
this.visibleRows = this.table.rowManager.getVisibleRows();
20900
}
20901
20902
return this.visibleRows;
20903
}
20904
20905
scroll(diff){
20906
this.vDomScrollPosLeft += diff;
20907
this.vDomScrollPosRight += diff;
20908
20909
if(Math.abs(diff) > (this.windowBuffer / 2)){
20910
this.rerenderColumns();
20911
}else {
20912
if(diff > 0){
20913
//scroll right
20914
this.addColRight();
20915
this.removeColLeft();
20916
}else {
20917
//scroll left
20918
this.addColLeft();
20919
this.removeColRight();
20920
}
20921
}
20922
}
20923
20924
colPositionAdjust (start, end, diff){
20925
for(let i = start; i < end; i++){
20926
let column = this.columns[i];
20927
20928
column.modules.vdomHoz.leftPos += diff;
20929
column.modules.vdomHoz.rightPos += diff;
20930
}
20931
}
20932
20933
addColRight(){
20934
var changes = false,
20935
working = true;
20936
20937
while(working){
20938
20939
let column = this.columns[this.rightCol + 1];
20940
20941
if(column){
20942
if(column.modules.vdomHoz.leftPos <= this.vDomScrollPosRight){
20943
changes = true;
20944
20945
this.getVisibleRows().forEach((row) => {
20946
if(row.type !== "group"){
20947
var cell = row.getCell(column);
20948
row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.rightCol]).getElement().nextSibling);
20949
cell.cellRendered();
20950
}
20951
});
20952
20953
this.fitDataColActualWidthCheck(column);
20954
20955
this.rightCol++; // Don't move this below the >= check below
20956
20957
this.getVisibleRows().forEach((row) => {
20958
if(row.type !== "group"){
20959
row.modules.vdomHoz.rightCol = this.rightCol;
20960
}
20961
});
20962
20963
if(this.rightCol >= (this.columns.length - 1)){
20964
this.vDomPadRight = 0;
20965
}else {
20966
this.vDomPadRight -= column.getWidth();
20967
}
20968
}else {
20969
working = false;
20970
}
20971
}else {
20972
working = false;
20973
}
20974
}
20975
20976
if(changes){
20977
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
20978
}
20979
}
20980
20981
addColLeft(){
20982
var changes = false,
20983
working = true;
20984
20985
while(working){
20986
let column = this.columns[this.leftCol - 1];
20987
20988
if(column){
20989
if(column.modules.vdomHoz.rightPos >= this.vDomScrollPosLeft){
20990
changes = true;
20991
20992
this.getVisibleRows().forEach((row) => {
20993
if(row.type !== "group"){
20994
var cell = row.getCell(column);
20995
row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.leftCol]).getElement());
20996
cell.cellRendered();
20997
}
20998
});
20999
21000
this.leftCol--; // don't move this below the <= check below
21001
21002
this.getVisibleRows().forEach((row) => {
21003
if(row.type !== "group"){
21004
row.modules.vdomHoz.leftCol = this.leftCol;
21005
}
21006
});
21007
21008
if(this.leftCol <= 0){ // replicating logic in addColRight
21009
this.vDomPadLeft = 0;
21010
}else {
21011
this.vDomPadLeft -= column.getWidth();
21012
}
21013
21014
let diff = this.fitDataColActualWidthCheck(column);
21015
21016
if(diff){
21017
this.scrollLeft = this.elementVertical.scrollLeft = this.elementVertical.scrollLeft + diff;
21018
this.vDomPadRight -= diff;
21019
}
21020
21021
}else {
21022
working = false;
21023
}
21024
}else {
21025
working = false;
21026
}
21027
}
21028
21029
if(changes){
21030
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
21031
}
21032
}
21033
21034
removeColRight(){
21035
var changes = false,
21036
working = true;
21037
21038
while(working){
21039
let column = this.columns[this.rightCol];
21040
21041
if(column){
21042
if(column.modules.vdomHoz.leftPos > this.vDomScrollPosRight){
21043
changes = true;
21044
21045
this.getVisibleRows().forEach((row) => {
21046
if(row.type !== "group"){
21047
var cell = row.getCell(column);
21048
21049
try {
21050
row.getElement().removeChild(cell.getElement());
21051
} catch (ex) {
21052
console.warn("Could not removeColRight", ex.message);
21053
}
21054
}
21055
});
21056
21057
this.vDomPadRight += column.getWidth();
21058
this.rightCol --;
21059
21060
this.getVisibleRows().forEach((row) => {
21061
if(row.type !== "group"){
21062
row.modules.vdomHoz.rightCol = this.rightCol;
21063
}
21064
});
21065
}else {
21066
working = false;
21067
}
21068
}else {
21069
working = false;
21070
}
21071
}
21072
21073
if(changes){
21074
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
21075
}
21076
}
21077
21078
removeColLeft(){
21079
var changes = false,
21080
working = true;
21081
21082
while(working){
21083
let column = this.columns[this.leftCol];
21084
21085
if(column){
21086
if(column.modules.vdomHoz.rightPos < this.vDomScrollPosLeft){
21087
changes = true;
21088
21089
this.getVisibleRows().forEach((row) => {
21090
if(row.type !== "group"){
21091
var cell = row.getCell(column);
21092
21093
try {
21094
row.getElement().removeChild(cell.getElement());
21095
} catch (ex) {
21096
console.warn("Could not removeColLeft", ex.message);
21097
}
21098
}
21099
});
21100
21101
this.vDomPadLeft += column.getWidth();
21102
this.leftCol ++;
21103
21104
this.getVisibleRows().forEach((row) => {
21105
if(row.type !== "group"){
21106
row.modules.vdomHoz.leftCol = this.leftCol;
21107
}
21108
});
21109
}else {
21110
working = false;
21111
}
21112
}else {
21113
working = false;
21114
}
21115
}
21116
21117
if(changes){
21118
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
21119
}
21120
}
21121
21122
fitDataColActualWidthCheck(column){
21123
var newWidth, widthDiff;
21124
21125
if(column.modules.vdomHoz.fitDataCheck){
21126
column.reinitializeWidth();
21127
21128
newWidth = column.getWidth();
21129
widthDiff = newWidth - column.modules.vdomHoz.width;
21130
21131
if(widthDiff){
21132
column.modules.vdomHoz.rightPos += widthDiff;
21133
column.modules.vdomHoz.width = newWidth;
21134
this.colPositionAdjust(this.columns.indexOf(column) + 1, this.columns.length, widthDiff);
21135
}
21136
21137
column.modules.vdomHoz.fitDataCheck = false;
21138
}
21139
21140
return widthDiff;
21141
}
21142
21143
initializeRow(row){
21144
if(row.type !== "group"){
21145
row.modules.vdomHoz = {
21146
leftCol:this.leftCol,
21147
rightCol:this.rightCol,
21148
};
21149
21150
if(this.table.modules.frozenColumns){
21151
this.table.modules.frozenColumns.leftColumns.forEach((column) => {
21152
this.appendCell(row, column);
21153
});
21154
}
21155
21156
for(let i = this.leftCol; i <= this.rightCol; i++){
21157
this.appendCell(row, this.columns[i]);
21158
}
21159
21160
if(this.table.modules.frozenColumns){
21161
this.table.modules.frozenColumns.rightColumns.forEach((column) => {
21162
this.appendCell(row, column);
21163
});
21164
}
21165
}
21166
}
21167
21168
appendCell(row, column){
21169
if(column && column.visible){
21170
let cell = row.getCell(column);
21171
21172
row.getElement().appendChild(cell.getElement());
21173
cell.cellRendered();
21174
}
21175
}
21176
21177
reinitializeRow(row, force){
21178
if(row.type !== "group"){
21179
if(force || !row.modules.vdomHoz || row.modules.vdomHoz.leftCol !== this.leftCol || row.modules.vdomHoz.rightCol !== this.rightCol){
21180
21181
var rowEl = row.getElement();
21182
while(rowEl.firstChild) rowEl.removeChild(rowEl.firstChild);
21183
21184
this.initializeRow(row);
21185
}
21186
}
21187
}
21188
}
21189
21190
class ColumnManager extends CoreFeature {
21191
21192
constructor (table){
21193
super(table);
21194
21195
this.blockHozScrollEvent = false;
21196
this.headersElement = null;
21197
this.contentsElement = null;
21198
this.element = null ; //containing element
21199
this.columns = []; // column definition object
21200
this.columnsByIndex = []; //columns by index
21201
this.columnsByField = {}; //columns by field
21202
this.scrollLeft = 0;
21203
this.optionsList = new OptionsList(this.table, "column definition", defaultColumnOptions);
21204
21205
this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing
21206
this.redrawBlockUpdate = null; //store latest redraw update only status
21207
21208
this.renderer = null;
21209
}
21210
21211
////////////// Setup Functions /////////////////
21212
21213
initialize(){
21214
this.initializeRenderer();
21215
21216
this.headersElement = this.createHeadersElement();
21217
this.contentsElement = this.createHeaderContentsElement();
21218
this.element = this.createHeaderElement();
21219
21220
this.contentsElement.insertBefore(this.headersElement, this.contentsElement.firstChild);
21221
this.element.insertBefore(this.contentsElement, this.element.firstChild);
21222
21223
this.initializeScrollWheelWatcher();
21224
21225
this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this));
21226
this.subscribe("scrollbar-vertical", this.padVerticalScrollbar.bind(this));
21227
}
21228
21229
padVerticalScrollbar(width){
21230
if(this.table.rtl){
21231
this.headersElement.style.marginLeft = width + "px";
21232
}else {
21233
this.headersElement.style.marginRight = width + "px";
21234
}
21235
}
21236
21237
initializeRenderer(){
21238
var renderClass;
21239
21240
var renderers = {
21241
"virtual": VirtualDomHorizontal,
21242
"basic": BasicHorizontal,
21243
};
21244
21245
if(typeof this.table.options.renderHorizontal === "string"){
21246
renderClass = renderers[this.table.options.renderHorizontal];
21247
}else {
21248
renderClass = this.table.options.renderHorizontal;
21249
}
21250
21251
if(renderClass){
21252
this.renderer = new renderClass(this.table, this.element, this.tableElement);
21253
this.renderer.initialize();
21254
}else {
21255
console.error("Unable to find matching renderer:", this.table.options.renderHorizontal);
21256
}
21257
}
21258
21259
21260
createHeadersElement (){
21261
var el = document.createElement("div");
21262
21263
el.classList.add("tabulator-headers");
21264
el.setAttribute("role", "row");
21265
21266
return el;
21267
}
21268
21269
createHeaderContentsElement (){
21270
var el = document.createElement("div");
21271
21272
el.classList.add("tabulator-header-contents");
21273
el.setAttribute("role", "rowgroup");
21274
21275
return el;
21276
}
21277
21278
createHeaderElement (){
21279
var el = document.createElement("div");
21280
21281
el.classList.add("tabulator-header");
21282
el.setAttribute("role", "rowgroup");
21283
21284
if(!this.table.options.headerVisible){
21285
el.classList.add("tabulator-header-hidden");
21286
}
21287
21288
return el;
21289
}
21290
21291
//return containing element
21292
getElement(){
21293
return this.element;
21294
}
21295
21296
//return containing contents element
21297
getContentsElement(){
21298
return this.contentsElement;
21299
}
21300
21301
21302
//return header containing element
21303
getHeadersElement(){
21304
return this.headersElement;
21305
}
21306
21307
//scroll horizontally to match table body
21308
scrollHorizontal(left){
21309
this.contentsElement.scrollLeft = left;
21310
21311
this.scrollLeft = left;
21312
21313
this.renderer.scrollColumns(left);
21314
}
21315
21316
initializeScrollWheelWatcher(){
21317
this.contentsElement.addEventListener("wheel", (e) => {
21318
var left;
21319
21320
if(e.deltaX){
21321
left = this.contentsElement.scrollLeft + e.deltaX;
21322
21323
this.table.rowManager.scrollHorizontal(left);
21324
this.table.columnManager.scrollHorizontal(left);
21325
}
21326
});
21327
}
21328
21329
///////////// Column Setup Functions /////////////
21330
generateColumnsFromRowData(data){
21331
var cols = [],
21332
definitions = this.table.options.autoColumnsDefinitions,
21333
row, sorter;
21334
21335
if(data && data.length){
21336
21337
row = data[0];
21338
21339
for(var key in row){
21340
let col = {
21341
field:key,
21342
title:key,
21343
};
21344
21345
let value = row[key];
21346
21347
switch(typeof value){
21348
case "undefined":
21349
sorter = "string";
21350
break;
21351
21352
case "boolean":
21353
sorter = "boolean";
21354
break;
21355
21356
case "object":
21357
if(Array.isArray(value)){
21358
sorter = "array";
21359
}else {
21360
sorter = "string";
21361
}
21362
break;
21363
21364
default:
21365
if(!isNaN(value) && value !== ""){
21366
sorter = "number";
21367
}else {
21368
if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
21369
sorter = "alphanum";
21370
}else {
21371
sorter = "string";
21372
}
21373
}
21374
break;
21375
}
21376
21377
col.sorter = sorter;
21378
21379
cols.push(col);
21380
}
21381
21382
if(definitions){
21383
21384
switch(typeof definitions){
21385
case "function":
21386
this.table.options.columns = definitions.call(this.table, cols);
21387
break;
21388
21389
case "object":
21390
if(Array.isArray(definitions)){
21391
cols.forEach((col) => {
21392
var match = definitions.find((def) => {
21393
return def.field === col.field;
21394
});
21395
21396
if(match){
21397
Object.assign(col, match);
21398
}
21399
});
21400
21401
}else {
21402
cols.forEach((col) => {
21403
if(definitions[col.field]){
21404
Object.assign(col, definitions[col.field]);
21405
}
21406
});
21407
}
21408
21409
this.table.options.columns = cols;
21410
break;
21411
}
21412
}else {
21413
this.table.options.columns = cols;
21414
}
21415
21416
this.setColumns(this.table.options.columns);
21417
}
21418
}
21419
21420
setColumns(cols, row){
21421
while(this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild);
21422
21423
this.columns = [];
21424
this.columnsByIndex = [];
21425
this.columnsByField = {};
21426
21427
this.dispatch("columns-loading");
21428
21429
cols.forEach((def, i) => {
21430
this._addColumn(def);
21431
});
21432
21433
this._reIndexColumns();
21434
21435
this.dispatch("columns-loaded");
21436
21437
this.rerenderColumns(false, true);
21438
21439
this.redraw(true);
21440
}
21441
21442
_addColumn(definition, before, nextToColumn){
21443
var column = new Column(definition, this),
21444
colEl = column.getElement(),
21445
index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn;
21446
21447
if(nextToColumn && index > -1){
21448
var topColumn = nextToColumn.getTopColumn();
21449
var parentIndex = this.columns.indexOf(topColumn);
21450
var nextEl = topColumn.getElement();
21451
21452
if(before){
21453
this.columns.splice(parentIndex, 0, column);
21454
nextEl.parentNode.insertBefore(colEl, nextEl);
21455
}else {
21456
this.columns.splice(parentIndex + 1, 0, column);
21457
nextEl.parentNode.insertBefore(colEl, nextEl.nextSibling);
21458
}
21459
}else {
21460
if(before){
21461
this.columns.unshift(column);
21462
this.headersElement.insertBefore(column.getElement(), this.headersElement.firstChild);
21463
}else {
21464
this.columns.push(column);
21465
this.headersElement.appendChild(column.getElement());
21466
}
21467
}
21468
21469
column.columnRendered();
21470
21471
return column;
21472
}
21473
21474
registerColumnField(col){
21475
if(col.definition.field){
21476
this.columnsByField[col.definition.field] = col;
21477
}
21478
}
21479
21480
registerColumnPosition(col){
21481
this.columnsByIndex.push(col);
21482
}
21483
21484
_reIndexColumns(){
21485
this.columnsByIndex = [];
21486
21487
this.columns.forEach(function(column){
21488
column.reRegisterPosition();
21489
});
21490
}
21491
21492
//ensure column headers take up the correct amount of space in column groups
21493
verticalAlignHeaders(){
21494
var minHeight = 0;
21495
21496
if(!this.redrawBlock){
21497
21498
this.headersElement.style.height="";
21499
21500
this.columns.forEach((column) => {
21501
column.clearVerticalAlign();
21502
});
21503
21504
this.columns.forEach((column) => {
21505
var height = column.getHeight();
21506
21507
if(height > minHeight){
21508
minHeight = height;
21509
}
21510
});
21511
21512
this.headersElement.style.height = minHeight + "px";
21513
21514
this.columns.forEach((column) => {
21515
column.verticalAlign(this.table.options.columnHeaderVertAlign, minHeight);
21516
});
21517
21518
this.table.rowManager.adjustTableSize();
21519
}
21520
}
21521
21522
//////////////// Column Details /////////////////
21523
findColumn(subject){
21524
var columns;
21525
21526
if(typeof subject == "object"){
21527
21528
if(subject instanceof Column){
21529
//subject is column element
21530
return subject;
21531
}else if(subject instanceof ColumnComponent){
21532
//subject is public column component
21533
return subject._getSelf() || false;
21534
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
21535
21536
columns = [];
21537
21538
this.columns.forEach((column) => {
21539
columns.push(column);
21540
columns = columns.concat(column.getColumns(true));
21541
});
21542
21543
//subject is a HTML element of the column header
21544
let match = columns.find((column) => {
21545
return column.element === subject;
21546
});
21547
21548
return match || false;
21549
}
21550
21551
}else {
21552
//subject should be treated as the field name of the column
21553
return this.columnsByField[subject] || false;
21554
}
21555
21556
//catch all for any other type of input
21557
return false;
21558
}
21559
21560
getColumnByField(field){
21561
return this.columnsByField[field];
21562
}
21563
21564
getColumnsByFieldRoot(root){
21565
var matches = [];
21566
21567
Object.keys(this.columnsByField).forEach((field) => {
21568
var fieldRoot = field.split(".")[0];
21569
if(fieldRoot === root){
21570
matches.push(this.columnsByField[field]);
21571
}
21572
});
21573
21574
return matches;
21575
}
21576
21577
getColumnByIndex(index){
21578
return this.columnsByIndex[index];
21579
}
21580
21581
getFirstVisibleColumn(){
21582
var index = this.columnsByIndex.findIndex((col) => {
21583
return col.visible;
21584
});
21585
21586
return index > -1 ? this.columnsByIndex[index] : false;
21587
}
21588
21589
getColumns(){
21590
return this.columns;
21591
}
21592
21593
findColumnIndex(column){
21594
return this.columnsByIndex.findIndex((col) => {
21595
return column === col;
21596
});
21597
}
21598
21599
//return all columns that are not groups
21600
getRealColumns(){
21601
return this.columnsByIndex;
21602
}
21603
21604
//traverse across columns and call action
21605
traverse(callback){
21606
this.columnsByIndex.forEach((column,i) =>{
21607
callback(column, i);
21608
});
21609
}
21610
21611
//get definitions of actual columns
21612
getDefinitions(active){
21613
var output = [];
21614
21615
this.columnsByIndex.forEach((column) => {
21616
if(!active || (active && column.visible)){
21617
output.push(column.getDefinition());
21618
}
21619
});
21620
21621
return output;
21622
}
21623
21624
//get full nested definition tree
21625
getDefinitionTree(){
21626
var output = [];
21627
21628
this.columns.forEach((column) => {
21629
output.push(column.getDefinition(true));
21630
});
21631
21632
return output;
21633
}
21634
21635
getComponents(structured){
21636
var output = [],
21637
columns = structured ? this.columns : this.columnsByIndex;
21638
21639
columns.forEach((column) => {
21640
output.push(column.getComponent());
21641
});
21642
21643
return output;
21644
}
21645
21646
getWidth(){
21647
var width = 0;
21648
21649
this.columnsByIndex.forEach((column) => {
21650
if(column.visible){
21651
width += column.getWidth();
21652
}
21653
});
21654
21655
return width;
21656
}
21657
21658
moveColumn(from, to, after){
21659
to.element.parentNode.insertBefore(from.element, to.element);
21660
21661
if(after){
21662
to.element.parentNode.insertBefore(to.element, from.element);
21663
}
21664
21665
this.moveColumnActual(from, to, after);
21666
21667
this.verticalAlignHeaders();
21668
21669
this.table.rowManager.reinitialize();
21670
}
21671
21672
moveColumnActual(from, to, after){
21673
if(from.parent.isGroup){
21674
this._moveColumnInArray(from.parent.columns, from, to, after);
21675
}else {
21676
this._moveColumnInArray(this.columns, from, to, after);
21677
}
21678
21679
this._moveColumnInArray(this.columnsByIndex, from, to, after, true);
21680
21681
this.rerenderColumns(true);
21682
21683
this.dispatch("column-moved", from, to, after);
21684
21685
if(this.subscribedExternal("columnMoved")){
21686
this.dispatchExternal("columnMoved", from.getComponent(), this.table.columnManager.getComponents());
21687
}
21688
}
21689
21690
_moveColumnInArray(columns, from, to, after, updateRows){
21691
var fromIndex = columns.indexOf(from),
21692
toIndex, rows = [];
21693
21694
if (fromIndex > -1) {
21695
21696
columns.splice(fromIndex, 1);
21697
21698
toIndex = columns.indexOf(to);
21699
21700
if (toIndex > -1) {
21701
21702
if(after){
21703
toIndex = toIndex+1;
21704
}
21705
21706
}else {
21707
toIndex = fromIndex;
21708
}
21709
21710
columns.splice(toIndex, 0, from);
21711
21712
if(updateRows){
21713
21714
rows = this.chain("column-moving-rows", [from, to, after], null, []) || [];
21715
21716
rows = rows.concat(this.table.rowManager.rows);
21717
21718
rows.forEach(function(row){
21719
if(row.cells.length){
21720
var cell = row.cells.splice(fromIndex, 1)[0];
21721
row.cells.splice(toIndex, 0, cell);
21722
}
21723
});
21724
21725
}
21726
}
21727
}
21728
21729
scrollToColumn(column, position, ifVisible){
21730
var left = 0,
21731
offset = column.getLeftOffset(),
21732
adjust = 0,
21733
colEl = column.getElement();
21734
21735
21736
return new Promise((resolve, reject) => {
21737
21738
if(typeof position === "undefined"){
21739
position = this.table.options.scrollToColumnPosition;
21740
}
21741
21742
if(typeof ifVisible === "undefined"){
21743
ifVisible = this.table.options.scrollToColumnIfVisible;
21744
}
21745
21746
if(column.visible){
21747
21748
//align to correct position
21749
switch(position){
21750
case "middle":
21751
case "center":
21752
adjust = -this.element.clientWidth / 2;
21753
break;
21754
21755
case "right":
21756
adjust = colEl.clientWidth - this.headersElement.clientWidth;
21757
break;
21758
}
21759
21760
//check column visibility
21761
if(!ifVisible){
21762
if(offset > 0 && offset + colEl.offsetWidth < this.element.clientWidth){
21763
return false;
21764
}
21765
}
21766
21767
//calculate scroll position
21768
left = offset + adjust;
21769
21770
left = Math.max(Math.min(left, this.table.rowManager.element.scrollWidth - this.table.rowManager.element.clientWidth),0);
21771
21772
this.table.rowManager.scrollHorizontal(left);
21773
this.scrollHorizontal(left);
21774
21775
resolve();
21776
}else {
21777
console.warn("Scroll Error - Column not visible");
21778
reject("Scroll Error - Column not visible");
21779
}
21780
21781
});
21782
}
21783
21784
//////////////// Cell Management /////////////////
21785
generateCells(row){
21786
var cells = [];
21787
21788
this.columnsByIndex.forEach((column) => {
21789
cells.push(column.generateCell(row));
21790
});
21791
21792
return cells;
21793
}
21794
21795
//////////////// Column Management /////////////////
21796
getFlexBaseWidth(){
21797
var totalWidth = this.table.element.clientWidth, //table element width
21798
fixedWidth = 0;
21799
21800
//adjust for vertical scrollbar if present
21801
if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){
21802
totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth;
21803
}
21804
21805
this.columnsByIndex.forEach(function(column){
21806
var width, minWidth, colWidth;
21807
21808
if(column.visible){
21809
21810
width = column.definition.width || 0;
21811
21812
minWidth = parseInt(column.minWidth);
21813
21814
if(typeof(width) == "string"){
21815
if(width.indexOf("%") > -1){
21816
colWidth = (totalWidth / 100) * parseInt(width) ;
21817
}else {
21818
colWidth = parseInt(width);
21819
}
21820
}else {
21821
colWidth = width;
21822
}
21823
21824
fixedWidth += colWidth > minWidth ? colWidth : minWidth;
21825
21826
}
21827
});
21828
21829
return fixedWidth;
21830
}
21831
21832
addColumn(definition, before, nextToColumn){
21833
return new Promise((resolve, reject) => {
21834
var column = this._addColumn(definition, before, nextToColumn);
21835
21836
this._reIndexColumns();
21837
21838
this.dispatch("column-add", definition, before, nextToColumn);
21839
21840
if(this.layoutMode() != "fitColumns"){
21841
column.reinitializeWidth();
21842
}
21843
21844
this.redraw(true);
21845
21846
this.table.rowManager.reinitialize();
21847
21848
this.rerenderColumns();
21849
21850
resolve(column);
21851
});
21852
}
21853
21854
//remove column from system
21855
deregisterColumn(column){
21856
var field = column.getField(),
21857
index;
21858
21859
//remove from field list
21860
if(field){
21861
delete this.columnsByField[field];
21862
}
21863
21864
//remove from index list
21865
index = this.columnsByIndex.indexOf(column);
21866
21867
if(index > -1){
21868
this.columnsByIndex.splice(index, 1);
21869
}
21870
21871
//remove from column list
21872
index = this.columns.indexOf(column);
21873
21874
if(index > -1){
21875
this.columns.splice(index, 1);
21876
}
21877
21878
this.verticalAlignHeaders();
21879
21880
this.redraw();
21881
}
21882
21883
rerenderColumns(update, silent){
21884
if(!this.redrawBlock){
21885
this.renderer.rerenderColumns(update, silent);
21886
}else {
21887
if(update === false || (update === true && this.redrawBlockUpdate === null)){
21888
this.redrawBlockUpdate = update;
21889
}
21890
}
21891
}
21892
21893
blockRedraw(){
21894
this.redrawBlock = true;
21895
this.redrawBlockUpdate = null;
21896
}
21897
21898
restoreRedraw(){
21899
this.redrawBlock = false;
21900
this.verticalAlignHeaders();
21901
this.renderer.rerenderColumns(this.redrawBlockUpdate);
21902
21903
}
21904
21905
//redraw columns
21906
redraw(force){
21907
if(Helpers.elVisible(this.element)){
21908
this.verticalAlignHeaders();
21909
}
21910
21911
if(force){
21912
this.table.rowManager.resetScroll();
21913
this.table.rowManager.reinitialize();
21914
}
21915
21916
if(!this.confirm("table-redrawing", force)){
21917
this.layoutRefresh(force);
21918
}
21919
21920
this.dispatch("table-redraw", force);
21921
21922
this.table.footerManager.redraw();
21923
}
21924
}
21925
21926
class BasicVertical extends Renderer{
21927
constructor(table){
21928
super(table);
21929
21930
this.verticalFillMode = "fill";
21931
21932
this.scrollTop = 0;
21933
this.scrollLeft = 0;
21934
21935
this.scrollTop = 0;
21936
this.scrollLeft = 0;
21937
}
21938
21939
clearRows(){
21940
var element = this.tableElement;
21941
21942
// element.children.detach();
21943
while(element.firstChild) element.removeChild(element.firstChild);
21944
21945
element.scrollTop = 0;
21946
element.scrollLeft = 0;
21947
21948
element.style.minWidth = "";
21949
element.style.minHeight = "";
21950
element.style.display = "";
21951
element.style.visibility = "";
21952
}
21953
21954
renderRows() {
21955
var element = this.tableElement,
21956
onlyGroupHeaders = true,
21957
tableFrag = document.createDocumentFragment(),
21958
rows = this.rows();
21959
21960
rows.forEach((row, index) => {
21961
this.styleRow(row, index);
21962
row.initialize(false, true);
21963
21964
if (row.type !== "group") {
21965
onlyGroupHeaders = false;
21966
}
21967
21968
tableFrag.appendChild(row.getElement());
21969
});
21970
21971
element.appendChild(tableFrag);
21972
21973
rows.forEach((row) => {
21974
row.rendered();
21975
21976
if(!row.heightInitialized) {
21977
row.calcHeight(true);
21978
}
21979
});
21980
21981
rows.forEach((row) => {
21982
if(!row.heightInitialized) {
21983
row.setCellHeight();
21984
}
21985
});
21986
21987
21988
21989
if(onlyGroupHeaders){
21990
element.style.minWidth = this.table.columnManager.getWidth() + "px";
21991
}else {
21992
element.style.minWidth = "";
21993
}
21994
}
21995
21996
21997
rerenderRows(callback){
21998
this.clearRows();
21999
22000
if(callback){
22001
callback();
22002
}
22003
22004
this.renderRows();
22005
}
22006
22007
scrollToRowNearestTop(row){
22008
var rowTop = Helpers.elOffset(row.getElement()).top;
22009
22010
return !(Math.abs(this.elementVertical.scrollTop - rowTop) > Math.abs(this.elementVertical.scrollTop + this.elementVertical.clientHeight - rowTop));
22011
}
22012
22013
scrollToRow(row){
22014
var rowEl = row.getElement();
22015
22016
this.elementVertical.scrollTop = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top + this.elementVertical.scrollTop;
22017
}
22018
22019
visibleRows(includingBuffer){
22020
return this.rows();
22021
}
22022
22023
}
22024
22025
class VirtualDomVertical extends Renderer{
22026
constructor(table){
22027
super(table);
22028
22029
this.verticalFillMode = "fill";
22030
22031
this.scrollTop = 0;
22032
this.scrollLeft = 0;
22033
22034
this.vDomRowHeight = 20; //approximation of row heights for padding
22035
22036
this.vDomTop = 0; //hold position for first rendered row in the virtual DOM
22037
this.vDomBottom = 0; //hold position for last rendered row in the virtual DOM
22038
22039
this.vDomScrollPosTop = 0; //last scroll position of the vDom top;
22040
this.vDomScrollPosBottom = 0; //last scroll position of the vDom bottom;
22041
22042
this.vDomTopPad = 0; //hold value of padding for top of virtual DOM
22043
this.vDomBottomPad = 0; //hold value of padding for bottom of virtual DOM
22044
22045
this.vDomMaxRenderChain = 90; //the maximum number of dom elements that can be rendered in 1 go
22046
22047
this.vDomWindowBuffer = 0; //window row buffer before removing elements, to smooth scrolling
22048
22049
this.vDomWindowMinTotalRows = 20; //minimum number of rows to be generated in virtual dom (prevent buffering issues on tables with tall rows)
22050
this.vDomWindowMinMarginRows = 5; //minimum number of rows to be generated in virtual dom margin
22051
22052
this.vDomTopNewRows = []; //rows to normalize after appending to optimize render speed
22053
this.vDomBottomNewRows = []; //rows to normalize after appending to optimize render speed
22054
}
22055
22056
//////////////////////////////////////
22057
///////// Public Functions ///////////
22058
//////////////////////////////////////
22059
22060
clearRows(){
22061
var element = this.tableElement;
22062
22063
// element.children.detach();
22064
while(element.firstChild) element.removeChild(element.firstChild);
22065
22066
element.style.paddingTop = "";
22067
element.style.paddingBottom = "";
22068
element.style.minHeight = "";
22069
element.style.display = "";
22070
element.style.visibility = "";
22071
22072
this.elementVertical.scrollTop = 0;
22073
this.elementVertical.scrollLeft = 0;
22074
22075
this.scrollTop = 0;
22076
this.scrollLeft = 0;
22077
22078
this.vDomTop = 0;
22079
this.vDomBottom = 0;
22080
this.vDomTopPad = 0;
22081
this.vDomBottomPad = 0;
22082
this.vDomScrollPosTop = 0;
22083
this.vDomScrollPosBottom = 0;
22084
}
22085
22086
renderRows(){
22087
this._virtualRenderFill();
22088
}
22089
22090
rerenderRows(callback){
22091
var scrollTop = this.elementVertical.scrollTop;
22092
var topRow = false;
22093
var topOffset = false;
22094
22095
var left = this.table.rowManager.scrollLeft;
22096
22097
var rows = this.rows();
22098
22099
for(var i = this.vDomTop; i <= this.vDomBottom; i++){
22100
22101
if(rows[i]){
22102
var diff = scrollTop - rows[i].getElement().offsetTop;
22103
22104
if(topOffset === false || Math.abs(diff) < topOffset){
22105
topOffset = diff;
22106
topRow = i;
22107
}else {
22108
break;
22109
}
22110
}
22111
}
22112
22113
rows.forEach((row) => {
22114
row.deinitializeHeight();
22115
});
22116
22117
if(callback){
22118
callback();
22119
}
22120
22121
if(this.rows().length){
22122
this._virtualRenderFill((topRow === false ? this.rows.length - 1 : topRow), true, topOffset || 0);
22123
}else {
22124
this.clear();
22125
this.table.rowManager.tableEmpty();
22126
}
22127
22128
this.scrollColumns(left);
22129
}
22130
22131
scrollColumns(left){
22132
this.table.rowManager.scrollHorizontal(left);
22133
}
22134
22135
scrollRows(top, dir){
22136
var topDiff = top - this.vDomScrollPosTop;
22137
var bottomDiff = top - this.vDomScrollPosBottom;
22138
var margin = this.vDomWindowBuffer * 2;
22139
var rows = this.rows();
22140
22141
this.scrollTop = top;
22142
22143
if(-topDiff > margin || bottomDiff > margin){
22144
//if big scroll redraw table;
22145
var left = this.table.rowManager.scrollLeft;
22146
this._virtualRenderFill(Math.floor((this.elementVertical.scrollTop / this.elementVertical.scrollHeight) * rows.length));
22147
this.scrollColumns(left);
22148
}else {
22149
22150
if(dir){
22151
//scrolling up
22152
if(topDiff < 0){
22153
this._addTopRow(rows, -topDiff);
22154
}
22155
22156
if(bottomDiff < 0){
22157
//hide bottom row if needed
22158
if(this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer){
22159
this._removeBottomRow(rows, -bottomDiff);
22160
}else {
22161
this.vDomScrollPosBottom = this.scrollTop;
22162
}
22163
}
22164
}else {
22165
22166
if(bottomDiff >= 0){
22167
this._addBottomRow(rows, bottomDiff);
22168
}
22169
22170
//scrolling down
22171
if(topDiff >= 0){
22172
//hide top row if needed
22173
if(this.scrollTop > this.vDomWindowBuffer){
22174
this._removeTopRow(rows, topDiff);
22175
}else {
22176
this.vDomScrollPosTop = this.scrollTop;
22177
}
22178
}
22179
}
22180
}
22181
}
22182
22183
resize(){
22184
this.vDomWindowBuffer = this.table.options.renderVerticalBuffer || this.elementVertical.clientHeight;
22185
}
22186
22187
scrollToRowNearestTop(row){
22188
var rowIndex = this.rows().indexOf(row);
22189
22190
return !(Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex));
22191
}
22192
22193
scrollToRow(row){
22194
var index = this.rows().indexOf(row);
22195
22196
if(index > -1){
22197
this._virtualRenderFill(index, true);
22198
}
22199
}
22200
22201
visibleRows(includingBuffer){
22202
var topEdge = this.elementVertical.scrollTop,
22203
bottomEdge = this.elementVertical.clientHeight + topEdge,
22204
topFound = false,
22205
topRow = 0,
22206
bottomRow = 0,
22207
rows = this.rows();
22208
22209
if(includingBuffer){
22210
topRow = this.vDomTop;
22211
bottomRow = this.vDomBottom;
22212
}else {
22213
for(var i = this.vDomTop; i <= this.vDomBottom; i++){
22214
if(rows[i]){
22215
if(!topFound){
22216
if((topEdge - rows[i].getElement().offsetTop) >= 0){
22217
topRow = i;
22218
}else {
22219
topFound = true;
22220
22221
if(bottomEdge - rows[i].getElement().offsetTop >= 0){
22222
bottomRow = i;
22223
}else {
22224
break;
22225
}
22226
}
22227
}else {
22228
if(bottomEdge - rows[i].getElement().offsetTop >= 0){
22229
bottomRow = i;
22230
}else {
22231
break;
22232
}
22233
}
22234
}
22235
}
22236
}
22237
22238
return rows.slice(topRow, bottomRow + 1);
22239
}
22240
22241
//////////////////////////////////////
22242
//////// Internal Rendering //////////
22243
//////////////////////////////////////
22244
22245
//full virtual render
22246
_virtualRenderFill(position, forceMove, offset) {
22247
var element = this.tableElement,
22248
holder = this.elementVertical,
22249
topPad = 0,
22250
rowsHeight = 0,
22251
rowHeight = 0,
22252
heightOccupied = 0,
22253
topPadHeight = 0,
22254
i = 0,
22255
rows = this.rows(),
22256
rowsCount = rows.length,
22257
index = 0,
22258
row,
22259
rowFragment,
22260
renderedRows = [],
22261
totalRowsRendered = 0,
22262
rowsToRender = 0,
22263
fixedHeight = this.table.rowManager.fixedHeight,
22264
containerHeight = this.elementVertical.clientHeight,
22265
avgRowHeight = this.table.options.rowHeight,
22266
resized = true;
22267
22268
position = position || 0;
22269
22270
offset = offset || 0;
22271
22272
if(!position){
22273
this.clear();
22274
}else {
22275
while(element.firstChild) element.removeChild(element.firstChild);
22276
22277
//check if position is too close to bottom of table
22278
heightOccupied = (rowsCount - position + 1) * this.vDomRowHeight;
22279
22280
if(heightOccupied < containerHeight){
22281
position -= Math.ceil((containerHeight - heightOccupied) / this.vDomRowHeight);
22282
if(position < 0){
22283
position = 0;
22284
}
22285
}
22286
22287
//calculate initial pad
22288
topPad = Math.min(Math.max(Math.floor(this.vDomWindowBuffer / this.vDomRowHeight), this.vDomWindowMinMarginRows), position);
22289
position -= topPad;
22290
}
22291
22292
if(rowsCount && Helpers.elVisible(this.elementVertical)){
22293
this.vDomTop = position;
22294
this.vDomBottom = position -1;
22295
22296
if(fixedHeight || this.table.options.maxHeight) {
22297
if(avgRowHeight) {
22298
rowsToRender = (containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight);
22299
}
22300
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil(rowsToRender));
22301
}
22302
else {
22303
rowsToRender = rowsCount;
22304
}
22305
22306
while(((rowsToRender == rowsCount || rowsHeight <= containerHeight + this.vDomWindowBuffer) || totalRowsRendered < this.vDomWindowMinTotalRows) && this.vDomBottom < rowsCount -1) {
22307
renderedRows = [];
22308
rowFragment = document.createDocumentFragment();
22309
22310
i = 0;
22311
22312
while ((i < rowsToRender) && this.vDomBottom < rowsCount -1) {
22313
index = this.vDomBottom + 1,
22314
row = rows[index];
22315
22316
this.styleRow(row, index);
22317
22318
row.initialize(false, true);
22319
if(!row.heightInitialized && !this.table.options.rowHeight){
22320
row.clearCellHeight();
22321
}
22322
22323
rowFragment.appendChild(row.getElement());
22324
renderedRows.push(row);
22325
this.vDomBottom ++;
22326
i++;
22327
}
22328
22329
if(!renderedRows.length){
22330
break;
22331
}
22332
22333
element.appendChild(rowFragment);
22334
22335
// NOTE: The next 3 loops are separate on purpose
22336
// This is to batch up the dom writes and reads which drastically improves performance
22337
22338
renderedRows.forEach((row) => {
22339
row.rendered();
22340
22341
if(!row.heightInitialized) {
22342
row.calcHeight(true);
22343
}
22344
});
22345
22346
renderedRows.forEach((row) => {
22347
if(!row.heightInitialized) {
22348
row.setCellHeight();
22349
}
22350
});
22351
22352
renderedRows.forEach((row) => {
22353
rowHeight = row.getHeight();
22354
22355
if(totalRowsRendered < topPad){
22356
topPadHeight += rowHeight;
22357
}else {
22358
rowsHeight += rowHeight;
22359
}
22360
22361
if(rowHeight > this.vDomWindowBuffer){
22362
this.vDomWindowBuffer = rowHeight * 2;
22363
}
22364
totalRowsRendered++;
22365
});
22366
22367
resized = this.table.rowManager.adjustTableSize();
22368
containerHeight = this.elementVertical.clientHeight;
22369
if(resized && (fixedHeight || this.table.options.maxHeight))
22370
{
22371
avgRowHeight = rowsHeight / totalRowsRendered;
22372
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil((containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight)));
22373
}
22374
}
22375
22376
if(!position){
22377
this.vDomTopPad = 0;
22378
//adjust row height to match average of rendered elements
22379
this.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / totalRowsRendered);
22380
this.vDomBottomPad = this.vDomRowHeight * (rowsCount - this.vDomBottom -1);
22381
22382
this.vDomScrollHeight = topPadHeight + rowsHeight + this.vDomBottomPad - containerHeight;
22383
}else {
22384
this.vDomTopPad = !forceMove ? this.scrollTop - topPadHeight : (this.vDomRowHeight * this.vDomTop) + offset;
22385
this.vDomBottomPad = this.vDomBottom == rowsCount-1 ? 0 : Math.max(this.vDomScrollHeight - this.vDomTopPad - rowsHeight - topPadHeight, 0);
22386
}
22387
22388
element.style.paddingTop = this.vDomTopPad+"px";
22389
element.style.paddingBottom = this.vDomBottomPad+"px";
22390
22391
if(forceMove){
22392
this.scrollTop = this.vDomTopPad + (topPadHeight) + offset - (this.elementVertical.scrollWidth > this.elementVertical.clientWidth ? this.elementVertical.offsetHeight - containerHeight : 0);
22393
}
22394
22395
this.scrollTop = Math.min(this.scrollTop, this.elementVertical.scrollHeight - containerHeight);
22396
22397
//adjust for horizontal scrollbar if present (and not at top of table)
22398
if(this.elementVertical.scrollWidth > this.elementVertical.clientWidth && forceMove){
22399
this.scrollTop += this.elementVertical.offsetHeight - containerHeight;
22400
}
22401
22402
this.vDomScrollPosTop = this.scrollTop;
22403
this.vDomScrollPosBottom = this.scrollTop;
22404
22405
holder.scrollTop = this.scrollTop;
22406
22407
this.dispatch("render-virtual-fill");
22408
}
22409
}
22410
22411
_addTopRow(rows, fillableSpace){
22412
var table = this.tableElement,
22413
addedRows = [],
22414
paddingAdjust = 0,
22415
index = this.vDomTop -1,
22416
i = 0,
22417
working = true;
22418
22419
while(working){
22420
if(this.vDomTop){
22421
let row = rows[index],
22422
rowHeight, initialized;
22423
22424
if(row && i < this.vDomMaxRenderChain){
22425
rowHeight = row.getHeight() || this.vDomRowHeight;
22426
initialized = row.initialized;
22427
22428
if(fillableSpace >= rowHeight){
22429
22430
this.styleRow(row, index);
22431
table.insertBefore(row.getElement(), table.firstChild);
22432
22433
if(!row.initialized || !row.heightInitialized){
22434
addedRows.push(row);
22435
}
22436
22437
row.initialize();
22438
22439
if(!initialized){
22440
rowHeight = row.getElement().offsetHeight;
22441
22442
if(rowHeight > this.vDomWindowBuffer){
22443
this.vDomWindowBuffer = rowHeight * 2;
22444
}
22445
}
22446
22447
fillableSpace -= rowHeight;
22448
paddingAdjust += rowHeight;
22449
22450
this.vDomTop--;
22451
index--;
22452
i++;
22453
22454
}else {
22455
working = false;
22456
}
22457
22458
}else {
22459
working = false;
22460
}
22461
22462
}else {
22463
working = false;
22464
}
22465
}
22466
22467
for (let row of addedRows){
22468
row.clearCellHeight();
22469
}
22470
22471
this._quickNormalizeRowHeight(addedRows);
22472
22473
if(paddingAdjust){
22474
this.vDomTopPad -= paddingAdjust;
22475
22476
if(this.vDomTopPad < 0){
22477
this.vDomTopPad = index * this.vDomRowHeight;
22478
}
22479
22480
if(index < 1){
22481
this.vDomTopPad = 0;
22482
}
22483
22484
table.style.paddingTop = this.vDomTopPad + "px";
22485
this.vDomScrollPosTop -= paddingAdjust;
22486
}
22487
}
22488
22489
_removeTopRow(rows, fillableSpace){
22490
var removableRows = [],
22491
paddingAdjust = 0,
22492
i = 0,
22493
working = true;
22494
22495
while(working){
22496
let row = rows[this.vDomTop],
22497
rowHeight;
22498
22499
if(row && i < this.vDomMaxRenderChain){
22500
rowHeight = row.getHeight() || this.vDomRowHeight;
22501
22502
if(fillableSpace >= rowHeight){
22503
this.vDomTop++;
22504
22505
fillableSpace -= rowHeight;
22506
paddingAdjust += rowHeight;
22507
22508
removableRows.push(row);
22509
i++;
22510
}else {
22511
working = false;
22512
}
22513
}else {
22514
working = false;
22515
}
22516
}
22517
22518
for (let row of removableRows){
22519
let rowEl = row.getElement();
22520
22521
if(rowEl.parentNode){
22522
rowEl.parentNode.removeChild(rowEl);
22523
}
22524
}
22525
22526
if(paddingAdjust){
22527
this.vDomTopPad += paddingAdjust;
22528
this.tableElement.style.paddingTop = this.vDomTopPad + "px";
22529
this.vDomScrollPosTop += this.vDomTop ? paddingAdjust : paddingAdjust + this.vDomWindowBuffer;
22530
}
22531
}
22532
22533
_addBottomRow(rows, fillableSpace){
22534
var table = this.tableElement,
22535
addedRows = [],
22536
paddingAdjust = 0,
22537
index = this.vDomBottom + 1,
22538
i = 0,
22539
working = true;
22540
22541
while(working){
22542
let row = rows[index],
22543
rowHeight, initialized;
22544
22545
if(row && i < this.vDomMaxRenderChain){
22546
rowHeight = row.getHeight() || this.vDomRowHeight;
22547
initialized = row.initialized;
22548
22549
if(fillableSpace >= rowHeight){
22550
22551
this.styleRow(row, index);
22552
table.appendChild(row.getElement());
22553
22554
if(!row.initialized || !row.heightInitialized){
22555
addedRows.push(row);
22556
}
22557
22558
row.initialize();
22559
22560
if(!initialized){
22561
rowHeight = row.getElement().offsetHeight;
22562
22563
if(rowHeight > this.vDomWindowBuffer){
22564
this.vDomWindowBuffer = rowHeight * 2;
22565
}
22566
}
22567
22568
fillableSpace -= rowHeight;
22569
paddingAdjust += rowHeight;
22570
22571
this.vDomBottom++;
22572
index++;
22573
i++;
22574
}else {
22575
working = false;
22576
}
22577
}else {
22578
working = false;
22579
}
22580
}
22581
22582
for (let row of addedRows){
22583
row.clearCellHeight();
22584
}
22585
22586
this._quickNormalizeRowHeight(addedRows);
22587
22588
if(paddingAdjust){
22589
this.vDomBottomPad -= paddingAdjust;
22590
22591
if(this.vDomBottomPad < 0 || index == rows.length -1){
22592
this.vDomBottomPad = 0;
22593
}
22594
22595
table.style.paddingBottom = this.vDomBottomPad + "px";
22596
this.vDomScrollPosBottom += paddingAdjust;
22597
}
22598
}
22599
22600
_removeBottomRow(rows, fillableSpace){
22601
var removableRows = [],
22602
paddingAdjust = 0,
22603
i = 0,
22604
working = true;
22605
22606
while(working){
22607
let row = rows[this.vDomBottom],
22608
rowHeight;
22609
22610
if(row && i < this.vDomMaxRenderChain){
22611
rowHeight = row.getHeight() || this.vDomRowHeight;
22612
22613
if(fillableSpace >= rowHeight){
22614
this.vDomBottom --;
22615
22616
fillableSpace -= rowHeight;
22617
paddingAdjust += rowHeight;
22618
22619
removableRows.push(row);
22620
i++;
22621
}else {
22622
working = false;
22623
}
22624
}else {
22625
working = false;
22626
}
22627
}
22628
22629
for (let row of removableRows){
22630
let rowEl = row.getElement();
22631
22632
if(rowEl.parentNode){
22633
rowEl.parentNode.removeChild(rowEl);
22634
}
22635
}
22636
22637
if(paddingAdjust){
22638
this.vDomBottomPad += paddingAdjust;
22639
22640
if(this.vDomBottomPad < 0){
22641
this.vDomBottomPad = 0;
22642
}
22643
22644
this.tableElement.style.paddingBottom = this.vDomBottomPad + "px";
22645
this.vDomScrollPosBottom -= paddingAdjust;
22646
}
22647
}
22648
22649
_quickNormalizeRowHeight(rows){
22650
for(let row of rows){
22651
row.calcHeight();
22652
}
22653
22654
for(let row of rows){
22655
row.setCellHeight();
22656
}
22657
}
22658
}
22659
22660
class RowManager extends CoreFeature{
22661
22662
constructor(table){
22663
super(table);
22664
22665
this.element = this.createHolderElement(); //containing element
22666
this.tableElement = this.createTableElement(); //table element
22667
this.heightFixer = this.createTableElement(); //table element
22668
this.placeholder = null; //placeholder element
22669
this.placeholderContents = null; //placeholder element
22670
22671
this.firstRender = false; //handle first render
22672
this.renderMode = "virtual"; //current rendering mode
22673
this.fixedHeight = false; //current rendering mode
22674
22675
this.rows = []; //hold row data objects
22676
this.activeRowsPipeline = []; //hold calculation of active rows
22677
this.activeRows = []; //rows currently available to on display in the table
22678
this.activeRowsCount = 0; //count of active rows
22679
22680
this.displayRows = []; //rows currently on display in the table
22681
this.displayRowsCount = 0; //count of display rows
22682
22683
this.scrollTop = 0;
22684
this.scrollLeft = 0;
22685
22686
this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing
22687
this.redrawBlockRestoreConfig = false; //store latest redraw function calls for when redraw is needed
22688
this.redrawBlockRenderInPosition = false; //store latest redraw function calls for when redraw is needed
22689
22690
this.dataPipeline = []; //hold data pipeline tasks
22691
this.displayPipeline = []; //hold data display pipeline tasks
22692
22693
this.scrollbarWidth = 0;
22694
22695
this.renderer = null;
22696
}
22697
22698
//////////////// Setup Functions /////////////////
22699
22700
createHolderElement (){
22701
var el = document.createElement("div");
22702
22703
el.classList.add("tabulator-tableholder");
22704
el.setAttribute("tabindex", 0);
22705
// el.setAttribute("role", "rowgroup");
22706
22707
return el;
22708
}
22709
22710
createTableElement (){
22711
var el = document.createElement("div");
22712
22713
el.classList.add("tabulator-table");
22714
el.setAttribute("role", "rowgroup");
22715
22716
return el;
22717
}
22718
22719
initializePlaceholder(){
22720
var placeholder = this.table.options.placeholder;
22721
22722
if(typeof placeholder === "function"){
22723
placeholder = placeholder.call(this.table);
22724
}
22725
22726
placeholder = this.chain("placeholder", [placeholder], placeholder, placeholder) || placeholder;
22727
22728
//configure placeholder element
22729
if(placeholder){
22730
let el = document.createElement("div");
22731
el.classList.add("tabulator-placeholder");
22732
22733
if(typeof placeholder == "string"){
22734
let contents = document.createElement("div");
22735
contents.classList.add("tabulator-placeholder-contents");
22736
contents.innerHTML = placeholder;
22737
22738
el.appendChild(contents);
22739
22740
this.placeholderContents = contents;
22741
22742
}else if(typeof HTMLElement !== "undefined" && placeholder instanceof HTMLElement){
22743
22744
el.appendChild(placeholder);
22745
this.placeholderContents = placeholder;
22746
}else {
22747
console.warn("Invalid placeholder provided, must be string or HTML Element", placeholder);
22748
22749
this.el = null;
22750
}
22751
22752
this.placeholder = el;
22753
}
22754
}
22755
22756
//return containing element
22757
getElement(){
22758
return this.element;
22759
}
22760
22761
//return table element
22762
getTableElement(){
22763
return this.tableElement;
22764
}
22765
22766
initialize(){
22767
this.initializePlaceholder();
22768
this.initializeRenderer();
22769
22770
//initialize manager
22771
this.element.appendChild(this.tableElement);
22772
22773
this.firstRender = true;
22774
22775
//scroll header along with table body
22776
this.element.addEventListener("scroll", () => {
22777
var left = this.element.scrollLeft,
22778
leftDir = this.scrollLeft > left,
22779
top = this.element.scrollTop,
22780
topDir = this.scrollTop > top;
22781
22782
//handle horizontal scrolling
22783
if(this.scrollLeft != left){
22784
this.scrollLeft = left;
22785
22786
this.dispatch("scroll-horizontal", left, leftDir);
22787
this.dispatchExternal("scrollHorizontal", left, leftDir);
22788
22789
this._positionPlaceholder();
22790
}
22791
22792
//handle vertical scrolling
22793
if(this.scrollTop != top){
22794
this.scrollTop = top;
22795
22796
this.renderer.scrollRows(top, topDir);
22797
22798
this.dispatch("scroll-vertical", top, topDir);
22799
this.dispatchExternal("scrollVertical", top, topDir);
22800
}
22801
});
22802
}
22803
22804
////////////////// Row Manipulation //////////////////
22805
findRow(subject){
22806
if(typeof subject == "object"){
22807
if(subject instanceof Row){
22808
//subject is row element
22809
return subject;
22810
}else if(subject instanceof RowComponent){
22811
//subject is public row component
22812
return subject._getSelf() || false;
22813
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
22814
//subject is a HTML element of the row
22815
let match = this.rows.find((row) => {
22816
return row.getElement() === subject;
22817
});
22818
22819
return match || false;
22820
}else if(subject === null){
22821
return false;
22822
}
22823
}else if(typeof subject == "undefined"){
22824
return false;
22825
}else {
22826
//subject should be treated as the index of the row
22827
let match = this.rows.find((row) => {
22828
return row.data[this.table.options.index] == subject;
22829
});
22830
22831
return match || false;
22832
}
22833
22834
//catch all for any other type of input
22835
return false;
22836
}
22837
22838
getRowFromDataObject(data){
22839
var match = this.rows.find((row) => {
22840
return row.data === data;
22841
});
22842
22843
return match || false;
22844
}
22845
22846
getRowFromPosition(position){
22847
return this.getDisplayRows().find((row) => {
22848
return row.getPosition() === position && row.isDisplayed();
22849
});
22850
}
22851
22852
scrollToRow(row, position, ifVisible){
22853
return this.renderer.scrollToRowPosition(row, position, ifVisible);
22854
}
22855
22856
////////////////// Data Handling //////////////////
22857
setData(data, renderInPosition, columnsChanged){
22858
return new Promise((resolve, reject)=>{
22859
if(renderInPosition && this.getDisplayRows().length){
22860
if(this.table.options.pagination){
22861
this._setDataActual(data, true);
22862
}else {
22863
this.reRenderInPosition(() => {
22864
this._setDataActual(data);
22865
});
22866
}
22867
}else {
22868
if(this.table.options.autoColumns && columnsChanged && this.table.initialized){
22869
this.table.columnManager.generateColumnsFromRowData(data);
22870
}
22871
this.resetScroll();
22872
22873
this._setDataActual(data);
22874
}
22875
22876
resolve();
22877
});
22878
}
22879
22880
_setDataActual(data, renderInPosition){
22881
this.dispatchExternal("dataProcessing", data);
22882
22883
this._wipeElements();
22884
22885
if(Array.isArray(data)){
22886
this.dispatch("data-processing", data);
22887
22888
data.forEach((def, i) => {
22889
if(def && typeof def === "object"){
22890
var row = new Row(def, this);
22891
this.rows.push(row);
22892
}else {
22893
console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but received:", def);
22894
}
22895
});
22896
22897
this.refreshActiveData(false, false, renderInPosition);
22898
22899
this.dispatch("data-processed", data);
22900
this.dispatchExternal("dataProcessed", data);
22901
}else {
22902
console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data);
22903
}
22904
}
22905
22906
_wipeElements(){
22907
this.dispatch("rows-wipe");
22908
22909
this.destroy();
22910
22911
this.adjustTableSize();
22912
22913
this.dispatch("rows-wiped");
22914
}
22915
22916
destroy(){
22917
this.rows.forEach((row) => {
22918
row.wipe();
22919
});
22920
22921
this.rows = [];
22922
this.activeRows = [];
22923
this.activeRowsPipeline = [];
22924
this.activeRowsCount = 0;
22925
this.displayRows = [];
22926
this.displayRowsCount = 0;
22927
}
22928
22929
deleteRow(row, blockRedraw){
22930
var allIndex = this.rows.indexOf(row),
22931
activeIndex = this.activeRows.indexOf(row);
22932
22933
if(activeIndex > -1){
22934
this.activeRows.splice(activeIndex, 1);
22935
}
22936
22937
if(allIndex > -1){
22938
this.rows.splice(allIndex, 1);
22939
}
22940
22941
this.setActiveRows(this.activeRows);
22942
22943
this.displayRowIterator((rows) => {
22944
var displayIndex = rows.indexOf(row);
22945
22946
if(displayIndex > -1){
22947
rows.splice(displayIndex, 1);
22948
}
22949
});
22950
22951
if(!blockRedraw){
22952
this.reRenderInPosition();
22953
}
22954
22955
this.regenerateRowPositions();
22956
22957
this.dispatchExternal("rowDeleted", row.getComponent());
22958
22959
if(!this.displayRowsCount){
22960
this.tableEmpty();
22961
}
22962
22963
if(this.subscribedExternal("dataChanged")){
22964
this.dispatchExternal("dataChanged", this.getData());
22965
}
22966
}
22967
22968
addRow(data, pos, index, blockRedraw){
22969
var row = this.addRowActual(data, pos, index, blockRedraw);
22970
return row;
22971
}
22972
22973
//add multiple rows
22974
addRows(data, pos, index, refreshDisplayOnly){
22975
var rows = [];
22976
22977
return new Promise((resolve, reject) => {
22978
pos = this.findAddRowPos(pos);
22979
22980
if(!Array.isArray(data)){
22981
data = [data];
22982
}
22983
22984
if((typeof index == "undefined" && pos) || (typeof index !== "undefined" && !pos)){
22985
data.reverse();
22986
}
22987
22988
data.forEach((item, i) => {
22989
var row = this.addRow(item, pos, index, true);
22990
rows.push(row);
22991
this.dispatch("row-added", row, item, pos, index);
22992
});
22993
22994
this.refreshActiveData(refreshDisplayOnly ? "displayPipeline" : false, false, true);
22995
22996
this.regenerateRowPositions();
22997
22998
if(rows.length){
22999
this._clearPlaceholder();
23000
}
23001
23002
resolve(rows);
23003
});
23004
}
23005
23006
findAddRowPos(pos){
23007
if(typeof pos === "undefined"){
23008
pos = this.table.options.addRowPos;
23009
}
23010
23011
if(pos === "pos"){
23012
pos = true;
23013
}
23014
23015
if(pos === "bottom"){
23016
pos = false;
23017
}
23018
23019
return pos;
23020
}
23021
23022
addRowActual(data, pos, index, blockRedraw){
23023
var row = data instanceof Row ? data : new Row(data || {}, this),
23024
top = this.findAddRowPos(pos),
23025
allIndex = -1,
23026
activeIndex, chainResult;
23027
23028
if(!index){
23029
chainResult = this.chain("row-adding-position", [row, top], null, {index, top});
23030
23031
index = chainResult.index;
23032
top = chainResult.top;
23033
}
23034
23035
if(typeof index !== "undefined"){
23036
index = this.findRow(index);
23037
}
23038
23039
index = this.chain("row-adding-index", [row, index, top], null, index);
23040
23041
if(index){
23042
allIndex = this.rows.indexOf(index);
23043
}
23044
23045
if(index && allIndex > -1){
23046
activeIndex = this.activeRows.indexOf(index);
23047
23048
this.displayRowIterator(function(rows){
23049
var displayIndex = rows.indexOf(index);
23050
23051
if(displayIndex > -1){
23052
rows.splice((top ? displayIndex : displayIndex + 1), 0, row);
23053
}
23054
});
23055
23056
if(activeIndex > -1){
23057
this.activeRows.splice((top ? activeIndex : activeIndex + 1), 0, row);
23058
}
23059
23060
this.rows.splice((top ? allIndex : allIndex + 1), 0, row);
23061
23062
}else {
23063
23064
if(top){
23065
23066
this.displayRowIterator(function(rows){
23067
rows.unshift(row);
23068
});
23069
23070
this.activeRows.unshift(row);
23071
this.rows.unshift(row);
23072
}else {
23073
this.displayRowIterator(function(rows){
23074
rows.push(row);
23075
});
23076
23077
this.activeRows.push(row);
23078
this.rows.push(row);
23079
}
23080
}
23081
23082
this.setActiveRows(this.activeRows);
23083
23084
this.dispatchExternal("rowAdded", row.getComponent());
23085
23086
if(this.subscribedExternal("dataChanged")){
23087
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
23088
}
23089
23090
if(!blockRedraw){
23091
this.reRenderInPosition();
23092
}
23093
23094
return row;
23095
}
23096
23097
moveRow(from, to, after){
23098
this.dispatch("row-move", from, to, after);
23099
23100
this.moveRowActual(from, to, after);
23101
23102
this.regenerateRowPositions();
23103
23104
this.dispatch("row-moved", from, to, after);
23105
this.dispatchExternal("rowMoved", from.getComponent());
23106
}
23107
23108
moveRowActual(from, to, after){
23109
this.moveRowInArray(this.rows, from, to, after);
23110
this.moveRowInArray(this.activeRows, from, to, after);
23111
23112
this.displayRowIterator((rows) => {
23113
this.moveRowInArray(rows, from, to, after);
23114
});
23115
23116
this.dispatch("row-moving", from, to, after);
23117
}
23118
23119
moveRowInArray(rows, from, to, after){
23120
var fromIndex, toIndex, start, end;
23121
23122
if(from !== to){
23123
23124
fromIndex = rows.indexOf(from);
23125
23126
if (fromIndex > -1) {
23127
23128
rows.splice(fromIndex, 1);
23129
23130
toIndex = rows.indexOf(to);
23131
23132
if (toIndex > -1) {
23133
23134
if(after){
23135
rows.splice(toIndex+1, 0, from);
23136
}else {
23137
rows.splice(toIndex, 0, from);
23138
}
23139
23140
}else {
23141
rows.splice(fromIndex, 0, from);
23142
}
23143
}
23144
23145
//restyle rows
23146
if(rows === this.getDisplayRows()){
23147
23148
start = fromIndex < toIndex ? fromIndex : toIndex;
23149
end = toIndex > fromIndex ? toIndex : fromIndex +1;
23150
23151
for(let i = start; i <= end; i++){
23152
if(rows[i]){
23153
this.styleRow(rows[i], i);
23154
}
23155
}
23156
}
23157
}
23158
}
23159
23160
clearData(){
23161
this.setData([]);
23162
}
23163
23164
getRowIndex(row){
23165
return this.findRowIndex(row, this.rows);
23166
}
23167
23168
getDisplayRowIndex(row){
23169
var index = this.getDisplayRows().indexOf(row);
23170
return index > -1 ? index : false;
23171
}
23172
23173
nextDisplayRow(row, rowOnly){
23174
var index = this.getDisplayRowIndex(row),
23175
nextRow = false;
23176
23177
23178
if(index !== false && index < this.displayRowsCount -1){
23179
nextRow = this.getDisplayRows()[index+1];
23180
}
23181
23182
if(nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")){
23183
return this.nextDisplayRow(nextRow, rowOnly);
23184
}
23185
23186
return nextRow;
23187
}
23188
23189
prevDisplayRow(row, rowOnly){
23190
var index = this.getDisplayRowIndex(row),
23191
prevRow = false;
23192
23193
if(index){
23194
prevRow = this.getDisplayRows()[index-1];
23195
}
23196
23197
if(rowOnly && prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")){
23198
return this.prevDisplayRow(prevRow, rowOnly);
23199
}
23200
23201
return prevRow;
23202
}
23203
23204
findRowIndex(row, list){
23205
var rowIndex;
23206
23207
row = this.findRow(row);
23208
23209
if(row){
23210
rowIndex = list.indexOf(row);
23211
23212
if(rowIndex > -1){
23213
return rowIndex;
23214
}
23215
}
23216
23217
return false;
23218
}
23219
23220
getData(active, transform){
23221
var output = [],
23222
rows = this.getRows(active);
23223
23224
rows.forEach(function(row){
23225
if(row.type == "row"){
23226
output.push(row.getData(transform || "data"));
23227
}
23228
});
23229
23230
return output;
23231
}
23232
23233
getComponents(active){
23234
var output = [],
23235
rows = this.getRows(active);
23236
23237
rows.forEach(function(row){
23238
output.push(row.getComponent());
23239
});
23240
23241
return output;
23242
}
23243
23244
getDataCount(active){
23245
var rows = this.getRows(active);
23246
23247
return rows.length;
23248
}
23249
23250
scrollHorizontal(left){
23251
this.scrollLeft = left;
23252
this.element.scrollLeft = left;
23253
23254
this.dispatch("scroll-horizontal", left);
23255
}
23256
23257
registerDataPipelineHandler(handler, priority){
23258
if(typeof priority !== "undefined"){
23259
this.dataPipeline.push({handler, priority});
23260
this.dataPipeline.sort((a, b) => {
23261
return a.priority - b.priority;
23262
});
23263
}else {
23264
console.error("Data pipeline handlers must have a priority in order to be registered");
23265
}
23266
}
23267
23268
registerDisplayPipelineHandler(handler, priority){
23269
if(typeof priority !== "undefined"){
23270
this.displayPipeline.push({handler, priority});
23271
this.displayPipeline.sort((a, b) => {
23272
return a.priority - b.priority;
23273
});
23274
}else {
23275
console.error("Display pipeline handlers must have a priority in order to be registered");
23276
}
23277
}
23278
23279
//set active data set
23280
refreshActiveData(handler, skipStage, renderInPosition){
23281
var table = this.table,
23282
stage = "",
23283
index = 0,
23284
cascadeOrder = ["all", "dataPipeline", "display", "displayPipeline", "end"];
23285
23286
if(!this.table.destroyed){
23287
if(typeof handler === "function"){
23288
index = this.dataPipeline.findIndex((item) => {
23289
return item.handler === handler;
23290
});
23291
23292
if(index > -1){
23293
stage = "dataPipeline";
23294
23295
if(skipStage){
23296
if(index == this.dataPipeline.length - 1){
23297
stage = "display";
23298
}else {
23299
index++;
23300
}
23301
}
23302
}else {
23303
index = this.displayPipeline.findIndex((item) => {
23304
return item.handler === handler;
23305
});
23306
23307
if(index > -1){
23308
stage = "displayPipeline";
23309
23310
if(skipStage){
23311
if(index == this.displayPipeline.length - 1){
23312
stage = "end";
23313
}else {
23314
index++;
23315
}
23316
}
23317
}else {
23318
console.error("Unable to refresh data, invalid handler provided", handler);
23319
return;
23320
}
23321
}
23322
}else {
23323
stage = handler || "all";
23324
index = 0;
23325
}
23326
23327
if(this.redrawBlock){
23328
if(!this.redrawBlockRestoreConfig || (this.redrawBlockRestoreConfig && ((this.redrawBlockRestoreConfig.stage === stage && index < this.redrawBlockRestoreConfig.index) || (cascadeOrder.indexOf(stage) < cascadeOrder.indexOf(this.redrawBlockRestoreConfig.stage))))){
23329
this.redrawBlockRestoreConfig = {
23330
handler: handler,
23331
skipStage: skipStage,
23332
renderInPosition: renderInPosition,
23333
stage:stage,
23334
index:index,
23335
};
23336
}
23337
23338
return;
23339
}else {
23340
if(Helpers.elVisible(this.element)){
23341
if(renderInPosition){
23342
this.reRenderInPosition(this.refreshPipelines.bind(this, handler, stage, index, renderInPosition));
23343
}else {
23344
this.refreshPipelines(handler, stage, index, renderInPosition);
23345
23346
if(!handler){
23347
this.table.columnManager.renderer.renderColumns();
23348
}
23349
23350
this.renderTable();
23351
23352
if(table.options.layoutColumnsOnNewData){
23353
this.table.columnManager.redraw(true);
23354
}
23355
}
23356
}else {
23357
this.refreshPipelines(handler, stage, index, renderInPosition);
23358
}
23359
23360
this.dispatch("data-refreshed");
23361
}
23362
}
23363
}
23364
23365
refreshPipelines(handler, stage, index, renderInPosition){
23366
this.dispatch("data-refreshing");
23367
23368
if(!handler){
23369
this.activeRowsPipeline[0] = this.rows.slice(0);
23370
}
23371
23372
//cascade through data refresh stages
23373
switch(stage){
23374
case "all":
23375
//handle case where all data needs refreshing
23376
23377
case "dataPipeline":
23378
23379
for(let i = index; i < this.dataPipeline.length; i++){
23380
let result = this.dataPipeline[i].handler(this.activeRowsPipeline[i].slice(0));
23381
23382
this.activeRowsPipeline[i + 1] = result || this.activeRowsPipeline[i].slice(0);
23383
}
23384
23385
this.setActiveRows(this.activeRowsPipeline[this.dataPipeline.length]);
23386
23387
case "display":
23388
index = 0;
23389
this.resetDisplayRows();
23390
23391
case "displayPipeline":
23392
for(let i = index; i < this.displayPipeline.length; i++){
23393
let result = this.displayPipeline[i].handler((i ? this.getDisplayRows(i - 1) : this.activeRows).slice(0), renderInPosition);
23394
23395
this.setDisplayRows(result || this.getDisplayRows(i - 1).slice(0), i);
23396
}
23397
23398
case "end":
23399
//case to handle scenario when trying to skip past end stage
23400
this.regenerateRowPositions();
23401
}
23402
23403
if(this.getDisplayRows().length){
23404
this._clearPlaceholder();
23405
}
23406
}
23407
23408
//regenerate row positions
23409
regenerateRowPositions(){
23410
var rows = this.getDisplayRows();
23411
var index = 1;
23412
23413
rows.forEach((row) => {
23414
if (row.type === "row"){
23415
row.setPosition(index);
23416
index++;
23417
}
23418
});
23419
}
23420
23421
setActiveRows(activeRows){
23422
this.activeRows = this.activeRows = Object.assign([], activeRows);
23423
this.activeRowsCount = this.activeRows.length;
23424
}
23425
23426
//reset display rows array
23427
resetDisplayRows(){
23428
this.displayRows = [];
23429
23430
this.displayRows.push(this.activeRows.slice(0));
23431
23432
this.displayRowsCount = this.displayRows[0].length;
23433
}
23434
23435
//set display row pipeline data
23436
setDisplayRows(displayRows, index){
23437
this.displayRows[index] = displayRows;
23438
23439
if(index == this.displayRows.length -1){
23440
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
23441
}
23442
}
23443
23444
getDisplayRows(index){
23445
if(typeof index == "undefined"){
23446
return this.displayRows.length ? this.displayRows[this.displayRows.length -1] : [];
23447
}else {
23448
return this.displayRows[index] || [];
23449
}
23450
}
23451
23452
getVisibleRows(chain, viewable){
23453
var rows = Object.assign([], this.renderer.visibleRows(!viewable));
23454
23455
if(chain){
23456
rows = this.chain("rows-visible", [viewable], rows, rows);
23457
}
23458
23459
return rows;
23460
}
23461
23462
//repeat action across display rows
23463
displayRowIterator(callback){
23464
this.activeRowsPipeline.forEach(callback);
23465
this.displayRows.forEach(callback);
23466
23467
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
23468
}
23469
23470
//return only actual rows (not group headers etc)
23471
getRows(type){
23472
var rows = [];
23473
23474
switch(type){
23475
case "active":
23476
rows = this.activeRows;
23477
break;
23478
23479
case "display":
23480
rows = this.table.rowManager.getDisplayRows();
23481
break;
23482
23483
case "visible":
23484
rows = this.getVisibleRows(false, true);
23485
break;
23486
23487
default:
23488
rows = this.chain("rows-retrieve", type, null, this.rows) || this.rows;
23489
}
23490
23491
return rows;
23492
}
23493
23494
///////////////// Table Rendering /////////////////
23495
//trigger rerender of table in current position
23496
reRenderInPosition(callback){
23497
if(this.redrawBlock){
23498
if(callback){
23499
callback();
23500
}else {
23501
this.redrawBlockRenderInPosition = true;
23502
}
23503
}else {
23504
this.dispatchExternal("renderStarted");
23505
23506
this.renderer.rerenderRows(callback);
23507
23508
if(!this.fixedHeight){
23509
this.adjustTableSize();
23510
}
23511
23512
this.scrollBarCheck();
23513
23514
this.dispatchExternal("renderComplete");
23515
}
23516
}
23517
23518
scrollBarCheck(){
23519
var scrollbarWidth = 0;
23520
23521
//adjust for vertical scrollbar moving table when present
23522
if(this.element.scrollHeight > this.element.clientHeight){
23523
scrollbarWidth = this.element.offsetWidth - this.element.clientWidth;
23524
}
23525
23526
if(scrollbarWidth !== this.scrollbarWidth){
23527
this.scrollbarWidth = scrollbarWidth;
23528
this.dispatch("scrollbar-vertical", scrollbarWidth);
23529
}
23530
}
23531
23532
initializeRenderer(){
23533
var renderClass;
23534
23535
var renderers = {
23536
"virtual": VirtualDomVertical,
23537
"basic": BasicVertical,
23538
};
23539
23540
if(typeof this.table.options.renderVertical === "string"){
23541
renderClass = renderers[this.table.options.renderVertical];
23542
}else {
23543
renderClass = this.table.options.renderVertical;
23544
}
23545
23546
if(renderClass){
23547
this.renderMode = this.table.options.renderVertical;
23548
23549
this.renderer = new renderClass(this.table, this.element, this.tableElement);
23550
this.renderer.initialize();
23551
23552
if((this.table.element.clientHeight || this.table.options.height) && !(this.table.options.minHeight && this.table.options.maxHeight)){
23553
this.fixedHeight = true;
23554
}else {
23555
this.fixedHeight = false;
23556
}
23557
}else {
23558
console.error("Unable to find matching renderer:", this.table.options.renderVertical);
23559
}
23560
}
23561
23562
getRenderMode(){
23563
return this.renderMode;
23564
}
23565
23566
renderTable(){
23567
this.dispatchExternal("renderStarted");
23568
23569
this.element.scrollTop = 0;
23570
23571
this._clearTable();
23572
23573
if(this.displayRowsCount){
23574
this.renderer.renderRows();
23575
23576
if(this.firstRender){
23577
this.firstRender = false;
23578
23579
if(!this.fixedHeight){
23580
this.adjustTableSize();
23581
}
23582
23583
this.layoutRefresh(true);
23584
}
23585
}else {
23586
this.renderEmptyScroll();
23587
}
23588
23589
if(!this.fixedHeight){
23590
this.adjustTableSize();
23591
}
23592
23593
this.dispatch("table-layout");
23594
23595
if(!this.displayRowsCount){
23596
this._showPlaceholder();
23597
}
23598
23599
this.scrollBarCheck();
23600
23601
this.dispatchExternal("renderComplete");
23602
}
23603
23604
//show scrollbars on empty table div
23605
renderEmptyScroll(){
23606
if(this.placeholder){
23607
this.tableElement.style.display = "none";
23608
}else {
23609
this.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px";
23610
// this.tableElement.style.minHeight = "1px";
23611
// this.tableElement.style.visibility = "hidden";
23612
}
23613
}
23614
23615
_clearTable(){
23616
this._clearPlaceholder();
23617
23618
this.scrollTop = 0;
23619
this.scrollLeft = 0;
23620
23621
this.renderer.clearRows();
23622
}
23623
23624
tableEmpty(){
23625
this.renderEmptyScroll();
23626
this._showPlaceholder();
23627
}
23628
23629
_showPlaceholder(){
23630
if(this.placeholder){
23631
if(this.placeholder && this.placeholder.parentNode){
23632
this.placeholder.parentNode.removeChild(this.placeholder);
23633
}
23634
23635
this.initializePlaceholder();
23636
23637
this.placeholder.setAttribute("tabulator-render-mode", this.renderMode);
23638
23639
this.getElement().appendChild(this.placeholder);
23640
this._positionPlaceholder();
23641
23642
this.adjustTableSize();
23643
}
23644
}
23645
23646
_clearPlaceholder(){
23647
if(this.placeholder && this.placeholder.parentNode){
23648
this.placeholder.parentNode.removeChild(this.placeholder);
23649
}
23650
23651
// clear empty table placeholder min
23652
this.tableElement.style.minWidth = "";
23653
this.tableElement.style.display = "";
23654
}
23655
23656
_positionPlaceholder(){
23657
if(this.placeholder && this.placeholder.parentNode){
23658
this.placeholder.style.width = this.table.columnManager.getWidth() + "px";
23659
this.placeholderContents.style.width = this.table.rowManager.element.clientWidth + "px";
23660
this.placeholderContents.style.marginLeft = this.scrollLeft + "px";
23661
}
23662
}
23663
23664
styleRow(row, index){
23665
var rowEl = row.getElement();
23666
23667
if(index % 2){
23668
rowEl.classList.add("tabulator-row-even");
23669
rowEl.classList.remove("tabulator-row-odd");
23670
}else {
23671
rowEl.classList.add("tabulator-row-odd");
23672
rowEl.classList.remove("tabulator-row-even");
23673
}
23674
}
23675
23676
//normalize height of active rows
23677
normalizeHeight(){
23678
this.activeRows.forEach(function(row){
23679
row.normalizeHeight();
23680
});
23681
}
23682
23683
//adjust the height of the table holder to fit in the Tabulator element
23684
adjustTableSize(){
23685
let initialHeight = this.element.clientHeight, minHeight;
23686
let resized = false;
23687
23688
if(this.renderer.verticalFillMode === "fill"){
23689
let otherHeight = Math.floor(this.table.columnManager.getElement().getBoundingClientRect().height + (this.table.footerManager && this.table.footerManager.active && !this.table.footerManager.external ? this.table.footerManager.getElement().getBoundingClientRect().height : 0));
23690
23691
if(this.fixedHeight){
23692
minHeight = isNaN(this.table.options.minHeight) ? this.table.options.minHeight : this.table.options.minHeight + "px";
23693
23694
const height = "calc(100% - " + otherHeight + "px)";
23695
this.element.style.minHeight = minHeight || "calc(100% - " + otherHeight + "px)";
23696
this.element.style.height = height;
23697
this.element.style.maxHeight = height;
23698
} else {
23699
this.element.style.height = "";
23700
this.element.style.height =
23701
this.table.element.clientHeight - otherHeight + "px";
23702
this.element.scrollTop = this.scrollTop;
23703
}
23704
23705
this.renderer.resize();
23706
23707
//check if the table has changed size when dealing with variable height tables
23708
if(!this.fixedHeight && initialHeight != this.element.clientHeight){
23709
resized = true;
23710
if(this.subscribed("table-resize")){
23711
this.dispatch("table-resize");
23712
}else {
23713
this.redraw();
23714
}
23715
}
23716
23717
this.scrollBarCheck();
23718
}
23719
23720
this._positionPlaceholder();
23721
return resized;
23722
}
23723
23724
//reinitialize all rows
23725
reinitialize(){
23726
this.rows.forEach(function(row){
23727
row.reinitialize(true);
23728
});
23729
}
23730
23731
//prevent table from being redrawn
23732
blockRedraw (){
23733
this.redrawBlock = true;
23734
this.redrawBlockRestoreConfig = false;
23735
}
23736
23737
//restore table redrawing
23738
restoreRedraw (){
23739
this.redrawBlock = false;
23740
23741
if(this.redrawBlockRestoreConfig){
23742
this.refreshActiveData(this.redrawBlockRestoreConfig.handler, this.redrawBlockRestoreConfig.skipStage, this.redrawBlockRestoreConfig.renderInPosition);
23743
23744
this.redrawBlockRestoreConfig = false;
23745
}else {
23746
if(this.redrawBlockRenderInPosition){
23747
this.reRenderInPosition();
23748
}
23749
}
23750
23751
this.redrawBlockRenderInPosition = false;
23752
}
23753
23754
//redraw table
23755
redraw (force){
23756
const resized = this.adjustTableSize();
23757
this.table.tableWidth = this.table.element.clientWidth;
23758
23759
if(!force){
23760
if(resized) {
23761
this.reRenderInPosition();
23762
}
23763
this.scrollHorizontal(this.scrollLeft);
23764
}else {
23765
this.renderTable();
23766
}
23767
}
23768
23769
resetScroll(){
23770
this.element.scrollLeft = 0;
23771
this.element.scrollTop = 0;
23772
23773
if(this.table.browser === "ie"){
23774
var event = document.createEvent("Event");
23775
event.initEvent("scroll", false, true);
23776
this.element.dispatchEvent(event);
23777
}else {
23778
this.element.dispatchEvent(new Event('scroll'));
23779
}
23780
}
23781
}
23782
23783
class FooterManager extends CoreFeature{
23784
23785
constructor(table){
23786
super(table);
23787
23788
this.active = false;
23789
this.element = this.createElement(); //containing element
23790
this.containerElement = this.createContainerElement(); //containing element
23791
this.external = false;
23792
}
23793
23794
initialize(){
23795
this.initializeElement();
23796
}
23797
23798
createElement(){
23799
var el = document.createElement("div");
23800
23801
el.classList.add("tabulator-footer");
23802
23803
return el;
23804
}
23805
23806
23807
createContainerElement(){
23808
var el = document.createElement("div");
23809
23810
el.classList.add("tabulator-footer-contents");
23811
23812
this.element.appendChild(el);
23813
23814
return el;
23815
}
23816
23817
initializeElement(){
23818
if(this.table.options.footerElement){
23819
23820
switch(typeof this.table.options.footerElement){
23821
case "string":
23822
if(this.table.options.footerElement[0] === "<"){
23823
this.containerElement.innerHTML = this.table.options.footerElement;
23824
}else {
23825
this.external = true;
23826
this.containerElement = document.querySelector(this.table.options.footerElement);
23827
}
23828
break;
23829
23830
default:
23831
this.element = this.table.options.footerElement;
23832
break;
23833
}
23834
}
23835
}
23836
23837
getElement(){
23838
return this.element;
23839
}
23840
23841
append(element){
23842
this.activate();
23843
23844
this.containerElement.appendChild(element);
23845
this.table.rowManager.adjustTableSize();
23846
}
23847
23848
prepend(element){
23849
this.activate();
23850
23851
this.element.insertBefore(element, this.element.firstChild);
23852
this.table.rowManager.adjustTableSize();
23853
}
23854
23855
remove(element){
23856
element.parentNode.removeChild(element);
23857
this.deactivate();
23858
}
23859
23860
deactivate(force){
23861
if(!this.element.firstChild || force){
23862
if(!this.external){
23863
this.element.parentNode.removeChild(this.element);
23864
}
23865
this.active = false;
23866
}
23867
}
23868
23869
activate(){
23870
if(!this.active){
23871
this.active = true;
23872
if(!this.external){
23873
this.table.element.appendChild(this.getElement());
23874
this.table.element.style.display = '';
23875
}
23876
}
23877
}
23878
23879
redraw(){
23880
this.dispatch("footer-redraw");
23881
}
23882
}
23883
23884
class InteractionManager extends CoreFeature {
23885
23886
constructor (table){
23887
super(table);
23888
23889
this.el = null;
23890
23891
this.abortClasses = ["tabulator-headers", "tabulator-table"];
23892
23893
this.previousTargets = {};
23894
23895
this.listeners = [
23896
"click",
23897
"dblclick",
23898
"contextmenu",
23899
"mouseenter",
23900
"mouseleave",
23901
"mouseover",
23902
"mouseout",
23903
"mousemove",
23904
"mouseup",
23905
"mousedown",
23906
"touchstart",
23907
"touchend",
23908
];
23909
23910
this.componentMap = {
23911
"tabulator-cell":"cell",
23912
"tabulator-row":"row",
23913
"tabulator-group":"group",
23914
"tabulator-col":"column",
23915
};
23916
23917
this.pseudoTrackers = {
23918
"row":{
23919
subscriber:null,
23920
target:null,
23921
},
23922
"cell":{
23923
subscriber:null,
23924
target:null,
23925
},
23926
"group":{
23927
subscriber:null,
23928
target:null,
23929
},
23930
"column":{
23931
subscriber:null,
23932
target:null,
23933
},
23934
};
23935
23936
this.pseudoTracking = false;
23937
}
23938
23939
initialize(){
23940
this.el = this.table.element;
23941
23942
this.buildListenerMap();
23943
this.bindSubscriptionWatchers();
23944
}
23945
23946
buildListenerMap(){
23947
var listenerMap = {};
23948
23949
this.listeners.forEach((listener) => {
23950
listenerMap[listener] = {
23951
handler:null,
23952
components:[],
23953
};
23954
});
23955
23956
this.listeners = listenerMap;
23957
}
23958
23959
bindPseudoEvents(){
23960
Object.keys(this.pseudoTrackers).forEach((key) => {
23961
this.pseudoTrackers[key].subscriber = this.pseudoMouseEnter.bind(this, key);
23962
this.subscribe(key + "-mouseover", this.pseudoTrackers[key].subscriber);
23963
});
23964
23965
this.pseudoTracking = true;
23966
}
23967
23968
pseudoMouseEnter(key, e, target){
23969
if(this.pseudoTrackers[key].target !== target){
23970
23971
if(this.pseudoTrackers[key].target){
23972
this.dispatch(key + "-mouseleave", e, this.pseudoTrackers[key].target);
23973
}
23974
23975
this.pseudoMouseLeave(key, e);
23976
23977
this.pseudoTrackers[key].target = target;
23978
23979
this.dispatch(key + "-mouseenter", e, target);
23980
}
23981
}
23982
23983
pseudoMouseLeave(key, e){
23984
var leaveList = Object.keys(this.pseudoTrackers),
23985
linkedKeys = {
23986
"row":["cell"],
23987
"cell":["row"],
23988
};
23989
23990
leaveList = leaveList.filter((item) => {
23991
var links = linkedKeys[key];
23992
return item !== key && (!links || (links && !links.includes(item)));
23993
});
23994
23995
23996
leaveList.forEach((key) => {
23997
var target = this.pseudoTrackers[key].target;
23998
23999
if(this.pseudoTrackers[key].target){
24000
this.dispatch(key + "-mouseleave", e, target);
24001
24002
this.pseudoTrackers[key].target = null;
24003
}
24004
});
24005
}
24006
24007
24008
bindSubscriptionWatchers(){
24009
var listeners = Object.keys(this.listeners),
24010
components = Object.values(this.componentMap);
24011
24012
for(let comp of components){
24013
for(let listener of listeners){
24014
let key = comp + "-" + listener;
24015
24016
this.subscriptionChange(key, this.subscriptionChanged.bind(this, comp, listener));
24017
}
24018
}
24019
24020
this.subscribe("table-destroy", this.clearWatchers.bind(this));
24021
}
24022
24023
subscriptionChanged(component, key, added){
24024
var listener = this.listeners[key].components,
24025
index = listener.indexOf(component),
24026
changed = false;
24027
24028
if(added){
24029
if(index === -1){
24030
listener.push(component);
24031
changed = true;
24032
}
24033
}else {
24034
if(!this.subscribed(component + "-" + key)){
24035
if(index > -1){
24036
listener.splice(index, 1);
24037
changed = true;
24038
}
24039
}
24040
}
24041
24042
if((key === "mouseenter" || key === "mouseleave") && !this.pseudoTracking){
24043
this.bindPseudoEvents();
24044
}
24045
24046
if(changed){
24047
this.updateEventListeners();
24048
}
24049
}
24050
24051
updateEventListeners(){
24052
for(let key in this.listeners){
24053
let listener = this.listeners[key];
24054
24055
if(listener.components.length){
24056
if(!listener.handler){
24057
listener.handler = this.track.bind(this, key);
24058
this.el.addEventListener(key, listener.handler);
24059
// this.el.addEventListener(key, listener.handler, {passive: true})
24060
}
24061
}else {
24062
if(listener.handler){
24063
this.el.removeEventListener(key, listener.handler);
24064
listener.handler = null;
24065
}
24066
}
24067
}
24068
}
24069
24070
track(type, e){
24071
var path = (e.composedPath && e.composedPath()) || e.path;
24072
24073
var targets = this.findTargets(path);
24074
targets = this.bindComponents(type, targets);
24075
24076
this.triggerEvents(type, e, targets);
24077
24078
if(this.pseudoTracking && (type == "mouseover" || type == "mouseleave") && !Object.keys(targets).length){
24079
this.pseudoMouseLeave("none", e);
24080
}
24081
}
24082
24083
findTargets(path){
24084
var targets = {};
24085
24086
let componentMap = Object.keys(this.componentMap);
24087
24088
for (let el of path) {
24089
let classList = el.classList ? [...el.classList] : [];
24090
24091
let abort = classList.filter((item) => {
24092
return this.abortClasses.includes(item);
24093
});
24094
24095
if(abort.length){
24096
break;
24097
}
24098
24099
let elTargets = classList.filter((item) => {
24100
return componentMap.includes(item);
24101
});
24102
24103
for (let target of elTargets) {
24104
if(!targets[this.componentMap[target]]){
24105
targets[this.componentMap[target]] = el;
24106
}
24107
}
24108
}
24109
24110
if(targets.group && targets.group === targets.row){
24111
delete targets.row;
24112
}
24113
24114
return targets;
24115
}
24116
24117
bindComponents(type, targets){
24118
//ensure row component is looked up before cell
24119
var keys = Object.keys(targets).reverse(),
24120
listener = this.listeners[type],
24121
matches = {},
24122
targetMatches = {};
24123
24124
for(let key of keys){
24125
let component,
24126
target = targets[key],
24127
previousTarget = this.previousTargets[key];
24128
24129
if(previousTarget && previousTarget.target === target){
24130
component = previousTarget.component;
24131
}else {
24132
switch(key){
24133
case "row":
24134
case "group":
24135
if(listener.components.includes("row") || listener.components.includes("cell") || listener.components.includes("group")){
24136
let rows = this.table.rowManager.getVisibleRows(true);
24137
24138
component = rows.find((row) => {
24139
return row.getElement() === target;
24140
});
24141
24142
if(targets["row"] && targets["row"].parentNode && targets["row"].parentNode.closest(".tabulator-row")){
24143
targets[key] = false;
24144
}
24145
}
24146
break;
24147
24148
case "column":
24149
if(listener.components.includes("column")){
24150
component = this.table.columnManager.findColumn(target);
24151
}
24152
break;
24153
24154
case "cell":
24155
if(listener.components.includes("cell")){
24156
if(matches["row"] instanceof Row){
24157
component = matches["row"].findCell(target);
24158
}else {
24159
if(targets["row"]){
24160
console.warn("Event Target Lookup Error - The row this cell is attached to cannot be found, has the table been reinitialized without being destroyed first?");
24161
}
24162
}
24163
}
24164
break;
24165
}
24166
}
24167
24168
if(component){
24169
matches[key] = component;
24170
targetMatches[key] = {
24171
target:target,
24172
component:component,
24173
};
24174
}
24175
}
24176
24177
this.previousTargets = targetMatches;
24178
24179
return matches;
24180
}
24181
24182
triggerEvents(type, e, targets){
24183
var listener = this.listeners[type];
24184
24185
for(let key in targets){
24186
if(targets[key] && listener.components.includes(key)){
24187
this.dispatch(key + "-" + type, e, targets[key]);
24188
}
24189
}
24190
}
24191
24192
clearWatchers(){
24193
for(let key in this.listeners){
24194
let listener = this.listeners[key];
24195
24196
if(listener.handler){
24197
this.el.removeEventListener(key, listener.handler);
24198
listener.handler = null;
24199
}
24200
}
24201
}
24202
}
24203
24204
class ComponentFunctionBinder{
24205
24206
constructor(table){
24207
this.table = table;
24208
24209
this.bindings = {};
24210
}
24211
24212
bind(type, funcName, handler){
24213
if(!this.bindings[type]){
24214
this.bindings[type] = {};
24215
}
24216
24217
if(this.bindings[type][funcName]){
24218
console.warn("Unable to bind component handler, a matching function name is already bound", type, funcName, handler);
24219
}else {
24220
this.bindings[type][funcName] = handler;
24221
}
24222
}
24223
24224
handle(type, component, name){
24225
if(this.bindings[type] && this.bindings[type][name] && typeof this.bindings[type][name].bind === 'function'){
24226
return this.bindings[type][name].bind(null, component);
24227
}else {
24228
if(name !== "then" && typeof name === "string" && !name.startsWith("_")){
24229
if(this.table.options.debugInvalidComponentFuncs){
24230
console.error("The " + type + " component does not have a " + name + " function, have you checked that you have the correct Tabulator module installed?");
24231
}
24232
}
24233
}
24234
}
24235
}
24236
24237
class DataLoader extends CoreFeature{
24238
constructor(table){
24239
super(table);
24240
24241
this.requestOrder = 0; //prevent requests coming out of sequence if overridden by another load request
24242
this.loading = false;
24243
}
24244
24245
initialize(){}
24246
24247
load(data, params, config, replace, silent, columnsChanged){
24248
var requestNo = ++this.requestOrder;
24249
24250
if(this.table.destroyed){
24251
return Promise.resolve();
24252
}
24253
24254
this.dispatchExternal("dataLoading", data);
24255
24256
//parse json data to array
24257
if (data && (data.indexOf("{") == 0 || data.indexOf("[") == 0)){
24258
data = JSON.parse(data);
24259
}
24260
24261
if(this.confirm("data-loading", [data, params, config, silent])){
24262
this.loading = true;
24263
24264
if(!silent){
24265
this.alertLoader();
24266
}
24267
24268
//get params for request
24269
params = this.chain("data-params", [data, config, silent], params || {}, params || {});
24270
24271
params = this.mapParams(params, this.table.options.dataSendParams);
24272
24273
var result = this.chain("data-load", [data, params, config, silent], false, Promise.resolve([]));
24274
24275
return result.then((response) => {
24276
if(!this.table.destroyed){
24277
if(!Array.isArray(response) && typeof response == "object"){
24278
response = this.mapParams(response, this.objectInvert(this.table.options.dataReceiveParams));
24279
}
24280
24281
var rowData = this.chain("data-loaded", response, null, response);
24282
24283
if(requestNo == this.requestOrder){
24284
this.clearAlert();
24285
24286
if(rowData !== false){
24287
this.dispatchExternal("dataLoaded", rowData);
24288
this.table.rowManager.setData(rowData, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged);
24289
}
24290
}else {
24291
console.warn("Data Load Response Blocked - An active data load request was blocked by an attempt to change table data while the request was being made");
24292
}
24293
}else {
24294
console.warn("Data Load Response Blocked - Table has been destroyed");
24295
}
24296
}).catch((error) => {
24297
console.error("Data Load Error: ", error);
24298
this.dispatchExternal("dataLoadError", error);
24299
24300
if(!silent){
24301
this.alertError();
24302
}
24303
24304
setTimeout(() => {
24305
this.clearAlert();
24306
}, this.table.options.dataLoaderErrorTimeout);
24307
})
24308
.finally(() => {
24309
this.loading = false;
24310
});
24311
}else {
24312
this.dispatchExternal("dataLoaded", data);
24313
24314
if(!data){
24315
data = [];
24316
}
24317
24318
this.table.rowManager.setData(data, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged);
24319
return Promise.resolve();
24320
}
24321
}
24322
24323
mapParams(params, map){
24324
var output = {};
24325
24326
for(let key in params){
24327
output[map.hasOwnProperty(key) ? map[key] : key] = params[key];
24328
}
24329
24330
return output;
24331
}
24332
24333
objectInvert(obj){
24334
var output = {};
24335
24336
for(let key in obj){
24337
output[obj[key]] = key;
24338
}
24339
24340
return output;
24341
}
24342
24343
blockActiveLoad(){
24344
this.requestOrder++;
24345
}
24346
24347
alertLoader(){
24348
var shouldLoad = typeof this.table.options.dataLoader === "function" ? this.table.options.dataLoader() : this.table.options.dataLoader;
24349
24350
if(shouldLoad){
24351
this.table.alertManager.alert(this.table.options.dataLoaderLoading || this.langText("data|loading"));
24352
}
24353
}
24354
24355
alertError(){
24356
this.table.alertManager.alert(this.table.options.dataLoaderError || this.langText("data|error"), "error");
24357
}
24358
24359
clearAlert(){
24360
this.table.alertManager.clear();
24361
}
24362
}
24363
24364
class ExternalEventBus {
24365
24366
constructor(table, optionsList, debug){
24367
this.table = table;
24368
this.events = {};
24369
this.optionsList = optionsList || {};
24370
this.subscriptionNotifiers = {};
24371
24372
this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this);
24373
this.debug = debug;
24374
}
24375
24376
subscriptionChange(key, callback){
24377
if(!this.subscriptionNotifiers[key]){
24378
this.subscriptionNotifiers[key] = [];
24379
}
24380
24381
this.subscriptionNotifiers[key].push(callback);
24382
24383
if(this.subscribed(key)){
24384
this._notifySubscriptionChange(key, true);
24385
}
24386
}
24387
24388
subscribe(key, callback){
24389
if(!this.events[key]){
24390
this.events[key] = [];
24391
}
24392
24393
this.events[key].push(callback);
24394
24395
this._notifySubscriptionChange(key, true);
24396
}
24397
24398
unsubscribe(key, callback){
24399
var index;
24400
24401
if(this.events[key]){
24402
if(callback){
24403
index = this.events[key].findIndex((item) => {
24404
return item === callback;
24405
});
24406
24407
if(index > -1){
24408
this.events[key].splice(index, 1);
24409
}else {
24410
console.warn("Cannot remove event, no matching event found:", key, callback);
24411
return;
24412
}
24413
}else {
24414
delete this.events[key];
24415
}
24416
}else {
24417
console.warn("Cannot remove event, no events set on:", key);
24418
return;
24419
}
24420
24421
this._notifySubscriptionChange(key, false);
24422
}
24423
24424
subscribed(key){
24425
return this.events[key] && this.events[key].length;
24426
}
24427
24428
_notifySubscriptionChange(key, subscribed){
24429
var notifiers = this.subscriptionNotifiers[key];
24430
24431
if(notifiers){
24432
notifiers.forEach((callback)=>{
24433
callback(subscribed);
24434
});
24435
}
24436
}
24437
24438
_dispatch(){
24439
var args = Array.from(arguments),
24440
key = args.shift(),
24441
result;
24442
24443
if(this.events[key]){
24444
this.events[key].forEach((callback, i) => {
24445
let callResult = callback.apply(this.table, args);
24446
24447
if(!i){
24448
result = callResult;
24449
}
24450
});
24451
}
24452
24453
return result;
24454
}
24455
24456
_debugDispatch(){
24457
var args = Array.from(arguments),
24458
key = args[0];
24459
24460
args[0] = "ExternalEvent:" + args[0];
24461
24462
if(this.debug === true || this.debug.includes(key)){
24463
console.log(...args);
24464
}
24465
24466
return this._dispatch(...arguments);
24467
}
24468
}
24469
24470
class InternalEventBus {
24471
24472
constructor(debug){
24473
this.events = {};
24474
this.subscriptionNotifiers = {};
24475
24476
this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this);
24477
this.chain = debug ? this._debugChain.bind(this) : this._chain.bind(this);
24478
this.confirm = debug ? this._debugConfirm.bind(this) : this._confirm.bind(this);
24479
this.debug = debug;
24480
}
24481
24482
subscriptionChange(key, callback){
24483
if(!this.subscriptionNotifiers[key]){
24484
this.subscriptionNotifiers[key] = [];
24485
}
24486
24487
this.subscriptionNotifiers[key].push(callback);
24488
24489
if(this.subscribed(key)){
24490
this._notifySubscriptionChange(key, true);
24491
}
24492
}
24493
24494
subscribe(key, callback, priority = 10000){
24495
if(!this.events[key]){
24496
this.events[key] = [];
24497
}
24498
24499
this.events[key].push({callback, priority});
24500
24501
this.events[key].sort((a, b) => {
24502
return a.priority - b.priority;
24503
});
24504
24505
this._notifySubscriptionChange(key, true);
24506
}
24507
24508
unsubscribe(key, callback){
24509
var index;
24510
24511
if(this.events[key]){
24512
if(callback){
24513
index = this.events[key].findIndex((item) => {
24514
return item.callback === callback;
24515
});
24516
24517
if(index > -1){
24518
this.events[key].splice(index, 1);
24519
}else {
24520
console.warn("Cannot remove event, no matching event found:", key, callback);
24521
return;
24522
}
24523
}
24524
}else {
24525
console.warn("Cannot remove event, no events set on:", key);
24526
return;
24527
}
24528
24529
this._notifySubscriptionChange(key, false);
24530
}
24531
24532
subscribed(key){
24533
return this.events[key] && this.events[key].length;
24534
}
24535
24536
_chain(key, args, initialValue, fallback){
24537
var value = initialValue;
24538
24539
if(!Array.isArray(args)){
24540
args = [args];
24541
}
24542
24543
if(this.subscribed(key)){
24544
this.events[key].forEach((subscriber, i) => {
24545
value = subscriber.callback.apply(this, args.concat([value]));
24546
});
24547
24548
return value;
24549
}else {
24550
return typeof fallback === "function" ? fallback() : fallback;
24551
}
24552
}
24553
24554
_confirm(key, args){
24555
var confirmed = false;
24556
24557
if(!Array.isArray(args)){
24558
args = [args];
24559
}
24560
24561
if(this.subscribed(key)){
24562
this.events[key].forEach((subscriber, i) => {
24563
if(subscriber.callback.apply(this, args)){
24564
confirmed = true;
24565
}
24566
});
24567
}
24568
24569
return confirmed;
24570
}
24571
24572
_notifySubscriptionChange(key, subscribed){
24573
var notifiers = this.subscriptionNotifiers[key];
24574
24575
if(notifiers){
24576
notifiers.forEach((callback)=>{
24577
callback(subscribed);
24578
});
24579
}
24580
}
24581
24582
_dispatch(){
24583
var args = Array.from(arguments),
24584
key = args.shift();
24585
24586
if(this.events[key]){
24587
this.events[key].forEach((subscriber) => {
24588
subscriber.callback.apply(this, args);
24589
});
24590
}
24591
}
24592
24593
_debugDispatch(){
24594
var args = Array.from(arguments),
24595
key = args[0];
24596
24597
args[0] = "InternalEvent:" + key;
24598
24599
if(this.debug === true || this.debug.includes(key)){
24600
console.log(...args);
24601
}
24602
24603
return this._dispatch(...arguments);
24604
}
24605
24606
_debugChain(){
24607
var args = Array.from(arguments),
24608
key = args[0];
24609
24610
args[0] = "InternalEvent:" + key;
24611
24612
if(this.debug === true || this.debug.includes(key)){
24613
console.log(...args);
24614
}
24615
24616
return this._chain(...arguments);
24617
}
24618
24619
_debugConfirm(){
24620
var args = Array.from(arguments),
24621
key = args[0];
24622
24623
args[0] = "InternalEvent:" + key;
24624
24625
if(this.debug === true || this.debug.includes(key)){
24626
console.log(...args);
24627
}
24628
24629
return this._confirm(...arguments);
24630
}
24631
}
24632
24633
class DeprecationAdvisor extends CoreFeature{
24634
24635
constructor(table){
24636
super(table);
24637
}
24638
24639
_warnUser(){
24640
if(this.options("debugDeprecation")){
24641
console.warn(...arguments);
24642
}
24643
}
24644
24645
check(oldOption, newOption){
24646
var msg = "";
24647
24648
if(typeof this.options(oldOption) !== "undefined"){
24649
msg = "Deprecated Setup Option - Use of the %c" + oldOption + "%c option is now deprecated";
24650
24651
if(newOption){
24652
msg = msg + ", Please use the %c" + newOption + "%c option instead";
24653
this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;');
24654
}else {
24655
this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;');
24656
}
24657
24658
return false;
24659
}else {
24660
return true;
24661
}
24662
}
24663
24664
checkMsg(oldOption, msg){
24665
if(typeof this.options(oldOption) !== "undefined"){
24666
this._warnUser("%cDeprecated Setup Option - Use of the %c" + oldOption + " %c option is now deprecated, " + msg, 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;');
24667
24668
return false;
24669
}else {
24670
return true;
24671
}
24672
}
24673
24674
msg(msg){
24675
this._warnUser(msg);
24676
}
24677
}
24678
24679
class TableRegistry {
24680
24681
static register(table){
24682
TableRegistry.tables.push(table);
24683
}
24684
24685
static deregister(table){
24686
var index = TableRegistry.tables.indexOf(table);
24687
24688
if(index > -1){
24689
TableRegistry.tables.splice(index, 1);
24690
}
24691
}
24692
24693
static lookupTable(query, silent){
24694
var results = [],
24695
matches, match;
24696
24697
if(typeof query === "string"){
24698
matches = document.querySelectorAll(query);
24699
24700
if(matches.length){
24701
for(var i = 0; i < matches.length; i++){
24702
match = TableRegistry.matchElement(matches[i]);
24703
24704
if(match){
24705
results.push(match);
24706
}
24707
}
24708
}
24709
24710
}else if((typeof HTMLElement !== "undefined" && query instanceof HTMLElement) || query instanceof Tabulator){
24711
match = TableRegistry.matchElement(query);
24712
24713
if(match){
24714
results.push(match);
24715
}
24716
}else if(Array.isArray(query)){
24717
query.forEach(function(item){
24718
results = results.concat(TableRegistry.lookupTable(item));
24719
});
24720
}else {
24721
if(!silent){
24722
console.warn("Table Connection Error - Invalid Selector", query);
24723
}
24724
}
24725
24726
return results;
24727
}
24728
24729
static matchElement(element){
24730
return TableRegistry.tables.find(function(table){
24731
return element instanceof Tabulator ? table === element : table.element === element;
24732
});
24733
}
24734
}
24735
24736
TableRegistry.tables = [];
24737
24738
//resize columns to fit data they contain
24739
function fitData(columns, forced){
24740
if(forced){
24741
this.table.columnManager.renderer.reinitializeColumnWidths(columns);
24742
}
24743
24744
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
24745
this.table.modules.responsiveLayout.update();
24746
}
24747
}
24748
24749
//resize columns to fit data they contain and stretch row to fill table, also used for fitDataTable
24750
function fitDataGeneral(columns, forced){
24751
columns.forEach(function(column){
24752
column.reinitializeWidth();
24753
});
24754
24755
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
24756
this.table.modules.responsiveLayout.update();
24757
}
24758
}
24759
24760
//resize columns to fit data the contain and stretch last column to fill table
24761
function fitDataStretch(columns, forced){
24762
var colsWidth = 0,
24763
tableWidth = this.table.rowManager.element.clientWidth,
24764
gap = 0,
24765
lastCol = false;
24766
24767
columns.forEach((column, i) => {
24768
if(!column.widthFixed){
24769
column.reinitializeWidth();
24770
}
24771
24772
if(this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible){
24773
lastCol = column;
24774
}
24775
24776
if(column.visible){
24777
colsWidth += column.getWidth();
24778
}
24779
});
24780
24781
if(lastCol){
24782
gap = tableWidth - colsWidth + lastCol.getWidth();
24783
24784
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
24785
lastCol.setWidth(0);
24786
this.table.modules.responsiveLayout.update();
24787
}
24788
24789
if(gap > 0){
24790
lastCol.setWidth(gap);
24791
}else {
24792
lastCol.reinitializeWidth();
24793
}
24794
}else {
24795
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
24796
this.table.modules.responsiveLayout.update();
24797
}
24798
}
24799
}
24800
24801
//resize columns to fit
24802
function fitColumns(columns, forced){
24803
var totalWidth = this.table.rowManager.element.getBoundingClientRect().width; //table element width
24804
var fixedWidth = 0; //total width of columns with a defined width
24805
var flexWidth = 0; //total width available to flexible columns
24806
var flexGrowUnits = 0; //total number of widthGrow blocks across all columns
24807
var flexColWidth = 0; //desired width of flexible columns
24808
var flexColumns = []; //array of flexible width columns
24809
var fixedShrinkColumns = []; //array of fixed width columns that can shrink
24810
var flexShrinkUnits = 0; //total number of widthShrink blocks across all columns
24811
var overflowWidth = 0; //horizontal overflow width
24812
var gapFill = 0; //number of pixels to be added to final column to close and half pixel gaps
24813
24814
function calcWidth(width){
24815
var colWidth;
24816
24817
if(typeof(width) == "string"){
24818
if(width.indexOf("%") > -1){
24819
colWidth = (totalWidth / 100) * parseInt(width);
24820
}else {
24821
colWidth = parseInt(width);
24822
}
24823
}else {
24824
colWidth = width;
24825
}
24826
24827
return colWidth;
24828
}
24829
24830
//ensure columns resize to take up the correct amount of space
24831
function scaleColumns(columns, freeSpace, colWidth, shrinkCols){
24832
var oversizeCols = [],
24833
oversizeSpace = 0,
24834
remainingSpace = 0,
24835
nextColWidth = 0,
24836
remainingFlexGrowUnits = flexGrowUnits,
24837
gap = 0,
24838
changeUnits = 0,
24839
undersizeCols = [];
24840
24841
function calcGrow(col){
24842
return (colWidth * (col.column.definition.widthGrow || 1));
24843
}
24844
24845
function calcShrink(col){
24846
return (calcWidth(col.width) - (colWidth * (col.column.definition.widthShrink || 0)));
24847
}
24848
24849
columns.forEach(function(col, i){
24850
var width = shrinkCols ? calcShrink(col) : calcGrow(col);
24851
if(col.column.minWidth >= width){
24852
oversizeCols.push(col);
24853
}else {
24854
if(col.column.maxWidth && col.column.maxWidth < width){
24855
col.width = col.column.maxWidth;
24856
freeSpace -= col.column.maxWidth;
24857
24858
remainingFlexGrowUnits -= shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1);
24859
24860
if(remainingFlexGrowUnits){
24861
colWidth = Math.floor(freeSpace/remainingFlexGrowUnits);
24862
}
24863
}else {
24864
undersizeCols.push(col);
24865
changeUnits += shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1);
24866
}
24867
}
24868
});
24869
24870
if(oversizeCols.length){
24871
oversizeCols.forEach(function(col){
24872
oversizeSpace += shrinkCols ? col.width - col.column.minWidth : col.column.minWidth;
24873
col.width = col.column.minWidth;
24874
});
24875
24876
remainingSpace = freeSpace - oversizeSpace;
24877
24878
nextColWidth = changeUnits ? Math.floor(remainingSpace/changeUnits) : remainingSpace;
24879
24880
gap = scaleColumns(undersizeCols, remainingSpace, nextColWidth, shrinkCols);
24881
}else {
24882
gap = changeUnits ? freeSpace - (Math.floor(freeSpace/changeUnits) * changeUnits) : freeSpace;
24883
24884
undersizeCols.forEach(function(column){
24885
column.width = shrinkCols ? calcShrink(column) : calcGrow(column);
24886
});
24887
}
24888
24889
return gap;
24890
}
24891
24892
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
24893
this.table.modules.responsiveLayout.update();
24894
}
24895
24896
//adjust for vertical scrollbar if present
24897
if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){
24898
totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth;
24899
}
24900
24901
columns.forEach(function(column){
24902
var width, minWidth, colWidth;
24903
24904
if(column.visible){
24905
24906
width = column.definition.width;
24907
minWidth = parseInt(column.minWidth);
24908
24909
if(width){
24910
24911
colWidth = calcWidth(width);
24912
24913
fixedWidth += colWidth > minWidth ? colWidth : minWidth;
24914
24915
if(column.definition.widthShrink){
24916
fixedShrinkColumns.push({
24917
column:column,
24918
width:colWidth > minWidth ? colWidth : minWidth
24919
});
24920
flexShrinkUnits += column.definition.widthShrink;
24921
}
24922
24923
}else {
24924
flexColumns.push({
24925
column:column,
24926
width:0,
24927
});
24928
flexGrowUnits += column.definition.widthGrow || 1;
24929
}
24930
}
24931
});
24932
24933
//calculate available space
24934
flexWidth = totalWidth - fixedWidth;
24935
24936
//calculate correct column size
24937
flexColWidth = Math.floor(flexWidth / flexGrowUnits);
24938
24939
//generate column widths
24940
gapFill = scaleColumns(flexColumns, flexWidth, flexColWidth, false);
24941
24942
//increase width of last column to account for rounding errors
24943
if(flexColumns.length && gapFill > 0){
24944
flexColumns[flexColumns.length-1].width += gapFill;
24945
}
24946
24947
//calculate space for columns to be shrunk into
24948
flexColumns.forEach(function(col){
24949
flexWidth -= col.width;
24950
});
24951
24952
overflowWidth = Math.abs(gapFill) + flexWidth;
24953
24954
//shrink oversize columns if there is no available space
24955
if(overflowWidth > 0 && flexShrinkUnits){
24956
gapFill = scaleColumns(fixedShrinkColumns, overflowWidth, Math.floor(overflowWidth / flexShrinkUnits), true);
24957
}
24958
24959
//decrease width of last column to account for rounding errors
24960
if(gapFill && fixedShrinkColumns.length){
24961
fixedShrinkColumns[fixedShrinkColumns.length-1].width -= gapFill;
24962
}
24963
24964
flexColumns.forEach(function(col){
24965
col.column.setWidth(col.width);
24966
});
24967
24968
fixedShrinkColumns.forEach(function(col){
24969
col.column.setWidth(col.width);
24970
});
24971
}
24972
24973
var defaultModes = {
24974
fitData:fitData,
24975
fitDataFill:fitDataGeneral,
24976
fitDataTable:fitDataGeneral,
24977
fitDataStretch:fitDataStretch,
24978
fitColumns:fitColumns ,
24979
};
24980
24981
class Layout extends Module{
24982
24983
constructor(table){
24984
super(table, "layout");
24985
24986
this.mode = null;
24987
24988
this.registerTableOption("layout", "fitData"); //layout type
24989
this.registerTableOption("layoutColumnsOnNewData", false); //update column widths on setData
24990
24991
this.registerColumnOption("widthGrow");
24992
this.registerColumnOption("widthShrink");
24993
}
24994
24995
//initialize layout system
24996
initialize(){
24997
var layout = this.table.options.layout;
24998
24999
if(Layout.modes[layout]){
25000
this.mode = layout;
25001
}else {
25002
console.warn("Layout Error - invalid mode set, defaulting to 'fitData' : " + layout);
25003
this.mode = 'fitData';
25004
}
25005
25006
this.table.element.setAttribute("tabulator-layout", this.mode);
25007
this.subscribe("column-init", this.initializeColumn.bind(this));
25008
}
25009
25010
initializeColumn(column){
25011
if(column.definition.widthGrow){
25012
column.definition.widthGrow = Number(column.definition.widthGrow);
25013
}
25014
if(column.definition.widthShrink){
25015
column.definition.widthShrink = Number(column.definition.widthShrink);
25016
}
25017
}
25018
25019
getMode(){
25020
return this.mode;
25021
}
25022
25023
//trigger table layout
25024
layout(dataChanged){
25025
this.dispatch("layout-refreshing");
25026
Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged);
25027
this.dispatch("layout-refreshed");
25028
}
25029
}
25030
25031
Layout.moduleName = "layout";
25032
25033
//load defaults
25034
Layout.modes = defaultModes;
25035
25036
var defaultLangs = {
25037
"default":{ //hold default locale text
25038
"groups":{
25039
"item":"item",
25040
"items":"items",
25041
},
25042
"columns":{
25043
},
25044
"data":{
25045
"loading":"Loading",
25046
"error":"Error",
25047
},
25048
"pagination":{
25049
"page_size":"Page Size",
25050
"page_title":"Show Page",
25051
"first":"First",
25052
"first_title":"First Page",
25053
"last":"Last",
25054
"last_title":"Last Page",
25055
"prev":"Prev",
25056
"prev_title":"Prev Page",
25057
"next":"Next",
25058
"next_title":"Next Page",
25059
"all":"All",
25060
"counter":{
25061
"showing": "Showing",
25062
"of": "of",
25063
"rows": "rows",
25064
"pages": "pages",
25065
}
25066
},
25067
"headerFilters":{
25068
"default":"filter column...",
25069
"columns":{}
25070
}
25071
},
25072
};
25073
25074
class Localize extends Module{
25075
25076
constructor(table){
25077
super(table);
25078
25079
this.locale = "default"; //current locale
25080
this.lang = false; //current language
25081
this.bindings = {}; //update events to call when locale is changed
25082
this.langList = {};
25083
25084
this.registerTableOption("locale", false); //current system language
25085
this.registerTableOption("langs", {});
25086
}
25087
25088
initialize(){
25089
this.langList = Helpers.deepClone(Localize.langs);
25090
25091
if(this.table.options.columnDefaults.headerFilterPlaceholder !== false){
25092
this.setHeaderFilterPlaceholder(this.table.options.columnDefaults.headerFilterPlaceholder);
25093
}
25094
25095
for(let locale in this.table.options.langs){
25096
this.installLang(locale, this.table.options.langs[locale]);
25097
}
25098
25099
this.setLocale(this.table.options.locale);
25100
25101
this.registerTableFunction("setLocale", this.setLocale.bind(this));
25102
this.registerTableFunction("getLocale", this.getLocale.bind(this));
25103
this.registerTableFunction("getLang", this.getLang.bind(this));
25104
}
25105
25106
//set header placeholder
25107
setHeaderFilterPlaceholder(placeholder){
25108
this.langList.default.headerFilters.default = placeholder;
25109
}
25110
25111
//setup a lang description object
25112
installLang(locale, lang){
25113
if(this.langList[locale]){
25114
this._setLangProp(this.langList[locale], lang);
25115
}else {
25116
this.langList[locale] = lang;
25117
}
25118
}
25119
25120
_setLangProp(lang, values){
25121
for(let key in values){
25122
if(lang[key] && typeof lang[key] == "object"){
25123
this._setLangProp(lang[key], values[key]);
25124
}else {
25125
lang[key] = values[key];
25126
}
25127
}
25128
}
25129
25130
//set current locale
25131
setLocale(desiredLocale){
25132
desiredLocale = desiredLocale || "default";
25133
25134
//fill in any matching language values
25135
function traverseLang(trans, path){
25136
for(var prop in trans){
25137
if(typeof trans[prop] == "object"){
25138
if(!path[prop]){
25139
path[prop] = {};
25140
}
25141
traverseLang(trans[prop], path[prop]);
25142
}else {
25143
path[prop] = trans[prop];
25144
}
25145
}
25146
}
25147
25148
//determining correct locale to load
25149
if(desiredLocale === true && navigator.language){
25150
//get local from system
25151
desiredLocale = navigator.language.toLowerCase();
25152
}
25153
25154
if(desiredLocale){
25155
//if locale is not set, check for matching top level locale else use default
25156
if(!this.langList[desiredLocale]){
25157
let prefix = desiredLocale.split("-")[0];
25158
25159
if(this.langList[prefix]){
25160
console.warn("Localization Error - Exact matching locale not found, using closest match: ", desiredLocale, prefix);
25161
desiredLocale = prefix;
25162
}else {
25163
console.warn("Localization Error - Matching locale not found, using default: ", desiredLocale);
25164
desiredLocale = "default";
25165
}
25166
}
25167
}
25168
25169
this.locale = desiredLocale;
25170
25171
//load default lang template
25172
this.lang = Helpers.deepClone(this.langList.default || {});
25173
25174
if(desiredLocale != "default"){
25175
traverseLang(this.langList[desiredLocale], this.lang);
25176
}
25177
25178
this.dispatchExternal("localized", this.locale, this.lang);
25179
25180
this._executeBindings();
25181
}
25182
25183
//get current locale
25184
getLocale(locale){
25185
return this.locale;
25186
}
25187
25188
//get lang object for given local or current if none provided
25189
getLang(locale){
25190
return locale ? this.langList[locale] : this.lang;
25191
}
25192
25193
//get text for current locale
25194
getText(path, value){
25195
var fillPath = value ? path + "|" + value : path,
25196
pathArray = fillPath.split("|"),
25197
text = this._getLangElement(pathArray, this.locale);
25198
25199
// if(text === false){
25200
// console.warn("Localization Error - Matching localized text not found for given path: ", path);
25201
// }
25202
25203
return text || "";
25204
}
25205
25206
//traverse langs object and find localized copy
25207
_getLangElement(path, locale){
25208
var root = this.lang;
25209
25210
path.forEach(function(level){
25211
var rootPath;
25212
25213
if(root){
25214
rootPath = root[level];
25215
25216
if(typeof rootPath != "undefined"){
25217
root = rootPath;
25218
}else {
25219
root = false;
25220
}
25221
}
25222
});
25223
25224
return root;
25225
}
25226
25227
//set update binding
25228
bind(path, callback){
25229
if(!this.bindings[path]){
25230
this.bindings[path] = [];
25231
}
25232
25233
this.bindings[path].push(callback);
25234
25235
callback(this.getText(path), this.lang);
25236
}
25237
25238
//iterate through bindings and trigger updates
25239
_executeBindings(){
25240
for(let path in this.bindings){
25241
this.bindings[path].forEach((binding) => {
25242
binding(this.getText(path), this.lang);
25243
});
25244
}
25245
}
25246
}
25247
25248
Localize.moduleName = "localize";
25249
25250
//load defaults
25251
Localize.langs = defaultLangs;
25252
25253
class Comms extends Module{
25254
25255
constructor(table){
25256
super(table);
25257
}
25258
25259
initialize(){
25260
this.registerTableFunction("tableComms", this.receive.bind(this));
25261
}
25262
25263
getConnections(selectors){
25264
var connections = [],
25265
connection;
25266
25267
connection = TableRegistry.lookupTable(selectors);
25268
25269
connection.forEach((con) =>{
25270
if(this.table !== con){
25271
connections.push(con);
25272
}
25273
});
25274
25275
return connections;
25276
}
25277
25278
send(selectors, module, action, data){
25279
var connections = this.getConnections(selectors);
25280
25281
connections.forEach((connection) => {
25282
connection.tableComms(this.table.element, module, action, data);
25283
});
25284
25285
if(!connections.length && selectors){
25286
console.warn("Table Connection Error - No tables matching selector found", selectors);
25287
}
25288
}
25289
25290
receive(table, module, action, data){
25291
if(this.table.modExists(module)){
25292
return this.table.modules[module].commsReceived(table, action, data);
25293
}else {
25294
console.warn("Inter-table Comms Error - no such module:", module);
25295
}
25296
}
25297
}
25298
25299
Comms.moduleName = "comms";
25300
25301
var coreModules = /*#__PURE__*/Object.freeze({
25302
__proto__: null,
25303
LayoutModule: Layout,
25304
LocalizeModule: Localize,
25305
CommsModule: Comms
25306
});
25307
25308
class ModuleBinder {
25309
25310
constructor(tabulator, modules){
25311
this.bindStaticFunctionality(tabulator);
25312
this.bindModules(tabulator, coreModules, true);
25313
25314
if(modules){
25315
this.bindModules(tabulator, modules);
25316
}
25317
}
25318
25319
bindStaticFunctionality(tabulator){
25320
tabulator.moduleBindings = {};
25321
25322
tabulator.extendModule = function(name, property, values){
25323
if(tabulator.moduleBindings[name]){
25324
var source = tabulator.moduleBindings[name][property];
25325
25326
if(source){
25327
if(typeof values == "object"){
25328
for(let key in values){
25329
source[key] = values[key];
25330
}
25331
}else {
25332
console.warn("Module Error - Invalid value type, it must be an object");
25333
}
25334
}else {
25335
console.warn("Module Error - property does not exist:", property);
25336
}
25337
}else {
25338
console.warn("Module Error - module does not exist:", name);
25339
}
25340
};
25341
25342
tabulator.registerModule = function(modules){
25343
if(!Array.isArray(modules)){
25344
modules = [modules];
25345
}
25346
25347
modules.forEach((mod) => {
25348
tabulator.registerModuleBinding(mod);
25349
});
25350
};
25351
25352
tabulator.registerModuleBinding = function(mod){
25353
tabulator.moduleBindings[mod.moduleName] = mod;
25354
};
25355
25356
tabulator.findTable = function(query){
25357
var results = TableRegistry.lookupTable(query, true);
25358
return Array.isArray(results) && !results.length ? false : results;
25359
};
25360
25361
//ensure that module are bound to instantiated function
25362
tabulator.prototype.bindModules = function(){
25363
var orderedStartMods = [],
25364
orderedEndMods = [],
25365
unOrderedMods = [];
25366
25367
this.modules = {};
25368
25369
for(var name in tabulator.moduleBindings){
25370
let mod = tabulator.moduleBindings[name];
25371
let module = new mod(this);
25372
25373
this.modules[name] = module;
25374
25375
if(mod.prototype.moduleCore){
25376
this.modulesCore.push(module);
25377
}else {
25378
if(mod.moduleInitOrder){
25379
if(mod.moduleInitOrder < 0){
25380
orderedStartMods.push(module);
25381
}else {
25382
orderedEndMods.push(module);
25383
}
25384
25385
}else {
25386
unOrderedMods.push(module);
25387
}
25388
}
25389
}
25390
25391
orderedStartMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1);
25392
orderedEndMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1);
25393
25394
this.modulesRegular = orderedStartMods.concat(unOrderedMods.concat(orderedEndMods));
25395
};
25396
}
25397
25398
bindModules(tabulator, modules, core){
25399
var mods = Object.values(modules);
25400
25401
if(core){
25402
mods.forEach((mod) => {
25403
mod.prototype.moduleCore = true;
25404
});
25405
}
25406
25407
tabulator.registerModule(mods);
25408
}
25409
}
25410
25411
class Alert extends CoreFeature{
25412
constructor(table){
25413
super(table);
25414
25415
this.element = this._createAlertElement();
25416
this.msgElement = this._createMsgElement();
25417
this.type = null;
25418
25419
this.element.appendChild(this.msgElement);
25420
}
25421
25422
_createAlertElement(){
25423
var el = document.createElement("div");
25424
el.classList.add("tabulator-alert");
25425
return el;
25426
}
25427
25428
_createMsgElement(){
25429
var el = document.createElement("div");
25430
el.classList.add("tabulator-alert-msg");
25431
el.setAttribute("role", "alert");
25432
return el;
25433
}
25434
25435
_typeClass(){
25436
return "tabulator-alert-state-" + this.type;
25437
}
25438
25439
alert(content, type = "msg"){
25440
if(content){
25441
this.clear();
25442
25443
this.type = type;
25444
25445
while(this.msgElement.firstChild) this.msgElement.removeChild(this.msgElement.firstChild);
25446
25447
this.msgElement.classList.add(this._typeClass());
25448
25449
if(typeof content === "function"){
25450
content = content();
25451
}
25452
25453
if(content instanceof HTMLElement){
25454
this.msgElement.appendChild(content);
25455
}else {
25456
this.msgElement.innerHTML = content;
25457
}
25458
25459
this.table.element.appendChild(this.element);
25460
}
25461
}
25462
25463
clear(){
25464
if(this.element.parentNode){
25465
this.element.parentNode.removeChild(this.element);
25466
}
25467
25468
this.msgElement.classList.remove(this._typeClass());
25469
}
25470
}
25471
25472
class Tabulator {
25473
25474
constructor(element, options){
25475
25476
this.options = {};
25477
25478
this.columnManager = null; // hold Column Manager
25479
this.rowManager = null; //hold Row Manager
25480
this.footerManager = null; //holder Footer Manager
25481
this.alertManager = null; //hold Alert Manager
25482
this.vdomHoz = null; //holder horizontal virtual dom
25483
this.externalEvents = null; //handle external event messaging
25484
this.eventBus = null; //handle internal event messaging
25485
this.interactionMonitor = false; //track user interaction
25486
this.browser = ""; //hold current browser type
25487
this.browserSlow = false; //handle reduced functionality for slower browsers
25488
this.browserMobile = false; //check if running on mobile, prevent resize cancelling edit on keyboard appearance
25489
this.rtl = false; //check if the table is in RTL mode
25490
this.originalElement = null; //hold original table element if it has been replaced
25491
25492
this.componentFunctionBinder = new ComponentFunctionBinder(this); //bind component functions
25493
this.dataLoader = false; //bind component functions
25494
25495
this.modules = {}; //hold all modules bound to this table
25496
this.modulesCore = []; //hold core modules bound to this table (for initialization purposes)
25497
this.modulesRegular = []; //hold regular modules bound to this table (for initialization purposes)
25498
25499
this.deprecationAdvisor = new DeprecationAdvisor(this);
25500
this.optionsList = new OptionsList(this, "table constructor");
25501
25502
this.initialized = false;
25503
this.destroyed = false;
25504
25505
if(this.initializeElement(element)){
25506
25507
this.initializeCoreSystems(options);
25508
25509
//delay table creation to allow event bindings immediately after the constructor
25510
setTimeout(() => {
25511
this._create();
25512
});
25513
}
25514
25515
TableRegistry.register(this); //register table for inter-device communication
25516
}
25517
25518
initializeElement(element){
25519
if(typeof HTMLElement !== "undefined" && element instanceof HTMLElement){
25520
this.element = element;
25521
return true;
25522
}else if(typeof element === "string"){
25523
this.element = document.querySelector(element);
25524
25525
if(this.element){
25526
return true;
25527
}else {
25528
console.error("Tabulator Creation Error - no element found matching selector: ", element);
25529
return false;
25530
}
25531
}else {
25532
console.error("Tabulator Creation Error - Invalid element provided:", element);
25533
return false;
25534
}
25535
}
25536
25537
initializeCoreSystems(options){
25538
this.columnManager = new ColumnManager(this);
25539
this.rowManager = new RowManager(this);
25540
this.footerManager = new FooterManager(this);
25541
this.dataLoader = new DataLoader(this);
25542
this.alertManager = new Alert(this);
25543
25544
this.bindModules();
25545
25546
this.options = this.optionsList.generate(Tabulator.defaultOptions, options);
25547
25548
this._clearObjectPointers();
25549
25550
this._mapDeprecatedFunctionality();
25551
25552
this.externalEvents = new ExternalEventBus(this, this.options, this.options.debugEventsExternal);
25553
this.eventBus = new InternalEventBus(this.options.debugEventsInternal);
25554
25555
this.interactionMonitor = new InteractionManager(this);
25556
25557
this.dataLoader.initialize();
25558
// this.columnManager.initialize();
25559
// this.rowManager.initialize();
25560
this.footerManager.initialize();
25561
}
25562
25563
//convert deprecated functionality to new functions
25564
_mapDeprecatedFunctionality(){
25565
//all previously deprecated functionality removed in the 5.0 release
25566
}
25567
25568
_clearSelection(){
25569
25570
this.element.classList.add("tabulator-block-select");
25571
25572
if (window.getSelection) {
25573
if (window.getSelection().empty) { // Chrome
25574
window.getSelection().empty();
25575
} else if (window.getSelection().removeAllRanges) { // Firefox
25576
window.getSelection().removeAllRanges();
25577
}
25578
} else if (document.selection) { // IE?
25579
document.selection.empty();
25580
}
25581
25582
this.element.classList.remove("tabulator-block-select");
25583
}
25584
25585
//create table
25586
_create(){
25587
this.externalEvents.dispatch("tableBuilding");
25588
this.eventBus.dispatch("table-building");
25589
25590
this._rtlCheck();
25591
25592
this._buildElement();
25593
25594
this._initializeTable();
25595
25596
this._loadInitialData();
25597
25598
this.initialized = true;
25599
25600
this.externalEvents.dispatch("tableBuilt");
25601
}
25602
25603
_rtlCheck(){
25604
var style = window.getComputedStyle(this.element);
25605
25606
switch(this.options.textDirection){
25607
case"auto":
25608
if(style.direction !== "rtl"){
25609
break;
25610
}
25611
25612
case "rtl":
25613
this.element.classList.add("tabulator-rtl");
25614
this.rtl = true;
25615
break;
25616
25617
case "ltr":
25618
this.element.classList.add("tabulator-ltr");
25619
25620
default:
25621
this.rtl = false;
25622
}
25623
}
25624
25625
//clear pointers to objects in default config object
25626
_clearObjectPointers(){
25627
this.options.columns = this.options.columns.slice(0);
25628
25629
if(Array.isArray(this.options.data) && !this.options.reactiveData){
25630
this.options.data = this.options.data.slice(0);
25631
}
25632
}
25633
25634
//build tabulator element
25635
_buildElement(){
25636
var element = this.element,
25637
options = this.options,
25638
newElement;
25639
25640
if(element.tagName === "TABLE"){
25641
this.originalElement = this.element;
25642
newElement = document.createElement("div");
25643
25644
//transfer attributes to new element
25645
var attributes = element.attributes;
25646
25647
// loop through attributes and apply them on div
25648
for(var i in attributes){
25649
if(typeof attributes[i] == "object"){
25650
newElement.setAttribute(attributes[i].name, attributes[i].value);
25651
}
25652
}
25653
25654
// replace table with div element
25655
element.parentNode.replaceChild(newElement, element);
25656
25657
this.element = element = newElement;
25658
}
25659
25660
element.classList.add("tabulator");
25661
element.setAttribute("role", "grid");
25662
25663
//empty element
25664
while(element.firstChild) element.removeChild(element.firstChild);
25665
25666
//set table height
25667
if(options.height){
25668
options.height = isNaN(options.height) ? options.height : options.height + "px";
25669
element.style.height = options.height;
25670
}
25671
25672
//set table min height
25673
if(options.minHeight !== false){
25674
options.minHeight = isNaN(options.minHeight) ? options.minHeight : options.minHeight + "px";
25675
element.style.minHeight = options.minHeight;
25676
}
25677
25678
//set table maxHeight
25679
if(options.maxHeight !== false){
25680
options.maxHeight = isNaN(options.maxHeight) ? options.maxHeight : options.maxHeight + "px";
25681
element.style.maxHeight = options.maxHeight;
25682
}
25683
}
25684
25685
//initialize core systems and modules
25686
_initializeTable(){
25687
var element = this.element,
25688
options = this.options;
25689
25690
this.interactionMonitor.initialize();
25691
25692
this.columnManager.initialize();
25693
this.rowManager.initialize();
25694
25695
this._detectBrowser();
25696
25697
//initialize core modules
25698
this.modulesCore.forEach((mod) => {
25699
mod.initialize();
25700
});
25701
25702
//build table elements
25703
element.appendChild(this.columnManager.getElement());
25704
element.appendChild(this.rowManager.getElement());
25705
25706
if(options.footerElement){
25707
this.footerManager.activate();
25708
}
25709
25710
if(options.autoColumns && options.data){
25711
25712
this.columnManager.generateColumnsFromRowData(this.options.data);
25713
}
25714
25715
//initialize regular modules
25716
this.modulesRegular.forEach((mod) => {
25717
mod.initialize();
25718
});
25719
25720
this.columnManager.setColumns(options.columns);
25721
25722
this.eventBus.dispatch("table-built");
25723
}
25724
25725
_loadInitialData(){
25726
this.dataLoader.load(this.options.data);
25727
}
25728
25729
//deconstructor
25730
destroy(){
25731
var element = this.element;
25732
25733
this.destroyed = true;
25734
25735
TableRegistry.deregister(this); //deregister table from inter-device communication
25736
25737
this.eventBus.dispatch("table-destroy");
25738
25739
//clear row data
25740
this.rowManager.destroy();
25741
25742
//clear DOM
25743
while(element.firstChild) element.removeChild(element.firstChild);
25744
element.classList.remove("tabulator");
25745
25746
this.externalEvents.dispatch("tableDestroyed");
25747
}
25748
25749
_detectBrowser(){
25750
var ua = navigator.userAgent||navigator.vendor||window.opera;
25751
25752
if(ua.indexOf("Trident") > -1){
25753
this.browser = "ie";
25754
this.browserSlow = true;
25755
}else if(ua.indexOf("Edge") > -1){
25756
this.browser = "edge";
25757
this.browserSlow = true;
25758
}else if(ua.indexOf("Firefox") > -1){
25759
this.browser = "firefox";
25760
this.browserSlow = false;
25761
}else if(ua.indexOf("Mac OS") > -1){
25762
this.browser = "safari";
25763
this.browserSlow = false;
25764
}else {
25765
this.browser = "other";
25766
this.browserSlow = false;
25767
}
25768
25769
this.browserMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(ua)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(ua.slice(0,4));
25770
}
25771
25772
initGuard(func, msg){
25773
var stack, line;
25774
25775
if(this.options.debugInitialization && !this.initialized){
25776
if(!func){
25777
stack = new Error().stack.split("\n");
25778
25779
line = stack[0] == "Error" ? stack[2] : stack[1];
25780
25781
if(line[0] == " "){
25782
func = line.trim().split(" ")[1].split(".")[1];
25783
}else {
25784
func = line.trim().split("@")[0];
25785
}
25786
}
25787
25788
console.warn("Table Not Initialized - Calling the " + func + " function before the table is initialized may result in inconsistent behavior, Please wait for the `tableBuilt` event before calling this function." + (msg ? " " + msg : ""));
25789
}
25790
25791
return this.initialized;
25792
}
25793
25794
////////////////// Data Handling //////////////////
25795
//block table redrawing
25796
blockRedraw(){
25797
this.initGuard();
25798
25799
this.eventBus.dispatch("redraw-blocking");
25800
25801
this.rowManager.blockRedraw();
25802
this.columnManager.blockRedraw();
25803
25804
this.eventBus.dispatch("redraw-blocked");
25805
}
25806
25807
//restore table redrawing
25808
restoreRedraw(){
25809
this.initGuard();
25810
25811
this.eventBus.dispatch("redraw-restoring");
25812
25813
this.rowManager.restoreRedraw();
25814
this.columnManager.restoreRedraw();
25815
25816
this.eventBus.dispatch("redraw-restored");
25817
}
25818
25819
//load data
25820
setData(data, params, config){
25821
this.initGuard(false, "To set initial data please use the 'data' property in the table constructor.");
25822
25823
return this.dataLoader.load(data, params, config, false);
25824
}
25825
25826
//clear data
25827
clearData(){
25828
this.initGuard();
25829
25830
this.dataLoader.blockActiveLoad();
25831
this.rowManager.clearData();
25832
}
25833
25834
//get table data array
25835
getData(active){
25836
return this.rowManager.getData(active);
25837
}
25838
25839
//get table data array count
25840
getDataCount(active){
25841
return this.rowManager.getDataCount(active);
25842
}
25843
25844
//replace data, keeping table in position with same sort
25845
replaceData(data, params, config){
25846
this.initGuard();
25847
25848
return this.dataLoader.load(data, params, config, true, true);
25849
}
25850
25851
//update table data
25852
updateData(data){
25853
var responses = 0;
25854
25855
this.initGuard();
25856
25857
return new Promise((resolve, reject) => {
25858
this.dataLoader.blockActiveLoad();
25859
25860
if(typeof data === "string"){
25861
data = JSON.parse(data);
25862
}
25863
25864
if(data && data.length > 0){
25865
data.forEach((item) => {
25866
var row = this.rowManager.findRow(item[this.options.index]);
25867
25868
if(row){
25869
responses++;
25870
25871
row.updateData(item)
25872
.then(()=>{
25873
responses--;
25874
25875
if(!responses){
25876
resolve();
25877
}
25878
})
25879
.catch((e) => {
25880
reject("Update Error - Unable to update row", item, e);
25881
});
25882
}else {
25883
reject("Update Error - Unable to find row", item);
25884
}
25885
});
25886
}else {
25887
console.warn("Update Error - No data provided");
25888
reject("Update Error - No data provided");
25889
}
25890
});
25891
}
25892
25893
addData(data, pos, index){
25894
this.initGuard();
25895
25896
return new Promise((resolve, reject) => {
25897
this.dataLoader.blockActiveLoad();
25898
25899
if(typeof data === "string"){
25900
data = JSON.parse(data);
25901
}
25902
25903
if(data){
25904
this.rowManager.addRows(data, pos, index)
25905
.then((rows) => {
25906
var output = [];
25907
25908
rows.forEach(function(row){
25909
output.push(row.getComponent());
25910
});
25911
25912
resolve(output);
25913
});
25914
}else {
25915
console.warn("Update Error - No data provided");
25916
reject("Update Error - No data provided");
25917
}
25918
});
25919
}
25920
25921
//update table data
25922
updateOrAddData(data){
25923
var rows = [],
25924
responses = 0;
25925
25926
this.initGuard();
25927
25928
return new Promise((resolve, reject) => {
25929
this.dataLoader.blockActiveLoad();
25930
25931
if(typeof data === "string"){
25932
data = JSON.parse(data);
25933
}
25934
25935
if(data && data.length > 0){
25936
data.forEach((item) => {
25937
var row = this.rowManager.findRow(item[this.options.index]);
25938
25939
responses++;
25940
25941
if(row){
25942
row.updateData(item)
25943
.then(()=>{
25944
responses--;
25945
rows.push(row.getComponent());
25946
25947
if(!responses){
25948
resolve(rows);
25949
}
25950
});
25951
}else {
25952
this.rowManager.addRows(item)
25953
.then((newRows)=>{
25954
responses--;
25955
rows.push(newRows[0].getComponent());
25956
25957
if(!responses){
25958
resolve(rows);
25959
}
25960
});
25961
}
25962
});
25963
}else {
25964
console.warn("Update Error - No data provided");
25965
reject("Update Error - No data provided");
25966
}
25967
});
25968
}
25969
25970
//get row object
25971
getRow(index){
25972
var row = this.rowManager.findRow(index);
25973
25974
if(row){
25975
return row.getComponent();
25976
}else {
25977
console.warn("Find Error - No matching row found:", index);
25978
return false;
25979
}
25980
}
25981
25982
//get row object
25983
getRowFromPosition(position){
25984
var row = this.rowManager.getRowFromPosition(position);
25985
25986
if(row){
25987
return row.getComponent();
25988
}else {
25989
console.warn("Find Error - No matching row found:", position);
25990
return false;
25991
}
25992
}
25993
25994
//delete row from table
25995
deleteRow(index){
25996
var foundRows = [];
25997
25998
this.initGuard();
25999
26000
if(!Array.isArray(index)){
26001
index = [index];
26002
}
26003
26004
//find matching rows
26005
for(let item of index){
26006
let row = this.rowManager.findRow(item, true);
26007
26008
if(row){
26009
foundRows.push(row);
26010
}else {
26011
console.error("Delete Error - No matching row found:", item);
26012
return Promise.reject("Delete Error - No matching row found");
26013
}
26014
}
26015
26016
//sort rows into correct order to ensure smooth delete from table
26017
foundRows.sort((a, b) => {
26018
return this.rowManager.rows.indexOf(a) > this.rowManager.rows.indexOf(b) ? 1 : -1;
26019
});
26020
26021
//delete rows
26022
foundRows.forEach((row) =>{
26023
row.delete();
26024
});
26025
26026
this.rowManager.reRenderInPosition();
26027
26028
return Promise.resolve();
26029
}
26030
26031
//add row to table
26032
addRow(data, pos, index){
26033
this.initGuard();
26034
26035
if(typeof data === "string"){
26036
data = JSON.parse(data);
26037
}
26038
26039
return this.rowManager.addRows(data, pos, index, true)
26040
.then((rows)=>{
26041
return rows[0].getComponent();
26042
});
26043
}
26044
26045
//update a row if it exists otherwise create it
26046
updateOrAddRow(index, data){
26047
var row = this.rowManager.findRow(index);
26048
26049
this.initGuard();
26050
26051
if(typeof data === "string"){
26052
data = JSON.parse(data);
26053
}
26054
26055
if(row){
26056
return row.updateData(data)
26057
.then(()=>{
26058
return row.getComponent();
26059
});
26060
}else {
26061
return this.rowManager.addRows(data)
26062
.then((rows)=>{
26063
return rows[0].getComponent();
26064
});
26065
}
26066
}
26067
26068
//update row data
26069
updateRow(index, data){
26070
var row = this.rowManager.findRow(index);
26071
26072
this.initGuard();
26073
26074
if(typeof data === "string"){
26075
data = JSON.parse(data);
26076
}
26077
26078
if(row){
26079
return row.updateData(data)
26080
.then(()=>{
26081
return Promise.resolve(row.getComponent());
26082
});
26083
}else {
26084
console.warn("Update Error - No matching row found:", index);
26085
return Promise.reject("Update Error - No matching row found");
26086
}
26087
}
26088
26089
//scroll to row in DOM
26090
scrollToRow(index, position, ifVisible){
26091
var row = this.rowManager.findRow(index);
26092
26093
if(row){
26094
return this.rowManager.scrollToRow(row, position, ifVisible);
26095
}else {
26096
console.warn("Scroll Error - No matching row found:", index);
26097
return Promise.reject("Scroll Error - No matching row found");
26098
}
26099
}
26100
26101
moveRow(from, to, after){
26102
var fromRow = this.rowManager.findRow(from);
26103
26104
this.initGuard();
26105
26106
if(fromRow){
26107
fromRow.moveToRow(to, after);
26108
}else {
26109
console.warn("Move Error - No matching row found:", from);
26110
}
26111
}
26112
26113
getRows(active){
26114
return this.rowManager.getComponents(active);
26115
}
26116
26117
//get position of row in table
26118
getRowPosition(index){
26119
var row = this.rowManager.findRow(index);
26120
26121
if(row){
26122
return row.getPosition();
26123
}else {
26124
console.warn("Position Error - No matching row found:", index);
26125
return false;
26126
}
26127
}
26128
26129
/////////////// Column Functions ///////////////
26130
setColumns(definition){
26131
this.initGuard(false, "To set initial columns please use the 'columns' property in the table constructor");
26132
26133
this.columnManager.setColumns(definition);
26134
}
26135
26136
getColumns(structured){
26137
return this.columnManager.getComponents(structured);
26138
}
26139
26140
getColumn(field){
26141
var column = this.columnManager.findColumn(field);
26142
26143
if(column){
26144
return column.getComponent();
26145
}else {
26146
console.warn("Find Error - No matching column found:", field);
26147
return false;
26148
}
26149
}
26150
26151
getColumnDefinitions(){
26152
return this.columnManager.getDefinitionTree();
26153
}
26154
26155
showColumn(field){
26156
var column = this.columnManager.findColumn(field);
26157
26158
this.initGuard();
26159
26160
if(column){
26161
column.show();
26162
}else {
26163
console.warn("Column Show Error - No matching column found:", field);
26164
return false;
26165
}
26166
}
26167
26168
hideColumn(field){
26169
var column = this.columnManager.findColumn(field);
26170
26171
this.initGuard();
26172
26173
if(column){
26174
column.hide();
26175
}else {
26176
console.warn("Column Hide Error - No matching column found:", field);
26177
return false;
26178
}
26179
}
26180
26181
toggleColumn(field){
26182
var column = this.columnManager.findColumn(field);
26183
26184
this.initGuard();
26185
26186
if(column){
26187
if(column.visible){
26188
column.hide();
26189
}else {
26190
column.show();
26191
}
26192
}else {
26193
console.warn("Column Visibility Toggle Error - No matching column found:", field);
26194
return false;
26195
}
26196
}
26197
26198
addColumn(definition, before, field){
26199
var column = this.columnManager.findColumn(field);
26200
26201
this.initGuard();
26202
26203
return this.columnManager.addColumn(definition, before, column)
26204
.then((column) => {
26205
return column.getComponent();
26206
});
26207
}
26208
26209
deleteColumn(field){
26210
var column = this.columnManager.findColumn(field);
26211
26212
this.initGuard();
26213
26214
if(column){
26215
return column.delete();
26216
}else {
26217
console.warn("Column Delete Error - No matching column found:", field);
26218
return Promise.reject();
26219
}
26220
}
26221
26222
updateColumnDefinition(field, definition){
26223
var column = this.columnManager.findColumn(field);
26224
26225
this.initGuard();
26226
26227
if(column){
26228
return column.updateDefinition(definition);
26229
}else {
26230
console.warn("Column Update Error - No matching column found:", field);
26231
return Promise.reject();
26232
}
26233
}
26234
26235
moveColumn(from, to, after){
26236
var fromColumn = this.columnManager.findColumn(from),
26237
toColumn = this.columnManager.findColumn(to);
26238
26239
this.initGuard();
26240
26241
if(fromColumn){
26242
if(toColumn){
26243
this.columnManager.moveColumn(fromColumn, toColumn, after);
26244
}else {
26245
console.warn("Move Error - No matching column found:", toColumn);
26246
}
26247
}else {
26248
console.warn("Move Error - No matching column found:", from);
26249
}
26250
}
26251
26252
//scroll to column in DOM
26253
scrollToColumn(field, position, ifVisible){
26254
return new Promise((resolve, reject) => {
26255
var column = this.columnManager.findColumn(field);
26256
26257
if(column){
26258
return this.columnManager.scrollToColumn(column, position, ifVisible);
26259
}else {
26260
console.warn("Scroll Error - No matching column found:", field);
26261
return Promise.reject("Scroll Error - No matching column found");
26262
}
26263
});
26264
}
26265
26266
//////////// General Public Functions ////////////
26267
//redraw list without updating data
26268
redraw(force){
26269
this.initGuard();
26270
26271
this.columnManager.redraw(force);
26272
this.rowManager.redraw(force);
26273
}
26274
26275
setHeight(height){
26276
this.options.height = isNaN(height) ? height : height + "px";
26277
this.element.style.height = this.options.height;
26278
this.rowManager.initializeRenderer();
26279
this.rowManager.redraw();
26280
}
26281
26282
//////////////////// Event Bus ///////////////////
26283
26284
on(key, callback){
26285
this.externalEvents.subscribe(key, callback);
26286
}
26287
26288
off(key, callback){
26289
this.externalEvents.unsubscribe(key, callback);
26290
}
26291
26292
dispatchEvent(){
26293
var args = Array.from(arguments);
26294
args.shift();
26295
26296
this.externalEvents.dispatch(...arguments);
26297
}
26298
26299
//////////////////// Alerts ///////////////////
26300
26301
alert(contents, type){
26302
this.initGuard();
26303
26304
this.alertManager.alert(contents, type);
26305
}
26306
26307
clearAlert(){
26308
this.initGuard();
26309
26310
this.alertManager.clear();
26311
}
26312
26313
////////////// Extension Management //////////////
26314
modExists(plugin, required){
26315
if(this.modules[plugin]){
26316
return true;
26317
}else {
26318
if(required){
26319
console.error("Tabulator Module Not Installed: " + plugin);
26320
}
26321
return false;
26322
}
26323
}
26324
26325
module(key){
26326
var mod = this.modules[key];
26327
26328
if(!mod){
26329
console.error("Tabulator module not installed: " + key);
26330
}
26331
26332
return mod;
26333
}
26334
}
26335
26336
//default setup options
26337
Tabulator.defaultOptions = defaultOptions;
26338
26339
//bind modules and static functionality
26340
new ModuleBinder(Tabulator);
26341
26342
//tabulator with all modules installed
26343
26344
class TabulatorFull extends Tabulator {}
26345
26346
//bind modules and static functionality
26347
new ModuleBinder(TabulatorFull, modules);
26348
26349
class PseudoRow {
26350
26351
constructor (type){
26352
this.type = type;
26353
this.element = this._createElement();
26354
}
26355
26356
_createElement(){
26357
var el = document.createElement("div");
26358
el.classList.add("tabulator-row");
26359
return el;
26360
}
26361
26362
getElement(){
26363
return this.element;
26364
}
26365
26366
getComponent(){
26367
return false;
26368
}
26369
26370
getData(){
26371
return {};
26372
}
26373
26374
getHeight(){
26375
return this.element.outerHeight;
26376
}
26377
26378
initialize(){}
26379
26380
reinitialize(){}
26381
26382
normalizeHeight(){}
26383
26384
generateCells(){}
26385
26386
reinitializeHeight(){}
26387
26388
calcHeight(){}
26389
26390
setCellHeight(){}
26391
26392
clearCellHeight(){}
26393
}
26394
26395
export { Accessor as AccessorModule, Ajax as AjaxModule, CalcComponent, CellComponent, Clipboard as ClipboardModule, ColumnCalcs as ColumnCalcsModule, ColumnComponent, DataTree as DataTreeModule, Download as DownloadModule, Edit$1 as EditModule, Export as ExportModule, Filter as FilterModule, Format as FormatModule, FrozenColumns as FrozenColumnsModule, FrozenRows as FrozenRowsModule, GroupComponent, GroupRows as GroupRowsModule, History as HistoryModule, HtmlTableImport as HtmlTableImportModule, Import as ImportModule, Interaction as InteractionModule, Keybindings as KeybindingsModule, Menu as MenuModule, Module, MoveColumns as MoveColumnsModule, MoveRows as MoveRowsModule, Mutator as MutatorModule, Page as PageModule, Persistence as PersistenceModule, Popup$1 as PopupModule, Print as PrintModule, PseudoRow, ReactiveData as ReactiveDataModule, Renderer, ResizeColumns as ResizeColumnsModule, ResizeRows as ResizeRowsModule, ResizeTable as ResizeTableModule, ResponsiveLayout as ResponsiveLayoutModule, RowComponent, SelectRow as SelectRowModule, Sort as SortModule, Tabulator, TabulatorFull, Tooltip as TooltipModule, Validate as ValidateModule };
26396
//# sourceMappingURL=tabulator_esm.js.map
26397
26398