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.js
3562 views
1
/* Tabulator v5.5.2 (c) Oliver Folkerd 2023 */
2
(function (global, factory) {
3
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4
typeof define === 'function' && define.amd ? define(factory) :
5
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tabulator = factory());
6
}(this, (function () { 'use strict';
7
8
var defaultOptions = {
9
10
debugEventsExternal:false, //flag to console log events
11
debugEventsInternal:false, //flag to console log events
12
debugInvalidOptions:true, //allow toggling of invalid option warnings
13
debugInvalidComponentFuncs:true, //allow toggling of invalid component warnings
14
debugInitialization:true, //allow toggling of pre initialization function call warnings
15
debugDeprecation:true, //allow toggling of deprecation warnings
16
17
height:false, //height of tabulator
18
minHeight:false, //minimum height of tabulator
19
maxHeight:false, //maximum height of tabulator
20
21
columnHeaderVertAlign:"top", //vertical alignment of column headers
22
23
popupContainer:false,
24
25
columns:[],//store for colum header info
26
columnDefaults:{}, //store column default props
27
28
data:false, //default starting data
29
30
autoColumns:false, //build columns from data row structure
31
autoColumnsDefinitions:false,
32
33
nestedFieldSeparator:".", //separator for nested data
34
35
footerElement:false, //hold footer element
36
37
index:"id", //filed for row index
38
39
textDirection:"auto",
40
41
addRowPos:"bottom", //position to insert blank rows, top|bottom
42
43
headerVisible:true, //hide header
44
45
renderVertical:"virtual",
46
renderHorizontal:"basic",
47
renderVerticalBuffer:0, // set virtual DOM buffer size
48
49
scrollToRowPosition:"top",
50
scrollToRowIfVisible:true,
51
52
scrollToColumnPosition:"left",
53
scrollToColumnIfVisible:true,
54
55
rowFormatter:false,
56
rowFormatterPrint:null,
57
rowFormatterClipboard:null,
58
rowFormatterHtmlOutput:null,
59
60
rowHeight:null,
61
62
placeholder:false,
63
64
dataLoader:true,
65
dataLoaderLoading:false,
66
dataLoaderError:false,
67
dataLoaderErrorTimeout:3000,
68
69
dataSendParams:{},
70
71
dataReceiveParams:{},
72
};
73
74
class CoreFeature{
75
76
constructor(table){
77
this.table = table;
78
}
79
80
//////////////////////////////////////////
81
/////////////// DataLoad /////////////////
82
//////////////////////////////////////////
83
84
reloadData(data, silent, columnsChanged){
85
return this.table.dataLoader.load(data, undefined, undefined, undefined, silent, columnsChanged);
86
}
87
88
//////////////////////////////////////////
89
///////////// Localization ///////////////
90
//////////////////////////////////////////
91
92
langText(){
93
return this.table.modules.localize.getText(...arguments);
94
}
95
96
langBind(){
97
return this.table.modules.localize.bind(...arguments);
98
}
99
100
langLocale(){
101
return this.table.modules.localize.getLocale(...arguments);
102
}
103
104
105
//////////////////////////////////////////
106
////////// Inter Table Comms /////////////
107
//////////////////////////////////////////
108
109
commsConnections(){
110
return this.table.modules.comms.getConnections(...arguments);
111
}
112
113
commsSend(){
114
return this.table.modules.comms.send(...arguments);
115
}
116
117
//////////////////////////////////////////
118
//////////////// Layout /////////////////
119
//////////////////////////////////////////
120
121
layoutMode(){
122
return this.table.modules.layout.getMode();
123
}
124
125
layoutRefresh(force){
126
return this.table.modules.layout.layout(force);
127
}
128
129
130
//////////////////////////////////////////
131
/////////////// Event Bus ////////////////
132
//////////////////////////////////////////
133
134
subscribe(){
135
return this.table.eventBus.subscribe(...arguments);
136
}
137
138
unsubscribe(){
139
return this.table.eventBus.unsubscribe(...arguments);
140
}
141
142
subscribed(key){
143
return this.table.eventBus.subscribed(key);
144
}
145
146
subscriptionChange(){
147
return this.table.eventBus.subscriptionChange(...arguments);
148
}
149
150
dispatch(){
151
return this.table.eventBus.dispatch(...arguments);
152
}
153
154
chain(){
155
return this.table.eventBus.chain(...arguments);
156
}
157
158
confirm(){
159
return this.table.eventBus.confirm(...arguments);
160
}
161
162
dispatchExternal(){
163
return this.table.externalEvents.dispatch(...arguments);
164
}
165
166
subscribedExternal(key){
167
return this.table.externalEvents.subscribed(key);
168
}
169
170
subscriptionChangeExternal(){
171
return this.table.externalEvents.subscriptionChange(...arguments);
172
}
173
174
//////////////////////////////////////////
175
//////////////// Options /////////////////
176
//////////////////////////////////////////
177
178
options(key){
179
return this.table.options[key];
180
}
181
182
setOption(key, value){
183
if(typeof value !== "undefined"){
184
this.table.options[key] = value;
185
}
186
187
return this.table.options[key];
188
}
189
190
//////////////////////////////////////////
191
/////////// Deprecation Checks ///////////
192
//////////////////////////////////////////
193
194
deprecationCheck(oldOption, newOption){
195
return this.table.deprecationAdvisor.check(oldOption, newOption);
196
}
197
198
deprecationCheckMsg(oldOption, msg){
199
return this.table.deprecationAdvisor.checkMsg(oldOption, msg);
200
}
201
202
deprecationMsg(msg){
203
return this.table.deprecationAdvisor.msg(msg);
204
}
205
//////////////////////////////////////////
206
//////////////// Modules /////////////////
207
//////////////////////////////////////////
208
209
module(key){
210
return this.table.module(key);
211
}
212
}
213
214
//public column object
215
class ColumnComponent {
216
constructor (column){
217
this._column = column;
218
this.type = "ColumnComponent";
219
220
return new Proxy(this, {
221
get: function(target, name, receiver) {
222
if (typeof target[name] !== "undefined") {
223
return target[name];
224
}else {
225
return target._column.table.componentFunctionBinder.handle("column", target._column, name);
226
}
227
}
228
});
229
}
230
231
getElement(){
232
return this._column.getElement();
233
}
234
235
getDefinition(){
236
return this._column.getDefinition();
237
}
238
239
getField(){
240
return this._column.getField();
241
}
242
243
getTitleDownload() {
244
return this._column.getTitleDownload();
245
}
246
247
getCells(){
248
var cells = [];
249
250
this._column.cells.forEach(function(cell){
251
cells.push(cell.getComponent());
252
});
253
254
return cells;
255
}
256
257
isVisible(){
258
return this._column.visible;
259
}
260
261
show(){
262
if(this._column.isGroup){
263
this._column.columns.forEach(function(column){
264
column.show();
265
});
266
}else {
267
this._column.show();
268
}
269
}
270
271
hide(){
272
if(this._column.isGroup){
273
this._column.columns.forEach(function(column){
274
column.hide();
275
});
276
}else {
277
this._column.hide();
278
}
279
}
280
281
toggle(){
282
if(this._column.visible){
283
this.hide();
284
}else {
285
this.show();
286
}
287
}
288
289
delete(){
290
return this._column.delete();
291
}
292
293
getSubColumns(){
294
var output = [];
295
296
if(this._column.columns.length){
297
this._column.columns.forEach(function(column){
298
output.push(column.getComponent());
299
});
300
}
301
302
return output;
303
}
304
305
getParentColumn(){
306
return this._column.parent instanceof Column ? this._column.parent.getComponent() : false;
307
}
308
309
_getSelf(){
310
return this._column;
311
}
312
313
scrollTo(position, ifVisible){
314
return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible);
315
}
316
317
getTable(){
318
return this._column.table;
319
}
320
321
move(to, after){
322
var toColumn = this._column.table.columnManager.findColumn(to);
323
324
if(toColumn){
325
this._column.table.columnManager.moveColumn(this._column, toColumn, after);
326
}else {
327
console.warn("Move Error - No matching column found:", toColumn);
328
}
329
}
330
331
getNextColumn(){
332
var nextCol = this._column.nextColumn();
333
334
return nextCol ? nextCol.getComponent() : false;
335
}
336
337
getPrevColumn(){
338
var prevCol = this._column.prevColumn();
339
340
return prevCol ? prevCol.getComponent() : false;
341
}
342
343
updateDefinition(updates){
344
return this._column.updateDefinition(updates);
345
}
346
347
getWidth(){
348
return this._column.getWidth();
349
}
350
351
setWidth(width){
352
var result;
353
354
if(width === true){
355
result = this._column.reinitializeWidth(true);
356
}else {
357
result = this._column.setWidth(width);
358
}
359
360
this._column.table.columnManager.rerenderColumns(true);
361
362
return result;
363
}
364
}
365
366
var defaultColumnOptions = {
367
"title": undefined,
368
"field": undefined,
369
"columns": undefined,
370
"visible": undefined,
371
"hozAlign": undefined,
372
"vertAlign": undefined,
373
"width": undefined,
374
"minWidth": 40,
375
"maxWidth": undefined,
376
"maxInitialWidth": undefined,
377
"cssClass": undefined,
378
"variableHeight": undefined,
379
"headerVertical": undefined,
380
"headerHozAlign": undefined,
381
"headerWordWrap": false,
382
"editableTitle": undefined,
383
};
384
385
//public cell object
386
class CellComponent {
387
388
constructor (cell){
389
this._cell = cell;
390
391
return new Proxy(this, {
392
get: function(target, name, receiver) {
393
if (typeof target[name] !== "undefined") {
394
return target[name];
395
}else {
396
return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name);
397
}
398
}
399
});
400
}
401
402
getValue(){
403
return this._cell.getValue();
404
}
405
406
getOldValue(){
407
return this._cell.getOldValue();
408
}
409
410
getInitialValue(){
411
return this._cell.initialValue;
412
}
413
414
getElement(){
415
return this._cell.getElement();
416
}
417
418
getRow(){
419
return this._cell.row.getComponent();
420
}
421
422
getData(transform){
423
return this._cell.row.getData(transform);
424
}
425
getType(){
426
return "cell";
427
}
428
getField(){
429
return this._cell.column.getField();
430
}
431
432
getColumn(){
433
return this._cell.column.getComponent();
434
}
435
436
setValue(value, mutate){
437
if(typeof mutate == "undefined"){
438
mutate = true;
439
}
440
441
this._cell.setValue(value, mutate);
442
}
443
444
restoreOldValue(){
445
this._cell.setValueActual(this._cell.getOldValue());
446
}
447
448
restoreInitialValue(){
449
this._cell.setValueActual(this._cell.initialValue);
450
}
451
452
checkHeight(){
453
this._cell.checkHeight();
454
}
455
456
getTable(){
457
return this._cell.table;
458
}
459
460
_getSelf(){
461
return this._cell;
462
}
463
}
464
465
class Cell extends CoreFeature{
466
constructor(column, row){
467
super(column.table);
468
469
this.table = column.table;
470
this.column = column;
471
this.row = row;
472
this.element = null;
473
this.value = null;
474
this.initialValue;
475
this.oldValue = null;
476
this.modules = {};
477
478
this.height = null;
479
this.width = null;
480
this.minWidth = null;
481
482
this.component = null;
483
484
this.loaded = false; //track if the cell has been added to the DOM yet
485
486
this.build();
487
}
488
489
//////////////// Setup Functions /////////////////
490
//generate element
491
build(){
492
this.generateElement();
493
494
this.setWidth();
495
496
this._configureCell();
497
498
this.setValueActual(this.column.getFieldValue(this.row.data));
499
500
this.initialValue = this.value;
501
}
502
503
generateElement(){
504
this.element = document.createElement('div');
505
this.element.className = "tabulator-cell";
506
this.element.setAttribute("role", "gridcell");
507
}
508
509
_configureCell(){
510
var element = this.element,
511
field = this.column.getField(),
512
vertAligns = {
513
top:"flex-start",
514
bottom:"flex-end",
515
middle:"center",
516
},
517
hozAligns = {
518
left:"flex-start",
519
right:"flex-end",
520
center:"center",
521
};
522
523
//set text alignment
524
element.style.textAlign = this.column.hozAlign;
525
526
if(this.column.vertAlign){
527
element.style.display = "inline-flex";
528
529
element.style.alignItems = vertAligns[this.column.vertAlign] || "";
530
531
if(this.column.hozAlign){
532
element.style.justifyContent = hozAligns[this.column.hozAlign] || "";
533
}
534
}
535
536
if(field){
537
element.setAttribute("tabulator-field", field);
538
}
539
540
//add class to cell if needed
541
if(this.column.definition.cssClass){
542
var classNames = this.column.definition.cssClass.split(" ");
543
classNames.forEach((className) => {
544
element.classList.add(className);
545
});
546
}
547
548
this.dispatch("cell-init", this);
549
550
//hide cell if not visible
551
if(!this.column.visible){
552
this.hide();
553
}
554
}
555
556
//generate cell contents
557
_generateContents(){
558
var val;
559
560
val = this.chain("cell-format", this, null, () => {
561
return this.element.innerHTML = this.value;
562
});
563
564
switch(typeof val){
565
case "object":
566
if(val instanceof Node){
567
568
//clear previous cell contents
569
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
570
571
this.element.appendChild(val);
572
}else {
573
this.element.innerHTML = "";
574
575
if(val != null){
576
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);
577
}
578
}
579
break;
580
case "undefined":
581
this.element.innerHTML = "";
582
break;
583
default:
584
this.element.innerHTML = val;
585
}
586
}
587
588
cellRendered(){
589
this.dispatch("cell-rendered", this);
590
}
591
592
//////////////////// Getters ////////////////////
593
getElement(containerOnly){
594
if(!this.loaded){
595
this.loaded = true;
596
if(!containerOnly){
597
this.layoutElement();
598
}
599
}
600
601
return this.element;
602
}
603
604
getValue(){
605
return this.value;
606
}
607
608
getOldValue(){
609
return this.oldValue;
610
}
611
612
//////////////////// Actions ////////////////////
613
setValue(value, mutate, force){
614
var changed = this.setValueProcessData(value, mutate, force);
615
616
if(changed){
617
this.dispatch("cell-value-updated", this);
618
619
this.cellRendered();
620
621
if(this.column.definition.cellEdited){
622
this.column.definition.cellEdited.call(this.table, this.getComponent());
623
}
624
625
this.dispatchExternal("cellEdited", this.getComponent());
626
627
if(this.subscribedExternal("dataChanged")){
628
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
629
}
630
}
631
}
632
633
setValueProcessData(value, mutate, force){
634
var changed = false;
635
636
if(this.value !== value || force){
637
638
changed = true;
639
640
if(mutate){
641
value = this.chain("cell-value-changing", [this, value], null, value);
642
}
643
}
644
645
this.setValueActual(value);
646
647
if(changed){
648
this.dispatch("cell-value-changed", this);
649
}
650
651
return changed;
652
}
653
654
setValueActual(value){
655
this.oldValue = this.value;
656
657
this.value = value;
658
659
this.dispatch("cell-value-save-before", this);
660
661
this.column.setFieldValue(this.row.data, value);
662
663
this.dispatch("cell-value-save-after", this);
664
665
if(this.loaded){
666
this.layoutElement();
667
}
668
}
669
670
layoutElement(){
671
this._generateContents();
672
673
this.dispatch("cell-layout", this);
674
}
675
676
setWidth(){
677
this.width = this.column.width;
678
this.element.style.width = this.column.widthStyled;
679
}
680
681
clearWidth(){
682
this.width = "";
683
this.element.style.width = "";
684
}
685
686
getWidth(){
687
return this.width || this.element.offsetWidth;
688
}
689
690
setMinWidth(){
691
this.minWidth = this.column.minWidth;
692
this.element.style.minWidth = this.column.minWidthStyled;
693
}
694
695
setMaxWidth(){
696
this.maxWidth = this.column.maxWidth;
697
this.element.style.maxWidth = this.column.maxWidthStyled;
698
}
699
700
checkHeight(){
701
// var height = this.element.css("height");
702
this.row.reinitializeHeight();
703
}
704
705
clearHeight(){
706
this.element.style.height = "";
707
this.height = null;
708
709
this.dispatch("cell-height", this, "");
710
}
711
712
setHeight(){
713
this.height = this.row.height;
714
this.element.style.height = this.row.heightStyled;
715
716
this.dispatch("cell-height", this, this.row.heightStyled);
717
}
718
719
getHeight(){
720
return this.height || this.element.offsetHeight;
721
}
722
723
show(){
724
this.element.style.display = this.column.vertAlign ? "inline-flex" : "";
725
}
726
727
hide(){
728
this.element.style.display = "none";
729
}
730
731
delete(){
732
this.dispatch("cell-delete", this);
733
734
if(!this.table.rowManager.redrawBlock && this.element.parentNode){
735
this.element.parentNode.removeChild(this.element);
736
}
737
738
this.element = false;
739
this.column.deleteCell(this);
740
this.row.deleteCell(this);
741
this.calcs = {};
742
}
743
744
getIndex(){
745
return this.row.getCellIndex(this);
746
}
747
748
//////////////// Object Generation /////////////////
749
getComponent(){
750
if(!this.component){
751
this.component = new CellComponent(this);
752
}
753
754
return this.component;
755
}
756
}
757
758
class Column extends CoreFeature{
759
760
constructor(def, parent){
761
super(parent.table);
762
763
this.definition = def; //column definition
764
this.parent = parent; //hold parent object
765
this.type = "column"; //type of element
766
this.columns = []; //child columns
767
this.cells = []; //cells bound to this column
768
this.element = this.createElement(); //column header element
769
this.contentElement = false;
770
this.titleHolderElement = false;
771
this.titleElement = false;
772
this.groupElement = this.createGroupElement(); //column group holder element
773
this.isGroup = false;
774
this.hozAlign = ""; //horizontal text alignment
775
this.vertAlign = ""; //vert text alignment
776
777
//multi dimensional filed handling
778
this.field ="";
779
this.fieldStructure = "";
780
this.getFieldValue = "";
781
this.setFieldValue = "";
782
783
this.titleDownload = null;
784
this.titleFormatterRendered = false;
785
786
this.mapDefinitions();
787
788
this.setField(this.definition.field);
789
790
this.modules = {}; //hold module variables;
791
792
this.width = null; //column width
793
this.widthStyled = ""; //column width pre-styled to improve render efficiency
794
this.maxWidth = null; //column maximum width
795
this.maxWidthStyled = ""; //column maximum pre-styled to improve render efficiency
796
this.maxInitialWidth = null;
797
this.minWidth = null; //column minimum width
798
this.minWidthStyled = ""; //column minimum pre-styled to improve render efficiency
799
this.widthFixed = false; //user has specified a width for this column
800
801
this.visible = true; //default visible state
802
803
this.component = null;
804
805
//initialize column
806
if(this.definition.columns){
807
808
this.isGroup = true;
809
810
this.definition.columns.forEach((def, i) => {
811
var newCol = new Column(def, this);
812
this.attachColumn(newCol);
813
});
814
815
this.checkColumnVisibility();
816
}else {
817
parent.registerColumnField(this);
818
}
819
820
this._initialize();
821
}
822
823
createElement (){
824
var el = document.createElement("div");
825
826
el.classList.add("tabulator-col");
827
el.setAttribute("role", "columnheader");
828
el.setAttribute("aria-sort", "none");
829
830
switch(this.table.options.columnHeaderVertAlign){
831
case "middle":
832
el.style.justifyContent = "center";
833
break;
834
case "bottom":
835
el.style.justifyContent = "flex-end";
836
break;
837
}
838
839
return el;
840
}
841
842
createGroupElement (){
843
var el = document.createElement("div");
844
845
el.classList.add("tabulator-col-group-cols");
846
847
return el;
848
}
849
850
mapDefinitions(){
851
var defaults = this.table.options.columnDefaults;
852
853
//map columnDefaults onto column definitions
854
if(defaults){
855
for(let key in defaults){
856
if(typeof this.definition[key] === "undefined"){
857
this.definition[key] = defaults[key];
858
}
859
}
860
}
861
862
this.definition = this.table.columnManager.optionsList.generate(Column.defaultOptionList, this.definition);
863
}
864
865
checkDefinition(){
866
Object.keys(this.definition).forEach((key) => {
867
if(Column.defaultOptionList.indexOf(key) === -1){
868
console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key);
869
}
870
});
871
}
872
873
setField(field){
874
this.field = field;
875
this.fieldStructure = field ? (this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field]) : [];
876
this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData;
877
this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData;
878
}
879
880
//register column position with column manager
881
registerColumnPosition(column){
882
this.parent.registerColumnPosition(column);
883
}
884
885
//register column position with column manager
886
registerColumnField(column){
887
this.parent.registerColumnField(column);
888
}
889
890
//trigger position registration
891
reRegisterPosition(){
892
if(this.isGroup){
893
this.columns.forEach(function(column){
894
column.reRegisterPosition();
895
});
896
}else {
897
this.registerColumnPosition(this);
898
}
899
}
900
901
//build header element
902
_initialize(){
903
var def = this.definition;
904
905
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
906
907
if(def.headerVertical){
908
this.element.classList.add("tabulator-col-vertical");
909
910
if(def.headerVertical === "flip"){
911
this.element.classList.add("tabulator-col-vertical-flip");
912
}
913
}
914
915
this.contentElement = this._buildColumnHeaderContent();
916
917
this.element.appendChild(this.contentElement);
918
919
if(this.isGroup){
920
this._buildGroupHeader();
921
}else {
922
this._buildColumnHeader();
923
}
924
925
this.dispatch("column-init", this);
926
}
927
928
//build header element for header
929
_buildColumnHeader(){
930
var def = this.definition;
931
932
this.dispatch("column-layout", this);
933
934
//set column visibility
935
if(typeof def.visible != "undefined"){
936
if(def.visible){
937
this.show(true);
938
}else {
939
this.hide(true);
940
}
941
}
942
943
//assign additional css classes to column header
944
if(def.cssClass){
945
var classNames = def.cssClass.split(" ");
946
classNames.forEach((className) => {
947
this.element.classList.add(className);
948
});
949
}
950
951
if(def.field){
952
this.element.setAttribute("tabulator-field", def.field);
953
}
954
955
//set min width if present
956
this.setMinWidth(parseInt(def.minWidth));
957
958
if (def.maxInitialWidth) {
959
this.maxInitialWidth = parseInt(def.maxInitialWidth);
960
}
961
962
if(def.maxWidth){
963
this.setMaxWidth(parseInt(def.maxWidth));
964
}
965
966
this.reinitializeWidth();
967
968
//set horizontal text alignment
969
this.hozAlign = this.definition.hozAlign;
970
this.vertAlign = this.definition.vertAlign;
971
972
this.titleElement.style.textAlign = this.definition.headerHozAlign;
973
}
974
975
_buildColumnHeaderContent(){
976
var contentElement = document.createElement("div");
977
contentElement.classList.add("tabulator-col-content");
978
979
this.titleHolderElement = document.createElement("div");
980
this.titleHolderElement.classList.add("tabulator-col-title-holder");
981
982
contentElement.appendChild(this.titleHolderElement);
983
984
this.titleElement = this._buildColumnHeaderTitle();
985
986
this.titleHolderElement.appendChild(this.titleElement);
987
988
return contentElement;
989
}
990
991
//build title element of column
992
_buildColumnHeaderTitle(){
993
var def = this.definition;
994
995
var titleHolderElement = document.createElement("div");
996
titleHolderElement.classList.add("tabulator-col-title");
997
998
if(def.headerWordWrap){
999
titleHolderElement.classList.add("tabulator-col-title-wrap");
1000
}
1001
1002
if(def.editableTitle){
1003
var titleElement = document.createElement("input");
1004
titleElement.classList.add("tabulator-title-editor");
1005
1006
titleElement.addEventListener("click", (e) => {
1007
e.stopPropagation();
1008
titleElement.focus();
1009
});
1010
1011
titleElement.addEventListener("change", () => {
1012
def.title = titleElement.value;
1013
this.dispatchExternal("columnTitleChanged", this.getComponent());
1014
});
1015
1016
titleHolderElement.appendChild(titleElement);
1017
1018
if(def.field){
1019
this.langBind("columns|" + def.field, (text) => {
1020
titleElement.value = text || (def.title || " ");
1021
});
1022
}else {
1023
titleElement.value = def.title || " ";
1024
}
1025
1026
}else {
1027
if(def.field){
1028
this.langBind("columns|" + def.field, (text) => {
1029
this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || " "));
1030
});
1031
}else {
1032
this._formatColumnHeaderTitle(titleHolderElement, def.title || " ");
1033
}
1034
}
1035
1036
return titleHolderElement;
1037
}
1038
1039
_formatColumnHeaderTitle(el, title){
1040
var contents = this.chain("column-format", [this, title, el], null, () => {
1041
return title;
1042
});
1043
1044
switch(typeof contents){
1045
case "object":
1046
if(contents instanceof Node){
1047
el.appendChild(contents);
1048
}else {
1049
el.innerHTML = "";
1050
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);
1051
}
1052
break;
1053
case "undefined":
1054
el.innerHTML = "";
1055
break;
1056
default:
1057
el.innerHTML = contents;
1058
}
1059
}
1060
1061
//build header element for column group
1062
_buildGroupHeader(){
1063
this.element.classList.add("tabulator-col-group");
1064
this.element.setAttribute("role", "columngroup");
1065
this.element.setAttribute("aria-title", this.definition.title);
1066
1067
//asign additional css classes to column header
1068
if(this.definition.cssClass){
1069
var classNames = this.definition.cssClass.split(" ");
1070
classNames.forEach((className) => {
1071
this.element.classList.add(className);
1072
});
1073
}
1074
1075
this.titleElement.style.textAlign = this.definition.headerHozAlign;
1076
1077
this.element.appendChild(this.groupElement);
1078
}
1079
1080
//flat field lookup
1081
_getFlatData(data){
1082
return data[this.field];
1083
}
1084
1085
//nested field lookup
1086
_getNestedData(data){
1087
var dataObj = data,
1088
structure = this.fieldStructure,
1089
length = structure.length,
1090
output;
1091
1092
for(let i = 0; i < length; i++){
1093
1094
dataObj = dataObj[structure[i]];
1095
1096
output = dataObj;
1097
1098
if(!dataObj){
1099
break;
1100
}
1101
}
1102
1103
return output;
1104
}
1105
1106
//flat field set
1107
_setFlatData(data, value){
1108
if(this.field){
1109
data[this.field] = value;
1110
}
1111
}
1112
1113
//nested field set
1114
_setNestedData(data, value){
1115
var dataObj = data,
1116
structure = this.fieldStructure,
1117
length = structure.length;
1118
1119
for(let i = 0; i < length; i++){
1120
1121
if(i == length -1){
1122
dataObj[structure[i]] = value;
1123
}else {
1124
if(!dataObj[structure[i]]){
1125
if(typeof value !== "undefined"){
1126
dataObj[structure[i]] = {};
1127
}else {
1128
break;
1129
}
1130
}
1131
1132
dataObj = dataObj[structure[i]];
1133
}
1134
}
1135
}
1136
1137
//attach column to this group
1138
attachColumn(column){
1139
if(this.groupElement){
1140
this.columns.push(column);
1141
this.groupElement.appendChild(column.getElement());
1142
1143
column.columnRendered();
1144
}else {
1145
console.warn("Column Warning - Column being attached to another column instead of column group");
1146
}
1147
}
1148
1149
//vertically align header in column
1150
verticalAlign(alignment, height){
1151
1152
//calculate height of column header and group holder element
1153
var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : (height || this.parent.getHeadersElement().clientHeight);
1154
// var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : this.parent.getHeadersElement().clientHeight;
1155
1156
this.element.style.height = parentHeight + "px";
1157
1158
this.dispatch("column-height", this, this.element.style.height);
1159
1160
if(this.isGroup){
1161
this.groupElement.style.minHeight = (parentHeight - this.contentElement.offsetHeight) + "px";
1162
}
1163
1164
//vertically align cell contents
1165
// if(!this.isGroup && alignment !== "top"){
1166
// if(alignment === "bottom"){
1167
// this.element.style.paddingTop = (this.element.clientHeight - this.contentElement.offsetHeight) + "px";
1168
// }else{
1169
// this.element.style.paddingTop = ((this.element.clientHeight - this.contentElement.offsetHeight) / 2) + "px";
1170
// }
1171
// }
1172
1173
this.columns.forEach(function(column){
1174
column.verticalAlign(alignment);
1175
});
1176
}
1177
1178
//clear vertical alignment
1179
clearVerticalAlign(){
1180
this.element.style.paddingTop = "";
1181
this.element.style.height = "";
1182
this.element.style.minHeight = "";
1183
this.groupElement.style.minHeight = "";
1184
1185
this.columns.forEach(function(column){
1186
column.clearVerticalAlign();
1187
});
1188
1189
this.dispatch("column-height", this, "");
1190
}
1191
1192
//// Retrieve Column Information ////
1193
//return column header element
1194
getElement(){
1195
return this.element;
1196
}
1197
1198
//return column group element
1199
getGroupElement(){
1200
return this.groupElement;
1201
}
1202
1203
//return field name
1204
getField(){
1205
return this.field;
1206
}
1207
1208
getTitleDownload() {
1209
return this.titleDownload;
1210
}
1211
1212
//return the first column in a group
1213
getFirstColumn(){
1214
if(!this.isGroup){
1215
return this;
1216
}else {
1217
if(this.columns.length){
1218
return this.columns[0].getFirstColumn();
1219
}else {
1220
return false;
1221
}
1222
}
1223
}
1224
1225
//return the last column in a group
1226
getLastColumn(){
1227
if(!this.isGroup){
1228
return this;
1229
}else {
1230
if(this.columns.length){
1231
return this.columns[this.columns.length -1].getLastColumn();
1232
}else {
1233
return false;
1234
}
1235
}
1236
}
1237
1238
//return all columns in a group
1239
getColumns(traverse){
1240
var columns = [];
1241
1242
if(traverse){
1243
this.columns.forEach((column) => {
1244
columns.push(column);
1245
1246
columns = columns.concat(column.getColumns(true));
1247
});
1248
}else {
1249
columns = this.columns;
1250
}
1251
1252
return columns;
1253
}
1254
1255
//return all columns in a group
1256
getCells(){
1257
return this.cells;
1258
}
1259
1260
//retrieve the top column in a group of columns
1261
getTopColumn(){
1262
if(this.parent.isGroup){
1263
return this.parent.getTopColumn();
1264
}else {
1265
return this;
1266
}
1267
}
1268
1269
//return column definition object
1270
getDefinition(updateBranches){
1271
var colDefs = [];
1272
1273
if(this.isGroup && updateBranches){
1274
this.columns.forEach(function(column){
1275
colDefs.push(column.getDefinition(true));
1276
});
1277
1278
this.definition.columns = colDefs;
1279
}
1280
1281
return this.definition;
1282
}
1283
1284
//////////////////// Actions ////////////////////
1285
checkColumnVisibility(){
1286
var visible = false;
1287
1288
this.columns.forEach(function(column){
1289
if(column.visible){
1290
visible = true;
1291
}
1292
});
1293
1294
if(visible){
1295
this.show();
1296
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
1297
}else {
1298
this.hide();
1299
}
1300
}
1301
1302
//show column
1303
show(silent, responsiveToggle){
1304
if(!this.visible){
1305
this.visible = true;
1306
1307
this.element.style.display = "";
1308
1309
if(this.parent.isGroup){
1310
this.parent.checkColumnVisibility();
1311
}
1312
1313
this.cells.forEach(function(cell){
1314
cell.show();
1315
});
1316
1317
if(!this.isGroup && this.width === null){
1318
this.reinitializeWidth();
1319
}
1320
1321
this.table.columnManager.verticalAlignHeaders();
1322
1323
this.dispatch("column-show", this, responsiveToggle);
1324
1325
if(!silent){
1326
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), true);
1327
}
1328
1329
if(this.parent.isGroup){
1330
this.parent.matchChildWidths();
1331
}
1332
1333
if(!this.silent){
1334
this.table.columnManager.rerenderColumns();
1335
}
1336
}
1337
}
1338
1339
//hide column
1340
hide(silent, responsiveToggle){
1341
if(this.visible){
1342
this.visible = false;
1343
1344
this.element.style.display = "none";
1345
1346
this.table.columnManager.verticalAlignHeaders();
1347
1348
if(this.parent.isGroup){
1349
this.parent.checkColumnVisibility();
1350
}
1351
1352
this.cells.forEach(function(cell){
1353
cell.hide();
1354
});
1355
1356
this.dispatch("column-hide", this, responsiveToggle);
1357
1358
if(!silent){
1359
this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
1360
}
1361
1362
if(this.parent.isGroup){
1363
this.parent.matchChildWidths();
1364
}
1365
1366
if(!this.silent){
1367
this.table.columnManager.rerenderColumns();
1368
}
1369
}
1370
}
1371
1372
matchChildWidths(){
1373
var childWidth = 0;
1374
1375
if(this.contentElement && this.columns.length){
1376
this.columns.forEach(function(column){
1377
if(column.visible){
1378
childWidth += column.getWidth();
1379
}
1380
});
1381
1382
this.contentElement.style.maxWidth = (childWidth - 1) + "px";
1383
1384
if(this.parent.isGroup){
1385
this.parent.matchChildWidths();
1386
}
1387
}
1388
}
1389
1390
removeChild(child){
1391
var index = this.columns.indexOf(child);
1392
1393
if(index > -1){
1394
this.columns.splice(index, 1);
1395
}
1396
1397
if(!this.columns.length){
1398
this.delete();
1399
}
1400
}
1401
1402
setWidth(width){
1403
this.widthFixed = true;
1404
this.setWidthActual(width);
1405
}
1406
1407
setWidthActual(width){
1408
if(isNaN(width)){
1409
width = Math.floor((this.table.element.clientWidth/100) * parseInt(width));
1410
}
1411
1412
width = Math.max(this.minWidth, width);
1413
1414
if(this.maxWidth){
1415
width = Math.min(this.maxWidth, width);
1416
}
1417
1418
this.width = width;
1419
this.widthStyled = width ? width + "px" : "";
1420
1421
this.element.style.width = this.widthStyled;
1422
1423
if(!this.isGroup){
1424
this.cells.forEach(function(cell){
1425
cell.setWidth();
1426
});
1427
}
1428
1429
if(this.parent.isGroup){
1430
this.parent.matchChildWidths();
1431
}
1432
1433
this.dispatch("column-width", this);
1434
}
1435
1436
checkCellHeights(){
1437
var rows = [];
1438
1439
this.cells.forEach(function(cell){
1440
if(cell.row.heightInitialized){
1441
if(cell.row.getElement().offsetParent !== null){
1442
rows.push(cell.row);
1443
cell.row.clearCellHeight();
1444
}else {
1445
cell.row.heightInitialized = false;
1446
}
1447
}
1448
});
1449
1450
rows.forEach(function(row){
1451
row.calcHeight();
1452
});
1453
1454
rows.forEach(function(row){
1455
row.setCellHeight();
1456
});
1457
}
1458
1459
getWidth(){
1460
var width = 0;
1461
1462
if(this.isGroup){
1463
this.columns.forEach(function(column){
1464
if(column.visible){
1465
width += column.getWidth();
1466
}
1467
});
1468
}else {
1469
width = this.width;
1470
}
1471
1472
return width;
1473
}
1474
1475
getLeftOffset(){
1476
var offset = this.element.offsetLeft;
1477
1478
if(this.parent.isGroup){
1479
offset += this.parent.getLeftOffset();
1480
}
1481
1482
return offset;
1483
}
1484
1485
getHeight(){
1486
return Math.ceil(this.element.getBoundingClientRect().height);
1487
}
1488
1489
setMinWidth(minWidth){
1490
if(this.maxWidth && minWidth > this.maxWidth){
1491
minWidth = this.maxWidth;
1492
1493
console.warn("the minWidth ("+ minWidth + "px) for column '" + this.field + "' cannot be bigger that its maxWidth ("+ this.maxWidthStyled + ")");
1494
}
1495
1496
this.minWidth = minWidth;
1497
this.minWidthStyled = minWidth ? minWidth + "px" : "";
1498
1499
this.element.style.minWidth = this.minWidthStyled;
1500
1501
this.cells.forEach(function(cell){
1502
cell.setMinWidth();
1503
});
1504
}
1505
1506
setMaxWidth(maxWidth){
1507
if(this.minWidth && maxWidth < this.minWidth){
1508
maxWidth = this.minWidth;
1509
1510
console.warn("the maxWidth ("+ maxWidth + "px) for column '" + this.field + "' cannot be smaller that its minWidth ("+ this.minWidthStyled + ")");
1511
}
1512
1513
this.maxWidth = maxWidth;
1514
this.maxWidthStyled = maxWidth ? maxWidth + "px" : "";
1515
1516
this.element.style.maxWidth = this.maxWidthStyled;
1517
1518
this.cells.forEach(function(cell){
1519
cell.setMaxWidth();
1520
});
1521
}
1522
1523
delete(){
1524
return new Promise((resolve, reject) => {
1525
if(this.isGroup){
1526
this.columns.forEach(function(column){
1527
column.delete();
1528
});
1529
}
1530
1531
this.dispatch("column-delete", this);
1532
1533
var cellCount = this.cells.length;
1534
1535
for(let i = 0; i < cellCount; i++){
1536
this.cells[0].delete();
1537
}
1538
1539
if(this.element.parentNode){
1540
this.element.parentNode.removeChild(this.element);
1541
}
1542
1543
this.element = false;
1544
this.contentElement = false;
1545
this.titleElement = false;
1546
this.groupElement = false;
1547
1548
if(this.parent.isGroup){
1549
this.parent.removeChild(this);
1550
}
1551
1552
this.table.columnManager.deregisterColumn(this);
1553
1554
this.table.columnManager.rerenderColumns(true);
1555
1556
resolve();
1557
});
1558
}
1559
1560
columnRendered(){
1561
if(this.titleFormatterRendered){
1562
this.titleFormatterRendered();
1563
}
1564
1565
this.dispatch("column-rendered", this);
1566
}
1567
1568
//////////////// Cell Management /////////////////
1569
//generate cell for this column
1570
generateCell(row){
1571
var cell = new Cell(this, row);
1572
1573
this.cells.push(cell);
1574
1575
return cell;
1576
}
1577
1578
nextColumn(){
1579
var index = this.table.columnManager.findColumnIndex(this);
1580
return index > -1 ? this._nextVisibleColumn(index + 1) : false;
1581
}
1582
1583
_nextVisibleColumn(index){
1584
var column = this.table.columnManager.getColumnByIndex(index);
1585
return !column || column.visible ? column : this._nextVisibleColumn(index + 1);
1586
}
1587
1588
prevColumn(){
1589
var index = this.table.columnManager.findColumnIndex(this);
1590
return index > -1 ? this._prevVisibleColumn(index - 1) : false;
1591
}
1592
1593
_prevVisibleColumn(index){
1594
var column = this.table.columnManager.getColumnByIndex(index);
1595
return !column || column.visible ? column : this._prevVisibleColumn(index - 1);
1596
}
1597
1598
reinitializeWidth(force){
1599
this.widthFixed = false;
1600
1601
//set width if present
1602
if(typeof this.definition.width !== "undefined" && !force){
1603
// maxInitialWidth ignored here as width specified
1604
this.setWidth(this.definition.width);
1605
}
1606
1607
this.dispatch("column-width-fit-before", this);
1608
1609
this.fitToData(force);
1610
1611
this.dispatch("column-width-fit-after", this);
1612
}
1613
1614
//set column width to maximum cell width for non group columns
1615
fitToData(force){
1616
if(this.isGroup){
1617
return;
1618
}
1619
1620
if(!this.widthFixed){
1621
this.element.style.width = "";
1622
1623
this.cells.forEach((cell) => {
1624
cell.clearWidth();
1625
});
1626
}
1627
1628
var maxWidth = this.element.offsetWidth;
1629
1630
if(!this.width || !this.widthFixed){
1631
this.cells.forEach((cell) => {
1632
var width = cell.getWidth();
1633
1634
if(width > maxWidth){
1635
maxWidth = width;
1636
}
1637
});
1638
1639
if(maxWidth){
1640
var setTo = maxWidth + 1;
1641
if (this.maxInitialWidth && !force) {
1642
setTo = Math.min(setTo, this.maxInitialWidth);
1643
}
1644
this.setWidthActual(setTo);
1645
}
1646
}
1647
}
1648
1649
updateDefinition(updates){
1650
var definition;
1651
1652
if(!this.isGroup){
1653
if(!this.parent.isGroup){
1654
definition = Object.assign({}, this.getDefinition());
1655
definition = Object.assign(definition, updates);
1656
1657
return this.table.columnManager.addColumn(definition, false, this)
1658
.then((column) => {
1659
1660
if(definition.field == this.field){
1661
this.field = false; //clear field name to prevent deletion of duplicate column from arrays
1662
}
1663
1664
return this.delete()
1665
.then(() => {
1666
return column.getComponent();
1667
});
1668
1669
});
1670
}else {
1671
console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
1672
return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
1673
}
1674
}else {
1675
console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
1676
return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
1677
}
1678
}
1679
1680
deleteCell(cell){
1681
var index = this.cells.indexOf(cell);
1682
1683
if(index > -1){
1684
this.cells.splice(index, 1);
1685
}
1686
}
1687
1688
//////////////// Object Generation /////////////////
1689
getComponent(){
1690
if(!this.component){
1691
this.component = new ColumnComponent(this);
1692
}
1693
1694
return this.component;
1695
}
1696
}
1697
1698
Column.defaultOptionList = defaultColumnOptions;
1699
1700
class Helpers{
1701
1702
static elVisible(el){
1703
return !(el.offsetWidth <= 0 && el.offsetHeight <= 0);
1704
}
1705
1706
static elOffset(el){
1707
var box = el.getBoundingClientRect();
1708
1709
return {
1710
top: box.top + window.pageYOffset - document.documentElement.clientTop,
1711
left: box.left + window.pageXOffset - document.documentElement.clientLeft
1712
};
1713
}
1714
1715
static deepClone(obj, clone, list = []){
1716
var objectProto = {}.__proto__,
1717
arrayProto = [].__proto__;
1718
1719
if (!clone){
1720
clone = Object.assign(Array.isArray(obj) ? [] : {}, obj);
1721
}
1722
1723
for(var i in obj) {
1724
let subject = obj[i],
1725
match, copy;
1726
1727
if(subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)){
1728
match = list.findIndex((item) => {
1729
return item.subject === subject;
1730
});
1731
1732
if(match > -1){
1733
clone[i] = list[match].copy;
1734
}else {
1735
copy = Object.assign(Array.isArray(subject) ? [] : {}, subject);
1736
1737
list.unshift({subject, copy});
1738
1739
clone[i] = this.deepClone(subject, copy, list);
1740
}
1741
}
1742
}
1743
1744
return clone;
1745
}
1746
}
1747
1748
class OptionsList {
1749
constructor(table, msgType, defaults = {}){
1750
this.table = table;
1751
this.msgType = msgType;
1752
this.registeredDefaults = Object.assign({}, defaults);
1753
}
1754
1755
register(option, value){
1756
this.registeredDefaults[option] = value;
1757
}
1758
1759
generate(defaultOptions, userOptions = {}){
1760
var output = Object.assign({}, this.registeredDefaults),
1761
warn = this.table.options.debugInvalidOptions || userOptions.debugInvalidOptions === true;
1762
1763
Object.assign(output, defaultOptions);
1764
1765
for (let key in userOptions){
1766
if(!output.hasOwnProperty(key)){
1767
if(warn){
1768
console.warn("Invalid " + this.msgType + " option:", key);
1769
}
1770
1771
output[key] = userOptions.key;
1772
}
1773
}
1774
1775
1776
for (let key in output){
1777
if(key in userOptions){
1778
output[key] = userOptions[key];
1779
}else {
1780
if(Array.isArray(output[key])){
1781
output[key] = Object.assign([], output[key]);
1782
}else if(typeof output[key] === "object" && output[key] !== null){
1783
output[key] = Object.assign({}, output[key]);
1784
}else if (typeof output[key] === "undefined"){
1785
delete output[key];
1786
}
1787
}
1788
}
1789
1790
return output;
1791
}
1792
}
1793
1794
class Renderer extends CoreFeature{
1795
constructor(table){
1796
super(table);
1797
1798
this.elementVertical = table.rowManager.element;
1799
this.elementHorizontal = table.columnManager.element;
1800
this.tableElement = table.rowManager.tableElement;
1801
1802
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)
1803
}
1804
1805
1806
///////////////////////////////////
1807
/////// Internal Bindings /////////
1808
///////////////////////////////////
1809
1810
initialize(){
1811
//initialize core functionality
1812
}
1813
1814
clearRows(){
1815
//clear down existing rows layout
1816
}
1817
1818
clearColumns(){
1819
//clear down existing columns layout
1820
}
1821
1822
1823
reinitializeColumnWidths(columns){
1824
//resize columns to fit data
1825
}
1826
1827
1828
renderRows(){
1829
//render rows from a clean slate
1830
}
1831
1832
renderColumns(){
1833
//render columns from a clean slate
1834
}
1835
1836
rerenderRows(callback){
1837
// rerender rows and keep position
1838
if(callback){
1839
callback();
1840
}
1841
}
1842
1843
rerenderColumns(update, blockRedraw){
1844
//rerender columns
1845
}
1846
1847
renderRowCells(row){
1848
//render the cells in a row
1849
}
1850
1851
rerenderRowCells(row, force){
1852
//rerender the cells in a row
1853
}
1854
1855
scrollColumns(left, dir){
1856
//handle horizontal scrolling
1857
}
1858
1859
scrollRows(top, dir){
1860
//handle vertical scrolling
1861
}
1862
1863
resize(){
1864
//container has resized, carry out any needed recalculations (DO NOT RERENDER IN THIS FUNCTION)
1865
}
1866
1867
scrollToRow(row){
1868
//scroll to a specific row
1869
}
1870
1871
scrollToRowNearestTop(row){
1872
//determine weather the row is nearest the top or bottom of the table, return true for top or false for bottom
1873
}
1874
1875
visibleRows(includingBuffer){
1876
//return the visible rows
1877
return [];
1878
}
1879
1880
///////////////////////////////////
1881
//////// Helper Functions /////////
1882
///////////////////////////////////
1883
1884
rows(){
1885
return this.table.rowManager.getDisplayRows();
1886
}
1887
1888
styleRow(row, index){
1889
var rowEl = row.getElement();
1890
1891
if(index % 2){
1892
rowEl.classList.add("tabulator-row-even");
1893
rowEl.classList.remove("tabulator-row-odd");
1894
}else {
1895
rowEl.classList.add("tabulator-row-odd");
1896
rowEl.classList.remove("tabulator-row-even");
1897
}
1898
}
1899
1900
///////////////////////////////////
1901
/////// External Triggers /////////
1902
/////// (DO NOT OVERRIDE) /////////
1903
///////////////////////////////////
1904
1905
clear(){
1906
//clear down existing layout
1907
this.clearRows();
1908
this.clearColumns();
1909
}
1910
1911
render(){
1912
//render from a clean slate
1913
this.renderRows();
1914
this.renderColumns();
1915
}
1916
1917
rerender(callback){
1918
// rerender and keep position
1919
this.rerenderRows();
1920
this.rerenderColumns();
1921
}
1922
1923
scrollToRowPosition(row, position, ifVisible){
1924
var rowIndex = this.rows().indexOf(row),
1925
rowEl = row.getElement(),
1926
offset = 0;
1927
1928
return new Promise((resolve, reject) => {
1929
if(rowIndex > -1){
1930
1931
if(typeof ifVisible === "undefined"){
1932
ifVisible = this.table.options.scrollToRowIfVisible;
1933
}
1934
1935
//check row visibility
1936
if(!ifVisible){
1937
if(Helpers.elVisible(rowEl)){
1938
offset = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top;
1939
1940
if(offset > 0 && offset < this.elementVertical.clientHeight - rowEl.offsetHeight){
1941
resolve();
1942
return false;
1943
}
1944
}
1945
}
1946
1947
if(typeof position === "undefined"){
1948
position = this.table.options.scrollToRowPosition;
1949
}
1950
1951
if(position === "nearest"){
1952
position = this.scrollToRowNearestTop(row) ? "top" : "bottom";
1953
}
1954
1955
//scroll to row
1956
this.scrollToRow(row);
1957
1958
//align to correct position
1959
switch(position){
1960
case "middle":
1961
case "center":
1962
1963
if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){
1964
this.elementVertical.scrollTop = this.elementVertical.scrollTop + (rowEl.offsetTop - this.elementVertical.scrollTop) - ((this.elementVertical.scrollHeight - rowEl.offsetTop) / 2);
1965
}else {
1966
this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.clientHeight / 2);
1967
}
1968
1969
break;
1970
1971
case "bottom":
1972
1973
if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){
1974
this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.scrollHeight - rowEl.offsetTop) + rowEl.offsetHeight;
1975
}else {
1976
this.elementVertical.scrollTop = this.elementVertical.scrollTop - this.elementVertical.clientHeight + rowEl.offsetHeight;
1977
}
1978
1979
break;
1980
1981
case "top":
1982
this.elementVertical.scrollTop = rowEl.offsetTop;
1983
break;
1984
}
1985
1986
resolve();
1987
1988
}else {
1989
console.warn("Scroll Error - Row not visible");
1990
reject("Scroll Error - Row not visible");
1991
}
1992
});
1993
}
1994
}
1995
1996
class BasicHorizontal extends Renderer{
1997
constructor(table){
1998
super(table);
1999
}
2000
2001
renderRowCells(row, inFragment) {
2002
const rowFrag = document.createDocumentFragment();
2003
row.cells.forEach((cell) => {
2004
rowFrag.appendChild(cell.getElement());
2005
});
2006
row.element.appendChild(rowFrag);
2007
2008
if(!inFragment){
2009
row.cells.forEach((cell) => {
2010
cell.cellRendered();
2011
});
2012
}
2013
}
2014
2015
reinitializeColumnWidths(columns){
2016
columns.forEach(function(column){
2017
column.reinitializeWidth();
2018
});
2019
}
2020
}
2021
2022
class VirtualDomHorizontal extends Renderer{
2023
constructor(table){
2024
super(table);
2025
2026
this.leftCol = 0;
2027
this.rightCol = 0;
2028
this.scrollLeft = 0;
2029
2030
this.vDomScrollPosLeft = 0;
2031
this.vDomScrollPosRight = 0;
2032
2033
this.vDomPadLeft = 0;
2034
this.vDomPadRight = 0;
2035
2036
this.fitDataColAvg = 0;
2037
2038
this.windowBuffer = 200; //pixel margin to make column visible before it is shown on screen
2039
2040
this.visibleRows = null;
2041
2042
this.initialized = false;
2043
this.isFitData = false;
2044
2045
this.columns = [];
2046
}
2047
2048
initialize(){
2049
this.compatibilityCheck();
2050
this.layoutCheck();
2051
this.vertScrollListen();
2052
}
2053
2054
compatibilityCheck(){
2055
if(this.options("layout") == "fitDataTable"){
2056
console.warn("Horizontal Virtual DOM is not compatible with fitDataTable layout mode");
2057
}
2058
2059
if(this.options("responsiveLayout")){
2060
console.warn("Horizontal Virtual DOM is not compatible with responsive columns");
2061
}
2062
2063
if(this.options("rtl")){
2064
console.warn("Horizontal Virtual DOM is not currently compatible with RTL text direction");
2065
}
2066
}
2067
2068
layoutCheck(){
2069
this.isFitData = this.options("layout").startsWith('fitData');
2070
}
2071
2072
vertScrollListen(){
2073
this.subscribe("scroll-vertical", this.clearVisRowCache.bind(this));
2074
this.subscribe("data-refreshed", this.clearVisRowCache.bind(this));
2075
}
2076
2077
clearVisRowCache(){
2078
this.visibleRows = null;
2079
}
2080
2081
//////////////////////////////////////
2082
///////// Public Functions ///////////
2083
//////////////////////////////////////
2084
2085
renderColumns(row, force){
2086
this.dataChange();
2087
}
2088
2089
2090
scrollColumns(left, dir){
2091
if(this.scrollLeft != left){
2092
this.scrollLeft = left;
2093
2094
this.scroll(left - (this.vDomScrollPosLeft + this.windowBuffer));
2095
}
2096
}
2097
2098
calcWindowBuffer(){
2099
var buffer = this.elementVertical.clientWidth;
2100
2101
this.table.columnManager.columnsByIndex.forEach((column) => {
2102
if(column.visible){
2103
var width = column.getWidth();
2104
2105
if(width > buffer){
2106
buffer = width;
2107
}
2108
}
2109
});
2110
2111
this.windowBuffer = buffer * 2;
2112
}
2113
2114
rerenderColumns(update, blockRedraw){
2115
var old = {
2116
cols:this.columns,
2117
leftCol:this.leftCol,
2118
rightCol:this.rightCol,
2119
},
2120
colPos = 0;
2121
2122
if(update && !this.initialized){
2123
return;
2124
}
2125
2126
this.clear();
2127
2128
this.calcWindowBuffer();
2129
2130
this.scrollLeft = this.elementVertical.scrollLeft;
2131
2132
this.vDomScrollPosLeft = this.scrollLeft - this.windowBuffer;
2133
this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer;
2134
2135
this.table.columnManager.columnsByIndex.forEach((column) => {
2136
var config = {},
2137
width;
2138
2139
if(column.visible){
2140
if(!column.modules.frozen){
2141
width = column.getWidth();
2142
2143
config.leftPos = colPos;
2144
config.rightPos = colPos + width;
2145
2146
config.width = width;
2147
2148
if (this.isFitData) {
2149
config.fitDataCheck = column.modules.vdomHoz ? column.modules.vdomHoz.fitDataCheck : true;
2150
}
2151
2152
if((colPos + width > this.vDomScrollPosLeft) && (colPos < this.vDomScrollPosRight)){
2153
//column is visible
2154
2155
if(this.leftCol == -1){
2156
this.leftCol = this.columns.length;
2157
this.vDomPadLeft = colPos;
2158
}
2159
2160
this.rightCol = this.columns.length;
2161
}else {
2162
// column is hidden
2163
if(this.leftCol !== -1){
2164
this.vDomPadRight += width;
2165
}
2166
}
2167
2168
this.columns.push(column);
2169
2170
column.modules.vdomHoz = config;
2171
2172
colPos += width;
2173
}
2174
}
2175
});
2176
2177
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
2178
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
2179
2180
this.initialized = true;
2181
2182
if(!blockRedraw){
2183
if(!update || this.reinitChanged(old)){
2184
this.reinitializeRows();
2185
}
2186
}
2187
2188
this.elementVertical.scrollLeft = this.scrollLeft;
2189
}
2190
2191
renderRowCells(row){
2192
if(this.initialized){
2193
this.initializeRow(row);
2194
}else {
2195
const rowFrag = document.createDocumentFragment();
2196
row.cells.forEach((cell) => {
2197
rowFrag.appendChild(cell.getElement());
2198
});
2199
row.element.appendChild(rowFrag);
2200
2201
row.cells.forEach((cell) => {
2202
cell.cellRendered();
2203
});
2204
}
2205
}
2206
2207
rerenderRowCells(row, force){
2208
this.reinitializeRow(row, force);
2209
}
2210
2211
reinitializeColumnWidths(columns){
2212
for(let i = this.leftCol; i <= this.rightCol; i++){
2213
this.columns[i].reinitializeWidth();
2214
}
2215
}
2216
2217
//////////////////////////////////////
2218
//////// Internal Rendering //////////
2219
//////////////////////////////////////
2220
2221
deinitialize(){
2222
this.initialized = false;
2223
}
2224
2225
clear(){
2226
this.columns = [];
2227
2228
this.leftCol = -1;
2229
this.rightCol = 0;
2230
2231
this.vDomScrollPosLeft = 0;
2232
this.vDomScrollPosRight = 0;
2233
this.vDomPadLeft = 0;
2234
this.vDomPadRight = 0;
2235
}
2236
2237
dataChange(){
2238
var change = false,
2239
row, rowEl;
2240
2241
if(this.isFitData){
2242
this.table.columnManager.columnsByIndex.forEach((column) => {
2243
if(!column.definition.width && column.visible){
2244
change = true;
2245
}
2246
});
2247
2248
if(change && this.table.rowManager.getDisplayRows().length){
2249
this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer;
2250
2251
row = this.chain("rows-sample", [1], [], () => {
2252
return this.table.rowManager.getDisplayRows();
2253
})[0];
2254
2255
if(row){
2256
rowEl = row.getElement();
2257
2258
row.generateCells();
2259
2260
this.tableElement.appendChild(rowEl);
2261
2262
for(let colEnd = 0; colEnd < row.cells.length; colEnd++){
2263
let cell = row.cells[colEnd];
2264
rowEl.appendChild(cell.getElement());
2265
2266
cell.column.reinitializeWidth();
2267
}
2268
2269
rowEl.parentNode.removeChild(rowEl);
2270
2271
this.rerenderColumns(false, true);
2272
}
2273
}
2274
}else {
2275
if(this.options("layout") === "fitColumns"){
2276
this.layoutRefresh();
2277
this.rerenderColumns(false, true);
2278
}
2279
}
2280
}
2281
2282
reinitChanged(old){
2283
var match = true;
2284
2285
if(old.cols.length !== this.columns.length || old.leftCol !== this.leftCol || old.rightCol !== this.rightCol){
2286
return true;
2287
}
2288
2289
old.cols.forEach((col, i) => {
2290
if(col !== this.columns[i]){
2291
match = false;
2292
}
2293
});
2294
2295
return !match;
2296
}
2297
2298
reinitializeRows(){
2299
var visibleRows = this.getVisibleRows(),
2300
otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row));
2301
2302
visibleRows.forEach((row) => {
2303
this.reinitializeRow(row, true);
2304
});
2305
2306
otherRows.forEach((row) =>{
2307
row.deinitialize();
2308
});
2309
}
2310
2311
getVisibleRows(){
2312
if (!this.visibleRows){
2313
this.visibleRows = this.table.rowManager.getVisibleRows();
2314
}
2315
2316
return this.visibleRows;
2317
}
2318
2319
scroll(diff){
2320
this.vDomScrollPosLeft += diff;
2321
this.vDomScrollPosRight += diff;
2322
2323
if(Math.abs(diff) > (this.windowBuffer / 2)){
2324
this.rerenderColumns();
2325
}else {
2326
if(diff > 0){
2327
//scroll right
2328
this.addColRight();
2329
this.removeColLeft();
2330
}else {
2331
//scroll left
2332
this.addColLeft();
2333
this.removeColRight();
2334
}
2335
}
2336
}
2337
2338
colPositionAdjust (start, end, diff){
2339
for(let i = start; i < end; i++){
2340
let column = this.columns[i];
2341
2342
column.modules.vdomHoz.leftPos += diff;
2343
column.modules.vdomHoz.rightPos += diff;
2344
}
2345
}
2346
2347
addColRight(){
2348
var changes = false,
2349
working = true;
2350
2351
while(working){
2352
2353
let column = this.columns[this.rightCol + 1];
2354
2355
if(column){
2356
if(column.modules.vdomHoz.leftPos <= this.vDomScrollPosRight){
2357
changes = true;
2358
2359
this.getVisibleRows().forEach((row) => {
2360
if(row.type !== "group"){
2361
var cell = row.getCell(column);
2362
row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.rightCol]).getElement().nextSibling);
2363
cell.cellRendered();
2364
}
2365
});
2366
2367
this.fitDataColActualWidthCheck(column);
2368
2369
this.rightCol++; // Don't move this below the >= check below
2370
2371
this.getVisibleRows().forEach((row) => {
2372
if(row.type !== "group"){
2373
row.modules.vdomHoz.rightCol = this.rightCol;
2374
}
2375
});
2376
2377
if(this.rightCol >= (this.columns.length - 1)){
2378
this.vDomPadRight = 0;
2379
}else {
2380
this.vDomPadRight -= column.getWidth();
2381
}
2382
}else {
2383
working = false;
2384
}
2385
}else {
2386
working = false;
2387
}
2388
}
2389
2390
if(changes){
2391
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
2392
}
2393
}
2394
2395
addColLeft(){
2396
var changes = false,
2397
working = true;
2398
2399
while(working){
2400
let column = this.columns[this.leftCol - 1];
2401
2402
if(column){
2403
if(column.modules.vdomHoz.rightPos >= this.vDomScrollPosLeft){
2404
changes = true;
2405
2406
this.getVisibleRows().forEach((row) => {
2407
if(row.type !== "group"){
2408
var cell = row.getCell(column);
2409
row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.leftCol]).getElement());
2410
cell.cellRendered();
2411
}
2412
});
2413
2414
this.leftCol--; // don't move this below the <= check below
2415
2416
this.getVisibleRows().forEach((row) => {
2417
if(row.type !== "group"){
2418
row.modules.vdomHoz.leftCol = this.leftCol;
2419
}
2420
});
2421
2422
if(this.leftCol <= 0){ // replicating logic in addColRight
2423
this.vDomPadLeft = 0;
2424
}else {
2425
this.vDomPadLeft -= column.getWidth();
2426
}
2427
2428
let diff = this.fitDataColActualWidthCheck(column);
2429
2430
if(diff){
2431
this.scrollLeft = this.elementVertical.scrollLeft = this.elementVertical.scrollLeft + diff;
2432
this.vDomPadRight -= diff;
2433
}
2434
2435
}else {
2436
working = false;
2437
}
2438
}else {
2439
working = false;
2440
}
2441
}
2442
2443
if(changes){
2444
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
2445
}
2446
}
2447
2448
removeColRight(){
2449
var changes = false,
2450
working = true;
2451
2452
while(working){
2453
let column = this.columns[this.rightCol];
2454
2455
if(column){
2456
if(column.modules.vdomHoz.leftPos > this.vDomScrollPosRight){
2457
changes = true;
2458
2459
this.getVisibleRows().forEach((row) => {
2460
if(row.type !== "group"){
2461
var cell = row.getCell(column);
2462
2463
try {
2464
row.getElement().removeChild(cell.getElement());
2465
} catch (ex) {
2466
console.warn("Could not removeColRight", ex.message);
2467
}
2468
}
2469
});
2470
2471
this.vDomPadRight += column.getWidth();
2472
this.rightCol --;
2473
2474
this.getVisibleRows().forEach((row) => {
2475
if(row.type !== "group"){
2476
row.modules.vdomHoz.rightCol = this.rightCol;
2477
}
2478
});
2479
}else {
2480
working = false;
2481
}
2482
}else {
2483
working = false;
2484
}
2485
}
2486
2487
if(changes){
2488
this.tableElement.style.paddingRight = this.vDomPadRight + "px";
2489
}
2490
}
2491
2492
removeColLeft(){
2493
var changes = false,
2494
working = true;
2495
2496
while(working){
2497
let column = this.columns[this.leftCol];
2498
2499
if(column){
2500
if(column.modules.vdomHoz.rightPos < this.vDomScrollPosLeft){
2501
changes = true;
2502
2503
this.getVisibleRows().forEach((row) => {
2504
if(row.type !== "group"){
2505
var cell = row.getCell(column);
2506
2507
try {
2508
row.getElement().removeChild(cell.getElement());
2509
} catch (ex) {
2510
console.warn("Could not removeColLeft", ex.message);
2511
}
2512
}
2513
});
2514
2515
this.vDomPadLeft += column.getWidth();
2516
this.leftCol ++;
2517
2518
this.getVisibleRows().forEach((row) => {
2519
if(row.type !== "group"){
2520
row.modules.vdomHoz.leftCol = this.leftCol;
2521
}
2522
});
2523
}else {
2524
working = false;
2525
}
2526
}else {
2527
working = false;
2528
}
2529
}
2530
2531
if(changes){
2532
this.tableElement.style.paddingLeft = this.vDomPadLeft + "px";
2533
}
2534
}
2535
2536
fitDataColActualWidthCheck(column){
2537
var newWidth, widthDiff;
2538
2539
if(column.modules.vdomHoz.fitDataCheck){
2540
column.reinitializeWidth();
2541
2542
newWidth = column.getWidth();
2543
widthDiff = newWidth - column.modules.vdomHoz.width;
2544
2545
if(widthDiff){
2546
column.modules.vdomHoz.rightPos += widthDiff;
2547
column.modules.vdomHoz.width = newWidth;
2548
this.colPositionAdjust(this.columns.indexOf(column) + 1, this.columns.length, widthDiff);
2549
}
2550
2551
column.modules.vdomHoz.fitDataCheck = false;
2552
}
2553
2554
return widthDiff;
2555
}
2556
2557
initializeRow(row){
2558
if(row.type !== "group"){
2559
row.modules.vdomHoz = {
2560
leftCol:this.leftCol,
2561
rightCol:this.rightCol,
2562
};
2563
2564
if(this.table.modules.frozenColumns){
2565
this.table.modules.frozenColumns.leftColumns.forEach((column) => {
2566
this.appendCell(row, column);
2567
});
2568
}
2569
2570
for(let i = this.leftCol; i <= this.rightCol; i++){
2571
this.appendCell(row, this.columns[i]);
2572
}
2573
2574
if(this.table.modules.frozenColumns){
2575
this.table.modules.frozenColumns.rightColumns.forEach((column) => {
2576
this.appendCell(row, column);
2577
});
2578
}
2579
}
2580
}
2581
2582
appendCell(row, column){
2583
if(column && column.visible){
2584
let cell = row.getCell(column);
2585
2586
row.getElement().appendChild(cell.getElement());
2587
cell.cellRendered();
2588
}
2589
}
2590
2591
reinitializeRow(row, force){
2592
if(row.type !== "group"){
2593
if(force || !row.modules.vdomHoz || row.modules.vdomHoz.leftCol !== this.leftCol || row.modules.vdomHoz.rightCol !== this.rightCol){
2594
2595
var rowEl = row.getElement();
2596
while(rowEl.firstChild) rowEl.removeChild(rowEl.firstChild);
2597
2598
this.initializeRow(row);
2599
}
2600
}
2601
}
2602
}
2603
2604
class ColumnManager extends CoreFeature {
2605
2606
constructor (table){
2607
super(table);
2608
2609
this.blockHozScrollEvent = false;
2610
this.headersElement = null;
2611
this.contentsElement = null;
2612
this.element = null ; //containing element
2613
this.columns = []; // column definition object
2614
this.columnsByIndex = []; //columns by index
2615
this.columnsByField = {}; //columns by field
2616
this.scrollLeft = 0;
2617
this.optionsList = new OptionsList(this.table, "column definition", defaultColumnOptions);
2618
2619
this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing
2620
this.redrawBlockUpdate = null; //store latest redraw update only status
2621
2622
this.renderer = null;
2623
}
2624
2625
////////////// Setup Functions /////////////////
2626
2627
initialize(){
2628
this.initializeRenderer();
2629
2630
this.headersElement = this.createHeadersElement();
2631
this.contentsElement = this.createHeaderContentsElement();
2632
this.element = this.createHeaderElement();
2633
2634
this.contentsElement.insertBefore(this.headersElement, this.contentsElement.firstChild);
2635
this.element.insertBefore(this.contentsElement, this.element.firstChild);
2636
2637
this.initializeScrollWheelWatcher();
2638
2639
this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this));
2640
this.subscribe("scrollbar-vertical", this.padVerticalScrollbar.bind(this));
2641
}
2642
2643
padVerticalScrollbar(width){
2644
if(this.table.rtl){
2645
this.headersElement.style.marginLeft = width + "px";
2646
}else {
2647
this.headersElement.style.marginRight = width + "px";
2648
}
2649
}
2650
2651
initializeRenderer(){
2652
var renderClass;
2653
2654
var renderers = {
2655
"virtual": VirtualDomHorizontal,
2656
"basic": BasicHorizontal,
2657
};
2658
2659
if(typeof this.table.options.renderHorizontal === "string"){
2660
renderClass = renderers[this.table.options.renderHorizontal];
2661
}else {
2662
renderClass = this.table.options.renderHorizontal;
2663
}
2664
2665
if(renderClass){
2666
this.renderer = new renderClass(this.table, this.element, this.tableElement);
2667
this.renderer.initialize();
2668
}else {
2669
console.error("Unable to find matching renderer:", this.table.options.renderHorizontal);
2670
}
2671
}
2672
2673
2674
createHeadersElement (){
2675
var el = document.createElement("div");
2676
2677
el.classList.add("tabulator-headers");
2678
el.setAttribute("role", "row");
2679
2680
return el;
2681
}
2682
2683
createHeaderContentsElement (){
2684
var el = document.createElement("div");
2685
2686
el.classList.add("tabulator-header-contents");
2687
el.setAttribute("role", "rowgroup");
2688
2689
return el;
2690
}
2691
2692
createHeaderElement (){
2693
var el = document.createElement("div");
2694
2695
el.classList.add("tabulator-header");
2696
el.setAttribute("role", "rowgroup");
2697
2698
if(!this.table.options.headerVisible){
2699
el.classList.add("tabulator-header-hidden");
2700
}
2701
2702
return el;
2703
}
2704
2705
//return containing element
2706
getElement(){
2707
return this.element;
2708
}
2709
2710
//return containing contents element
2711
getContentsElement(){
2712
return this.contentsElement;
2713
}
2714
2715
2716
//return header containing element
2717
getHeadersElement(){
2718
return this.headersElement;
2719
}
2720
2721
//scroll horizontally to match table body
2722
scrollHorizontal(left){
2723
this.contentsElement.scrollLeft = left;
2724
2725
this.scrollLeft = left;
2726
2727
this.renderer.scrollColumns(left);
2728
}
2729
2730
initializeScrollWheelWatcher(){
2731
this.contentsElement.addEventListener("wheel", (e) => {
2732
var left;
2733
2734
if(e.deltaX){
2735
left = this.contentsElement.scrollLeft + e.deltaX;
2736
2737
this.table.rowManager.scrollHorizontal(left);
2738
this.table.columnManager.scrollHorizontal(left);
2739
}
2740
});
2741
}
2742
2743
///////////// Column Setup Functions /////////////
2744
generateColumnsFromRowData(data){
2745
var cols = [],
2746
definitions = this.table.options.autoColumnsDefinitions,
2747
row, sorter;
2748
2749
if(data && data.length){
2750
2751
row = data[0];
2752
2753
for(var key in row){
2754
let col = {
2755
field:key,
2756
title:key,
2757
};
2758
2759
let value = row[key];
2760
2761
switch(typeof value){
2762
case "undefined":
2763
sorter = "string";
2764
break;
2765
2766
case "boolean":
2767
sorter = "boolean";
2768
break;
2769
2770
case "object":
2771
if(Array.isArray(value)){
2772
sorter = "array";
2773
}else {
2774
sorter = "string";
2775
}
2776
break;
2777
2778
default:
2779
if(!isNaN(value) && value !== ""){
2780
sorter = "number";
2781
}else {
2782
if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
2783
sorter = "alphanum";
2784
}else {
2785
sorter = "string";
2786
}
2787
}
2788
break;
2789
}
2790
2791
col.sorter = sorter;
2792
2793
cols.push(col);
2794
}
2795
2796
if(definitions){
2797
2798
switch(typeof definitions){
2799
case "function":
2800
this.table.options.columns = definitions.call(this.table, cols);
2801
break;
2802
2803
case "object":
2804
if(Array.isArray(definitions)){
2805
cols.forEach((col) => {
2806
var match = definitions.find((def) => {
2807
return def.field === col.field;
2808
});
2809
2810
if(match){
2811
Object.assign(col, match);
2812
}
2813
});
2814
2815
}else {
2816
cols.forEach((col) => {
2817
if(definitions[col.field]){
2818
Object.assign(col, definitions[col.field]);
2819
}
2820
});
2821
}
2822
2823
this.table.options.columns = cols;
2824
break;
2825
}
2826
}else {
2827
this.table.options.columns = cols;
2828
}
2829
2830
this.setColumns(this.table.options.columns);
2831
}
2832
}
2833
2834
setColumns(cols, row){
2835
while(this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild);
2836
2837
this.columns = [];
2838
this.columnsByIndex = [];
2839
this.columnsByField = {};
2840
2841
this.dispatch("columns-loading");
2842
2843
cols.forEach((def, i) => {
2844
this._addColumn(def);
2845
});
2846
2847
this._reIndexColumns();
2848
2849
this.dispatch("columns-loaded");
2850
2851
this.rerenderColumns(false, true);
2852
2853
this.redraw(true);
2854
}
2855
2856
_addColumn(definition, before, nextToColumn){
2857
var column = new Column(definition, this),
2858
colEl = column.getElement(),
2859
index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn;
2860
2861
if(nextToColumn && index > -1){
2862
var topColumn = nextToColumn.getTopColumn();
2863
var parentIndex = this.columns.indexOf(topColumn);
2864
var nextEl = topColumn.getElement();
2865
2866
if(before){
2867
this.columns.splice(parentIndex, 0, column);
2868
nextEl.parentNode.insertBefore(colEl, nextEl);
2869
}else {
2870
this.columns.splice(parentIndex + 1, 0, column);
2871
nextEl.parentNode.insertBefore(colEl, nextEl.nextSibling);
2872
}
2873
}else {
2874
if(before){
2875
this.columns.unshift(column);
2876
this.headersElement.insertBefore(column.getElement(), this.headersElement.firstChild);
2877
}else {
2878
this.columns.push(column);
2879
this.headersElement.appendChild(column.getElement());
2880
}
2881
}
2882
2883
column.columnRendered();
2884
2885
return column;
2886
}
2887
2888
registerColumnField(col){
2889
if(col.definition.field){
2890
this.columnsByField[col.definition.field] = col;
2891
}
2892
}
2893
2894
registerColumnPosition(col){
2895
this.columnsByIndex.push(col);
2896
}
2897
2898
_reIndexColumns(){
2899
this.columnsByIndex = [];
2900
2901
this.columns.forEach(function(column){
2902
column.reRegisterPosition();
2903
});
2904
}
2905
2906
//ensure column headers take up the correct amount of space in column groups
2907
verticalAlignHeaders(){
2908
var minHeight = 0;
2909
2910
if(!this.redrawBlock){
2911
2912
this.headersElement.style.height="";
2913
2914
this.columns.forEach((column) => {
2915
column.clearVerticalAlign();
2916
});
2917
2918
this.columns.forEach((column) => {
2919
var height = column.getHeight();
2920
2921
if(height > minHeight){
2922
minHeight = height;
2923
}
2924
});
2925
2926
this.headersElement.style.height = minHeight + "px";
2927
2928
this.columns.forEach((column) => {
2929
column.verticalAlign(this.table.options.columnHeaderVertAlign, minHeight);
2930
});
2931
2932
this.table.rowManager.adjustTableSize();
2933
}
2934
}
2935
2936
//////////////// Column Details /////////////////
2937
findColumn(subject){
2938
var columns;
2939
2940
if(typeof subject == "object"){
2941
2942
if(subject instanceof Column){
2943
//subject is column element
2944
return subject;
2945
}else if(subject instanceof ColumnComponent){
2946
//subject is public column component
2947
return subject._getSelf() || false;
2948
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
2949
2950
columns = [];
2951
2952
this.columns.forEach((column) => {
2953
columns.push(column);
2954
columns = columns.concat(column.getColumns(true));
2955
});
2956
2957
//subject is a HTML element of the column header
2958
let match = columns.find((column) => {
2959
return column.element === subject;
2960
});
2961
2962
return match || false;
2963
}
2964
2965
}else {
2966
//subject should be treated as the field name of the column
2967
return this.columnsByField[subject] || false;
2968
}
2969
2970
//catch all for any other type of input
2971
return false;
2972
}
2973
2974
getColumnByField(field){
2975
return this.columnsByField[field];
2976
}
2977
2978
getColumnsByFieldRoot(root){
2979
var matches = [];
2980
2981
Object.keys(this.columnsByField).forEach((field) => {
2982
var fieldRoot = field.split(".")[0];
2983
if(fieldRoot === root){
2984
matches.push(this.columnsByField[field]);
2985
}
2986
});
2987
2988
return matches;
2989
}
2990
2991
getColumnByIndex(index){
2992
return this.columnsByIndex[index];
2993
}
2994
2995
getFirstVisibleColumn(){
2996
var index = this.columnsByIndex.findIndex((col) => {
2997
return col.visible;
2998
});
2999
3000
return index > -1 ? this.columnsByIndex[index] : false;
3001
}
3002
3003
getColumns(){
3004
return this.columns;
3005
}
3006
3007
findColumnIndex(column){
3008
return this.columnsByIndex.findIndex((col) => {
3009
return column === col;
3010
});
3011
}
3012
3013
//return all columns that are not groups
3014
getRealColumns(){
3015
return this.columnsByIndex;
3016
}
3017
3018
//traverse across columns and call action
3019
traverse(callback){
3020
this.columnsByIndex.forEach((column,i) =>{
3021
callback(column, i);
3022
});
3023
}
3024
3025
//get definitions of actual columns
3026
getDefinitions(active){
3027
var output = [];
3028
3029
this.columnsByIndex.forEach((column) => {
3030
if(!active || (active && column.visible)){
3031
output.push(column.getDefinition());
3032
}
3033
});
3034
3035
return output;
3036
}
3037
3038
//get full nested definition tree
3039
getDefinitionTree(){
3040
var output = [];
3041
3042
this.columns.forEach((column) => {
3043
output.push(column.getDefinition(true));
3044
});
3045
3046
return output;
3047
}
3048
3049
getComponents(structured){
3050
var output = [],
3051
columns = structured ? this.columns : this.columnsByIndex;
3052
3053
columns.forEach((column) => {
3054
output.push(column.getComponent());
3055
});
3056
3057
return output;
3058
}
3059
3060
getWidth(){
3061
var width = 0;
3062
3063
this.columnsByIndex.forEach((column) => {
3064
if(column.visible){
3065
width += column.getWidth();
3066
}
3067
});
3068
3069
return width;
3070
}
3071
3072
moveColumn(from, to, after){
3073
to.element.parentNode.insertBefore(from.element, to.element);
3074
3075
if(after){
3076
to.element.parentNode.insertBefore(to.element, from.element);
3077
}
3078
3079
this.moveColumnActual(from, to, after);
3080
3081
this.verticalAlignHeaders();
3082
3083
this.table.rowManager.reinitialize();
3084
}
3085
3086
moveColumnActual(from, to, after){
3087
if(from.parent.isGroup){
3088
this._moveColumnInArray(from.parent.columns, from, to, after);
3089
}else {
3090
this._moveColumnInArray(this.columns, from, to, after);
3091
}
3092
3093
this._moveColumnInArray(this.columnsByIndex, from, to, after, true);
3094
3095
this.rerenderColumns(true);
3096
3097
this.dispatch("column-moved", from, to, after);
3098
3099
if(this.subscribedExternal("columnMoved")){
3100
this.dispatchExternal("columnMoved", from.getComponent(), this.table.columnManager.getComponents());
3101
}
3102
}
3103
3104
_moveColumnInArray(columns, from, to, after, updateRows){
3105
var fromIndex = columns.indexOf(from),
3106
toIndex, rows = [];
3107
3108
if (fromIndex > -1) {
3109
3110
columns.splice(fromIndex, 1);
3111
3112
toIndex = columns.indexOf(to);
3113
3114
if (toIndex > -1) {
3115
3116
if(after){
3117
toIndex = toIndex+1;
3118
}
3119
3120
}else {
3121
toIndex = fromIndex;
3122
}
3123
3124
columns.splice(toIndex, 0, from);
3125
3126
if(updateRows){
3127
3128
rows = this.chain("column-moving-rows", [from, to, after], null, []) || [];
3129
3130
rows = rows.concat(this.table.rowManager.rows);
3131
3132
rows.forEach(function(row){
3133
if(row.cells.length){
3134
var cell = row.cells.splice(fromIndex, 1)[0];
3135
row.cells.splice(toIndex, 0, cell);
3136
}
3137
});
3138
3139
}
3140
}
3141
}
3142
3143
scrollToColumn(column, position, ifVisible){
3144
var left = 0,
3145
offset = column.getLeftOffset(),
3146
adjust = 0,
3147
colEl = column.getElement();
3148
3149
3150
return new Promise((resolve, reject) => {
3151
3152
if(typeof position === "undefined"){
3153
position = this.table.options.scrollToColumnPosition;
3154
}
3155
3156
if(typeof ifVisible === "undefined"){
3157
ifVisible = this.table.options.scrollToColumnIfVisible;
3158
}
3159
3160
if(column.visible){
3161
3162
//align to correct position
3163
switch(position){
3164
case "middle":
3165
case "center":
3166
adjust = -this.element.clientWidth / 2;
3167
break;
3168
3169
case "right":
3170
adjust = colEl.clientWidth - this.headersElement.clientWidth;
3171
break;
3172
}
3173
3174
//check column visibility
3175
if(!ifVisible){
3176
if(offset > 0 && offset + colEl.offsetWidth < this.element.clientWidth){
3177
return false;
3178
}
3179
}
3180
3181
//calculate scroll position
3182
left = offset + adjust;
3183
3184
left = Math.max(Math.min(left, this.table.rowManager.element.scrollWidth - this.table.rowManager.element.clientWidth),0);
3185
3186
this.table.rowManager.scrollHorizontal(left);
3187
this.scrollHorizontal(left);
3188
3189
resolve();
3190
}else {
3191
console.warn("Scroll Error - Column not visible");
3192
reject("Scroll Error - Column not visible");
3193
}
3194
3195
});
3196
}
3197
3198
//////////////// Cell Management /////////////////
3199
generateCells(row){
3200
var cells = [];
3201
3202
this.columnsByIndex.forEach((column) => {
3203
cells.push(column.generateCell(row));
3204
});
3205
3206
return cells;
3207
}
3208
3209
//////////////// Column Management /////////////////
3210
getFlexBaseWidth(){
3211
var totalWidth = this.table.element.clientWidth, //table element width
3212
fixedWidth = 0;
3213
3214
//adjust for vertical scrollbar if present
3215
if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){
3216
totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth;
3217
}
3218
3219
this.columnsByIndex.forEach(function(column){
3220
var width, minWidth, colWidth;
3221
3222
if(column.visible){
3223
3224
width = column.definition.width || 0;
3225
3226
minWidth = parseInt(column.minWidth);
3227
3228
if(typeof(width) == "string"){
3229
if(width.indexOf("%") > -1){
3230
colWidth = (totalWidth / 100) * parseInt(width) ;
3231
}else {
3232
colWidth = parseInt(width);
3233
}
3234
}else {
3235
colWidth = width;
3236
}
3237
3238
fixedWidth += colWidth > minWidth ? colWidth : minWidth;
3239
3240
}
3241
});
3242
3243
return fixedWidth;
3244
}
3245
3246
addColumn(definition, before, nextToColumn){
3247
return new Promise((resolve, reject) => {
3248
var column = this._addColumn(definition, before, nextToColumn);
3249
3250
this._reIndexColumns();
3251
3252
this.dispatch("column-add", definition, before, nextToColumn);
3253
3254
if(this.layoutMode() != "fitColumns"){
3255
column.reinitializeWidth();
3256
}
3257
3258
this.redraw(true);
3259
3260
this.table.rowManager.reinitialize();
3261
3262
this.rerenderColumns();
3263
3264
resolve(column);
3265
});
3266
}
3267
3268
//remove column from system
3269
deregisterColumn(column){
3270
var field = column.getField(),
3271
index;
3272
3273
//remove from field list
3274
if(field){
3275
delete this.columnsByField[field];
3276
}
3277
3278
//remove from index list
3279
index = this.columnsByIndex.indexOf(column);
3280
3281
if(index > -1){
3282
this.columnsByIndex.splice(index, 1);
3283
}
3284
3285
//remove from column list
3286
index = this.columns.indexOf(column);
3287
3288
if(index > -1){
3289
this.columns.splice(index, 1);
3290
}
3291
3292
this.verticalAlignHeaders();
3293
3294
this.redraw();
3295
}
3296
3297
rerenderColumns(update, silent){
3298
if(!this.redrawBlock){
3299
this.renderer.rerenderColumns(update, silent);
3300
}else {
3301
if(update === false || (update === true && this.redrawBlockUpdate === null)){
3302
this.redrawBlockUpdate = update;
3303
}
3304
}
3305
}
3306
3307
blockRedraw(){
3308
this.redrawBlock = true;
3309
this.redrawBlockUpdate = null;
3310
}
3311
3312
restoreRedraw(){
3313
this.redrawBlock = false;
3314
this.verticalAlignHeaders();
3315
this.renderer.rerenderColumns(this.redrawBlockUpdate);
3316
3317
}
3318
3319
//redraw columns
3320
redraw(force){
3321
if(Helpers.elVisible(this.element)){
3322
this.verticalAlignHeaders();
3323
}
3324
3325
if(force){
3326
this.table.rowManager.resetScroll();
3327
this.table.rowManager.reinitialize();
3328
}
3329
3330
if(!this.confirm("table-redrawing", force)){
3331
this.layoutRefresh(force);
3332
}
3333
3334
this.dispatch("table-redraw", force);
3335
3336
this.table.footerManager.redraw();
3337
}
3338
}
3339
3340
//public row object
3341
class RowComponent {
3342
3343
constructor (row){
3344
this._row = row;
3345
3346
return new Proxy(this, {
3347
get: function(target, name, receiver) {
3348
if (typeof target[name] !== "undefined") {
3349
return target[name];
3350
}else {
3351
return target._row.table.componentFunctionBinder.handle("row", target._row, name);
3352
}
3353
}
3354
});
3355
}
3356
3357
getData(transform){
3358
return this._row.getData(transform);
3359
}
3360
3361
getElement(){
3362
return this._row.getElement();
3363
}
3364
3365
getCells(){
3366
var cells = [];
3367
3368
this._row.getCells().forEach(function(cell){
3369
cells.push(cell.getComponent());
3370
});
3371
3372
return cells;
3373
}
3374
3375
getCell(column){
3376
var cell = this._row.getCell(column);
3377
return cell ? cell.getComponent() : false;
3378
}
3379
3380
getIndex(){
3381
return this._row.getData("data")[this._row.table.options.index];
3382
}
3383
3384
getPosition(){
3385
return this._row.getPosition();
3386
}
3387
3388
watchPosition(callback){
3389
return this._row.watchPosition(callback);
3390
}
3391
3392
delete(){
3393
return this._row.delete();
3394
}
3395
3396
scrollTo(position, ifVisible){
3397
return this._row.table.rowManager.scrollToRow(this._row, position, ifVisible);
3398
}
3399
3400
move(to, after){
3401
this._row.moveToRow(to, after);
3402
}
3403
3404
update(data){
3405
return this._row.updateData(data);
3406
}
3407
3408
normalizeHeight(){
3409
this._row.normalizeHeight(true);
3410
}
3411
3412
_getSelf(){
3413
return this._row;
3414
}
3415
3416
reformat(){
3417
return this._row.reinitialize();
3418
}
3419
3420
getTable(){
3421
return this._row.table;
3422
}
3423
3424
getNextRow(){
3425
var row = this._row.nextRow();
3426
return row ? row.getComponent() : row;
3427
}
3428
3429
getPrevRow(){
3430
var row = this._row.prevRow();
3431
return row ? row.getComponent() : row;
3432
}
3433
}
3434
3435
class Row extends CoreFeature{
3436
constructor (data, parent, type = "row"){
3437
super(parent.table);
3438
3439
this.parent = parent;
3440
this.data = {};
3441
this.type = type; //type of element
3442
this.element = false;
3443
this.modules = {}; //hold module variables;
3444
this.cells = [];
3445
this.height = 0; //hold element height
3446
this.heightStyled = ""; //hold element height pre-styled to improve render efficiency
3447
this.manualHeight = false; //user has manually set row height
3448
this.outerHeight = 0; //hold elements outer height
3449
this.initialized = false; //element has been rendered
3450
this.heightInitialized = false; //element has resized cells to fit
3451
this.position = 0; //store position of element in row list
3452
this.positionWatchers = [];
3453
3454
this.component = null;
3455
3456
this.created = false;
3457
3458
this.setData(data);
3459
}
3460
3461
create(){
3462
if(!this.created){
3463
this.created = true;
3464
this.generateElement();
3465
}
3466
}
3467
3468
createElement (){
3469
var el = document.createElement("div");
3470
3471
el.classList.add("tabulator-row");
3472
el.setAttribute("role", "row");
3473
3474
this.element = el;
3475
}
3476
3477
getElement(){
3478
this.create();
3479
return this.element;
3480
}
3481
3482
detachElement(){
3483
if (this.element && this.element.parentNode){
3484
this.element.parentNode.removeChild(this.element);
3485
}
3486
}
3487
3488
generateElement(){
3489
this.createElement();
3490
this.dispatch("row-init", this);
3491
}
3492
3493
generateCells(){
3494
this.cells = this.table.columnManager.generateCells(this);
3495
}
3496
3497
//functions to setup on first render
3498
initialize(force, inFragment){
3499
this.create();
3500
3501
if(!this.initialized || force){
3502
3503
this.deleteCells();
3504
3505
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
3506
3507
this.dispatch("row-layout-before", this);
3508
3509
this.generateCells();
3510
3511
this.initialized = true;
3512
3513
this.table.columnManager.renderer.renderRowCells(this, inFragment);
3514
3515
if(force){
3516
this.normalizeHeight();
3517
}
3518
3519
this.dispatch("row-layout", this);
3520
3521
if(this.table.options.rowFormatter){
3522
this.table.options.rowFormatter(this.getComponent());
3523
}
3524
3525
this.dispatch("row-layout-after", this);
3526
}else {
3527
this.table.columnManager.renderer.rerenderRowCells(this, inFragment);
3528
}
3529
}
3530
3531
rendered(){
3532
this.cells.forEach((cell) => {
3533
cell.cellRendered();
3534
});
3535
}
3536
3537
reinitializeHeight(){
3538
this.heightInitialized = false;
3539
3540
if(this.element && this.element.offsetParent !== null){
3541
this.normalizeHeight(true);
3542
}
3543
}
3544
3545
deinitialize(){
3546
this.initialized = false;
3547
}
3548
3549
deinitializeHeight(){
3550
this.heightInitialized = false;
3551
}
3552
3553
reinitialize(children){
3554
this.initialized = false;
3555
this.heightInitialized = false;
3556
3557
if(!this.manualHeight){
3558
this.height = 0;
3559
this.heightStyled = "";
3560
}
3561
3562
if(this.element && this.element.offsetParent !== null){
3563
this.initialize(true);
3564
}
3565
3566
this.dispatch("row-relayout", this);
3567
}
3568
3569
//get heights when doing bulk row style calcs in virtual DOM
3570
calcHeight(force){
3571
var maxHeight = 0,
3572
minHeight;
3573
3574
if(this.table.options.rowHeight){
3575
this.height = this.table.options.rowHeight;
3576
}else {
3577
minHeight = this.table.options.resizableRows ? this.element.clientHeight : 0;
3578
3579
this.cells.forEach(function(cell){
3580
var height = cell.getHeight();
3581
if(height > maxHeight){
3582
maxHeight = height;
3583
}
3584
});
3585
3586
if(force){
3587
this.height = Math.max(maxHeight, minHeight);
3588
}else {
3589
this.height = this.manualHeight ? this.height : Math.max(maxHeight, minHeight);
3590
}
3591
}
3592
3593
this.heightStyled = this.height ? this.height + "px" : "";
3594
this.outerHeight = this.element.offsetHeight;
3595
}
3596
3597
//set of cells
3598
setCellHeight(){
3599
this.cells.forEach(function(cell){
3600
cell.setHeight();
3601
});
3602
3603
this.heightInitialized = true;
3604
}
3605
3606
clearCellHeight(){
3607
this.cells.forEach(function(cell){
3608
cell.clearHeight();
3609
});
3610
}
3611
3612
//normalize the height of elements in the row
3613
normalizeHeight(force){
3614
if(force && !this.table.options.rowHeight){
3615
this.clearCellHeight();
3616
}
3617
3618
this.calcHeight(force);
3619
3620
this.setCellHeight();
3621
}
3622
3623
//set height of rows
3624
setHeight(height, force){
3625
if(this.height != height || force){
3626
3627
this.manualHeight = true;
3628
3629
this.height = height;
3630
this.heightStyled = height ? height + "px" : "";
3631
3632
this.setCellHeight();
3633
3634
// this.outerHeight = this.element.outerHeight();
3635
this.outerHeight = this.element.offsetHeight;
3636
}
3637
}
3638
3639
//return rows outer height
3640
getHeight(){
3641
return this.outerHeight;
3642
}
3643
3644
//return rows outer Width
3645
getWidth(){
3646
return this.element.offsetWidth;
3647
}
3648
3649
//////////////// Cell Management /////////////////
3650
deleteCell(cell){
3651
var index = this.cells.indexOf(cell);
3652
3653
if(index > -1){
3654
this.cells.splice(index, 1);
3655
}
3656
}
3657
3658
//////////////// Data Management /////////////////
3659
setData(data){
3660
this.data = this.chain("row-data-init-before", [this, data], undefined, data);
3661
3662
this.dispatch("row-data-init-after", this);
3663
}
3664
3665
//update the rows data
3666
updateData(updatedData){
3667
var visible = this.element && Helpers.elVisible(this.element),
3668
tempData = {},
3669
newRowData;
3670
3671
return new Promise((resolve, reject) => {
3672
3673
if(typeof updatedData === "string"){
3674
updatedData = JSON.parse(updatedData);
3675
}
3676
3677
this.dispatch("row-data-save-before", this);
3678
3679
if(this.subscribed("row-data-changing")){
3680
tempData = Object.assign(tempData, this.data);
3681
tempData = Object.assign(tempData, updatedData);
3682
}
3683
3684
newRowData = this.chain("row-data-changing", [this, tempData, updatedData], null, updatedData);
3685
3686
//set data
3687
for (let attrname in newRowData) {
3688
this.data[attrname] = newRowData[attrname];
3689
}
3690
3691
this.dispatch("row-data-save-after", this);
3692
3693
//update affected cells only
3694
for (let attrname in updatedData) {
3695
3696
let columns = this.table.columnManager.getColumnsByFieldRoot(attrname);
3697
3698
columns.forEach((column) => {
3699
let cell = this.getCell(column.getField());
3700
3701
if(cell){
3702
let value = column.getFieldValue(newRowData);
3703
if(cell.getValue() !== value){
3704
cell.setValueProcessData(value);
3705
3706
if(visible){
3707
cell.cellRendered();
3708
}
3709
}
3710
}
3711
});
3712
}
3713
3714
//Partial reinitialization if visible
3715
if(visible){
3716
this.normalizeHeight(true);
3717
3718
if(this.table.options.rowFormatter){
3719
this.table.options.rowFormatter(this.getComponent());
3720
}
3721
}else {
3722
this.initialized = false;
3723
this.height = 0;
3724
this.heightStyled = "";
3725
}
3726
3727
this.dispatch("row-data-changed", this, visible, updatedData);
3728
3729
//this.reinitialize();
3730
3731
this.dispatchExternal("rowUpdated", this.getComponent());
3732
3733
if(this.subscribedExternal("dataChanged")){
3734
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
3735
}
3736
3737
resolve();
3738
});
3739
}
3740
3741
getData(transform){
3742
if(transform){
3743
return this.chain("row-data-retrieve", [this, transform], null, this.data);
3744
}
3745
3746
return this.data;
3747
}
3748
3749
getCell(column){
3750
var match = false;
3751
3752
column = this.table.columnManager.findColumn(column);
3753
3754
if(!this.initialized && this.cells.length === 0){
3755
this.generateCells();
3756
}
3757
3758
match = this.cells.find(function(cell){
3759
return cell.column === column;
3760
});
3761
3762
return match;
3763
}
3764
3765
getCellIndex(findCell){
3766
return this.cells.findIndex(function(cell){
3767
return cell === findCell;
3768
});
3769
}
3770
3771
findCell(subject){
3772
return this.cells.find((cell) => {
3773
return cell.element === subject;
3774
});
3775
}
3776
3777
getCells(){
3778
if(!this.initialized && this.cells.length === 0){
3779
this.generateCells();
3780
}
3781
3782
return this.cells;
3783
}
3784
3785
nextRow(){
3786
var row = this.table.rowManager.nextDisplayRow(this, true);
3787
return row || false;
3788
}
3789
3790
prevRow(){
3791
var row = this.table.rowManager.prevDisplayRow(this, true);
3792
return row || false;
3793
}
3794
3795
moveToRow(to, before){
3796
var toRow = this.table.rowManager.findRow(to);
3797
3798
if(toRow){
3799
this.table.rowManager.moveRowActual(this, toRow, !before);
3800
this.table.rowManager.refreshActiveData("display", false, true);
3801
}else {
3802
console.warn("Move Error - No matching row found:", to);
3803
}
3804
}
3805
3806
///////////////////// Actions /////////////////////
3807
delete(){
3808
this.dispatch("row-delete", this);
3809
3810
this.deleteActual();
3811
3812
return Promise.resolve();
3813
}
3814
3815
deleteActual(blockRedraw){
3816
this.detachModules();
3817
3818
this.table.rowManager.deleteRow(this, blockRedraw);
3819
3820
this.deleteCells();
3821
3822
this.initialized = false;
3823
this.heightInitialized = false;
3824
this.element = false;
3825
3826
this.dispatch("row-deleted", this);
3827
}
3828
3829
detachModules(){
3830
this.dispatch("row-deleting", this);
3831
}
3832
3833
deleteCells(){
3834
var cellCount = this.cells.length;
3835
3836
for(let i = 0; i < cellCount; i++){
3837
this.cells[0].delete();
3838
}
3839
}
3840
3841
wipe(){
3842
this.detachModules();
3843
this.deleteCells();
3844
3845
if(this.element){
3846
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
3847
3848
if(this.element.parentNode){
3849
this.element.parentNode.removeChild(this.element);
3850
}
3851
}
3852
3853
this.element = false;
3854
this.modules = {};
3855
}
3856
3857
isDisplayed(){
3858
return this.table.rowManager.getDisplayRows().includes(this);
3859
}
3860
3861
getPosition(){
3862
return this.isDisplayed() ? this.position : false;
3863
}
3864
3865
setPosition(position){
3866
if(position != this.position){
3867
this.position = position;
3868
3869
this.positionWatchers.forEach((callback) => {
3870
callback(this.position);
3871
});
3872
}
3873
}
3874
3875
watchPosition(callback){
3876
this.positionWatchers.push(callback);
3877
3878
callback(this.position);
3879
}
3880
3881
getGroup(){
3882
return this.modules.group || false;
3883
}
3884
3885
//////////////// Object Generation /////////////////
3886
getComponent(){
3887
if(!this.component){
3888
this.component = new RowComponent(this);
3889
}
3890
3891
return this.component;
3892
}
3893
}
3894
3895
class BasicVertical extends Renderer{
3896
constructor(table){
3897
super(table);
3898
3899
this.verticalFillMode = "fill";
3900
3901
this.scrollTop = 0;
3902
this.scrollLeft = 0;
3903
3904
this.scrollTop = 0;
3905
this.scrollLeft = 0;
3906
}
3907
3908
clearRows(){
3909
var element = this.tableElement;
3910
3911
// element.children.detach();
3912
while(element.firstChild) element.removeChild(element.firstChild);
3913
3914
element.scrollTop = 0;
3915
element.scrollLeft = 0;
3916
3917
element.style.minWidth = "";
3918
element.style.minHeight = "";
3919
element.style.display = "";
3920
element.style.visibility = "";
3921
}
3922
3923
renderRows() {
3924
var element = this.tableElement,
3925
onlyGroupHeaders = true,
3926
tableFrag = document.createDocumentFragment(),
3927
rows = this.rows();
3928
3929
rows.forEach((row, index) => {
3930
this.styleRow(row, index);
3931
row.initialize(false, true);
3932
3933
if (row.type !== "group") {
3934
onlyGroupHeaders = false;
3935
}
3936
3937
tableFrag.appendChild(row.getElement());
3938
});
3939
3940
element.appendChild(tableFrag);
3941
3942
rows.forEach((row) => {
3943
row.rendered();
3944
3945
if(!row.heightInitialized) {
3946
row.calcHeight(true);
3947
}
3948
});
3949
3950
rows.forEach((row) => {
3951
if(!row.heightInitialized) {
3952
row.setCellHeight();
3953
}
3954
});
3955
3956
3957
3958
if(onlyGroupHeaders){
3959
element.style.minWidth = this.table.columnManager.getWidth() + "px";
3960
}else {
3961
element.style.minWidth = "";
3962
}
3963
}
3964
3965
3966
rerenderRows(callback){
3967
this.clearRows();
3968
3969
if(callback){
3970
callback();
3971
}
3972
3973
this.renderRows();
3974
}
3975
3976
scrollToRowNearestTop(row){
3977
var rowTop = Helpers.elOffset(row.getElement()).top;
3978
3979
return !(Math.abs(this.elementVertical.scrollTop - rowTop) > Math.abs(this.elementVertical.scrollTop + this.elementVertical.clientHeight - rowTop));
3980
}
3981
3982
scrollToRow(row){
3983
var rowEl = row.getElement();
3984
3985
this.elementVertical.scrollTop = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top + this.elementVertical.scrollTop;
3986
}
3987
3988
visibleRows(includingBuffer){
3989
return this.rows();
3990
}
3991
3992
}
3993
3994
class VirtualDomVertical extends Renderer{
3995
constructor(table){
3996
super(table);
3997
3998
this.verticalFillMode = "fill";
3999
4000
this.scrollTop = 0;
4001
this.scrollLeft = 0;
4002
4003
this.vDomRowHeight = 20; //approximation of row heights for padding
4004
4005
this.vDomTop = 0; //hold position for first rendered row in the virtual DOM
4006
this.vDomBottom = 0; //hold position for last rendered row in the virtual DOM
4007
4008
this.vDomScrollPosTop = 0; //last scroll position of the vDom top;
4009
this.vDomScrollPosBottom = 0; //last scroll position of the vDom bottom;
4010
4011
this.vDomTopPad = 0; //hold value of padding for top of virtual DOM
4012
this.vDomBottomPad = 0; //hold value of padding for bottom of virtual DOM
4013
4014
this.vDomMaxRenderChain = 90; //the maximum number of dom elements that can be rendered in 1 go
4015
4016
this.vDomWindowBuffer = 0; //window row buffer before removing elements, to smooth scrolling
4017
4018
this.vDomWindowMinTotalRows = 20; //minimum number of rows to be generated in virtual dom (prevent buffering issues on tables with tall rows)
4019
this.vDomWindowMinMarginRows = 5; //minimum number of rows to be generated in virtual dom margin
4020
4021
this.vDomTopNewRows = []; //rows to normalize after appending to optimize render speed
4022
this.vDomBottomNewRows = []; //rows to normalize after appending to optimize render speed
4023
}
4024
4025
//////////////////////////////////////
4026
///////// Public Functions ///////////
4027
//////////////////////////////////////
4028
4029
clearRows(){
4030
var element = this.tableElement;
4031
4032
// element.children.detach();
4033
while(element.firstChild) element.removeChild(element.firstChild);
4034
4035
element.style.paddingTop = "";
4036
element.style.paddingBottom = "";
4037
element.style.minHeight = "";
4038
element.style.display = "";
4039
element.style.visibility = "";
4040
4041
this.elementVertical.scrollTop = 0;
4042
this.elementVertical.scrollLeft = 0;
4043
4044
this.scrollTop = 0;
4045
this.scrollLeft = 0;
4046
4047
this.vDomTop = 0;
4048
this.vDomBottom = 0;
4049
this.vDomTopPad = 0;
4050
this.vDomBottomPad = 0;
4051
this.vDomScrollPosTop = 0;
4052
this.vDomScrollPosBottom = 0;
4053
}
4054
4055
renderRows(){
4056
this._virtualRenderFill();
4057
}
4058
4059
rerenderRows(callback){
4060
var scrollTop = this.elementVertical.scrollTop;
4061
var topRow = false;
4062
var topOffset = false;
4063
4064
var left = this.table.rowManager.scrollLeft;
4065
4066
var rows = this.rows();
4067
4068
for(var i = this.vDomTop; i <= this.vDomBottom; i++){
4069
4070
if(rows[i]){
4071
var diff = scrollTop - rows[i].getElement().offsetTop;
4072
4073
if(topOffset === false || Math.abs(diff) < topOffset){
4074
topOffset = diff;
4075
topRow = i;
4076
}else {
4077
break;
4078
}
4079
}
4080
}
4081
4082
rows.forEach((row) => {
4083
row.deinitializeHeight();
4084
});
4085
4086
if(callback){
4087
callback();
4088
}
4089
4090
if(this.rows().length){
4091
this._virtualRenderFill((topRow === false ? this.rows.length - 1 : topRow), true, topOffset || 0);
4092
}else {
4093
this.clear();
4094
this.table.rowManager.tableEmpty();
4095
}
4096
4097
this.scrollColumns(left);
4098
}
4099
4100
scrollColumns(left){
4101
this.table.rowManager.scrollHorizontal(left);
4102
}
4103
4104
scrollRows(top, dir){
4105
var topDiff = top - this.vDomScrollPosTop;
4106
var bottomDiff = top - this.vDomScrollPosBottom;
4107
var margin = this.vDomWindowBuffer * 2;
4108
var rows = this.rows();
4109
4110
this.scrollTop = top;
4111
4112
if(-topDiff > margin || bottomDiff > margin){
4113
//if big scroll redraw table;
4114
var left = this.table.rowManager.scrollLeft;
4115
this._virtualRenderFill(Math.floor((this.elementVertical.scrollTop / this.elementVertical.scrollHeight) * rows.length));
4116
this.scrollColumns(left);
4117
}else {
4118
4119
if(dir){
4120
//scrolling up
4121
if(topDiff < 0){
4122
this._addTopRow(rows, -topDiff);
4123
}
4124
4125
if(bottomDiff < 0){
4126
//hide bottom row if needed
4127
if(this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer){
4128
this._removeBottomRow(rows, -bottomDiff);
4129
}else {
4130
this.vDomScrollPosBottom = this.scrollTop;
4131
}
4132
}
4133
}else {
4134
4135
if(bottomDiff >= 0){
4136
this._addBottomRow(rows, bottomDiff);
4137
}
4138
4139
//scrolling down
4140
if(topDiff >= 0){
4141
//hide top row if needed
4142
if(this.scrollTop > this.vDomWindowBuffer){
4143
this._removeTopRow(rows, topDiff);
4144
}else {
4145
this.vDomScrollPosTop = this.scrollTop;
4146
}
4147
}
4148
}
4149
}
4150
}
4151
4152
resize(){
4153
this.vDomWindowBuffer = this.table.options.renderVerticalBuffer || this.elementVertical.clientHeight;
4154
}
4155
4156
scrollToRowNearestTop(row){
4157
var rowIndex = this.rows().indexOf(row);
4158
4159
return !(Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex));
4160
}
4161
4162
scrollToRow(row){
4163
var index = this.rows().indexOf(row);
4164
4165
if(index > -1){
4166
this._virtualRenderFill(index, true);
4167
}
4168
}
4169
4170
visibleRows(includingBuffer){
4171
var topEdge = this.elementVertical.scrollTop,
4172
bottomEdge = this.elementVertical.clientHeight + topEdge,
4173
topFound = false,
4174
topRow = 0,
4175
bottomRow = 0,
4176
rows = this.rows();
4177
4178
if(includingBuffer){
4179
topRow = this.vDomTop;
4180
bottomRow = this.vDomBottom;
4181
}else {
4182
for(var i = this.vDomTop; i <= this.vDomBottom; i++){
4183
if(rows[i]){
4184
if(!topFound){
4185
if((topEdge - rows[i].getElement().offsetTop) >= 0){
4186
topRow = i;
4187
}else {
4188
topFound = true;
4189
4190
if(bottomEdge - rows[i].getElement().offsetTop >= 0){
4191
bottomRow = i;
4192
}else {
4193
break;
4194
}
4195
}
4196
}else {
4197
if(bottomEdge - rows[i].getElement().offsetTop >= 0){
4198
bottomRow = i;
4199
}else {
4200
break;
4201
}
4202
}
4203
}
4204
}
4205
}
4206
4207
return rows.slice(topRow, bottomRow + 1);
4208
}
4209
4210
//////////////////////////////////////
4211
//////// Internal Rendering //////////
4212
//////////////////////////////////////
4213
4214
//full virtual render
4215
_virtualRenderFill(position, forceMove, offset) {
4216
var element = this.tableElement,
4217
holder = this.elementVertical,
4218
topPad = 0,
4219
rowsHeight = 0,
4220
rowHeight = 0,
4221
heightOccupied = 0,
4222
topPadHeight = 0,
4223
i = 0,
4224
rows = this.rows(),
4225
rowsCount = rows.length,
4226
index = 0,
4227
row,
4228
rowFragment,
4229
renderedRows = [],
4230
totalRowsRendered = 0,
4231
rowsToRender = 0,
4232
fixedHeight = this.table.rowManager.fixedHeight,
4233
containerHeight = this.elementVertical.clientHeight,
4234
avgRowHeight = this.table.options.rowHeight,
4235
resized = true;
4236
4237
position = position || 0;
4238
4239
offset = offset || 0;
4240
4241
if(!position){
4242
this.clear();
4243
}else {
4244
while(element.firstChild) element.removeChild(element.firstChild);
4245
4246
//check if position is too close to bottom of table
4247
heightOccupied = (rowsCount - position + 1) * this.vDomRowHeight;
4248
4249
if(heightOccupied < containerHeight){
4250
position -= Math.ceil((containerHeight - heightOccupied) / this.vDomRowHeight);
4251
if(position < 0){
4252
position = 0;
4253
}
4254
}
4255
4256
//calculate initial pad
4257
topPad = Math.min(Math.max(Math.floor(this.vDomWindowBuffer / this.vDomRowHeight), this.vDomWindowMinMarginRows), position);
4258
position -= topPad;
4259
}
4260
4261
if(rowsCount && Helpers.elVisible(this.elementVertical)){
4262
this.vDomTop = position;
4263
this.vDomBottom = position -1;
4264
4265
if(fixedHeight || this.table.options.maxHeight) {
4266
if(avgRowHeight) {
4267
rowsToRender = (containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight);
4268
}
4269
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil(rowsToRender));
4270
}
4271
else {
4272
rowsToRender = rowsCount;
4273
}
4274
4275
while(((rowsToRender == rowsCount || rowsHeight <= containerHeight + this.vDomWindowBuffer) || totalRowsRendered < this.vDomWindowMinTotalRows) && this.vDomBottom < rowsCount -1) {
4276
renderedRows = [];
4277
rowFragment = document.createDocumentFragment();
4278
4279
i = 0;
4280
4281
while ((i < rowsToRender) && this.vDomBottom < rowsCount -1) {
4282
index = this.vDomBottom + 1,
4283
row = rows[index];
4284
4285
this.styleRow(row, index);
4286
4287
row.initialize(false, true);
4288
if(!row.heightInitialized && !this.table.options.rowHeight){
4289
row.clearCellHeight();
4290
}
4291
4292
rowFragment.appendChild(row.getElement());
4293
renderedRows.push(row);
4294
this.vDomBottom ++;
4295
i++;
4296
}
4297
4298
if(!renderedRows.length){
4299
break;
4300
}
4301
4302
element.appendChild(rowFragment);
4303
4304
// NOTE: The next 3 loops are separate on purpose
4305
// This is to batch up the dom writes and reads which drastically improves performance
4306
4307
renderedRows.forEach((row) => {
4308
row.rendered();
4309
4310
if(!row.heightInitialized) {
4311
row.calcHeight(true);
4312
}
4313
});
4314
4315
renderedRows.forEach((row) => {
4316
if(!row.heightInitialized) {
4317
row.setCellHeight();
4318
}
4319
});
4320
4321
renderedRows.forEach((row) => {
4322
rowHeight = row.getHeight();
4323
4324
if(totalRowsRendered < topPad){
4325
topPadHeight += rowHeight;
4326
}else {
4327
rowsHeight += rowHeight;
4328
}
4329
4330
if(rowHeight > this.vDomWindowBuffer){
4331
this.vDomWindowBuffer = rowHeight * 2;
4332
}
4333
totalRowsRendered++;
4334
});
4335
4336
resized = this.table.rowManager.adjustTableSize();
4337
containerHeight = this.elementVertical.clientHeight;
4338
if(resized && (fixedHeight || this.table.options.maxHeight))
4339
{
4340
avgRowHeight = rowsHeight / totalRowsRendered;
4341
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil((containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight)));
4342
}
4343
}
4344
4345
if(!position){
4346
this.vDomTopPad = 0;
4347
//adjust row height to match average of rendered elements
4348
this.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / totalRowsRendered);
4349
this.vDomBottomPad = this.vDomRowHeight * (rowsCount - this.vDomBottom -1);
4350
4351
this.vDomScrollHeight = topPadHeight + rowsHeight + this.vDomBottomPad - containerHeight;
4352
}else {
4353
this.vDomTopPad = !forceMove ? this.scrollTop - topPadHeight : (this.vDomRowHeight * this.vDomTop) + offset;
4354
this.vDomBottomPad = this.vDomBottom == rowsCount-1 ? 0 : Math.max(this.vDomScrollHeight - this.vDomTopPad - rowsHeight - topPadHeight, 0);
4355
}
4356
4357
element.style.paddingTop = this.vDomTopPad+"px";
4358
element.style.paddingBottom = this.vDomBottomPad+"px";
4359
4360
if(forceMove){
4361
this.scrollTop = this.vDomTopPad + (topPadHeight) + offset - (this.elementVertical.scrollWidth > this.elementVertical.clientWidth ? this.elementVertical.offsetHeight - containerHeight : 0);
4362
}
4363
4364
this.scrollTop = Math.min(this.scrollTop, this.elementVertical.scrollHeight - containerHeight);
4365
4366
//adjust for horizontal scrollbar if present (and not at top of table)
4367
if(this.elementVertical.scrollWidth > this.elementVertical.clientWidth && forceMove){
4368
this.scrollTop += this.elementVertical.offsetHeight - containerHeight;
4369
}
4370
4371
this.vDomScrollPosTop = this.scrollTop;
4372
this.vDomScrollPosBottom = this.scrollTop;
4373
4374
holder.scrollTop = this.scrollTop;
4375
4376
this.dispatch("render-virtual-fill");
4377
}
4378
}
4379
4380
_addTopRow(rows, fillableSpace){
4381
var table = this.tableElement,
4382
addedRows = [],
4383
paddingAdjust = 0,
4384
index = this.vDomTop -1,
4385
i = 0,
4386
working = true;
4387
4388
while(working){
4389
if(this.vDomTop){
4390
let row = rows[index],
4391
rowHeight, initialized;
4392
4393
if(row && i < this.vDomMaxRenderChain){
4394
rowHeight = row.getHeight() || this.vDomRowHeight;
4395
initialized = row.initialized;
4396
4397
if(fillableSpace >= rowHeight){
4398
4399
this.styleRow(row, index);
4400
table.insertBefore(row.getElement(), table.firstChild);
4401
4402
if(!row.initialized || !row.heightInitialized){
4403
addedRows.push(row);
4404
}
4405
4406
row.initialize();
4407
4408
if(!initialized){
4409
rowHeight = row.getElement().offsetHeight;
4410
4411
if(rowHeight > this.vDomWindowBuffer){
4412
this.vDomWindowBuffer = rowHeight * 2;
4413
}
4414
}
4415
4416
fillableSpace -= rowHeight;
4417
paddingAdjust += rowHeight;
4418
4419
this.vDomTop--;
4420
index--;
4421
i++;
4422
4423
}else {
4424
working = false;
4425
}
4426
4427
}else {
4428
working = false;
4429
}
4430
4431
}else {
4432
working = false;
4433
}
4434
}
4435
4436
for (let row of addedRows){
4437
row.clearCellHeight();
4438
}
4439
4440
this._quickNormalizeRowHeight(addedRows);
4441
4442
if(paddingAdjust){
4443
this.vDomTopPad -= paddingAdjust;
4444
4445
if(this.vDomTopPad < 0){
4446
this.vDomTopPad = index * this.vDomRowHeight;
4447
}
4448
4449
if(index < 1){
4450
this.vDomTopPad = 0;
4451
}
4452
4453
table.style.paddingTop = this.vDomTopPad + "px";
4454
this.vDomScrollPosTop -= paddingAdjust;
4455
}
4456
}
4457
4458
_removeTopRow(rows, fillableSpace){
4459
var removableRows = [],
4460
paddingAdjust = 0,
4461
i = 0,
4462
working = true;
4463
4464
while(working){
4465
let row = rows[this.vDomTop],
4466
rowHeight;
4467
4468
if(row && i < this.vDomMaxRenderChain){
4469
rowHeight = row.getHeight() || this.vDomRowHeight;
4470
4471
if(fillableSpace >= rowHeight){
4472
this.vDomTop++;
4473
4474
fillableSpace -= rowHeight;
4475
paddingAdjust += rowHeight;
4476
4477
removableRows.push(row);
4478
i++;
4479
}else {
4480
working = false;
4481
}
4482
}else {
4483
working = false;
4484
}
4485
}
4486
4487
for (let row of removableRows){
4488
let rowEl = row.getElement();
4489
4490
if(rowEl.parentNode){
4491
rowEl.parentNode.removeChild(rowEl);
4492
}
4493
}
4494
4495
if(paddingAdjust){
4496
this.vDomTopPad += paddingAdjust;
4497
this.tableElement.style.paddingTop = this.vDomTopPad + "px";
4498
this.vDomScrollPosTop += this.vDomTop ? paddingAdjust : paddingAdjust + this.vDomWindowBuffer;
4499
}
4500
}
4501
4502
_addBottomRow(rows, fillableSpace){
4503
var table = this.tableElement,
4504
addedRows = [],
4505
paddingAdjust = 0,
4506
index = this.vDomBottom + 1,
4507
i = 0,
4508
working = true;
4509
4510
while(working){
4511
let row = rows[index],
4512
rowHeight, initialized;
4513
4514
if(row && i < this.vDomMaxRenderChain){
4515
rowHeight = row.getHeight() || this.vDomRowHeight;
4516
initialized = row.initialized;
4517
4518
if(fillableSpace >= rowHeight){
4519
4520
this.styleRow(row, index);
4521
table.appendChild(row.getElement());
4522
4523
if(!row.initialized || !row.heightInitialized){
4524
addedRows.push(row);
4525
}
4526
4527
row.initialize();
4528
4529
if(!initialized){
4530
rowHeight = row.getElement().offsetHeight;
4531
4532
if(rowHeight > this.vDomWindowBuffer){
4533
this.vDomWindowBuffer = rowHeight * 2;
4534
}
4535
}
4536
4537
fillableSpace -= rowHeight;
4538
paddingAdjust += rowHeight;
4539
4540
this.vDomBottom++;
4541
index++;
4542
i++;
4543
}else {
4544
working = false;
4545
}
4546
}else {
4547
working = false;
4548
}
4549
}
4550
4551
for (let row of addedRows){
4552
row.clearCellHeight();
4553
}
4554
4555
this._quickNormalizeRowHeight(addedRows);
4556
4557
if(paddingAdjust){
4558
this.vDomBottomPad -= paddingAdjust;
4559
4560
if(this.vDomBottomPad < 0 || index == rows.length -1){
4561
this.vDomBottomPad = 0;
4562
}
4563
4564
table.style.paddingBottom = this.vDomBottomPad + "px";
4565
this.vDomScrollPosBottom += paddingAdjust;
4566
}
4567
}
4568
4569
_removeBottomRow(rows, fillableSpace){
4570
var removableRows = [],
4571
paddingAdjust = 0,
4572
i = 0,
4573
working = true;
4574
4575
while(working){
4576
let row = rows[this.vDomBottom],
4577
rowHeight;
4578
4579
if(row && i < this.vDomMaxRenderChain){
4580
rowHeight = row.getHeight() || this.vDomRowHeight;
4581
4582
if(fillableSpace >= rowHeight){
4583
this.vDomBottom --;
4584
4585
fillableSpace -= rowHeight;
4586
paddingAdjust += rowHeight;
4587
4588
removableRows.push(row);
4589
i++;
4590
}else {
4591
working = false;
4592
}
4593
}else {
4594
working = false;
4595
}
4596
}
4597
4598
for (let row of removableRows){
4599
let rowEl = row.getElement();
4600
4601
if(rowEl.parentNode){
4602
rowEl.parentNode.removeChild(rowEl);
4603
}
4604
}
4605
4606
if(paddingAdjust){
4607
this.vDomBottomPad += paddingAdjust;
4608
4609
if(this.vDomBottomPad < 0){
4610
this.vDomBottomPad = 0;
4611
}
4612
4613
this.tableElement.style.paddingBottom = this.vDomBottomPad + "px";
4614
this.vDomScrollPosBottom -= paddingAdjust;
4615
}
4616
}
4617
4618
_quickNormalizeRowHeight(rows){
4619
for(let row of rows){
4620
row.calcHeight();
4621
}
4622
4623
for(let row of rows){
4624
row.setCellHeight();
4625
}
4626
}
4627
}
4628
4629
class RowManager extends CoreFeature{
4630
4631
constructor(table){
4632
super(table);
4633
4634
this.element = this.createHolderElement(); //containing element
4635
this.tableElement = this.createTableElement(); //table element
4636
this.heightFixer = this.createTableElement(); //table element
4637
this.placeholder = null; //placeholder element
4638
this.placeholderContents = null; //placeholder element
4639
4640
this.firstRender = false; //handle first render
4641
this.renderMode = "virtual"; //current rendering mode
4642
this.fixedHeight = false; //current rendering mode
4643
4644
this.rows = []; //hold row data objects
4645
this.activeRowsPipeline = []; //hold calculation of active rows
4646
this.activeRows = []; //rows currently available to on display in the table
4647
this.activeRowsCount = 0; //count of active rows
4648
4649
this.displayRows = []; //rows currently on display in the table
4650
this.displayRowsCount = 0; //count of display rows
4651
4652
this.scrollTop = 0;
4653
this.scrollLeft = 0;
4654
4655
this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing
4656
this.redrawBlockRestoreConfig = false; //store latest redraw function calls for when redraw is needed
4657
this.redrawBlockRenderInPosition = false; //store latest redraw function calls for when redraw is needed
4658
4659
this.dataPipeline = []; //hold data pipeline tasks
4660
this.displayPipeline = []; //hold data display pipeline tasks
4661
4662
this.scrollbarWidth = 0;
4663
4664
this.renderer = null;
4665
}
4666
4667
//////////////// Setup Functions /////////////////
4668
4669
createHolderElement (){
4670
var el = document.createElement("div");
4671
4672
el.classList.add("tabulator-tableholder");
4673
el.setAttribute("tabindex", 0);
4674
// el.setAttribute("role", "rowgroup");
4675
4676
return el;
4677
}
4678
4679
createTableElement (){
4680
var el = document.createElement("div");
4681
4682
el.classList.add("tabulator-table");
4683
el.setAttribute("role", "rowgroup");
4684
4685
return el;
4686
}
4687
4688
initializePlaceholder(){
4689
var placeholder = this.table.options.placeholder;
4690
4691
if(typeof placeholder === "function"){
4692
placeholder = placeholder.call(this.table);
4693
}
4694
4695
placeholder = this.chain("placeholder", [placeholder], placeholder, placeholder) || placeholder;
4696
4697
//configure placeholder element
4698
if(placeholder){
4699
let el = document.createElement("div");
4700
el.classList.add("tabulator-placeholder");
4701
4702
if(typeof placeholder == "string"){
4703
let contents = document.createElement("div");
4704
contents.classList.add("tabulator-placeholder-contents");
4705
contents.innerHTML = placeholder;
4706
4707
el.appendChild(contents);
4708
4709
this.placeholderContents = contents;
4710
4711
}else if(typeof HTMLElement !== "undefined" && placeholder instanceof HTMLElement){
4712
4713
el.appendChild(placeholder);
4714
this.placeholderContents = placeholder;
4715
}else {
4716
console.warn("Invalid placeholder provided, must be string or HTML Element", placeholder);
4717
4718
this.el = null;
4719
}
4720
4721
this.placeholder = el;
4722
}
4723
}
4724
4725
//return containing element
4726
getElement(){
4727
return this.element;
4728
}
4729
4730
//return table element
4731
getTableElement(){
4732
return this.tableElement;
4733
}
4734
4735
initialize(){
4736
this.initializePlaceholder();
4737
this.initializeRenderer();
4738
4739
//initialize manager
4740
this.element.appendChild(this.tableElement);
4741
4742
this.firstRender = true;
4743
4744
//scroll header along with table body
4745
this.element.addEventListener("scroll", () => {
4746
var left = this.element.scrollLeft,
4747
leftDir = this.scrollLeft > left,
4748
top = this.element.scrollTop,
4749
topDir = this.scrollTop > top;
4750
4751
//handle horizontal scrolling
4752
if(this.scrollLeft != left){
4753
this.scrollLeft = left;
4754
4755
this.dispatch("scroll-horizontal", left, leftDir);
4756
this.dispatchExternal("scrollHorizontal", left, leftDir);
4757
4758
this._positionPlaceholder();
4759
}
4760
4761
//handle vertical scrolling
4762
if(this.scrollTop != top){
4763
this.scrollTop = top;
4764
4765
this.renderer.scrollRows(top, topDir);
4766
4767
this.dispatch("scroll-vertical", top, topDir);
4768
this.dispatchExternal("scrollVertical", top, topDir);
4769
}
4770
});
4771
}
4772
4773
////////////////// Row Manipulation //////////////////
4774
findRow(subject){
4775
if(typeof subject == "object"){
4776
if(subject instanceof Row){
4777
//subject is row element
4778
return subject;
4779
}else if(subject instanceof RowComponent){
4780
//subject is public row component
4781
return subject._getSelf() || false;
4782
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
4783
//subject is a HTML element of the row
4784
let match = this.rows.find((row) => {
4785
return row.getElement() === subject;
4786
});
4787
4788
return match || false;
4789
}else if(subject === null){
4790
return false;
4791
}
4792
}else if(typeof subject == "undefined"){
4793
return false;
4794
}else {
4795
//subject should be treated as the index of the row
4796
let match = this.rows.find((row) => {
4797
return row.data[this.table.options.index] == subject;
4798
});
4799
4800
return match || false;
4801
}
4802
4803
//catch all for any other type of input
4804
return false;
4805
}
4806
4807
getRowFromDataObject(data){
4808
var match = this.rows.find((row) => {
4809
return row.data === data;
4810
});
4811
4812
return match || false;
4813
}
4814
4815
getRowFromPosition(position){
4816
return this.getDisplayRows().find((row) => {
4817
return row.getPosition() === position && row.isDisplayed();
4818
});
4819
}
4820
4821
scrollToRow(row, position, ifVisible){
4822
return this.renderer.scrollToRowPosition(row, position, ifVisible);
4823
}
4824
4825
////////////////// Data Handling //////////////////
4826
setData(data, renderInPosition, columnsChanged){
4827
return new Promise((resolve, reject)=>{
4828
if(renderInPosition && this.getDisplayRows().length){
4829
if(this.table.options.pagination){
4830
this._setDataActual(data, true);
4831
}else {
4832
this.reRenderInPosition(() => {
4833
this._setDataActual(data);
4834
});
4835
}
4836
}else {
4837
if(this.table.options.autoColumns && columnsChanged && this.table.initialized){
4838
this.table.columnManager.generateColumnsFromRowData(data);
4839
}
4840
this.resetScroll();
4841
4842
this._setDataActual(data);
4843
}
4844
4845
resolve();
4846
});
4847
}
4848
4849
_setDataActual(data, renderInPosition){
4850
this.dispatchExternal("dataProcessing", data);
4851
4852
this._wipeElements();
4853
4854
if(Array.isArray(data)){
4855
this.dispatch("data-processing", data);
4856
4857
data.forEach((def, i) => {
4858
if(def && typeof def === "object"){
4859
var row = new Row(def, this);
4860
this.rows.push(row);
4861
}else {
4862
console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but received:", def);
4863
}
4864
});
4865
4866
this.refreshActiveData(false, false, renderInPosition);
4867
4868
this.dispatch("data-processed", data);
4869
this.dispatchExternal("dataProcessed", data);
4870
}else {
4871
console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data);
4872
}
4873
}
4874
4875
_wipeElements(){
4876
this.dispatch("rows-wipe");
4877
4878
this.destroy();
4879
4880
this.adjustTableSize();
4881
4882
this.dispatch("rows-wiped");
4883
}
4884
4885
destroy(){
4886
this.rows.forEach((row) => {
4887
row.wipe();
4888
});
4889
4890
this.rows = [];
4891
this.activeRows = [];
4892
this.activeRowsPipeline = [];
4893
this.activeRowsCount = 0;
4894
this.displayRows = [];
4895
this.displayRowsCount = 0;
4896
}
4897
4898
deleteRow(row, blockRedraw){
4899
var allIndex = this.rows.indexOf(row),
4900
activeIndex = this.activeRows.indexOf(row);
4901
4902
if(activeIndex > -1){
4903
this.activeRows.splice(activeIndex, 1);
4904
}
4905
4906
if(allIndex > -1){
4907
this.rows.splice(allIndex, 1);
4908
}
4909
4910
this.setActiveRows(this.activeRows);
4911
4912
this.displayRowIterator((rows) => {
4913
var displayIndex = rows.indexOf(row);
4914
4915
if(displayIndex > -1){
4916
rows.splice(displayIndex, 1);
4917
}
4918
});
4919
4920
if(!blockRedraw){
4921
this.reRenderInPosition();
4922
}
4923
4924
this.regenerateRowPositions();
4925
4926
this.dispatchExternal("rowDeleted", row.getComponent());
4927
4928
if(!this.displayRowsCount){
4929
this.tableEmpty();
4930
}
4931
4932
if(this.subscribedExternal("dataChanged")){
4933
this.dispatchExternal("dataChanged", this.getData());
4934
}
4935
}
4936
4937
addRow(data, pos, index, blockRedraw){
4938
var row = this.addRowActual(data, pos, index, blockRedraw);
4939
return row;
4940
}
4941
4942
//add multiple rows
4943
addRows(data, pos, index, refreshDisplayOnly){
4944
var rows = [];
4945
4946
return new Promise((resolve, reject) => {
4947
pos = this.findAddRowPos(pos);
4948
4949
if(!Array.isArray(data)){
4950
data = [data];
4951
}
4952
4953
if((typeof index == "undefined" && pos) || (typeof index !== "undefined" && !pos)){
4954
data.reverse();
4955
}
4956
4957
data.forEach((item, i) => {
4958
var row = this.addRow(item, pos, index, true);
4959
rows.push(row);
4960
this.dispatch("row-added", row, item, pos, index);
4961
});
4962
4963
this.refreshActiveData(refreshDisplayOnly ? "displayPipeline" : false, false, true);
4964
4965
this.regenerateRowPositions();
4966
4967
if(rows.length){
4968
this._clearPlaceholder();
4969
}
4970
4971
resolve(rows);
4972
});
4973
}
4974
4975
findAddRowPos(pos){
4976
if(typeof pos === "undefined"){
4977
pos = this.table.options.addRowPos;
4978
}
4979
4980
if(pos === "pos"){
4981
pos = true;
4982
}
4983
4984
if(pos === "bottom"){
4985
pos = false;
4986
}
4987
4988
return pos;
4989
}
4990
4991
addRowActual(data, pos, index, blockRedraw){
4992
var row = data instanceof Row ? data : new Row(data || {}, this),
4993
top = this.findAddRowPos(pos),
4994
allIndex = -1,
4995
activeIndex, chainResult;
4996
4997
if(!index){
4998
chainResult = this.chain("row-adding-position", [row, top], null, {index, top});
4999
5000
index = chainResult.index;
5001
top = chainResult.top;
5002
}
5003
5004
if(typeof index !== "undefined"){
5005
index = this.findRow(index);
5006
}
5007
5008
index = this.chain("row-adding-index", [row, index, top], null, index);
5009
5010
if(index){
5011
allIndex = this.rows.indexOf(index);
5012
}
5013
5014
if(index && allIndex > -1){
5015
activeIndex = this.activeRows.indexOf(index);
5016
5017
this.displayRowIterator(function(rows){
5018
var displayIndex = rows.indexOf(index);
5019
5020
if(displayIndex > -1){
5021
rows.splice((top ? displayIndex : displayIndex + 1), 0, row);
5022
}
5023
});
5024
5025
if(activeIndex > -1){
5026
this.activeRows.splice((top ? activeIndex : activeIndex + 1), 0, row);
5027
}
5028
5029
this.rows.splice((top ? allIndex : allIndex + 1), 0, row);
5030
5031
}else {
5032
5033
if(top){
5034
5035
this.displayRowIterator(function(rows){
5036
rows.unshift(row);
5037
});
5038
5039
this.activeRows.unshift(row);
5040
this.rows.unshift(row);
5041
}else {
5042
this.displayRowIterator(function(rows){
5043
rows.push(row);
5044
});
5045
5046
this.activeRows.push(row);
5047
this.rows.push(row);
5048
}
5049
}
5050
5051
this.setActiveRows(this.activeRows);
5052
5053
this.dispatchExternal("rowAdded", row.getComponent());
5054
5055
if(this.subscribedExternal("dataChanged")){
5056
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
5057
}
5058
5059
if(!blockRedraw){
5060
this.reRenderInPosition();
5061
}
5062
5063
return row;
5064
}
5065
5066
moveRow(from, to, after){
5067
this.dispatch("row-move", from, to, after);
5068
5069
this.moveRowActual(from, to, after);
5070
5071
this.regenerateRowPositions();
5072
5073
this.dispatch("row-moved", from, to, after);
5074
this.dispatchExternal("rowMoved", from.getComponent());
5075
}
5076
5077
moveRowActual(from, to, after){
5078
this.moveRowInArray(this.rows, from, to, after);
5079
this.moveRowInArray(this.activeRows, from, to, after);
5080
5081
this.displayRowIterator((rows) => {
5082
this.moveRowInArray(rows, from, to, after);
5083
});
5084
5085
this.dispatch("row-moving", from, to, after);
5086
}
5087
5088
moveRowInArray(rows, from, to, after){
5089
var fromIndex, toIndex, start, end;
5090
5091
if(from !== to){
5092
5093
fromIndex = rows.indexOf(from);
5094
5095
if (fromIndex > -1) {
5096
5097
rows.splice(fromIndex, 1);
5098
5099
toIndex = rows.indexOf(to);
5100
5101
if (toIndex > -1) {
5102
5103
if(after){
5104
rows.splice(toIndex+1, 0, from);
5105
}else {
5106
rows.splice(toIndex, 0, from);
5107
}
5108
5109
}else {
5110
rows.splice(fromIndex, 0, from);
5111
}
5112
}
5113
5114
//restyle rows
5115
if(rows === this.getDisplayRows()){
5116
5117
start = fromIndex < toIndex ? fromIndex : toIndex;
5118
end = toIndex > fromIndex ? toIndex : fromIndex +1;
5119
5120
for(let i = start; i <= end; i++){
5121
if(rows[i]){
5122
this.styleRow(rows[i], i);
5123
}
5124
}
5125
}
5126
}
5127
}
5128
5129
clearData(){
5130
this.setData([]);
5131
}
5132
5133
getRowIndex(row){
5134
return this.findRowIndex(row, this.rows);
5135
}
5136
5137
getDisplayRowIndex(row){
5138
var index = this.getDisplayRows().indexOf(row);
5139
return index > -1 ? index : false;
5140
}
5141
5142
nextDisplayRow(row, rowOnly){
5143
var index = this.getDisplayRowIndex(row),
5144
nextRow = false;
5145
5146
5147
if(index !== false && index < this.displayRowsCount -1){
5148
nextRow = this.getDisplayRows()[index+1];
5149
}
5150
5151
if(nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")){
5152
return this.nextDisplayRow(nextRow, rowOnly);
5153
}
5154
5155
return nextRow;
5156
}
5157
5158
prevDisplayRow(row, rowOnly){
5159
var index = this.getDisplayRowIndex(row),
5160
prevRow = false;
5161
5162
if(index){
5163
prevRow = this.getDisplayRows()[index-1];
5164
}
5165
5166
if(rowOnly && prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")){
5167
return this.prevDisplayRow(prevRow, rowOnly);
5168
}
5169
5170
return prevRow;
5171
}
5172
5173
findRowIndex(row, list){
5174
var rowIndex;
5175
5176
row = this.findRow(row);
5177
5178
if(row){
5179
rowIndex = list.indexOf(row);
5180
5181
if(rowIndex > -1){
5182
return rowIndex;
5183
}
5184
}
5185
5186
return false;
5187
}
5188
5189
getData(active, transform){
5190
var output = [],
5191
rows = this.getRows(active);
5192
5193
rows.forEach(function(row){
5194
if(row.type == "row"){
5195
output.push(row.getData(transform || "data"));
5196
}
5197
});
5198
5199
return output;
5200
}
5201
5202
getComponents(active){
5203
var output = [],
5204
rows = this.getRows(active);
5205
5206
rows.forEach(function(row){
5207
output.push(row.getComponent());
5208
});
5209
5210
return output;
5211
}
5212
5213
getDataCount(active){
5214
var rows = this.getRows(active);
5215
5216
return rows.length;
5217
}
5218
5219
scrollHorizontal(left){
5220
this.scrollLeft = left;
5221
this.element.scrollLeft = left;
5222
5223
this.dispatch("scroll-horizontal", left);
5224
}
5225
5226
registerDataPipelineHandler(handler, priority){
5227
if(typeof priority !== "undefined"){
5228
this.dataPipeline.push({handler, priority});
5229
this.dataPipeline.sort((a, b) => {
5230
return a.priority - b.priority;
5231
});
5232
}else {
5233
console.error("Data pipeline handlers must have a priority in order to be registered");
5234
}
5235
}
5236
5237
registerDisplayPipelineHandler(handler, priority){
5238
if(typeof priority !== "undefined"){
5239
this.displayPipeline.push({handler, priority});
5240
this.displayPipeline.sort((a, b) => {
5241
return a.priority - b.priority;
5242
});
5243
}else {
5244
console.error("Display pipeline handlers must have a priority in order to be registered");
5245
}
5246
}
5247
5248
//set active data set
5249
refreshActiveData(handler, skipStage, renderInPosition){
5250
var table = this.table,
5251
stage = "",
5252
index = 0,
5253
cascadeOrder = ["all", "dataPipeline", "display", "displayPipeline", "end"];
5254
5255
if(!this.table.destroyed){
5256
if(typeof handler === "function"){
5257
index = this.dataPipeline.findIndex((item) => {
5258
return item.handler === handler;
5259
});
5260
5261
if(index > -1){
5262
stage = "dataPipeline";
5263
5264
if(skipStage){
5265
if(index == this.dataPipeline.length - 1){
5266
stage = "display";
5267
}else {
5268
index++;
5269
}
5270
}
5271
}else {
5272
index = this.displayPipeline.findIndex((item) => {
5273
return item.handler === handler;
5274
});
5275
5276
if(index > -1){
5277
stage = "displayPipeline";
5278
5279
if(skipStage){
5280
if(index == this.displayPipeline.length - 1){
5281
stage = "end";
5282
}else {
5283
index++;
5284
}
5285
}
5286
}else {
5287
console.error("Unable to refresh data, invalid handler provided", handler);
5288
return;
5289
}
5290
}
5291
}else {
5292
stage = handler || "all";
5293
index = 0;
5294
}
5295
5296
if(this.redrawBlock){
5297
if(!this.redrawBlockRestoreConfig || (this.redrawBlockRestoreConfig && ((this.redrawBlockRestoreConfig.stage === stage && index < this.redrawBlockRestoreConfig.index) || (cascadeOrder.indexOf(stage) < cascadeOrder.indexOf(this.redrawBlockRestoreConfig.stage))))){
5298
this.redrawBlockRestoreConfig = {
5299
handler: handler,
5300
skipStage: skipStage,
5301
renderInPosition: renderInPosition,
5302
stage:stage,
5303
index:index,
5304
};
5305
}
5306
5307
return;
5308
}else {
5309
if(Helpers.elVisible(this.element)){
5310
if(renderInPosition){
5311
this.reRenderInPosition(this.refreshPipelines.bind(this, handler, stage, index, renderInPosition));
5312
}else {
5313
this.refreshPipelines(handler, stage, index, renderInPosition);
5314
5315
if(!handler){
5316
this.table.columnManager.renderer.renderColumns();
5317
}
5318
5319
this.renderTable();
5320
5321
if(table.options.layoutColumnsOnNewData){
5322
this.table.columnManager.redraw(true);
5323
}
5324
}
5325
}else {
5326
this.refreshPipelines(handler, stage, index, renderInPosition);
5327
}
5328
5329
this.dispatch("data-refreshed");
5330
}
5331
}
5332
}
5333
5334
refreshPipelines(handler, stage, index, renderInPosition){
5335
this.dispatch("data-refreshing");
5336
5337
if(!handler){
5338
this.activeRowsPipeline[0] = this.rows.slice(0);
5339
}
5340
5341
//cascade through data refresh stages
5342
switch(stage){
5343
case "all":
5344
//handle case where all data needs refreshing
5345
5346
case "dataPipeline":
5347
5348
for(let i = index; i < this.dataPipeline.length; i++){
5349
let result = this.dataPipeline[i].handler(this.activeRowsPipeline[i].slice(0));
5350
5351
this.activeRowsPipeline[i + 1] = result || this.activeRowsPipeline[i].slice(0);
5352
}
5353
5354
this.setActiveRows(this.activeRowsPipeline[this.dataPipeline.length]);
5355
5356
case "display":
5357
index = 0;
5358
this.resetDisplayRows();
5359
5360
case "displayPipeline":
5361
for(let i = index; i < this.displayPipeline.length; i++){
5362
let result = this.displayPipeline[i].handler((i ? this.getDisplayRows(i - 1) : this.activeRows).slice(0), renderInPosition);
5363
5364
this.setDisplayRows(result || this.getDisplayRows(i - 1).slice(0), i);
5365
}
5366
5367
case "end":
5368
//case to handle scenario when trying to skip past end stage
5369
this.regenerateRowPositions();
5370
}
5371
5372
if(this.getDisplayRows().length){
5373
this._clearPlaceholder();
5374
}
5375
}
5376
5377
//regenerate row positions
5378
regenerateRowPositions(){
5379
var rows = this.getDisplayRows();
5380
var index = 1;
5381
5382
rows.forEach((row) => {
5383
if (row.type === "row"){
5384
row.setPosition(index);
5385
index++;
5386
}
5387
});
5388
}
5389
5390
setActiveRows(activeRows){
5391
this.activeRows = this.activeRows = Object.assign([], activeRows);
5392
this.activeRowsCount = this.activeRows.length;
5393
}
5394
5395
//reset display rows array
5396
resetDisplayRows(){
5397
this.displayRows = [];
5398
5399
this.displayRows.push(this.activeRows.slice(0));
5400
5401
this.displayRowsCount = this.displayRows[0].length;
5402
}
5403
5404
//set display row pipeline data
5405
setDisplayRows(displayRows, index){
5406
this.displayRows[index] = displayRows;
5407
5408
if(index == this.displayRows.length -1){
5409
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
5410
}
5411
}
5412
5413
getDisplayRows(index){
5414
if(typeof index == "undefined"){
5415
return this.displayRows.length ? this.displayRows[this.displayRows.length -1] : [];
5416
}else {
5417
return this.displayRows[index] || [];
5418
}
5419
}
5420
5421
getVisibleRows(chain, viewable){
5422
var rows = Object.assign([], this.renderer.visibleRows(!viewable));
5423
5424
if(chain){
5425
rows = this.chain("rows-visible", [viewable], rows, rows);
5426
}
5427
5428
return rows;
5429
}
5430
5431
//repeat action across display rows
5432
displayRowIterator(callback){
5433
this.activeRowsPipeline.forEach(callback);
5434
this.displayRows.forEach(callback);
5435
5436
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
5437
}
5438
5439
//return only actual rows (not group headers etc)
5440
getRows(type){
5441
var rows = [];
5442
5443
switch(type){
5444
case "active":
5445
rows = this.activeRows;
5446
break;
5447
5448
case "display":
5449
rows = this.table.rowManager.getDisplayRows();
5450
break;
5451
5452
case "visible":
5453
rows = this.getVisibleRows(false, true);
5454
break;
5455
5456
default:
5457
rows = this.chain("rows-retrieve", type, null, this.rows) || this.rows;
5458
}
5459
5460
return rows;
5461
}
5462
5463
///////////////// Table Rendering /////////////////
5464
//trigger rerender of table in current position
5465
reRenderInPosition(callback){
5466
if(this.redrawBlock){
5467
if(callback){
5468
callback();
5469
}else {
5470
this.redrawBlockRenderInPosition = true;
5471
}
5472
}else {
5473
this.dispatchExternal("renderStarted");
5474
5475
this.renderer.rerenderRows(callback);
5476
5477
if(!this.fixedHeight){
5478
this.adjustTableSize();
5479
}
5480
5481
this.scrollBarCheck();
5482
5483
this.dispatchExternal("renderComplete");
5484
}
5485
}
5486
5487
scrollBarCheck(){
5488
var scrollbarWidth = 0;
5489
5490
//adjust for vertical scrollbar moving table when present
5491
if(this.element.scrollHeight > this.element.clientHeight){
5492
scrollbarWidth = this.element.offsetWidth - this.element.clientWidth;
5493
}
5494
5495
if(scrollbarWidth !== this.scrollbarWidth){
5496
this.scrollbarWidth = scrollbarWidth;
5497
this.dispatch("scrollbar-vertical", scrollbarWidth);
5498
}
5499
}
5500
5501
initializeRenderer(){
5502
var renderClass;
5503
5504
var renderers = {
5505
"virtual": VirtualDomVertical,
5506
"basic": BasicVertical,
5507
};
5508
5509
if(typeof this.table.options.renderVertical === "string"){
5510
renderClass = renderers[this.table.options.renderVertical];
5511
}else {
5512
renderClass = this.table.options.renderVertical;
5513
}
5514
5515
if(renderClass){
5516
this.renderMode = this.table.options.renderVertical;
5517
5518
this.renderer = new renderClass(this.table, this.element, this.tableElement);
5519
this.renderer.initialize();
5520
5521
if((this.table.element.clientHeight || this.table.options.height) && !(this.table.options.minHeight && this.table.options.maxHeight)){
5522
this.fixedHeight = true;
5523
}else {
5524
this.fixedHeight = false;
5525
}
5526
}else {
5527
console.error("Unable to find matching renderer:", this.table.options.renderVertical);
5528
}
5529
}
5530
5531
getRenderMode(){
5532
return this.renderMode;
5533
}
5534
5535
renderTable(){
5536
this.dispatchExternal("renderStarted");
5537
5538
this.element.scrollTop = 0;
5539
5540
this._clearTable();
5541
5542
if(this.displayRowsCount){
5543
this.renderer.renderRows();
5544
5545
if(this.firstRender){
5546
this.firstRender = false;
5547
5548
if(!this.fixedHeight){
5549
this.adjustTableSize();
5550
}
5551
5552
this.layoutRefresh(true);
5553
}
5554
}else {
5555
this.renderEmptyScroll();
5556
}
5557
5558
if(!this.fixedHeight){
5559
this.adjustTableSize();
5560
}
5561
5562
this.dispatch("table-layout");
5563
5564
if(!this.displayRowsCount){
5565
this._showPlaceholder();
5566
}
5567
5568
this.scrollBarCheck();
5569
5570
this.dispatchExternal("renderComplete");
5571
}
5572
5573
//show scrollbars on empty table div
5574
renderEmptyScroll(){
5575
if(this.placeholder){
5576
this.tableElement.style.display = "none";
5577
}else {
5578
this.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px";
5579
// this.tableElement.style.minHeight = "1px";
5580
// this.tableElement.style.visibility = "hidden";
5581
}
5582
}
5583
5584
_clearTable(){
5585
this._clearPlaceholder();
5586
5587
this.scrollTop = 0;
5588
this.scrollLeft = 0;
5589
5590
this.renderer.clearRows();
5591
}
5592
5593
tableEmpty(){
5594
this.renderEmptyScroll();
5595
this._showPlaceholder();
5596
}
5597
5598
_showPlaceholder(){
5599
if(this.placeholder){
5600
if(this.placeholder && this.placeholder.parentNode){
5601
this.placeholder.parentNode.removeChild(this.placeholder);
5602
}
5603
5604
this.initializePlaceholder();
5605
5606
this.placeholder.setAttribute("tabulator-render-mode", this.renderMode);
5607
5608
this.getElement().appendChild(this.placeholder);
5609
this._positionPlaceholder();
5610
5611
this.adjustTableSize();
5612
}
5613
}
5614
5615
_clearPlaceholder(){
5616
if(this.placeholder && this.placeholder.parentNode){
5617
this.placeholder.parentNode.removeChild(this.placeholder);
5618
}
5619
5620
// clear empty table placeholder min
5621
this.tableElement.style.minWidth = "";
5622
this.tableElement.style.display = "";
5623
}
5624
5625
_positionPlaceholder(){
5626
if(this.placeholder && this.placeholder.parentNode){
5627
this.placeholder.style.width = this.table.columnManager.getWidth() + "px";
5628
this.placeholderContents.style.width = this.table.rowManager.element.clientWidth + "px";
5629
this.placeholderContents.style.marginLeft = this.scrollLeft + "px";
5630
}
5631
}
5632
5633
styleRow(row, index){
5634
var rowEl = row.getElement();
5635
5636
if(index % 2){
5637
rowEl.classList.add("tabulator-row-even");
5638
rowEl.classList.remove("tabulator-row-odd");
5639
}else {
5640
rowEl.classList.add("tabulator-row-odd");
5641
rowEl.classList.remove("tabulator-row-even");
5642
}
5643
}
5644
5645
//normalize height of active rows
5646
normalizeHeight(){
5647
this.activeRows.forEach(function(row){
5648
row.normalizeHeight();
5649
});
5650
}
5651
5652
//adjust the height of the table holder to fit in the Tabulator element
5653
adjustTableSize(){
5654
let initialHeight = this.element.clientHeight, minHeight;
5655
let resized = false;
5656
5657
if(this.renderer.verticalFillMode === "fill"){
5658
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));
5659
5660
if(this.fixedHeight){
5661
minHeight = isNaN(this.table.options.minHeight) ? this.table.options.minHeight : this.table.options.minHeight + "px";
5662
5663
const height = "calc(100% - " + otherHeight + "px)";
5664
this.element.style.minHeight = minHeight || "calc(100% - " + otherHeight + "px)";
5665
this.element.style.height = height;
5666
this.element.style.maxHeight = height;
5667
} else {
5668
this.element.style.height = "";
5669
this.element.style.height =
5670
this.table.element.clientHeight - otherHeight + "px";
5671
this.element.scrollTop = this.scrollTop;
5672
}
5673
5674
this.renderer.resize();
5675
5676
//check if the table has changed size when dealing with variable height tables
5677
if(!this.fixedHeight && initialHeight != this.element.clientHeight){
5678
resized = true;
5679
if(this.subscribed("table-resize")){
5680
this.dispatch("table-resize");
5681
}else {
5682
this.redraw();
5683
}
5684
}
5685
5686
this.scrollBarCheck();
5687
}
5688
5689
this._positionPlaceholder();
5690
return resized;
5691
}
5692
5693
//reinitialize all rows
5694
reinitialize(){
5695
this.rows.forEach(function(row){
5696
row.reinitialize(true);
5697
});
5698
}
5699
5700
//prevent table from being redrawn
5701
blockRedraw (){
5702
this.redrawBlock = true;
5703
this.redrawBlockRestoreConfig = false;
5704
}
5705
5706
//restore table redrawing
5707
restoreRedraw (){
5708
this.redrawBlock = false;
5709
5710
if(this.redrawBlockRestoreConfig){
5711
this.refreshActiveData(this.redrawBlockRestoreConfig.handler, this.redrawBlockRestoreConfig.skipStage, this.redrawBlockRestoreConfig.renderInPosition);
5712
5713
this.redrawBlockRestoreConfig = false;
5714
}else {
5715
if(this.redrawBlockRenderInPosition){
5716
this.reRenderInPosition();
5717
}
5718
}
5719
5720
this.redrawBlockRenderInPosition = false;
5721
}
5722
5723
//redraw table
5724
redraw (force){
5725
const resized = this.adjustTableSize();
5726
this.table.tableWidth = this.table.element.clientWidth;
5727
5728
if(!force){
5729
if(resized) {
5730
this.reRenderInPosition();
5731
}
5732
this.scrollHorizontal(this.scrollLeft);
5733
}else {
5734
this.renderTable();
5735
}
5736
}
5737
5738
resetScroll(){
5739
this.element.scrollLeft = 0;
5740
this.element.scrollTop = 0;
5741
5742
if(this.table.browser === "ie"){
5743
var event = document.createEvent("Event");
5744
event.initEvent("scroll", false, true);
5745
this.element.dispatchEvent(event);
5746
}else {
5747
this.element.dispatchEvent(new Event('scroll'));
5748
}
5749
}
5750
}
5751
5752
class FooterManager extends CoreFeature{
5753
5754
constructor(table){
5755
super(table);
5756
5757
this.active = false;
5758
this.element = this.createElement(); //containing element
5759
this.containerElement = this.createContainerElement(); //containing element
5760
this.external = false;
5761
}
5762
5763
initialize(){
5764
this.initializeElement();
5765
}
5766
5767
createElement(){
5768
var el = document.createElement("div");
5769
5770
el.classList.add("tabulator-footer");
5771
5772
return el;
5773
}
5774
5775
5776
createContainerElement(){
5777
var el = document.createElement("div");
5778
5779
el.classList.add("tabulator-footer-contents");
5780
5781
this.element.appendChild(el);
5782
5783
return el;
5784
}
5785
5786
initializeElement(){
5787
if(this.table.options.footerElement){
5788
5789
switch(typeof this.table.options.footerElement){
5790
case "string":
5791
if(this.table.options.footerElement[0] === "<"){
5792
this.containerElement.innerHTML = this.table.options.footerElement;
5793
}else {
5794
this.external = true;
5795
this.containerElement = document.querySelector(this.table.options.footerElement);
5796
}
5797
break;
5798
5799
default:
5800
this.element = this.table.options.footerElement;
5801
break;
5802
}
5803
}
5804
}
5805
5806
getElement(){
5807
return this.element;
5808
}
5809
5810
append(element){
5811
this.activate();
5812
5813
this.containerElement.appendChild(element);
5814
this.table.rowManager.adjustTableSize();
5815
}
5816
5817
prepend(element){
5818
this.activate();
5819
5820
this.element.insertBefore(element, this.element.firstChild);
5821
this.table.rowManager.adjustTableSize();
5822
}
5823
5824
remove(element){
5825
element.parentNode.removeChild(element);
5826
this.deactivate();
5827
}
5828
5829
deactivate(force){
5830
if(!this.element.firstChild || force){
5831
if(!this.external){
5832
this.element.parentNode.removeChild(this.element);
5833
}
5834
this.active = false;
5835
}
5836
}
5837
5838
activate(){
5839
if(!this.active){
5840
this.active = true;
5841
if(!this.external){
5842
this.table.element.appendChild(this.getElement());
5843
this.table.element.style.display = '';
5844
}
5845
}
5846
}
5847
5848
redraw(){
5849
this.dispatch("footer-redraw");
5850
}
5851
}
5852
5853
class InteractionManager extends CoreFeature {
5854
5855
constructor (table){
5856
super(table);
5857
5858
this.el = null;
5859
5860
this.abortClasses = ["tabulator-headers", "tabulator-table"];
5861
5862
this.previousTargets = {};
5863
5864
this.listeners = [
5865
"click",
5866
"dblclick",
5867
"contextmenu",
5868
"mouseenter",
5869
"mouseleave",
5870
"mouseover",
5871
"mouseout",
5872
"mousemove",
5873
"mouseup",
5874
"mousedown",
5875
"touchstart",
5876
"touchend",
5877
];
5878
5879
this.componentMap = {
5880
"tabulator-cell":"cell",
5881
"tabulator-row":"row",
5882
"tabulator-group":"group",
5883
"tabulator-col":"column",
5884
};
5885
5886
this.pseudoTrackers = {
5887
"row":{
5888
subscriber:null,
5889
target:null,
5890
},
5891
"cell":{
5892
subscriber:null,
5893
target:null,
5894
},
5895
"group":{
5896
subscriber:null,
5897
target:null,
5898
},
5899
"column":{
5900
subscriber:null,
5901
target:null,
5902
},
5903
};
5904
5905
this.pseudoTracking = false;
5906
}
5907
5908
initialize(){
5909
this.el = this.table.element;
5910
5911
this.buildListenerMap();
5912
this.bindSubscriptionWatchers();
5913
}
5914
5915
buildListenerMap(){
5916
var listenerMap = {};
5917
5918
this.listeners.forEach((listener) => {
5919
listenerMap[listener] = {
5920
handler:null,
5921
components:[],
5922
};
5923
});
5924
5925
this.listeners = listenerMap;
5926
}
5927
5928
bindPseudoEvents(){
5929
Object.keys(this.pseudoTrackers).forEach((key) => {
5930
this.pseudoTrackers[key].subscriber = this.pseudoMouseEnter.bind(this, key);
5931
this.subscribe(key + "-mouseover", this.pseudoTrackers[key].subscriber);
5932
});
5933
5934
this.pseudoTracking = true;
5935
}
5936
5937
pseudoMouseEnter(key, e, target){
5938
if(this.pseudoTrackers[key].target !== target){
5939
5940
if(this.pseudoTrackers[key].target){
5941
this.dispatch(key + "-mouseleave", e, this.pseudoTrackers[key].target);
5942
}
5943
5944
this.pseudoMouseLeave(key, e);
5945
5946
this.pseudoTrackers[key].target = target;
5947
5948
this.dispatch(key + "-mouseenter", e, target);
5949
}
5950
}
5951
5952
pseudoMouseLeave(key, e){
5953
var leaveList = Object.keys(this.pseudoTrackers),
5954
linkedKeys = {
5955
"row":["cell"],
5956
"cell":["row"],
5957
};
5958
5959
leaveList = leaveList.filter((item) => {
5960
var links = linkedKeys[key];
5961
return item !== key && (!links || (links && !links.includes(item)));
5962
});
5963
5964
5965
leaveList.forEach((key) => {
5966
var target = this.pseudoTrackers[key].target;
5967
5968
if(this.pseudoTrackers[key].target){
5969
this.dispatch(key + "-mouseleave", e, target);
5970
5971
this.pseudoTrackers[key].target = null;
5972
}
5973
});
5974
}
5975
5976
5977
bindSubscriptionWatchers(){
5978
var listeners = Object.keys(this.listeners),
5979
components = Object.values(this.componentMap);
5980
5981
for(let comp of components){
5982
for(let listener of listeners){
5983
let key = comp + "-" + listener;
5984
5985
this.subscriptionChange(key, this.subscriptionChanged.bind(this, comp, listener));
5986
}
5987
}
5988
5989
this.subscribe("table-destroy", this.clearWatchers.bind(this));
5990
}
5991
5992
subscriptionChanged(component, key, added){
5993
var listener = this.listeners[key].components,
5994
index = listener.indexOf(component),
5995
changed = false;
5996
5997
if(added){
5998
if(index === -1){
5999
listener.push(component);
6000
changed = true;
6001
}
6002
}else {
6003
if(!this.subscribed(component + "-" + key)){
6004
if(index > -1){
6005
listener.splice(index, 1);
6006
changed = true;
6007
}
6008
}
6009
}
6010
6011
if((key === "mouseenter" || key === "mouseleave") && !this.pseudoTracking){
6012
this.bindPseudoEvents();
6013
}
6014
6015
if(changed){
6016
this.updateEventListeners();
6017
}
6018
}
6019
6020
updateEventListeners(){
6021
for(let key in this.listeners){
6022
let listener = this.listeners[key];
6023
6024
if(listener.components.length){
6025
if(!listener.handler){
6026
listener.handler = this.track.bind(this, key);
6027
this.el.addEventListener(key, listener.handler);
6028
// this.el.addEventListener(key, listener.handler, {passive: true})
6029
}
6030
}else {
6031
if(listener.handler){
6032
this.el.removeEventListener(key, listener.handler);
6033
listener.handler = null;
6034
}
6035
}
6036
}
6037
}
6038
6039
track(type, e){
6040
var path = (e.composedPath && e.composedPath()) || e.path;
6041
6042
var targets = this.findTargets(path);
6043
targets = this.bindComponents(type, targets);
6044
6045
this.triggerEvents(type, e, targets);
6046
6047
if(this.pseudoTracking && (type == "mouseover" || type == "mouseleave") && !Object.keys(targets).length){
6048
this.pseudoMouseLeave("none", e);
6049
}
6050
}
6051
6052
findTargets(path){
6053
var targets = {};
6054
6055
let componentMap = Object.keys(this.componentMap);
6056
6057
for (let el of path) {
6058
let classList = el.classList ? [...el.classList] : [];
6059
6060
let abort = classList.filter((item) => {
6061
return this.abortClasses.includes(item);
6062
});
6063
6064
if(abort.length){
6065
break;
6066
}
6067
6068
let elTargets = classList.filter((item) => {
6069
return componentMap.includes(item);
6070
});
6071
6072
for (let target of elTargets) {
6073
if(!targets[this.componentMap[target]]){
6074
targets[this.componentMap[target]] = el;
6075
}
6076
}
6077
}
6078
6079
if(targets.group && targets.group === targets.row){
6080
delete targets.row;
6081
}
6082
6083
return targets;
6084
}
6085
6086
bindComponents(type, targets){
6087
//ensure row component is looked up before cell
6088
var keys = Object.keys(targets).reverse(),
6089
listener = this.listeners[type],
6090
matches = {},
6091
targetMatches = {};
6092
6093
for(let key of keys){
6094
let component,
6095
target = targets[key],
6096
previousTarget = this.previousTargets[key];
6097
6098
if(previousTarget && previousTarget.target === target){
6099
component = previousTarget.component;
6100
}else {
6101
switch(key){
6102
case "row":
6103
case "group":
6104
if(listener.components.includes("row") || listener.components.includes("cell") || listener.components.includes("group")){
6105
let rows = this.table.rowManager.getVisibleRows(true);
6106
6107
component = rows.find((row) => {
6108
return row.getElement() === target;
6109
});
6110
6111
if(targets["row"] && targets["row"].parentNode && targets["row"].parentNode.closest(".tabulator-row")){
6112
targets[key] = false;
6113
}
6114
}
6115
break;
6116
6117
case "column":
6118
if(listener.components.includes("column")){
6119
component = this.table.columnManager.findColumn(target);
6120
}
6121
break;
6122
6123
case "cell":
6124
if(listener.components.includes("cell")){
6125
if(matches["row"] instanceof Row){
6126
component = matches["row"].findCell(target);
6127
}else {
6128
if(targets["row"]){
6129
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?");
6130
}
6131
}
6132
}
6133
break;
6134
}
6135
}
6136
6137
if(component){
6138
matches[key] = component;
6139
targetMatches[key] = {
6140
target:target,
6141
component:component,
6142
};
6143
}
6144
}
6145
6146
this.previousTargets = targetMatches;
6147
6148
return matches;
6149
}
6150
6151
triggerEvents(type, e, targets){
6152
var listener = this.listeners[type];
6153
6154
for(let key in targets){
6155
if(targets[key] && listener.components.includes(key)){
6156
this.dispatch(key + "-" + type, e, targets[key]);
6157
}
6158
}
6159
}
6160
6161
clearWatchers(){
6162
for(let key in this.listeners){
6163
let listener = this.listeners[key];
6164
6165
if(listener.handler){
6166
this.el.removeEventListener(key, listener.handler);
6167
listener.handler = null;
6168
}
6169
}
6170
}
6171
}
6172
6173
class ComponentFunctionBinder{
6174
6175
constructor(table){
6176
this.table = table;
6177
6178
this.bindings = {};
6179
}
6180
6181
bind(type, funcName, handler){
6182
if(!this.bindings[type]){
6183
this.bindings[type] = {};
6184
}
6185
6186
if(this.bindings[type][funcName]){
6187
console.warn("Unable to bind component handler, a matching function name is already bound", type, funcName, handler);
6188
}else {
6189
this.bindings[type][funcName] = handler;
6190
}
6191
}
6192
6193
handle(type, component, name){
6194
if(this.bindings[type] && this.bindings[type][name] && typeof this.bindings[type][name].bind === 'function'){
6195
return this.bindings[type][name].bind(null, component);
6196
}else {
6197
if(name !== "then" && typeof name === "string" && !name.startsWith("_")){
6198
if(this.table.options.debugInvalidComponentFuncs){
6199
console.error("The " + type + " component does not have a " + name + " function, have you checked that you have the correct Tabulator module installed?");
6200
}
6201
}
6202
}
6203
}
6204
}
6205
6206
class DataLoader extends CoreFeature{
6207
constructor(table){
6208
super(table);
6209
6210
this.requestOrder = 0; //prevent requests coming out of sequence if overridden by another load request
6211
this.loading = false;
6212
}
6213
6214
initialize(){}
6215
6216
load(data, params, config, replace, silent, columnsChanged){
6217
var requestNo = ++this.requestOrder;
6218
6219
if(this.table.destroyed){
6220
return Promise.resolve();
6221
}
6222
6223
this.dispatchExternal("dataLoading", data);
6224
6225
//parse json data to array
6226
if (data && (data.indexOf("{") == 0 || data.indexOf("[") == 0)){
6227
data = JSON.parse(data);
6228
}
6229
6230
if(this.confirm("data-loading", [data, params, config, silent])){
6231
this.loading = true;
6232
6233
if(!silent){
6234
this.alertLoader();
6235
}
6236
6237
//get params for request
6238
params = this.chain("data-params", [data, config, silent], params || {}, params || {});
6239
6240
params = this.mapParams(params, this.table.options.dataSendParams);
6241
6242
var result = this.chain("data-load", [data, params, config, silent], false, Promise.resolve([]));
6243
6244
return result.then((response) => {
6245
if(!this.table.destroyed){
6246
if(!Array.isArray(response) && typeof response == "object"){
6247
response = this.mapParams(response, this.objectInvert(this.table.options.dataReceiveParams));
6248
}
6249
6250
var rowData = this.chain("data-loaded", response, null, response);
6251
6252
if(requestNo == this.requestOrder){
6253
this.clearAlert();
6254
6255
if(rowData !== false){
6256
this.dispatchExternal("dataLoaded", rowData);
6257
this.table.rowManager.setData(rowData, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged);
6258
}
6259
}else {
6260
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");
6261
}
6262
}else {
6263
console.warn("Data Load Response Blocked - Table has been destroyed");
6264
}
6265
}).catch((error) => {
6266
console.error("Data Load Error: ", error);
6267
this.dispatchExternal("dataLoadError", error);
6268
6269
if(!silent){
6270
this.alertError();
6271
}
6272
6273
setTimeout(() => {
6274
this.clearAlert();
6275
}, this.table.options.dataLoaderErrorTimeout);
6276
})
6277
.finally(() => {
6278
this.loading = false;
6279
});
6280
}else {
6281
this.dispatchExternal("dataLoaded", data);
6282
6283
if(!data){
6284
data = [];
6285
}
6286
6287
this.table.rowManager.setData(data, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged);
6288
return Promise.resolve();
6289
}
6290
}
6291
6292
mapParams(params, map){
6293
var output = {};
6294
6295
for(let key in params){
6296
output[map.hasOwnProperty(key) ? map[key] : key] = params[key];
6297
}
6298
6299
return output;
6300
}
6301
6302
objectInvert(obj){
6303
var output = {};
6304
6305
for(let key in obj){
6306
output[obj[key]] = key;
6307
}
6308
6309
return output;
6310
}
6311
6312
blockActiveLoad(){
6313
this.requestOrder++;
6314
}
6315
6316
alertLoader(){
6317
var shouldLoad = typeof this.table.options.dataLoader === "function" ? this.table.options.dataLoader() : this.table.options.dataLoader;
6318
6319
if(shouldLoad){
6320
this.table.alertManager.alert(this.table.options.dataLoaderLoading || this.langText("data|loading"));
6321
}
6322
}
6323
6324
alertError(){
6325
this.table.alertManager.alert(this.table.options.dataLoaderError || this.langText("data|error"), "error");
6326
}
6327
6328
clearAlert(){
6329
this.table.alertManager.clear();
6330
}
6331
}
6332
6333
class ExternalEventBus {
6334
6335
constructor(table, optionsList, debug){
6336
this.table = table;
6337
this.events = {};
6338
this.optionsList = optionsList || {};
6339
this.subscriptionNotifiers = {};
6340
6341
this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this);
6342
this.debug = debug;
6343
}
6344
6345
subscriptionChange(key, callback){
6346
if(!this.subscriptionNotifiers[key]){
6347
this.subscriptionNotifiers[key] = [];
6348
}
6349
6350
this.subscriptionNotifiers[key].push(callback);
6351
6352
if(this.subscribed(key)){
6353
this._notifySubscriptionChange(key, true);
6354
}
6355
}
6356
6357
subscribe(key, callback){
6358
if(!this.events[key]){
6359
this.events[key] = [];
6360
}
6361
6362
this.events[key].push(callback);
6363
6364
this._notifySubscriptionChange(key, true);
6365
}
6366
6367
unsubscribe(key, callback){
6368
var index;
6369
6370
if(this.events[key]){
6371
if(callback){
6372
index = this.events[key].findIndex((item) => {
6373
return item === callback;
6374
});
6375
6376
if(index > -1){
6377
this.events[key].splice(index, 1);
6378
}else {
6379
console.warn("Cannot remove event, no matching event found:", key, callback);
6380
return;
6381
}
6382
}else {
6383
delete this.events[key];
6384
}
6385
}else {
6386
console.warn("Cannot remove event, no events set on:", key);
6387
return;
6388
}
6389
6390
this._notifySubscriptionChange(key, false);
6391
}
6392
6393
subscribed(key){
6394
return this.events[key] && this.events[key].length;
6395
}
6396
6397
_notifySubscriptionChange(key, subscribed){
6398
var notifiers = this.subscriptionNotifiers[key];
6399
6400
if(notifiers){
6401
notifiers.forEach((callback)=>{
6402
callback(subscribed);
6403
});
6404
}
6405
}
6406
6407
_dispatch(){
6408
var args = Array.from(arguments),
6409
key = args.shift(),
6410
result;
6411
6412
if(this.events[key]){
6413
this.events[key].forEach((callback, i) => {
6414
let callResult = callback.apply(this.table, args);
6415
6416
if(!i){
6417
result = callResult;
6418
}
6419
});
6420
}
6421
6422
return result;
6423
}
6424
6425
_debugDispatch(){
6426
var args = Array.from(arguments),
6427
key = args[0];
6428
6429
args[0] = "ExternalEvent:" + args[0];
6430
6431
if(this.debug === true || this.debug.includes(key)){
6432
console.log(...args);
6433
}
6434
6435
return this._dispatch(...arguments);
6436
}
6437
}
6438
6439
class InternalEventBus {
6440
6441
constructor(debug){
6442
this.events = {};
6443
this.subscriptionNotifiers = {};
6444
6445
this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this);
6446
this.chain = debug ? this._debugChain.bind(this) : this._chain.bind(this);
6447
this.confirm = debug ? this._debugConfirm.bind(this) : this._confirm.bind(this);
6448
this.debug = debug;
6449
}
6450
6451
subscriptionChange(key, callback){
6452
if(!this.subscriptionNotifiers[key]){
6453
this.subscriptionNotifiers[key] = [];
6454
}
6455
6456
this.subscriptionNotifiers[key].push(callback);
6457
6458
if(this.subscribed(key)){
6459
this._notifySubscriptionChange(key, true);
6460
}
6461
}
6462
6463
subscribe(key, callback, priority = 10000){
6464
if(!this.events[key]){
6465
this.events[key] = [];
6466
}
6467
6468
this.events[key].push({callback, priority});
6469
6470
this.events[key].sort((a, b) => {
6471
return a.priority - b.priority;
6472
});
6473
6474
this._notifySubscriptionChange(key, true);
6475
}
6476
6477
unsubscribe(key, callback){
6478
var index;
6479
6480
if(this.events[key]){
6481
if(callback){
6482
index = this.events[key].findIndex((item) => {
6483
return item.callback === callback;
6484
});
6485
6486
if(index > -1){
6487
this.events[key].splice(index, 1);
6488
}else {
6489
console.warn("Cannot remove event, no matching event found:", key, callback);
6490
return;
6491
}
6492
}
6493
}else {
6494
console.warn("Cannot remove event, no events set on:", key);
6495
return;
6496
}
6497
6498
this._notifySubscriptionChange(key, false);
6499
}
6500
6501
subscribed(key){
6502
return this.events[key] && this.events[key].length;
6503
}
6504
6505
_chain(key, args, initialValue, fallback){
6506
var value = initialValue;
6507
6508
if(!Array.isArray(args)){
6509
args = [args];
6510
}
6511
6512
if(this.subscribed(key)){
6513
this.events[key].forEach((subscriber, i) => {
6514
value = subscriber.callback.apply(this, args.concat([value]));
6515
});
6516
6517
return value;
6518
}else {
6519
return typeof fallback === "function" ? fallback() : fallback;
6520
}
6521
}
6522
6523
_confirm(key, args){
6524
var confirmed = false;
6525
6526
if(!Array.isArray(args)){
6527
args = [args];
6528
}
6529
6530
if(this.subscribed(key)){
6531
this.events[key].forEach((subscriber, i) => {
6532
if(subscriber.callback.apply(this, args)){
6533
confirmed = true;
6534
}
6535
});
6536
}
6537
6538
return confirmed;
6539
}
6540
6541
_notifySubscriptionChange(key, subscribed){
6542
var notifiers = this.subscriptionNotifiers[key];
6543
6544
if(notifiers){
6545
notifiers.forEach((callback)=>{
6546
callback(subscribed);
6547
});
6548
}
6549
}
6550
6551
_dispatch(){
6552
var args = Array.from(arguments),
6553
key = args.shift();
6554
6555
if(this.events[key]){
6556
this.events[key].forEach((subscriber) => {
6557
subscriber.callback.apply(this, args);
6558
});
6559
}
6560
}
6561
6562
_debugDispatch(){
6563
var args = Array.from(arguments),
6564
key = args[0];
6565
6566
args[0] = "InternalEvent:" + key;
6567
6568
if(this.debug === true || this.debug.includes(key)){
6569
console.log(...args);
6570
}
6571
6572
return this._dispatch(...arguments);
6573
}
6574
6575
_debugChain(){
6576
var args = Array.from(arguments),
6577
key = args[0];
6578
6579
args[0] = "InternalEvent:" + key;
6580
6581
if(this.debug === true || this.debug.includes(key)){
6582
console.log(...args);
6583
}
6584
6585
return this._chain(...arguments);
6586
}
6587
6588
_debugConfirm(){
6589
var args = Array.from(arguments),
6590
key = args[0];
6591
6592
args[0] = "InternalEvent:" + key;
6593
6594
if(this.debug === true || this.debug.includes(key)){
6595
console.log(...args);
6596
}
6597
6598
return this._confirm(...arguments);
6599
}
6600
}
6601
6602
class DeprecationAdvisor extends CoreFeature{
6603
6604
constructor(table){
6605
super(table);
6606
}
6607
6608
_warnUser(){
6609
if(this.options("debugDeprecation")){
6610
console.warn(...arguments);
6611
}
6612
}
6613
6614
check(oldOption, newOption){
6615
var msg = "";
6616
6617
if(typeof this.options(oldOption) !== "undefined"){
6618
msg = "Deprecated Setup Option - Use of the %c" + oldOption + "%c option is now deprecated";
6619
6620
if(newOption){
6621
msg = msg + ", Please use the %c" + newOption + "%c option instead";
6622
this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;');
6623
}else {
6624
this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;');
6625
}
6626
6627
return false;
6628
}else {
6629
return true;
6630
}
6631
}
6632
6633
checkMsg(oldOption, msg){
6634
if(typeof this.options(oldOption) !== "undefined"){
6635
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;');
6636
6637
return false;
6638
}else {
6639
return true;
6640
}
6641
}
6642
6643
msg(msg){
6644
this._warnUser(msg);
6645
}
6646
}
6647
6648
class TableRegistry {
6649
6650
static register(table){
6651
TableRegistry.tables.push(table);
6652
}
6653
6654
static deregister(table){
6655
var index = TableRegistry.tables.indexOf(table);
6656
6657
if(index > -1){
6658
TableRegistry.tables.splice(index, 1);
6659
}
6660
}
6661
6662
static lookupTable(query, silent){
6663
var results = [],
6664
matches, match;
6665
6666
if(typeof query === "string"){
6667
matches = document.querySelectorAll(query);
6668
6669
if(matches.length){
6670
for(var i = 0; i < matches.length; i++){
6671
match = TableRegistry.matchElement(matches[i]);
6672
6673
if(match){
6674
results.push(match);
6675
}
6676
}
6677
}
6678
6679
}else if((typeof HTMLElement !== "undefined" && query instanceof HTMLElement) || query instanceof Tabulator){
6680
match = TableRegistry.matchElement(query);
6681
6682
if(match){
6683
results.push(match);
6684
}
6685
}else if(Array.isArray(query)){
6686
query.forEach(function(item){
6687
results = results.concat(TableRegistry.lookupTable(item));
6688
});
6689
}else {
6690
if(!silent){
6691
console.warn("Table Connection Error - Invalid Selector", query);
6692
}
6693
}
6694
6695
return results;
6696
}
6697
6698
static matchElement(element){
6699
return TableRegistry.tables.find(function(table){
6700
return element instanceof Tabulator ? table === element : table.element === element;
6701
});
6702
}
6703
}
6704
6705
TableRegistry.tables = [];
6706
6707
class Popup extends CoreFeature{
6708
constructor(table, element, parent){
6709
super(table);
6710
6711
this.element = element;
6712
this.container = this._lookupContainer();
6713
6714
this.parent = parent;
6715
6716
this.reversedX = false;
6717
this.childPopup = null;
6718
this.blurable = false;
6719
this.blurCallback = null;
6720
this.blurEventsBound = false;
6721
this.renderedCallback = null;
6722
6723
this.visible = false;
6724
this.hideable = true;
6725
6726
this.element.classList.add("tabulator-popup-container");
6727
6728
this.blurEvent = this.hide.bind(this, false);
6729
this.escEvent = this._escapeCheck.bind(this);
6730
6731
this.destroyBinding = this.tableDestroyed.bind(this);
6732
this.destroyed = false;
6733
}
6734
6735
tableDestroyed(){
6736
this.destroyed = true;
6737
this.hide(true);
6738
}
6739
6740
_lookupContainer(){
6741
var container = this.table.options.popupContainer;
6742
6743
if(typeof container === "string"){
6744
container = document.querySelector(container);
6745
6746
if(!container){
6747
console.warn("Menu Error - no container element found matching selector:", this.table.options.popupContainer , "(defaulting to document body)");
6748
}
6749
}else if (container === true){
6750
container = this.table.element;
6751
}
6752
6753
if(container && !this._checkContainerIsParent(container)){
6754
container = false;
6755
console.warn("Menu Error - container element does not contain this table:", this.table.options.popupContainer , "(defaulting to document body)");
6756
}
6757
6758
if(!container){
6759
container = document.body;
6760
}
6761
6762
return container;
6763
}
6764
6765
_checkContainerIsParent(container, element = this.table.element){
6766
if(container === element){
6767
return true;
6768
}else {
6769
return element.parentNode ? this._checkContainerIsParent(container, element.parentNode) : false;
6770
}
6771
}
6772
6773
renderCallback(callback){
6774
this.renderedCallback = callback;
6775
}
6776
6777
containerEventCoords(e){
6778
var touch = !(e instanceof MouseEvent);
6779
6780
var x = touch ? e.touches[0].pageX : e.pageX;
6781
var y = touch ? e.touches[0].pageY : e.pageY;
6782
6783
if(this.container !== document.body){
6784
let parentOffset = Helpers.elOffset(this.container);
6785
6786
x -= parentOffset.left;
6787
y -= parentOffset.top;
6788
}
6789
6790
return {x, y};
6791
}
6792
6793
elementPositionCoords(element, position = "right"){
6794
var offset = Helpers.elOffset(element),
6795
containerOffset, x, y;
6796
6797
if(this.container !== document.body){
6798
containerOffset = Helpers.elOffset(this.container);
6799
6800
offset.left -= containerOffset.left;
6801
offset.top -= containerOffset.top;
6802
}
6803
6804
switch(position){
6805
case "right":
6806
x = offset.left + element.offsetWidth;
6807
y = offset.top - 1;
6808
break;
6809
6810
case "bottom":
6811
x = offset.left;
6812
y = offset.top + element.offsetHeight;
6813
break;
6814
6815
case "left":
6816
x = offset.left;
6817
y = offset.top - 1;
6818
break;
6819
6820
case "top":
6821
x = offset.left;
6822
y = offset.top;
6823
break;
6824
6825
case "center":
6826
x = offset.left + (element.offsetWidth / 2);
6827
y = offset.top + (element.offsetHeight / 2);
6828
break;
6829
6830
}
6831
6832
return {x, y, offset};
6833
}
6834
6835
show(origin, position){
6836
var x, y, parentEl, parentOffset, coords;
6837
6838
if(this.destroyed || this.table.destroyed){
6839
return this;
6840
}
6841
6842
if(origin instanceof HTMLElement){
6843
parentEl = origin;
6844
coords = this.elementPositionCoords(origin, position);
6845
6846
parentOffset = coords.offset;
6847
x = coords.x;
6848
y = coords.y;
6849
6850
}else if(typeof origin === "number"){
6851
parentOffset = {top:0, left:0};
6852
x = origin;
6853
y = position;
6854
}else {
6855
coords = this.containerEventCoords(origin);
6856
6857
x = coords.x;
6858
y = coords.y;
6859
6860
this.reversedX = false;
6861
}
6862
6863
this.element.style.top = y + "px";
6864
this.element.style.left = x + "px";
6865
6866
this.container.appendChild(this.element);
6867
6868
if(typeof this.renderedCallback === "function"){
6869
this.renderedCallback();
6870
}
6871
6872
this._fitToScreen(x, y, parentEl, parentOffset, position);
6873
6874
this.visible = true;
6875
6876
this.subscribe("table-destroy", this.destroyBinding);
6877
6878
this.element.addEventListener("mousedown", (e) => {
6879
e.stopPropagation();
6880
});
6881
6882
return this;
6883
}
6884
6885
_fitToScreen(x, y, parentEl, parentOffset, position){
6886
var scrollTop = this.container === document.body ? document.documentElement.scrollTop : this.container.scrollTop;
6887
6888
//move menu to start on right edge if it is too close to the edge of the screen
6889
if((x + this.element.offsetWidth) >= this.container.offsetWidth || this.reversedX){
6890
this.element.style.left = "";
6891
6892
if(parentEl){
6893
this.element.style.right = (this.container.offsetWidth - parentOffset.left) + "px";
6894
}else {
6895
this.element.style.right = (this.container.offsetWidth - x) + "px";
6896
}
6897
6898
this.reversedX = true;
6899
}
6900
6901
//move menu to start on bottom edge if it is too close to the edge of the screen
6902
if((y + this.element.offsetHeight) > Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0)) {
6903
if(parentEl){
6904
switch(position){
6905
case "bottom":
6906
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1) + "px";
6907
break;
6908
6909
default:
6910
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1) + "px";
6911
}
6912
6913
}else {
6914
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight) + "px";
6915
}
6916
}
6917
}
6918
6919
isVisible(){
6920
return this.visible;
6921
}
6922
6923
hideOnBlur(callback){
6924
this.blurable = true;
6925
6926
if(this.visible){
6927
setTimeout(() => {
6928
if(this.visible){
6929
this.table.rowManager.element.addEventListener("scroll", this.blurEvent);
6930
this.subscribe("cell-editing", this.blurEvent);
6931
document.body.addEventListener("click", this.blurEvent);
6932
document.body.addEventListener("contextmenu", this.blurEvent);
6933
document.body.addEventListener("mousedown", this.blurEvent);
6934
window.addEventListener("resize", this.blurEvent);
6935
document.body.addEventListener("keydown", this.escEvent);
6936
6937
this.blurEventsBound = true;
6938
}
6939
}, 100);
6940
6941
this.blurCallback = callback;
6942
}
6943
6944
return this;
6945
}
6946
6947
_escapeCheck(e){
6948
if(e.keyCode == 27){
6949
this.hide();
6950
}
6951
}
6952
6953
blockHide(){
6954
this.hideable = false;
6955
}
6956
6957
restoreHide(){
6958
this.hideable = true;
6959
}
6960
6961
hide(silent = false){
6962
if(this.visible && this.hideable){
6963
if(this.blurable && this.blurEventsBound){
6964
document.body.removeEventListener("keydown", this.escEvent);
6965
document.body.removeEventListener("click", this.blurEvent);
6966
document.body.removeEventListener("contextmenu", this.blurEvent);
6967
document.body.removeEventListener("mousedown", this.blurEvent);
6968
window.removeEventListener("resize", this.blurEvent);
6969
this.table.rowManager.element.removeEventListener("scroll", this.blurEvent);
6970
this.unsubscribe("cell-editing", this.blurEvent);
6971
6972
this.blurEventsBound = false;
6973
}
6974
6975
if(this.childPopup){
6976
this.childPopup.hide();
6977
}
6978
6979
if(this.parent){
6980
this.parent.childPopup = null;
6981
}
6982
6983
if(this.element.parentNode){
6984
this.element.parentNode.removeChild(this.element);
6985
}
6986
6987
this.visible = false;
6988
6989
if(this.blurCallback && !silent){
6990
this.blurCallback();
6991
}
6992
6993
this.unsubscribe("table-destroy", this.destroyBinding);
6994
}
6995
6996
return this;
6997
}
6998
6999
child(element){
7000
if(this.childPopup){
7001
this.childPopup.hide();
7002
}
7003
7004
this.childPopup = new Popup(this.table, element, this);
7005
7006
return this.childPopup;
7007
}
7008
}
7009
7010
class Module extends CoreFeature{
7011
7012
constructor(table, name){
7013
super(table);
7014
7015
this._handler = null;
7016
}
7017
7018
initialize(){
7019
// setup module when table is initialized, to be overridden in module
7020
}
7021
7022
7023
///////////////////////////////////
7024
////// Options Registration ///////
7025
///////////////////////////////////
7026
7027
registerTableOption(key, value){
7028
this.table.optionsList.register(key, value);
7029
}
7030
7031
registerColumnOption(key, value){
7032
this.table.columnManager.optionsList.register(key, value);
7033
}
7034
7035
///////////////////////////////////
7036
/// Public Function Registration ///
7037
///////////////////////////////////
7038
7039
registerTableFunction(name, func){
7040
if(typeof this.table[name] === "undefined"){
7041
this.table[name] = (...args) => {
7042
this.table.initGuard(name);
7043
7044
return func(...args);
7045
};
7046
}else {
7047
console.warn("Unable to bind table function, name already in use", name);
7048
}
7049
}
7050
7051
registerComponentFunction(component, func, handler){
7052
return this.table.componentFunctionBinder.bind(component, func, handler);
7053
}
7054
7055
///////////////////////////////////
7056
////////// Data Pipeline //////////
7057
///////////////////////////////////
7058
7059
registerDataHandler(handler, priority){
7060
this.table.rowManager.registerDataPipelineHandler(handler, priority);
7061
this._handler = handler;
7062
}
7063
7064
registerDisplayHandler(handler, priority){
7065
this.table.rowManager.registerDisplayPipelineHandler(handler, priority);
7066
this._handler = handler;
7067
}
7068
7069
displayRows(adjust){
7070
var index = this.table.rowManager.displayRows.length - 1,
7071
lookupIndex;
7072
7073
if(this._handler){
7074
lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => {
7075
return item.handler === this._handler;
7076
});
7077
7078
if(lookupIndex > -1){
7079
index = lookupIndex;
7080
}
7081
}
7082
7083
if(adjust){
7084
index = index + adjust;
7085
}
7086
7087
if(this._handler){
7088
if(index > -1){
7089
return this.table.rowManager.getDisplayRows(index);
7090
}else {
7091
return this.activeRows();
7092
}
7093
}
7094
}
7095
7096
activeRows(){
7097
return this.table.rowManager.activeRows;
7098
}
7099
7100
refreshData(renderInPosition, handler){
7101
if(!handler){
7102
handler = this._handler;
7103
}
7104
7105
if(handler){
7106
this.table.rowManager.refreshActiveData(handler, false, renderInPosition);
7107
}
7108
}
7109
7110
///////////////////////////////////
7111
//////// Footer Management ////////
7112
///////////////////////////////////
7113
7114
footerAppend(element){
7115
return this.table.footerManager.append(element);
7116
}
7117
7118
footerPrepend(element){
7119
return this.table.footerManager.prepend(element);
7120
}
7121
7122
footerRemove(element){
7123
return this.table.footerManager.remove(element);
7124
}
7125
7126
///////////////////////////////////
7127
//////// Popups Management ////////
7128
///////////////////////////////////
7129
7130
popup(menuEl, menuContainer){
7131
return new Popup(this.table, menuEl, menuContainer);
7132
}
7133
7134
///////////////////////////////////
7135
//////// Alert Management ////////
7136
///////////////////////////////////
7137
7138
alert(content, type){
7139
return this.table.alertManager.alert(content, type);
7140
}
7141
7142
clearAlert(){
7143
return this.table.alertManager.clear();
7144
}
7145
7146
}
7147
7148
//resize columns to fit data they contain
7149
function fitData(columns, forced){
7150
if(forced){
7151
this.table.columnManager.renderer.reinitializeColumnWidths(columns);
7152
}
7153
7154
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
7155
this.table.modules.responsiveLayout.update();
7156
}
7157
}
7158
7159
//resize columns to fit data they contain and stretch row to fill table, also used for fitDataTable
7160
function fitDataGeneral(columns, forced){
7161
columns.forEach(function(column){
7162
column.reinitializeWidth();
7163
});
7164
7165
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
7166
this.table.modules.responsiveLayout.update();
7167
}
7168
}
7169
7170
//resize columns to fit data the contain and stretch last column to fill table
7171
function fitDataStretch(columns, forced){
7172
var colsWidth = 0,
7173
tableWidth = this.table.rowManager.element.clientWidth,
7174
gap = 0,
7175
lastCol = false;
7176
7177
columns.forEach((column, i) => {
7178
if(!column.widthFixed){
7179
column.reinitializeWidth();
7180
}
7181
7182
if(this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible){
7183
lastCol = column;
7184
}
7185
7186
if(column.visible){
7187
colsWidth += column.getWidth();
7188
}
7189
});
7190
7191
if(lastCol){
7192
gap = tableWidth - colsWidth + lastCol.getWidth();
7193
7194
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
7195
lastCol.setWidth(0);
7196
this.table.modules.responsiveLayout.update();
7197
}
7198
7199
if(gap > 0){
7200
lastCol.setWidth(gap);
7201
}else {
7202
lastCol.reinitializeWidth();
7203
}
7204
}else {
7205
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
7206
this.table.modules.responsiveLayout.update();
7207
}
7208
}
7209
}
7210
7211
//resize columns to fit
7212
function fitColumns(columns, forced){
7213
var totalWidth = this.table.rowManager.element.getBoundingClientRect().width; //table element width
7214
var fixedWidth = 0; //total width of columns with a defined width
7215
var flexWidth = 0; //total width available to flexible columns
7216
var flexGrowUnits = 0; //total number of widthGrow blocks across all columns
7217
var flexColWidth = 0; //desired width of flexible columns
7218
var flexColumns = []; //array of flexible width columns
7219
var fixedShrinkColumns = []; //array of fixed width columns that can shrink
7220
var flexShrinkUnits = 0; //total number of widthShrink blocks across all columns
7221
var overflowWidth = 0; //horizontal overflow width
7222
var gapFill = 0; //number of pixels to be added to final column to close and half pixel gaps
7223
7224
function calcWidth(width){
7225
var colWidth;
7226
7227
if(typeof(width) == "string"){
7228
if(width.indexOf("%") > -1){
7229
colWidth = (totalWidth / 100) * parseInt(width);
7230
}else {
7231
colWidth = parseInt(width);
7232
}
7233
}else {
7234
colWidth = width;
7235
}
7236
7237
return colWidth;
7238
}
7239
7240
//ensure columns resize to take up the correct amount of space
7241
function scaleColumns(columns, freeSpace, colWidth, shrinkCols){
7242
var oversizeCols = [],
7243
oversizeSpace = 0,
7244
remainingSpace = 0,
7245
nextColWidth = 0,
7246
remainingFlexGrowUnits = flexGrowUnits,
7247
gap = 0,
7248
changeUnits = 0,
7249
undersizeCols = [];
7250
7251
function calcGrow(col){
7252
return (colWidth * (col.column.definition.widthGrow || 1));
7253
}
7254
7255
function calcShrink(col){
7256
return (calcWidth(col.width) - (colWidth * (col.column.definition.widthShrink || 0)));
7257
}
7258
7259
columns.forEach(function(col, i){
7260
var width = shrinkCols ? calcShrink(col) : calcGrow(col);
7261
if(col.column.minWidth >= width){
7262
oversizeCols.push(col);
7263
}else {
7264
if(col.column.maxWidth && col.column.maxWidth < width){
7265
col.width = col.column.maxWidth;
7266
freeSpace -= col.column.maxWidth;
7267
7268
remainingFlexGrowUnits -= shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1);
7269
7270
if(remainingFlexGrowUnits){
7271
colWidth = Math.floor(freeSpace/remainingFlexGrowUnits);
7272
}
7273
}else {
7274
undersizeCols.push(col);
7275
changeUnits += shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1);
7276
}
7277
}
7278
});
7279
7280
if(oversizeCols.length){
7281
oversizeCols.forEach(function(col){
7282
oversizeSpace += shrinkCols ? col.width - col.column.minWidth : col.column.minWidth;
7283
col.width = col.column.minWidth;
7284
});
7285
7286
remainingSpace = freeSpace - oversizeSpace;
7287
7288
nextColWidth = changeUnits ? Math.floor(remainingSpace/changeUnits) : remainingSpace;
7289
7290
gap = scaleColumns(undersizeCols, remainingSpace, nextColWidth, shrinkCols);
7291
}else {
7292
gap = changeUnits ? freeSpace - (Math.floor(freeSpace/changeUnits) * changeUnits) : freeSpace;
7293
7294
undersizeCols.forEach(function(column){
7295
column.width = shrinkCols ? calcShrink(column) : calcGrow(column);
7296
});
7297
}
7298
7299
return gap;
7300
}
7301
7302
if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){
7303
this.table.modules.responsiveLayout.update();
7304
}
7305
7306
//adjust for vertical scrollbar if present
7307
if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){
7308
totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth;
7309
}
7310
7311
columns.forEach(function(column){
7312
var width, minWidth, colWidth;
7313
7314
if(column.visible){
7315
7316
width = column.definition.width;
7317
minWidth = parseInt(column.minWidth);
7318
7319
if(width){
7320
7321
colWidth = calcWidth(width);
7322
7323
fixedWidth += colWidth > minWidth ? colWidth : minWidth;
7324
7325
if(column.definition.widthShrink){
7326
fixedShrinkColumns.push({
7327
column:column,
7328
width:colWidth > minWidth ? colWidth : minWidth
7329
});
7330
flexShrinkUnits += column.definition.widthShrink;
7331
}
7332
7333
}else {
7334
flexColumns.push({
7335
column:column,
7336
width:0,
7337
});
7338
flexGrowUnits += column.definition.widthGrow || 1;
7339
}
7340
}
7341
});
7342
7343
//calculate available space
7344
flexWidth = totalWidth - fixedWidth;
7345
7346
//calculate correct column size
7347
flexColWidth = Math.floor(flexWidth / flexGrowUnits);
7348
7349
//generate column widths
7350
gapFill = scaleColumns(flexColumns, flexWidth, flexColWidth, false);
7351
7352
//increase width of last column to account for rounding errors
7353
if(flexColumns.length && gapFill > 0){
7354
flexColumns[flexColumns.length-1].width += gapFill;
7355
}
7356
7357
//calculate space for columns to be shrunk into
7358
flexColumns.forEach(function(col){
7359
flexWidth -= col.width;
7360
});
7361
7362
overflowWidth = Math.abs(gapFill) + flexWidth;
7363
7364
//shrink oversize columns if there is no available space
7365
if(overflowWidth > 0 && flexShrinkUnits){
7366
gapFill = scaleColumns(fixedShrinkColumns, overflowWidth, Math.floor(overflowWidth / flexShrinkUnits), true);
7367
}
7368
7369
//decrease width of last column to account for rounding errors
7370
if(gapFill && fixedShrinkColumns.length){
7371
fixedShrinkColumns[fixedShrinkColumns.length-1].width -= gapFill;
7372
}
7373
7374
flexColumns.forEach(function(col){
7375
col.column.setWidth(col.width);
7376
});
7377
7378
fixedShrinkColumns.forEach(function(col){
7379
col.column.setWidth(col.width);
7380
});
7381
}
7382
7383
var defaultModes = {
7384
fitData:fitData,
7385
fitDataFill:fitDataGeneral,
7386
fitDataTable:fitDataGeneral,
7387
fitDataStretch:fitDataStretch,
7388
fitColumns:fitColumns ,
7389
};
7390
7391
class Layout extends Module{
7392
7393
constructor(table){
7394
super(table, "layout");
7395
7396
this.mode = null;
7397
7398
this.registerTableOption("layout", "fitData"); //layout type
7399
this.registerTableOption("layoutColumnsOnNewData", false); //update column widths on setData
7400
7401
this.registerColumnOption("widthGrow");
7402
this.registerColumnOption("widthShrink");
7403
}
7404
7405
//initialize layout system
7406
initialize(){
7407
var layout = this.table.options.layout;
7408
7409
if(Layout.modes[layout]){
7410
this.mode = layout;
7411
}else {
7412
console.warn("Layout Error - invalid mode set, defaulting to 'fitData' : " + layout);
7413
this.mode = 'fitData';
7414
}
7415
7416
this.table.element.setAttribute("tabulator-layout", this.mode);
7417
this.subscribe("column-init", this.initializeColumn.bind(this));
7418
}
7419
7420
initializeColumn(column){
7421
if(column.definition.widthGrow){
7422
column.definition.widthGrow = Number(column.definition.widthGrow);
7423
}
7424
if(column.definition.widthShrink){
7425
column.definition.widthShrink = Number(column.definition.widthShrink);
7426
}
7427
}
7428
7429
getMode(){
7430
return this.mode;
7431
}
7432
7433
//trigger table layout
7434
layout(dataChanged){
7435
this.dispatch("layout-refreshing");
7436
Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged);
7437
this.dispatch("layout-refreshed");
7438
}
7439
}
7440
7441
Layout.moduleName = "layout";
7442
7443
//load defaults
7444
Layout.modes = defaultModes;
7445
7446
var defaultLangs = {
7447
"default":{ //hold default locale text
7448
"groups":{
7449
"item":"item",
7450
"items":"items",
7451
},
7452
"columns":{
7453
},
7454
"data":{
7455
"loading":"Loading",
7456
"error":"Error",
7457
},
7458
"pagination":{
7459
"page_size":"Page Size",
7460
"page_title":"Show Page",
7461
"first":"First",
7462
"first_title":"First Page",
7463
"last":"Last",
7464
"last_title":"Last Page",
7465
"prev":"Prev",
7466
"prev_title":"Prev Page",
7467
"next":"Next",
7468
"next_title":"Next Page",
7469
"all":"All",
7470
"counter":{
7471
"showing": "Showing",
7472
"of": "of",
7473
"rows": "rows",
7474
"pages": "pages",
7475
}
7476
},
7477
"headerFilters":{
7478
"default":"filter column...",
7479
"columns":{}
7480
}
7481
},
7482
};
7483
7484
class Localize extends Module{
7485
7486
constructor(table){
7487
super(table);
7488
7489
this.locale = "default"; //current locale
7490
this.lang = false; //current language
7491
this.bindings = {}; //update events to call when locale is changed
7492
this.langList = {};
7493
7494
this.registerTableOption("locale", false); //current system language
7495
this.registerTableOption("langs", {});
7496
}
7497
7498
initialize(){
7499
this.langList = Helpers.deepClone(Localize.langs);
7500
7501
if(this.table.options.columnDefaults.headerFilterPlaceholder !== false){
7502
this.setHeaderFilterPlaceholder(this.table.options.columnDefaults.headerFilterPlaceholder);
7503
}
7504
7505
for(let locale in this.table.options.langs){
7506
this.installLang(locale, this.table.options.langs[locale]);
7507
}
7508
7509
this.setLocale(this.table.options.locale);
7510
7511
this.registerTableFunction("setLocale", this.setLocale.bind(this));
7512
this.registerTableFunction("getLocale", this.getLocale.bind(this));
7513
this.registerTableFunction("getLang", this.getLang.bind(this));
7514
}
7515
7516
//set header placeholder
7517
setHeaderFilterPlaceholder(placeholder){
7518
this.langList.default.headerFilters.default = placeholder;
7519
}
7520
7521
//setup a lang description object
7522
installLang(locale, lang){
7523
if(this.langList[locale]){
7524
this._setLangProp(this.langList[locale], lang);
7525
}else {
7526
this.langList[locale] = lang;
7527
}
7528
}
7529
7530
_setLangProp(lang, values){
7531
for(let key in values){
7532
if(lang[key] && typeof lang[key] == "object"){
7533
this._setLangProp(lang[key], values[key]);
7534
}else {
7535
lang[key] = values[key];
7536
}
7537
}
7538
}
7539
7540
//set current locale
7541
setLocale(desiredLocale){
7542
desiredLocale = desiredLocale || "default";
7543
7544
//fill in any matching language values
7545
function traverseLang(trans, path){
7546
for(var prop in trans){
7547
if(typeof trans[prop] == "object"){
7548
if(!path[prop]){
7549
path[prop] = {};
7550
}
7551
traverseLang(trans[prop], path[prop]);
7552
}else {
7553
path[prop] = trans[prop];
7554
}
7555
}
7556
}
7557
7558
//determining correct locale to load
7559
if(desiredLocale === true && navigator.language){
7560
//get local from system
7561
desiredLocale = navigator.language.toLowerCase();
7562
}
7563
7564
if(desiredLocale){
7565
//if locale is not set, check for matching top level locale else use default
7566
if(!this.langList[desiredLocale]){
7567
let prefix = desiredLocale.split("-")[0];
7568
7569
if(this.langList[prefix]){
7570
console.warn("Localization Error - Exact matching locale not found, using closest match: ", desiredLocale, prefix);
7571
desiredLocale = prefix;
7572
}else {
7573
console.warn("Localization Error - Matching locale not found, using default: ", desiredLocale);
7574
desiredLocale = "default";
7575
}
7576
}
7577
}
7578
7579
this.locale = desiredLocale;
7580
7581
//load default lang template
7582
this.lang = Helpers.deepClone(this.langList.default || {});
7583
7584
if(desiredLocale != "default"){
7585
traverseLang(this.langList[desiredLocale], this.lang);
7586
}
7587
7588
this.dispatchExternal("localized", this.locale, this.lang);
7589
7590
this._executeBindings();
7591
}
7592
7593
//get current locale
7594
getLocale(locale){
7595
return this.locale;
7596
}
7597
7598
//get lang object for given local or current if none provided
7599
getLang(locale){
7600
return locale ? this.langList[locale] : this.lang;
7601
}
7602
7603
//get text for current locale
7604
getText(path, value){
7605
var fillPath = value ? path + "|" + value : path,
7606
pathArray = fillPath.split("|"),
7607
text = this._getLangElement(pathArray, this.locale);
7608
7609
// if(text === false){
7610
// console.warn("Localization Error - Matching localized text not found for given path: ", path);
7611
// }
7612
7613
return text || "";
7614
}
7615
7616
//traverse langs object and find localized copy
7617
_getLangElement(path, locale){
7618
var root = this.lang;
7619
7620
path.forEach(function(level){
7621
var rootPath;
7622
7623
if(root){
7624
rootPath = root[level];
7625
7626
if(typeof rootPath != "undefined"){
7627
root = rootPath;
7628
}else {
7629
root = false;
7630
}
7631
}
7632
});
7633
7634
return root;
7635
}
7636
7637
//set update binding
7638
bind(path, callback){
7639
if(!this.bindings[path]){
7640
this.bindings[path] = [];
7641
}
7642
7643
this.bindings[path].push(callback);
7644
7645
callback(this.getText(path), this.lang);
7646
}
7647
7648
//iterate through bindings and trigger updates
7649
_executeBindings(){
7650
for(let path in this.bindings){
7651
this.bindings[path].forEach((binding) => {
7652
binding(this.getText(path), this.lang);
7653
});
7654
}
7655
}
7656
}
7657
7658
Localize.moduleName = "localize";
7659
7660
//load defaults
7661
Localize.langs = defaultLangs;
7662
7663
class Comms extends Module{
7664
7665
constructor(table){
7666
super(table);
7667
}
7668
7669
initialize(){
7670
this.registerTableFunction("tableComms", this.receive.bind(this));
7671
}
7672
7673
getConnections(selectors){
7674
var connections = [],
7675
connection;
7676
7677
connection = TableRegistry.lookupTable(selectors);
7678
7679
connection.forEach((con) =>{
7680
if(this.table !== con){
7681
connections.push(con);
7682
}
7683
});
7684
7685
return connections;
7686
}
7687
7688
send(selectors, module, action, data){
7689
var connections = this.getConnections(selectors);
7690
7691
connections.forEach((connection) => {
7692
connection.tableComms(this.table.element, module, action, data);
7693
});
7694
7695
if(!connections.length && selectors){
7696
console.warn("Table Connection Error - No tables matching selector found", selectors);
7697
}
7698
}
7699
7700
receive(table, module, action, data){
7701
if(this.table.modExists(module)){
7702
return this.table.modules[module].commsReceived(table, action, data);
7703
}else {
7704
console.warn("Inter-table Comms Error - no such module:", module);
7705
}
7706
}
7707
}
7708
7709
Comms.moduleName = "comms";
7710
7711
var coreModules = /*#__PURE__*/Object.freeze({
7712
__proto__: null,
7713
LayoutModule: Layout,
7714
LocalizeModule: Localize,
7715
CommsModule: Comms
7716
});
7717
7718
class ModuleBinder {
7719
7720
constructor(tabulator, modules){
7721
this.bindStaticFunctionality(tabulator);
7722
this.bindModules(tabulator, coreModules, true);
7723
7724
if(modules){
7725
this.bindModules(tabulator, modules);
7726
}
7727
}
7728
7729
bindStaticFunctionality(tabulator){
7730
tabulator.moduleBindings = {};
7731
7732
tabulator.extendModule = function(name, property, values){
7733
if(tabulator.moduleBindings[name]){
7734
var source = tabulator.moduleBindings[name][property];
7735
7736
if(source){
7737
if(typeof values == "object"){
7738
for(let key in values){
7739
source[key] = values[key];
7740
}
7741
}else {
7742
console.warn("Module Error - Invalid value type, it must be an object");
7743
}
7744
}else {
7745
console.warn("Module Error - property does not exist:", property);
7746
}
7747
}else {
7748
console.warn("Module Error - module does not exist:", name);
7749
}
7750
};
7751
7752
tabulator.registerModule = function(modules){
7753
if(!Array.isArray(modules)){
7754
modules = [modules];
7755
}
7756
7757
modules.forEach((mod) => {
7758
tabulator.registerModuleBinding(mod);
7759
});
7760
};
7761
7762
tabulator.registerModuleBinding = function(mod){
7763
tabulator.moduleBindings[mod.moduleName] = mod;
7764
};
7765
7766
tabulator.findTable = function(query){
7767
var results = TableRegistry.lookupTable(query, true);
7768
return Array.isArray(results) && !results.length ? false : results;
7769
};
7770
7771
//ensure that module are bound to instantiated function
7772
tabulator.prototype.bindModules = function(){
7773
var orderedStartMods = [],
7774
orderedEndMods = [],
7775
unOrderedMods = [];
7776
7777
this.modules = {};
7778
7779
for(var name in tabulator.moduleBindings){
7780
let mod = tabulator.moduleBindings[name];
7781
let module = new mod(this);
7782
7783
this.modules[name] = module;
7784
7785
if(mod.prototype.moduleCore){
7786
this.modulesCore.push(module);
7787
}else {
7788
if(mod.moduleInitOrder){
7789
if(mod.moduleInitOrder < 0){
7790
orderedStartMods.push(module);
7791
}else {
7792
orderedEndMods.push(module);
7793
}
7794
7795
}else {
7796
unOrderedMods.push(module);
7797
}
7798
}
7799
}
7800
7801
orderedStartMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1);
7802
orderedEndMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1);
7803
7804
this.modulesRegular = orderedStartMods.concat(unOrderedMods.concat(orderedEndMods));
7805
};
7806
}
7807
7808
bindModules(tabulator, modules, core){
7809
var mods = Object.values(modules);
7810
7811
if(core){
7812
mods.forEach((mod) => {
7813
mod.prototype.moduleCore = true;
7814
});
7815
}
7816
7817
tabulator.registerModule(mods);
7818
}
7819
}
7820
7821
class Alert extends CoreFeature{
7822
constructor(table){
7823
super(table);
7824
7825
this.element = this._createAlertElement();
7826
this.msgElement = this._createMsgElement();
7827
this.type = null;
7828
7829
this.element.appendChild(this.msgElement);
7830
}
7831
7832
_createAlertElement(){
7833
var el = document.createElement("div");
7834
el.classList.add("tabulator-alert");
7835
return el;
7836
}
7837
7838
_createMsgElement(){
7839
var el = document.createElement("div");
7840
el.classList.add("tabulator-alert-msg");
7841
el.setAttribute("role", "alert");
7842
return el;
7843
}
7844
7845
_typeClass(){
7846
return "tabulator-alert-state-" + this.type;
7847
}
7848
7849
alert(content, type = "msg"){
7850
if(content){
7851
this.clear();
7852
7853
this.type = type;
7854
7855
while(this.msgElement.firstChild) this.msgElement.removeChild(this.msgElement.firstChild);
7856
7857
this.msgElement.classList.add(this._typeClass());
7858
7859
if(typeof content === "function"){
7860
content = content();
7861
}
7862
7863
if(content instanceof HTMLElement){
7864
this.msgElement.appendChild(content);
7865
}else {
7866
this.msgElement.innerHTML = content;
7867
}
7868
7869
this.table.element.appendChild(this.element);
7870
}
7871
}
7872
7873
clear(){
7874
if(this.element.parentNode){
7875
this.element.parentNode.removeChild(this.element);
7876
}
7877
7878
this.msgElement.classList.remove(this._typeClass());
7879
}
7880
}
7881
7882
class Tabulator {
7883
7884
constructor(element, options){
7885
7886
this.options = {};
7887
7888
this.columnManager = null; // hold Column Manager
7889
this.rowManager = null; //hold Row Manager
7890
this.footerManager = null; //holder Footer Manager
7891
this.alertManager = null; //hold Alert Manager
7892
this.vdomHoz = null; //holder horizontal virtual dom
7893
this.externalEvents = null; //handle external event messaging
7894
this.eventBus = null; //handle internal event messaging
7895
this.interactionMonitor = false; //track user interaction
7896
this.browser = ""; //hold current browser type
7897
this.browserSlow = false; //handle reduced functionality for slower browsers
7898
this.browserMobile = false; //check if running on mobile, prevent resize cancelling edit on keyboard appearance
7899
this.rtl = false; //check if the table is in RTL mode
7900
this.originalElement = null; //hold original table element if it has been replaced
7901
7902
this.componentFunctionBinder = new ComponentFunctionBinder(this); //bind component functions
7903
this.dataLoader = false; //bind component functions
7904
7905
this.modules = {}; //hold all modules bound to this table
7906
this.modulesCore = []; //hold core modules bound to this table (for initialization purposes)
7907
this.modulesRegular = []; //hold regular modules bound to this table (for initialization purposes)
7908
7909
this.deprecationAdvisor = new DeprecationAdvisor(this);
7910
this.optionsList = new OptionsList(this, "table constructor");
7911
7912
this.initialized = false;
7913
this.destroyed = false;
7914
7915
if(this.initializeElement(element)){
7916
7917
this.initializeCoreSystems(options);
7918
7919
//delay table creation to allow event bindings immediately after the constructor
7920
setTimeout(() => {
7921
this._create();
7922
});
7923
}
7924
7925
TableRegistry.register(this); //register table for inter-device communication
7926
}
7927
7928
initializeElement(element){
7929
if(typeof HTMLElement !== "undefined" && element instanceof HTMLElement){
7930
this.element = element;
7931
return true;
7932
}else if(typeof element === "string"){
7933
this.element = document.querySelector(element);
7934
7935
if(this.element){
7936
return true;
7937
}else {
7938
console.error("Tabulator Creation Error - no element found matching selector: ", element);
7939
return false;
7940
}
7941
}else {
7942
console.error("Tabulator Creation Error - Invalid element provided:", element);
7943
return false;
7944
}
7945
}
7946
7947
initializeCoreSystems(options){
7948
this.columnManager = new ColumnManager(this);
7949
this.rowManager = new RowManager(this);
7950
this.footerManager = new FooterManager(this);
7951
this.dataLoader = new DataLoader(this);
7952
this.alertManager = new Alert(this);
7953
7954
this.bindModules();
7955
7956
this.options = this.optionsList.generate(Tabulator.defaultOptions, options);
7957
7958
this._clearObjectPointers();
7959
7960
this._mapDeprecatedFunctionality();
7961
7962
this.externalEvents = new ExternalEventBus(this, this.options, this.options.debugEventsExternal);
7963
this.eventBus = new InternalEventBus(this.options.debugEventsInternal);
7964
7965
this.interactionMonitor = new InteractionManager(this);
7966
7967
this.dataLoader.initialize();
7968
// this.columnManager.initialize();
7969
// this.rowManager.initialize();
7970
this.footerManager.initialize();
7971
}
7972
7973
//convert deprecated functionality to new functions
7974
_mapDeprecatedFunctionality(){
7975
//all previously deprecated functionality removed in the 5.0 release
7976
}
7977
7978
_clearSelection(){
7979
7980
this.element.classList.add("tabulator-block-select");
7981
7982
if (window.getSelection) {
7983
if (window.getSelection().empty) { // Chrome
7984
window.getSelection().empty();
7985
} else if (window.getSelection().removeAllRanges) { // Firefox
7986
window.getSelection().removeAllRanges();
7987
}
7988
} else if (document.selection) { // IE?
7989
document.selection.empty();
7990
}
7991
7992
this.element.classList.remove("tabulator-block-select");
7993
}
7994
7995
//create table
7996
_create(){
7997
this.externalEvents.dispatch("tableBuilding");
7998
this.eventBus.dispatch("table-building");
7999
8000
this._rtlCheck();
8001
8002
this._buildElement();
8003
8004
this._initializeTable();
8005
8006
this._loadInitialData();
8007
8008
this.initialized = true;
8009
8010
this.externalEvents.dispatch("tableBuilt");
8011
}
8012
8013
_rtlCheck(){
8014
var style = window.getComputedStyle(this.element);
8015
8016
switch(this.options.textDirection){
8017
case"auto":
8018
if(style.direction !== "rtl"){
8019
break;
8020
}
8021
8022
case "rtl":
8023
this.element.classList.add("tabulator-rtl");
8024
this.rtl = true;
8025
break;
8026
8027
case "ltr":
8028
this.element.classList.add("tabulator-ltr");
8029
8030
default:
8031
this.rtl = false;
8032
}
8033
}
8034
8035
//clear pointers to objects in default config object
8036
_clearObjectPointers(){
8037
this.options.columns = this.options.columns.slice(0);
8038
8039
if(Array.isArray(this.options.data) && !this.options.reactiveData){
8040
this.options.data = this.options.data.slice(0);
8041
}
8042
}
8043
8044
//build tabulator element
8045
_buildElement(){
8046
var element = this.element,
8047
options = this.options,
8048
newElement;
8049
8050
if(element.tagName === "TABLE"){
8051
this.originalElement = this.element;
8052
newElement = document.createElement("div");
8053
8054
//transfer attributes to new element
8055
var attributes = element.attributes;
8056
8057
// loop through attributes and apply them on div
8058
for(var i in attributes){
8059
if(typeof attributes[i] == "object"){
8060
newElement.setAttribute(attributes[i].name, attributes[i].value);
8061
}
8062
}
8063
8064
// replace table with div element
8065
element.parentNode.replaceChild(newElement, element);
8066
8067
this.element = element = newElement;
8068
}
8069
8070
element.classList.add("tabulator");
8071
element.setAttribute("role", "grid");
8072
8073
//empty element
8074
while(element.firstChild) element.removeChild(element.firstChild);
8075
8076
//set table height
8077
if(options.height){
8078
options.height = isNaN(options.height) ? options.height : options.height + "px";
8079
element.style.height = options.height;
8080
}
8081
8082
//set table min height
8083
if(options.minHeight !== false){
8084
options.minHeight = isNaN(options.minHeight) ? options.minHeight : options.minHeight + "px";
8085
element.style.minHeight = options.minHeight;
8086
}
8087
8088
//set table maxHeight
8089
if(options.maxHeight !== false){
8090
options.maxHeight = isNaN(options.maxHeight) ? options.maxHeight : options.maxHeight + "px";
8091
element.style.maxHeight = options.maxHeight;
8092
}
8093
}
8094
8095
//initialize core systems and modules
8096
_initializeTable(){
8097
var element = this.element,
8098
options = this.options;
8099
8100
this.interactionMonitor.initialize();
8101
8102
this.columnManager.initialize();
8103
this.rowManager.initialize();
8104
8105
this._detectBrowser();
8106
8107
//initialize core modules
8108
this.modulesCore.forEach((mod) => {
8109
mod.initialize();
8110
});
8111
8112
//build table elements
8113
element.appendChild(this.columnManager.getElement());
8114
element.appendChild(this.rowManager.getElement());
8115
8116
if(options.footerElement){
8117
this.footerManager.activate();
8118
}
8119
8120
if(options.autoColumns && options.data){
8121
8122
this.columnManager.generateColumnsFromRowData(this.options.data);
8123
}
8124
8125
//initialize regular modules
8126
this.modulesRegular.forEach((mod) => {
8127
mod.initialize();
8128
});
8129
8130
this.columnManager.setColumns(options.columns);
8131
8132
this.eventBus.dispatch("table-built");
8133
}
8134
8135
_loadInitialData(){
8136
this.dataLoader.load(this.options.data);
8137
}
8138
8139
//deconstructor
8140
destroy(){
8141
var element = this.element;
8142
8143
this.destroyed = true;
8144
8145
TableRegistry.deregister(this); //deregister table from inter-device communication
8146
8147
this.eventBus.dispatch("table-destroy");
8148
8149
//clear row data
8150
this.rowManager.destroy();
8151
8152
//clear DOM
8153
while(element.firstChild) element.removeChild(element.firstChild);
8154
element.classList.remove("tabulator");
8155
8156
this.externalEvents.dispatch("tableDestroyed");
8157
}
8158
8159
_detectBrowser(){
8160
var ua = navigator.userAgent||navigator.vendor||window.opera;
8161
8162
if(ua.indexOf("Trident") > -1){
8163
this.browser = "ie";
8164
this.browserSlow = true;
8165
}else if(ua.indexOf("Edge") > -1){
8166
this.browser = "edge";
8167
this.browserSlow = true;
8168
}else if(ua.indexOf("Firefox") > -1){
8169
this.browser = "firefox";
8170
this.browserSlow = false;
8171
}else if(ua.indexOf("Mac OS") > -1){
8172
this.browser = "safari";
8173
this.browserSlow = false;
8174
}else {
8175
this.browser = "other";
8176
this.browserSlow = false;
8177
}
8178
8179
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));
8180
}
8181
8182
initGuard(func, msg){
8183
var stack, line;
8184
8185
if(this.options.debugInitialization && !this.initialized){
8186
if(!func){
8187
stack = new Error().stack.split("\n");
8188
8189
line = stack[0] == "Error" ? stack[2] : stack[1];
8190
8191
if(line[0] == " "){
8192
func = line.trim().split(" ")[1].split(".")[1];
8193
}else {
8194
func = line.trim().split("@")[0];
8195
}
8196
}
8197
8198
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 : ""));
8199
}
8200
8201
return this.initialized;
8202
}
8203
8204
////////////////// Data Handling //////////////////
8205
//block table redrawing
8206
blockRedraw(){
8207
this.initGuard();
8208
8209
this.eventBus.dispatch("redraw-blocking");
8210
8211
this.rowManager.blockRedraw();
8212
this.columnManager.blockRedraw();
8213
8214
this.eventBus.dispatch("redraw-blocked");
8215
}
8216
8217
//restore table redrawing
8218
restoreRedraw(){
8219
this.initGuard();
8220
8221
this.eventBus.dispatch("redraw-restoring");
8222
8223
this.rowManager.restoreRedraw();
8224
this.columnManager.restoreRedraw();
8225
8226
this.eventBus.dispatch("redraw-restored");
8227
}
8228
8229
//load data
8230
setData(data, params, config){
8231
this.initGuard(false, "To set initial data please use the 'data' property in the table constructor.");
8232
8233
return this.dataLoader.load(data, params, config, false);
8234
}
8235
8236
//clear data
8237
clearData(){
8238
this.initGuard();
8239
8240
this.dataLoader.blockActiveLoad();
8241
this.rowManager.clearData();
8242
}
8243
8244
//get table data array
8245
getData(active){
8246
return this.rowManager.getData(active);
8247
}
8248
8249
//get table data array count
8250
getDataCount(active){
8251
return this.rowManager.getDataCount(active);
8252
}
8253
8254
//replace data, keeping table in position with same sort
8255
replaceData(data, params, config){
8256
this.initGuard();
8257
8258
return this.dataLoader.load(data, params, config, true, true);
8259
}
8260
8261
//update table data
8262
updateData(data){
8263
var responses = 0;
8264
8265
this.initGuard();
8266
8267
return new Promise((resolve, reject) => {
8268
this.dataLoader.blockActiveLoad();
8269
8270
if(typeof data === "string"){
8271
data = JSON.parse(data);
8272
}
8273
8274
if(data && data.length > 0){
8275
data.forEach((item) => {
8276
var row = this.rowManager.findRow(item[this.options.index]);
8277
8278
if(row){
8279
responses++;
8280
8281
row.updateData(item)
8282
.then(()=>{
8283
responses--;
8284
8285
if(!responses){
8286
resolve();
8287
}
8288
})
8289
.catch((e) => {
8290
reject("Update Error - Unable to update row", item, e);
8291
});
8292
}else {
8293
reject("Update Error - Unable to find row", item);
8294
}
8295
});
8296
}else {
8297
console.warn("Update Error - No data provided");
8298
reject("Update Error - No data provided");
8299
}
8300
});
8301
}
8302
8303
addData(data, pos, index){
8304
this.initGuard();
8305
8306
return new Promise((resolve, reject) => {
8307
this.dataLoader.blockActiveLoad();
8308
8309
if(typeof data === "string"){
8310
data = JSON.parse(data);
8311
}
8312
8313
if(data){
8314
this.rowManager.addRows(data, pos, index)
8315
.then((rows) => {
8316
var output = [];
8317
8318
rows.forEach(function(row){
8319
output.push(row.getComponent());
8320
});
8321
8322
resolve(output);
8323
});
8324
}else {
8325
console.warn("Update Error - No data provided");
8326
reject("Update Error - No data provided");
8327
}
8328
});
8329
}
8330
8331
//update table data
8332
updateOrAddData(data){
8333
var rows = [],
8334
responses = 0;
8335
8336
this.initGuard();
8337
8338
return new Promise((resolve, reject) => {
8339
this.dataLoader.blockActiveLoad();
8340
8341
if(typeof data === "string"){
8342
data = JSON.parse(data);
8343
}
8344
8345
if(data && data.length > 0){
8346
data.forEach((item) => {
8347
var row = this.rowManager.findRow(item[this.options.index]);
8348
8349
responses++;
8350
8351
if(row){
8352
row.updateData(item)
8353
.then(()=>{
8354
responses--;
8355
rows.push(row.getComponent());
8356
8357
if(!responses){
8358
resolve(rows);
8359
}
8360
});
8361
}else {
8362
this.rowManager.addRows(item)
8363
.then((newRows)=>{
8364
responses--;
8365
rows.push(newRows[0].getComponent());
8366
8367
if(!responses){
8368
resolve(rows);
8369
}
8370
});
8371
}
8372
});
8373
}else {
8374
console.warn("Update Error - No data provided");
8375
reject("Update Error - No data provided");
8376
}
8377
});
8378
}
8379
8380
//get row object
8381
getRow(index){
8382
var row = this.rowManager.findRow(index);
8383
8384
if(row){
8385
return row.getComponent();
8386
}else {
8387
console.warn("Find Error - No matching row found:", index);
8388
return false;
8389
}
8390
}
8391
8392
//get row object
8393
getRowFromPosition(position){
8394
var row = this.rowManager.getRowFromPosition(position);
8395
8396
if(row){
8397
return row.getComponent();
8398
}else {
8399
console.warn("Find Error - No matching row found:", position);
8400
return false;
8401
}
8402
}
8403
8404
//delete row from table
8405
deleteRow(index){
8406
var foundRows = [];
8407
8408
this.initGuard();
8409
8410
if(!Array.isArray(index)){
8411
index = [index];
8412
}
8413
8414
//find matching rows
8415
for(let item of index){
8416
let row = this.rowManager.findRow(item, true);
8417
8418
if(row){
8419
foundRows.push(row);
8420
}else {
8421
console.error("Delete Error - No matching row found:", item);
8422
return Promise.reject("Delete Error - No matching row found");
8423
}
8424
}
8425
8426
//sort rows into correct order to ensure smooth delete from table
8427
foundRows.sort((a, b) => {
8428
return this.rowManager.rows.indexOf(a) > this.rowManager.rows.indexOf(b) ? 1 : -1;
8429
});
8430
8431
//delete rows
8432
foundRows.forEach((row) =>{
8433
row.delete();
8434
});
8435
8436
this.rowManager.reRenderInPosition();
8437
8438
return Promise.resolve();
8439
}
8440
8441
//add row to table
8442
addRow(data, pos, index){
8443
this.initGuard();
8444
8445
if(typeof data === "string"){
8446
data = JSON.parse(data);
8447
}
8448
8449
return this.rowManager.addRows(data, pos, index, true)
8450
.then((rows)=>{
8451
return rows[0].getComponent();
8452
});
8453
}
8454
8455
//update a row if it exists otherwise create it
8456
updateOrAddRow(index, data){
8457
var row = this.rowManager.findRow(index);
8458
8459
this.initGuard();
8460
8461
if(typeof data === "string"){
8462
data = JSON.parse(data);
8463
}
8464
8465
if(row){
8466
return row.updateData(data)
8467
.then(()=>{
8468
return row.getComponent();
8469
});
8470
}else {
8471
return this.rowManager.addRows(data)
8472
.then((rows)=>{
8473
return rows[0].getComponent();
8474
});
8475
}
8476
}
8477
8478
//update row data
8479
updateRow(index, data){
8480
var row = this.rowManager.findRow(index);
8481
8482
this.initGuard();
8483
8484
if(typeof data === "string"){
8485
data = JSON.parse(data);
8486
}
8487
8488
if(row){
8489
return row.updateData(data)
8490
.then(()=>{
8491
return Promise.resolve(row.getComponent());
8492
});
8493
}else {
8494
console.warn("Update Error - No matching row found:", index);
8495
return Promise.reject("Update Error - No matching row found");
8496
}
8497
}
8498
8499
//scroll to row in DOM
8500
scrollToRow(index, position, ifVisible){
8501
var row = this.rowManager.findRow(index);
8502
8503
if(row){
8504
return this.rowManager.scrollToRow(row, position, ifVisible);
8505
}else {
8506
console.warn("Scroll Error - No matching row found:", index);
8507
return Promise.reject("Scroll Error - No matching row found");
8508
}
8509
}
8510
8511
moveRow(from, to, after){
8512
var fromRow = this.rowManager.findRow(from);
8513
8514
this.initGuard();
8515
8516
if(fromRow){
8517
fromRow.moveToRow(to, after);
8518
}else {
8519
console.warn("Move Error - No matching row found:", from);
8520
}
8521
}
8522
8523
getRows(active){
8524
return this.rowManager.getComponents(active);
8525
}
8526
8527
//get position of row in table
8528
getRowPosition(index){
8529
var row = this.rowManager.findRow(index);
8530
8531
if(row){
8532
return row.getPosition();
8533
}else {
8534
console.warn("Position Error - No matching row found:", index);
8535
return false;
8536
}
8537
}
8538
8539
/////////////// Column Functions ///////////////
8540
setColumns(definition){
8541
this.initGuard(false, "To set initial columns please use the 'columns' property in the table constructor");
8542
8543
this.columnManager.setColumns(definition);
8544
}
8545
8546
getColumns(structured){
8547
return this.columnManager.getComponents(structured);
8548
}
8549
8550
getColumn(field){
8551
var column = this.columnManager.findColumn(field);
8552
8553
if(column){
8554
return column.getComponent();
8555
}else {
8556
console.warn("Find Error - No matching column found:", field);
8557
return false;
8558
}
8559
}
8560
8561
getColumnDefinitions(){
8562
return this.columnManager.getDefinitionTree();
8563
}
8564
8565
showColumn(field){
8566
var column = this.columnManager.findColumn(field);
8567
8568
this.initGuard();
8569
8570
if(column){
8571
column.show();
8572
}else {
8573
console.warn("Column Show Error - No matching column found:", field);
8574
return false;
8575
}
8576
}
8577
8578
hideColumn(field){
8579
var column = this.columnManager.findColumn(field);
8580
8581
this.initGuard();
8582
8583
if(column){
8584
column.hide();
8585
}else {
8586
console.warn("Column Hide Error - No matching column found:", field);
8587
return false;
8588
}
8589
}
8590
8591
toggleColumn(field){
8592
var column = this.columnManager.findColumn(field);
8593
8594
this.initGuard();
8595
8596
if(column){
8597
if(column.visible){
8598
column.hide();
8599
}else {
8600
column.show();
8601
}
8602
}else {
8603
console.warn("Column Visibility Toggle Error - No matching column found:", field);
8604
return false;
8605
}
8606
}
8607
8608
addColumn(definition, before, field){
8609
var column = this.columnManager.findColumn(field);
8610
8611
this.initGuard();
8612
8613
return this.columnManager.addColumn(definition, before, column)
8614
.then((column) => {
8615
return column.getComponent();
8616
});
8617
}
8618
8619
deleteColumn(field){
8620
var column = this.columnManager.findColumn(field);
8621
8622
this.initGuard();
8623
8624
if(column){
8625
return column.delete();
8626
}else {
8627
console.warn("Column Delete Error - No matching column found:", field);
8628
return Promise.reject();
8629
}
8630
}
8631
8632
updateColumnDefinition(field, definition){
8633
var column = this.columnManager.findColumn(field);
8634
8635
this.initGuard();
8636
8637
if(column){
8638
return column.updateDefinition(definition);
8639
}else {
8640
console.warn("Column Update Error - No matching column found:", field);
8641
return Promise.reject();
8642
}
8643
}
8644
8645
moveColumn(from, to, after){
8646
var fromColumn = this.columnManager.findColumn(from),
8647
toColumn = this.columnManager.findColumn(to);
8648
8649
this.initGuard();
8650
8651
if(fromColumn){
8652
if(toColumn){
8653
this.columnManager.moveColumn(fromColumn, toColumn, after);
8654
}else {
8655
console.warn("Move Error - No matching column found:", toColumn);
8656
}
8657
}else {
8658
console.warn("Move Error - No matching column found:", from);
8659
}
8660
}
8661
8662
//scroll to column in DOM
8663
scrollToColumn(field, position, ifVisible){
8664
return new Promise((resolve, reject) => {
8665
var column = this.columnManager.findColumn(field);
8666
8667
if(column){
8668
return this.columnManager.scrollToColumn(column, position, ifVisible);
8669
}else {
8670
console.warn("Scroll Error - No matching column found:", field);
8671
return Promise.reject("Scroll Error - No matching column found");
8672
}
8673
});
8674
}
8675
8676
//////////// General Public Functions ////////////
8677
//redraw list without updating data
8678
redraw(force){
8679
this.initGuard();
8680
8681
this.columnManager.redraw(force);
8682
this.rowManager.redraw(force);
8683
}
8684
8685
setHeight(height){
8686
this.options.height = isNaN(height) ? height : height + "px";
8687
this.element.style.height = this.options.height;
8688
this.rowManager.initializeRenderer();
8689
this.rowManager.redraw();
8690
}
8691
8692
//////////////////// Event Bus ///////////////////
8693
8694
on(key, callback){
8695
this.externalEvents.subscribe(key, callback);
8696
}
8697
8698
off(key, callback){
8699
this.externalEvents.unsubscribe(key, callback);
8700
}
8701
8702
dispatchEvent(){
8703
var args = Array.from(arguments);
8704
args.shift();
8705
8706
this.externalEvents.dispatch(...arguments);
8707
}
8708
8709
//////////////////// Alerts ///////////////////
8710
8711
alert(contents, type){
8712
this.initGuard();
8713
8714
this.alertManager.alert(contents, type);
8715
}
8716
8717
clearAlert(){
8718
this.initGuard();
8719
8720
this.alertManager.clear();
8721
}
8722
8723
////////////// Extension Management //////////////
8724
modExists(plugin, required){
8725
if(this.modules[plugin]){
8726
return true;
8727
}else {
8728
if(required){
8729
console.error("Tabulator Module Not Installed: " + plugin);
8730
}
8731
return false;
8732
}
8733
}
8734
8735
module(key){
8736
var mod = this.modules[key];
8737
8738
if(!mod){
8739
console.error("Tabulator module not installed: " + key);
8740
}
8741
8742
return mod;
8743
}
8744
}
8745
8746
//default setup options
8747
Tabulator.defaultOptions = defaultOptions;
8748
8749
//bind modules and static functionality
8750
new ModuleBinder(Tabulator);
8751
8752
var defaultAccessors = {};
8753
8754
class Accessor extends Module{
8755
8756
constructor(table){
8757
super(table);
8758
8759
this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types
8760
8761
this.registerColumnOption("accessor");
8762
this.registerColumnOption("accessorParams");
8763
this.registerColumnOption("accessorData");
8764
this.registerColumnOption("accessorDataParams");
8765
this.registerColumnOption("accessorDownload");
8766
this.registerColumnOption("accessorDownloadParams");
8767
this.registerColumnOption("accessorClipboard");
8768
this.registerColumnOption("accessorClipboardParams");
8769
this.registerColumnOption("accessorPrint");
8770
this.registerColumnOption("accessorPrintParams");
8771
this.registerColumnOption("accessorHtmlOutput");
8772
this.registerColumnOption("accessorHtmlOutputParams");
8773
}
8774
8775
initialize(){
8776
this.subscribe("column-layout", this.initializeColumn.bind(this));
8777
this.subscribe("row-data-retrieve", this.transformRow.bind(this));
8778
}
8779
8780
//initialize column accessor
8781
initializeColumn(column){
8782
var match = false,
8783
config = {};
8784
8785
this.allowedTypes.forEach((type) => {
8786
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
8787
accessor;
8788
8789
if(column.definition[key]){
8790
accessor = this.lookupAccessor(column.definition[key]);
8791
8792
if(accessor){
8793
match = true;
8794
8795
config[key] = {
8796
accessor:accessor,
8797
params: column.definition[key + "Params"] || {},
8798
};
8799
}
8800
}
8801
});
8802
8803
if(match){
8804
column.modules.accessor = config;
8805
}
8806
}
8807
8808
lookupAccessor(value){
8809
var accessor = false;
8810
8811
//set column accessor
8812
switch(typeof value){
8813
case "string":
8814
if(Accessor.accessors[value]){
8815
accessor = Accessor.accessors[value];
8816
}else {
8817
console.warn("Accessor Error - No such accessor found, ignoring: ", value);
8818
}
8819
break;
8820
8821
case "function":
8822
accessor = value;
8823
break;
8824
}
8825
8826
return accessor;
8827
}
8828
8829
//apply accessor to row
8830
transformRow(row, type){
8831
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
8832
rowComponent = row.getComponent();
8833
8834
//clone data object with deep copy to isolate internal data from returned result
8835
var data = Helpers.deepClone(row.data || {});
8836
8837
this.table.columnManager.traverse(function(column){
8838
var value, accessor, params, colComponent;
8839
8840
if(column.modules.accessor){
8841
8842
accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false;
8843
8844
if(accessor){
8845
value = column.getFieldValue(data);
8846
8847
if(value != "undefined"){
8848
colComponent = column.getComponent();
8849
params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params;
8850
column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent));
8851
}
8852
}
8853
}
8854
});
8855
8856
return data;
8857
}
8858
}
8859
8860
//load defaults
8861
Accessor.moduleName = "accessor";
8862
Accessor.accessors = defaultAccessors;
8863
8864
var defaultConfig = {
8865
method: "GET",
8866
};
8867
8868
function generateParamsList(data, prefix){
8869
var output = [];
8870
8871
prefix = prefix || "";
8872
8873
if(Array.isArray(data)){
8874
data.forEach((item, i) => {
8875
output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i));
8876
});
8877
}else if (typeof data === "object"){
8878
for (var key in data){
8879
output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key));
8880
}
8881
}else {
8882
output.push({key:prefix, value:data});
8883
}
8884
8885
return output;
8886
}
8887
8888
function serializeParams(params){
8889
var output = generateParamsList(params),
8890
encoded = [];
8891
8892
output.forEach(function(item){
8893
encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value));
8894
});
8895
8896
return encoded.join("&");
8897
}
8898
8899
function urlBuilder(url, config, params){
8900
if(url){
8901
if(params && Object.keys(params).length){
8902
if(!config.method || config.method.toLowerCase() == "get"){
8903
config.method = "get";
8904
8905
url += (url.includes("?") ? "&" : "?") + serializeParams(params);
8906
}
8907
}
8908
}
8909
8910
return url;
8911
}
8912
8913
function defaultLoaderPromise(url, config, params){
8914
var contentType;
8915
8916
return new Promise((resolve, reject) => {
8917
//set url
8918
url = this.urlGenerator.call(this.table, url, config, params);
8919
8920
//set body content if not GET request
8921
if(config.method.toUpperCase() != "GET"){
8922
contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType];
8923
if(contentType){
8924
8925
for(var key in contentType.headers){
8926
if(!config.headers){
8927
config.headers = {};
8928
}
8929
8930
if(typeof config.headers[key] === "undefined"){
8931
config.headers[key] = contentType.headers[key];
8932
}
8933
}
8934
8935
config.body = contentType.body.call(this, url, config, params);
8936
8937
}else {
8938
console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType);
8939
}
8940
}
8941
8942
if(url){
8943
//configure headers
8944
if(typeof config.headers === "undefined"){
8945
config.headers = {};
8946
}
8947
8948
if(typeof config.headers.Accept === "undefined"){
8949
config.headers.Accept = "application/json";
8950
}
8951
8952
if(typeof config.headers["X-Requested-With"] === "undefined"){
8953
config.headers["X-Requested-With"] = "XMLHttpRequest";
8954
}
8955
8956
if(typeof config.mode === "undefined"){
8957
config.mode = "cors";
8958
}
8959
8960
if(config.mode == "cors"){
8961
if(typeof config.headers["Origin"] === "undefined"){
8962
config.headers["Origin"] = window.location.origin;
8963
}
8964
8965
if(typeof config.credentials === "undefined"){
8966
config.credentials = 'same-origin';
8967
}
8968
}else {
8969
if(typeof config.credentials === "undefined"){
8970
config.credentials = 'include';
8971
}
8972
}
8973
8974
//send request
8975
fetch(url, config)
8976
.then((response)=>{
8977
if(response.ok) {
8978
response.json()
8979
.then((data)=>{
8980
resolve(data);
8981
}).catch((error)=>{
8982
reject(error);
8983
console.warn("Ajax Load Error - Invalid JSON returned", error);
8984
});
8985
}else {
8986
console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText);
8987
reject(response);
8988
}
8989
})
8990
.catch((error)=>{
8991
console.error("Ajax Load Error - Connection Error: ", error);
8992
reject(error);
8993
});
8994
}else {
8995
console.warn("Ajax Load Error - No URL Set");
8996
resolve([]);
8997
}
8998
});
8999
}
9000
9001
function generateParamsList$1(data, prefix){
9002
var output = [];
9003
9004
prefix = prefix || "";
9005
9006
if(Array.isArray(data)){
9007
data.forEach((item, i) => {
9008
output = output.concat(generateParamsList$1(item, prefix ? prefix + "[" + i + "]" : i));
9009
});
9010
}else if (typeof data === "object"){
9011
for (var key in data){
9012
output = output.concat(generateParamsList$1(data[key], prefix ? prefix + "[" + key + "]" : key));
9013
}
9014
}else {
9015
output.push({key:prefix, value:data});
9016
}
9017
9018
return output;
9019
}
9020
9021
var defaultContentTypeFormatters = {
9022
"json":{
9023
headers:{
9024
'Content-Type': 'application/json',
9025
},
9026
body:function(url, config, params){
9027
return JSON.stringify(params);
9028
},
9029
},
9030
"form":{
9031
headers:{
9032
},
9033
body:function(url, config, params){
9034
9035
var output = generateParamsList$1(params),
9036
form = new FormData();
9037
9038
output.forEach(function(item){
9039
form.append(item.key, item.value);
9040
});
9041
9042
return form;
9043
},
9044
},
9045
};
9046
9047
class Ajax extends Module{
9048
9049
constructor(table){
9050
super(table);
9051
9052
this.config = {}; //hold config object for ajax request
9053
this.url = ""; //request URL
9054
this.urlGenerator = false;
9055
this.params = false; //request parameters
9056
9057
this.loaderPromise = false;
9058
9059
this.registerTableOption("ajaxURL", false); //url for ajax loading
9060
this.registerTableOption("ajaxURLGenerator", false);
9061
this.registerTableOption("ajaxParams", {}); //params for ajax loading
9062
this.registerTableOption("ajaxConfig", "get"); //ajax request type
9063
this.registerTableOption("ajaxContentType", "form"); //ajax request type
9064
this.registerTableOption("ajaxRequestFunc", false); //promise function
9065
9066
this.registerTableOption("ajaxRequesting", function(){});
9067
this.registerTableOption("ajaxResponse", false);
9068
9069
this.contentTypeFormatters = Ajax.contentTypeFormatters;
9070
}
9071
9072
//initialize setup options
9073
initialize(){
9074
this.loaderPromise = this.table.options.ajaxRequestFunc || Ajax.defaultLoaderPromise;
9075
this.urlGenerator = this.table.options.ajaxURLGenerator || Ajax.defaultURLGenerator;
9076
9077
if(this.table.options.ajaxURL){
9078
this.setUrl(this.table.options.ajaxURL);
9079
}
9080
9081
9082
this.setDefaultConfig(this.table.options.ajaxConfig);
9083
9084
this.registerTableFunction("getAjaxUrl", this.getUrl.bind(this));
9085
9086
this.subscribe("data-loading", this.requestDataCheck.bind(this));
9087
this.subscribe("data-params", this.requestParams.bind(this));
9088
this.subscribe("data-load", this.requestData.bind(this));
9089
}
9090
9091
requestParams(data, config, silent, params){
9092
var ajaxParams = this.table.options.ajaxParams;
9093
9094
if(ajaxParams){
9095
if(typeof ajaxParams === "function"){
9096
ajaxParams = ajaxParams.call(this.table);
9097
}
9098
9099
params = Object.assign(params, ajaxParams);
9100
}
9101
9102
return params;
9103
}
9104
9105
requestDataCheck(data, params, config, silent){
9106
return !!((!data && this.url) || typeof data === "string");
9107
}
9108
9109
requestData(url, params, config, silent, previousData){
9110
var ajaxConfig;
9111
9112
if(!previousData && this.requestDataCheck(url)){
9113
if(url){
9114
this.setUrl(url);
9115
}
9116
9117
ajaxConfig = this.generateConfig(config);
9118
9119
return this.sendRequest(this.url, params, ajaxConfig);
9120
}else {
9121
return previousData;
9122
}
9123
}
9124
9125
setDefaultConfig(config = {}){
9126
this.config = Object.assign({}, Ajax.defaultConfig);
9127
9128
if(typeof config == "string"){
9129
this.config.method = config;
9130
}else {
9131
Object.assign(this.config, config);
9132
}
9133
}
9134
9135
//load config object
9136
generateConfig(config = {}){
9137
var ajaxConfig = Object.assign({}, this.config);
9138
9139
if(typeof config == "string"){
9140
ajaxConfig.method = config;
9141
}else {
9142
Object.assign(ajaxConfig, config);
9143
}
9144
9145
return ajaxConfig;
9146
}
9147
9148
//set request url
9149
setUrl(url){
9150
this.url = url;
9151
}
9152
9153
//get request url
9154
getUrl(){
9155
return this.url;
9156
}
9157
9158
//send ajax request
9159
sendRequest(url, params, config){
9160
if(this.table.options.ajaxRequesting.call(this.table, url, params) !== false){
9161
return this.loaderPromise(url, config, params)
9162
.then((data)=>{
9163
if(this.table.options.ajaxResponse){
9164
data = this.table.options.ajaxResponse.call(this.table, url, params, data);
9165
}
9166
9167
return data;
9168
});
9169
}else {
9170
return Promise.reject();
9171
}
9172
}
9173
}
9174
9175
Ajax.moduleName = "ajax";
9176
9177
//load defaults
9178
Ajax.defaultConfig = defaultConfig;
9179
Ajax.defaultURLGenerator = urlBuilder;
9180
Ajax.defaultLoaderPromise = defaultLoaderPromise;
9181
Ajax.contentTypeFormatters = defaultContentTypeFormatters;
9182
9183
var defaultPasteActions = {
9184
replace:function(rows){
9185
return this.table.setData(rows);
9186
},
9187
update:function(rows){
9188
return this.table.updateOrAddData(rows);
9189
},
9190
insert:function(rows){
9191
return this.table.addData(rows);
9192
},
9193
};
9194
9195
var defaultPasteParsers = {
9196
table:function(clipboard){
9197
var data = [],
9198
headerFindSuccess = true,
9199
columns = this.table.columnManager.columns,
9200
columnMap = [],
9201
rows = [];
9202
9203
//get data from clipboard into array of columns and rows.
9204
clipboard = clipboard.split("\n");
9205
9206
clipboard.forEach(function(row){
9207
data.push(row.split("\t"));
9208
});
9209
9210
if(data.length && !(data.length === 1 && data[0].length < 2)){
9211
9212
//check if headers are present by title
9213
data[0].forEach(function(value){
9214
var column = columns.find(function(column){
9215
return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim();
9216
});
9217
9218
if(column){
9219
columnMap.push(column);
9220
}else {
9221
headerFindSuccess = false;
9222
}
9223
});
9224
9225
//check if column headers are present by field
9226
if(!headerFindSuccess){
9227
headerFindSuccess = true;
9228
columnMap = [];
9229
9230
data[0].forEach(function(value){
9231
var column = columns.find(function(column){
9232
return value && column.field && value.trim() && column.field.trim() === value.trim();
9233
});
9234
9235
if(column){
9236
columnMap.push(column);
9237
}else {
9238
headerFindSuccess = false;
9239
}
9240
});
9241
9242
if(!headerFindSuccess){
9243
columnMap = this.table.columnManager.columnsByIndex;
9244
}
9245
}
9246
9247
//remove header row if found
9248
if(headerFindSuccess){
9249
data.shift();
9250
}
9251
9252
data.forEach(function(item){
9253
var row = {};
9254
9255
item.forEach(function(value, i){
9256
if(columnMap[i]){
9257
row[columnMap[i].field] = value;
9258
}
9259
});
9260
9261
rows.push(row);
9262
});
9263
9264
return rows;
9265
}else {
9266
return false;
9267
}
9268
}
9269
};
9270
9271
class Clipboard extends Module{
9272
9273
constructor(table){
9274
super(table);
9275
9276
this.mode = true;
9277
this.pasteParser = function(){};
9278
this.pasteAction = function(){};
9279
this.customSelection = false;
9280
this.rowRange = false;
9281
this.blocked = true; //block copy actions not originating from this command
9282
9283
this.registerTableOption("clipboard", false); //enable clipboard
9284
this.registerTableOption("clipboardCopyStyled", true); //formatted table data
9285
this.registerTableOption("clipboardCopyConfig", false); //clipboard config
9286
this.registerTableOption("clipboardCopyFormatter", false); //DEPRECATED - REMOVE in 5.0
9287
this.registerTableOption("clipboardCopyRowRange", "active"); //restrict clipboard to visible rows only
9288
this.registerTableOption("clipboardPasteParser", "table"); //convert pasted clipboard data to rows
9289
this.registerTableOption("clipboardPasteAction", "insert"); //how to insert pasted data into the table
9290
9291
this.registerColumnOption("clipboard");
9292
this.registerColumnOption("titleClipboard");
9293
}
9294
9295
initialize(){
9296
this.mode = this.table.options.clipboard;
9297
9298
this.rowRange = this.table.options.clipboardCopyRowRange;
9299
9300
if(this.mode === true || this.mode === "copy"){
9301
this.table.element.addEventListener("copy", (e) => {
9302
var plain, html, list;
9303
9304
if(!this.blocked){
9305
e.preventDefault();
9306
9307
if(this.customSelection){
9308
plain = this.customSelection;
9309
9310
if(this.table.options.clipboardCopyFormatter){
9311
plain = this.table.options.clipboardCopyFormatter("plain", plain);
9312
}
9313
}else {
9314
9315
list = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard");
9316
9317
html = this.table.modules.export.generateHTMLTable(list);
9318
plain = html ? this.generatePlainContent(list) : "";
9319
9320
if(this.table.options.clipboardCopyFormatter){
9321
plain = this.table.options.clipboardCopyFormatter("plain", plain);
9322
html = this.table.options.clipboardCopyFormatter("html", html);
9323
}
9324
}
9325
9326
if (window.clipboardData && window.clipboardData.setData) {
9327
window.clipboardData.setData('Text', plain);
9328
} else if (e.clipboardData && e.clipboardData.setData) {
9329
e.clipboardData.setData('text/plain', plain);
9330
if(html){
9331
e.clipboardData.setData('text/html', html);
9332
}
9333
} else if (e.originalEvent && e.originalEvent.clipboardData.setData) {
9334
e.originalEvent.clipboardData.setData('text/plain', plain);
9335
if(html){
9336
e.originalEvent.clipboardData.setData('text/html', html);
9337
}
9338
}
9339
9340
this.dispatchExternal("clipboardCopied", plain, html);
9341
9342
this.reset();
9343
}
9344
});
9345
}
9346
9347
if(this.mode === true || this.mode === "paste"){
9348
this.table.element.addEventListener("paste", (e) => {
9349
this.paste(e);
9350
});
9351
}
9352
9353
this.setPasteParser(this.table.options.clipboardPasteParser);
9354
this.setPasteAction(this.table.options.clipboardPasteAction);
9355
9356
this.registerTableFunction("copyToClipboard", this.copy.bind(this));
9357
}
9358
9359
reset(){
9360
this.blocked = true;
9361
this.customSelection = false;
9362
}
9363
9364
generatePlainContent (list) {
9365
var output = [];
9366
9367
list.forEach((row) => {
9368
var rowData = [];
9369
9370
row.columns.forEach((col) => {
9371
var value = "";
9372
9373
if(col){
9374
9375
if(row.type === "group"){
9376
col.value = col.component.getKey();
9377
}
9378
9379
if(col.value === null){
9380
value = "";
9381
}else {
9382
switch(typeof col.value){
9383
case "object":
9384
value = JSON.stringify(col.value);
9385
break;
9386
9387
case "undefined":
9388
value = "";
9389
break;
9390
9391
default:
9392
value = col.value;
9393
}
9394
}
9395
}
9396
9397
rowData.push(value);
9398
});
9399
9400
output.push(rowData.join("\t"));
9401
});
9402
9403
return output.join("\n");
9404
}
9405
9406
copy (range, internal) {
9407
var sel, textRange;
9408
this.blocked = false;
9409
this.customSelection = false;
9410
9411
if (this.mode === true || this.mode === "copy") {
9412
9413
this.rowRange = range || this.table.options.clipboardCopyRowRange;
9414
9415
if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
9416
range = document.createRange();
9417
range.selectNodeContents(this.table.element);
9418
sel = window.getSelection();
9419
9420
if (sel.toString() && internal) {
9421
this.customSelection = sel.toString();
9422
}
9423
9424
sel.removeAllRanges();
9425
sel.addRange(range);
9426
} else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") {
9427
textRange = document.body.createTextRange();
9428
textRange.moveToElementText(this.table.element);
9429
textRange.select();
9430
}
9431
9432
document.execCommand('copy');
9433
9434
if (sel) {
9435
sel.removeAllRanges();
9436
}
9437
}
9438
}
9439
9440
//PASTE EVENT HANDLING
9441
setPasteAction(action){
9442
9443
switch(typeof action){
9444
case "string":
9445
this.pasteAction = Clipboard.pasteActions[action];
9446
9447
if(!this.pasteAction){
9448
console.warn("Clipboard Error - No such paste action found:", action);
9449
}
9450
break;
9451
9452
case "function":
9453
this.pasteAction = action;
9454
break;
9455
}
9456
}
9457
9458
setPasteParser(parser){
9459
switch(typeof parser){
9460
case "string":
9461
this.pasteParser = Clipboard.pasteParsers[parser];
9462
9463
if(!this.pasteParser){
9464
console.warn("Clipboard Error - No such paste parser found:", parser);
9465
}
9466
break;
9467
9468
case "function":
9469
this.pasteParser = parser;
9470
break;
9471
}
9472
}
9473
9474
paste(e){
9475
var data, rowData, rows;
9476
9477
if(this.checkPaseOrigin(e)){
9478
9479
data = this.getPasteData(e);
9480
9481
rowData = this.pasteParser.call(this, data);
9482
9483
if(rowData){
9484
e.preventDefault();
9485
9486
if(this.table.modExists("mutator")){
9487
rowData = this.mutateData(rowData);
9488
}
9489
9490
rows = this.pasteAction.call(this, rowData);
9491
9492
this.dispatchExternal("clipboardPasted", data, rowData, rows);
9493
}else {
9494
this.dispatchExternal("clipboardPasteError", data);
9495
}
9496
}
9497
}
9498
9499
mutateData(data){
9500
var output = [];
9501
9502
if(Array.isArray(data)){
9503
data.forEach((row) => {
9504
output.push(this.table.modules.mutator.transformRow(row, "clipboard"));
9505
});
9506
}else {
9507
output = data;
9508
}
9509
9510
return output;
9511
}
9512
9513
9514
checkPaseOrigin(e){
9515
var valid = true;
9516
9517
if(e.target.tagName != "DIV" || this.table.modules.edit.currentCell){
9518
valid = false;
9519
}
9520
9521
return valid;
9522
}
9523
9524
getPasteData(e){
9525
var data;
9526
9527
if (window.clipboardData && window.clipboardData.getData) {
9528
data = window.clipboardData.getData('Text');
9529
} else if (e.clipboardData && e.clipboardData.getData) {
9530
data = e.clipboardData.getData('text/plain');
9531
} else if (e.originalEvent && e.originalEvent.clipboardData.getData) {
9532
data = e.originalEvent.clipboardData.getData('text/plain');
9533
}
9534
9535
return data;
9536
}
9537
}
9538
9539
Clipboard.moduleName = "clipboard";
9540
9541
//load defaults
9542
Clipboard.pasteActions = defaultPasteActions;
9543
Clipboard.pasteParsers = defaultPasteParsers;
9544
9545
class CalcComponent{
9546
constructor (row){
9547
this._row = row;
9548
9549
return new Proxy(this, {
9550
get: function(target, name, receiver) {
9551
if (typeof target[name] !== "undefined") {
9552
return target[name];
9553
}else {
9554
return target._row.table.componentFunctionBinder.handle("row", target._row, name);
9555
}
9556
}
9557
});
9558
}
9559
9560
getData(transform){
9561
return this._row.getData(transform);
9562
}
9563
9564
getElement(){
9565
return this._row.getElement();
9566
}
9567
9568
getTable(){
9569
return this._row.table;
9570
}
9571
9572
getCells(){
9573
var cells = [];
9574
9575
this._row.getCells().forEach(function(cell){
9576
cells.push(cell.getComponent());
9577
});
9578
9579
return cells;
9580
}
9581
9582
getCell(column){
9583
var cell = this._row.getCell(column);
9584
return cell ? cell.getComponent() : false;
9585
}
9586
9587
_getSelf(){
9588
return this._row;
9589
}
9590
}
9591
9592
var defaultCalculations = {
9593
"avg":function(values, data, calcParams){
9594
var output = 0,
9595
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : 2;
9596
9597
if(values.length){
9598
output = values.reduce(function(sum, value){
9599
return Number(sum) + Number(value);
9600
});
9601
9602
output = output / values.length;
9603
9604
output = precision !== false ? output.toFixed(precision) : output;
9605
}
9606
9607
return parseFloat(output).toString();
9608
},
9609
"max":function(values, data, calcParams){
9610
var output = null,
9611
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
9612
9613
values.forEach(function(value){
9614
9615
value = Number(value);
9616
9617
if(value > output || output === null){
9618
output = value;
9619
}
9620
});
9621
9622
return output !== null ? (precision !== false ? output.toFixed(precision) : output) : "";
9623
},
9624
"min":function(values, data, calcParams){
9625
var output = null,
9626
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
9627
9628
values.forEach(function(value){
9629
9630
value = Number(value);
9631
9632
if(value < output || output === null){
9633
output = value;
9634
}
9635
});
9636
9637
return output !== null ? (precision !== false ? output.toFixed(precision) : output) : "";
9638
},
9639
"sum":function(values, data, calcParams){
9640
var output = 0,
9641
precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false;
9642
9643
if(values.length){
9644
values.forEach(function(value){
9645
value = Number(value);
9646
9647
output += !isNaN(value) ? Number(value) : 0;
9648
});
9649
}
9650
9651
return precision !== false ? output.toFixed(precision) : output;
9652
},
9653
"concat":function(values, data, calcParams){
9654
var output = 0;
9655
9656
if(values.length){
9657
output = values.reduce(function(sum, value){
9658
return String(sum) + String(value);
9659
});
9660
}
9661
9662
return output;
9663
},
9664
"count":function(values, data, calcParams){
9665
var output = 0;
9666
9667
if(values.length){
9668
values.forEach(function(value){
9669
if(value){
9670
output ++;
9671
}
9672
});
9673
}
9674
9675
return output;
9676
},
9677
"unique":function(values, data, calcParams){
9678
var unique = values.filter((value, index) => {
9679
return (values || value === 0) && values.indexOf(value) === index;
9680
});
9681
9682
return unique.length;
9683
},
9684
};
9685
9686
class ColumnCalcs extends Module{
9687
9688
constructor(table){
9689
super(table);
9690
9691
this.topCalcs = [];
9692
this.botCalcs = [];
9693
this.genColumn = false;
9694
this.topElement = this.createElement();
9695
this.botElement = this.createElement();
9696
this.topRow = false;
9697
this.botRow = false;
9698
this.topInitialized = false;
9699
this.botInitialized = false;
9700
9701
this.blocked = false;
9702
this.recalcAfterBlock = false;
9703
9704
this.registerTableOption("columnCalcs", true);
9705
9706
this.registerColumnOption("topCalc");
9707
this.registerColumnOption("topCalcParams");
9708
this.registerColumnOption("topCalcFormatter");
9709
this.registerColumnOption("topCalcFormatterParams");
9710
this.registerColumnOption("bottomCalc");
9711
this.registerColumnOption("bottomCalcParams");
9712
this.registerColumnOption("bottomCalcFormatter");
9713
this.registerColumnOption("bottomCalcFormatterParams");
9714
}
9715
9716
createElement (){
9717
var el = document.createElement("div");
9718
el.classList.add("tabulator-calcs-holder");
9719
return el;
9720
}
9721
9722
initialize(){
9723
this.genColumn = new Column({field:"value"}, this);
9724
9725
this.subscribe("cell-value-changed", this.cellValueChanged.bind(this));
9726
this.subscribe("column-init", this.initializeColumnCheck.bind(this));
9727
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
9728
this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this));
9729
this.subscribe("row-added", this.rowsUpdated.bind(this));
9730
this.subscribe("column-moved", this.recalcActiveRows.bind(this));
9731
this.subscribe("column-add", this.recalcActiveRows.bind(this));
9732
this.subscribe("data-refreshed", this.recalcActiveRowsRefresh.bind(this));
9733
this.subscribe("table-redraw", this.tableRedraw.bind(this));
9734
this.subscribe("rows-visible", this.visibleRows.bind(this));
9735
this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this));
9736
9737
this.subscribe("redraw-blocked", this.blockRedraw.bind(this));
9738
this.subscribe("redraw-restored", this.restoreRedraw.bind(this));
9739
9740
this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this));
9741
this.subscribe("column-resized", this.resizeHolderWidth.bind(this));
9742
this.subscribe("column-show", this.resizeHolderWidth.bind(this));
9743
this.subscribe("column-hide", this.resizeHolderWidth.bind(this));
9744
9745
this.registerTableFunction("getCalcResults", this.getResults.bind(this));
9746
this.registerTableFunction("recalc", this.userRecalc.bind(this));
9747
9748
9749
this.resizeHolderWidth();
9750
}
9751
9752
resizeHolderWidth(){
9753
this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px";
9754
}
9755
9756
9757
tableRedraw(force){
9758
this.recalc(this.table.rowManager.activeRows);
9759
9760
if(force){
9761
this.redraw();
9762
}
9763
}
9764
9765
blockRedraw(){
9766
this.blocked = true;
9767
this.recalcAfterBlock = false;
9768
}
9769
9770
9771
restoreRedraw(){
9772
this.blocked = false;
9773
9774
if(this.recalcAfterBlock){
9775
this.recalcAfterBlock = false;
9776
this.recalcActiveRowsRefresh();
9777
}
9778
}
9779
9780
///////////////////////////////////
9781
///////// Table Functions /////////
9782
///////////////////////////////////
9783
userRecalc(){
9784
this.recalc(this.table.rowManager.activeRows);
9785
}
9786
9787
///////////////////////////////////
9788
///////// Internal Logic //////////
9789
///////////////////////////////////
9790
9791
blockCheck(){
9792
if(this.blocked){
9793
this.recalcAfterBlock = true;
9794
}
9795
9796
return this.blocked;
9797
}
9798
9799
visibleRows(viewable, rows){
9800
if(this.topRow){
9801
rows.unshift(this.topRow);
9802
}
9803
9804
if(this.botRow){
9805
rows.push(this.botRow);
9806
}
9807
9808
return rows;
9809
}
9810
9811
rowsUpdated(row){
9812
if(this.table.options.groupBy){
9813
this.recalcRowGroup(row);
9814
}else {
9815
this.recalcActiveRows();
9816
}
9817
}
9818
9819
recalcActiveRowsRefresh(){
9820
if(this.table.options.groupBy && this.table.options.dataTreeStartExpanded && this.table.options.dataTree){
9821
this.recalcAll();
9822
}else {
9823
this.recalcActiveRows();
9824
}
9825
}
9826
9827
recalcActiveRows(){
9828
this.recalc(this.table.rowManager.activeRows);
9829
}
9830
9831
cellValueChanged(cell){
9832
if(cell.column.definition.topCalc || cell.column.definition.bottomCalc){
9833
if(this.table.options.groupBy){
9834
if(this.table.options.columnCalcs == "table" || this.table.options.columnCalcs == "both"){
9835
this.recalcActiveRows();
9836
}
9837
9838
if(this.table.options.columnCalcs != "table"){
9839
this.recalcRowGroup(cell.row);
9840
}
9841
}else {
9842
this.recalcActiveRows();
9843
}
9844
}
9845
}
9846
9847
initializeColumnCheck(column){
9848
if(column.definition.topCalc || column.definition.bottomCalc){
9849
this.initializeColumn(column);
9850
}
9851
}
9852
9853
//initialize column calcs
9854
initializeColumn(column){
9855
var def = column.definition;
9856
9857
var config = {
9858
topCalcParams:def.topCalcParams || {},
9859
botCalcParams:def.bottomCalcParams || {},
9860
};
9861
9862
if(def.topCalc){
9863
9864
switch(typeof def.topCalc){
9865
case "string":
9866
if(ColumnCalcs.calculations[def.topCalc]){
9867
config.topCalc = ColumnCalcs.calculations[def.topCalc];
9868
}else {
9869
console.warn("Column Calc Error - No such calculation found, ignoring: ", def.topCalc);
9870
}
9871
break;
9872
9873
case "function":
9874
config.topCalc = def.topCalc;
9875
break;
9876
9877
}
9878
9879
if(config.topCalc){
9880
column.modules.columnCalcs = config;
9881
this.topCalcs.push(column);
9882
9883
if(this.table.options.columnCalcs != "group"){
9884
this.initializeTopRow();
9885
}
9886
}
9887
9888
}
9889
9890
if(def.bottomCalc){
9891
switch(typeof def.bottomCalc){
9892
case "string":
9893
if(ColumnCalcs.calculations[def.bottomCalc]){
9894
config.botCalc = ColumnCalcs.calculations[def.bottomCalc];
9895
}else {
9896
console.warn("Column Calc Error - No such calculation found, ignoring: ", def.bottomCalc);
9897
}
9898
break;
9899
9900
case "function":
9901
config.botCalc = def.bottomCalc;
9902
break;
9903
9904
}
9905
9906
if(config.botCalc){
9907
column.modules.columnCalcs = config;
9908
this.botCalcs.push(column);
9909
9910
if(this.table.options.columnCalcs != "group"){
9911
this.initializeBottomRow();
9912
}
9913
}
9914
}
9915
9916
}
9917
9918
//dummy functions to handle being mock column manager
9919
registerColumnField(){}
9920
9921
removeCalcs(){
9922
var changed = false;
9923
9924
if(this.topInitialized){
9925
this.topInitialized = false;
9926
this.topElement.parentNode.removeChild(this.topElement);
9927
changed = true;
9928
}
9929
9930
if(this.botInitialized){
9931
this.botInitialized = false;
9932
this.footerRemove(this.botElement);
9933
changed = true;
9934
}
9935
9936
if(changed){
9937
this.table.rowManager.adjustTableSize();
9938
}
9939
}
9940
9941
reinitializeCalcs(){
9942
if(this.topCalcs.length){
9943
this.initializeTopRow();
9944
}
9945
9946
if(this.botCalcs.length){
9947
this.initializeBottomRow();
9948
}
9949
}
9950
9951
initializeTopRow(){
9952
if(!this.topInitialized){
9953
this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling);
9954
this.topInitialized = true;
9955
}
9956
}
9957
9958
initializeBottomRow(){
9959
if(!this.botInitialized){
9960
this.footerPrepend(this.botElement);
9961
this.botInitialized = true;
9962
}
9963
}
9964
9965
scrollHorizontal(left){
9966
if(this.botInitialized && this.botRow){
9967
this.botElement.scrollLeft = left;
9968
}
9969
}
9970
9971
recalc(rows){
9972
var data, row;
9973
9974
if(!this.blockCheck()){
9975
if(this.topInitialized || this.botInitialized){
9976
data = this.rowsToData(rows);
9977
9978
if(this.topInitialized){
9979
if(this.topRow){
9980
this.topRow.deleteCells();
9981
}
9982
9983
row = this.generateRow("top", data);
9984
this.topRow = row;
9985
while(this.topElement.firstChild) this.topElement.removeChild(this.topElement.firstChild);
9986
this.topElement.appendChild(row.getElement());
9987
row.initialize(true);
9988
}
9989
9990
if(this.botInitialized){
9991
if(this.botRow){
9992
this.botRow.deleteCells();
9993
}
9994
9995
row = this.generateRow("bottom", data);
9996
this.botRow = row;
9997
while(this.botElement.firstChild) this.botElement.removeChild(this.botElement.firstChild);
9998
this.botElement.appendChild(row.getElement());
9999
row.initialize(true);
10000
}
10001
10002
this.table.rowManager.adjustTableSize();
10003
10004
//set resizable handles
10005
if(this.table.modExists("frozenColumns")){
10006
this.table.modules.frozenColumns.layout();
10007
}
10008
}
10009
}
10010
}
10011
10012
recalcRowGroup(row){
10013
this.recalcGroup(this.table.modules.groupRows.getRowGroup(row));
10014
}
10015
10016
recalcAll(){
10017
if(this.topCalcs.length || this.botCalcs.length){
10018
if(this.table.options.columnCalcs !== "group"){
10019
this.recalcActiveRows();
10020
}
10021
10022
if(this.table.options.groupBy && this.table.options.columnCalcs !== "table"){
10023
10024
var groups = this.table.modules.groupRows.getChildGroups();
10025
10026
groups.forEach((group) => {
10027
this.recalcGroup(group);
10028
});
10029
}
10030
}
10031
}
10032
10033
recalcGroup(group){
10034
var data, rowData;
10035
10036
if(!this.blockCheck()){
10037
if(group){
10038
if(group.calcs){
10039
if(group.calcs.bottom){
10040
data = this.rowsToData(group.rows);
10041
rowData = this.generateRowData("bottom", data);
10042
10043
group.calcs.bottom.updateData(rowData);
10044
group.calcs.bottom.reinitialize();
10045
}
10046
10047
if(group.calcs.top){
10048
data = this.rowsToData(group.rows);
10049
rowData = this.generateRowData("top", data);
10050
10051
group.calcs.top.updateData(rowData);
10052
group.calcs.top.reinitialize();
10053
}
10054
}
10055
}
10056
}
10057
}
10058
10059
//generate top stats row
10060
generateTopRow(rows){
10061
return this.generateRow("top", this.rowsToData(rows));
10062
}
10063
//generate bottom stats row
10064
generateBottomRow(rows){
10065
return this.generateRow("bottom", this.rowsToData(rows));
10066
}
10067
10068
rowsToData(rows){
10069
var data = [];
10070
10071
rows.forEach((row) => {
10072
data.push(row.getData());
10073
10074
if(this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs){
10075
if(row.modules.dataTree && row.modules.dataTree.open){
10076
var children = this.rowsToData(this.table.modules.dataTree.getFilteredTreeChildren(row));
10077
data = data.concat(children);
10078
}
10079
}
10080
});
10081
10082
return data;
10083
}
10084
10085
//generate stats row
10086
generateRow(pos, data){
10087
var rowData = this.generateRowData(pos, data),
10088
row;
10089
10090
if(this.table.modExists("mutator")){
10091
this.table.modules.mutator.disable();
10092
}
10093
10094
row = new Row(rowData, this, "calc");
10095
10096
if(this.table.modExists("mutator")){
10097
this.table.modules.mutator.enable();
10098
}
10099
10100
row.getElement().classList.add("tabulator-calcs", "tabulator-calcs-" + pos);
10101
10102
row.component = false;
10103
10104
row.getComponent = () => {
10105
if(!row.component){
10106
row.component = new CalcComponent(row);
10107
}
10108
10109
return row.component;
10110
};
10111
10112
row.generateCells = () => {
10113
10114
var cells = [];
10115
10116
this.table.columnManager.columnsByIndex.forEach((column) => {
10117
10118
//set field name of mock column
10119
this.genColumn.setField(column.getField());
10120
this.genColumn.hozAlign = column.hozAlign;
10121
10122
if(column.definition[pos + "CalcFormatter"] && this.table.modExists("format")){
10123
this.genColumn.modules.format = {
10124
formatter: this.table.modules.format.getFormatter(column.definition[pos + "CalcFormatter"]),
10125
params: column.definition[pos + "CalcFormatterParams"] || {},
10126
};
10127
}else {
10128
this.genColumn.modules.format = {
10129
formatter: this.table.modules.format.getFormatter("plaintext"),
10130
params:{}
10131
};
10132
}
10133
10134
//ensure css class definition is replicated to calculation cell
10135
this.genColumn.definition.cssClass = column.definition.cssClass;
10136
10137
//generate cell and assign to correct column
10138
var cell = new Cell(this.genColumn, row);
10139
cell.getElement();
10140
cell.column = column;
10141
cell.setWidth();
10142
10143
column.cells.push(cell);
10144
cells.push(cell);
10145
10146
if(!column.visible){
10147
cell.hide();
10148
}
10149
});
10150
10151
row.cells = cells;
10152
};
10153
10154
return row;
10155
}
10156
10157
//generate stats row
10158
generateRowData(pos, data){
10159
var rowData = {},
10160
calcs = pos == "top" ? this.topCalcs : this.botCalcs,
10161
type = pos == "top" ? "topCalc" : "botCalc",
10162
params, paramKey;
10163
10164
calcs.forEach(function(column){
10165
var values = [];
10166
10167
if(column.modules.columnCalcs && column.modules.columnCalcs[type]){
10168
data.forEach(function(item){
10169
values.push(column.getFieldValue(item));
10170
});
10171
10172
paramKey = type + "Params";
10173
params = typeof column.modules.columnCalcs[paramKey] === "function" ? column.modules.columnCalcs[paramKey](values, data) : column.modules.columnCalcs[paramKey];
10174
10175
column.setFieldValue(rowData, column.modules.columnCalcs[type](values, data, params));
10176
}
10177
});
10178
10179
return rowData;
10180
}
10181
10182
hasTopCalcs(){
10183
return !!(this.topCalcs.length);
10184
}
10185
10186
hasBottomCalcs(){
10187
return !!(this.botCalcs.length);
10188
}
10189
10190
//handle table redraw
10191
redraw(){
10192
if(this.topRow){
10193
this.topRow.normalizeHeight(true);
10194
}
10195
if(this.botRow){
10196
this.botRow.normalizeHeight(true);
10197
}
10198
}
10199
10200
//return the calculated
10201
getResults(){
10202
var results = {},
10203
groups;
10204
10205
if(this.table.options.groupBy && this.table.modExists("groupRows")){
10206
groups = this.table.modules.groupRows.getGroups(true);
10207
10208
groups.forEach((group) => {
10209
results[group.getKey()] = this.getGroupResults(group);
10210
});
10211
}else {
10212
results = {
10213
top: this.topRow ? this.topRow.getData() : {},
10214
bottom: this.botRow ? this.botRow.getData() : {},
10215
};
10216
}
10217
10218
return results;
10219
}
10220
10221
//get results from a group
10222
getGroupResults(group){
10223
var groupObj = group._getSelf(),
10224
subGroups = group.getSubGroups(),
10225
subGroupResults = {},
10226
results = {};
10227
10228
subGroups.forEach((subgroup) => {
10229
subGroupResults[subgroup.getKey()] = this.getGroupResults(subgroup);
10230
});
10231
10232
results = {
10233
top: groupObj.calcs.top ? groupObj.calcs.top.getData() : {},
10234
bottom: groupObj.calcs.bottom ? groupObj.calcs.bottom.getData() : {},
10235
groups: subGroupResults,
10236
};
10237
10238
return results;
10239
}
10240
10241
adjustForScrollbar(width){
10242
if(this.botRow){
10243
if(this.table.rtl){
10244
this.botElement.style.paddingLeft = width + "px";
10245
}else {
10246
this.botElement.style.paddingRight = width + "px";
10247
}
10248
}
10249
}
10250
}
10251
10252
ColumnCalcs.moduleName = "columnCalcs";
10253
10254
//load defaults
10255
ColumnCalcs.calculations = defaultCalculations;
10256
10257
class DataTree extends Module{
10258
10259
constructor(table){
10260
super(table);
10261
10262
this.indent = 10;
10263
this.field = "";
10264
this.collapseEl = null;
10265
this.expandEl = null;
10266
this.branchEl = null;
10267
this.elementField = false;
10268
10269
this.startOpen = function(){};
10270
10271
this.registerTableOption("dataTree", false); //enable data tree
10272
this.registerTableOption("dataTreeFilter", true); //filter child rows
10273
this.registerTableOption("dataTreeSort", true); //sort child rows
10274
this.registerTableOption("dataTreeElementColumn", false);
10275
this.registerTableOption("dataTreeBranchElement", true);//show data tree branch element
10276
this.registerTableOption("dataTreeChildIndent", 9); //data tree child indent in px
10277
this.registerTableOption("dataTreeChildField", "_children");//data tre column field to look for child rows
10278
this.registerTableOption("dataTreeCollapseElement", false);//data tree row collapse element
10279
this.registerTableOption("dataTreeExpandElement", false);//data tree row expand element
10280
this.registerTableOption("dataTreeStartExpanded", false);
10281
this.registerTableOption("dataTreeChildColumnCalcs", false);//include visible data tree rows in column calculations
10282
this.registerTableOption("dataTreeSelectPropagate", false);//selecting a parent row selects its children
10283
10284
//register component functions
10285
this.registerComponentFunction("row", "treeCollapse", this.collapseRow.bind(this));
10286
this.registerComponentFunction("row", "treeExpand", this.expandRow.bind(this));
10287
this.registerComponentFunction("row", "treeToggle", this.toggleRow.bind(this));
10288
this.registerComponentFunction("row", "getTreeParent", this.getTreeParent.bind(this));
10289
this.registerComponentFunction("row", "getTreeChildren", this.getRowChildren.bind(this));
10290
this.registerComponentFunction("row", "addTreeChild", this.addTreeChildRow.bind(this));
10291
this.registerComponentFunction("row", "isTreeExpanded", this.isRowExpanded.bind(this));
10292
}
10293
10294
initialize(){
10295
if(this.table.options.dataTree){
10296
var dummyEl = null,
10297
options = this.table.options;
10298
10299
this.field = options.dataTreeChildField;
10300
this.indent = options.dataTreeChildIndent;
10301
10302
if(this.options("movableRows")){
10303
console.warn("The movableRows option is not available with dataTree enabled, moving of child rows could result in unpredictable behavior");
10304
}
10305
10306
if(options.dataTreeBranchElement){
10307
10308
if(options.dataTreeBranchElement === true){
10309
this.branchEl = document.createElement("div");
10310
this.branchEl.classList.add("tabulator-data-tree-branch");
10311
}else {
10312
if(typeof options.dataTreeBranchElement === "string"){
10313
dummyEl = document.createElement("div");
10314
dummyEl.innerHTML = options.dataTreeBranchElement;
10315
this.branchEl = dummyEl.firstChild;
10316
}else {
10317
this.branchEl = options.dataTreeBranchElement;
10318
}
10319
}
10320
}else {
10321
this.branchEl = document.createElement("div");
10322
this.branchEl.classList.add("tabulator-data-tree-branch-empty");
10323
}
10324
10325
if(options.dataTreeCollapseElement){
10326
if(typeof options.dataTreeCollapseElement === "string"){
10327
dummyEl = document.createElement("div");
10328
dummyEl.innerHTML = options.dataTreeCollapseElement;
10329
this.collapseEl = dummyEl.firstChild;
10330
}else {
10331
this.collapseEl = options.dataTreeCollapseElement;
10332
}
10333
}else {
10334
this.collapseEl = document.createElement("div");
10335
this.collapseEl.classList.add("tabulator-data-tree-control");
10336
this.collapseEl.tabIndex = 0;
10337
this.collapseEl.innerHTML = "<div class='tabulator-data-tree-control-collapse'></div>";
10338
}
10339
10340
if(options.dataTreeExpandElement){
10341
if(typeof options.dataTreeExpandElement === "string"){
10342
dummyEl = document.createElement("div");
10343
dummyEl.innerHTML = options.dataTreeExpandElement;
10344
this.expandEl = dummyEl.firstChild;
10345
}else {
10346
this.expandEl = options.dataTreeExpandElement;
10347
}
10348
}else {
10349
this.expandEl = document.createElement("div");
10350
this.expandEl.classList.add("tabulator-data-tree-control");
10351
this.expandEl.tabIndex = 0;
10352
this.expandEl.innerHTML = "<div class='tabulator-data-tree-control-expand'></div>";
10353
}
10354
10355
10356
switch(typeof options.dataTreeStartExpanded){
10357
case "boolean":
10358
this.startOpen = function(row, index){
10359
return options.dataTreeStartExpanded;
10360
};
10361
break;
10362
10363
case "function":
10364
this.startOpen = options.dataTreeStartExpanded;
10365
break;
10366
10367
default:
10368
this.startOpen = function(row, index){
10369
return options.dataTreeStartExpanded[index];
10370
};
10371
break;
10372
}
10373
10374
this.subscribe("row-init", this.initializeRow.bind(this));
10375
this.subscribe("row-layout-after", this.layoutRow.bind(this));
10376
this.subscribe("row-deleted", this.rowDelete.bind(this),0);
10377
this.subscribe("row-data-changed", this.rowDataChanged.bind(this), 10);
10378
this.subscribe("cell-value-updated", this.cellValueChanged.bind(this));
10379
this.subscribe("edit-cancelled", this.cellValueChanged.bind(this));
10380
this.subscribe("column-moving-rows", this.columnMoving.bind(this));
10381
this.subscribe("table-built", this.initializeElementField.bind(this));
10382
this.subscribe("table-redrawing", this.tableRedrawing.bind(this));
10383
10384
this.registerDisplayHandler(this.getRows.bind(this), 30);
10385
}
10386
}
10387
10388
tableRedrawing(force){
10389
var rows;
10390
10391
if(force){
10392
rows = this.table.rowManager.getRows();
10393
10394
rows.forEach((row) => {
10395
this.reinitializeRowChildren(row);
10396
});
10397
}
10398
}
10399
10400
initializeElementField(){
10401
var firstCol = this.table.columnManager.getFirstVisibleColumn();
10402
10403
this.elementField = this.table.options.dataTreeElementColumn || (firstCol ? firstCol.field : false);
10404
}
10405
10406
getRowChildren(row){
10407
return this.getTreeChildren(row, true);
10408
}
10409
10410
columnMoving(){
10411
var rows = [];
10412
10413
this.table.rowManager.rows.forEach((row) => {
10414
rows = rows.concat(this.getTreeChildren(row, false, true));
10415
});
10416
10417
return rows;
10418
}
10419
10420
rowDataChanged(row, visible, updatedData){
10421
if(this.redrawNeeded(updatedData)){
10422
this.initializeRow(row);
10423
10424
if(visible){
10425
this.layoutRow(row);
10426
this.refreshData(true);
10427
}
10428
}
10429
}
10430
10431
cellValueChanged(cell){
10432
var field = cell.column.getField();
10433
10434
if(field === this.elementField){
10435
this.layoutRow(cell.row);
10436
}
10437
}
10438
10439
initializeRow(row){
10440
var childArray = row.getData()[this.field];
10441
var isArray = Array.isArray(childArray);
10442
10443
var children = isArray || (!isArray && typeof childArray === "object" && childArray !== null);
10444
10445
if(!children && row.modules.dataTree && row.modules.dataTree.branchEl){
10446
row.modules.dataTree.branchEl.parentNode.removeChild(row.modules.dataTree.branchEl);
10447
}
10448
10449
if(!children && row.modules.dataTree && row.modules.dataTree.controlEl){
10450
row.modules.dataTree.controlEl.parentNode.removeChild(row.modules.dataTree.controlEl);
10451
}
10452
10453
row.modules.dataTree = {
10454
index: row.modules.dataTree ? row.modules.dataTree.index : 0,
10455
open: children ? (row.modules.dataTree ? row.modules.dataTree.open : this.startOpen(row.getComponent(), 0)) : false,
10456
controlEl: row.modules.dataTree && children ? row.modules.dataTree.controlEl : false,
10457
branchEl: row.modules.dataTree && children ? row.modules.dataTree.branchEl : false,
10458
parent: row.modules.dataTree ? row.modules.dataTree.parent : false,
10459
children:children,
10460
};
10461
}
10462
10463
reinitializeRowChildren(row){
10464
var children = this.getTreeChildren(row, false, true);
10465
10466
children.forEach(function(child){
10467
child.reinitialize(true);
10468
});
10469
}
10470
10471
layoutRow(row){
10472
var cell = this.elementField ? row.getCell(this.elementField) : row.getCells()[0],
10473
el = cell.getElement(),
10474
config = row.modules.dataTree;
10475
10476
if(config.branchEl){
10477
if(config.branchEl.parentNode){
10478
config.branchEl.parentNode.removeChild(config.branchEl);
10479
}
10480
config.branchEl = false;
10481
}
10482
10483
if(config.controlEl){
10484
if(config.controlEl.parentNode){
10485
config.controlEl.parentNode.removeChild(config.controlEl);
10486
}
10487
config.controlEl = false;
10488
}
10489
10490
this.generateControlElement(row, el);
10491
10492
row.getElement().classList.add("tabulator-tree-level-" + config.index);
10493
10494
if(config.index){
10495
if(this.branchEl){
10496
config.branchEl = this.branchEl.cloneNode(true);
10497
el.insertBefore(config.branchEl, el.firstChild);
10498
10499
if(this.table.rtl){
10500
config.branchEl.style.marginRight = (((config.branchEl.offsetWidth + config.branchEl.style.marginLeft) * (config.index - 1)) + (config.index * this.indent)) + "px";
10501
}else {
10502
config.branchEl.style.marginLeft = (((config.branchEl.offsetWidth + config.branchEl.style.marginRight) * (config.index - 1)) + (config.index * this.indent)) + "px";
10503
}
10504
}else {
10505
10506
if(this.table.rtl){
10507
el.style.paddingRight = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-right')) + (config.index * this.indent) + "px";
10508
}else {
10509
el.style.paddingLeft = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-left')) + (config.index * this.indent) + "px";
10510
}
10511
}
10512
}
10513
}
10514
10515
generateControlElement(row, el){
10516
var config = row.modules.dataTree,
10517
oldControl = config.controlEl;
10518
10519
el = el || row.getCells()[0].getElement();
10520
10521
if(config.children !== false){
10522
10523
if(config.open){
10524
config.controlEl = this.collapseEl.cloneNode(true);
10525
config.controlEl.addEventListener("click", (e) => {
10526
e.stopPropagation();
10527
this.collapseRow(row);
10528
});
10529
}else {
10530
config.controlEl = this.expandEl.cloneNode(true);
10531
config.controlEl.addEventListener("click", (e) => {
10532
e.stopPropagation();
10533
this.expandRow(row);
10534
});
10535
}
10536
10537
config.controlEl.addEventListener("mousedown", (e) => {
10538
e.stopPropagation();
10539
});
10540
10541
if(oldControl && oldControl.parentNode === el){
10542
oldControl.parentNode.replaceChild(config.controlEl,oldControl);
10543
}else {
10544
el.insertBefore(config.controlEl, el.firstChild);
10545
}
10546
}
10547
}
10548
10549
getRows(rows){
10550
var output = [];
10551
10552
rows.forEach((row, i) => {
10553
var config, children;
10554
10555
output.push(row);
10556
10557
if(row instanceof Row){
10558
10559
row.create();
10560
10561
config = row.modules.dataTree;
10562
10563
if(!config.index && config.children !== false){
10564
children = this.getChildren(row);
10565
10566
children.forEach((child) => {
10567
child.create();
10568
output.push(child);
10569
});
10570
}
10571
}
10572
});
10573
10574
return output;
10575
}
10576
10577
getChildren(row, allChildren){
10578
var config = row.modules.dataTree,
10579
children = [],
10580
output = [];
10581
10582
if(config.children !== false && (config.open || allChildren)){
10583
if(!Array.isArray(config.children)){
10584
config.children = this.generateChildren(row);
10585
}
10586
10587
if(this.table.modExists("filter") && this.table.options.dataTreeFilter){
10588
children = this.table.modules.filter.filter(config.children);
10589
}else {
10590
children = config.children;
10591
}
10592
10593
if(this.table.modExists("sort") && this.table.options.dataTreeSort){
10594
this.table.modules.sort.sort(children);
10595
}
10596
10597
children.forEach((child) => {
10598
output.push(child);
10599
10600
var subChildren = this.getChildren(child);
10601
10602
subChildren.forEach((sub) => {
10603
output.push(sub);
10604
});
10605
});
10606
}
10607
10608
return output;
10609
}
10610
10611
generateChildren(row){
10612
var children = [];
10613
10614
var childArray = row.getData()[this.field];
10615
10616
if(!Array.isArray(childArray)){
10617
childArray = [childArray];
10618
}
10619
10620
childArray.forEach((childData) => {
10621
var childRow = new Row(childData || {}, this.table.rowManager);
10622
10623
childRow.create();
10624
10625
childRow.modules.dataTree.index = row.modules.dataTree.index + 1;
10626
childRow.modules.dataTree.parent = row;
10627
10628
if(childRow.modules.dataTree.children){
10629
childRow.modules.dataTree.open = this.startOpen(childRow.getComponent(), childRow.modules.dataTree.index);
10630
}
10631
children.push(childRow);
10632
});
10633
10634
return children;
10635
}
10636
10637
expandRow(row, silent){
10638
var config = row.modules.dataTree;
10639
10640
if(config.children !== false){
10641
config.open = true;
10642
10643
row.reinitialize();
10644
10645
this.refreshData(true);
10646
10647
this.dispatchExternal("dataTreeRowExpanded", row.getComponent(), row.modules.dataTree.index);
10648
}
10649
}
10650
10651
collapseRow(row){
10652
var config = row.modules.dataTree;
10653
10654
if(config.children !== false){
10655
config.open = false;
10656
10657
row.reinitialize();
10658
10659
this.refreshData(true);
10660
10661
this.dispatchExternal("dataTreeRowCollapsed", row.getComponent(), row.modules.dataTree.index);
10662
}
10663
}
10664
10665
toggleRow(row){
10666
var config = row.modules.dataTree;
10667
10668
if(config.children !== false){
10669
if(config.open){
10670
this.collapseRow(row);
10671
}else {
10672
this.expandRow(row);
10673
}
10674
}
10675
}
10676
10677
isRowExpanded(row){
10678
return row.modules.dataTree.open;
10679
}
10680
10681
getTreeParent(row){
10682
return row.modules.dataTree.parent ? row.modules.dataTree.parent.getComponent() : false;
10683
}
10684
10685
getTreeParentRoot(row){
10686
return row.modules.dataTree && row.modules.dataTree.parent ? this.getTreeParentRoot(row.modules.dataTree.parent) : row;
10687
}
10688
10689
getFilteredTreeChildren(row){
10690
var config = row.modules.dataTree,
10691
output = [], children;
10692
10693
if(config.children){
10694
10695
if(!Array.isArray(config.children)){
10696
config.children = this.generateChildren(row);
10697
}
10698
10699
if(this.table.modExists("filter") && this.table.options.dataTreeFilter){
10700
children = this.table.modules.filter.filter(config.children);
10701
}else {
10702
children = config.children;
10703
}
10704
10705
children.forEach((childRow) => {
10706
if(childRow instanceof Row){
10707
output.push(childRow);
10708
}
10709
});
10710
}
10711
10712
return output;
10713
}
10714
10715
rowDelete(row){
10716
var parent = row.modules.dataTree.parent,
10717
childIndex;
10718
10719
if(parent){
10720
childIndex = this.findChildIndex(row, parent);
10721
10722
if(childIndex !== false){
10723
parent.data[this.field].splice(childIndex, 1);
10724
}
10725
10726
if(!parent.data[this.field].length){
10727
delete parent.data[this.field];
10728
}
10729
10730
this.initializeRow(parent);
10731
this.layoutRow(parent);
10732
}
10733
10734
this.refreshData(true);
10735
}
10736
10737
addTreeChildRow(row, data, top, index){
10738
var childIndex = false;
10739
10740
if(typeof data === "string"){
10741
data = JSON.parse(data);
10742
}
10743
10744
if(!Array.isArray(row.data[this.field])){
10745
row.data[this.field] = [];
10746
10747
row.modules.dataTree.open = this.startOpen(row.getComponent(), row.modules.dataTree.index);
10748
}
10749
10750
if(typeof index !== "undefined"){
10751
childIndex = this.findChildIndex(index, row);
10752
10753
if(childIndex !== false){
10754
row.data[this.field].splice((top ? childIndex : childIndex + 1), 0, data);
10755
}
10756
}
10757
10758
if(childIndex === false){
10759
if(top){
10760
row.data[this.field].unshift(data);
10761
}else {
10762
row.data[this.field].push(data);
10763
}
10764
}
10765
10766
this.initializeRow(row);
10767
this.layoutRow(row);
10768
10769
this.refreshData(true);
10770
}
10771
10772
findChildIndex(subject, parent){
10773
var match = false;
10774
10775
if(typeof subject == "object"){
10776
10777
if(subject instanceof Row){
10778
//subject is row element
10779
match = subject.data;
10780
}else if(subject instanceof RowComponent){
10781
//subject is public row component
10782
match = subject._getSelf().data;
10783
}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
10784
if(parent.modules.dataTree){
10785
match = parent.modules.dataTree.children.find((childRow) => {
10786
return childRow instanceof Row ? childRow.element === subject : false;
10787
});
10788
10789
if(match){
10790
match = match.data;
10791
}
10792
}
10793
}else if(subject === null){
10794
match = false;
10795
}
10796
10797
}else if(typeof subject == "undefined"){
10798
match = false;
10799
}else {
10800
//subject should be treated as the index of the row
10801
match = parent.data[this.field].find((row) => {
10802
return row.data[this.table.options.index] == subject;
10803
});
10804
}
10805
10806
if(match){
10807
10808
if(Array.isArray(parent.data[this.field])){
10809
match = parent.data[this.field].indexOf(match);
10810
}
10811
10812
if(match == -1){
10813
match = false;
10814
}
10815
}
10816
10817
//catch all for any other type of input
10818
10819
return match;
10820
}
10821
10822
getTreeChildren(row, component, recurse){
10823
var config = row.modules.dataTree,
10824
output = [];
10825
10826
if(config && config.children){
10827
10828
if(!Array.isArray(config.children)){
10829
config.children = this.generateChildren(row);
10830
}
10831
10832
config.children.forEach((childRow) => {
10833
if(childRow instanceof Row){
10834
output.push(component ? childRow.getComponent() : childRow);
10835
10836
if(recurse){
10837
output = output.concat(this.getTreeChildren(childRow, component, recurse));
10838
}
10839
}
10840
});
10841
}
10842
10843
return output;
10844
}
10845
10846
getChildField(){
10847
return this.field;
10848
}
10849
10850
redrawNeeded(data){
10851
return (this.field ? typeof data[this.field] !== "undefined" : false) || (this.elementField ? typeof data[this.elementField] !== "undefined" : false);
10852
}
10853
}
10854
10855
DataTree.moduleName = "dataTree";
10856
10857
function csv(list, options = {}, setFileContents){
10858
var delimiter = options.delimiter ? options.delimiter : ",",
10859
fileContents = [],
10860
headers = [];
10861
10862
list.forEach((row) => {
10863
var item = [];
10864
10865
switch(row.type){
10866
case "group":
10867
console.warn("Download Warning - CSV downloader cannot process row groups");
10868
break;
10869
10870
case "calc":
10871
console.warn("Download Warning - CSV downloader cannot process column calculations");
10872
break;
10873
10874
case "header":
10875
row.columns.forEach((col, i) => {
10876
if(col && col.depth === 1){
10877
headers[i] = typeof col.value == "undefined" || col.value === null ? "" : ('"' + String(col.value).split('"').join('""') + '"');
10878
}
10879
});
10880
break;
10881
10882
case "row":
10883
row.columns.forEach((col) => {
10884
10885
if(col){
10886
10887
switch(typeof col.value){
10888
case "object":
10889
col.value = col.value !== null ? JSON.stringify(col.value) : "";
10890
break;
10891
10892
case "undefined":
10893
col.value = "";
10894
break;
10895
}
10896
10897
item.push('"' + String(col.value).split('"').join('""') + '"');
10898
}
10899
});
10900
10901
fileContents.push(item.join(delimiter));
10902
break;
10903
}
10904
});
10905
10906
if(headers.length){
10907
fileContents.unshift(headers.join(delimiter));
10908
}
10909
10910
fileContents = fileContents.join("\n");
10911
10912
if(options.bom){
10913
fileContents = "\ufeff" + fileContents;
10914
}
10915
10916
setFileContents(fileContents, "text/csv");
10917
}
10918
10919
function json(list, options, setFileContents){
10920
var fileContents = [];
10921
10922
list.forEach((row) => {
10923
var item = {};
10924
10925
switch(row.type){
10926
case "header":
10927
break;
10928
10929
case "group":
10930
console.warn("Download Warning - JSON downloader cannot process row groups");
10931
break;
10932
10933
case "calc":
10934
console.warn("Download Warning - JSON downloader cannot process column calculations");
10935
break;
10936
10937
case "row":
10938
row.columns.forEach((col) => {
10939
if(col){
10940
item[col.component.getTitleDownload() || col.component.getField()] = col.value;
10941
}
10942
});
10943
10944
fileContents.push(item);
10945
break;
10946
}
10947
});
10948
10949
fileContents = JSON.stringify(fileContents, null, '\t');
10950
10951
setFileContents(fileContents, "application/json");
10952
}
10953
10954
function pdf(list, options = {}, setFileContents){
10955
var header = [],
10956
body = [],
10957
autoTableParams = {},
10958
rowGroupStyles = options.rowGroupStyles || {
10959
fontStyle: "bold",
10960
fontSize: 12,
10961
cellPadding: 6,
10962
fillColor: 220,
10963
},
10964
rowCalcStyles = options.rowCalcStyles || {
10965
fontStyle: "bold",
10966
fontSize: 10,
10967
cellPadding: 4,
10968
fillColor: 232,
10969
},
10970
jsPDFParams = options.jsPDF || {},
10971
title = options.title ? options.title : "";
10972
10973
if(!jsPDFParams.orientation){
10974
jsPDFParams.orientation = options.orientation || "landscape";
10975
}
10976
10977
if(!jsPDFParams.unit){
10978
jsPDFParams.unit = "pt";
10979
}
10980
10981
//parse row list
10982
list.forEach((row) => {
10983
switch(row.type){
10984
case "header":
10985
header.push(parseRow(row));
10986
break;
10987
10988
case "group":
10989
body.push(parseRow(row, rowGroupStyles));
10990
break;
10991
10992
case "calc":
10993
body.push(parseRow(row, rowCalcStyles));
10994
break;
10995
10996
case "row":
10997
body.push(parseRow(row));
10998
break;
10999
}
11000
});
11001
11002
function parseRow(row, styles){
11003
var rowData = [];
11004
11005
row.columns.forEach((col) =>{
11006
var cell;
11007
11008
if(col){
11009
switch(typeof col.value){
11010
case "object":
11011
col.value = col.value !== null ? JSON.stringify(col.value) : "";
11012
break;
11013
11014
case "undefined":
11015
col.value = "";
11016
break;
11017
}
11018
11019
cell = {
11020
content:col.value,
11021
colSpan:col.width,
11022
rowSpan:col.height,
11023
};
11024
11025
if(styles){
11026
cell.styles = styles;
11027
}
11028
11029
rowData.push(cell);
11030
}
11031
});
11032
11033
return rowData;
11034
}
11035
11036
11037
//configure PDF
11038
var doc = new jspdf.jsPDF(jsPDFParams); //set document to landscape, better for most tables
11039
11040
if(options.autoTable){
11041
if(typeof options.autoTable === "function"){
11042
autoTableParams = options.autoTable(doc) || {};
11043
}else {
11044
autoTableParams = options.autoTable;
11045
}
11046
}
11047
11048
if(title){
11049
autoTableParams.didDrawPage = function(data) {
11050
doc.text(title, 40, 30);
11051
};
11052
}
11053
11054
autoTableParams.head = header;
11055
autoTableParams.body = body;
11056
11057
doc.autoTable(autoTableParams);
11058
11059
if(options.documentProcessing){
11060
options.documentProcessing(doc);
11061
}
11062
11063
setFileContents(doc.output("arraybuffer"), "application/pdf");
11064
}
11065
11066
function xlsx(list, options, setFileContents){
11067
var self = this,
11068
sheetName = options.sheetName || "Sheet1",
11069
workbook = XLSX.utils.book_new(),
11070
tableFeatures = new CoreFeature(this),
11071
compression = 'compress' in options ? options.compress : true,
11072
writeOptions = options.writeOptions || {bookType:'xlsx', bookSST:true, compression},
11073
output;
11074
11075
writeOptions.type = 'binary';
11076
11077
workbook.SheetNames = [];
11078
workbook.Sheets = {};
11079
11080
function generateSheet(){
11081
var rows = [],
11082
merges = [],
11083
worksheet = {},
11084
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 }};
11085
11086
//parse row list
11087
list.forEach((row, i) => {
11088
var rowData = [];
11089
11090
row.columns.forEach(function(col, j){
11091
11092
if(col){
11093
rowData.push(!(col.value instanceof Date) && typeof col.value === "object" ? JSON.stringify(col.value) : col.value);
11094
11095
if(col.width > 1 || col.height > -1){
11096
if(col.height > 1 || col.width > 1){
11097
merges.push({s:{r:i,c:j},e:{r:i + col.height - 1,c:j + col.width - 1}});
11098
}
11099
}
11100
}else {
11101
rowData.push("");
11102
}
11103
});
11104
11105
rows.push(rowData);
11106
});
11107
11108
//convert rows to worksheet
11109
XLSX.utils.sheet_add_aoa(worksheet, rows);
11110
11111
worksheet['!ref'] = XLSX.utils.encode_range(range);
11112
11113
if(merges.length){
11114
worksheet["!merges"] = merges;
11115
}
11116
11117
return worksheet;
11118
}
11119
11120
if(options.sheetOnly){
11121
setFileContents(generateSheet());
11122
return;
11123
}
11124
11125
if(options.sheets){
11126
for(var sheet in options.sheets){
11127
11128
if(options.sheets[sheet] === true){
11129
workbook.SheetNames.push(sheet);
11130
workbook.Sheets[sheet] = generateSheet();
11131
}else {
11132
11133
workbook.SheetNames.push(sheet);
11134
11135
tableFeatures.commsSend(options.sheets[sheet], "download", "intercept",{
11136
type:"xlsx",
11137
options:{sheetOnly:true},
11138
active:self.active,
11139
intercept:function(data){
11140
workbook.Sheets[sheet] = data;
11141
}
11142
});
11143
}
11144
}
11145
}else {
11146
workbook.SheetNames.push(sheetName);
11147
workbook.Sheets[sheetName] = generateSheet();
11148
}
11149
11150
if(options.documentProcessing){
11151
workbook = options.documentProcessing(workbook);
11152
}
11153
11154
//convert workbook to binary array
11155
function s2ab(s) {
11156
var buf = new ArrayBuffer(s.length);
11157
var view = new Uint8Array(buf);
11158
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
11159
return buf;
11160
}
11161
11162
output = XLSX.write(workbook, writeOptions);
11163
11164
setFileContents(s2ab(output), "application/octet-stream");
11165
}
11166
11167
function html(list, options, setFileContents){
11168
if(this.modExists("export", true)){
11169
setFileContents(this.modules.export.generateHTMLTable(list), "text/html");
11170
}
11171
}
11172
11173
function jsonLines (list, options, setFileContents) {
11174
const fileContents = [];
11175
11176
list.forEach((row) => {
11177
const item = {};
11178
11179
switch (row.type) {
11180
case "header":
11181
break;
11182
11183
case "group":
11184
console.warn("Download Warning - JSON downloader cannot process row groups");
11185
break;
11186
11187
case "calc":
11188
console.warn("Download Warning - JSON downloader cannot process column calculations");
11189
break;
11190
11191
case "row":
11192
row.columns.forEach((col) => {
11193
if (col) {
11194
item[col.component.getTitleDownload() || col.component.getField()] = col.value;
11195
}
11196
});
11197
11198
fileContents.push(JSON.stringify(item));
11199
break;
11200
}
11201
});
11202
11203
setFileContents(fileContents.join("\n"), "application/x-ndjson");
11204
}
11205
11206
var defaultDownloaders = {
11207
csv:csv,
11208
json:json,
11209
jsonLines:jsonLines,
11210
pdf:pdf,
11211
xlsx:xlsx,
11212
html:html,
11213
};
11214
11215
class Download extends Module{
11216
11217
constructor(table){
11218
super(table);
11219
11220
this.registerTableOption("downloadEncoder", function(data, mimeType){
11221
return new Blob([data],{type:mimeType});
11222
}); //function to manipulate download data
11223
this.registerTableOption("downloadReady", undefined); //warn of function deprecation
11224
this.registerTableOption("downloadConfig", {}); //download config
11225
this.registerTableOption("downloadRowRange", "active"); //restrict download to active rows only
11226
11227
this.registerColumnOption("download");
11228
this.registerColumnOption("titleDownload");
11229
}
11230
11231
initialize(){
11232
this.deprecatedOptionsCheck();
11233
11234
this.registerTableFunction("download", this.download.bind(this));
11235
this.registerTableFunction("downloadToTab", this.downloadToTab.bind(this));
11236
}
11237
11238
deprecatedOptionsCheck(){
11239
this.deprecationCheck("downloadReady", "downloadEncoder");
11240
}
11241
11242
///////////////////////////////////
11243
///////// Table Functions /////////
11244
///////////////////////////////////
11245
11246
downloadToTab(type, filename, options, active){
11247
this.download(type, filename, options, active, true);
11248
}
11249
11250
///////////////////////////////////
11251
///////// Internal Logic //////////
11252
///////////////////////////////////
11253
11254
//trigger file download
11255
download(type, filename, options, range, interceptCallback){
11256
var downloadFunc = false;
11257
11258
function buildLink(data, mime){
11259
if(interceptCallback){
11260
if(interceptCallback === true){
11261
this.triggerDownload(data, mime, type, filename, true);
11262
}else {
11263
interceptCallback(data);
11264
}
11265
11266
}else {
11267
this.triggerDownload(data, mime, type, filename);
11268
}
11269
}
11270
11271
if(typeof type == "function"){
11272
downloadFunc = type;
11273
}else {
11274
if(Download.downloaders[type]){
11275
downloadFunc = Download.downloaders[type];
11276
}else {
11277
console.warn("Download Error - No such download type found: ", type);
11278
}
11279
}
11280
11281
if(downloadFunc){
11282
var list = this.generateExportList(range);
11283
11284
downloadFunc.call(this.table, list , options || {}, buildLink.bind(this));
11285
}
11286
}
11287
11288
generateExportList(range){
11289
var list = this.table.modules.export.generateExportList(this.table.options.downloadConfig, false, range || this.table.options.downloadRowRange, "download");
11290
11291
//assign group header formatter
11292
var groupHeader = this.table.options.groupHeaderDownload;
11293
11294
if(groupHeader && !Array.isArray(groupHeader)){
11295
groupHeader = [groupHeader];
11296
}
11297
11298
list.forEach((row) => {
11299
var group;
11300
11301
if(row.type === "group"){
11302
group = row.columns[0];
11303
11304
if(groupHeader && groupHeader[row.indent]){
11305
group.value = groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
11306
}
11307
}
11308
});
11309
11310
return list;
11311
}
11312
11313
triggerDownload(data, mime, type, filename, newTab){
11314
var element = document.createElement('a'),
11315
blob = this.table.options.downloadEncoder(data, mime);
11316
11317
if(blob){
11318
if(newTab){
11319
window.open(window.URL.createObjectURL(blob));
11320
}else {
11321
filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type);
11322
11323
if(navigator.msSaveOrOpenBlob){
11324
navigator.msSaveOrOpenBlob(blob, filename);
11325
}else {
11326
element.setAttribute('href', window.URL.createObjectURL(blob));
11327
11328
//set file title
11329
element.setAttribute('download', filename);
11330
11331
//trigger download
11332
element.style.display = 'none';
11333
document.body.appendChild(element);
11334
element.click();
11335
11336
//remove temporary link element
11337
document.body.removeChild(element);
11338
}
11339
}
11340
11341
this.dispatchExternal("downloadComplete");
11342
}
11343
}
11344
11345
commsReceived(table, action, data){
11346
switch(action){
11347
case "intercept":
11348
this.download(data.type, "", data.options, data.active, data.intercept);
11349
break;
11350
}
11351
}
11352
}
11353
11354
Download.moduleName = "download";
11355
11356
//load defaults
11357
Download.downloaders = defaultDownloaders;
11358
11359
function maskInput(el, options){
11360
var mask = options.mask,
11361
maskLetter = typeof options.maskLetterChar !== "undefined" ? options.maskLetterChar : "A",
11362
maskNumber = typeof options.maskNumberChar !== "undefined" ? options.maskNumberChar : "9",
11363
maskWildcard = typeof options.maskWildcardChar !== "undefined" ? options.maskWildcardChar : "*";
11364
11365
function fillSymbols(index){
11366
var symbol = mask[index];
11367
if(typeof symbol !== "undefined" && symbol !== maskWildcard && symbol !== maskLetter && symbol !== maskNumber){
11368
el.value = el.value + "" + symbol;
11369
fillSymbols(index+1);
11370
}
11371
}
11372
11373
el.addEventListener("keydown", (e) => {
11374
var index = el.value.length,
11375
char = e.key;
11376
11377
if(e.keyCode > 46 && !e.ctrlKey && !e.metaKey){
11378
if(index >= mask.length){
11379
e.preventDefault();
11380
e.stopPropagation();
11381
return false;
11382
}else {
11383
switch(mask[index]){
11384
case maskLetter:
11385
if(char.toUpperCase() == char.toLowerCase()){
11386
e.preventDefault();
11387
e.stopPropagation();
11388
return false;
11389
}
11390
break;
11391
11392
case maskNumber:
11393
if(isNaN(char)){
11394
e.preventDefault();
11395
e.stopPropagation();
11396
return false;
11397
}
11398
break;
11399
11400
case maskWildcard:
11401
break;
11402
11403
default:
11404
if(char !== mask[index]){
11405
e.preventDefault();
11406
e.stopPropagation();
11407
return false;
11408
}
11409
}
11410
}
11411
}
11412
11413
return;
11414
});
11415
11416
el.addEventListener("keyup", (e) => {
11417
if(e.keyCode > 46){
11418
if(options.maskAutoFill){
11419
fillSymbols(el.value.length);
11420
}
11421
}
11422
});
11423
11424
11425
if(!el.placeholder){
11426
el.placeholder = mask;
11427
}
11428
11429
if(options.maskAutoFill){
11430
fillSymbols(el.value.length);
11431
}
11432
}
11433
11434
//input element
11435
function input(cell, onRendered, success, cancel, editorParams){
11436
//create and style input
11437
var cellValue = cell.getValue(),
11438
input = document.createElement("input");
11439
11440
input.setAttribute("type", editorParams.search ? "search" : "text");
11441
11442
input.style.padding = "4px";
11443
input.style.width = "100%";
11444
input.style.boxSizing = "border-box";
11445
11446
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11447
for (let key in editorParams.elementAttributes){
11448
if(key.charAt(0) == "+"){
11449
key = key.slice(1);
11450
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11451
}else {
11452
input.setAttribute(key, editorParams.elementAttributes[key]);
11453
}
11454
}
11455
}
11456
11457
input.value = typeof cellValue !== "undefined" ? cellValue : "";
11458
11459
onRendered(function(){
11460
if(cell.getType() === "cell"){
11461
input.focus({preventScroll: true});
11462
input.style.height = "100%";
11463
11464
if(editorParams.selectContents){
11465
input.select();
11466
}
11467
}
11468
});
11469
11470
function onChange(e){
11471
if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){
11472
if(success(input.value)){
11473
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
11474
}
11475
}else {
11476
cancel();
11477
}
11478
}
11479
11480
//submit new value on blur or change
11481
input.addEventListener("change", onChange);
11482
input.addEventListener("blur", onChange);
11483
11484
//submit new value on enter
11485
input.addEventListener("keydown", function(e){
11486
switch(e.keyCode){
11487
// case 9:
11488
case 13:
11489
onChange();
11490
break;
11491
11492
case 27:
11493
cancel();
11494
break;
11495
11496
case 35:
11497
case 36:
11498
e.stopPropagation();
11499
break;
11500
}
11501
});
11502
11503
if(editorParams.mask){
11504
maskInput(input, editorParams);
11505
}
11506
11507
return input;
11508
}
11509
11510
//resizable text area element
11511
function textarea(cell, onRendered, success, cancel, editorParams){
11512
var cellValue = cell.getValue(),
11513
vertNav = editorParams.verticalNavigation || "hybrid",
11514
value = String(cellValue !== null && typeof cellValue !== "undefined" ? cellValue : ""),
11515
input = document.createElement("textarea"),
11516
scrollHeight = 0;
11517
11518
//create and style input
11519
input.style.display = "block";
11520
input.style.padding = "2px";
11521
input.style.height = "100%";
11522
input.style.width = "100%";
11523
input.style.boxSizing = "border-box";
11524
input.style.whiteSpace = "pre-wrap";
11525
input.style.resize = "none";
11526
11527
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11528
for (let key in editorParams.elementAttributes){
11529
if(key.charAt(0) == "+"){
11530
key = key.slice(1);
11531
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11532
}else {
11533
input.setAttribute(key, editorParams.elementAttributes[key]);
11534
}
11535
}
11536
}
11537
11538
input.value = value;
11539
11540
onRendered(function(){
11541
if(cell.getType() === "cell"){
11542
input.focus({preventScroll: true});
11543
input.style.height = "100%";
11544
11545
input.scrollHeight;
11546
input.style.height = input.scrollHeight + "px";
11547
cell.getRow().normalizeHeight();
11548
11549
if(editorParams.selectContents){
11550
input.select();
11551
}
11552
}
11553
});
11554
11555
function onChange(e){
11556
11557
if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){
11558
11559
if(success(input.value)){
11560
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
11561
}
11562
11563
setTimeout(function(){
11564
cell.getRow().normalizeHeight();
11565
},300);
11566
}else {
11567
cancel();
11568
}
11569
}
11570
11571
//submit new value on blur or change
11572
input.addEventListener("change", onChange);
11573
input.addEventListener("blur", onChange);
11574
11575
input.addEventListener("keyup", function(){
11576
11577
input.style.height = "";
11578
11579
var heightNow = input.scrollHeight;
11580
11581
input.style.height = heightNow + "px";
11582
11583
if(heightNow != scrollHeight){
11584
scrollHeight = heightNow;
11585
cell.getRow().normalizeHeight();
11586
}
11587
});
11588
11589
input.addEventListener("keydown", function(e){
11590
11591
switch(e.keyCode){
11592
11593
case 13:
11594
if(e.shiftKey && editorParams.shiftEnterSubmit){
11595
onChange();
11596
}
11597
break;
11598
11599
case 27:
11600
cancel();
11601
break;
11602
11603
case 38: //up arrow
11604
if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart)){
11605
e.stopImmediatePropagation();
11606
e.stopPropagation();
11607
}
11608
11609
break;
11610
11611
case 40: //down arrow
11612
if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart !== input.value.length)){
11613
e.stopImmediatePropagation();
11614
e.stopPropagation();
11615
}
11616
break;
11617
11618
case 35:
11619
case 36:
11620
e.stopPropagation();
11621
break;
11622
}
11623
});
11624
11625
if(editorParams.mask){
11626
maskInput(input, editorParams);
11627
}
11628
11629
return input;
11630
}
11631
11632
//input element with type of number
11633
function number(cell, onRendered, success, cancel, editorParams){
11634
var cellValue = cell.getValue(),
11635
vertNav = editorParams.verticalNavigation || "editor",
11636
input = document.createElement("input");
11637
11638
input.setAttribute("type", "number");
11639
11640
if(typeof editorParams.max != "undefined"){
11641
input.setAttribute("max", editorParams.max);
11642
}
11643
11644
if(typeof editorParams.min != "undefined"){
11645
input.setAttribute("min", editorParams.min);
11646
}
11647
11648
if(typeof editorParams.step != "undefined"){
11649
input.setAttribute("step", editorParams.step);
11650
}
11651
11652
//create and style input
11653
input.style.padding = "4px";
11654
input.style.width = "100%";
11655
input.style.boxSizing = "border-box";
11656
11657
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11658
for (let key in editorParams.elementAttributes){
11659
if(key.charAt(0) == "+"){
11660
key = key.slice(1);
11661
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11662
}else {
11663
input.setAttribute(key, editorParams.elementAttributes[key]);
11664
}
11665
}
11666
}
11667
11668
input.value = cellValue;
11669
11670
var blurFunc = function(e){
11671
onChange();
11672
};
11673
11674
onRendered(function () {
11675
if(cell.getType() === "cell"){
11676
//submit new value on blur
11677
input.removeEventListener("blur", blurFunc);
11678
11679
input.focus({preventScroll: true});
11680
input.style.height = "100%";
11681
11682
//submit new value on blur
11683
input.addEventListener("blur", blurFunc);
11684
11685
if(editorParams.selectContents){
11686
input.select();
11687
}
11688
}
11689
});
11690
11691
function onChange(){
11692
var value = input.value;
11693
11694
if(!isNaN(value) && value !==""){
11695
value = Number(value);
11696
}
11697
11698
if(value !== cellValue){
11699
if(success(value)){
11700
cellValue = value; //persist value if successfully validated incase editor is used as header filter
11701
}
11702
}else {
11703
cancel();
11704
}
11705
}
11706
11707
//submit new value on enter
11708
input.addEventListener("keydown", function(e){
11709
switch(e.keyCode){
11710
case 13:
11711
// case 9:
11712
onChange();
11713
break;
11714
11715
case 27:
11716
cancel();
11717
break;
11718
11719
case 38: //up arrow
11720
case 40: //down arrow
11721
if(vertNav == "editor"){
11722
e.stopImmediatePropagation();
11723
e.stopPropagation();
11724
}
11725
break;
11726
11727
case 35:
11728
case 36:
11729
e.stopPropagation();
11730
break;
11731
}
11732
});
11733
11734
if(editorParams.mask){
11735
maskInput(input, editorParams);
11736
}
11737
11738
return input;
11739
}
11740
11741
//input element with type of number
11742
function range(cell, onRendered, success, cancel, editorParams){
11743
var cellValue = cell.getValue(),
11744
input = document.createElement("input");
11745
11746
input.setAttribute("type", "range");
11747
11748
if (typeof editorParams.max != "undefined") {
11749
input.setAttribute("max", editorParams.max);
11750
}
11751
11752
if (typeof editorParams.min != "undefined") {
11753
input.setAttribute("min", editorParams.min);
11754
}
11755
11756
if (typeof editorParams.step != "undefined") {
11757
input.setAttribute("step", editorParams.step);
11758
}
11759
11760
//create and style input
11761
input.style.padding = "4px";
11762
input.style.width = "100%";
11763
input.style.boxSizing = "border-box";
11764
11765
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11766
for (let key in editorParams.elementAttributes){
11767
if(key.charAt(0) == "+"){
11768
key = key.slice(1);
11769
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11770
}else {
11771
input.setAttribute(key, editorParams.elementAttributes[key]);
11772
}
11773
}
11774
}
11775
11776
input.value = cellValue;
11777
11778
onRendered(function () {
11779
if(cell.getType() === "cell"){
11780
input.focus({preventScroll: true});
11781
input.style.height = "100%";
11782
}
11783
});
11784
11785
function onChange(){
11786
var value = input.value;
11787
11788
if(!isNaN(value) && value !==""){
11789
value = Number(value);
11790
}
11791
11792
if(value != cellValue){
11793
if(success(value)){
11794
cellValue = value; //persist value if successfully validated incase editor is used as header filter
11795
}
11796
}else {
11797
cancel();
11798
}
11799
}
11800
11801
//submit new value on blur
11802
input.addEventListener("blur", function(e){
11803
onChange();
11804
});
11805
11806
//submit new value on enter
11807
input.addEventListener("keydown", function(e){
11808
switch(e.keyCode){
11809
case 13:
11810
// case 9:
11811
onChange();
11812
break;
11813
11814
case 27:
11815
cancel();
11816
break;
11817
}
11818
});
11819
11820
return input;
11821
}
11822
11823
//input element
11824
function date(cell, onRendered, success, cancel, editorParams){
11825
var inputFormat = editorParams.format,
11826
vertNav = editorParams.verticalNavigation || "editor",
11827
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null;
11828
11829
//create and style input
11830
var cellValue = cell.getValue(),
11831
input = document.createElement("input");
11832
11833
function convertDate(value){
11834
var newDatetime;
11835
11836
if(DT.isDateTime(value)){
11837
newDatetime = value;
11838
}else if(inputFormat === "iso"){
11839
newDatetime = DT.fromISO(String(value));
11840
}else {
11841
newDatetime = DT.fromFormat(String(value), inputFormat);
11842
}
11843
11844
return newDatetime.toFormat("yyyy-MM-dd");
11845
}
11846
11847
input.type = "date";
11848
input.style.padding = "4px";
11849
input.style.width = "100%";
11850
input.style.boxSizing = "border-box";
11851
11852
if(editorParams.max){
11853
input.setAttribute("max", inputFormat ? convertDate(editorParams.max) : editorParams.max);
11854
}
11855
11856
if(editorParams.min){
11857
input.setAttribute("min", inputFormat ? convertDate(editorParams.min) : editorParams.min);
11858
}
11859
11860
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11861
for (let key in editorParams.elementAttributes){
11862
if(key.charAt(0) == "+"){
11863
key = key.slice(1);
11864
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11865
}else {
11866
input.setAttribute(key, editorParams.elementAttributes[key]);
11867
}
11868
}
11869
}
11870
11871
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
11872
11873
if(inputFormat){
11874
if(DT){
11875
cellValue = convertDate(cellValue);
11876
}else {
11877
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
11878
}
11879
}
11880
11881
input.value = cellValue;
11882
11883
onRendered(function(){
11884
if(cell.getType() === "cell"){
11885
input.focus({preventScroll: true});
11886
input.style.height = "100%";
11887
11888
if(editorParams.selectContents){
11889
input.select();
11890
}
11891
}
11892
});
11893
11894
function onChange(){
11895
var value = input.value,
11896
luxDate;
11897
11898
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
11899
11900
if(value && inputFormat){
11901
luxDate = DT.fromFormat(String(value), "yyyy-MM-dd");
11902
11903
switch(inputFormat){
11904
case true:
11905
value = luxDate;
11906
break;
11907
11908
case "iso":
11909
value = luxDate.toISO();
11910
break;
11911
11912
default:
11913
value = luxDate.toFormat(inputFormat);
11914
}
11915
}
11916
11917
if(success(value)){
11918
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
11919
}
11920
}else {
11921
cancel();
11922
}
11923
}
11924
11925
//submit new value on blur
11926
input.addEventListener("blur", function(e) {
11927
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
11928
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
11929
}
11930
});
11931
11932
//submit new value on enter
11933
input.addEventListener("keydown", function(e){
11934
switch(e.keyCode){
11935
// case 9:
11936
case 13:
11937
onChange();
11938
break;
11939
11940
case 27:
11941
cancel();
11942
break;
11943
11944
case 35:
11945
case 36:
11946
e.stopPropagation();
11947
break;
11948
11949
case 38: //up arrow
11950
case 40: //down arrow
11951
if(vertNav == "editor"){
11952
e.stopImmediatePropagation();
11953
e.stopPropagation();
11954
}
11955
break;
11956
}
11957
});
11958
11959
return input;
11960
}
11961
11962
//input element
11963
function time(cell, onRendered, success, cancel, editorParams){
11964
var inputFormat = editorParams.format,
11965
vertNav = editorParams.verticalNavigation || "editor",
11966
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null,
11967
newDatetime;
11968
11969
//create and style input
11970
var cellValue = cell.getValue(),
11971
input = document.createElement("input");
11972
11973
input.type = "time";
11974
input.style.padding = "4px";
11975
input.style.width = "100%";
11976
input.style.boxSizing = "border-box";
11977
11978
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
11979
for (let key in editorParams.elementAttributes){
11980
if(key.charAt(0) == "+"){
11981
key = key.slice(1);
11982
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
11983
}else {
11984
input.setAttribute(key, editorParams.elementAttributes[key]);
11985
}
11986
}
11987
}
11988
11989
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
11990
11991
if(inputFormat){
11992
if(DT){
11993
if(DT.isDateTime(cellValue)){
11994
newDatetime = cellValue;
11995
}else if(inputFormat === "iso"){
11996
newDatetime = DT.fromISO(String(cellValue));
11997
}else {
11998
newDatetime = DT.fromFormat(String(cellValue), inputFormat);
11999
}
12000
12001
cellValue = newDatetime.toFormat("hh:mm");
12002
12003
}else {
12004
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
12005
}
12006
}
12007
12008
input.value = cellValue;
12009
12010
onRendered(function(){
12011
if(cell.getType() == "cell"){
12012
input.focus({preventScroll: true});
12013
input.style.height = "100%";
12014
12015
if(editorParams.selectContents){
12016
input.select();
12017
}
12018
}
12019
});
12020
12021
function onChange(){
12022
var value = input.value,
12023
luxTime;
12024
12025
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
12026
12027
if(value && inputFormat){
12028
luxTime = DT.fromFormat(String(value), "hh:mm");
12029
12030
switch(inputFormat){
12031
case true:
12032
value = luxTime;
12033
break;
12034
12035
case "iso":
12036
value = luxTime.toISO();
12037
break;
12038
12039
default:
12040
value = luxTime.toFormat(inputFormat);
12041
}
12042
}
12043
12044
if(success(value)){
12045
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
12046
}
12047
}else {
12048
cancel();
12049
}
12050
}
12051
12052
//submit new value on blur
12053
input.addEventListener("blur", function(e) {
12054
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
12055
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
12056
}
12057
});
12058
12059
//submit new value on enter
12060
input.addEventListener("keydown", function(e){
12061
switch(e.keyCode){
12062
// case 9:
12063
case 13:
12064
onChange();
12065
break;
12066
12067
case 27:
12068
cancel();
12069
break;
12070
12071
case 35:
12072
case 36:
12073
e.stopPropagation();
12074
break;
12075
12076
case 38: //up arrow
12077
case 40: //down arrow
12078
if(vertNav == "editor"){
12079
e.stopImmediatePropagation();
12080
e.stopPropagation();
12081
}
12082
break;
12083
}
12084
});
12085
12086
return input;
12087
}
12088
12089
//input element
12090
function datetime(cell, onRendered, success, cancel, editorParams){
12091
var inputFormat = editorParams.format,
12092
vertNav = editorParams.verticalNavigation || "editor",
12093
DT = inputFormat ? (window.DateTime || luxon.DateTime) : null,
12094
newDatetime;
12095
12096
//create and style input
12097
var cellValue = cell.getValue(),
12098
input = document.createElement("input");
12099
12100
input.type = "datetime-local";
12101
input.style.padding = "4px";
12102
input.style.width = "100%";
12103
input.style.boxSizing = "border-box";
12104
12105
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
12106
for (let key in editorParams.elementAttributes){
12107
if(key.charAt(0) == "+"){
12108
key = key.slice(1);
12109
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
12110
}else {
12111
input.setAttribute(key, editorParams.elementAttributes[key]);
12112
}
12113
}
12114
}
12115
12116
cellValue = typeof cellValue !== "undefined" ? cellValue : "";
12117
12118
if(inputFormat){
12119
if(DT){
12120
if(DT.isDateTime(cellValue)){
12121
newDatetime = cellValue;
12122
}else if(inputFormat === "iso"){
12123
newDatetime = DT.fromISO(String(cellValue));
12124
}else {
12125
newDatetime = DT.fromFormat(String(cellValue), inputFormat);
12126
}
12127
12128
cellValue = newDatetime.toFormat("yyyy-MM-dd") + "T" + newDatetime.toFormat("hh:mm");
12129
}else {
12130
console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js");
12131
}
12132
}
12133
12134
input.value = cellValue;
12135
12136
onRendered(function(){
12137
if(cell.getType() === "cell"){
12138
input.focus({preventScroll: true});
12139
input.style.height = "100%";
12140
12141
if(editorParams.selectContents){
12142
input.select();
12143
}
12144
}
12145
});
12146
12147
function onChange(){
12148
var value = input.value,
12149
luxDateTime;
12150
12151
if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){
12152
12153
if(value && inputFormat){
12154
luxDateTime = DT.fromISO(String(value));
12155
12156
switch(inputFormat){
12157
case true:
12158
value = luxDateTime;
12159
break;
12160
12161
case "iso":
12162
value = luxDateTime.toISO();
12163
break;
12164
12165
default:
12166
value = luxDateTime.toFormat(inputFormat);
12167
}
12168
}
12169
12170
if(success(value)){
12171
cellValue = input.value; //persist value if successfully validated incase editor is used as header filter
12172
}
12173
}else {
12174
cancel();
12175
}
12176
}
12177
12178
//submit new value on blur
12179
input.addEventListener("blur", function(e) {
12180
if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) {
12181
onChange(); // only on a "true" blur; not when focusing browser's date/time picker
12182
}
12183
});
12184
12185
//submit new value on enter
12186
input.addEventListener("keydown", function(e){
12187
switch(e.keyCode){
12188
// case 9:
12189
case 13:
12190
onChange();
12191
break;
12192
12193
case 27:
12194
cancel();
12195
break;
12196
12197
case 35:
12198
case 36:
12199
e.stopPropagation();
12200
break;
12201
12202
case 38: //up arrow
12203
case 40: //down arrow
12204
if(vertNav == "editor"){
12205
e.stopImmediatePropagation();
12206
e.stopPropagation();
12207
}
12208
break;
12209
}
12210
});
12211
12212
return input;
12213
}
12214
12215
class Edit{
12216
constructor(editor, cell, onRendered, success, cancel, editorParams){
12217
this.edit = editor;
12218
this.table = editor.table;
12219
this.cell = cell;
12220
this.params = this._initializeParams(editorParams);
12221
12222
this.data = [];
12223
this.displayItems = [];
12224
this.currentItems = [];
12225
this.focusedItem = null;
12226
12227
this.input = this._createInputElement();
12228
this.listEl = this._createListElement();
12229
12230
this.initialValues = null;
12231
12232
this.isFilter = cell.getType() === "header";
12233
12234
this.filterTimeout = null;
12235
this.filtered = false;
12236
this.typing = false;
12237
12238
this.values = [];
12239
this.popup = null;
12240
12241
this.listIteration = 0;
12242
12243
this.lastAction="";
12244
this.filterTerm="";
12245
12246
this.blurable = true;
12247
12248
this.actions = {
12249
success:success,
12250
cancel:cancel
12251
};
12252
12253
this._deprecatedOptionsCheck();
12254
this._initializeValue();
12255
12256
onRendered(this._onRendered.bind(this));
12257
}
12258
12259
_deprecatedOptionsCheck(){
12260
if(this.params.listItemFormatter){
12261
this.cell.getTable().deprecationAdvisor.msg("The listItemFormatter editor param has been deprecated, please see the latest editor documentation for updated options");
12262
}
12263
12264
if(this.params.sortValuesList){
12265
this.cell.getTable().deprecationAdvisor.msg("The sortValuesList editor param has been deprecated, please see the latest editor documentation for updated options");
12266
}
12267
12268
if(this.params.searchFunc){
12269
this.cell.getTable().deprecationAdvisor.msg("The searchFunc editor param has been deprecated, please see the latest editor documentation for updated options");
12270
}
12271
12272
if(this.params.searchingPlaceholder){
12273
this.cell.getTable().deprecationAdvisor.msg("The searchingPlaceholder editor param has been deprecated, please see the latest editor documentation for updated options");
12274
}
12275
}
12276
12277
_initializeValue(){
12278
var initialValue = this.cell.getValue();
12279
12280
if(typeof initialValue === "undefined" && typeof this.params.defaultValue !== "undefined"){
12281
initialValue = this.params.defaultValue;
12282
}
12283
12284
this.initialValues = this.params.multiselect ? initialValue : [initialValue];
12285
12286
if(this.isFilter){
12287
this.input.value = this.initialValues ? this.initialValues.join(",") : "";
12288
this.headerFilterInitialListGen();
12289
}
12290
}
12291
12292
_onRendered(){
12293
var cellEl = this.cell.getElement();
12294
12295
function clickStop(e){
12296
e.stopPropagation();
12297
}
12298
12299
if(!this.isFilter){
12300
this.input.style.height = "100%";
12301
this.input.focus({preventScroll: true});
12302
}
12303
12304
12305
cellEl.addEventListener("click", clickStop);
12306
12307
setTimeout(() => {
12308
cellEl.removeEventListener("click", clickStop);
12309
}, 1000);
12310
12311
this.input.addEventListener("mousedown", this._preventPopupBlur.bind(this));
12312
}
12313
12314
_createListElement(){
12315
var listEl = document.createElement("div");
12316
listEl.classList.add("tabulator-edit-list");
12317
12318
listEl.addEventListener("mousedown", this._preventBlur.bind(this));
12319
listEl.addEventListener("keydown", this._inputKeyDown.bind(this));
12320
12321
return listEl;
12322
}
12323
12324
_setListWidth(){
12325
var element = this.isFilter ? this.input : this.cell.getElement();
12326
12327
this.listEl.style.minWidth = element.offsetWidth + "px";
12328
12329
if(this.params.maxWidth){
12330
if(this.params.maxWidth === true){
12331
this.listEl.style.maxWidth = element.offsetWidth + "px";
12332
}else if(typeof this.params.maxWidth === "number"){
12333
this.listEl.style.maxWidth = this.params.maxWidth + "px";
12334
}else {
12335
this.listEl.style.maxWidth = this.params.maxWidth;
12336
}
12337
}
12338
12339
}
12340
12341
_createInputElement(){
12342
var attribs = this.params.elementAttributes;
12343
var input = document.createElement("input");
12344
12345
input.setAttribute("type", this.params.clearable ? "search" : "text");
12346
12347
input.style.padding = "4px";
12348
input.style.width = "100%";
12349
input.style.boxSizing = "border-box";
12350
12351
if(!this.params.autocomplete){
12352
input.style.cursor = "default";
12353
input.style.caretColor = "transparent";
12354
// input.readOnly = (this.edit.currentCell != false);
12355
}
12356
12357
if(attribs && typeof attribs == "object"){
12358
for (let key in attribs){
12359
if(key.charAt(0) == "+"){
12360
key = key.slice(1);
12361
input.setAttribute(key, input.getAttribute(key) + attribs["+" + key]);
12362
}else {
12363
input.setAttribute(key, attribs[key]);
12364
}
12365
}
12366
}
12367
12368
if(this.params.mask){
12369
maskInput(input, this.params);
12370
}
12371
12372
this._bindInputEvents(input);
12373
12374
return input;
12375
}
12376
12377
_initializeParams(params){
12378
var valueKeys = ["values", "valuesURL", "valuesLookup"],
12379
valueCheck;
12380
12381
params = Object.assign({}, params);
12382
12383
params.verticalNavigation = params.verticalNavigation || "editor";
12384
params.placeholderLoading = typeof params.placeholderLoading === "undefined" ? "Searching ..." : params.placeholderLoading;
12385
params.placeholderEmpty = typeof params.placeholderEmpty === "undefined" ? "No Results Found" : params.placeholderEmpty;
12386
params.filterDelay = typeof params.filterDelay === "undefined" ? 300 : params.filterDelay;
12387
12388
params.emptyValue = Object.keys(params).includes("emptyValue") ? params.emptyValue : "";
12389
12390
valueCheck = Object.keys(params).filter(key => valueKeys.includes(key)).length;
12391
12392
if(!valueCheck){
12393
console.warn("list editor config error - either the values, valuesURL, or valuesLookup option must be set");
12394
}else if(valueCheck > 1){
12395
console.warn("list editor config error - only one of the values, valuesURL, or valuesLookup options can be set on the same editor");
12396
}
12397
12398
if(params.autocomplete){
12399
if(params.multiselect){
12400
params.multiselect = false;
12401
console.warn("list editor config error - multiselect option is not available when autocomplete is enabled");
12402
}
12403
}else {
12404
if(params.freetext){
12405
params.freetext = false;
12406
console.warn("list editor config error - freetext option is only available when autocomplete is enabled");
12407
}
12408
12409
if(params.filterFunc){
12410
params.filterFunc = false;
12411
console.warn("list editor config error - filterFunc option is only available when autocomplete is enabled");
12412
}
12413
12414
if(params.filterRemote){
12415
params.filterRemote = false;
12416
console.warn("list editor config error - filterRemote option is only available when autocomplete is enabled");
12417
}
12418
12419
if(params.mask){
12420
params.mask = false;
12421
console.warn("list editor config error - mask option is only available when autocomplete is enabled");
12422
}
12423
12424
if(params.allowEmpty){
12425
params.allowEmpty = false;
12426
console.warn("list editor config error - allowEmpty option is only available when autocomplete is enabled");
12427
}
12428
12429
if(params.listOnEmpty){
12430
params.listOnEmpty = false;
12431
console.warn("list editor config error - listOnEmpty option is only available when autocomplete is enabled");
12432
}
12433
}
12434
12435
if(params.filterRemote && !(typeof params.valuesLookup === "function" || params.valuesURL)){
12436
params.filterRemote = false;
12437
console.warn("list editor config error - filterRemote option should only be used when values list is populated from a remote source");
12438
}
12439
return params;
12440
}
12441
//////////////////////////////////////
12442
////////// Event Handling ////////////
12443
//////////////////////////////////////
12444
12445
_bindInputEvents(input){
12446
input.addEventListener("focus", this._inputFocus.bind(this));
12447
input.addEventListener("click", this._inputClick.bind(this));
12448
input.addEventListener("blur", this._inputBlur.bind(this));
12449
input.addEventListener("keydown", this._inputKeyDown.bind(this));
12450
input.addEventListener("search", this._inputSearch.bind(this));
12451
12452
if(this.params.autocomplete){
12453
input.addEventListener("keyup", this._inputKeyUp.bind(this));
12454
}
12455
}
12456
12457
12458
_inputFocus(e){
12459
this.rebuildOptionsList();
12460
}
12461
12462
_filter(){
12463
if(this.params.filterRemote){
12464
clearTimeout(this.filterTimeout);
12465
12466
this.filterTimeout = setTimeout(() => {
12467
this.rebuildOptionsList();
12468
}, this.params.filterDelay);
12469
}else {
12470
this._filterList();
12471
}
12472
}
12473
12474
_inputClick(e){
12475
e.stopPropagation();
12476
}
12477
12478
_inputBlur(e){
12479
if(this.blurable){
12480
if(this.popup){
12481
this.popup.hide();
12482
}else {
12483
this._resolveValue(true);
12484
}
12485
}
12486
}
12487
12488
_inputSearch(){
12489
this._clearChoices();
12490
}
12491
12492
_inputKeyDown(e){
12493
switch(e.keyCode){
12494
12495
case 38: //up arrow
12496
this._keyUp(e);
12497
break;
12498
12499
case 40: //down arrow
12500
this._keyDown(e);
12501
break;
12502
12503
case 37: //left arrow
12504
case 39: //right arrow
12505
this._keySide(e);
12506
break;
12507
12508
case 13: //enter
12509
this._keyEnter();
12510
break;
12511
12512
case 27: //escape
12513
this._keyEsc();
12514
break;
12515
12516
case 36: //home
12517
case 35: //end
12518
this._keyHomeEnd(e);
12519
break;
12520
12521
case 9: //tab
12522
this._keyTab(e);
12523
break;
12524
12525
default:
12526
this._keySelectLetter(e);
12527
}
12528
}
12529
12530
_inputKeyUp(e){
12531
switch(e.keyCode){
12532
case 38: //up arrow
12533
case 37: //left arrow
12534
case 39: //up arrow
12535
case 40: //right arrow
12536
case 13: //enter
12537
case 27: //escape
12538
break;
12539
12540
default:
12541
this._keyAutoCompLetter(e);
12542
}
12543
}
12544
12545
_preventPopupBlur(){
12546
if(this.popup){
12547
this.popup.blockHide();
12548
}
12549
12550
setTimeout(() =>{
12551
if(this.popup){
12552
this.popup.restoreHide();
12553
}
12554
}, 10);
12555
}
12556
12557
_preventBlur(){
12558
this.blurable = false;
12559
12560
setTimeout(() =>{
12561
this.blurable = true;
12562
}, 10);
12563
}
12564
12565
//////////////////////////////////////
12566
//////// Keyboard Navigation /////////
12567
//////////////////////////////////////
12568
12569
_keyTab(e){
12570
if(this.params.autocomplete && this.lastAction === "typing"){
12571
this._resolveValue(true);
12572
}else {
12573
if(this.focusedItem){
12574
this._chooseItem(this.focusedItem, true);
12575
}
12576
}
12577
}
12578
12579
_keyUp(e){
12580
var index = this.displayItems.indexOf(this.focusedItem);
12581
12582
if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index)){
12583
e.stopImmediatePropagation();
12584
e.stopPropagation();
12585
e.preventDefault();
12586
12587
if(index > 0){
12588
this._focusItem(this.displayItems[index - 1]);
12589
}
12590
}
12591
}
12592
12593
_keyDown(e){
12594
var index = this.displayItems.indexOf(this.focusedItem);
12595
12596
if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index < this.displayItems.length - 1)){
12597
e.stopImmediatePropagation();
12598
e.stopPropagation();
12599
e.preventDefault();
12600
12601
if(index < this.displayItems.length - 1){
12602
if(index == -1){
12603
this._focusItem(this.displayItems[0]);
12604
}else {
12605
this._focusItem(this.displayItems[index + 1]);
12606
}
12607
}
12608
}
12609
}
12610
12611
_keySide(e){
12612
if(!this.params.autocomplete){
12613
e.stopImmediatePropagation();
12614
e.stopPropagation();
12615
e.preventDefault();
12616
}
12617
}
12618
12619
_keyEnter(e){
12620
if(this.params.autocomplete && this.lastAction === "typing"){
12621
this._resolveValue(true);
12622
}else {
12623
if(this.focusedItem){
12624
this._chooseItem(this.focusedItem);
12625
}
12626
}
12627
}
12628
12629
_keyEsc(e){
12630
this._cancel();
12631
}
12632
12633
_keyHomeEnd(e){
12634
if(this.params.autocomplete){
12635
//prevent table navigation while using input element
12636
e.stopImmediatePropagation();
12637
}
12638
}
12639
12640
_keySelectLetter(e){
12641
if(!this.params.autocomplete){
12642
// if(this.edit.currentCell === false){
12643
e.preventDefault();
12644
// }
12645
12646
if(e.keyCode >= 38 && e.keyCode <= 90){
12647
this._scrollToValue(e.keyCode);
12648
}
12649
}
12650
}
12651
12652
_keyAutoCompLetter(e){
12653
this._filter();
12654
this.lastAction = "typing";
12655
this.typing = true;
12656
}
12657
12658
12659
_scrollToValue(char){
12660
clearTimeout(this.filterTimeout);
12661
12662
var character = String.fromCharCode(char).toLowerCase();
12663
this.filterTerm += character.toLowerCase();
12664
12665
var match = this.displayItems.find((item) => {
12666
return typeof item.label !== "undefined" && item.label.toLowerCase().startsWith(this.filterTerm);
12667
});
12668
12669
if(match){
12670
this._focusItem(match);
12671
}
12672
12673
this.filterTimeout = setTimeout(() => {
12674
this.filterTerm = "";
12675
}, 800);
12676
}
12677
12678
_focusItem(item){
12679
this.lastAction = "focus";
12680
12681
if(this.focusedItem && this.focusedItem.element){
12682
this.focusedItem.element.classList.remove("focused");
12683
}
12684
12685
this.focusedItem = item;
12686
12687
if(item && item.element){
12688
item.element.classList.add("focused");
12689
item.element.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'});
12690
}
12691
}
12692
12693
12694
//////////////////////////////////////
12695
/////// Data List Generation /////////
12696
//////////////////////////////////////
12697
headerFilterInitialListGen(){
12698
this._generateOptions(true);
12699
}
12700
12701
rebuildOptionsList(){
12702
this._generateOptions()
12703
.then(this._sortOptions.bind(this))
12704
.then(this._buildList.bind(this))
12705
.then(this._showList.bind(this))
12706
.catch((e) => {
12707
if(!Number.isInteger(e)){
12708
console.error("List generation error", e);
12709
}
12710
});
12711
}
12712
12713
_filterList(){
12714
this._buildList(this._filterOptions());
12715
this._showList();
12716
}
12717
12718
_generateOptions(silent){
12719
var values = [];
12720
var iteration = ++ this.listIteration;
12721
12722
this.filtered = false;
12723
12724
if(this.params.values){
12725
values = this.params.values;
12726
}else if (this.params.valuesURL){
12727
values = this._ajaxRequest(this.params.valuesURL, this.input.value);
12728
}else {
12729
if(typeof this.params.valuesLookup === "function"){
12730
values = this.params.valuesLookup(this.cell, this.input.value);
12731
}else if(this.params.valuesLookup){
12732
values = this._uniqueColumnValues(this.params.valuesLookupField);
12733
}
12734
}
12735
12736
if(values instanceof Promise){
12737
if(!silent){
12738
this._addPlaceholder(this.params.placeholderLoading);
12739
}
12740
12741
return values.then()
12742
.then((responseValues) => {
12743
if(this.listIteration === iteration){
12744
return this._parseList(responseValues);
12745
}else {
12746
return Promise.reject(iteration);
12747
}
12748
});
12749
}else {
12750
return Promise.resolve(this._parseList(values));
12751
}
12752
}
12753
12754
_addPlaceholder(contents){
12755
var placeholder = document.createElement("div");
12756
12757
if(typeof contents === "function"){
12758
contents = contents(this.cell.getComponent(), this.listEl);
12759
}
12760
12761
if(contents){
12762
this._clearList();
12763
12764
if(contents instanceof HTMLElement){
12765
placeholder = contents;
12766
}else {
12767
placeholder.classList.add("tabulator-edit-list-placeholder");
12768
placeholder.innerHTML = contents;
12769
}
12770
12771
this.listEl.appendChild(placeholder);
12772
12773
this._showList();
12774
}
12775
}
12776
12777
_ajaxRequest(url, term){
12778
var params = this.params.filterRemote ? {term:term} : {};
12779
url = urlBuilder(url, {}, params);
12780
12781
return fetch(url)
12782
.then((response)=>{
12783
if(response.ok) {
12784
return response.json()
12785
.catch((error)=>{
12786
console.warn("List Ajax Load Error - Invalid JSON returned", error);
12787
return Promise.reject(error);
12788
});
12789
}else {
12790
console.error("List Ajax Load Error - Connection Error: " + response.status, response.statusText);
12791
return Promise.reject(response);
12792
}
12793
})
12794
.catch((error)=>{
12795
console.error("List Ajax Load Error - Connection Error: ", error);
12796
return Promise.reject(error);
12797
});
12798
}
12799
12800
_uniqueColumnValues(field){
12801
var output = {},
12802
data = this.table.getData(this.params.valuesLookup),
12803
column;
12804
12805
if(field){
12806
column = this.table.columnManager.getColumnByField(field);
12807
}else {
12808
column = this.cell.getColumn()._getSelf();
12809
}
12810
12811
if(column){
12812
data.forEach((row) => {
12813
var val = column.getFieldValue(row);
12814
12815
if(val !== null && typeof val !== "undefined" && val !== ""){
12816
output[val] = true;
12817
}
12818
});
12819
}else {
12820
console.warn("unable to find matching column to create select lookup list:", field);
12821
output = [];
12822
}
12823
12824
return Object.keys(output);
12825
}
12826
12827
12828
_parseList(inputValues){
12829
var data = [];
12830
12831
if(!Array.isArray(inputValues)){
12832
inputValues = Object.entries(inputValues).map(([key, value]) => {
12833
return {
12834
label:value,
12835
value:key,
12836
};
12837
});
12838
}
12839
12840
inputValues.forEach((value) => {
12841
if(typeof value !== "object"){
12842
value = {
12843
label:value,
12844
value:value,
12845
};
12846
}
12847
12848
this._parseListItem(value, data, 0);
12849
});
12850
12851
if(!this.currentItems.length && this.params.freetext){
12852
this.input.value = this.initialValues;
12853
this.typing = true;
12854
this.lastAction = "typing";
12855
}
12856
12857
this.data = data;
12858
12859
return data;
12860
}
12861
12862
_parseListItem(option, data, level){
12863
var item = {};
12864
12865
if(option.options){
12866
item = this._parseListGroup(option, level + 1);
12867
}else {
12868
item = {
12869
label:option.label,
12870
value:option.value,
12871
itemParams:option.itemParams,
12872
elementAttributes: option.elementAttributes,
12873
element:false,
12874
selected:false,
12875
visible:true,
12876
level:level,
12877
original:option,
12878
};
12879
12880
if(this.initialValues && this.initialValues.indexOf(option.value) > -1){
12881
this._chooseItem(item, true);
12882
}
12883
}
12884
12885
data.push(item);
12886
}
12887
12888
_parseListGroup(option, level){
12889
var item = {
12890
label:option.label,
12891
group:true,
12892
itemParams:option.itemParams,
12893
elementAttributes:option.elementAttributes,
12894
element:false,
12895
visible:true,
12896
level:level,
12897
options:[],
12898
original:option,
12899
};
12900
12901
option.options.forEach((child) => {
12902
this._parseListItem(child, item.options, level);
12903
});
12904
12905
return item;
12906
}
12907
12908
_sortOptions(options){
12909
var sorter;
12910
12911
if(this.params.sort){
12912
sorter = typeof this.params.sort === "function" ? this.params.sort : this._defaultSortFunction.bind(this);
12913
12914
this._sortGroup(sorter, options);
12915
}
12916
12917
return options;
12918
}
12919
12920
_sortGroup(sorter, options){
12921
options.sort((a,b) => {
12922
return sorter(a.label, b.label, a.value, b.value, a.original, b.original);
12923
});
12924
12925
options.forEach((option) => {
12926
if(option.group){
12927
this._sortGroup(sorter, option.options);
12928
}
12929
});
12930
}
12931
12932
_defaultSortFunction(as, bs){
12933
var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
12934
var emptyAlign = 0;
12935
12936
if(this.params.sort === "desc"){
12937
[as, bs] = [bs, as];
12938
}
12939
12940
//handle empty values
12941
if(!as && as!== 0){
12942
emptyAlign = !bs && bs!== 0 ? 0 : -1;
12943
}else if(!bs && bs!== 0){
12944
emptyAlign = 1;
12945
}else {
12946
if(isFinite(as) && isFinite(bs)) return as - bs;
12947
a = String(as).toLowerCase();
12948
b = String(bs).toLowerCase();
12949
if(a === b) return 0;
12950
if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
12951
a = a.match(rx);
12952
b = b.match(rx);
12953
L = a.length > b.length ? b.length : a.length;
12954
while(i < L){
12955
a1= a[i];
12956
b1= b[i++];
12957
if(a1 !== b1){
12958
if(isFinite(a1) && isFinite(b1)){
12959
if(a1.charAt(0) === "0") a1 = "." + a1;
12960
if(b1.charAt(0) === "0") b1 = "." + b1;
12961
return a1 - b1;
12962
}
12963
else return a1 > b1 ? 1 : -1;
12964
}
12965
}
12966
12967
return a.length > b.length;
12968
}
12969
12970
return emptyAlign;
12971
}
12972
12973
_filterOptions(){
12974
var filterFunc = this.params.filterFunc || this._defaultFilterFunc,
12975
term = this.input.value;
12976
12977
if(term){
12978
this.filtered = true;
12979
12980
this.data.forEach((item) => {
12981
this._filterItem(filterFunc, term, item);
12982
});
12983
}else {
12984
this.filtered = false;
12985
}
12986
12987
return this.data;
12988
}
12989
12990
_filterItem(func, term, item){
12991
var matches = false;
12992
12993
if(!item.group){
12994
item.visible = func(term, item.label, item.value, item.original);
12995
}else {
12996
item.options.forEach((option) => {
12997
if(this._filterItem(func, term, option)){
12998
matches = true;
12999
}
13000
});
13001
13002
item.visible = matches;
13003
}
13004
13005
return item.visible;
13006
}
13007
13008
_defaultFilterFunc(term, label, value, item){
13009
term = String(term).toLowerCase();
13010
13011
if(label !== null && typeof label !== "undefined"){
13012
if(String(label).toLowerCase().indexOf(term) > -1 || String(value).toLowerCase().indexOf(term) > -1){
13013
return true;
13014
}
13015
}
13016
13017
return false;
13018
}
13019
13020
//////////////////////////////////////
13021
/////////// Display List /////////////
13022
//////////////////////////////////////
13023
13024
_clearList(){
13025
while(this.listEl.firstChild) this.listEl.removeChild(this.listEl.firstChild);
13026
13027
this.displayItems = [];
13028
}
13029
13030
_buildList(data){
13031
this._clearList();
13032
13033
data.forEach((option) => {
13034
this._buildItem(option);
13035
});
13036
13037
if(!this.displayItems.length){
13038
this._addPlaceholder(this.params.placeholderEmpty);
13039
}
13040
}
13041
13042
_buildItem(item){
13043
var el = item.element,
13044
contents;
13045
13046
if(!this.filtered || item.visible){
13047
13048
if(!el){
13049
el = document.createElement("div");
13050
el.tabIndex = 0;
13051
13052
contents = this.params.itemFormatter ? this.params.itemFormatter(item.label, item.value, item.original, el) : item.label;
13053
13054
if(contents instanceof HTMLElement){
13055
el.appendChild(contents);
13056
}else {
13057
el.innerHTML = contents;
13058
}
13059
13060
if(item.group){
13061
el.classList.add("tabulator-edit-list-group");
13062
}else {
13063
el.classList.add("tabulator-edit-list-item");
13064
}
13065
13066
el.classList.add("tabulator-edit-list-group-level-" + item.level);
13067
13068
if(item.elementAttributes && typeof item.elementAttributes == "object"){
13069
for (let key in item.elementAttributes){
13070
if(key.charAt(0) == "+"){
13071
key = key.slice(1);
13072
el.setAttribute(key, this.input.getAttribute(key) + item.elementAttributes["+" + key]);
13073
}else {
13074
el.setAttribute(key, item.elementAttributes[key]);
13075
}
13076
}
13077
}
13078
13079
if(item.group){
13080
el.addEventListener("click", this._groupClick.bind(this, item));
13081
}else {
13082
el.addEventListener("click", this._itemClick.bind(this, item));
13083
}
13084
13085
el.addEventListener("mousedown", this._preventBlur.bind(this));
13086
13087
item.element = el;
13088
}
13089
13090
this._styleItem(item);
13091
13092
this.listEl.appendChild(el);
13093
13094
if(item.group){
13095
item.options.forEach((option) => {
13096
this._buildItem(option);
13097
});
13098
}else {
13099
this.displayItems.push(item);
13100
}
13101
}
13102
}
13103
13104
_showList(){
13105
var startVis = this.popup && this.popup.isVisible();
13106
13107
if(this.input.parentNode){
13108
if(this.params.autocomplete && this.input.value === "" && !this.params.listOnEmpty){
13109
if(this.popup){
13110
this.popup.hide(true);
13111
}
13112
return;
13113
}
13114
13115
this._setListWidth();
13116
13117
if(!this.popup){
13118
this.popup = this.edit.popup(this.listEl);
13119
}
13120
13121
this.popup.show(this.cell.getElement(), "bottom");
13122
13123
if(!startVis){
13124
setTimeout(() => {
13125
this.popup.hideOnBlur(this._resolveValue.bind(this, true));
13126
}, 10);
13127
}
13128
}
13129
}
13130
13131
_styleItem(item){
13132
if(item && item.element){
13133
if(item.selected){
13134
item.element.classList.add("active");
13135
}else {
13136
item.element.classList.remove("active");
13137
}
13138
}
13139
}
13140
13141
//////////////////////////////////////
13142
///////// User Interaction ///////////
13143
//////////////////////////////////////
13144
13145
_itemClick(item, e){
13146
e.stopPropagation();
13147
13148
this._chooseItem(item);
13149
}
13150
13151
_groupClick(item, e){
13152
e.stopPropagation();
13153
}
13154
13155
13156
//////////////////////////////////////
13157
////// Current Item Management ///////
13158
//////////////////////////////////////
13159
13160
_cancel(){
13161
this.popup.hide(true);
13162
this.actions.cancel();
13163
}
13164
13165
_clearChoices(){
13166
this.typing = true;
13167
13168
this.currentItems.forEach((item) => {
13169
item.selected = false;
13170
this._styleItem(item);
13171
});
13172
13173
this.currentItems = [];
13174
13175
this.focusedItem = null;
13176
}
13177
13178
_chooseItem(item, silent){
13179
var index;
13180
13181
this.typing = false;
13182
13183
if(this.params.multiselect){
13184
index = this.currentItems.indexOf(item);
13185
13186
if(index > -1){
13187
this.currentItems.splice(index, 1);
13188
item.selected = false;
13189
}else {
13190
this.currentItems.push(item);
13191
item.selected = true;
13192
}
13193
13194
this.input.value = this.currentItems.map(item => item.label).join(",");
13195
13196
this._styleItem(item);
13197
13198
}else {
13199
this.currentItems = [item];
13200
item.selected = true;
13201
13202
this.input.value = item.label;
13203
13204
this._styleItem(item);
13205
13206
if(!silent){
13207
this._resolveValue();
13208
}
13209
}
13210
13211
this._focusItem(item);
13212
}
13213
13214
_resolveValue(blur){
13215
var output, initialValue;
13216
13217
if(this.popup){
13218
this.popup.hide(true);
13219
}
13220
13221
if(this.params.multiselect){
13222
output = this.currentItems.map(item => item.value);
13223
}else {
13224
if(blur && this.params.autocomplete && this.typing){
13225
if(this.params.freetext || (this.params.allowEmpty && this.input.value === "")){
13226
output = this.input.value;
13227
}else {
13228
this.actions.cancel();
13229
return;
13230
}
13231
}else {
13232
if(this.currentItems[0]){
13233
output = this.currentItems[0].value;
13234
}else {
13235
initialValue = Array.isArray(this.initialValues) ? this.initialValues[0] : this.initialValues;
13236
13237
if(initialValue === null || typeof initialValue === "undefined" || initialValue === ""){
13238
output = initialValue;
13239
}else {
13240
output = this.params.emptyValue;
13241
}
13242
}
13243
13244
}
13245
}
13246
13247
if(output === ""){
13248
output = this.params.emptyValue;
13249
}
13250
13251
this.actions.success(output);
13252
13253
if(this.isFilter){
13254
this.initialValues = output && !Array.isArray(output) ? [output] : output;
13255
this.currentItems = [];
13256
}
13257
}
13258
13259
}
13260
13261
function select(cell, onRendered, success, cancel, editorParams){
13262
13263
this.deprecationMsg("The select editor has been deprecated, please use the new list editor");
13264
13265
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
13266
13267
return list.input;
13268
}
13269
13270
function list(cell, onRendered, success, cancel, editorParams){
13271
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
13272
13273
return list.input;
13274
}
13275
13276
function autocomplete(cell, onRendered, success, cancel, editorParams){
13277
13278
this.deprecationMsg("The autocomplete editor has been deprecated, please use the new list editor with the 'autocomplete' editorParam");
13279
13280
editorParams.autocomplete = true;
13281
13282
var list = new Edit(this, cell, onRendered, success, cancel, editorParams);
13283
13284
return list.input;
13285
}
13286
13287
//star rating
13288
function star(cell, onRendered, success, cancel, editorParams){
13289
var self = this,
13290
element = cell.getElement(),
13291
value = cell.getValue(),
13292
maxStars = element.getElementsByTagName("svg").length || 5,
13293
size = element.getElementsByTagName("svg")[0] ? element.getElementsByTagName("svg")[0].getAttribute("width") : 14,
13294
stars = [],
13295
starsHolder = document.createElement("div"),
13296
star = document.createElementNS('http://www.w3.org/2000/svg', "svg");
13297
13298
13299
//change star type
13300
function starChange(val){
13301
stars.forEach(function(star, i){
13302
if(i < val){
13303
if(self.table.browser == "ie"){
13304
star.setAttribute("class", "tabulator-star-active");
13305
}else {
13306
star.classList.replace("tabulator-star-inactive", "tabulator-star-active");
13307
}
13308
13309
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 "/>';
13310
}else {
13311
if(self.table.browser == "ie"){
13312
star.setAttribute("class", "tabulator-star-inactive");
13313
}else {
13314
star.classList.replace("tabulator-star-active", "tabulator-star-inactive");
13315
}
13316
13317
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 "/>';
13318
}
13319
});
13320
}
13321
13322
//build stars
13323
function buildStar(i){
13324
13325
var starHolder = document.createElement("span");
13326
var nextStar = star.cloneNode(true);
13327
13328
stars.push(nextStar);
13329
13330
starHolder.addEventListener("mouseenter", function(e){
13331
e.stopPropagation();
13332
e.stopImmediatePropagation();
13333
starChange(i);
13334
});
13335
13336
starHolder.addEventListener("mousemove", function(e){
13337
e.stopPropagation();
13338
e.stopImmediatePropagation();
13339
});
13340
13341
starHolder.addEventListener("click", function(e){
13342
e.stopPropagation();
13343
e.stopImmediatePropagation();
13344
success(i);
13345
element.blur();
13346
});
13347
13348
starHolder.appendChild(nextStar);
13349
starsHolder.appendChild(starHolder);
13350
13351
}
13352
13353
//handle keyboard navigation value change
13354
function changeValue(val){
13355
value = val;
13356
starChange(val);
13357
}
13358
13359
//style cell
13360
element.style.whiteSpace = "nowrap";
13361
element.style.overflow = "hidden";
13362
element.style.textOverflow = "ellipsis";
13363
13364
//style holding element
13365
starsHolder.style.verticalAlign = "middle";
13366
starsHolder.style.display = "inline-block";
13367
starsHolder.style.padding = "4px";
13368
13369
//style star
13370
star.setAttribute("width", size);
13371
star.setAttribute("height", size);
13372
star.setAttribute("viewBox", "0 0 512 512");
13373
star.setAttribute("xml:space", "preserve");
13374
star.style.padding = "0 1px";
13375
13376
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
13377
for (let key in editorParams.elementAttributes){
13378
if(key.charAt(0) == "+"){
13379
key = key.slice(1);
13380
starsHolder.setAttribute(key, starsHolder.getAttribute(key) + editorParams.elementAttributes["+" + key]);
13381
}else {
13382
starsHolder.setAttribute(key, editorParams.elementAttributes[key]);
13383
}
13384
}
13385
}
13386
13387
//create correct number of stars
13388
for(var i=1;i<= maxStars;i++){
13389
buildStar(i);
13390
}
13391
13392
//ensure value does not exceed number of stars
13393
value = Math.min(parseInt(value), maxStars);
13394
13395
// set initial styling of stars
13396
starChange(value);
13397
13398
starsHolder.addEventListener("mousemove", function(e){
13399
starChange(0);
13400
});
13401
13402
starsHolder.addEventListener("click", function(e){
13403
success(0);
13404
});
13405
13406
element.addEventListener("blur", function(e){
13407
cancel();
13408
});
13409
13410
//allow key based navigation
13411
element.addEventListener("keydown", function(e){
13412
switch(e.keyCode){
13413
case 39: //right arrow
13414
changeValue(value + 1);
13415
break;
13416
13417
case 37: //left arrow
13418
changeValue(value - 1);
13419
break;
13420
13421
case 13: //enter
13422
success(value);
13423
break;
13424
13425
case 27: //escape
13426
cancel();
13427
break;
13428
}
13429
});
13430
13431
return starsHolder;
13432
}
13433
13434
//draggable progress bar
13435
function progress(cell, onRendered, success, cancel, editorParams){
13436
var element = cell.getElement(),
13437
max = typeof editorParams.max === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("max")) || 100) : editorParams.max,
13438
min = typeof editorParams.min === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("min")) || 0) : editorParams.min,
13439
percent = (max - min) / 100,
13440
value = cell.getValue() || 0,
13441
handle = document.createElement("div"),
13442
bar = document.createElement("div"),
13443
mouseDrag, mouseDragWidth;
13444
13445
//set new value
13446
function updateValue(){
13447
var style = window.getComputedStyle(element, null);
13448
13449
var calcVal = (percent * Math.round(bar.offsetWidth / ((element.clientWidth - parseInt(style.getPropertyValue("padding-left")) - parseInt(style.getPropertyValue("padding-right")))/100))) + min;
13450
success(calcVal);
13451
element.setAttribute("aria-valuenow", calcVal);
13452
element.setAttribute("aria-label", value);
13453
}
13454
13455
//style handle
13456
handle.style.position = "absolute";
13457
handle.style.right = "0";
13458
handle.style.top = "0";
13459
handle.style.bottom = "0";
13460
handle.style.width = "5px";
13461
handle.classList.add("tabulator-progress-handle");
13462
13463
//style bar
13464
bar.style.display = "inline-block";
13465
bar.style.position = "relative";
13466
// bar.style.top = "8px";
13467
// bar.style.bottom = "8px";
13468
// bar.style.left = "4px";
13469
// bar.style.marginRight = "4px";
13470
bar.style.height = "100%";
13471
bar.style.backgroundColor = "#488CE9";
13472
bar.style.maxWidth = "100%";
13473
bar.style.minWidth = "0%";
13474
13475
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
13476
for (let key in editorParams.elementAttributes){
13477
if(key.charAt(0) == "+"){
13478
key = key.slice(1);
13479
bar.setAttribute(key, bar.getAttribute(key) + editorParams.elementAttributes["+" + key]);
13480
}else {
13481
bar.setAttribute(key, editorParams.elementAttributes[key]);
13482
}
13483
}
13484
}
13485
13486
//style cell
13487
element.style.padding = "4px 4px";
13488
13489
//make sure value is in range
13490
value = Math.min(parseFloat(value), max);
13491
value = Math.max(parseFloat(value), min);
13492
13493
//workout percentage
13494
value = Math.round((value - min) / percent);
13495
// bar.style.right = value + "%";
13496
bar.style.width = value + "%";
13497
13498
element.setAttribute("aria-valuemin", min);
13499
element.setAttribute("aria-valuemax", max);
13500
13501
bar.appendChild(handle);
13502
13503
handle.addEventListener("mousedown", function(e){
13504
mouseDrag = e.screenX;
13505
mouseDragWidth = bar.offsetWidth;
13506
});
13507
13508
handle.addEventListener("mouseover", function(){
13509
handle.style.cursor = "ew-resize";
13510
});
13511
13512
element.addEventListener("mousemove", function(e){
13513
if(mouseDrag){
13514
bar.style.width = (mouseDragWidth + e.screenX - mouseDrag) + "px";
13515
}
13516
});
13517
13518
element.addEventListener("mouseup", function(e){
13519
if(mouseDrag){
13520
e.stopPropagation();
13521
e.stopImmediatePropagation();
13522
13523
mouseDrag = false;
13524
mouseDragWidth = false;
13525
13526
updateValue();
13527
}
13528
});
13529
13530
//allow key based navigation
13531
element.addEventListener("keydown", function(e){
13532
switch(e.keyCode){
13533
case 39: //right arrow
13534
e.preventDefault();
13535
bar.style.width = (bar.clientWidth + element.clientWidth/100) + "px";
13536
break;
13537
13538
case 37: //left arrow
13539
e.preventDefault();
13540
bar.style.width = (bar.clientWidth - element.clientWidth/100) + "px";
13541
break;
13542
13543
case 9: //tab
13544
case 13: //enter
13545
updateValue();
13546
break;
13547
13548
case 27: //escape
13549
cancel();
13550
break;
13551
13552
}
13553
});
13554
13555
element.addEventListener("blur", function(){
13556
cancel();
13557
});
13558
13559
return bar;
13560
}
13561
13562
//checkbox
13563
function tickCross(cell, onRendered, success, cancel, editorParams){
13564
var value = cell.getValue(),
13565
input = document.createElement("input"),
13566
tristate = editorParams.tristate,
13567
indetermValue = typeof editorParams.indeterminateValue === "undefined" ? null : editorParams.indeterminateValue,
13568
indetermState = false,
13569
trueValueSet = Object.keys(editorParams).includes("trueValue"),
13570
falseValueSet = Object.keys(editorParams).includes("falseValue");
13571
13572
input.setAttribute("type", "checkbox");
13573
input.style.marginTop = "5px";
13574
input.style.boxSizing = "border-box";
13575
13576
if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){
13577
for (let key in editorParams.elementAttributes){
13578
if(key.charAt(0) == "+"){
13579
key = key.slice(1);
13580
input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]);
13581
}else {
13582
input.setAttribute(key, editorParams.elementAttributes[key]);
13583
}
13584
}
13585
}
13586
13587
input.value = value;
13588
13589
if(tristate && (typeof value === "undefined" || value === indetermValue || value === "")){
13590
indetermState = true;
13591
input.indeterminate = true;
13592
}
13593
13594
if(this.table.browser != "firefox" && this.table.browser != "safari"){ //prevent blur issue on mac firefox
13595
onRendered(function(){
13596
if(cell.getType() === "cell"){
13597
input.focus({preventScroll: true});
13598
}
13599
});
13600
}
13601
13602
input.checked = trueValueSet ? value === editorParams.trueValue : (value === true || value === "true" || value === "True" || value === 1);
13603
13604
function setValue(blur){
13605
var checkedValue = input.checked;
13606
13607
if(trueValueSet && checkedValue){
13608
checkedValue = editorParams.trueValue;
13609
}else if(falseValueSet && !checkedValue){
13610
checkedValue = editorParams.falseValue;
13611
}
13612
13613
if(tristate){
13614
if(!blur){
13615
if(input.checked && !indetermState){
13616
input.checked = false;
13617
input.indeterminate = true;
13618
indetermState = true;
13619
return indetermValue;
13620
}else {
13621
indetermState = false;
13622
return checkedValue;
13623
}
13624
}else {
13625
if(indetermState){
13626
return indetermValue;
13627
}else {
13628
return checkedValue;
13629
}
13630
}
13631
}else {
13632
return checkedValue;
13633
}
13634
}
13635
13636
//submit new value on blur
13637
input.addEventListener("change", function(e){
13638
success(setValue());
13639
});
13640
13641
input.addEventListener("blur", function(e){
13642
success(setValue(true));
13643
});
13644
13645
//submit new value on enter
13646
input.addEventListener("keydown", function(e){
13647
if(e.keyCode == 13){
13648
success(setValue());
13649
}
13650
if(e.keyCode == 27){
13651
cancel();
13652
}
13653
});
13654
13655
return input;
13656
}
13657
13658
var defaultEditors = {
13659
input:input,
13660
textarea:textarea,
13661
number:number,
13662
range:range,
13663
date:date,
13664
time:time,
13665
datetime:datetime,
13666
select:select,
13667
list:list,
13668
autocomplete:autocomplete,
13669
star:star,
13670
progress:progress,
13671
tickCross:tickCross,
13672
};
13673
13674
class Edit$1 extends Module{
13675
13676
constructor(table){
13677
super(table);
13678
13679
this.currentCell = false; //hold currently editing cell
13680
this.mouseClick = false; //hold mousedown state to prevent click binding being overridden by editor opening
13681
this.recursionBlock = false; //prevent focus recursion
13682
this.invalidEdit = false;
13683
this.editedCells = [];
13684
13685
this.editors = Edit$1.editors;
13686
13687
this.registerColumnOption("editable");
13688
this.registerColumnOption("editor");
13689
this.registerColumnOption("editorParams");
13690
13691
this.registerColumnOption("cellEditing");
13692
this.registerColumnOption("cellEdited");
13693
this.registerColumnOption("cellEditCancelled");
13694
13695
this.registerTableFunction("getEditedCells", this.getEditedCells.bind(this));
13696
this.registerTableFunction("clearCellEdited", this.clearCellEdited.bind(this));
13697
this.registerTableFunction("navigatePrev", this.navigatePrev.bind(this));
13698
this.registerTableFunction("navigateNext", this.navigateNext.bind(this));
13699
this.registerTableFunction("navigateLeft", this.navigateLeft.bind(this));
13700
this.registerTableFunction("navigateRight", this.navigateRight.bind(this));
13701
this.registerTableFunction("navigateUp", this.navigateUp.bind(this));
13702
this.registerTableFunction("navigateDown", this.navigateDown.bind(this));
13703
13704
this.registerComponentFunction("cell", "isEdited", this.cellIsEdited.bind(this));
13705
this.registerComponentFunction("cell", "clearEdited", this.clearEdited.bind(this));
13706
this.registerComponentFunction("cell", "edit", this.editCell.bind(this));
13707
this.registerComponentFunction("cell", "cancelEdit", this.cellCancelEdit.bind(this));
13708
13709
this.registerComponentFunction("cell", "navigatePrev", this.navigatePrev.bind(this));
13710
this.registerComponentFunction("cell", "navigateNext", this.navigateNext.bind(this));
13711
this.registerComponentFunction("cell", "navigateLeft", this.navigateLeft.bind(this));
13712
this.registerComponentFunction("cell", "navigateRight", this.navigateRight.bind(this));
13713
this.registerComponentFunction("cell", "navigateUp", this.navigateUp.bind(this));
13714
this.registerComponentFunction("cell", "navigateDown", this.navigateDown.bind(this));
13715
}
13716
13717
initialize(){
13718
this.subscribe("cell-init", this.bindEditor.bind(this));
13719
this.subscribe("cell-delete", this.clearEdited.bind(this));
13720
this.subscribe("cell-value-changed", this.updateCellClass.bind(this));
13721
this.subscribe("column-layout", this.initializeColumnCheck.bind(this));
13722
this.subscribe("column-delete", this.columnDeleteCheck.bind(this));
13723
this.subscribe("row-deleting", this.rowDeleteCheck.bind(this));
13724
this.subscribe("row-layout", this.rowEditableCheck.bind(this));
13725
this.subscribe("data-refreshing", this.cancelEdit.bind(this));
13726
13727
this.subscribe("keybinding-nav-prev", this.navigatePrev.bind(this, undefined));
13728
this.subscribe("keybinding-nav-next", this.keybindingNavigateNext.bind(this));
13729
this.subscribe("keybinding-nav-left", this.navigateLeft.bind(this, undefined));
13730
this.subscribe("keybinding-nav-right", this.navigateRight.bind(this, undefined));
13731
this.subscribe("keybinding-nav-up", this.navigateUp.bind(this, undefined));
13732
this.subscribe("keybinding-nav-down", this.navigateDown.bind(this, undefined));
13733
}
13734
13735
13736
///////////////////////////////////
13737
////// Keybinding Functions ///////
13738
///////////////////////////////////
13739
13740
keybindingNavigateNext(e){
13741
var cell = this.currentCell,
13742
newRow = this.options("tabEndNewRow");
13743
13744
if(cell){
13745
if(!this.navigateNext(cell, e)){
13746
if(newRow){
13747
cell.getElement().firstChild.blur();
13748
13749
if(newRow === true){
13750
newRow = this.table.addRow({});
13751
}else {
13752
if(typeof newRow == "function"){
13753
newRow = this.table.addRow(newRow(cell.row.getComponent()));
13754
}else {
13755
newRow = this.table.addRow(Object.assign({}, newRow));
13756
}
13757
}
13758
13759
newRow.then(() => {
13760
setTimeout(() => {
13761
cell.getComponent().navigateNext();
13762
});
13763
});
13764
}
13765
}
13766
}
13767
}
13768
13769
///////////////////////////////////
13770
///////// Cell Functions //////////
13771
///////////////////////////////////
13772
13773
cellIsEdited(cell){
13774
return !! cell.modules.edit && cell.modules.edit.edited;
13775
}
13776
13777
cellCancelEdit(cell){
13778
if(cell === this.currentCell){
13779
this.table.modules.edit.cancelEdit();
13780
}else {
13781
console.warn("Cancel Editor Error - This cell is not currently being edited ");
13782
}
13783
}
13784
13785
13786
///////////////////////////////////
13787
///////// Table Functions /////////
13788
///////////////////////////////////
13789
updateCellClass(cell){
13790
if(this.allowEdit(cell)) {
13791
cell.getElement().classList.add("tabulator-editable");
13792
}
13793
else {
13794
cell.getElement().classList.remove("tabulator-editable");
13795
}
13796
}
13797
13798
clearCellEdited(cells){
13799
if(!cells){
13800
cells = this.table.modules.edit.getEditedCells();
13801
}
13802
13803
if(!Array.isArray(cells)){
13804
cells = [cells];
13805
}
13806
13807
cells.forEach((cell) => {
13808
this.table.modules.edit.clearEdited(cell._getSelf());
13809
});
13810
}
13811
13812
navigatePrev(cell = this.currentCell, e){
13813
var nextCell, prevRow;
13814
13815
if(cell){
13816
13817
if(e){
13818
e.preventDefault();
13819
}
13820
13821
nextCell = this.navigateLeft();
13822
13823
if(nextCell){
13824
return true;
13825
}else {
13826
prevRow = this.table.rowManager.prevDisplayRow(cell.row, true);
13827
13828
if(prevRow){
13829
nextCell = this.findPrevEditableCell(prevRow, prevRow.cells.length);
13830
13831
if(nextCell){
13832
nextCell.getComponent().edit();
13833
return true;
13834
}
13835
}
13836
}
13837
}
13838
13839
return false;
13840
}
13841
13842
navigateNext(cell = this.currentCell, e){
13843
var nextCell, nextRow;
13844
13845
if(cell){
13846
13847
if(e){
13848
e.preventDefault();
13849
}
13850
13851
nextCell = this.navigateRight();
13852
13853
if(nextCell){
13854
return true;
13855
}else {
13856
nextRow = this.table.rowManager.nextDisplayRow(cell.row, true);
13857
13858
if(nextRow){
13859
nextCell = this.findNextEditableCell(nextRow, -1);
13860
13861
if(nextCell){
13862
nextCell.getComponent().edit();
13863
return true;
13864
}
13865
}
13866
}
13867
}
13868
13869
return false;
13870
}
13871
13872
navigateLeft(cell = this.currentCell, e){
13873
var index, nextCell;
13874
13875
if(cell){
13876
13877
if(e){
13878
e.preventDefault();
13879
}
13880
13881
index = cell.getIndex();
13882
nextCell = this.findPrevEditableCell(cell.row, index);
13883
13884
if(nextCell){
13885
nextCell.getComponent().edit();
13886
return true;
13887
}
13888
}
13889
13890
return false;
13891
}
13892
13893
navigateRight(cell = this.currentCell, e){
13894
var index, nextCell;
13895
13896
if(cell){
13897
13898
if(e){
13899
e.preventDefault();
13900
}
13901
13902
index = cell.getIndex();
13903
nextCell = this.findNextEditableCell(cell.row, index);
13904
13905
if(nextCell){
13906
nextCell.getComponent().edit();
13907
return true;
13908
}
13909
}
13910
13911
return false;
13912
}
13913
13914
navigateUp(cell = this.currentCell, e){
13915
var index, nextRow;
13916
13917
if(cell){
13918
13919
if(e){
13920
e.preventDefault();
13921
}
13922
13923
index = cell.getIndex();
13924
nextRow = this.table.rowManager.prevDisplayRow(cell.row, true);
13925
13926
if(nextRow){
13927
nextRow.cells[index].getComponent().edit();
13928
return true;
13929
}
13930
}
13931
13932
return false;
13933
}
13934
13935
navigateDown(cell = this.currentCell, e){
13936
var index, nextRow;
13937
13938
if(cell){
13939
13940
if(e){
13941
e.preventDefault();
13942
}
13943
13944
index = cell.getIndex();
13945
nextRow = this.table.rowManager.nextDisplayRow(cell.row, true);
13946
13947
if(nextRow){
13948
nextRow.cells[index].getComponent().edit();
13949
return true;
13950
}
13951
}
13952
13953
return false;
13954
}
13955
13956
findNextEditableCell(row, index){
13957
var nextCell = false;
13958
13959
if(index < row.cells.length-1){
13960
for(var i = index+1; i < row.cells.length; i++){
13961
let cell = row.cells[i];
13962
13963
if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){
13964
let allowEdit = this.allowEdit(cell);
13965
13966
if(allowEdit){
13967
nextCell = cell;
13968
break;
13969
}
13970
}
13971
}
13972
}
13973
13974
return nextCell;
13975
}
13976
13977
findPrevEditableCell(row, index){
13978
var prevCell = false;
13979
13980
if(index > 0){
13981
for(var i = index-1; i >= 0; i--){
13982
let cell = row.cells[i];
13983
13984
if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){
13985
let allowEdit = this.allowEdit(cell);
13986
13987
if(allowEdit){
13988
prevCell = cell;
13989
break;
13990
}
13991
}
13992
}
13993
}
13994
13995
return prevCell;
13996
}
13997
13998
///////////////////////////////////
13999
///////// Internal Logic //////////
14000
///////////////////////////////////
14001
14002
initializeColumnCheck(column){
14003
if(typeof column.definition.editor !== "undefined"){
14004
this.initializeColumn(column);
14005
}
14006
}
14007
14008
columnDeleteCheck(column){
14009
if(this.currentCell && this.currentCell.column === column){
14010
this.cancelEdit();
14011
}
14012
}
14013
14014
rowDeleteCheck(row){
14015
if(this.currentCell && this.currentCell.row === row){
14016
this.cancelEdit();
14017
}
14018
}
14019
14020
rowEditableCheck(row){
14021
row.getCells().forEach((cell) => {
14022
if(cell.column.modules.edit && typeof cell.column.modules.edit.check === "function"){
14023
this.updateCellClass(cell);
14024
}
14025
});
14026
}
14027
14028
//initialize column editor
14029
initializeColumn(column){
14030
var config = {
14031
editor:false,
14032
blocked:false,
14033
check:column.definition.editable,
14034
params:column.definition.editorParams || {}
14035
};
14036
14037
//set column editor
14038
switch(typeof column.definition.editor){
14039
case "string":
14040
if(this.editors[column.definition.editor]){
14041
config.editor = this.editors[column.definition.editor];
14042
}else {
14043
console.warn("Editor Error - No such editor found: ", column.definition.editor);
14044
}
14045
break;
14046
14047
case "function":
14048
config.editor = column.definition.editor;
14049
break;
14050
14051
case "boolean":
14052
if(column.definition.editor === true){
14053
if(typeof column.definition.formatter !== "function"){
14054
if(this.editors[column.definition.formatter]){
14055
config.editor = this.editors[column.definition.formatter];
14056
}else {
14057
config.editor = this.editors["input"];
14058
}
14059
}else {
14060
console.warn("Editor Error - Cannot auto lookup editor for a custom formatter: ", column.definition.formatter);
14061
}
14062
}
14063
break;
14064
}
14065
14066
if(config.editor){
14067
column.modules.edit = config;
14068
}
14069
}
14070
14071
getCurrentCell(){
14072
return this.currentCell ? this.currentCell.getComponent() : false;
14073
}
14074
14075
clearEditor(cancel){
14076
var cell = this.currentCell,
14077
cellEl;
14078
14079
this.invalidEdit = false;
14080
14081
if(cell){
14082
this.currentCell = false;
14083
14084
cellEl = cell.getElement();
14085
14086
this.dispatch("edit-editor-clear", cell, cancel);
14087
14088
cellEl.classList.remove("tabulator-editing");
14089
14090
while(cellEl.firstChild) cellEl.removeChild(cellEl.firstChild);
14091
14092
cell.row.getElement().classList.remove("tabulator-editing");
14093
14094
cell.table.element.classList.remove("tabulator-editing");
14095
}
14096
}
14097
14098
cancelEdit(){
14099
if(this.currentCell){
14100
var cell = this.currentCell;
14101
var component = this.currentCell.getComponent();
14102
14103
this.clearEditor(true);
14104
cell.setValueActual(cell.getValue());
14105
cell.cellRendered();
14106
14107
if(cell.column.definition.editor == "textarea" || cell.column.definition.variableHeight){
14108
cell.row.normalizeHeight(true);
14109
}
14110
14111
if(cell.column.definition.cellEditCancelled){
14112
cell.column.definition.cellEditCancelled.call(this.table, component);
14113
}
14114
14115
this.dispatch("edit-cancelled", cell);
14116
this.dispatchExternal("cellEditCancelled", component);
14117
}
14118
}
14119
14120
//return a formatted value for a cell
14121
bindEditor(cell){
14122
if(cell.column.modules.edit){
14123
var self = this,
14124
element = cell.getElement(true);
14125
14126
this.updateCellClass(cell);
14127
element.setAttribute("tabindex", 0);
14128
14129
element.addEventListener("click", function(e){
14130
if(!element.classList.contains("tabulator-editing")){
14131
element.focus({preventScroll: true});
14132
}
14133
});
14134
14135
element.addEventListener("mousedown", function(e){
14136
if (e.button === 2) {
14137
e.preventDefault();
14138
}else {
14139
self.mouseClick = true;
14140
}
14141
});
14142
14143
element.addEventListener("focus", function(e){
14144
if(!self.recursionBlock){
14145
self.edit(cell, e, false);
14146
}
14147
});
14148
}
14149
}
14150
14151
focusCellNoEvent(cell, block){
14152
this.recursionBlock = true;
14153
14154
if(!(block && this.table.browser === "ie")){
14155
cell.getElement().focus({preventScroll: true});
14156
}
14157
14158
this.recursionBlock = false;
14159
}
14160
14161
editCell(cell, forceEdit){
14162
this.focusCellNoEvent(cell);
14163
this.edit(cell, false, forceEdit);
14164
}
14165
14166
focusScrollAdjust(cell){
14167
if(this.table.rowManager.getRenderMode() == "virtual"){
14168
var topEdge = this.table.rowManager.element.scrollTop,
14169
bottomEdge = this.table.rowManager.element.clientHeight + this.table.rowManager.element.scrollTop,
14170
rowEl = cell.row.getElement();
14171
14172
if(rowEl.offsetTop < topEdge){
14173
this.table.rowManager.element.scrollTop -= (topEdge - rowEl.offsetTop);
14174
}else {
14175
if(rowEl.offsetTop + rowEl.offsetHeight > bottomEdge){
14176
this.table.rowManager.element.scrollTop += (rowEl.offsetTop + rowEl.offsetHeight - bottomEdge);
14177
}
14178
}
14179
14180
var leftEdge = this.table.rowManager.element.scrollLeft,
14181
rightEdge = this.table.rowManager.element.clientWidth + this.table.rowManager.element.scrollLeft,
14182
cellEl = cell.getElement();
14183
14184
if(this.table.modExists("frozenColumns")){
14185
leftEdge += parseInt(this.table.modules.frozenColumns.leftMargin || 0);
14186
rightEdge -= parseInt(this.table.modules.frozenColumns.rightMargin || 0);
14187
}
14188
14189
if(this.table.options.renderHorizontal === "virtual"){
14190
leftEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft);
14191
rightEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft);
14192
}
14193
14194
if(cellEl.offsetLeft < leftEdge){
14195
this.table.rowManager.element.scrollLeft -= (leftEdge - cellEl.offsetLeft);
14196
}else {
14197
if(cellEl.offsetLeft + cellEl.offsetWidth > rightEdge){
14198
this.table.rowManager.element.scrollLeft += (cellEl.offsetLeft + cellEl.offsetWidth - rightEdge);
14199
}
14200
}
14201
}
14202
}
14203
14204
allowEdit(cell) {
14205
var check = cell.column.modules.edit ? true : false;
14206
14207
if(cell.column.modules.edit){
14208
switch(typeof cell.column.modules.edit.check){
14209
case "function":
14210
if(cell.row.initialized){
14211
check = cell.column.modules.edit.check(cell.getComponent());
14212
}
14213
break;
14214
14215
case "string":
14216
check = !!cell.row.data[cell.column.modules.edit.check];
14217
break;
14218
14219
case "boolean":
14220
check = cell.column.modules.edit.check;
14221
break;
14222
}
14223
}
14224
14225
return check;
14226
}
14227
14228
edit(cell, e, forceEdit){
14229
var self = this,
14230
allowEdit = true,
14231
rendered = function(){},
14232
element = cell.getElement(),
14233
cellEditor, component, params;
14234
14235
//prevent editing if another cell is refusing to leave focus (eg. validation fail)
14236
14237
if(this.currentCell){
14238
if(!this.invalidEdit && this.currentCell !== cell){
14239
this.cancelEdit();
14240
}
14241
return;
14242
}
14243
14244
//handle successful value change
14245
function success(value){
14246
if(self.currentCell === cell){
14247
var valid = self.chain("edit-success", [cell, value], true, true);
14248
14249
if(valid === true || self.table.options.validationMode === "highlight"){
14250
self.clearEditor();
14251
14252
14253
if(!cell.modules.edit){
14254
cell.modules.edit = {};
14255
}
14256
14257
cell.modules.edit.edited = true;
14258
14259
if(self.editedCells.indexOf(cell) == -1){
14260
self.editedCells.push(cell);
14261
}
14262
14263
cell.setValue(value, true);
14264
14265
return valid === true;
14266
}else {
14267
self.invalidEdit = true;
14268
self.focusCellNoEvent(cell, true);
14269
rendered();
14270
return false;
14271
}
14272
}
14273
}
14274
14275
//handle aborted edit
14276
function cancel(){
14277
if(self.currentCell === cell){
14278
self.cancelEdit();
14279
}
14280
}
14281
14282
function onRendered(callback){
14283
rendered = callback;
14284
}
14285
14286
if(!cell.column.modules.edit.blocked){
14287
if(e){
14288
e.stopPropagation();
14289
}
14290
14291
allowEdit = this.allowEdit(cell);
14292
14293
if(allowEdit || forceEdit){
14294
14295
self.cancelEdit();
14296
14297
self.currentCell = cell;
14298
14299
this.focusScrollAdjust(cell);
14300
14301
component = cell.getComponent();
14302
14303
if(this.mouseClick){
14304
this.mouseClick = false;
14305
14306
if(cell.column.definition.cellClick){
14307
cell.column.definition.cellClick.call(this.table, e, component);
14308
}
14309
}
14310
14311
if(cell.column.definition.cellEditing){
14312
cell.column.definition.cellEditing.call(this.table, component);
14313
}
14314
14315
this.dispatch("cell-editing", cell);
14316
this.dispatchExternal("cellEditing", component);
14317
14318
params = typeof cell.column.modules.edit.params === "function" ? cell.column.modules.edit.params(component) : cell.column.modules.edit.params;
14319
14320
cellEditor = cell.column.modules.edit.editor.call(self, component, onRendered, success, cancel, params);
14321
14322
//if editor returned, add to DOM, if false, abort edit
14323
if(this.currentCell && cellEditor !== false){
14324
if(cellEditor instanceof Node){
14325
element.classList.add("tabulator-editing");
14326
cell.row.getElement().classList.add("tabulator-editing");
14327
cell.table.element.classList.add("tabulator-editing");
14328
while(element.firstChild) element.removeChild(element.firstChild);
14329
element.appendChild(cellEditor);
14330
14331
//trigger onRendered Callback
14332
rendered();
14333
14334
//prevent editing from triggering rowClick event
14335
var children = element.children;
14336
14337
for (var i = 0; i < children.length; i++) {
14338
children[i].addEventListener("click", function(e){
14339
e.stopPropagation();
14340
});
14341
}
14342
}else {
14343
console.warn("Edit Error - Editor should return an instance of Node, the editor returned:", cellEditor);
14344
element.blur();
14345
return false;
14346
}
14347
}else {
14348
element.blur();
14349
return false;
14350
}
14351
14352
return true;
14353
}else {
14354
this.mouseClick = false;
14355
element.blur();
14356
return false;
14357
}
14358
}else {
14359
this.mouseClick = false;
14360
element.blur();
14361
return false;
14362
}
14363
}
14364
14365
getEditedCells(){
14366
var output = [];
14367
14368
this.editedCells.forEach((cell) => {
14369
output.push(cell.getComponent());
14370
});
14371
14372
return output;
14373
}
14374
14375
clearEdited(cell){
14376
var editIndex;
14377
14378
if(cell.modules.edit && cell.modules.edit.edited){
14379
cell.modules.edit.edited = false;
14380
14381
this.dispatch("edit-edited-clear", cell);
14382
}
14383
14384
editIndex = this.editedCells.indexOf(cell);
14385
14386
if(editIndex > -1){
14387
this.editedCells.splice(editIndex, 1);
14388
}
14389
}
14390
}
14391
14392
Edit$1.moduleName = "edit";
14393
14394
//load defaults
14395
Edit$1.editors = defaultEditors;
14396
14397
class ExportRow{
14398
constructor(type, columns, component, indent){
14399
this.type = type;
14400
this.columns = columns;
14401
this.component = component || false;
14402
this.indent = indent || 0;
14403
}
14404
}
14405
14406
class ExportColumn{
14407
constructor(value, component, width, height, depth){
14408
this.value = value;
14409
this.component = component || false;
14410
this.width = width;
14411
this.height = height;
14412
this.depth = depth;
14413
}
14414
}
14415
14416
class Export extends Module{
14417
14418
constructor(table){
14419
super(table);
14420
14421
this.config = {};
14422
this.cloneTableStyle = true;
14423
this.colVisProp = "";
14424
14425
this.registerTableOption("htmlOutputConfig", false); //html output config
14426
14427
this.registerColumnOption("htmlOutput");
14428
this.registerColumnOption("titleHtmlOutput");
14429
}
14430
14431
initialize(){
14432
this.registerTableFunction("getHtml", this.getHtml.bind(this));
14433
}
14434
14435
///////////////////////////////////
14436
///////// Table Functions /////////
14437
///////////////////////////////////
14438
14439
14440
///////////////////////////////////
14441
///////// Internal Logic //////////
14442
///////////////////////////////////
14443
14444
generateExportList(config, style, range, colVisProp){
14445
this.cloneTableStyle = style;
14446
this.config = config || {};
14447
this.colVisProp = colVisProp;
14448
14449
var headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders()) : [];
14450
var body = this.bodyToExportRows(this.rowLookup(range));
14451
14452
return headers.concat(body);
14453
}
14454
14455
generateTable(config, style, range, colVisProp){
14456
var list = this.generateExportList(config, style, range, colVisProp);
14457
14458
return this.generateTableElement(list);
14459
}
14460
14461
rowLookup(range){
14462
var rows = [];
14463
14464
if(typeof range == "function"){
14465
range.call(this.table).forEach((row) =>{
14466
row = this.table.rowManager.findRow(row);
14467
14468
if(row){
14469
rows.push(row);
14470
}
14471
});
14472
}else {
14473
switch(range){
14474
case true:
14475
case "visible":
14476
rows = this.table.rowManager.getVisibleRows(false, true);
14477
break;
14478
14479
case "all":
14480
rows = this.table.rowManager.rows;
14481
break;
14482
14483
case "selected":
14484
rows = this.table.modules.selectRow.selectedRows;
14485
break;
14486
14487
case "active":
14488
default:
14489
if(this.table.options.pagination){
14490
rows = this.table.rowManager.getDisplayRows(this.table.rowManager.displayRows.length - 2);
14491
}else {
14492
rows = this.table.rowManager.getDisplayRows();
14493
}
14494
}
14495
}
14496
14497
return Object.assign([], rows);
14498
}
14499
14500
generateColumnGroupHeaders(){
14501
var output = [];
14502
14503
var columns = this.config.columnGroups !== false ? this.table.columnManager.columns : this.table.columnManager.columnsByIndex;
14504
14505
columns.forEach((column) => {
14506
var colData = this.processColumnGroup(column);
14507
14508
if(colData){
14509
output.push(colData);
14510
}
14511
});
14512
14513
return output;
14514
}
14515
14516
processColumnGroup(column){
14517
var subGroups = column.columns,
14518
maxDepth = 0,
14519
title = column.definition["title" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))] || column.definition.title;
14520
14521
var groupData = {
14522
title:title,
14523
column:column,
14524
depth:1,
14525
};
14526
14527
if(subGroups.length){
14528
groupData.subGroups = [];
14529
groupData.width = 0;
14530
14531
subGroups.forEach((subGroup) => {
14532
var subGroupData = this.processColumnGroup(subGroup);
14533
14534
if(subGroupData){
14535
groupData.width += subGroupData.width;
14536
groupData.subGroups.push(subGroupData);
14537
14538
if(subGroupData.depth > maxDepth){
14539
maxDepth = subGroupData.depth;
14540
}
14541
}
14542
});
14543
14544
groupData.depth += maxDepth;
14545
14546
if(!groupData.width){
14547
return false;
14548
}
14549
}else {
14550
if(this.columnVisCheck(column)){
14551
groupData.width = 1;
14552
}else {
14553
return false;
14554
}
14555
}
14556
14557
return groupData;
14558
}
14559
14560
columnVisCheck(column){
14561
var visProp = column.definition[this.colVisProp];
14562
14563
if(typeof visProp === "function"){
14564
visProp = visProp.call(this.table, column.getComponent());
14565
}
14566
14567
return visProp !== false && (column.visible || (!column.visible && visProp));
14568
}
14569
14570
headersToExportRows(columns){
14571
var headers = [],
14572
headerDepth = 0,
14573
exportRows = [];
14574
14575
function parseColumnGroup(column, level){
14576
14577
var depth = headerDepth - level;
14578
14579
if(typeof headers[level] === "undefined"){
14580
headers[level] = [];
14581
}
14582
14583
column.height = column.subGroups ? 1 : (depth - column.depth) + 1;
14584
14585
headers[level].push(column);
14586
14587
if(column.height > 1){
14588
for(let i = 1; i < column.height; i ++){
14589
14590
if(typeof headers[level + i] === "undefined"){
14591
headers[level + i] = [];
14592
}
14593
14594
headers[level + i].push(false);
14595
}
14596
}
14597
14598
if(column.width > 1){
14599
for(let i = 1; i < column.width; i ++){
14600
headers[level].push(false);
14601
}
14602
}
14603
14604
if(column.subGroups){
14605
column.subGroups.forEach(function(subGroup){
14606
parseColumnGroup(subGroup, level+1);
14607
});
14608
}
14609
}
14610
14611
//calculate maximum header depth
14612
columns.forEach(function(column){
14613
if(column.depth > headerDepth){
14614
headerDepth = column.depth;
14615
}
14616
});
14617
14618
columns.forEach(function(column){
14619
parseColumnGroup(column,0);
14620
});
14621
14622
headers.forEach((header) => {
14623
var columns = [];
14624
14625
header.forEach((col) => {
14626
if(col){
14627
let title = typeof col.title === "undefined" ? "" : col.title;
14628
columns.push(new ExportColumn(title, col.column.getComponent(), col.width, col.height, col.depth));
14629
}else {
14630
columns.push(null);
14631
}
14632
});
14633
14634
exportRows.push(new ExportRow("header", columns));
14635
});
14636
14637
return exportRows;
14638
}
14639
14640
bodyToExportRows(rows){
14641
14642
var columns = [];
14643
var exportRows = [];
14644
14645
this.table.columnManager.columnsByIndex.forEach((column) => {
14646
if (this.columnVisCheck(column)) {
14647
columns.push(column.getComponent());
14648
}
14649
});
14650
14651
if(this.config.columnCalcs !== false && this.table.modExists("columnCalcs")){
14652
if(this.table.modules.columnCalcs.topInitialized){
14653
rows.unshift(this.table.modules.columnCalcs.topRow);
14654
}
14655
14656
if(this.table.modules.columnCalcs.botInitialized){
14657
rows.push(this.table.modules.columnCalcs.botRow);
14658
}
14659
}
14660
14661
rows = rows.filter((row) => {
14662
switch(row.type){
14663
case "group":
14664
return this.config.rowGroups !== false;
14665
14666
case "calc":
14667
return this.config.columnCalcs !== false;
14668
14669
case "row":
14670
return !(this.table.options.dataTree && this.config.dataTree === false && row.modules.dataTree.parent);
14671
}
14672
14673
return true;
14674
});
14675
14676
rows.forEach((row, i) => {
14677
var rowData = row.getData(this.colVisProp);
14678
var exportCols = [];
14679
var indent = 0;
14680
14681
switch(row.type){
14682
case "group":
14683
indent = row.level;
14684
exportCols.push(new ExportColumn(row.key, row.getComponent(), columns.length, 1));
14685
break;
14686
14687
case "calc" :
14688
case "row" :
14689
columns.forEach((col) => {
14690
exportCols.push(new ExportColumn(col._column.getFieldValue(rowData), col, 1, 1));
14691
});
14692
14693
if(this.table.options.dataTree && this.config.dataTree !== false){
14694
indent = row.modules.dataTree.index;
14695
}
14696
break;
14697
}
14698
14699
exportRows.push(new ExportRow(row.type, exportCols, row.getComponent(), indent));
14700
});
14701
14702
return exportRows;
14703
}
14704
14705
generateTableElement(list){
14706
var table = document.createElement("table"),
14707
headerEl = document.createElement("thead"),
14708
bodyEl = document.createElement("tbody"),
14709
styles = this.lookupTableStyles(),
14710
rowFormatter = this.table.options["rowFormatter" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))],
14711
setup = {};
14712
14713
setup.rowFormatter = rowFormatter !== null ? rowFormatter : this.table.options.rowFormatter;
14714
14715
if(this.table.options.dataTree &&this.config.dataTree !== false && this.table.modExists("columnCalcs")){
14716
setup.treeElementField = this.table.modules.dataTree.elementField;
14717
}
14718
14719
//assign group header formatter
14720
setup.groupHeader = this.table.options["groupHeader" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))];
14721
14722
if(setup.groupHeader && !Array.isArray(setup.groupHeader)){
14723
setup.groupHeader = [setup.groupHeader];
14724
}
14725
14726
table.classList.add("tabulator-print-table");
14727
14728
this.mapElementStyles(this.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
14729
14730
14731
if(list.length > 1000){
14732
console.warn("It may take a long time to render an HTML table with more than 1000 rows");
14733
}
14734
14735
list.forEach((row, i) => {
14736
let rowEl;
14737
14738
switch(row.type){
14739
case "header":
14740
headerEl.appendChild(this.generateHeaderElement(row, setup, styles));
14741
break;
14742
14743
case "group":
14744
bodyEl.appendChild(this.generateGroupElement(row, setup, styles));
14745
break;
14746
14747
case "calc":
14748
bodyEl.appendChild(this.generateCalcElement(row, setup, styles));
14749
break;
14750
14751
case "row":
14752
rowEl = this.generateRowElement(row, setup, styles);
14753
14754
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"]);
14755
bodyEl.appendChild(rowEl);
14756
break;
14757
}
14758
});
14759
14760
if(headerEl.innerHTML){
14761
table.appendChild(headerEl);
14762
}
14763
14764
table.appendChild(bodyEl);
14765
14766
14767
this.mapElementStyles(this.table.element, table, ["border-top", "border-left", "border-right", "border-bottom"]);
14768
return table;
14769
}
14770
14771
lookupTableStyles(){
14772
var styles = {};
14773
14774
//lookup row styles
14775
if(this.cloneTableStyle && window.getComputedStyle){
14776
styles.oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)");
14777
styles.evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)");
14778
styles.calcRow = this.table.element.querySelector(".tabulator-row.tabulator-calcs");
14779
styles.firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)");
14780
styles.firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0];
14781
14782
if(styles.firstRow){
14783
styles.styleCells = styles.firstRow.getElementsByClassName("tabulator-cell");
14784
styles.firstCell = styles.styleCells[0];
14785
styles.lastCell = styles.styleCells[styles.styleCells.length - 1];
14786
}
14787
}
14788
14789
return styles;
14790
}
14791
14792
generateHeaderElement(row, setup, styles){
14793
var rowEl = document.createElement("tr");
14794
14795
row.columns.forEach((column) => {
14796
if(column){
14797
var cellEl = document.createElement("th");
14798
var classNames = column.component._column.definition.cssClass ? column.component._column.definition.cssClass.split(" ") : [];
14799
14800
cellEl.colSpan = column.width;
14801
cellEl.rowSpan = column.height;
14802
14803
cellEl.innerHTML = column.value;
14804
14805
if(this.cloneTableStyle){
14806
cellEl.style.boxSizing = "border-box";
14807
}
14808
14809
classNames.forEach(function(className) {
14810
cellEl.classList.add(className);
14811
});
14812
14813
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"]);
14814
this.mapElementStyles(column.component._column.contentElement, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);
14815
14816
if(column.component._column.visible){
14817
this.mapElementStyles(column.component.getElement(), cellEl, ["width"]);
14818
}else {
14819
if(column.component._column.definition.width){
14820
cellEl.style.width = column.component._column.definition.width + "px";
14821
}
14822
}
14823
14824
if(column.component._column.parent){
14825
this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]);
14826
}
14827
14828
rowEl.appendChild(cellEl);
14829
}
14830
});
14831
14832
return rowEl;
14833
}
14834
14835
generateGroupElement(row, setup, styles){
14836
14837
var rowEl = document.createElement("tr"),
14838
cellEl = document.createElement("td"),
14839
group = row.columns[0];
14840
14841
rowEl.classList.add("tabulator-print-table-row");
14842
14843
if(setup.groupHeader && setup.groupHeader[row.indent]){
14844
group.value = setup.groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
14845
}else {
14846
if(setup.groupHeader !== false){
14847
group.value = row.component._group.generator(group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
14848
}
14849
}
14850
14851
cellEl.colSpan = group.width;
14852
cellEl.innerHTML = group.value;
14853
14854
rowEl.classList.add("tabulator-print-table-group");
14855
rowEl.classList.add("tabulator-group-level-" + row.indent);
14856
14857
if(group.component.isVisible()){
14858
rowEl.classList.add("tabulator-group-visible");
14859
}
14860
14861
this.mapElementStyles(styles.firstGroup, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
14862
this.mapElementStyles(styles.firstGroup, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);
14863
14864
rowEl.appendChild(cellEl);
14865
14866
return rowEl;
14867
}
14868
14869
generateCalcElement(row, setup, styles){
14870
var rowEl = this.generateRowElement(row, setup, styles);
14871
14872
rowEl.classList.add("tabulator-print-table-calcs");
14873
this.mapElementStyles(styles.calcRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
14874
14875
return rowEl;
14876
}
14877
14878
generateRowElement(row, setup, styles){
14879
var rowEl = document.createElement("tr");
14880
14881
rowEl.classList.add("tabulator-print-table-row");
14882
14883
row.columns.forEach((col, i) => {
14884
if(col){
14885
var cellEl = document.createElement("td"),
14886
column = col.component._column,
14887
index = this.table.columnManager.findColumnIndex(column),
14888
value = col.value,
14889
cellStyle;
14890
14891
var cellWrapper = {
14892
modules:{},
14893
getValue:function(){
14894
return value;
14895
},
14896
getField:function(){
14897
return column.definition.field;
14898
},
14899
getElement:function(){
14900
return cellEl;
14901
},
14902
getType:function(){
14903
return "cell";
14904
},
14905
getColumn:function(){
14906
return column.getComponent();
14907
},
14908
getData:function(){
14909
return row.component.getData();
14910
},
14911
getRow:function(){
14912
return row.component;
14913
},
14914
getComponent:function(){
14915
return cellWrapper;
14916
},
14917
column:column,
14918
};
14919
14920
var classNames = column.definition.cssClass ? column.definition.cssClass.split(" ") : [];
14921
14922
classNames.forEach(function(className) {
14923
cellEl.classList.add(className);
14924
});
14925
14926
if(this.table.modExists("format") && this.config.formatCells !== false){
14927
value = this.table.modules.format.formatExportValue(cellWrapper, this.colVisProp);
14928
}else {
14929
switch(typeof value){
14930
case "object":
14931
value = value !== null ? JSON.stringify(value) : "";
14932
break;
14933
14934
case "undefined":
14935
value = "";
14936
break;
14937
}
14938
}
14939
14940
if(value instanceof Node){
14941
cellEl.appendChild(value);
14942
}else {
14943
cellEl.innerHTML = value;
14944
}
14945
14946
cellStyle = styles.styleCells && styles.styleCells[index] ? styles.styleCells[index] : styles.firstCell;
14947
14948
if(cellStyle){
14949
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"]);
14950
14951
if(column.definition.align){
14952
cellEl.style.textAlign = column.definition.align;
14953
}
14954
}
14955
14956
if(this.table.options.dataTree && this.config.dataTree !== false){
14957
if((setup.treeElementField && setup.treeElementField == column.field) || (!setup.treeElementField && i == 0)){
14958
if(row.component._row.modules.dataTree.controlEl){
14959
cellEl.insertBefore(row.component._row.modules.dataTree.controlEl.cloneNode(true), cellEl.firstChild);
14960
}
14961
if(row.component._row.modules.dataTree.branchEl){
14962
cellEl.insertBefore(row.component._row.modules.dataTree.branchEl.cloneNode(true), cellEl.firstChild);
14963
}
14964
}
14965
}
14966
14967
rowEl.appendChild(cellEl);
14968
14969
if(cellWrapper.modules.format && cellWrapper.modules.format.renderedCallback){
14970
cellWrapper.modules.format.renderedCallback();
14971
}
14972
}
14973
});
14974
14975
if(setup.rowFormatter && row.type === "row" && this.config.formatCells !== false){
14976
let formatComponent = Object.assign(row.component);
14977
14978
formatComponent.getElement = function(){return rowEl;};
14979
14980
setup.rowFormatter(row.component);
14981
}
14982
14983
return rowEl;
14984
}
14985
14986
generateHTMLTable(list){
14987
var holder = document.createElement("div");
14988
14989
holder.appendChild(this.generateTableElement(list));
14990
14991
return holder.innerHTML;
14992
}
14993
14994
getHtml(visible, style, config, colVisProp){
14995
var list = this.generateExportList(config || this.table.options.htmlOutputConfig, style, visible, colVisProp || "htmlOutput");
14996
14997
return this.generateHTMLTable(list);
14998
}
14999
15000
mapElementStyles(from, to, props){
15001
if(this.cloneTableStyle && from && to){
15002
15003
var lookup = {
15004
"background-color" : "backgroundColor",
15005
"color" : "fontColor",
15006
"width" : "width",
15007
"font-weight" : "fontWeight",
15008
"font-family" : "fontFamily",
15009
"font-size" : "fontSize",
15010
"text-align" : "textAlign",
15011
"border-top" : "borderTop",
15012
"border-left" : "borderLeft",
15013
"border-right" : "borderRight",
15014
"border-bottom" : "borderBottom",
15015
"padding-top" : "paddingTop",
15016
"padding-left" : "paddingLeft",
15017
"padding-right" : "paddingRight",
15018
"padding-bottom" : "paddingBottom",
15019
};
15020
15021
if(window.getComputedStyle){
15022
var fromStyle = window.getComputedStyle(from);
15023
15024
props.forEach(function(prop){
15025
if(!to.style[lookup[prop]]){
15026
to.style[lookup[prop]] = fromStyle.getPropertyValue(prop);
15027
}
15028
});
15029
}
15030
}
15031
}
15032
}
15033
15034
Export.moduleName = "export";
15035
15036
var defaultFilters = {
15037
15038
//equal to
15039
"=":function(filterVal, rowVal, rowData, filterParams){
15040
return rowVal == filterVal ? true : false;
15041
},
15042
15043
//less than
15044
"<":function(filterVal, rowVal, rowData, filterParams){
15045
return rowVal < filterVal ? true : false;
15046
},
15047
15048
//less than or equal to
15049
"<=":function(filterVal, rowVal, rowData, filterParams){
15050
return rowVal <= filterVal ? true : false;
15051
},
15052
15053
//greater than
15054
">":function(filterVal, rowVal, rowData, filterParams){
15055
return rowVal > filterVal ? true : false;
15056
},
15057
15058
//greater than or equal to
15059
">=":function(filterVal, rowVal, rowData, filterParams){
15060
return rowVal >= filterVal ? true : false;
15061
},
15062
15063
//not equal to
15064
"!=":function(filterVal, rowVal, rowData, filterParams){
15065
return rowVal != filterVal ? true : false;
15066
},
15067
15068
"regex":function(filterVal, rowVal, rowData, filterParams){
15069
15070
if(typeof filterVal == "string"){
15071
filterVal = new RegExp(filterVal);
15072
}
15073
15074
return filterVal.test(rowVal);
15075
},
15076
15077
//contains the string
15078
"like":function(filterVal, rowVal, rowData, filterParams){
15079
if(filterVal === null || typeof filterVal === "undefined"){
15080
return rowVal === filterVal ? true : false;
15081
}else {
15082
if(typeof rowVal !== 'undefined' && rowVal !== null){
15083
return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1;
15084
}
15085
else {
15086
return false;
15087
}
15088
}
15089
},
15090
15091
//contains the keywords
15092
"keywords":function(filterVal, rowVal, rowData, filterParams){
15093
var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator),
15094
value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(),
15095
matches = [];
15096
15097
keywords.forEach((keyword) =>{
15098
if(value.includes(keyword)){
15099
matches.push(true);
15100
}
15101
});
15102
15103
return filterParams.matchAll ? matches.length === keywords.length : !!matches.length;
15104
},
15105
15106
//starts with the string
15107
"starts":function(filterVal, rowVal, rowData, filterParams){
15108
if(filterVal === null || typeof filterVal === "undefined"){
15109
return rowVal === filterVal ? true : false;
15110
}else {
15111
if(typeof rowVal !== 'undefined' && rowVal !== null){
15112
return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase());
15113
}
15114
else {
15115
return false;
15116
}
15117
}
15118
},
15119
15120
//ends with the string
15121
"ends":function(filterVal, rowVal, rowData, filterParams){
15122
if(filterVal === null || typeof filterVal === "undefined"){
15123
return rowVal === filterVal ? true : false;
15124
}else {
15125
if(typeof rowVal !== 'undefined' && rowVal !== null){
15126
return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase());
15127
}
15128
else {
15129
return false;
15130
}
15131
}
15132
},
15133
15134
//in array
15135
"in":function(filterVal, rowVal, rowData, filterParams){
15136
if(Array.isArray(filterVal)){
15137
return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true;
15138
}else {
15139
console.warn("Filter Error - filter value is not an array:", filterVal);
15140
return false;
15141
}
15142
},
15143
};
15144
15145
class Filter extends Module{
15146
15147
constructor(table){
15148
super(table);
15149
15150
this.filterList = []; //hold filter list
15151
this.headerFilters = {}; //hold column filters
15152
this.headerFilterColumns = []; //hold columns that use header filters
15153
15154
this.prevHeaderFilterChangeCheck = "";
15155
this.prevHeaderFilterChangeCheck = "{}";
15156
15157
this.changed = false; //has filtering changed since last render
15158
this.tableInitialized = false;
15159
15160
this.registerTableOption("filterMode", "local"); //local or remote filtering
15161
15162
this.registerTableOption("initialFilter", false); //initial filtering criteria
15163
this.registerTableOption("initialHeaderFilter", false); //initial header filtering criteria
15164
this.registerTableOption("headerFilterLiveFilterDelay", 300); //delay before updating column after user types in header filter
15165
this.registerTableOption("placeholderHeaderFilter", false); //placeholder when header filter is empty
15166
15167
this.registerColumnOption("headerFilter");
15168
this.registerColumnOption("headerFilterPlaceholder");
15169
this.registerColumnOption("headerFilterParams");
15170
this.registerColumnOption("headerFilterEmptyCheck");
15171
this.registerColumnOption("headerFilterFunc");
15172
this.registerColumnOption("headerFilterFuncParams");
15173
this.registerColumnOption("headerFilterLiveFilter");
15174
15175
this.registerTableFunction("searchRows", this.searchRows.bind(this));
15176
this.registerTableFunction("searchData", this.searchData.bind(this));
15177
15178
this.registerTableFunction("setFilter", this.userSetFilter.bind(this));
15179
this.registerTableFunction("refreshFilter", this.userRefreshFilter.bind(this));
15180
this.registerTableFunction("addFilter", this.userAddFilter.bind(this));
15181
this.registerTableFunction("getFilters", this.getFilters.bind(this));
15182
this.registerTableFunction("setHeaderFilterFocus", this.userSetHeaderFilterFocus.bind(this));
15183
this.registerTableFunction("getHeaderFilterValue", this.userGetHeaderFilterValue.bind(this));
15184
this.registerTableFunction("setHeaderFilterValue", this.userSetHeaderFilterValue.bind(this));
15185
this.registerTableFunction("getHeaderFilters", this.getHeaderFilters.bind(this));
15186
this.registerTableFunction("removeFilter", this.userRemoveFilter.bind(this));
15187
this.registerTableFunction("clearFilter", this.userClearFilter.bind(this));
15188
this.registerTableFunction("clearHeaderFilter", this.userClearHeaderFilter.bind(this));
15189
15190
this.registerComponentFunction("column", "headerFilterFocus", this.setHeaderFilterFocus.bind(this));
15191
this.registerComponentFunction("column", "reloadHeaderFilter", this.reloadHeaderFilter.bind(this));
15192
this.registerComponentFunction("column", "getHeaderFilterValue", this.getHeaderFilterValue.bind(this));
15193
this.registerComponentFunction("column", "setHeaderFilterValue", this.setHeaderFilterValue.bind(this));
15194
}
15195
15196
initialize(){
15197
this.subscribe("column-init", this.initializeColumnHeaderFilter.bind(this));
15198
this.subscribe("column-width-fit-before", this.hideHeaderFilterElements.bind(this));
15199
this.subscribe("column-width-fit-after", this.showHeaderFilterElements.bind(this));
15200
this.subscribe("table-built", this.tableBuilt.bind(this));
15201
this.subscribe("placeholder", this.generatePlaceholder.bind(this));
15202
15203
if(this.table.options.filterMode === "remote"){
15204
this.subscribe("data-params", this.remoteFilterParams.bind(this));
15205
}
15206
15207
this.registerDataHandler(this.filter.bind(this), 10);
15208
}
15209
15210
tableBuilt(){
15211
if(this.table.options.initialFilter){
15212
this.setFilter(this.table.options.initialFilter);
15213
}
15214
15215
if(this.table.options.initialHeaderFilter){
15216
this.table.options.initialHeaderFilter.forEach((item) => {
15217
15218
var column = this.table.columnManager.findColumn(item.field);
15219
15220
if(column){
15221
this.setHeaderFilterValue(column, item.value);
15222
}else {
15223
console.warn("Column Filter Error - No matching column found:", item.field);
15224
return false;
15225
}
15226
});
15227
}
15228
15229
this.tableInitialized = true;
15230
}
15231
15232
remoteFilterParams(data, config, silent, params){
15233
params.filter = this.getFilters(true, true);
15234
return params;
15235
}
15236
15237
generatePlaceholder(text){
15238
if(this.table.options.placeholderHeaderFilter && Object.keys(this.headerFilters).length){
15239
return this.table.options.placeholderHeaderFilter;
15240
}
15241
}
15242
15243
///////////////////////////////////
15244
///////// Table Functions /////////
15245
///////////////////////////////////
15246
15247
//set standard filters
15248
userSetFilter(field, type, value, params){
15249
this.setFilter(field, type, value, params);
15250
this.refreshFilter();
15251
}
15252
15253
//set standard filters
15254
userRefreshFilter(){
15255
this.refreshFilter();
15256
}
15257
15258
//add filter to array
15259
userAddFilter(field, type, value, params){
15260
this.addFilter(field, type, value, params);
15261
this.refreshFilter();
15262
}
15263
15264
userSetHeaderFilterFocus(field){
15265
var column = this.table.columnManager.findColumn(field);
15266
15267
if(column){
15268
this.setHeaderFilterFocus(column);
15269
}else {
15270
console.warn("Column Filter Focus Error - No matching column found:", field);
15271
return false;
15272
}
15273
}
15274
15275
userGetHeaderFilterValue(field) {
15276
var column = this.table.columnManager.findColumn(field);
15277
15278
if(column){
15279
return this.getHeaderFilterValue(column);
15280
}else {
15281
console.warn("Column Filter Error - No matching column found:", field);
15282
}
15283
}
15284
15285
userSetHeaderFilterValue(field, value){
15286
var column = this.table.columnManager.findColumn(field);
15287
15288
if(column){
15289
this.setHeaderFilterValue(column, value);
15290
}else {
15291
console.warn("Column Filter Error - No matching column found:", field);
15292
return false;
15293
}
15294
}
15295
15296
//remove filter from array
15297
userRemoveFilter(field, type, value){
15298
this.removeFilter(field, type, value);
15299
this.refreshFilter();
15300
}
15301
15302
//clear filters
15303
userClearFilter(all){
15304
this.clearFilter(all);
15305
this.refreshFilter();
15306
}
15307
15308
//clear header filters
15309
userClearHeaderFilter(){
15310
this.clearHeaderFilter();
15311
this.refreshFilter();
15312
}
15313
15314
15315
//search for specific row components
15316
searchRows(field, type, value){
15317
return this.search("rows", field, type, value);
15318
}
15319
15320
//search for specific data
15321
searchData(field, type, value){
15322
return this.search("data", field, type, value);
15323
}
15324
15325
///////////////////////////////////
15326
///////// Internal Logic //////////
15327
///////////////////////////////////
15328
15329
initializeColumnHeaderFilter(column){
15330
var def = column.definition;
15331
15332
if(def.headerFilter){
15333
this.initializeColumn(column);
15334
}
15335
}
15336
15337
//initialize column header filter
15338
initializeColumn(column, value){
15339
var self = this,
15340
field = column.getField();
15341
15342
//handle successfully value change
15343
function success(value){
15344
var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match",
15345
type = "",
15346
filterChangeCheck = "",
15347
filterFunc;
15348
15349
if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){
15350
15351
column.modules.filter.prevSuccess = value;
15352
15353
if(!column.modules.filter.emptyFunc(value)){
15354
column.modules.filter.value = value;
15355
15356
switch(typeof column.definition.headerFilterFunc){
15357
case "string":
15358
if(Filter.filters[column.definition.headerFilterFunc]){
15359
type = column.definition.headerFilterFunc;
15360
filterFunc = function(data){
15361
var params = column.definition.headerFilterFuncParams || {};
15362
var fieldVal = column.getFieldValue(data);
15363
15364
params = typeof params === "function" ? params(value, fieldVal, data) : params;
15365
15366
return Filter.filters[column.definition.headerFilterFunc](value, fieldVal, data, params);
15367
};
15368
}else {
15369
console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc);
15370
}
15371
break;
15372
15373
case "function":
15374
filterFunc = function(data){
15375
var params = column.definition.headerFilterFuncParams || {};
15376
var fieldVal = column.getFieldValue(data);
15377
15378
params = typeof params === "function" ? params(value, fieldVal, data) : params;
15379
15380
return column.definition.headerFilterFunc(value, fieldVal, data, params);
15381
};
15382
15383
type = filterFunc;
15384
break;
15385
}
15386
15387
if(!filterFunc){
15388
switch(filterType){
15389
case "partial":
15390
filterFunc = function(data){
15391
var colVal = column.getFieldValue(data);
15392
15393
if(typeof colVal !== 'undefined' && colVal !== null){
15394
return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1;
15395
}else {
15396
return false;
15397
}
15398
};
15399
type = "like";
15400
break;
15401
15402
default:
15403
filterFunc = function(data){
15404
return column.getFieldValue(data) == value;
15405
};
15406
type = "=";
15407
}
15408
}
15409
15410
self.headerFilters[field] = {value:value, func:filterFunc, type:type};
15411
}else {
15412
delete self.headerFilters[field];
15413
}
15414
15415
column.modules.filter.value = value;
15416
15417
filterChangeCheck = JSON.stringify(self.headerFilters);
15418
15419
if(self.prevHeaderFilterChangeCheck !== filterChangeCheck){
15420
self.prevHeaderFilterChangeCheck = filterChangeCheck;
15421
15422
self.trackChanges();
15423
self.refreshFilter();
15424
}
15425
}
15426
15427
return true;
15428
}
15429
15430
column.modules.filter = {
15431
success:success,
15432
attrType:false,
15433
tagType:false,
15434
emptyFunc:false,
15435
};
15436
15437
this.generateHeaderFilterElement(column);
15438
}
15439
15440
generateHeaderFilterElement(column, initialValue, reinitialize){
15441
var self = this,
15442
success = column.modules.filter.success,
15443
field = column.getField(),
15444
filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params, onRenderedCallback;
15445
15446
column.modules.filter.value = initialValue;
15447
15448
//handle aborted edit
15449
function cancel(){}
15450
15451
function onRendered(callback){
15452
onRenderedCallback = callback;
15453
}
15454
15455
if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){
15456
column.contentElement.removeChild(column.modules.filter.headerElement.parentNode);
15457
}
15458
15459
if(field){
15460
15461
//set empty value function
15462
column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){
15463
return !value && value !== 0;
15464
};
15465
15466
filterElement = document.createElement("div");
15467
filterElement.classList.add("tabulator-header-filter");
15468
15469
//set column editor
15470
switch(typeof column.definition.headerFilter){
15471
case "string":
15472
if(self.table.modules.edit.editors[column.definition.headerFilter]){
15473
editor = self.table.modules.edit.editors[column.definition.headerFilter];
15474
15475
if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){
15476
column.modules.filter.emptyFunc = function(value){
15477
return value !== true && value !== false;
15478
};
15479
}
15480
}else {
15481
console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor);
15482
}
15483
break;
15484
15485
case "function":
15486
editor = column.definition.headerFilter;
15487
break;
15488
15489
case "boolean":
15490
if(column.modules.edit && column.modules.edit.editor){
15491
editor = column.modules.edit.editor;
15492
}else {
15493
if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){
15494
editor = self.table.modules.edit.editors[column.definition.formatter];
15495
15496
if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){
15497
column.modules.filter.emptyFunc = function(value){
15498
return value !== true && value !== false;
15499
};
15500
}
15501
}else {
15502
editor = self.table.modules.edit.editors["input"];
15503
}
15504
}
15505
break;
15506
}
15507
15508
if(editor){
15509
15510
cellWrapper = {
15511
getValue:function(){
15512
return typeof initialValue !== "undefined" ? initialValue : "";
15513
},
15514
getField:function(){
15515
return column.definition.field;
15516
},
15517
getElement:function(){
15518
return filterElement;
15519
},
15520
getColumn:function(){
15521
return column.getComponent();
15522
},
15523
getTable:() => {
15524
return this.table;
15525
},
15526
getType:() => {
15527
return "header";
15528
},
15529
getRow:function(){
15530
return {
15531
normalizeHeight:function(){
15532
15533
}
15534
};
15535
}
15536
};
15537
15538
params = column.definition.headerFilterParams || {};
15539
15540
params = typeof params === "function" ? params.call(self.table, cellWrapper) : params;
15541
15542
editorElement = editor.call(this.table.modules.edit, cellWrapper, onRendered, success, cancel, params);
15543
15544
if(!editorElement){
15545
console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false");
15546
return;
15547
}
15548
15549
if(!(editorElement instanceof Node)){
15550
console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement);
15551
return;
15552
}
15553
15554
//set Placeholder Text
15555
self.langBind("headerFilters|columns|" + column.definition.field, function(value){
15556
editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : (column.definition.headerFilterPlaceholder || self.langText("headerFilters|default")));
15557
});
15558
15559
//focus on element on click
15560
editorElement.addEventListener("click", function(e){
15561
e.stopPropagation();
15562
editorElement.focus();
15563
});
15564
15565
editorElement.addEventListener("focus", (e) => {
15566
var left = this.table.columnManager.contentsElement.scrollLeft;
15567
15568
var headerPos = this.table.rowManager.element.scrollLeft;
15569
15570
if(left !== headerPos){
15571
this.table.rowManager.scrollHorizontal(left);
15572
this.table.columnManager.scrollHorizontal(left);
15573
}
15574
});
15575
15576
//live update filters as user types
15577
typingTimer = false;
15578
15579
searchTrigger = function(e){
15580
if(typingTimer){
15581
clearTimeout(typingTimer);
15582
}
15583
15584
typingTimer = setTimeout(function(){
15585
success(editorElement.value);
15586
},self.table.options.headerFilterLiveFilterDelay);
15587
};
15588
15589
column.modules.filter.headerElement = editorElement;
15590
column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ;
15591
column.modules.filter.tagType = editorElement.tagName.toLowerCase();
15592
15593
if(column.definition.headerFilterLiveFilter !== false){
15594
15595
if (
15596
!(
15597
column.definition.headerFilter === 'autocomplete' ||
15598
column.definition.headerFilter === 'tickCross' ||
15599
((column.definition.editor === 'autocomplete' ||
15600
column.definition.editor === 'tickCross') &&
15601
column.definition.headerFilter === true)
15602
)
15603
) {
15604
editorElement.addEventListener("keyup", searchTrigger);
15605
editorElement.addEventListener("search", searchTrigger);
15606
15607
15608
//update number filtered columns on change
15609
if(column.modules.filter.attrType == "number"){
15610
editorElement.addEventListener("change", function(e){
15611
success(editorElement.value);
15612
});
15613
}
15614
15615
//change text inputs to search inputs to allow for clearing of field
15616
if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){
15617
editorElement.setAttribute("type", "search");
15618
// editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click
15619
}
15620
15621
}
15622
15623
//prevent input and select elements from propagating click to column sorters etc
15624
if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){
15625
editorElement.addEventListener("mousedown",function(e){
15626
e.stopPropagation();
15627
});
15628
}
15629
}
15630
15631
filterElement.appendChild(editorElement);
15632
15633
column.contentElement.appendChild(filterElement);
15634
15635
if(!reinitialize){
15636
self.headerFilterColumns.push(column);
15637
}
15638
15639
if(onRenderedCallback){
15640
onRenderedCallback();
15641
}
15642
}
15643
}else {
15644
console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title);
15645
}
15646
}
15647
15648
//hide all header filter elements (used to ensure correct column widths in "fitData" layout mode)
15649
hideHeaderFilterElements(){
15650
this.headerFilterColumns.forEach(function(column){
15651
if(column.modules.filter && column.modules.filter.headerElement){
15652
column.modules.filter.headerElement.style.display = 'none';
15653
}
15654
});
15655
}
15656
15657
//show all header filter elements (used to ensure correct column widths in "fitData" layout mode)
15658
showHeaderFilterElements(){
15659
this.headerFilterColumns.forEach(function(column){
15660
if(column.modules.filter && column.modules.filter.headerElement){
15661
column.modules.filter.headerElement.style.display = '';
15662
}
15663
});
15664
}
15665
15666
//programmatically set focus of header filter
15667
setHeaderFilterFocus(column){
15668
if(column.modules.filter && column.modules.filter.headerElement){
15669
column.modules.filter.headerElement.focus();
15670
}else {
15671
console.warn("Column Filter Focus Error - No header filter set on column:", column.getField());
15672
}
15673
}
15674
15675
//programmatically get value of header filter
15676
getHeaderFilterValue(column){
15677
if(column.modules.filter && column.modules.filter.headerElement){
15678
return column.modules.filter.value;
15679
} else {
15680
console.warn("Column Filter Error - No header filter set on column:", column.getField());
15681
}
15682
}
15683
15684
//programmatically set value of header filter
15685
setHeaderFilterValue(column, value){
15686
if (column){
15687
if(column.modules.filter && column.modules.filter.headerElement){
15688
this.generateHeaderFilterElement(column, value, true);
15689
column.modules.filter.success(value);
15690
}else {
15691
console.warn("Column Filter Error - No header filter set on column:", column.getField());
15692
}
15693
}
15694
}
15695
15696
reloadHeaderFilter(column){
15697
if (column){
15698
if(column.modules.filter && column.modules.filter.headerElement){
15699
this.generateHeaderFilterElement(column, column.modules.filter.value, true);
15700
}else {
15701
console.warn("Column Filter Error - No header filter set on column:", column.getField());
15702
}
15703
}
15704
}
15705
15706
refreshFilter(){
15707
if(this.tableInitialized){
15708
if(this.table.options.filterMode === "remote"){
15709
this.reloadData(null, false, false);
15710
}else {
15711
this.refreshData(true);
15712
}
15713
}
15714
15715
//TODO - Persist left position of row manager
15716
// left = this.scrollLeft;
15717
// this.scrollHorizontal(left);
15718
}
15719
15720
//check if the filters has changed since last use
15721
trackChanges(){
15722
this.changed = true;
15723
this.dispatch("filter-changed");
15724
}
15725
15726
//check if the filters has changed since last use
15727
hasChanged(){
15728
var changed = this.changed;
15729
this.changed = false;
15730
return changed;
15731
}
15732
15733
//set standard filters
15734
setFilter(field, type, value, params){
15735
this.filterList = [];
15736
15737
if(!Array.isArray(field)){
15738
field = [{field:field, type:type, value:value, params:params}];
15739
}
15740
15741
this.addFilter(field);
15742
}
15743
15744
//add filter to array
15745
addFilter(field, type, value, params){
15746
var changed = false;
15747
15748
if(!Array.isArray(field)){
15749
field = [{field:field, type:type, value:value, params:params}];
15750
}
15751
15752
field.forEach((filter) => {
15753
filter = this.findFilter(filter);
15754
15755
if(filter){
15756
this.filterList.push(filter);
15757
changed = true;
15758
}
15759
});
15760
15761
if(changed){
15762
this.trackChanges();
15763
}
15764
}
15765
15766
findFilter(filter){
15767
var column;
15768
15769
if(Array.isArray(filter)){
15770
return this.findSubFilters(filter);
15771
}
15772
15773
var filterFunc = false;
15774
15775
if(typeof filter.field == "function"){
15776
filterFunc = function(data){
15777
return filter.field(data, filter.type || {});// pass params to custom filter function
15778
};
15779
}else {
15780
15781
if(Filter.filters[filter.type]){
15782
15783
column = this.table.columnManager.getColumnByField(filter.field);
15784
15785
if(column){
15786
filterFunc = function(data){
15787
return Filter.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {});
15788
};
15789
}else {
15790
filterFunc = function(data){
15791
return Filter.filters[filter.type](filter.value, data[filter.field], data, filter.params || {});
15792
};
15793
}
15794
15795
15796
}else {
15797
console.warn("Filter Error - No such filter type found, ignoring: ", filter.type);
15798
}
15799
}
15800
15801
filter.func = filterFunc;
15802
15803
return filter.func ? filter : false;
15804
}
15805
15806
findSubFilters(filters){
15807
var output = [];
15808
15809
filters.forEach((filter) => {
15810
filter = this.findFilter(filter);
15811
15812
if(filter){
15813
output.push(filter);
15814
}
15815
});
15816
15817
return output.length ? output : false;
15818
}
15819
15820
//get all filters
15821
getFilters(all, ajax){
15822
var output = [];
15823
15824
if(all){
15825
output = this.getHeaderFilters();
15826
}
15827
15828
if(ajax){
15829
output.forEach(function(item){
15830
if(typeof item.type == "function"){
15831
item.type = "function";
15832
}
15833
});
15834
}
15835
15836
output = output.concat(this.filtersToArray(this.filterList, ajax));
15837
15838
return output;
15839
}
15840
15841
//filter to Object
15842
filtersToArray(filterList, ajax){
15843
var output = [];
15844
15845
filterList.forEach((filter) => {
15846
var item;
15847
15848
if(Array.isArray(filter)){
15849
output.push(this.filtersToArray(filter, ajax));
15850
}else {
15851
item = {field:filter.field, type:filter.type, value:filter.value};
15852
15853
if(ajax){
15854
if(typeof item.type == "function"){
15855
item.type = "function";
15856
}
15857
}
15858
15859
output.push(item);
15860
}
15861
});
15862
15863
return output;
15864
}
15865
15866
//get all filters
15867
getHeaderFilters(){
15868
var output = [];
15869
15870
for(var key in this.headerFilters){
15871
output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value});
15872
}
15873
15874
return output;
15875
}
15876
15877
//remove filter from array
15878
removeFilter(field, type, value){
15879
if(!Array.isArray(field)){
15880
field = [{field:field, type:type, value:value}];
15881
}
15882
15883
field.forEach((filter) => {
15884
var index = -1;
15885
15886
if(typeof filter.field == "object"){
15887
index = this.filterList.findIndex((element) => {
15888
return filter === element;
15889
});
15890
}else {
15891
index = this.filterList.findIndex((element) => {
15892
return filter.field === element.field && filter.type === element.type && filter.value === element.value;
15893
});
15894
}
15895
15896
if(index > -1){
15897
this.filterList.splice(index, 1);
15898
}else {
15899
console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type);
15900
}
15901
});
15902
15903
this.trackChanges();
15904
}
15905
15906
//clear filters
15907
clearFilter(all){
15908
this.filterList = [];
15909
15910
if(all){
15911
this.clearHeaderFilter();
15912
}
15913
15914
this.trackChanges();
15915
}
15916
15917
//clear header filters
15918
clearHeaderFilter(){
15919
this.headerFilters = {};
15920
this.prevHeaderFilterChangeCheck = "{}";
15921
15922
this.headerFilterColumns.forEach((column) => {
15923
if(typeof column.modules.filter.value !== "undefined"){
15924
delete column.modules.filter.value;
15925
}
15926
column.modules.filter.prevSuccess = undefined;
15927
this.reloadHeaderFilter(column);
15928
});
15929
15930
this.trackChanges();
15931
}
15932
15933
//search data and return matching rows
15934
search (searchType, field, type, value){
15935
var activeRows = [],
15936
filterList = [];
15937
15938
if(!Array.isArray(field)){
15939
field = [{field:field, type:type, value:value}];
15940
}
15941
15942
field.forEach((filter) => {
15943
filter = this.findFilter(filter);
15944
15945
if(filter){
15946
filterList.push(filter);
15947
}
15948
});
15949
15950
this.table.rowManager.rows.forEach((row) => {
15951
var match = true;
15952
15953
filterList.forEach((filter) => {
15954
if(!this.filterRecurse(filter, row.getData())){
15955
match = false;
15956
}
15957
});
15958
15959
if(match){
15960
activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent());
15961
}
15962
15963
});
15964
15965
return activeRows;
15966
}
15967
15968
//filter row array
15969
filter(rowList, filters){
15970
var activeRows = [],
15971
activeRowComponents = [];
15972
15973
if(this.subscribedExternal("dataFiltering")){
15974
this.dispatchExternal("dataFiltering", this.getFilters(true));
15975
}
15976
15977
if(this.table.options.filterMode !== "remote" && (this.filterList.length || Object.keys(this.headerFilters).length)){
15978
15979
rowList.forEach((row) => {
15980
if(this.filterRow(row)){
15981
activeRows.push(row);
15982
}
15983
});
15984
15985
}else {
15986
activeRows = rowList.slice(0);
15987
}
15988
15989
if(this.subscribedExternal("dataFiltered")){
15990
15991
activeRows.forEach((row) => {
15992
activeRowComponents.push(row.getComponent());
15993
});
15994
15995
this.dispatchExternal("dataFiltered", this.getFilters(true), activeRowComponents);
15996
}
15997
15998
return activeRows;
15999
}
16000
16001
//filter individual row
16002
filterRow(row, filters){
16003
var match = true,
16004
data = row.getData();
16005
16006
this.filterList.forEach((filter) => {
16007
if(!this.filterRecurse(filter, data)){
16008
match = false;
16009
}
16010
});
16011
16012
16013
for(var field in this.headerFilters){
16014
if(!this.headerFilters[field].func(data)){
16015
match = false;
16016
}
16017
}
16018
16019
return match;
16020
}
16021
16022
filterRecurse(filter, data){
16023
var match = false;
16024
16025
if(Array.isArray(filter)){
16026
filter.forEach((subFilter) => {
16027
if(this.filterRecurse(subFilter, data)){
16028
match = true;
16029
}
16030
});
16031
}else {
16032
match = filter.func(data);
16033
}
16034
16035
return match;
16036
}
16037
}
16038
16039
Filter.moduleName = "filter";
16040
16041
//load defaults
16042
Filter.filters = defaultFilters;
16043
16044
function plaintext(cell, formatterParams, onRendered){
16045
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
16046
}
16047
16048
function html$1(cell, formatterParams, onRendered){
16049
return cell.getValue();
16050
}
16051
16052
function textarea$1(cell, formatterParams, onRendered){
16053
cell.getElement().style.whiteSpace = "pre-wrap";
16054
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
16055
}
16056
16057
function money(cell, formatterParams, onRendered){
16058
var floatVal = parseFloat(cell.getValue()),
16059
sign = "",
16060
number, integer, decimal, rgx, value;
16061
16062
var decimalSym = formatterParams.decimal || ".";
16063
var thousandSym = formatterParams.thousand || ",";
16064
var negativeSign = formatterParams.negativeSign || "-";
16065
var symbol = formatterParams.symbol || "";
16066
var after = !!formatterParams.symbolAfter;
16067
var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2;
16068
16069
if(isNaN(floatVal)){
16070
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
16071
}
16072
16073
if(floatVal < 0){
16074
floatVal = Math.abs(floatVal);
16075
sign = negativeSign;
16076
}
16077
16078
number = precision !== false ? floatVal.toFixed(precision) : floatVal;
16079
number = String(number).split(".");
16080
16081
integer = number[0];
16082
decimal = number.length > 1 ? decimalSym + number[1] : "";
16083
16084
if (formatterParams.thousand !== false) {
16085
rgx = /(\d+)(\d{3})/;
16086
16087
while (rgx.test(integer)){
16088
integer = integer.replace(rgx, "$1" + thousandSym + "$2");
16089
}
16090
}
16091
16092
value = integer + decimal;
16093
16094
if(sign === true){
16095
value = "(" + value + ")";
16096
return after ? value + symbol : symbol + value;
16097
}else {
16098
return after ? sign + value + symbol : sign + symbol + value;
16099
}
16100
}
16101
16102
function link(cell, formatterParams, onRendered){
16103
var value = cell.getValue(),
16104
urlPrefix = formatterParams.urlPrefix || "",
16105
download = formatterParams.download,
16106
label = value,
16107
el = document.createElement("a"),
16108
data;
16109
16110
function labelTraverse(path, data){
16111
var item = path.shift(),
16112
value = data[item];
16113
16114
if(path.length && typeof value === "object"){
16115
return labelTraverse(path, value);
16116
}
16117
16118
return value;
16119
}
16120
16121
if(formatterParams.labelField){
16122
data = cell.getData();
16123
label = labelTraverse(formatterParams.labelField.split(this.table.options.nestedFieldSeparator), data);
16124
}
16125
16126
if(formatterParams.label){
16127
switch(typeof formatterParams.label){
16128
case "string":
16129
label = formatterParams.label;
16130
break;
16131
16132
case "function":
16133
label = formatterParams.label(cell);
16134
break;
16135
}
16136
}
16137
16138
if(label){
16139
if(formatterParams.urlField){
16140
data = cell.getData();
16141
value = data[formatterParams.urlField];
16142
}
16143
16144
if(formatterParams.url){
16145
switch(typeof formatterParams.url){
16146
case "string":
16147
value = formatterParams.url;
16148
break;
16149
16150
case "function":
16151
value = formatterParams.url(cell);
16152
break;
16153
}
16154
}
16155
16156
el.setAttribute("href", urlPrefix + value);
16157
16158
if(formatterParams.target){
16159
el.setAttribute("target", formatterParams.target);
16160
}
16161
16162
if(formatterParams.download){
16163
16164
if(typeof download == "function"){
16165
download = download(cell);
16166
}else {
16167
download = download === true ? "" : download;
16168
}
16169
16170
el.setAttribute("download", download);
16171
}
16172
16173
el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label));
16174
16175
return el;
16176
}else {
16177
return "&nbsp;";
16178
}
16179
}
16180
16181
function image(cell, formatterParams, onRendered){
16182
var el = document.createElement("img"),
16183
src = cell.getValue();
16184
16185
if(formatterParams.urlPrefix){
16186
src = formatterParams.urlPrefix + cell.getValue();
16187
}
16188
16189
if(formatterParams.urlSuffix){
16190
src = src + formatterParams.urlSuffix;
16191
}
16192
16193
el.setAttribute("src", src);
16194
16195
switch(typeof formatterParams.height){
16196
case "number":
16197
el.style.height = formatterParams.height + "px";
16198
break;
16199
16200
case "string":
16201
el.style.height = formatterParams.height;
16202
break;
16203
}
16204
16205
switch(typeof formatterParams.width){
16206
case "number":
16207
el.style.width = formatterParams.width + "px";
16208
break;
16209
16210
case "string":
16211
el.style.width = formatterParams.width;
16212
break;
16213
}
16214
16215
el.addEventListener("load", function(){
16216
cell.getRow().normalizeHeight();
16217
});
16218
16219
return el;
16220
}
16221
16222
function tickCross$1(cell, formatterParams, onRendered){
16223
var value = cell.getValue(),
16224
element = cell.getElement(),
16225
empty = formatterParams.allowEmpty,
16226
truthy = formatterParams.allowTruthy,
16227
trueValueSet = Object.keys(formatterParams).includes("trueValue"),
16228
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>',
16229
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>';
16230
16231
if((trueValueSet && value === formatterParams.trueValue) || (!trueValueSet && ((truthy && value) || (value === true || value === "true" || value === "True" || value === 1 || value === "1")))){
16232
element.setAttribute("aria-checked", true);
16233
return tick || "";
16234
}else {
16235
if(empty && (value === "null" || value === "" || value === null || typeof value === "undefined")){
16236
element.setAttribute("aria-checked", "mixed");
16237
return "";
16238
}else {
16239
element.setAttribute("aria-checked", false);
16240
return cross || "";
16241
}
16242
}
16243
}
16244
16245
function datetime$1(cell, formatterParams, onRendered){
16246
var DT = window.DateTime || luxon.DateTime;
16247
var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss";
16248
var outputFormat = formatterParams.outputFormat || "dd/MM/yyyy HH:mm:ss";
16249
var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
16250
var value = cell.getValue();
16251
16252
if(typeof DT != "undefined"){
16253
var newDatetime;
16254
16255
if(DT.isDateTime(value)){
16256
newDatetime = value;
16257
}else if(inputFormat === "iso"){
16258
newDatetime = DT.fromISO(String(value));
16259
}else {
16260
newDatetime = DT.fromFormat(String(value), inputFormat);
16261
}
16262
16263
if(newDatetime.isValid){
16264
if(formatterParams.timezone){
16265
newDatetime = newDatetime.setZone(formatterParams.timezone);
16266
}
16267
16268
return newDatetime.toFormat(outputFormat);
16269
}else {
16270
if(invalid === true || !value){
16271
return value;
16272
}else if(typeof invalid === "function"){
16273
return invalid(value);
16274
}else {
16275
return invalid;
16276
}
16277
}
16278
}else {
16279
console.error("Format Error - 'datetime' formatter is dependant on luxon.js");
16280
}
16281
}
16282
16283
function datetimediff (cell, formatterParams, onRendered) {
16284
var DT = window.DateTime || luxon.DateTime;
16285
var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss";
16286
var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
16287
var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false;
16288
var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : "days";
16289
var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false;
16290
var date = typeof formatterParams.date !== "undefined" ? formatterParams.date : DT.now();
16291
var value = cell.getValue();
16292
16293
if(typeof DT != "undefined"){
16294
var newDatetime;
16295
16296
if(DT.isDateTime(value)){
16297
newDatetime = value;
16298
}else if(inputFormat === "iso"){
16299
newDatetime = DT.fromISO(String(value));
16300
}else {
16301
newDatetime = DT.fromFormat(String(value), inputFormat);
16302
}
16303
16304
if (newDatetime.isValid){
16305
if(humanize){
16306
return newDatetime.diff(date, unit).toHuman() + (suffix ? " " + suffix : "");
16307
}else {
16308
return parseInt(newDatetime.diff(date, unit)[unit]) + (suffix ? " " + suffix : "");
16309
}
16310
} else {
16311
16312
if (invalid === true) {
16313
return value;
16314
} else if (typeof invalid === "function") {
16315
return invalid(value);
16316
} else {
16317
return invalid;
16318
}
16319
}
16320
}else {
16321
console.error("Format Error - 'datetimediff' formatter is dependant on luxon.js");
16322
}
16323
}
16324
16325
function lookup (cell, formatterParams, onRendered) {
16326
var value = cell.getValue();
16327
16328
if (typeof formatterParams[value] === "undefined") {
16329
console.warn('Missing display value for ' + value);
16330
return value;
16331
}
16332
16333
return formatterParams[value];
16334
}
16335
16336
function star$1(cell, formatterParams, onRendered){
16337
var value = cell.getValue(),
16338
element = cell.getElement(),
16339
maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5,
16340
stars = document.createElement("span"),
16341
star = document.createElementNS('http://www.w3.org/2000/svg', "svg"),
16342
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 "/>',
16343
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 "/>';
16344
16345
//style stars holder
16346
stars.style.verticalAlign = "middle";
16347
16348
//style star
16349
star.setAttribute("width", "14");
16350
star.setAttribute("height", "14");
16351
star.setAttribute("viewBox", "0 0 512 512");
16352
star.setAttribute("xml:space", "preserve");
16353
star.style.padding = "0 1px";
16354
16355
value = value && !isNaN(value) ? parseInt(value) : 0;
16356
16357
value = Math.max(0, Math.min(value, maxStars));
16358
16359
for(var i=1;i<= maxStars;i++){
16360
var nextStar = star.cloneNode(true);
16361
nextStar.innerHTML = i <= value ? starActive : starInactive;
16362
16363
stars.appendChild(nextStar);
16364
}
16365
16366
element.style.whiteSpace = "nowrap";
16367
element.style.overflow = "hidden";
16368
element.style.textOverflow = "ellipsis";
16369
16370
element.setAttribute("aria-label", value);
16371
16372
return stars;
16373
}
16374
16375
function traffic(cell, formatterParams, onRendered){
16376
var value = this.sanitizeHTML(cell.getValue()) || 0,
16377
el = document.createElement("span"),
16378
max = formatterParams && formatterParams.max ? formatterParams.max : 100,
16379
min = formatterParams && formatterParams.min ? formatterParams.min : 0,
16380
colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"],
16381
color = "#666666",
16382
percent, percentValue;
16383
16384
if(isNaN(value) || typeof cell.getValue() === "undefined"){
16385
return;
16386
}
16387
16388
el.classList.add("tabulator-traffic-light");
16389
16390
//make sure value is in range
16391
percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
16392
percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;
16393
16394
//workout percentage
16395
percent = (max - min) / 100;
16396
percentValue = Math.round((percentValue - min) / percent);
16397
16398
//set color
16399
switch(typeof colors){
16400
case "string":
16401
color = colors;
16402
break;
16403
case "function":
16404
color = colors(value);
16405
break;
16406
case "object":
16407
if(Array.isArray(colors)){
16408
var unit = 100 / colors.length;
16409
var index = Math.floor(percentValue / unit);
16410
16411
index = Math.min(index, colors.length - 1);
16412
index = Math.max(index, 0);
16413
color = colors[index];
16414
break;
16415
}
16416
}
16417
16418
el.style.backgroundColor = color;
16419
16420
return el;
16421
}
16422
16423
function progress$1(cell, formatterParams = {}, onRendered){ //progress bar
16424
var value = this.sanitizeHTML(cell.getValue()) || 0,
16425
element = cell.getElement(),
16426
max = formatterParams.max ? formatterParams.max : 100,
16427
min = formatterParams.min ? formatterParams.min : 0,
16428
legendAlign = formatterParams.legendAlign ? formatterParams.legendAlign : "center",
16429
percent, percentValue, color, legend, legendColor;
16430
16431
//make sure value is in range
16432
percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
16433
percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;
16434
16435
//workout percentage
16436
percent = (max - min) / 100;
16437
percentValue = Math.round((percentValue - min) / percent);
16438
16439
//set bar color
16440
switch(typeof formatterParams.color){
16441
case "string":
16442
color = formatterParams.color;
16443
break;
16444
case "function":
16445
color = formatterParams.color(value);
16446
break;
16447
case "object":
16448
if(Array.isArray(formatterParams.color)){
16449
let unit = 100 / formatterParams.color.length;
16450
let index = Math.floor(percentValue / unit);
16451
16452
index = Math.min(index, formatterParams.color.length - 1);
16453
index = Math.max(index, 0);
16454
color = formatterParams.color[index];
16455
break;
16456
}
16457
default:
16458
color = "#2DC214";
16459
}
16460
16461
//generate legend
16462
switch(typeof formatterParams.legend){
16463
case "string":
16464
legend = formatterParams.legend;
16465
break;
16466
case "function":
16467
legend = formatterParams.legend(value);
16468
break;
16469
case "boolean":
16470
legend = value;
16471
break;
16472
default:
16473
legend = false;
16474
}
16475
16476
//set legend color
16477
switch(typeof formatterParams.legendColor){
16478
case "string":
16479
legendColor = formatterParams.legendColor;
16480
break;
16481
case "function":
16482
legendColor = formatterParams.legendColor(value);
16483
break;
16484
case "object":
16485
if(Array.isArray(formatterParams.legendColor)){
16486
let unit = 100 / formatterParams.legendColor.length;
16487
let index = Math.floor(percentValue / unit);
16488
16489
index = Math.min(index, formatterParams.legendColor.length - 1);
16490
index = Math.max(index, 0);
16491
legendColor = formatterParams.legendColor[index];
16492
}
16493
break;
16494
default:
16495
legendColor = "#000";
16496
}
16497
16498
element.style.minWidth = "30px";
16499
element.style.position = "relative";
16500
16501
element.setAttribute("aria-label", percentValue);
16502
16503
var barEl = document.createElement("div");
16504
barEl.style.display = "inline-block";
16505
barEl.style.width = percentValue + "%";
16506
barEl.style.backgroundColor = color;
16507
barEl.style.height = "100%";
16508
16509
barEl.setAttribute('data-max', max);
16510
barEl.setAttribute('data-min', min);
16511
16512
var barContainer = document.createElement("div");
16513
barContainer.style.position = "relative";
16514
barContainer.style.width = "100%";
16515
barContainer.style.height = "100%";
16516
16517
if(legend){
16518
var legendEl = document.createElement("div");
16519
legendEl.style.position = "absolute";
16520
legendEl.style.top = 0;
16521
legendEl.style.left = 0;
16522
legendEl.style.textAlign = legendAlign;
16523
legendEl.style.width = "100%";
16524
legendEl.style.color = legendColor;
16525
legendEl.innerHTML = legend;
16526
}
16527
16528
onRendered(function(){
16529
16530
//handle custom element needed if formatter is to be included in printed/downloaded output
16531
if(!(cell instanceof CellComponent)){
16532
var holderEl = document.createElement("div");
16533
holderEl.style.position = "absolute";
16534
holderEl.style.top = "4px";
16535
holderEl.style.bottom = "4px";
16536
holderEl.style.left = "4px";
16537
holderEl.style.right = "4px";
16538
16539
element.appendChild(holderEl);
16540
16541
element = holderEl;
16542
}
16543
16544
element.appendChild(barContainer);
16545
barContainer.appendChild(barEl);
16546
16547
if(legend){
16548
barContainer.appendChild(legendEl);
16549
}
16550
});
16551
16552
return "";
16553
}
16554
16555
function color(cell, formatterParams, onRendered){
16556
cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue());
16557
return "";
16558
}
16559
16560
function buttonTick(cell, formatterParams, onRendered){
16561
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>';
16562
}
16563
16564
function buttonCross(cell, formatterParams, onRendered){
16565
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>';
16566
}
16567
16568
function rownum(cell, formatterParams, onRendered){
16569
var content = document.createElement("span");
16570
var row = cell.getRow();
16571
16572
row.watchPosition((position) => {
16573
content.innerText = position;
16574
});
16575
16576
return content;
16577
}
16578
16579
function handle(cell, formatterParams, onRendered){
16580
cell.getElement().classList.add("tabulator-row-handle");
16581
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>";
16582
}
16583
16584
function responsiveCollapse(cell, formatterParams, onRendered){
16585
var el = document.createElement("div"),
16586
config = cell.getRow()._row.modules.responsiveLayout;
16587
16588
el.classList.add("tabulator-responsive-collapse-toggle");
16589
16590
el.innerHTML = `<svg class='tabulator-responsive-collapse-toggle-open' viewbox="0 0 24 24">
16591
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
16592
<line y1="7" x1="12" y2="17" x2="12" fill="none" stroke-width="3" stroke-linecap="round" />
16593
</svg>
16594
16595
<svg class='tabulator-responsive-collapse-toggle-close' viewbox="0 0 24 24">
16596
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
16597
</svg>`;
16598
16599
cell.getElement().classList.add("tabulator-row-handle");
16600
16601
function toggleList(isOpen){
16602
var collapseEl = config.element;
16603
16604
config.open = isOpen;
16605
16606
if(collapseEl){
16607
16608
if(config.open){
16609
el.classList.add("open");
16610
collapseEl.style.display = '';
16611
}else {
16612
el.classList.remove("open");
16613
collapseEl.style.display = 'none';
16614
}
16615
}
16616
}
16617
16618
el.addEventListener("click", function(e){
16619
e.stopImmediatePropagation();
16620
toggleList(!config.open);
16621
cell.getTable().rowManager.adjustTableSize();
16622
});
16623
16624
toggleList(config.open);
16625
16626
return el;
16627
}
16628
16629
function rowSelection(cell, formatterParams, onRendered){
16630
var checkbox = document.createElement("input");
16631
var blocked = false;
16632
16633
checkbox.type = 'checkbox';
16634
16635
checkbox.setAttribute("aria-label", "Select Row");
16636
16637
if(this.table.modExists("selectRow", true)){
16638
16639
checkbox.addEventListener("click", (e) => {
16640
e.stopPropagation();
16641
});
16642
16643
if(typeof cell.getRow == 'function'){
16644
var row = cell.getRow();
16645
16646
if(row instanceof RowComponent){
16647
16648
checkbox.addEventListener("change", (e) => {
16649
if(this.table.options.selectableRangeMode === "click"){
16650
if(!blocked){
16651
row.toggleSelect();
16652
}else {
16653
blocked = false;
16654
}
16655
}else {
16656
row.toggleSelect();
16657
}
16658
});
16659
16660
if(this.table.options.selectableRangeMode === "click"){
16661
checkbox.addEventListener("click", (e) => {
16662
blocked = true;
16663
this.table.modules.selectRow.handleComplexRowClick(row._row, e);
16664
});
16665
}
16666
16667
checkbox.checked = row.isSelected && row.isSelected();
16668
this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox);
16669
}else {
16670
checkbox = "";
16671
}
16672
}else {
16673
checkbox.addEventListener("change", (e) => {
16674
if(this.table.modules.selectRow.selectedRows.length){
16675
this.table.deselectRow();
16676
}else {
16677
this.table.selectRow(formatterParams.rowRange);
16678
}
16679
});
16680
16681
this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox);
16682
}
16683
}
16684
16685
return checkbox;
16686
}
16687
16688
var defaultFormatters = {
16689
plaintext:plaintext,
16690
html:html$1,
16691
textarea:textarea$1,
16692
money:money,
16693
link:link,
16694
image:image,
16695
tickCross:tickCross$1,
16696
datetime:datetime$1,
16697
datetimediff:datetimediff,
16698
lookup:lookup,
16699
star:star$1,
16700
traffic:traffic,
16701
progress:progress$1,
16702
color:color,
16703
buttonTick:buttonTick,
16704
buttonCross:buttonCross,
16705
rownum:rownum,
16706
handle:handle,
16707
responsiveCollapse:responsiveCollapse,
16708
rowSelection:rowSelection,
16709
};
16710
16711
class Format extends Module{
16712
16713
constructor(table){
16714
super(table);
16715
16716
this.registerColumnOption("formatter");
16717
this.registerColumnOption("formatterParams");
16718
16719
this.registerColumnOption("formatterPrint");
16720
this.registerColumnOption("formatterPrintParams");
16721
this.registerColumnOption("formatterClipboard");
16722
this.registerColumnOption("formatterClipboardParams");
16723
this.registerColumnOption("formatterHtmlOutput");
16724
this.registerColumnOption("formatterHtmlOutputParams");
16725
this.registerColumnOption("titleFormatter");
16726
this.registerColumnOption("titleFormatterParams");
16727
}
16728
16729
initialize(){
16730
this.subscribe("cell-format", this.formatValue.bind(this));
16731
this.subscribe("cell-rendered", this.cellRendered.bind(this));
16732
this.subscribe("column-layout", this.initializeColumn.bind(this));
16733
this.subscribe("column-format", this.formatHeader.bind(this));
16734
}
16735
16736
//initialize column formatter
16737
initializeColumn(column){
16738
column.modules.format = this.lookupFormatter(column, "");
16739
16740
if(typeof column.definition.formatterPrint !== "undefined"){
16741
column.modules.format.print = this.lookupFormatter(column, "Print");
16742
}
16743
16744
if(typeof column.definition.formatterClipboard !== "undefined"){
16745
column.modules.format.clipboard = this.lookupFormatter(column, "Clipboard");
16746
}
16747
16748
if(typeof column.definition.formatterHtmlOutput !== "undefined"){
16749
column.modules.format.htmlOutput = this.lookupFormatter(column, "HtmlOutput");
16750
}
16751
}
16752
16753
lookupFormatter(column, type){
16754
var config = {params:column.definition["formatter" + type + "Params"] || {}},
16755
formatter = column.definition["formatter" + type];
16756
16757
//set column formatter
16758
switch(typeof formatter){
16759
case "string":
16760
if(Format.formatters[formatter]){
16761
config.formatter = Format.formatters[formatter];
16762
}else {
16763
console.warn("Formatter Error - No such formatter found: ", formatter);
16764
config.formatter = Format.formatters.plaintext;
16765
}
16766
break;
16767
16768
case "function":
16769
config.formatter = formatter;
16770
break;
16771
16772
default:
16773
config.formatter = Format.formatters.plaintext;
16774
break;
16775
}
16776
16777
return config;
16778
}
16779
16780
cellRendered(cell){
16781
if(cell.modules.format && cell.modules.format.renderedCallback && !cell.modules.format.rendered){
16782
cell.modules.format.renderedCallback();
16783
cell.modules.format.rendered = true;
16784
}
16785
}
16786
16787
//return a formatted value for a column header
16788
formatHeader(column, title, el){
16789
var formatter, params, onRendered, mockCell;
16790
16791
if(column.definition.titleFormatter){
16792
formatter = this.getFormatter(column.definition.titleFormatter);
16793
16794
onRendered = (callback) => {
16795
column.titleFormatterRendered = callback;
16796
};
16797
16798
mockCell = {
16799
getValue:function(){
16800
return title;
16801
},
16802
getElement:function(){
16803
return el;
16804
},
16805
getType:function(){
16806
return "header";
16807
},
16808
getColumn:function(){
16809
return column.getComponent();
16810
},
16811
getTable:() => {
16812
return this.table;
16813
}
16814
};
16815
16816
params = column.definition.titleFormatterParams || {};
16817
16818
params = typeof params === "function" ? params() : params;
16819
16820
return formatter.call(this, mockCell, params, onRendered);
16821
}else {
16822
return title;
16823
}
16824
}
16825
16826
16827
//return a formatted value for a cell
16828
formatValue(cell){
16829
var component = cell.getComponent(),
16830
params = typeof cell.column.modules.format.params === "function" ? cell.column.modules.format.params(component) : cell.column.modules.format.params;
16831
16832
function onRendered(callback){
16833
if(!cell.modules.format){
16834
cell.modules.format = {};
16835
}
16836
16837
cell.modules.format.renderedCallback = callback;
16838
cell.modules.format.rendered = false;
16839
}
16840
16841
return cell.column.modules.format.formatter.call(this, component, params, onRendered);
16842
}
16843
16844
formatExportValue(cell, type){
16845
var formatter = cell.column.modules.format[type],
16846
params;
16847
16848
if(formatter){
16849
params = typeof formatter.params === "function" ? formatter.params(cell.getComponent()) : formatter.params;
16850
16851
function onRendered(callback){
16852
if(!cell.modules.format){
16853
cell.modules.format = {};
16854
}
16855
16856
cell.modules.format.renderedCallback = callback;
16857
cell.modules.format.rendered = false;
16858
}
16859
16860
return formatter.formatter.call(this, cell.getComponent(), params, onRendered);
16861
16862
}else {
16863
return this.formatValue(cell);
16864
}
16865
}
16866
16867
sanitizeHTML(value){
16868
if(value){
16869
var entityMap = {
16870
'&': '&amp;',
16871
'<': '&lt;',
16872
'>': '&gt;',
16873
'"': '&quot;',
16874
"'": '&#39;',
16875
'/': '&#x2F;',
16876
'`': '&#x60;',
16877
'=': '&#x3D;'
16878
};
16879
16880
return String(value).replace(/[&<>"'`=/]/g, function (s) {
16881
return entityMap[s];
16882
});
16883
}else {
16884
return value;
16885
}
16886
}
16887
16888
emptyToSpace(value){
16889
return value === null || typeof value === "undefined" || value === "" ? "&nbsp;" : value;
16890
}
16891
16892
//get formatter for cell
16893
getFormatter(formatter){
16894
switch(typeof formatter){
16895
case "string":
16896
if(Format.formatters[formatter]){
16897
formatter = Format.formatters[formatter];
16898
}else {
16899
console.warn("Formatter Error - No such formatter found: ", formatter);
16900
formatter = Format.formatters.plaintext;
16901
}
16902
break;
16903
16904
case "function":
16905
//Custom formatter Function, do nothing
16906
break;
16907
16908
default:
16909
formatter = Format.formatters.plaintext;
16910
break;
16911
}
16912
16913
return formatter;
16914
}
16915
}
16916
16917
Format.moduleName = "format";
16918
16919
//load defaults
16920
Format.formatters = defaultFormatters;
16921
16922
class FrozenColumns extends Module{
16923
16924
constructor(table){
16925
super(table);
16926
16927
this.leftColumns = [];
16928
this.rightColumns = [];
16929
this.initializationMode = "left";
16930
this.active = false;
16931
this.blocked = true;
16932
16933
this.registerColumnOption("frozen");
16934
}
16935
16936
//reset initial state
16937
reset(){
16938
this.initializationMode = "left";
16939
this.leftColumns = [];
16940
this.rightColumns = [];
16941
this.active = false;
16942
}
16943
16944
initialize(){
16945
this.subscribe("cell-layout", this.layoutCell.bind(this));
16946
this.subscribe("column-init", this.initializeColumn.bind(this));
16947
this.subscribe("column-width", this.layout.bind(this));
16948
this.subscribe("row-layout-after", this.layoutRow.bind(this));
16949
this.subscribe("table-layout", this.layout.bind(this));
16950
this.subscribe("columns-loading", this.reset.bind(this));
16951
16952
this.subscribe("column-add", this.reinitializeColumns.bind(this));
16953
this.subscribe("column-delete", this.reinitializeColumns.bind(this));
16954
16955
this.subscribe("table-redraw", this.layout.bind(this));
16956
this.subscribe("layout-refreshing", this.blockLayout.bind(this));
16957
this.subscribe("layout-refreshed", this.unblockLayout.bind(this));
16958
this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this));
16959
}
16960
16961
blockLayout(){
16962
this.blocked = true;
16963
}
16964
16965
unblockLayout(){
16966
this.blocked = false;
16967
}
16968
16969
layoutCell(cell){
16970
this.layoutElement(cell.element, cell.column);
16971
}
16972
16973
reinitializeColumns(){
16974
this.reset();
16975
16976
this.table.columnManager.columnsByIndex.forEach((column) => {
16977
this.initializeColumn(column);
16978
});
16979
}
16980
16981
//initialize specific column
16982
initializeColumn(column){
16983
var config = {margin:0, edge:false};
16984
16985
if(!column.isGroup){
16986
16987
if(this.frozenCheck(column)){
16988
16989
config.position = this.initializationMode;
16990
16991
if(this.initializationMode == "left"){
16992
this.leftColumns.push(column);
16993
}else {
16994
this.rightColumns.unshift(column);
16995
}
16996
16997
this.active = true;
16998
16999
column.modules.frozen = config;
17000
}else {
17001
this.initializationMode = "right";
17002
}
17003
}
17004
}
17005
17006
frozenCheck(column){
17007
if(column.parent.isGroup && column.definition.frozen){
17008
console.warn("Frozen Column Error - Parent column group must be frozen, not individual columns or sub column groups");
17009
}
17010
17011
if(column.parent.isGroup){
17012
return this.frozenCheck(column.parent);
17013
}else {
17014
return column.definition.frozen;
17015
}
17016
}
17017
17018
//layout calculation rows
17019
layoutCalcRows(){
17020
if(this.table.modExists("columnCalcs")){
17021
if(this.table.modules.columnCalcs.topInitialized && this.table.modules.columnCalcs.topRow){
17022
this.layoutRow(this.table.modules.columnCalcs.topRow);
17023
}
17024
17025
if(this.table.modules.columnCalcs.botInitialized && this.table.modules.columnCalcs.botRow){
17026
this.layoutRow(this.table.modules.columnCalcs.botRow);
17027
}
17028
17029
if(this.table.modExists("groupRows")){
17030
this.layoutGroupCalcs(this.table.modules.groupRows.getGroups());
17031
}
17032
}
17033
}
17034
17035
layoutGroupCalcs(groups){
17036
groups.forEach((group) => {
17037
if(group.calcs.top){
17038
this.layoutRow(group.calcs.top);
17039
}
17040
17041
if(group.calcs.bottom){
17042
this.layoutRow(group.calcs.bottom);
17043
}
17044
17045
if(group.groupList && group.groupList.length){
17046
this.layoutGroupCalcs(group.groupList);
17047
}
17048
});
17049
}
17050
17051
//calculate column positions and layout headers
17052
layoutColumnPosition(allCells){
17053
var leftParents = [];
17054
17055
var leftMargin = 0;
17056
var rightMargin = 0;
17057
17058
this.leftColumns.forEach((column, i) => {
17059
column.modules.frozen.marginValue = leftMargin;
17060
column.modules.frozen.margin = column.modules.frozen.marginValue + "px";
17061
17062
if(column.visible){
17063
leftMargin += column.getWidth();
17064
}
17065
17066
if(i == this.leftColumns.length - 1){
17067
column.modules.frozen.edge = true;
17068
}else {
17069
column.modules.frozen.edge = false;
17070
}
17071
17072
if(column.parent.isGroup){
17073
var parentEl = this.getColGroupParentElement(column);
17074
if(!leftParents.includes(parentEl)){
17075
this.layoutElement(parentEl, column);
17076
leftParents.push(parentEl);
17077
}
17078
17079
if(column.modules.frozen.edge){
17080
parentEl.classList.add("tabulator-frozen-" + column.modules.frozen.position);
17081
}
17082
}else {
17083
this.layoutElement(column.getElement(), column);
17084
}
17085
17086
if(allCells){
17087
column.cells.forEach((cell) => {
17088
this.layoutElement(cell.getElement(true), column);
17089
});
17090
}
17091
});
17092
17093
this.rightColumns.forEach((column, i) => {
17094
17095
column.modules.frozen.marginValue = rightMargin;
17096
column.modules.frozen.margin = column.modules.frozen.marginValue + "px";
17097
17098
if(column.visible){
17099
rightMargin += column.getWidth();
17100
}
17101
17102
if(i == this.rightColumns.length - 1){
17103
column.modules.frozen.edge = true;
17104
}else {
17105
column.modules.frozen.edge = false;
17106
}
17107
17108
if(column.parent.isGroup){
17109
this.layoutElement(this.getColGroupParentElement(column), column);
17110
}else {
17111
this.layoutElement(column.getElement(), column);
17112
}
17113
17114
if(allCells){
17115
column.cells.forEach((cell) => {
17116
this.layoutElement(cell.getElement(true), column);
17117
});
17118
}
17119
});
17120
}
17121
17122
getColGroupParentElement(column){
17123
return column.parent.isGroup ? this.getColGroupParentElement(column.parent) : column.getElement();
17124
}
17125
17126
//layout columns appropriately
17127
layout(){
17128
if(this.active && !this.blocked){
17129
17130
//calculate left columns
17131
this.layoutColumnPosition();
17132
17133
this.reinitializeRows();
17134
17135
this.layoutCalcRows();
17136
}
17137
}
17138
17139
reinitializeRows(){
17140
var visibleRows = this.table.rowManager.getVisibleRows(true);
17141
var otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row));
17142
17143
otherRows.forEach((row) =>{
17144
row.deinitialize();
17145
});
17146
17147
visibleRows.forEach((row) =>{
17148
if(row.type === "row"){
17149
this.layoutRow(row);
17150
}
17151
});
17152
}
17153
17154
layoutRow(row){
17155
if(this.table.options.layout === "fitDataFill" && this.rightColumns.length){
17156
this.table.rowManager.getTableElement().style.minWidth = "calc(100% - " + this.rightMargin + ")";
17157
}
17158
17159
this.leftColumns.forEach((column) => {
17160
var cell = row.getCell(column);
17161
17162
if(cell){
17163
this.layoutElement(cell.getElement(true), column);
17164
}
17165
});
17166
17167
this.rightColumns.forEach((column) => {
17168
var cell = row.getCell(column);
17169
17170
if(cell){
17171
this.layoutElement(cell.getElement(true), column);
17172
}
17173
});
17174
}
17175
17176
layoutElement(element, column){
17177
var position;
17178
17179
if(column.modules.frozen && element){
17180
element.style.position = "sticky";
17181
17182
if(this.table.rtl){
17183
position = column.modules.frozen.position === "left" ? "right" : "left";
17184
}else {
17185
position = column.modules.frozen.position;
17186
}
17187
17188
element.style[position] = column.modules.frozen.margin;
17189
17190
element.classList.add("tabulator-frozen");
17191
17192
if(column.modules.frozen.edge){
17193
element.classList.add("tabulator-frozen-" + column.modules.frozen.position);
17194
}
17195
}
17196
}
17197
17198
adjustForScrollbar(width){
17199
if(this.rightColumns.length){
17200
this.table.columnManager.getContentsElement().style.width = "calc(100% - " + width + "px)";
17201
}
17202
}
17203
17204
_calcSpace(columns, index){
17205
var width = 0;
17206
17207
for (let i = 0; i < index; i++){
17208
if(columns[i].visible){
17209
width += columns[i].getWidth();
17210
}
17211
}
17212
17213
return width;
17214
}
17215
}
17216
17217
FrozenColumns.moduleName = "frozenColumns";
17218
17219
class FrozenRows extends Module{
17220
17221
constructor(table){
17222
super(table);
17223
17224
this.topElement = document.createElement("div");
17225
this.rows = [];
17226
17227
//register component functions
17228
this.registerComponentFunction("row", "freeze", this.freezeRow.bind(this));
17229
this.registerComponentFunction("row", "unfreeze", this.unfreezeRow.bind(this));
17230
this.registerComponentFunction("row", "isFrozen", this.isRowFrozen.bind(this));
17231
17232
//register table options
17233
this.registerTableOption("frozenRowsField", "id"); //field to choose frozen rows by
17234
this.registerTableOption("frozenRows", false); //holder for frozen row identifiers
17235
}
17236
17237
initialize(){
17238
this.rows = [];
17239
17240
this.topElement.classList.add("tabulator-frozen-rows-holder");
17241
17242
// this.table.columnManager.element.append(this.topElement);
17243
this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling);
17244
17245
this.subscribe("row-deleting", this.detachRow.bind(this));
17246
this.subscribe("rows-visible", this.visibleRows.bind(this));
17247
17248
this.registerDisplayHandler(this.getRows.bind(this), 10);
17249
17250
if(this.table.options.frozenRows){
17251
this.subscribe("data-processed", this.initializeRows.bind(this));
17252
this.subscribe("row-added", this.initializeRow.bind(this));
17253
this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this));
17254
this.subscribe("column-resized", this.resizeHolderWidth.bind(this));
17255
this.subscribe("column-show", this.resizeHolderWidth.bind(this));
17256
this.subscribe("column-hide", this.resizeHolderWidth.bind(this));
17257
}
17258
17259
this.resizeHolderWidth();
17260
}
17261
17262
resizeHolderWidth(){
17263
this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px";
17264
}
17265
17266
initializeRows(){
17267
this.table.rowManager.getRows().forEach((row) => {
17268
this.initializeRow(row);
17269
});
17270
}
17271
17272
initializeRow(row){
17273
var frozenRows = this.table.options.frozenRows,
17274
rowType = typeof frozenRows;
17275
17276
if(rowType === "number"){
17277
if(row.getPosition() && (row.getPosition() + this.rows.length) <= frozenRows){
17278
this.freezeRow(row);
17279
}
17280
}else if(rowType === "function"){
17281
if(frozenRows.call(this.table, row.getComponent())){
17282
this.freezeRow(row);
17283
}
17284
}else if(Array.isArray(frozenRows)){
17285
if(frozenRows.includes(row.data[this.options("frozenRowsField")])){
17286
this.freezeRow(row);
17287
}
17288
}
17289
}
17290
17291
isRowFrozen(row){
17292
var index = this.rows.indexOf(row);
17293
return index > -1;
17294
}
17295
17296
isFrozen(){
17297
return !!this.rows.length;
17298
}
17299
17300
visibleRows(viewable, rows){
17301
this.rows.forEach((row) => {
17302
rows.push(row);
17303
});
17304
17305
return rows;
17306
}
17307
17308
//filter frozen rows out of display data
17309
getRows(rows){
17310
var output = rows.slice(0);
17311
17312
this.rows.forEach(function(row){
17313
var index = output.indexOf(row);
17314
17315
if(index > -1){
17316
output.splice(index, 1);
17317
}
17318
});
17319
17320
return output;
17321
}
17322
17323
freezeRow(row){
17324
if(!row.modules.frozen){
17325
row.modules.frozen = true;
17326
this.topElement.appendChild(row.getElement());
17327
row.initialize();
17328
row.normalizeHeight();
17329
17330
this.rows.push(row);
17331
17332
this.refreshData(false, "display");
17333
17334
this.table.rowManager.adjustTableSize();
17335
17336
this.styleRows();
17337
17338
}else {
17339
console.warn("Freeze Error - Row is already frozen");
17340
}
17341
}
17342
17343
unfreezeRow(row){
17344
if(row.modules.frozen){
17345
17346
row.modules.frozen = false;
17347
17348
this.detachRow(row);
17349
17350
this.table.rowManager.adjustTableSize();
17351
17352
this.refreshData(false, "display");
17353
17354
if(this.rows.length){
17355
this.styleRows();
17356
}
17357
17358
}else {
17359
console.warn("Freeze Error - Row is already unfrozen");
17360
}
17361
}
17362
17363
detachRow(row){
17364
var index = this.rows.indexOf(row);
17365
17366
if(index > -1){
17367
var rowEl = row.getElement();
17368
17369
if(rowEl.parentNode){
17370
rowEl.parentNode.removeChild(rowEl);
17371
}
17372
17373
this.rows.splice(index, 1);
17374
}
17375
}
17376
17377
styleRows(row){
17378
this.rows.forEach((row, i) => {
17379
this.table.rowManager.styleRow(row, i);
17380
});
17381
}
17382
}
17383
17384
FrozenRows.moduleName = "frozenRows";
17385
17386
//public group object
17387
class GroupComponent {
17388
constructor (group){
17389
this._group = group;
17390
this.type = "GroupComponent";
17391
17392
return new Proxy(this, {
17393
get: function(target, name, receiver) {
17394
if (typeof target[name] !== "undefined") {
17395
return target[name];
17396
}else {
17397
return target._group.groupManager.table.componentFunctionBinder.handle("group", target._group, name);
17398
}
17399
}
17400
});
17401
}
17402
17403
getKey(){
17404
return this._group.key;
17405
}
17406
17407
getField(){
17408
return this._group.field;
17409
}
17410
17411
getElement(){
17412
return this._group.element;
17413
}
17414
17415
getRows(){
17416
return this._group.getRows(true);
17417
}
17418
17419
getSubGroups(){
17420
return this._group.getSubGroups(true);
17421
}
17422
17423
getParentGroup(){
17424
return this._group.parent ? this._group.parent.getComponent() : false;
17425
}
17426
17427
isVisible(){
17428
return this._group.visible;
17429
}
17430
17431
show(){
17432
this._group.show();
17433
}
17434
17435
hide(){
17436
this._group.hide();
17437
}
17438
17439
toggle(){
17440
this._group.toggleVisibility();
17441
}
17442
17443
scrollTo(position, ifVisible){
17444
return this._group.groupManager.table.rowManager.scrollToRow(this._group, position, ifVisible);
17445
}
17446
17447
_getSelf(){
17448
return this._group;
17449
}
17450
17451
getTable(){
17452
return this._group.groupManager.table;
17453
}
17454
}
17455
17456
//Group functions
17457
class Group{
17458
17459
constructor(groupManager, parent, level, key, field, generator, oldGroup){
17460
this.groupManager = groupManager;
17461
this.parent = parent;
17462
this.key = key;
17463
this.level = level;
17464
this.field = field;
17465
this.hasSubGroups = level < (groupManager.groupIDLookups.length - 1);
17466
this.addRow = this.hasSubGroups ? this._addRowToGroup : this._addRow;
17467
this.type = "group"; //type of element
17468
this.old = oldGroup;
17469
this.rows = [];
17470
this.groups = [];
17471
this.groupList = [];
17472
this.generator = generator;
17473
this.element = false;
17474
this.elementContents = false;
17475
this.height = 0;
17476
this.outerHeight = 0;
17477
this.initialized = false;
17478
this.calcs = {};
17479
this.initialized = false;
17480
this.modules = {};
17481
this.arrowElement = false;
17482
17483
this.visible = oldGroup ? oldGroup.visible : (typeof groupManager.startOpen[level] !== "undefined" ? groupManager.startOpen[level] : groupManager.startOpen[0]);
17484
17485
this.component = null;
17486
17487
this.createElements();
17488
this.addBindings();
17489
17490
this.createValueGroups();
17491
}
17492
17493
wipe(elementsOnly){
17494
if(!elementsOnly){
17495
if(this.groupList.length){
17496
this.groupList.forEach(function(group){
17497
group.wipe();
17498
});
17499
}else {
17500
this.rows.forEach((row) => {
17501
if(row.modules){
17502
delete row.modules.group;
17503
}
17504
});
17505
}
17506
}
17507
17508
this.element = false;
17509
this.arrowElement = false;
17510
this.elementContents = false;
17511
}
17512
17513
createElements(){
17514
var arrow = document.createElement("div");
17515
arrow.classList.add("tabulator-arrow");
17516
17517
this.element = document.createElement("div");
17518
this.element.classList.add("tabulator-row");
17519
this.element.classList.add("tabulator-group");
17520
this.element.classList.add("tabulator-group-level-" + this.level);
17521
this.element.setAttribute("role", "rowgroup");
17522
17523
this.arrowElement = document.createElement("div");
17524
this.arrowElement.classList.add("tabulator-group-toggle");
17525
this.arrowElement.appendChild(arrow);
17526
17527
//setup movable rows
17528
if(this.groupManager.table.options.movableRows !== false && this.groupManager.table.modExists("moveRow")){
17529
this.groupManager.table.modules.moveRow.initializeGroupHeader(this);
17530
}
17531
}
17532
17533
createValueGroups(){
17534
var level = this.level + 1;
17535
if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){
17536
this.groupManager.allowedValues[level].forEach((value) => {
17537
this._createGroup(value, level);
17538
});
17539
}
17540
}
17541
17542
addBindings(){
17543
var toggleElement;
17544
17545
if(this.groupManager.table.options.groupToggleElement){
17546
toggleElement = this.groupManager.table.options.groupToggleElement == "arrow" ? this.arrowElement : this.element;
17547
17548
toggleElement.addEventListener("click", (e) => {
17549
if(this.groupManager.table.options.groupToggleElement === "arrow"){
17550
e.stopPropagation();
17551
e.stopImmediatePropagation();
17552
}
17553
17554
//allow click event to propagate before toggling visibility
17555
setTimeout(() => {
17556
this.toggleVisibility();
17557
});
17558
});
17559
}
17560
}
17561
17562
_createGroup(groupID, level){
17563
var groupKey = level + "_" + groupID;
17564
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);
17565
17566
this.groups[groupKey] = group;
17567
this.groupList.push(group);
17568
}
17569
17570
_addRowToGroup(row){
17571
17572
var level = this.level + 1;
17573
17574
if(this.hasSubGroups){
17575
var groupID = this.groupManager.groupIDLookups[level].func(row.getData()),
17576
groupKey = level + "_" + groupID;
17577
17578
if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){
17579
if(this.groups[groupKey]){
17580
this.groups[groupKey].addRow(row);
17581
}
17582
}else {
17583
if(!this.groups[groupKey]){
17584
this._createGroup(groupID, level);
17585
}
17586
17587
this.groups[groupKey].addRow(row);
17588
}
17589
}
17590
}
17591
17592
_addRow(row){
17593
this.rows.push(row);
17594
row.modules.group = this;
17595
}
17596
17597
insertRow(row, to, after){
17598
var data = this.conformRowData({});
17599
17600
row.updateData(data);
17601
17602
var toIndex = this.rows.indexOf(to);
17603
17604
if(toIndex > -1){
17605
if(after){
17606
this.rows.splice(toIndex+1, 0, row);
17607
}else {
17608
this.rows.splice(toIndex, 0, row);
17609
}
17610
}else {
17611
if(after){
17612
this.rows.push(row);
17613
}else {
17614
this.rows.unshift(row);
17615
}
17616
}
17617
17618
row.modules.group = this;
17619
17620
// this.generateGroupHeaderContents();
17621
17622
if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){
17623
this.groupManager.table.modules.columnCalcs.recalcGroup(this);
17624
}
17625
17626
this.groupManager.updateGroupRows(true);
17627
}
17628
17629
scrollHeader(left){
17630
if(this.arrowElement){
17631
this.arrowElement.style.marginLeft = left;
17632
17633
this.groupList.forEach(function(child){
17634
child.scrollHeader(left);
17635
});
17636
}
17637
}
17638
17639
getRowIndex(row){}
17640
17641
//update row data to match grouping constraints
17642
conformRowData(data){
17643
if(this.field){
17644
data[this.field] = this.key;
17645
}else {
17646
console.warn("Data Conforming Error - Cannot conform row data to match new group as groupBy is a function");
17647
}
17648
17649
if(this.parent){
17650
data = this.parent.conformRowData(data);
17651
}
17652
17653
return data;
17654
}
17655
17656
removeRow(row){
17657
var index = this.rows.indexOf(row);
17658
var el = row.getElement();
17659
17660
if(index > -1){
17661
this.rows.splice(index, 1);
17662
}
17663
17664
if(!this.groupManager.table.options.groupValues && !this.rows.length){
17665
if(this.parent){
17666
this.parent.removeGroup(this);
17667
}else {
17668
this.groupManager.removeGroup(this);
17669
}
17670
17671
this.groupManager.updateGroupRows(true);
17672
17673
}else {
17674
17675
if(el.parentNode){
17676
el.parentNode.removeChild(el);
17677
}
17678
17679
if(!this.groupManager.blockRedraw){
17680
this.generateGroupHeaderContents();
17681
17682
if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){
17683
this.groupManager.table.modules.columnCalcs.recalcGroup(this);
17684
}
17685
}
17686
17687
}
17688
}
17689
17690
removeGroup(group){
17691
var groupKey = group.level + "_" + group.key,
17692
index;
17693
17694
if(this.groups[groupKey]){
17695
delete this.groups[groupKey];
17696
17697
index = this.groupList.indexOf(group);
17698
17699
if(index > -1){
17700
this.groupList.splice(index, 1);
17701
}
17702
17703
if(!this.groupList.length){
17704
if(this.parent){
17705
this.parent.removeGroup(this);
17706
}else {
17707
this.groupManager.removeGroup(this);
17708
}
17709
}
17710
}
17711
}
17712
17713
getHeadersAndRows(){
17714
var output = [];
17715
17716
output.push(this);
17717
17718
this._visSet();
17719
17720
17721
if(this.calcs.top){
17722
this.calcs.top.detachElement();
17723
this.calcs.top.deleteCells();
17724
}
17725
17726
if(this.calcs.bottom){
17727
this.calcs.bottom.detachElement();
17728
this.calcs.bottom.deleteCells();
17729
}
17730
17731
17732
17733
if(this.visible){
17734
if(this.groupList.length){
17735
this.groupList.forEach(function(group){
17736
output = output.concat(group.getHeadersAndRows());
17737
});
17738
17739
}else {
17740
if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasTopCalcs()){
17741
this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows);
17742
output.push(this.calcs.top);
17743
}
17744
17745
output = output.concat(this.rows);
17746
17747
if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){
17748
this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows);
17749
output.push(this.calcs.bottom);
17750
}
17751
}
17752
}else {
17753
if(!this.groupList.length && this.groupManager.table.options.columnCalcs != "table"){
17754
17755
if(this.groupManager.table.modExists("columnCalcs")){
17756
if(this.groupManager.table.modules.columnCalcs.hasTopCalcs()){
17757
if(this.groupManager.table.options.groupClosedShowCalcs){
17758
this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows);
17759
output.push(this.calcs.top);
17760
}
17761
}
17762
17763
if(this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){
17764
if(this.groupManager.table.options.groupClosedShowCalcs){
17765
this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows);
17766
output.push(this.calcs.bottom);
17767
}
17768
}
17769
}
17770
}
17771
17772
}
17773
17774
return output;
17775
}
17776
17777
getData(visible, transform){
17778
var output = [];
17779
17780
this._visSet();
17781
17782
if(!visible || (visible && this.visible)){
17783
this.rows.forEach((row) => {
17784
output.push(row.getData(transform || "data"));
17785
});
17786
}
17787
17788
return output;
17789
}
17790
17791
getRowCount(){
17792
var count = 0;
17793
17794
if(this.groupList.length){
17795
this.groupList.forEach((group) => {
17796
count += group.getRowCount();
17797
});
17798
}else {
17799
count = this.rows.length;
17800
}
17801
return count;
17802
}
17803
17804
17805
toggleVisibility(){
17806
if(this.visible){
17807
this.hide();
17808
}else {
17809
this.show();
17810
}
17811
}
17812
17813
hide(){
17814
this.visible = false;
17815
17816
if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){
17817
17818
this.element.classList.remove("tabulator-group-visible");
17819
17820
if(this.groupList.length){
17821
this.groupList.forEach((group) => {
17822
17823
var rows = group.getHeadersAndRows();
17824
17825
rows.forEach((row) => {
17826
row.detachElement();
17827
});
17828
});
17829
17830
}else {
17831
this.rows.forEach((row) => {
17832
var rowEl = row.getElement();
17833
rowEl.parentNode.removeChild(rowEl);
17834
});
17835
}
17836
17837
this.groupManager.updateGroupRows(true);
17838
17839
}else {
17840
this.groupManager.updateGroupRows(true);
17841
}
17842
17843
this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), false);
17844
}
17845
17846
show(){
17847
this.visible = true;
17848
17849
if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){
17850
17851
this.element.classList.add("tabulator-group-visible");
17852
17853
var prev = this.generateElement();
17854
17855
if(this.groupList.length){
17856
this.groupList.forEach((group) => {
17857
var rows = group.getHeadersAndRows();
17858
17859
rows.forEach((row) => {
17860
var rowEl = row.getElement();
17861
prev.parentNode.insertBefore(rowEl, prev.nextSibling);
17862
row.initialize();
17863
prev = rowEl;
17864
});
17865
});
17866
17867
}else {
17868
this.rows.forEach((row) => {
17869
var rowEl = row.getElement();
17870
prev.parentNode.insertBefore(rowEl, prev.nextSibling);
17871
row.initialize();
17872
prev = rowEl;
17873
});
17874
}
17875
17876
this.groupManager.updateGroupRows(true);
17877
}else {
17878
this.groupManager.updateGroupRows(true);
17879
}
17880
17881
this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), true);
17882
}
17883
17884
_visSet(){
17885
var data = [];
17886
17887
if(typeof this.visible == "function"){
17888
17889
this.rows.forEach(function(row){
17890
data.push(row.getData());
17891
});
17892
17893
this.visible = this.visible(this.key, this.getRowCount(), data, this.getComponent());
17894
}
17895
}
17896
17897
getRowGroup(row){
17898
var match = false;
17899
if(this.groupList.length){
17900
this.groupList.forEach(function(group){
17901
var result = group.getRowGroup(row);
17902
17903
if(result){
17904
match = result;
17905
}
17906
});
17907
}else {
17908
if(this.rows.find(function(item){
17909
return item === row;
17910
})){
17911
match = this;
17912
}
17913
}
17914
17915
return match;
17916
}
17917
17918
getSubGroups(component){
17919
var output = [];
17920
17921
this.groupList.forEach(function(child){
17922
output.push(component ? child.getComponent() : child);
17923
});
17924
17925
return output;
17926
}
17927
17928
getRows(component, includeChildren){
17929
var output = [];
17930
17931
if(includeChildren && this.groupList.length){
17932
this.groupList.forEach((group) => {
17933
output = output.concat(group.getRows(component, includeChildren));
17934
});
17935
}else {
17936
this.rows.forEach(function(row){
17937
output.push(component ? row.getComponent() : row);
17938
});
17939
}
17940
17941
return output;
17942
}
17943
17944
generateGroupHeaderContents(){
17945
var data = [];
17946
17947
var rows = this.getRows(false, true);
17948
17949
rows.forEach(function(row){
17950
data.push(row.getData());
17951
});
17952
17953
this.elementContents = this.generator(this.key, this.getRowCount(), data, this.getComponent());
17954
17955
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
17956
17957
if(typeof this.elementContents === "string"){
17958
this.element.innerHTML = this.elementContents;
17959
}else {
17960
this.element.appendChild(this.elementContents);
17961
}
17962
17963
this.element.insertBefore(this.arrowElement, this.element.firstChild);
17964
}
17965
17966
getPath(path = []) {
17967
path.unshift(this.key);
17968
if(this.parent) {
17969
this.parent.getPath(path);
17970
}
17971
return path;
17972
}
17973
17974
////////////// Standard Row Functions //////////////
17975
17976
getElement(){
17977
return this.elementContents ? this.element : this.generateElement();
17978
}
17979
17980
generateElement(){
17981
this.addBindings = false;
17982
17983
this._visSet();
17984
17985
if(this.visible){
17986
this.element.classList.add("tabulator-group-visible");
17987
}else {
17988
this.element.classList.remove("tabulator-group-visible");
17989
}
17990
17991
for(var i = 0; i < this.element.childNodes.length; ++i){
17992
this.element.childNodes[i].parentNode.removeChild(this.element.childNodes[i]);
17993
}
17994
17995
this.generateGroupHeaderContents();
17996
17997
// this.addBindings();
17998
17999
return this.element;
18000
}
18001
18002
detachElement(){
18003
if (this.element && this.element.parentNode){
18004
this.element.parentNode.removeChild(this.element);
18005
}
18006
}
18007
18008
//normalize the height of elements in the row
18009
normalizeHeight(){
18010
this.setHeight(this.element.clientHeight);
18011
}
18012
18013
initialize(force){
18014
if(!this.initialized || force){
18015
this.normalizeHeight();
18016
this.initialized = true;
18017
}
18018
}
18019
18020
reinitialize(){
18021
this.initialized = false;
18022
this.height = 0;
18023
18024
if(Helpers.elVisible(this.element)){
18025
this.initialize(true);
18026
}
18027
}
18028
18029
setHeight(height){
18030
if(this.height != height){
18031
this.height = height;
18032
this.outerHeight = this.element.offsetHeight;
18033
}
18034
}
18035
18036
//return rows outer height
18037
getHeight(){
18038
return this.outerHeight;
18039
}
18040
18041
getGroup(){
18042
return this;
18043
}
18044
18045
reinitializeHeight(){}
18046
18047
calcHeight(){}
18048
18049
setCellHeight(){}
18050
18051
clearCellHeight(){}
18052
18053
deinitializeHeight(){}
18054
18055
rendered(){}
18056
18057
//////////////// Object Generation /////////////////
18058
getComponent(){
18059
if(!this.component){
18060
this.component = new GroupComponent(this);
18061
}
18062
18063
return this.component;
18064
}
18065
}
18066
18067
class GroupRows extends Module{
18068
18069
constructor(table){
18070
super(table);
18071
18072
this.groupIDLookups = false; //enable table grouping and set field to group by
18073
this.startOpen = [function(){return false;}]; //starting state of group
18074
this.headerGenerator = [function(){return "";}];
18075
this.groupList = []; //ordered list of groups
18076
this.allowedValues = false;
18077
this.groups = {}; //hold row groups
18078
18079
this.displayHandler = this.getRows.bind(this);
18080
18081
this.blockRedraw = false;
18082
18083
//register table options
18084
this.registerTableOption("groupBy", false); //enable table grouping and set field to group by
18085
this.registerTableOption("groupStartOpen", true); //starting state of group
18086
this.registerTableOption("groupValues", false);
18087
this.registerTableOption("groupUpdateOnCellEdit", false);
18088
this.registerTableOption("groupHeader", false); //header generation function
18089
this.registerTableOption("groupHeaderPrint", null);
18090
this.registerTableOption("groupHeaderClipboard", null);
18091
this.registerTableOption("groupHeaderHtmlOutput", null);
18092
this.registerTableOption("groupHeaderDownload", null);
18093
this.registerTableOption("groupToggleElement", "arrow");
18094
this.registerTableOption("groupClosedShowCalcs", false);
18095
18096
//register table functions
18097
this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this));
18098
this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this));
18099
this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this));
18100
this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this));
18101
this.registerTableFunction("getGroups", this.userGetGroups.bind(this));
18102
this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this));
18103
18104
//register component functions
18105
this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this));
18106
}
18107
18108
//initialize group configuration
18109
initialize(){
18110
this.subscribe("table-destroy", this._blockRedrawing.bind(this));
18111
this.subscribe("rows-wipe", this._blockRedrawing.bind(this));
18112
this.subscribe("rows-wiped", this._restore_redrawing.bind(this));
18113
18114
if(this.table.options.groupBy){
18115
if(this.table.options.groupUpdateOnCellEdit){
18116
this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
18117
this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0);
18118
}
18119
18120
this.subscribe("table-built", this.configureGroupSetup.bind(this));
18121
18122
this.subscribe("row-deleting", this.rowDeleting.bind(this));
18123
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
18124
this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this));
18125
this.subscribe("rows-wipe", this.wipe.bind(this));
18126
this.subscribe("rows-added", this.rowsUpdated.bind(this));
18127
this.subscribe("row-moving", this.rowMoving.bind(this));
18128
this.subscribe("row-adding-index", this.rowAddingIndex.bind(this));
18129
18130
this.subscribe("rows-sample", this.rowSample.bind(this));
18131
18132
this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this));
18133
18134
this.registerDisplayHandler(this.displayHandler, 20);
18135
18136
this.initialized = true;
18137
}
18138
}
18139
18140
_blockRedrawing(){
18141
this.blockRedraw = true;
18142
}
18143
18144
_restore_redrawing(){
18145
this.blockRedraw = false;
18146
}
18147
18148
configureGroupSetup(){
18149
if(this.table.options.groupBy){
18150
var groupBy = this.table.options.groupBy,
18151
startOpen = this.table.options.groupStartOpen,
18152
groupHeader = this.table.options.groupHeader;
18153
18154
this.allowedValues = this.table.options.groupValues;
18155
18156
if(Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length){
18157
console.warn("Error creating group headers, groupHeader array is shorter than groupBy array");
18158
}
18159
18160
this.headerGenerator = [function(){return "";}];
18161
this.startOpen = [function(){return false;}]; //starting state of group
18162
18163
this.langBind("groups|item", (langValue, lang) => {
18164
this.headerGenerator[0] = (value, count, data) => { //header layout function
18165
return (typeof value === "undefined" ? "" : value) + "<span>(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")</span>";
18166
};
18167
});
18168
18169
this.groupIDLookups = [];
18170
18171
if(groupBy){
18172
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both"){
18173
this.table.modules.columnCalcs.removeCalcs();
18174
}
18175
}else {
18176
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group"){
18177
18178
var cols = this.table.columnManager.getRealColumns();
18179
18180
cols.forEach((col) => {
18181
if(col.definition.topCalc){
18182
this.table.modules.columnCalcs.initializeTopRow();
18183
}
18184
18185
if(col.definition.bottomCalc){
18186
this.table.modules.columnCalcs.initializeBottomRow();
18187
}
18188
});
18189
}
18190
}
18191
18192
if(!Array.isArray(groupBy)){
18193
groupBy = [groupBy];
18194
}
18195
18196
groupBy.forEach((group, i) => {
18197
var lookupFunc, column;
18198
18199
if(typeof group == "function"){
18200
lookupFunc = group;
18201
}else {
18202
column = this.table.columnManager.getColumnByField(group);
18203
18204
if(column){
18205
lookupFunc = function(data){
18206
return column.getFieldValue(data);
18207
};
18208
}else {
18209
lookupFunc = function(data){
18210
return data[group];
18211
};
18212
}
18213
}
18214
18215
this.groupIDLookups.push({
18216
field: typeof group === "function" ? false : group,
18217
func:lookupFunc,
18218
values:this.allowedValues ? this.allowedValues[i] : false,
18219
});
18220
});
18221
18222
if(startOpen){
18223
if(!Array.isArray(startOpen)){
18224
startOpen = [startOpen];
18225
}
18226
18227
startOpen.forEach((level) => {
18228
});
18229
18230
this.startOpen = startOpen;
18231
}
18232
18233
if(groupHeader){
18234
this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader];
18235
}
18236
}else {
18237
this.groupList = [];
18238
this.groups = {};
18239
}
18240
}
18241
18242
rowSample(rows, prevValue){
18243
if(this.table.options.groupBy){
18244
var group = this.getGroups(false)[0];
18245
18246
prevValue.push(group.getRows(false)[0]);
18247
}
18248
18249
return prevValue;
18250
}
18251
18252
virtualRenderFill(){
18253
var el = this.table.rowManager.tableElement;
18254
var rows = this.table.rowManager.getVisibleRows();
18255
18256
if(this.table.options.groupBy){
18257
rows = rows.filter((row) => {
18258
return row.type !== "group";
18259
});
18260
18261
el.style.minWidth = !rows.length ? this.table.columnManager.getWidth() + "px" : "";
18262
}else {
18263
return rows;
18264
}
18265
}
18266
18267
rowAddingIndex(row, index, top){
18268
if(this.table.options.groupBy){
18269
this.assignRowToGroup(row);
18270
18271
var groupRows = row.modules.group.rows;
18272
18273
if(groupRows.length > 1){
18274
if(!index || (index && groupRows.indexOf(index) == -1)){
18275
if(top){
18276
if(groupRows[0] !== row){
18277
index = groupRows[0];
18278
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
18279
}
18280
}else {
18281
if(groupRows[groupRows.length -1] !== row){
18282
index = groupRows[groupRows.length -1];
18283
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
18284
}
18285
}
18286
}else {
18287
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
18288
}
18289
}
18290
18291
return index;
18292
}
18293
}
18294
18295
trackChanges(){
18296
this.dispatch("group-changed");
18297
}
18298
18299
///////////////////////////////////
18300
///////// Table Functions /////////
18301
///////////////////////////////////
18302
18303
setGroupBy(groups){
18304
this.table.options.groupBy = groups;
18305
18306
if(!this.initialized){
18307
this.initialize();
18308
}
18309
18310
this.configureGroupSetup();
18311
18312
if(!groups && this.table.modExists("columnCalcs") && this.table.options.columnCalcs === true){
18313
this.table.modules.columnCalcs.reinitializeCalcs();
18314
}
18315
18316
this.refreshData();
18317
18318
this.trackChanges();
18319
}
18320
18321
setGroupValues(groupValues){
18322
this.table.options.groupValues = groupValues;
18323
this.configureGroupSetup();
18324
this.refreshData();
18325
18326
this.trackChanges();
18327
}
18328
18329
setGroupStartOpen(values){
18330
this.table.options.groupStartOpen = values;
18331
this.configureGroupSetup();
18332
18333
if(this.table.options.groupBy){
18334
this.refreshData();
18335
18336
this.trackChanges();
18337
}else {
18338
console.warn("Grouping Update - cant refresh view, no groups have been set");
18339
}
18340
}
18341
18342
setGroupHeader(values){
18343
this.table.options.groupHeader = values;
18344
this.configureGroupSetup();
18345
18346
if(this.table.options.groupBy){
18347
this.refreshData();
18348
18349
this.trackChanges();
18350
}else {
18351
console.warn("Grouping Update - cant refresh view, no groups have been set");
18352
}
18353
}
18354
18355
userGetGroups(values){
18356
return this.getGroups(true);
18357
}
18358
18359
// get grouped table data in the same format as getData()
18360
userGetGroupedData(){
18361
return this.table.options.groupBy ? this.getGroupedData() : this.getData();
18362
}
18363
18364
18365
///////////////////////////////////////
18366
///////// Component Functions /////////
18367
///////////////////////////////////////
18368
18369
rowGetGroup(row){
18370
return row.modules.group ? row.modules.group.getComponent() : false;
18371
}
18372
18373
///////////////////////////////////
18374
///////// Internal Logic //////////
18375
///////////////////////////////////
18376
18377
rowMoving(from, to, after){
18378
if(this.table.options.groupBy){
18379
if(!after && to instanceof Group){
18380
to = this.table.rowManager.prevDisplayRow(from) || to;
18381
}
18382
18383
var toGroup = to instanceof Group ? to : to.modules.group;
18384
var fromGroup = from instanceof Group ? from : from.modules.group;
18385
18386
if(toGroup === fromGroup){
18387
this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after);
18388
}else {
18389
if(fromGroup){
18390
fromGroup.removeRow(from);
18391
}
18392
18393
toGroup.insertRow(from, to, after);
18394
}
18395
}
18396
}
18397
18398
18399
rowDeleting(row){
18400
//remove from group
18401
if(this.table.options.groupBy && row.modules.group){
18402
row.modules.group.removeRow(row);
18403
}
18404
}
18405
18406
rowsUpdated(row){
18407
if(this.table.options.groupBy){
18408
this.updateGroupRows(true);
18409
}
18410
}
18411
18412
cellUpdated(cell){
18413
if(this.table.options.groupBy){
18414
this.reassignRowToGroup(cell.row);
18415
}
18416
}
18417
18418
//return appropriate rows with group headers
18419
getRows(rows){
18420
if(this.table.options.groupBy && this.groupIDLookups.length){
18421
18422
this.dispatchExternal("dataGrouping");
18423
18424
this.generateGroups(rows);
18425
18426
if(this.subscribedExternal("dataGrouped")){
18427
this.dispatchExternal("dataGrouped", this.getGroups(true));
18428
}
18429
18430
return this.updateGroupRows();
18431
18432
}else {
18433
return rows.slice(0);
18434
}
18435
}
18436
18437
getGroups(component){
18438
var groupComponents = [];
18439
18440
this.groupList.forEach(function(group){
18441
groupComponents.push(component ? group.getComponent() : group);
18442
});
18443
18444
return groupComponents;
18445
}
18446
18447
getChildGroups(group){
18448
var groupComponents = [];
18449
18450
if(!group){
18451
group = this;
18452
}
18453
18454
group.groupList.forEach((child) => {
18455
if(child.groupList.length){
18456
groupComponents = groupComponents.concat(this.getChildGroups(child));
18457
}else {
18458
groupComponents.push(child);
18459
}
18460
});
18461
18462
return groupComponents;
18463
}
18464
18465
wipe(){
18466
if(this.table.options.groupBy){
18467
this.groupList.forEach(function(group){
18468
group.wipe();
18469
});
18470
18471
this.groupList = [];
18472
this.groups = {};
18473
}
18474
}
18475
18476
pullGroupListData(groupList) {
18477
var groupListData = [];
18478
18479
groupList.forEach((group) => {
18480
var groupHeader = {};
18481
groupHeader.level = 0;
18482
groupHeader.rowCount = 0;
18483
groupHeader.headerContent = "";
18484
var childData = [];
18485
18486
if (group.hasSubGroups) {
18487
childData = this.pullGroupListData(group.groupList);
18488
18489
groupHeader.level = group.level;
18490
groupHeader.rowCount = childData.length - group.groupList.length; // data length minus number of sub-headers
18491
groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group);
18492
18493
groupListData.push(groupHeader);
18494
groupListData = groupListData.concat(childData);
18495
}
18496
18497
else {
18498
groupHeader.level = group.level;
18499
groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group);
18500
groupHeader.rowCount = group.getRows().length;
18501
18502
groupListData.push(groupHeader);
18503
18504
group.getRows().forEach((row) => {
18505
groupListData.push(row.getData("data"));
18506
});
18507
}
18508
});
18509
18510
return groupListData;
18511
}
18512
18513
getGroupedData(){
18514
18515
return this.pullGroupListData(this.groupList);
18516
}
18517
18518
getRowGroup(row){
18519
var match = false;
18520
18521
if(this.options("dataTree")){
18522
row = this.table.modules.dataTree.getTreeParentRoot(row);
18523
}
18524
18525
this.groupList.forEach((group) => {
18526
var result = group.getRowGroup(row);
18527
18528
if(result){
18529
match = result;
18530
}
18531
});
18532
18533
return match;
18534
}
18535
18536
countGroups(){
18537
return this.groupList.length;
18538
}
18539
18540
generateGroups(rows){
18541
var oldGroups = this.groups;
18542
18543
this.groups = {};
18544
this.groupList = [];
18545
18546
if(this.allowedValues && this.allowedValues[0]){
18547
this.allowedValues[0].forEach((value) => {
18548
this.createGroup(value, 0, oldGroups);
18549
});
18550
18551
rows.forEach((row) => {
18552
this.assignRowToExistingGroup(row, oldGroups);
18553
});
18554
}else {
18555
rows.forEach((row) => {
18556
this.assignRowToGroup(row, oldGroups);
18557
});
18558
}
18559
18560
Object.values(oldGroups).forEach((group) => {
18561
group.wipe(true);
18562
});
18563
}
18564
18565
18566
createGroup(groupID, level, oldGroups){
18567
var groupKey = level + "_" + groupID,
18568
group;
18569
18570
oldGroups = oldGroups || [];
18571
18572
group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]);
18573
18574
this.groups[groupKey] = group;
18575
this.groupList.push(group);
18576
}
18577
18578
assignRowToExistingGroup(row, oldGroups){
18579
var groupID = this.groupIDLookups[0].func(row.getData()),
18580
groupKey = "0_" + groupID;
18581
18582
if(this.groups[groupKey]){
18583
this.groups[groupKey].addRow(row);
18584
}
18585
}
18586
18587
assignRowToGroup(row, oldGroups){
18588
var groupID = this.groupIDLookups[0].func(row.getData()),
18589
newGroupNeeded = !this.groups["0_" + groupID];
18590
18591
if(newGroupNeeded){
18592
this.createGroup(groupID, 0, oldGroups);
18593
}
18594
18595
this.groups["0_" + groupID].addRow(row);
18596
18597
return !newGroupNeeded;
18598
}
18599
18600
reassignRowToGroup(row){
18601
if(row.type === "row"){
18602
var oldRowGroup = row.modules.group,
18603
oldGroupPath = oldRowGroup.getPath(),
18604
newGroupPath = this.getExpectedPath(row),
18605
samePath;
18606
18607
// figure out if new group path is the same as old group path
18608
samePath = (oldGroupPath.length == newGroupPath.length) && oldGroupPath.every((element, index) => {
18609
return element === newGroupPath[index];
18610
});
18611
18612
// refresh if they new path and old path aren't the same (aka the row's groupings have changed)
18613
if(!samePath) {
18614
oldRowGroup.removeRow(row);
18615
this.assignRowToGroup(row, this.groups);
18616
this.refreshData(true);
18617
}
18618
}
18619
}
18620
18621
getExpectedPath(row) {
18622
var groupPath = [], rowData = row.getData();
18623
18624
this.groupIDLookups.forEach((groupId) => {
18625
groupPath.push(groupId.func(rowData));
18626
});
18627
18628
return groupPath;
18629
}
18630
18631
updateGroupRows(force){
18632
var output = [];
18633
18634
if(!this.blockRedraw){
18635
this.groupList.forEach((group) => {
18636
output = output.concat(group.getHeadersAndRows());
18637
});
18638
18639
if(force){
18640
this.refreshData(true);
18641
}
18642
}
18643
18644
return output;
18645
}
18646
18647
scrollHeaders(left){
18648
if(this.table.options.groupBy){
18649
if(this.table.options.renderHorizontal === "virtual"){
18650
left -= this.table.columnManager.renderer.vDomPadLeft;
18651
}
18652
18653
left = left + "px";
18654
18655
this.groupList.forEach((group) => {
18656
group.scrollHeader(left);
18657
});
18658
}
18659
}
18660
18661
removeGroup(group){
18662
var groupKey = group.level + "_" + group.key,
18663
index;
18664
18665
if(this.groups[groupKey]){
18666
delete this.groups[groupKey];
18667
18668
index = this.groupList.indexOf(group);
18669
18670
if(index > -1){
18671
this.groupList.splice(index, 1);
18672
}
18673
}
18674
}
18675
18676
checkBasicModeGroupHeaderWidth(){
18677
var element = this.table.rowManager.tableElement,
18678
onlyGroupHeaders = true;
18679
18680
this.table.rowManager.getDisplayRows().forEach((row, index) =>{
18681
this.table.rowManager.styleRow(row, index);
18682
element.appendChild(row.getElement());
18683
row.initialize(true);
18684
18685
if(row.type !== "group"){
18686
onlyGroupHeaders = false;
18687
}
18688
});
18689
18690
if(onlyGroupHeaders){
18691
element.style.minWidth = this.table.columnManager.getWidth() + "px";
18692
}else {
18693
element.style.minWidth = "";
18694
}
18695
}
18696
18697
}
18698
18699
GroupRows.moduleName = "groupRows";
18700
18701
var defaultUndoers = {
18702
cellEdit: function(action){
18703
action.component.setValueProcessData(action.data.oldValue);
18704
action.component.cellRendered();
18705
},
18706
18707
rowAdd: function(action){
18708
action.component.deleteActual();
18709
},
18710
18711
rowDelete: function(action){
18712
var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
18713
18714
if(this.table.options.groupBy && this.table.modExists("groupRows")){
18715
this.table.modules.groupRows.updateGroupRows(true);
18716
}
18717
18718
this._rebindRow(action.component, newRow);
18719
},
18720
18721
rowMove: function(action){
18722
var after = (action.data.posFrom - action.data.posTo) > 0;
18723
18724
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posFrom), after);
18725
18726
this.table.rowManager.regenerateRowPositions();
18727
this.table.rowManager.reRenderInPosition();
18728
},
18729
};
18730
18731
var defaultRedoers = {
18732
cellEdit: function(action){
18733
action.component.setValueProcessData(action.data.newValue);
18734
action.component.cellRendered();
18735
},
18736
18737
rowAdd: function(action){
18738
var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
18739
18740
if(this.table.options.groupBy && this.table.modExists("groupRows")){
18741
this.table.modules.groupRows.updateGroupRows(true);
18742
}
18743
18744
this._rebindRow(action.component, newRow);
18745
},
18746
18747
rowDelete:function(action){
18748
action.component.deleteActual();
18749
},
18750
18751
rowMove: function(action){
18752
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posTo), action.data.after);
18753
18754
this.table.rowManager.regenerateRowPositions();
18755
this.table.rowManager.reRenderInPosition();
18756
},
18757
};
18758
18759
class History extends Module{
18760
18761
constructor(table){
18762
super(table);
18763
18764
this.history = [];
18765
this.index = -1;
18766
18767
this.registerTableOption("history", false); //enable edit history
18768
}
18769
18770
initialize(){
18771
if(this.table.options.history){
18772
this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
18773
this.subscribe("cell-delete", this.clearComponentHistory.bind(this));
18774
this.subscribe("row-delete", this.rowDeleted.bind(this));
18775
this.subscribe("rows-wipe", this.clear.bind(this));
18776
this.subscribe("row-added", this.rowAdded.bind(this));
18777
this.subscribe("row-move", this.rowMoved.bind(this));
18778
}
18779
18780
this.registerTableFunction("undo", this.undo.bind(this));
18781
this.registerTableFunction("redo", this.redo.bind(this));
18782
this.registerTableFunction("getHistoryUndoSize", this.getHistoryUndoSize.bind(this));
18783
this.registerTableFunction("getHistoryRedoSize", this.getHistoryRedoSize.bind(this));
18784
this.registerTableFunction("clearHistory", this.clear.bind(this));
18785
}
18786
18787
rowMoved(from, to, after){
18788
this.action("rowMove", from, {posFrom:from.getPosition(), posTo:to.getPosition(), to:to, after:after});
18789
}
18790
18791
rowAdded(row, data, pos, index){
18792
this.action("rowAdd", row, {data:data, pos:pos, index:index});
18793
}
18794
18795
rowDeleted(row){
18796
var index, rows;
18797
18798
if(this.table.options.groupBy){
18799
18800
rows = row.getComponent().getGroup()._getSelf().rows;
18801
index = rows.indexOf(row);
18802
18803
if(index){
18804
index = rows[index-1];
18805
}
18806
}else {
18807
index = row.table.rowManager.getRowIndex(row);
18808
18809
if(index){
18810
index = row.table.rowManager.rows[index-1];
18811
}
18812
}
18813
18814
this.action("rowDelete", row, {data:row.getData(), pos:!index, index:index});
18815
}
18816
18817
cellUpdated(cell){
18818
this.action("cellEdit", cell, {oldValue:cell.oldValue, newValue:cell.value});
18819
}
18820
18821
clear(){
18822
this.history = [];
18823
this.index = -1;
18824
}
18825
18826
action(type, component, data){
18827
this.history = this.history.slice(0, this.index + 1);
18828
18829
this.history.push({
18830
type:type,
18831
component:component,
18832
data:data,
18833
});
18834
18835
this.index ++;
18836
}
18837
18838
getHistoryUndoSize(){
18839
return this.index + 1;
18840
}
18841
18842
getHistoryRedoSize(){
18843
return this.history.length - (this.index + 1);
18844
}
18845
18846
clearComponentHistory(component){
18847
var index = this.history.findIndex(function(item){
18848
return item.component === component;
18849
});
18850
18851
if(index > -1){
18852
this.history.splice(index, 1);
18853
if(index <= this.index){
18854
this.index--;
18855
}
18856
18857
this.clearComponentHistory(component);
18858
}
18859
}
18860
18861
undo(){
18862
if(this.index > -1){
18863
let action = this.history[this.index];
18864
18865
History.undoers[action.type].call(this, action);
18866
18867
this.index--;
18868
18869
this.dispatchExternal("historyUndo", action.type, action.component.getComponent(), action.data);
18870
18871
return true;
18872
}else {
18873
console.warn("History Undo Error - No more history to undo");
18874
return false;
18875
}
18876
}
18877
18878
redo(){
18879
if(this.history.length-1 > this.index){
18880
18881
this.index++;
18882
18883
let action = this.history[this.index];
18884
18885
History.redoers[action.type].call(this, action);
18886
18887
this.dispatchExternal("historyRedo", action.type, action.component.getComponent(), action.data);
18888
18889
return true;
18890
}else {
18891
console.warn("History Redo Error - No more history to redo");
18892
return false;
18893
}
18894
}
18895
18896
//rebind rows to new element after deletion
18897
_rebindRow(oldRow, newRow){
18898
this.history.forEach(function(action){
18899
if(action.component instanceof Row){
18900
if(action.component === oldRow){
18901
action.component = newRow;
18902
}
18903
}else if(action.component instanceof Cell){
18904
if(action.component.row === oldRow){
18905
var field = action.component.column.getField();
18906
18907
if(field){
18908
action.component = newRow.getCell(field);
18909
}
18910
18911
}
18912
}
18913
});
18914
}
18915
}
18916
18917
History.moduleName = "history";
18918
18919
//load defaults
18920
History.undoers = defaultUndoers;
18921
History.redoers = defaultRedoers;
18922
18923
class HtmlTableImport extends Module{
18924
18925
constructor(table){
18926
super(table);
18927
18928
this.fieldIndex = [];
18929
this.hasIndex = false;
18930
}
18931
18932
initialize(){
18933
this.tableElementCheck();
18934
}
18935
18936
tableElementCheck(){
18937
if(this.table.originalElement && this.table.originalElement.tagName === "TABLE"){
18938
if(this.table.originalElement.childNodes.length){
18939
this.parseTable();
18940
}else {
18941
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.");
18942
}
18943
}
18944
}
18945
18946
parseTable(){
18947
var element = this.table.originalElement,
18948
options = this.table.options,
18949
headers = element.getElementsByTagName("th"),
18950
rows = element.getElementsByTagName("tbody")[0],
18951
data = [];
18952
18953
this.hasIndex = false;
18954
18955
this.dispatchExternal("htmlImporting");
18956
18957
rows = rows ? rows.getElementsByTagName("tr") : [];
18958
18959
//check for Tabulator inline options
18960
this._extractOptions(element, options);
18961
18962
if(headers.length){
18963
this._extractHeaders(headers, rows);
18964
}else {
18965
this._generateBlankHeaders(headers, rows);
18966
}
18967
18968
//iterate through table rows and build data set
18969
for(var index = 0; index < rows.length; index++){
18970
var row = rows[index],
18971
cells = row.getElementsByTagName("td"),
18972
item = {};
18973
18974
//create index if the don't exist in table
18975
if(!this.hasIndex){
18976
item[options.index] = index;
18977
}
18978
18979
for(var i = 0; i < cells.length; i++){
18980
var cell = cells[i];
18981
if(typeof this.fieldIndex[i] !== "undefined"){
18982
item[this.fieldIndex[i]] = cell.innerHTML;
18983
}
18984
}
18985
18986
//add row data to item
18987
data.push(item);
18988
}
18989
18990
options.data = data;
18991
18992
this.dispatchExternal("htmlImported");
18993
}
18994
18995
//extract tabulator attribute options
18996
_extractOptions(element, options, defaultOptions){
18997
var attributes = element.attributes;
18998
var optionsArr = defaultOptions ? Object.keys(defaultOptions) : Object.keys(options);
18999
var optionsList = {};
19000
19001
optionsArr.forEach((item) => {
19002
optionsList[item.toLowerCase()] = item;
19003
});
19004
19005
for(var index in attributes){
19006
var attrib = attributes[index];
19007
var name;
19008
19009
if(attrib && typeof attrib == "object" && attrib.name && attrib.name.indexOf("tabulator-") === 0){
19010
name = attrib.name.replace("tabulator-", "");
19011
19012
if(typeof optionsList[name] !== "undefined"){
19013
options[optionsList[name]] = this._attribValue(attrib.value);
19014
}
19015
}
19016
}
19017
}
19018
19019
//get value of attribute
19020
_attribValue(value){
19021
if(value === "true"){
19022
return true;
19023
}
19024
19025
if(value === "false"){
19026
return false;
19027
}
19028
19029
return value;
19030
}
19031
19032
//find column if it has already been defined
19033
_findCol(title){
19034
var match = this.table.options.columns.find((column) => {
19035
return column.title === title;
19036
});
19037
19038
return match || false;
19039
}
19040
19041
//extract column from headers
19042
_extractHeaders(headers, rows){
19043
for(var index = 0; index < headers.length; index++){
19044
var header = headers[index],
19045
exists = false,
19046
col = this._findCol(header.textContent),
19047
width;
19048
19049
if(col){
19050
exists = true;
19051
}else {
19052
col = {title:header.textContent.trim()};
19053
}
19054
19055
if(!col.field) {
19056
col.field = header.textContent.trim().toLowerCase().replaceAll(" ", "_");
19057
}
19058
19059
width = header.getAttribute("width");
19060
19061
if(width && !col.width) {
19062
col.width = width;
19063
}
19064
19065
//check for Tabulator inline options
19066
this._extractOptions(header, col, this.table.columnManager.optionsList.registeredDefaults);
19067
19068
this.fieldIndex[index] = col.field;
19069
19070
if(col.field == this.table.options.index){
19071
this.hasIndex = true;
19072
}
19073
19074
if(!exists){
19075
this.table.options.columns.push(col);
19076
}
19077
19078
}
19079
}
19080
19081
//generate blank headers
19082
_generateBlankHeaders(headers, rows){
19083
for(var index = 0; index < headers.length; index++){
19084
var header = headers[index],
19085
col = {title:"", field:"col" + index};
19086
19087
this.fieldIndex[index] = col.field;
19088
19089
var width = header.getAttribute("width");
19090
19091
if(width){
19092
col.width = width;
19093
}
19094
19095
this.table.options.columns.push(col);
19096
}
19097
}
19098
}
19099
19100
HtmlTableImport.moduleName = "htmlTableImport";
19101
19102
function csvImporter(input){
19103
var data = [],
19104
row = 0,
19105
col = 0,
19106
inQuote = false;
19107
19108
//Iterate over each character
19109
for (let index = 0; index < input.length; index++) {
19110
let char = input[index],
19111
nextChar = input[index+1];
19112
19113
//Initialize empty row
19114
if(!data[row]){
19115
data[row] = [];
19116
}
19117
19118
//Initialize empty column
19119
if(!data[row][col]){
19120
data[row][col] = "";
19121
}
19122
19123
//Handle quotation mark inside string
19124
if (char == '"' && inQuote && nextChar == '"') {
19125
data[row][col] += char;
19126
index++;
19127
continue;
19128
}
19129
19130
//Begin / End Quote
19131
if (char == '"') {
19132
inQuote = !inQuote;
19133
continue;
19134
}
19135
19136
//Next column (if not in quote)
19137
if (char == ',' && !inQuote) {
19138
col++;
19139
continue;
19140
}
19141
19142
//New row if new line and not in quote (CRLF)
19143
if (char == '\r' && nextChar == '\n' && !inQuote) {
19144
col = 0;
19145
row++;
19146
index++;
19147
continue;
19148
}
19149
19150
//New row if new line and not in quote (CR or LF)
19151
if ((char == '\r' || char == '\n') && !inQuote) {
19152
col = 0;
19153
row++;
19154
continue;
19155
}
19156
19157
//Normal Character, append to column
19158
data[row][col] += char;
19159
}
19160
19161
return data;
19162
}
19163
19164
function json$1(input){
19165
try {
19166
return JSON.parse(input);
19167
} catch(e) {
19168
console.warn("JSON Import Error - File contents is invalid JSON", e);
19169
return Promise.reject();
19170
}
19171
}
19172
19173
function arrayImporter(input){
19174
return input;
19175
}
19176
19177
var defaultImporters = {
19178
csv:csvImporter,
19179
json:json$1,
19180
array:arrayImporter,
19181
};
19182
19183
class Import extends Module{
19184
19185
constructor(table){
19186
super(table);
19187
19188
this.registerTableOption("importFormat");
19189
this.registerTableOption("importReader", "text");
19190
}
19191
19192
initialize(){
19193
this.registerTableFunction("import", this.importFromFile.bind(this));
19194
19195
if(this.table.options.importFormat){
19196
this.subscribe("data-loading", this.loadDataCheck.bind(this), 10);
19197
this.subscribe("data-load", this.loadData.bind(this), 10);
19198
}
19199
}
19200
19201
loadDataCheck(data){
19202
return this.table.options.importFormat && (typeof data === "string" || (Array.isArray(data) && data.length && Array.isArray(data)));
19203
}
19204
19205
loadData(data, params, config, silent, previousData){
19206
return this.importData(this.lookupImporter(), data)
19207
.then(this.structureData.bind(this))
19208
.catch((err) => {
19209
console.error("Import Error:", err || "Unable to import data");
19210
return Promise.reject(err);
19211
});
19212
}
19213
19214
lookupImporter(importFormat){
19215
var importer;
19216
19217
if(!importFormat){
19218
importFormat = this.table.options.importFormat;
19219
}
19220
19221
if(typeof importFormat === "string"){
19222
importer = Import.importers[importFormat];
19223
}else {
19224
importer = importFormat;
19225
}
19226
19227
if(!importer){
19228
console.error("Import Error - Importer not found:", importFormat);
19229
}
19230
19231
return importer;
19232
}
19233
19234
importFromFile(importFormat, extension){
19235
var importer = this.lookupImporter(importFormat);
19236
19237
if(importer){
19238
return this.pickFile(extension)
19239
.then(this.importData.bind(this, importer))
19240
.then(this.structureData.bind(this))
19241
.then(this.setData.bind(this))
19242
.catch((err) => {
19243
console.error("Import Error:", err || "Unable to import file");
19244
return Promise.reject(err);
19245
});
19246
}
19247
}
19248
19249
pickFile(extensions){
19250
return new Promise((resolve, reject) => {
19251
var input = document.createElement("input");
19252
input.type = "file";
19253
input.accept = extensions;
19254
19255
input.addEventListener("change", (e) => {
19256
var file = input.files[0],
19257
reader = new FileReader();
19258
19259
switch(this.table.options.importReader){
19260
case "buffer":
19261
reader.readAsArrayBuffer(file);
19262
break;
19263
19264
case "binary":
19265
reader.readAsBinaryString(file);
19266
break;
19267
19268
case "url":
19269
reader.readAsDataURL(file);
19270
break;
19271
19272
case "text":
19273
default:
19274
reader.readAsText(file);
19275
}
19276
19277
reader.onload = (e) => {
19278
resolve(reader.result);
19279
};
19280
19281
reader.onerror = (e) => {
19282
console.warn("File Load Error - Unable to read file");
19283
reject();
19284
};
19285
});
19286
19287
input.click();
19288
});
19289
}
19290
19291
importData(importer, fileContents){
19292
var data = importer.call(this.table, fileContents);
19293
19294
if(data instanceof Promise){
19295
return data;
19296
}else {
19297
return data ? Promise.resolve(data) : Promise.reject();
19298
}
19299
}
19300
19301
structureData(parsedData){
19302
var data = [];
19303
19304
if(Array.isArray(parsedData) && parsedData.length && Array.isArray(parsedData[0])){
19305
if(this.table.options.autoColumns){
19306
data = this.structureArrayToObject(parsedData);
19307
}else {
19308
data = this.structureArrayToColumns(parsedData);
19309
}
19310
19311
return data;
19312
}else {
19313
return parsedData;
19314
}
19315
}
19316
19317
structureArrayToObject(parsedData){
19318
var columns = parsedData.shift();
19319
19320
var data = parsedData.map((values) => {
19321
var row = {};
19322
19323
columns.forEach((key, i) => {
19324
row[key] = values[i];
19325
});
19326
19327
return row;
19328
});
19329
19330
return data;
19331
}
19332
19333
structureArrayToColumns(parsedData){
19334
var data = [],
19335
columns = this.table.getColumns();
19336
19337
//remove first row if it is the column names
19338
if(columns[0] && parsedData[0][0]){
19339
if(columns[0].getDefinition().title === parsedData[0][0]){
19340
parsedData.shift();
19341
}
19342
}
19343
19344
//convert row arrays to objects
19345
parsedData.forEach((rowData) => {
19346
var row = {};
19347
19348
rowData.forEach((value, index) => {
19349
var column = columns[index];
19350
19351
if(column){
19352
row[column.getField()] = value;
19353
}
19354
});
19355
19356
data.push(row);
19357
});
19358
19359
return data;
19360
}
19361
19362
setData(data){
19363
return this.table.setData(data);
19364
}
19365
}
19366
19367
Import.moduleName = "import";
19368
19369
//load defaults
19370
Import.importers = defaultImporters;
19371
19372
class Interaction extends Module{
19373
19374
constructor(table){
19375
super(table);
19376
19377
this.eventMap = {
19378
//row events
19379
rowClick:"row-click",
19380
rowDblClick:"row-dblclick",
19381
rowContext:"row-contextmenu",
19382
rowMouseEnter:"row-mouseenter",
19383
rowMouseLeave:"row-mouseleave",
19384
rowMouseOver:"row-mouseover",
19385
rowMouseOut:"row-mouseout",
19386
rowMouseMove:"row-mousemove",
19387
rowMouseDown:"row-mousedown",
19388
rowMouseUp:"row-mouseup",
19389
rowTap:"row",
19390
rowDblTap:"row",
19391
rowTapHold:"row",
19392
19393
//cell events
19394
cellClick:"cell-click",
19395
cellDblClick:"cell-dblclick",
19396
cellContext:"cell-contextmenu",
19397
cellMouseEnter:"cell-mouseenter",
19398
cellMouseLeave:"cell-mouseleave",
19399
cellMouseOver:"cell-mouseover",
19400
cellMouseOut:"cell-mouseout",
19401
cellMouseMove:"cell-mousemove",
19402
cellMouseDown:"cell-mousedown",
19403
cellMouseUp:"cell-mouseup",
19404
cellTap:"cell",
19405
cellDblTap:"cell",
19406
cellTapHold:"cell",
19407
19408
//column header events
19409
headerClick:"column-click",
19410
headerDblClick:"column-dblclick",
19411
headerContext:"column-contextmenu",
19412
headerMouseEnter:"column-mouseenter",
19413
headerMouseLeave:"column-mouseleave",
19414
headerMouseOver:"column-mouseover",
19415
headerMouseOut:"column-mouseout",
19416
headerMouseMove:"column-mousemove",
19417
headerMouseDown:"column-mousedown",
19418
headerMouseUp:"column-mouseup",
19419
headerTap:"column",
19420
headerDblTap:"column",
19421
headerTapHold:"column",
19422
19423
//group header
19424
groupClick:"group-click",
19425
groupDblClick:"group-dblclick",
19426
groupContext:"group-contextmenu",
19427
groupMouseEnter:"group-mouseenter",
19428
groupMouseLeave:"group-mouseleave",
19429
groupMouseOver:"group-mouseover",
19430
groupMouseOut:"group-mouseout",
19431
groupMouseMove:"group-mousemove",
19432
groupMouseDown:"group-mousedown",
19433
groupMouseUp:"group-mouseup",
19434
groupTap:"group",
19435
groupDblTap:"group",
19436
groupTapHold:"group",
19437
};
19438
19439
this.subscribers = {};
19440
19441
this.touchSubscribers = {};
19442
19443
this.columnSubscribers = {};
19444
19445
this.touchWatchers = {
19446
row:{
19447
tap:null,
19448
tapDbl:null,
19449
tapHold:null,
19450
},
19451
cell:{
19452
tap:null,
19453
tapDbl:null,
19454
tapHold:null,
19455
},
19456
column:{
19457
tap:null,
19458
tapDbl:null,
19459
tapHold:null,
19460
},
19461
group:{
19462
tap:null,
19463
tapDbl:null,
19464
tapHold:null,
19465
}
19466
};
19467
19468
this.registerColumnOption("headerClick");
19469
this.registerColumnOption("headerDblClick");
19470
this.registerColumnOption("headerContext");
19471
this.registerColumnOption("headerMouseEnter");
19472
this.registerColumnOption("headerMouseLeave");
19473
this.registerColumnOption("headerMouseOver");
19474
this.registerColumnOption("headerMouseOut");
19475
this.registerColumnOption("headerMouseMove");
19476
this.registerColumnOption("headerMouseDown");
19477
this.registerColumnOption("headerMouseUp");
19478
this.registerColumnOption("headerTap");
19479
this.registerColumnOption("headerDblTap");
19480
this.registerColumnOption("headerTapHold");
19481
19482
this.registerColumnOption("cellClick");
19483
this.registerColumnOption("cellDblClick");
19484
this.registerColumnOption("cellContext");
19485
this.registerColumnOption("cellMouseEnter");
19486
this.registerColumnOption("cellMouseLeave");
19487
this.registerColumnOption("cellMouseOver");
19488
this.registerColumnOption("cellMouseOut");
19489
this.registerColumnOption("cellMouseMove");
19490
this.registerColumnOption("cellMouseDown");
19491
this.registerColumnOption("cellMouseUp");
19492
this.registerColumnOption("cellTap");
19493
this.registerColumnOption("cellDblTap");
19494
this.registerColumnOption("cellTapHold");
19495
19496
}
19497
19498
initialize(){
19499
this.initializeExternalEvents();
19500
19501
this.subscribe("column-init", this.initializeColumn.bind(this));
19502
this.subscribe("cell-dblclick", this.cellContentsSelectionFixer.bind(this));
19503
this.subscribe("scroll-horizontal", this.clearTouchWatchers.bind(this));
19504
this.subscribe("scroll-vertical", this.clearTouchWatchers.bind(this));
19505
}
19506
19507
clearTouchWatchers(){
19508
var types = Object.values(this.touchWatchers);
19509
19510
types.forEach((type) => {
19511
for(let key in type){
19512
type[key] = null;
19513
}
19514
});
19515
}
19516
19517
cellContentsSelectionFixer(e, cell){
19518
var range;
19519
19520
if(this.table.modExists("edit")){
19521
if (this.table.modules.edit.currentCell === cell){
19522
return; //prevent instant selection of editor content
19523
}
19524
}
19525
19526
e.preventDefault();
19527
19528
try{
19529
if (document.selection) { // IE
19530
range = document.body.createTextRange();
19531
range.moveToElementText(cell.getElement());
19532
range.select();
19533
} else if (window.getSelection) {
19534
range = document.createRange();
19535
range.selectNode(cell.getElement());
19536
window.getSelection().removeAllRanges();
19537
window.getSelection().addRange(range);
19538
}
19539
}catch(e){}
19540
}
19541
19542
initializeExternalEvents(){
19543
for(let key in this.eventMap){
19544
this.subscriptionChangeExternal(key, this.subscriptionChanged.bind(this, key));
19545
}
19546
}
19547
19548
subscriptionChanged(key, added){
19549
if(added){
19550
if(!this.subscribers[key]){
19551
if(this.eventMap[key].includes("-")){
19552
this.subscribers[key] = this.handle.bind(this, key);
19553
this.subscribe(this.eventMap[key], this.subscribers[key]);
19554
}else {
19555
this.subscribeTouchEvents(key);
19556
}
19557
}
19558
}else {
19559
if(this.eventMap[key].includes("-")){
19560
if(this.subscribers[key] && !this.columnSubscribers[key] && !this.subscribedExternal(key)){
19561
this.unsubscribe(this.eventMap[key], this.subscribers[key]);
19562
delete this.subscribers[key];
19563
}
19564
}else {
19565
this.unsubscribeTouchEvents(key);
19566
}
19567
}
19568
}
19569
19570
19571
subscribeTouchEvents(key){
19572
var type = this.eventMap[key];
19573
19574
if(!this.touchSubscribers[type + "-touchstart"]){
19575
this.touchSubscribers[type + "-touchstart"] = this.handleTouch.bind(this, type, "start");
19576
this.touchSubscribers[type + "-touchend"] = this.handleTouch.bind(this, type, "end");
19577
19578
this.subscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]);
19579
this.subscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]);
19580
}
19581
19582
this.subscribers[key] = true;
19583
}
19584
19585
unsubscribeTouchEvents(key){
19586
var noTouch = true,
19587
type = this.eventMap[key];
19588
19589
if(this.subscribers[key] && !this.subscribedExternal(key)){
19590
delete this.subscribers[key];
19591
19592
for(let i in this.eventMap){
19593
if(this.eventMap[i] === type){
19594
if(this.subscribers[i]){
19595
noTouch = false;
19596
}
19597
}
19598
}
19599
19600
if(noTouch){
19601
this.unsubscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]);
19602
this.unsubscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]);
19603
19604
delete this.touchSubscribers[type + "-touchstart"];
19605
delete this.touchSubscribers[type + "-touchend"];
19606
}
19607
}
19608
}
19609
19610
initializeColumn(column){
19611
var def = column.definition;
19612
19613
for(let key in this.eventMap){
19614
if(def[key]){
19615
this.subscriptionChanged(key, true);
19616
19617
if(!this.columnSubscribers[key]){
19618
this.columnSubscribers[key] = [];
19619
}
19620
19621
this.columnSubscribers[key].push(column);
19622
}
19623
}
19624
}
19625
19626
handle(action, e, component){
19627
this.dispatchEvent(action, e, component);
19628
}
19629
19630
handleTouch(type, action, e, component){
19631
var watchers = this.touchWatchers[type];
19632
19633
if(type === "column"){
19634
type = "header";
19635
}
19636
19637
switch(action){
19638
case "start":
19639
watchers.tap = true;
19640
19641
clearTimeout(watchers.tapHold);
19642
19643
watchers.tapHold = setTimeout(() => {
19644
clearTimeout(watchers.tapHold);
19645
watchers.tapHold = null;
19646
19647
watchers.tap = null;
19648
clearTimeout(watchers.tapDbl);
19649
watchers.tapDbl = null;
19650
19651
this.dispatchEvent(type + "TapHold", e, component);
19652
}, 1000);
19653
break;
19654
19655
case "end":
19656
if(watchers.tap){
19657
19658
watchers.tap = null;
19659
this.dispatchEvent(type + "Tap", e, component);
19660
}
19661
19662
if(watchers.tapDbl){
19663
clearTimeout(watchers.tapDbl);
19664
watchers.tapDbl = null;
19665
19666
this.dispatchEvent(type + "DblTap", e, component);
19667
}else {
19668
watchers.tapDbl = setTimeout(() => {
19669
clearTimeout(watchers.tapDbl);
19670
watchers.tapDbl = null;
19671
}, 300);
19672
}
19673
19674
clearTimeout(watchers.tapHold);
19675
watchers.tapHold = null;
19676
break;
19677
}
19678
}
19679
19680
dispatchEvent(action, e, component){
19681
var componentObj = component.getComponent(),
19682
callback;
19683
19684
if(this.columnSubscribers[action]){
19685
19686
if(component instanceof Cell){
19687
callback = component.column.definition[action];
19688
}else if(component instanceof Column){
19689
callback = component.definition[action];
19690
}
19691
19692
if(callback){
19693
callback(e, componentObj);
19694
}
19695
}
19696
19697
this.dispatchExternal(action, e, componentObj);
19698
}
19699
}
19700
19701
Interaction.moduleName = "interaction";
19702
19703
var defaultBindings = {
19704
navPrev:"shift + 9",
19705
navNext:9,
19706
navUp:38,
19707
navDown:40,
19708
scrollPageUp:33,
19709
scrollPageDown:34,
19710
scrollToStart:36,
19711
scrollToEnd:35,
19712
undo:["ctrl + 90", "meta + 90"],
19713
redo:["ctrl + 89", "meta + 89"],
19714
copyToClipboard:["ctrl + 67", "meta + 67"],
19715
};
19716
19717
var defaultActions = {
19718
keyBlock:function(e){
19719
e.stopPropagation();
19720
e.preventDefault();
19721
},
19722
scrollPageUp:function(e){
19723
var rowManager = this.table.rowManager,
19724
newPos = rowManager.scrollTop - rowManager.element.clientHeight;
19725
19726
e.preventDefault();
19727
19728
if(rowManager.displayRowsCount){
19729
if(newPos >= 0){
19730
rowManager.element.scrollTop = newPos;
19731
}else {
19732
rowManager.scrollToRow(rowManager.getDisplayRows()[0]);
19733
}
19734
}
19735
19736
this.table.element.focus();
19737
},
19738
scrollPageDown:function(e){
19739
var rowManager = this.table.rowManager,
19740
newPos = rowManager.scrollTop + rowManager.element.clientHeight,
19741
scrollMax = rowManager.element.scrollHeight;
19742
19743
e.preventDefault();
19744
19745
if(rowManager.displayRowsCount){
19746
if(newPos <= scrollMax){
19747
rowManager.element.scrollTop = newPos;
19748
}else {
19749
rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]);
19750
}
19751
}
19752
19753
this.table.element.focus();
19754
19755
},
19756
scrollToStart:function(e){
19757
var rowManager = this.table.rowManager;
19758
19759
e.preventDefault();
19760
19761
if(rowManager.displayRowsCount){
19762
rowManager.scrollToRow(rowManager.getDisplayRows()[0]);
19763
}
19764
19765
this.table.element.focus();
19766
},
19767
scrollToEnd:function(e){
19768
var rowManager = this.table.rowManager;
19769
19770
e.preventDefault();
19771
19772
if(rowManager.displayRowsCount){
19773
rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]);
19774
}
19775
19776
this.table.element.focus();
19777
},
19778
navPrev:function(e){
19779
this.dispatch("keybinding-nav-prev", e);
19780
},
19781
19782
navNext:function(e){
19783
this.dispatch("keybinding-nav-next", e);
19784
},
19785
19786
navLeft:function(e){
19787
this.dispatch("keybinding-nav-left", e);
19788
},
19789
19790
navRight:function(e){
19791
this.dispatch("keybinding-nav-right", e);
19792
},
19793
19794
navUp:function(e){
19795
this.dispatch("keybinding-nav-up", e);
19796
},
19797
19798
navDown:function(e){
19799
this.dispatch("keybinding-nav-down", e);
19800
},
19801
19802
undo:function(e){
19803
var cell = false;
19804
if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){
19805
19806
cell = this.table.modules.edit.currentCell;
19807
19808
if(!cell){
19809
e.preventDefault();
19810
this.table.modules.history.undo();
19811
}
19812
}
19813
},
19814
19815
redo:function(e){
19816
var cell = false;
19817
if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){
19818
19819
cell = this.table.modules.edit.currentCell;
19820
19821
if(!cell){
19822
e.preventDefault();
19823
this.table.modules.history.redo();
19824
}
19825
}
19826
},
19827
19828
copyToClipboard:function(e){
19829
if(!this.table.modules.edit.currentCell){
19830
if(this.table.modExists("clipboard", true)){
19831
this.table.modules.clipboard.copy(false, true);
19832
}
19833
}
19834
},
19835
};
19836
19837
class Keybindings extends Module{
19838
19839
constructor(table){
19840
super(table);
19841
19842
this.watchKeys = null;
19843
this.pressedKeys = null;
19844
this.keyupBinding = false;
19845
this.keydownBinding = false;
19846
19847
this.registerTableOption("keybindings", {}); //array for keybindings
19848
this.registerTableOption("tabEndNewRow", false); //create new row when tab to end of table
19849
}
19850
19851
initialize(){
19852
var bindings = this.table.options.keybindings,
19853
mergedBindings = {};
19854
19855
this.watchKeys = {};
19856
this.pressedKeys = [];
19857
19858
if(bindings !== false){
19859
Object.assign(mergedBindings, Keybindings.bindings);
19860
Object.assign(mergedBindings, bindings);
19861
19862
this.mapBindings(mergedBindings);
19863
this.bindEvents();
19864
}
19865
19866
this.subscribe("table-destroy", this.clearBindings.bind(this));
19867
}
19868
19869
mapBindings(bindings){
19870
for(let key in bindings){
19871
if(Keybindings.actions[key]){
19872
if(bindings[key]){
19873
if(typeof bindings[key] !== "object"){
19874
bindings[key] = [bindings[key]];
19875
}
19876
19877
bindings[key].forEach((binding) => {
19878
var bindingList = Array.isArray(binding) ? binding : [binding];
19879
19880
bindingList.forEach((item) => {
19881
this.mapBinding(key, item);
19882
});
19883
});
19884
}
19885
}else {
19886
console.warn("Key Binding Error - no such action:", key);
19887
}
19888
}
19889
}
19890
19891
mapBinding(action, symbolsList){
19892
var binding = {
19893
action: Keybindings.actions[action],
19894
keys: [],
19895
ctrl: false,
19896
shift: false,
19897
meta: false,
19898
};
19899
19900
var symbols = symbolsList.toString().toLowerCase().split(" ").join("").split("+");
19901
19902
symbols.forEach((symbol) => {
19903
switch(symbol){
19904
case "ctrl":
19905
binding.ctrl = true;
19906
break;
19907
19908
case "shift":
19909
binding.shift = true;
19910
break;
19911
19912
case "meta":
19913
binding.meta = true;
19914
break;
19915
19916
default:
19917
symbol = isNaN(symbol) ? symbol.toUpperCase().charCodeAt(0) : parseInt(symbol);
19918
binding.keys.push(symbol);
19919
19920
if(!this.watchKeys[symbol]){
19921
this.watchKeys[symbol] = [];
19922
}
19923
19924
this.watchKeys[symbol].push(binding);
19925
}
19926
});
19927
}
19928
19929
bindEvents(){
19930
var self = this;
19931
19932
this.keyupBinding = function(e){
19933
var code = e.keyCode;
19934
var bindings = self.watchKeys[code];
19935
19936
if(bindings){
19937
19938
self.pressedKeys.push(code);
19939
19940
bindings.forEach(function(binding){
19941
self.checkBinding(e, binding);
19942
});
19943
}
19944
};
19945
19946
this.keydownBinding = function(e){
19947
var code = e.keyCode;
19948
var bindings = self.watchKeys[code];
19949
19950
if(bindings){
19951
19952
var index = self.pressedKeys.indexOf(code);
19953
19954
if(index > -1){
19955
self.pressedKeys.splice(index, 1);
19956
}
19957
}
19958
};
19959
19960
this.table.element.addEventListener("keydown", this.keyupBinding);
19961
19962
this.table.element.addEventListener("keyup", this.keydownBinding);
19963
}
19964
19965
clearBindings(){
19966
if(this.keyupBinding){
19967
this.table.element.removeEventListener("keydown", this.keyupBinding);
19968
}
19969
19970
if(this.keydownBinding){
19971
this.table.element.removeEventListener("keyup", this.keydownBinding);
19972
}
19973
}
19974
19975
checkBinding(e, binding){
19976
var match = true;
19977
19978
if(e.ctrlKey == binding.ctrl && e.shiftKey == binding.shift && e.metaKey == binding.meta){
19979
binding.keys.forEach((key) => {
19980
var index = this.pressedKeys.indexOf(key);
19981
19982
if(index == -1){
19983
match = false;
19984
}
19985
});
19986
19987
if(match){
19988
binding.action.call(this, e);
19989
}
19990
19991
return true;
19992
}
19993
19994
return false;
19995
}
19996
}
19997
19998
Keybindings.moduleName = "keybindings";
19999
20000
//load defaults
20001
Keybindings.bindings = defaultBindings;
20002
Keybindings.actions = defaultActions;
20003
20004
class Menu extends Module{
20005
20006
constructor(table){
20007
super(table);
20008
20009
this.menuContainer = null;
20010
this.nestedMenuBlock = false;
20011
20012
this.currentComponent = null;
20013
this.rootPopup = null;
20014
20015
this.columnSubscribers = {};
20016
20017
this.registerTableOption("menuContainer", undefined); //deprecated
20018
20019
this.registerTableOption("rowContextMenu", false);
20020
this.registerTableOption("rowClickMenu", false);
20021
this.registerTableOption("rowDblClickMenu", false);
20022
this.registerTableOption("groupContextMenu", false);
20023
this.registerTableOption("groupClickMenu", false);
20024
this.registerTableOption("groupDblClickMenu", false);
20025
20026
this.registerColumnOption("headerContextMenu");
20027
this.registerColumnOption("headerClickMenu");
20028
this.registerColumnOption("headerDblClickMenu");
20029
this.registerColumnOption("headerMenu");
20030
this.registerColumnOption("headerMenuIcon");
20031
this.registerColumnOption("contextMenu");
20032
this.registerColumnOption("clickMenu");
20033
this.registerColumnOption("dblClickMenu");
20034
20035
}
20036
20037
initialize(){
20038
this.deprecatedOptionsCheck();
20039
this.initializeRowWatchers();
20040
this.initializeGroupWatchers();
20041
20042
this.subscribe("column-init", this.initializeColumn.bind(this));
20043
}
20044
20045
deprecatedOptionsCheck(){
20046
if(!this.deprecationCheck("menuContainer", "popupContainer")){
20047
this.table.options.popupContainer = this.table.options.menuContainer;
20048
}
20049
}
20050
20051
initializeRowWatchers(){
20052
if(this.table.options.rowContextMenu){
20053
this.subscribe("row-contextmenu", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu));
20054
this.table.on("rowTapHold", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu));
20055
}
20056
20057
if(this.table.options.rowClickMenu){
20058
this.subscribe("row-click", this.loadMenuEvent.bind(this, this.table.options.rowClickMenu));
20059
}
20060
20061
if(this.table.options.rowDblClickMenu){
20062
this.subscribe("row-dblclick", this.loadMenuEvent.bind(this, this.table.options.rowDblClickMenu));
20063
}
20064
}
20065
20066
initializeGroupWatchers(){
20067
if(this.table.options.groupContextMenu){
20068
this.subscribe("group-contextmenu", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu));
20069
this.table.on("groupTapHold", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu));
20070
}
20071
20072
if(this.table.options.groupClickMenu){
20073
this.subscribe("group-click", this.loadMenuEvent.bind(this, this.table.options.groupClickMenu));
20074
}
20075
20076
if(this.table.options.groupDblClickMenu){
20077
this.subscribe("group-dblclick", this.loadMenuEvent.bind(this, this.table.options.groupDblClickMenu));
20078
}
20079
}
20080
20081
initializeColumn(column){
20082
var def = column.definition;
20083
20084
//handle column events
20085
if(def.headerContextMenu && !this.columnSubscribers.headerContextMenu){
20086
this.columnSubscribers.headerContextMenu = this.loadMenuTableColumnEvent.bind(this, "headerContextMenu");
20087
this.subscribe("column-contextmenu", this.columnSubscribers.headerContextMenu);
20088
this.table.on("headerTapHold", this.loadMenuTableColumnEvent.bind(this, "headerContextMenu"));
20089
}
20090
20091
if(def.headerClickMenu && !this.columnSubscribers.headerClickMenu){
20092
this.columnSubscribers.headerClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerClickMenu");
20093
this.subscribe("column-click", this.columnSubscribers.headerClickMenu);
20094
}
20095
20096
if(def.headerDblClickMenu && !this.columnSubscribers.headerDblClickMenu){
20097
this.columnSubscribers.headerDblClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerDblClickMenu");
20098
this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickMenu);
20099
}
20100
20101
if(def.headerMenu){
20102
this.initializeColumnHeaderMenu(column);
20103
}
20104
20105
//handle cell events
20106
if(def.contextMenu && !this.columnSubscribers.contextMenu){
20107
this.columnSubscribers.contextMenu = this.loadMenuTableCellEvent.bind(this, "contextMenu");
20108
this.subscribe("cell-contextmenu", this.columnSubscribers.contextMenu);
20109
this.table.on("cellTapHold", this.loadMenuTableCellEvent.bind(this, "contextMenu"));
20110
}
20111
20112
if(def.clickMenu && !this.columnSubscribers.clickMenu){
20113
this.columnSubscribers.clickMenu = this.loadMenuTableCellEvent.bind(this, "clickMenu");
20114
this.subscribe("cell-click", this.columnSubscribers.clickMenu);
20115
}
20116
20117
if(def.dblClickMenu && !this.columnSubscribers.dblClickMenu){
20118
this.columnSubscribers.dblClickMenu = this.loadMenuTableCellEvent.bind(this, "dblClickMenu");
20119
this.subscribe("cell-dblclick", this.columnSubscribers.dblClickMenu);
20120
}
20121
}
20122
20123
initializeColumnHeaderMenu(column){
20124
var icon = column.definition.headerMenuIcon,
20125
headerMenuEl;
20126
20127
headerMenuEl = document.createElement("span");
20128
headerMenuEl.classList.add("tabulator-header-popup-button");
20129
20130
if(icon){
20131
if(typeof icon === "function"){
20132
icon = icon(column.getComponent());
20133
}
20134
20135
if(icon instanceof HTMLElement){
20136
headerMenuEl.appendChild(icon);
20137
}else {
20138
headerMenuEl.innerHTML = icon;
20139
}
20140
}else {
20141
headerMenuEl.innerHTML = "&vellip;";
20142
}
20143
20144
headerMenuEl.addEventListener("click", (e) => {
20145
e.stopPropagation();
20146
e.preventDefault();
20147
20148
this.loadMenuEvent(column.definition.headerMenu, e, column);
20149
});
20150
20151
column.titleElement.insertBefore(headerMenuEl, column.titleElement.firstChild);
20152
}
20153
20154
loadMenuTableCellEvent(option, e, cell){
20155
if(cell._cell){
20156
cell = cell._cell;
20157
}
20158
20159
if(cell.column.definition[option]){
20160
this.loadMenuEvent(cell.column.definition[option], e, cell);
20161
}
20162
}
20163
20164
loadMenuTableColumnEvent(option, e, column){
20165
if(column._column){
20166
column = column._column;
20167
}
20168
20169
if(column.definition[option]){
20170
this.loadMenuEvent(column.definition[option], e, column);
20171
}
20172
}
20173
20174
loadMenuEvent(menu, e, component){
20175
if(component._group){
20176
component = component._group;
20177
}else if(component._row){
20178
component = component._row;
20179
}
20180
20181
menu = typeof menu == "function" ? menu.call(this.table, e, component.getComponent()) : menu;
20182
20183
this.loadMenu(e, component, menu);
20184
}
20185
20186
loadMenu(e, component, menu, parentEl, parentPopup){
20187
var touch = !(e instanceof MouseEvent),
20188
menuEl = document.createElement("div"),
20189
popup;
20190
20191
menuEl.classList.add("tabulator-menu");
20192
20193
if(!touch){
20194
e.preventDefault();
20195
}
20196
20197
//abort if no menu set
20198
if(!menu || !menu.length){
20199
return;
20200
}
20201
20202
if(!parentEl){
20203
if(this.nestedMenuBlock){
20204
//abort if child menu already open
20205
if(this.rootPopup){
20206
return;
20207
}
20208
}else {
20209
this.nestedMenuBlock = setTimeout(() => {
20210
this.nestedMenuBlock = false;
20211
}, 100);
20212
}
20213
20214
if(this.rootPopup){
20215
this.rootPopup.hide();
20216
}
20217
20218
this.rootPopup = popup = this.popup(menuEl);
20219
20220
}else {
20221
popup = parentPopup.child(menuEl);
20222
}
20223
20224
menu.forEach((item) => {
20225
var itemEl = document.createElement("div"),
20226
label = item.label,
20227
disabled = item.disabled;
20228
20229
if(item.separator){
20230
itemEl.classList.add("tabulator-menu-separator");
20231
}else {
20232
itemEl.classList.add("tabulator-menu-item");
20233
20234
if(typeof label == "function"){
20235
label = label.call(this.table, component.getComponent());
20236
}
20237
20238
if(label instanceof Node){
20239
itemEl.appendChild(label);
20240
}else {
20241
itemEl.innerHTML = label;
20242
}
20243
20244
if(typeof disabled == "function"){
20245
disabled = disabled.call(this.table, component.getComponent());
20246
}
20247
20248
if(disabled){
20249
itemEl.classList.add("tabulator-menu-item-disabled");
20250
itemEl.addEventListener("click", (e) => {
20251
e.stopPropagation();
20252
});
20253
}else {
20254
if(item.menu && item.menu.length){
20255
itemEl.addEventListener("click", (e) => {
20256
e.stopPropagation();
20257
this.loadMenu(e, component, item.menu, itemEl, popup);
20258
});
20259
}else {
20260
if(item.action){
20261
itemEl.addEventListener("click", (e) => {
20262
item.action(e, component.getComponent());
20263
});
20264
}
20265
}
20266
}
20267
20268
if(item.menu && item.menu.length){
20269
itemEl.classList.add("tabulator-menu-item-submenu");
20270
}
20271
}
20272
20273
menuEl.appendChild(itemEl);
20274
});
20275
20276
menuEl.addEventListener("click", (e) => {
20277
if(this.rootPopup){
20278
this.rootPopup.hide();
20279
}
20280
});
20281
20282
popup.show(parentEl || e);
20283
20284
if(popup === this.rootPopup){
20285
this.rootPopup.hideOnBlur(() => {
20286
this.rootPopup = null;
20287
20288
if(this.currentComponent){
20289
this.dispatchExternal("menuClosed", this.currentComponent.getComponent());
20290
this.currentComponent = null;
20291
}
20292
});
20293
20294
this.currentComponent = component;
20295
20296
this.dispatchExternal("menuOpened", component.getComponent());
20297
}
20298
}
20299
}
20300
20301
Menu.moduleName = "menu";
20302
20303
class MoveColumns extends Module{
20304
20305
constructor(table){
20306
super(table);
20307
20308
this.placeholderElement = this.createPlaceholderElement();
20309
this.hoverElement = false; //floating column header element
20310
this.checkTimeout = false; //click check timeout holder
20311
this.checkPeriod = 250; //period to wait on mousedown to consider this a move and not a click
20312
this.moving = false; //currently moving column
20313
this.toCol = false; //destination column
20314
this.toColAfter = false; //position of moving column relative to the destination column
20315
this.startX = 0; //starting position within header element
20316
this.autoScrollMargin = 40; //auto scroll on edge when within margin
20317
this.autoScrollStep = 5; //auto scroll distance in pixels
20318
this.autoScrollTimeout = false; //auto scroll timeout
20319
this.touchMove = false;
20320
20321
this.moveHover = this.moveHover.bind(this);
20322
this.endMove = this.endMove.bind(this);
20323
20324
this.registerTableOption("movableColumns", false); //enable movable columns
20325
}
20326
20327
createPlaceholderElement(){
20328
var el = document.createElement("div");
20329
20330
el.classList.add("tabulator-col");
20331
el.classList.add("tabulator-col-placeholder");
20332
20333
return el;
20334
}
20335
20336
initialize(){
20337
if(this.table.options.movableColumns){
20338
this.subscribe("column-init", this.initializeColumn.bind(this));
20339
}
20340
}
20341
20342
initializeColumn(column){
20343
var self = this,
20344
config = {},
20345
colEl;
20346
20347
if(!column.modules.frozen && !column.isGroup){
20348
colEl = column.getElement();
20349
20350
config.mousemove = function(e){
20351
if(column.parent === self.moving.parent){
20352
if((((self.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(colEl).left) + self.table.columnManager.contentsElement.scrollLeft) > (column.getWidth() / 2)){
20353
if(self.toCol !== column || !self.toColAfter){
20354
colEl.parentNode.insertBefore(self.placeholderElement, colEl.nextSibling);
20355
self.moveColumn(column, true);
20356
}
20357
}else {
20358
if(self.toCol !== column || self.toColAfter){
20359
colEl.parentNode.insertBefore(self.placeholderElement, colEl);
20360
self.moveColumn(column, false);
20361
}
20362
}
20363
}
20364
}.bind(self);
20365
20366
colEl.addEventListener("mousedown", function(e){
20367
self.touchMove = false;
20368
if(e.which === 1){
20369
self.checkTimeout = setTimeout(function(){
20370
self.startMove(e, column);
20371
}, self.checkPeriod);
20372
}
20373
});
20374
20375
colEl.addEventListener("mouseup", function(e){
20376
if(e.which === 1){
20377
if(self.checkTimeout){
20378
clearTimeout(self.checkTimeout);
20379
}
20380
}
20381
});
20382
20383
self.bindTouchEvents(column);
20384
}
20385
20386
column.modules.moveColumn = config;
20387
}
20388
20389
bindTouchEvents(column){
20390
var colEl = column.getElement(),
20391
startXMove = false, //shifting center position of the cell
20392
nextCol, prevCol, nextColWidth, prevColWidth, nextColWidthLast, prevColWidthLast;
20393
20394
colEl.addEventListener("touchstart", (e) => {
20395
this.checkTimeout = setTimeout(() => {
20396
this.touchMove = true;
20397
nextCol = column.nextColumn();
20398
nextColWidth = nextCol ? nextCol.getWidth()/2 : 0;
20399
prevCol = column.prevColumn();
20400
prevColWidth = prevCol ? prevCol.getWidth()/2 : 0;
20401
nextColWidthLast = 0;
20402
prevColWidthLast = 0;
20403
startXMove = false;
20404
20405
this.startMove(e, column);
20406
}, this.checkPeriod);
20407
}, {passive: true});
20408
20409
colEl.addEventListener("touchmove", (e) => {
20410
var diff, moveToCol;
20411
20412
if(this.moving){
20413
this.moveHover(e);
20414
20415
if(!startXMove){
20416
startXMove = e.touches[0].pageX;
20417
}
20418
20419
diff = e.touches[0].pageX - startXMove;
20420
20421
if(diff > 0){
20422
if(nextCol && diff - nextColWidthLast > nextColWidth){
20423
moveToCol = nextCol;
20424
20425
if(moveToCol !== column){
20426
startXMove = e.touches[0].pageX;
20427
moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement().nextSibling);
20428
this.moveColumn(moveToCol, true);
20429
}
20430
}
20431
}else {
20432
if(prevCol && -diff - prevColWidthLast > prevColWidth){
20433
moveToCol = prevCol;
20434
20435
if(moveToCol !== column){
20436
startXMove = e.touches[0].pageX;
20437
moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement());
20438
this.moveColumn(moveToCol, false);
20439
}
20440
}
20441
}
20442
20443
if(moveToCol){
20444
nextCol = moveToCol.nextColumn();
20445
nextColWidthLast = nextColWidth;
20446
nextColWidth = nextCol ? nextCol.getWidth() / 2 : 0;
20447
prevCol = moveToCol.prevColumn();
20448
prevColWidthLast = prevColWidth;
20449
prevColWidth = prevCol ? prevCol.getWidth() / 2 : 0;
20450
}
20451
}
20452
}, {passive: true});
20453
20454
colEl.addEventListener("touchend", (e) => {
20455
if(this.checkTimeout){
20456
clearTimeout(this.checkTimeout);
20457
}
20458
if(this.moving){
20459
this.endMove(e);
20460
}
20461
});
20462
}
20463
20464
startMove(e, column){
20465
var element = column.getElement(),
20466
headerElement = this.table.columnManager.getContentsElement(),
20467
headersElement = this.table.columnManager.getHeadersElement();
20468
20469
this.moving = column;
20470
this.startX = (this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(element).left;
20471
20472
this.table.element.classList.add("tabulator-block-select");
20473
20474
//create placeholder
20475
this.placeholderElement.style.width = column.getWidth() + "px";
20476
this.placeholderElement.style.height = column.getHeight() + "px";
20477
20478
element.parentNode.insertBefore(this.placeholderElement, element);
20479
element.parentNode.removeChild(element);
20480
20481
//create hover element
20482
this.hoverElement = element.cloneNode(true);
20483
this.hoverElement.classList.add("tabulator-moving");
20484
20485
headerElement.appendChild(this.hoverElement);
20486
20487
this.hoverElement.style.left = "0";
20488
this.hoverElement.style.bottom = (headerElement.clientHeight - headersElement.offsetHeight) + "px";
20489
20490
if(!this.touchMove){
20491
this._bindMouseMove();
20492
20493
document.body.addEventListener("mousemove", this.moveHover);
20494
document.body.addEventListener("mouseup", this.endMove);
20495
}
20496
20497
this.moveHover(e);
20498
}
20499
20500
_bindMouseMove(){
20501
this.table.columnManager.columnsByIndex.forEach(function(column){
20502
if(column.modules.moveColumn.mousemove){
20503
column.getElement().addEventListener("mousemove", column.modules.moveColumn.mousemove);
20504
}
20505
});
20506
}
20507
20508
_unbindMouseMove(){
20509
this.table.columnManager.columnsByIndex.forEach(function(column){
20510
if(column.modules.moveColumn.mousemove){
20511
column.getElement().removeEventListener("mousemove", column.modules.moveColumn.mousemove);
20512
}
20513
});
20514
}
20515
20516
moveColumn(column, after){
20517
var movingCells = this.moving.getCells();
20518
20519
this.toCol = column;
20520
this.toColAfter = after;
20521
20522
if(after){
20523
column.getCells().forEach(function(cell, i){
20524
var cellEl = cell.getElement(true);
20525
20526
if(cellEl.parentNode && movingCells[i]){
20527
cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl.nextSibling);
20528
}
20529
});
20530
}else {
20531
column.getCells().forEach(function(cell, i){
20532
var cellEl = cell.getElement(true);
20533
20534
if(cellEl.parentNode && movingCells[i]){
20535
cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl);
20536
}
20537
});
20538
}
20539
}
20540
20541
endMove(e){
20542
if(e.which === 1 || this.touchMove){
20543
this._unbindMouseMove();
20544
20545
this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling);
20546
this.placeholderElement.parentNode.removeChild(this.placeholderElement);
20547
this.hoverElement.parentNode.removeChild(this.hoverElement);
20548
20549
this.table.element.classList.remove("tabulator-block-select");
20550
20551
if(this.toCol){
20552
this.table.columnManager.moveColumnActual(this.moving, this.toCol, this.toColAfter);
20553
}
20554
20555
this.moving = false;
20556
this.toCol = false;
20557
this.toColAfter = false;
20558
20559
if(!this.touchMove){
20560
document.body.removeEventListener("mousemove", this.moveHover);
20561
document.body.removeEventListener("mouseup", this.endMove);
20562
}
20563
}
20564
}
20565
20566
moveHover(e){
20567
var columnHolder = this.table.columnManager.getContentsElement(),
20568
scrollLeft = columnHolder.scrollLeft,
20569
xPos = ((this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(columnHolder).left) + scrollLeft,
20570
scrollPos;
20571
20572
this.hoverElement.style.left = (xPos - this.startX) + "px";
20573
20574
if(xPos - scrollLeft < this.autoScrollMargin){
20575
if(!this.autoScrollTimeout){
20576
this.autoScrollTimeout = setTimeout(() => {
20577
scrollPos = Math.max(0,scrollLeft-5);
20578
this.table.rowManager.getElement().scrollLeft = scrollPos;
20579
this.autoScrollTimeout = false;
20580
}, 1);
20581
}
20582
}
20583
20584
if(scrollLeft + columnHolder.clientWidth - xPos < this.autoScrollMargin){
20585
if(!this.autoScrollTimeout){
20586
this.autoScrollTimeout = setTimeout(() => {
20587
scrollPos = Math.min(columnHolder.clientWidth, scrollLeft+5);
20588
this.table.rowManager.getElement().scrollLeft = scrollPos;
20589
this.autoScrollTimeout = false;
20590
}, 1);
20591
}
20592
}
20593
}
20594
}
20595
20596
MoveColumns.moduleName = "moveColumn";
20597
20598
class MoveRows extends Module{
20599
20600
constructor(table){
20601
super(table);
20602
20603
this.placeholderElement = this.createPlaceholderElement();
20604
this.hoverElement = false; //floating row header element
20605
this.checkTimeout = false; //click check timeout holder
20606
this.checkPeriod = 150; //period to wait on mousedown to consider this a move and not a click
20607
this.moving = false; //currently moving row
20608
this.toRow = false; //destination row
20609
this.toRowAfter = false; //position of moving row relative to the destination row
20610
this.hasHandle = false; //row has handle instead of fully movable row
20611
this.startY = 0; //starting Y position within header element
20612
this.startX = 0; //starting X position within header element
20613
20614
this.moveHover = this.moveHover.bind(this);
20615
this.endMove = this.endMove.bind(this);
20616
this.tableRowDropEvent = false;
20617
20618
this.touchMove = false;
20619
20620
this.connection = false;
20621
this.connectionSelectorsTables = false;
20622
this.connectionSelectorsElements = false;
20623
this.connectionElements = [];
20624
this.connections = [];
20625
20626
this.connectedTable = false;
20627
this.connectedRow = false;
20628
20629
this.registerTableOption("movableRows", false); //enable movable rows
20630
this.registerTableOption("movableRowsConnectedTables", false); //tables for movable rows to be connected to
20631
this.registerTableOption("movableRowsConnectedElements", false); //other elements for movable rows to be connected to
20632
this.registerTableOption("movableRowsSender", false);
20633
this.registerTableOption("movableRowsReceiver", "insert");
20634
20635
this.registerColumnOption("rowHandle");
20636
}
20637
20638
createPlaceholderElement(){
20639
var el = document.createElement("div");
20640
20641
el.classList.add("tabulator-row");
20642
el.classList.add("tabulator-row-placeholder");
20643
20644
return el;
20645
}
20646
20647
initialize(){
20648
if(this.table.options.movableRows){
20649
this.connectionSelectorsTables = this.table.options.movableRowsConnectedTables;
20650
this.connectionSelectorsElements = this.table.options.movableRowsConnectedElements;
20651
20652
this.connection = this.connectionSelectorsTables || this.connectionSelectorsElements;
20653
20654
this.subscribe("cell-init", this.initializeCell.bind(this));
20655
this.subscribe("column-init", this.initializeColumn.bind(this));
20656
this.subscribe("row-init", this.initializeRow.bind(this));
20657
}
20658
}
20659
20660
initializeGroupHeader(group){
20661
var self = this,
20662
config = {};
20663
20664
//inter table drag drop
20665
config.mouseup = function(e){
20666
self.tableRowDrop(e, group);
20667
}.bind(self);
20668
20669
//same table drag drop
20670
config.mousemove = function(e){
20671
var rowEl;
20672
20673
if(((e.pageY - Helpers.elOffset(group.element).top) + self.table.rowManager.element.scrollTop) > (group.getHeight() / 2)){
20674
if(self.toRow !== group || !self.toRowAfter){
20675
rowEl = group.getElement();
20676
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
20677
self.moveRow(group, true);
20678
}
20679
}else {
20680
if(self.toRow !== group || self.toRowAfter){
20681
rowEl = group.getElement();
20682
if(rowEl.previousSibling){
20683
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
20684
self.moveRow(group, false);
20685
}
20686
}
20687
}
20688
}.bind(self);
20689
20690
group.modules.moveRow = config;
20691
}
20692
20693
initializeRow(row){
20694
var self = this,
20695
config = {},
20696
rowEl;
20697
20698
//inter table drag drop
20699
config.mouseup = function(e){
20700
self.tableRowDrop(e, row);
20701
}.bind(self);
20702
20703
//same table drag drop
20704
config.mousemove = function(e){
20705
var rowEl = row.getElement();
20706
20707
if(((e.pageY - Helpers.elOffset(rowEl).top) + self.table.rowManager.element.scrollTop) > (row.getHeight() / 2)){
20708
if(self.toRow !== row || !self.toRowAfter){
20709
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
20710
self.moveRow(row, true);
20711
}
20712
}else {
20713
if(self.toRow !== row || self.toRowAfter){
20714
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
20715
self.moveRow(row, false);
20716
}
20717
}
20718
}.bind(self);
20719
20720
20721
if(!this.hasHandle){
20722
20723
rowEl = row.getElement();
20724
20725
rowEl.addEventListener("mousedown", function(e){
20726
if(e.which === 1){
20727
self.checkTimeout = setTimeout(function(){
20728
self.startMove(e, row);
20729
}, self.checkPeriod);
20730
}
20731
});
20732
20733
rowEl.addEventListener("mouseup", function(e){
20734
if(e.which === 1){
20735
if(self.checkTimeout){
20736
clearTimeout(self.checkTimeout);
20737
}
20738
}
20739
});
20740
20741
this.bindTouchEvents(row, row.getElement());
20742
}
20743
20744
row.modules.moveRow = config;
20745
}
20746
20747
initializeColumn(column){
20748
if(column.definition.rowHandle && this.table.options.movableRows !== false){
20749
this.hasHandle = true;
20750
}
20751
}
20752
20753
initializeCell(cell){
20754
if(cell.column.definition.rowHandle && this.table.options.movableRows !== false){
20755
var self = this,
20756
cellEl = cell.getElement(true);
20757
20758
cellEl.addEventListener("mousedown", function(e){
20759
if(e.which === 1){
20760
self.checkTimeout = setTimeout(function(){
20761
self.startMove(e, cell.row);
20762
}, self.checkPeriod);
20763
}
20764
});
20765
20766
cellEl.addEventListener("mouseup", function(e){
20767
if(e.which === 1){
20768
if(self.checkTimeout){
20769
clearTimeout(self.checkTimeout);
20770
}
20771
}
20772
});
20773
20774
this.bindTouchEvents(cell.row, cellEl);
20775
}
20776
}
20777
20778
bindTouchEvents(row, element){
20779
var startYMove = false, //shifting center position of the cell
20780
nextRow, prevRow, nextRowHeight, prevRowHeight, nextRowHeightLast, prevRowHeightLast;
20781
20782
element.addEventListener("touchstart", (e) => {
20783
this.checkTimeout = setTimeout(() => {
20784
this.touchMove = true;
20785
nextRow = row.nextRow();
20786
nextRowHeight = nextRow ? nextRow.getHeight()/2 : 0;
20787
prevRow = row.prevRow();
20788
prevRowHeight = prevRow ? prevRow.getHeight()/2 : 0;
20789
nextRowHeightLast = 0;
20790
prevRowHeightLast = 0;
20791
startYMove = false;
20792
20793
this.startMove(e, row);
20794
}, this.checkPeriod);
20795
}, {passive: true});
20796
this.moving, this.toRow, this.toRowAfter;
20797
element.addEventListener("touchmove", (e) => {
20798
20799
var diff, moveToRow;
20800
20801
if(this.moving){
20802
e.preventDefault();
20803
20804
this.moveHover(e);
20805
20806
if(!startYMove){
20807
startYMove = e.touches[0].pageY;
20808
}
20809
20810
diff = e.touches[0].pageY - startYMove;
20811
20812
if(diff > 0){
20813
if(nextRow && diff - nextRowHeightLast > nextRowHeight){
20814
moveToRow = nextRow;
20815
20816
if(moveToRow !== row){
20817
startYMove = e.touches[0].pageY;
20818
moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement().nextSibling);
20819
this.moveRow(moveToRow, true);
20820
}
20821
}
20822
}else {
20823
if(prevRow && -diff - prevRowHeightLast > prevRowHeight){
20824
moveToRow = prevRow;
20825
20826
if(moveToRow !== row){
20827
startYMove = e.touches[0].pageY;
20828
moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement());
20829
this.moveRow(moveToRow, false);
20830
}
20831
}
20832
}
20833
20834
if(moveToRow){
20835
nextRow = moveToRow.nextRow();
20836
nextRowHeightLast = nextRowHeight;
20837
nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0;
20838
prevRow = moveToRow.prevRow();
20839
prevRowHeightLast = prevRowHeight;
20840
prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0;
20841
}
20842
}
20843
});
20844
20845
element.addEventListener("touchend", (e) => {
20846
if(this.checkTimeout){
20847
clearTimeout(this.checkTimeout);
20848
}
20849
if(this.moving){
20850
this.endMove(e);
20851
this.touchMove = false;
20852
}
20853
});
20854
}
20855
20856
_bindMouseMove(){
20857
this.table.rowManager.getDisplayRows().forEach((row) => {
20858
if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){
20859
row.getElement().addEventListener("mousemove", row.modules.moveRow.mousemove);
20860
}
20861
});
20862
}
20863
20864
_unbindMouseMove(){
20865
this.table.rowManager.getDisplayRows().forEach((row) => {
20866
if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){
20867
row.getElement().removeEventListener("mousemove", row.modules.moveRow.mousemove);
20868
}
20869
});
20870
}
20871
20872
startMove(e, row){
20873
var element = row.getElement();
20874
20875
this.setStartPosition(e, row);
20876
20877
this.moving = row;
20878
20879
this.table.element.classList.add("tabulator-block-select");
20880
20881
//create placeholder
20882
this.placeholderElement.style.width = row.getWidth() + "px";
20883
this.placeholderElement.style.height = row.getHeight() + "px";
20884
20885
if(!this.connection){
20886
element.parentNode.insertBefore(this.placeholderElement, element);
20887
element.parentNode.removeChild(element);
20888
}else {
20889
this.table.element.classList.add("tabulator-movingrow-sending");
20890
this.connectToTables(row);
20891
}
20892
20893
//create hover element
20894
this.hoverElement = element.cloneNode(true);
20895
this.hoverElement.classList.add("tabulator-moving");
20896
20897
if(this.connection){
20898
document.body.appendChild(this.hoverElement);
20899
this.hoverElement.style.left = "0";
20900
this.hoverElement.style.top = "0";
20901
this.hoverElement.style.width = this.table.element.clientWidth + "px";
20902
this.hoverElement.style.whiteSpace = "nowrap";
20903
this.hoverElement.style.overflow = "hidden";
20904
this.hoverElement.style.pointerEvents = "none";
20905
}else {
20906
this.table.rowManager.getTableElement().appendChild(this.hoverElement);
20907
20908
this.hoverElement.style.left = "0";
20909
this.hoverElement.style.top = "0";
20910
20911
this._bindMouseMove();
20912
}
20913
20914
document.body.addEventListener("mousemove", this.moveHover);
20915
document.body.addEventListener("mouseup", this.endMove);
20916
20917
this.dispatchExternal("rowMoving", row.getComponent());
20918
20919
this.moveHover(e);
20920
}
20921
20922
setStartPosition(e, row){
20923
var pageX = this.touchMove ? e.touches[0].pageX : e.pageX,
20924
pageY = this.touchMove ? e.touches[0].pageY : e.pageY,
20925
element, position;
20926
20927
element = row.getElement();
20928
if(this.connection){
20929
position = element.getBoundingClientRect();
20930
20931
this.startX = position.left - pageX + window.pageXOffset;
20932
this.startY = position.top - pageY + window.pageYOffset;
20933
}else {
20934
this.startY = (pageY - element.getBoundingClientRect().top);
20935
}
20936
}
20937
20938
endMove(e){
20939
if(!e || e.which === 1 || this.touchMove){
20940
this._unbindMouseMove();
20941
20942
if(!this.connection){
20943
this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling);
20944
this.placeholderElement.parentNode.removeChild(this.placeholderElement);
20945
}
20946
20947
this.hoverElement.parentNode.removeChild(this.hoverElement);
20948
20949
this.table.element.classList.remove("tabulator-block-select");
20950
20951
if(this.toRow){
20952
this.table.rowManager.moveRow(this.moving, this.toRow, this.toRowAfter);
20953
}else {
20954
this.dispatchExternal("rowMoveCancelled", this.moving.getComponent());
20955
}
20956
20957
this.moving = false;
20958
this.toRow = false;
20959
this.toRowAfter = false;
20960
20961
document.body.removeEventListener("mousemove", this.moveHover);
20962
document.body.removeEventListener("mouseup", this.endMove);
20963
20964
if(this.connection){
20965
this.table.element.classList.remove("tabulator-movingrow-sending");
20966
this.disconnectFromTables();
20967
}
20968
}
20969
}
20970
20971
moveRow(row, after){
20972
this.toRow = row;
20973
this.toRowAfter = after;
20974
}
20975
20976
moveHover(e){
20977
if(this.connection){
20978
this.moveHoverConnections.call(this, e);
20979
}else {
20980
this.moveHoverTable.call(this, e);
20981
}
20982
}
20983
20984
moveHoverTable(e){
20985
var rowHolder = this.table.rowManager.getElement(),
20986
scrollTop = rowHolder.scrollTop,
20987
yPos = ((this.touchMove ? e.touches[0].pageY : e.pageY) - rowHolder.getBoundingClientRect().top) + scrollTop;
20988
20989
this.hoverElement.style.top = Math.min(yPos - this.startY, this.table.rowManager.element.scrollHeight - this.hoverElement.offsetHeight) + "px";
20990
}
20991
20992
moveHoverConnections(e){
20993
this.hoverElement.style.left = (this.startX + (this.touchMove ? e.touches[0].pageX : e.pageX)) + "px";
20994
this.hoverElement.style.top = (this.startY + (this.touchMove ? e.touches[0].pageY : e.pageY)) + "px";
20995
}
20996
20997
elementRowDrop(e, element, row){
20998
this.dispatchExternal("movableRowsElementDrop", e, element, row ? row.getComponent() : false);
20999
}
21000
21001
//establish connection with other tables
21002
connectToTables(row){
21003
var connectionTables;
21004
21005
if(this.connectionSelectorsTables){
21006
connectionTables = this.commsConnections(this.connectionSelectorsTables);
21007
21008
this.dispatchExternal("movableRowsSendingStart", connectionTables);
21009
21010
this.commsSend(this.connectionSelectorsTables, "moveRow", "connect", {
21011
row:row,
21012
});
21013
}
21014
21015
if(this.connectionSelectorsElements){
21016
21017
this.connectionElements = [];
21018
21019
if(!Array.isArray(this.connectionSelectorsElements)){
21020
this.connectionSelectorsElements = [this.connectionSelectorsElements];
21021
}
21022
21023
this.connectionSelectorsElements.forEach((query) => {
21024
if(typeof query === "string"){
21025
this.connectionElements = this.connectionElements.concat(Array.prototype.slice.call(document.querySelectorAll(query)));
21026
}else {
21027
this.connectionElements.push(query);
21028
}
21029
});
21030
21031
this.connectionElements.forEach((element) => {
21032
var dropEvent = (e) => {
21033
this.elementRowDrop(e, element, this.moving);
21034
};
21035
21036
element.addEventListener("mouseup", dropEvent);
21037
element.tabulatorElementDropEvent = dropEvent;
21038
21039
element.classList.add("tabulator-movingrow-receiving");
21040
});
21041
}
21042
}
21043
21044
//disconnect from other tables
21045
disconnectFromTables(){
21046
var connectionTables;
21047
21048
if(this.connectionSelectorsTables){
21049
connectionTables = this.commsConnections(this.connectionSelectorsTables);
21050
21051
this.dispatchExternal("movableRowsSendingStop", connectionTables);
21052
21053
this.commsSend(this.connectionSelectorsTables, "moveRow", "disconnect");
21054
}
21055
21056
this.connectionElements.forEach((element) => {
21057
element.classList.remove("tabulator-movingrow-receiving");
21058
element.removeEventListener("mouseup", element.tabulatorElementDropEvent);
21059
delete element.tabulatorElementDropEvent;
21060
});
21061
}
21062
21063
//accept incomming connection
21064
connect(table, row){
21065
if(!this.connectedTable){
21066
this.connectedTable = table;
21067
this.connectedRow = row;
21068
21069
this.table.element.classList.add("tabulator-movingrow-receiving");
21070
21071
this.table.rowManager.getDisplayRows().forEach((row) => {
21072
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
21073
row.getElement().addEventListener("mouseup", row.modules.moveRow.mouseup);
21074
}
21075
});
21076
21077
this.tableRowDropEvent = this.tableRowDrop.bind(this);
21078
21079
this.table.element.addEventListener("mouseup", this.tableRowDropEvent);
21080
21081
this.dispatchExternal("movableRowsReceivingStart", row, table);
21082
21083
return true;
21084
}else {
21085
console.warn("Move Row Error - Table cannot accept connection, already connected to table:", this.connectedTable);
21086
return false;
21087
}
21088
}
21089
21090
//close incoming connection
21091
disconnect(table){
21092
if(table === this.connectedTable){
21093
this.connectedTable = false;
21094
this.connectedRow = false;
21095
21096
this.table.element.classList.remove("tabulator-movingrow-receiving");
21097
21098
this.table.rowManager.getDisplayRows().forEach((row) =>{
21099
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
21100
row.getElement().removeEventListener("mouseup", row.modules.moveRow.mouseup);
21101
}
21102
});
21103
21104
this.table.element.removeEventListener("mouseup", this.tableRowDropEvent);
21105
21106
this.dispatchExternal("movableRowsReceivingStop", table);
21107
}else {
21108
console.warn("Move Row Error - trying to disconnect from non connected table");
21109
}
21110
}
21111
21112
dropComplete(table, row, success){
21113
var sender = false;
21114
21115
if(success){
21116
21117
switch(typeof this.table.options.movableRowsSender){
21118
case "string":
21119
sender = this.senders[this.table.options.movableRowsSender];
21120
break;
21121
21122
case "function":
21123
sender = this.table.options.movableRowsSender;
21124
break;
21125
}
21126
21127
if(sender){
21128
sender.call(this, this.moving ? this.moving.getComponent() : undefined, row ? row.getComponent() : undefined, table);
21129
}else {
21130
if(this.table.options.movableRowsSender){
21131
console.warn("Mover Row Error - no matching sender found:", this.table.options.movableRowsSender);
21132
}
21133
}
21134
21135
this.dispatchExternal("movableRowsSent", this.moving.getComponent(), row ? row.getComponent() : undefined, table);
21136
}else {
21137
this.dispatchExternal("movableRowsSentFailed", this.moving.getComponent(), row ? row.getComponent() : undefined, table);
21138
}
21139
21140
this.endMove();
21141
}
21142
21143
tableRowDrop(e, row){
21144
var receiver = false,
21145
success = false;
21146
21147
e.stopImmediatePropagation();
21148
21149
switch(typeof this.table.options.movableRowsReceiver){
21150
case "string":
21151
receiver = this.receivers[this.table.options.movableRowsReceiver];
21152
break;
21153
21154
case "function":
21155
receiver = this.table.options.movableRowsReceiver;
21156
break;
21157
}
21158
21159
if(receiver){
21160
success = receiver.call(this, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
21161
}else {
21162
console.warn("Mover Row Error - no matching receiver found:", this.table.options.movableRowsReceiver);
21163
}
21164
21165
if(success){
21166
this.dispatchExternal("movableRowsReceived", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
21167
}else {
21168
this.dispatchExternal("movableRowsReceivedFailed", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
21169
}
21170
21171
this.commsSend(this.connectedTable, "moveRow", "dropcomplete", {
21172
row:row,
21173
success:success,
21174
});
21175
}
21176
21177
commsReceived(table, action, data){
21178
switch(action){
21179
case "connect":
21180
return this.connect(table, data.row);
21181
21182
case "disconnect":
21183
return this.disconnect(table);
21184
21185
case "dropcomplete":
21186
return this.dropComplete(table, data.row, data.success);
21187
}
21188
}
21189
}
21190
21191
MoveRows.prototype.receivers = {
21192
insert:function(fromRow, toRow, fromTable){
21193
this.table.addRow(fromRow.getData(), undefined, toRow);
21194
return true;
21195
},
21196
21197
add:function(fromRow, toRow, fromTable){
21198
this.table.addRow(fromRow.getData());
21199
return true;
21200
},
21201
21202
update:function(fromRow, toRow, fromTable){
21203
if(toRow){
21204
toRow.update(fromRow.getData());
21205
return true;
21206
}
21207
21208
return false;
21209
},
21210
21211
replace:function(fromRow, toRow, fromTable){
21212
if(toRow){
21213
this.table.addRow(fromRow.getData(), undefined, toRow);
21214
toRow.delete();
21215
return true;
21216
}
21217
21218
return false;
21219
},
21220
};
21221
21222
MoveRows.prototype.senders = {
21223
delete:function(fromRow, toRow, toTable){
21224
fromRow.delete();
21225
}
21226
};
21227
21228
MoveRows.moduleName = "moveRow";
21229
21230
var defaultMutators = {};
21231
21232
class Mutator extends Module{
21233
21234
constructor(table){
21235
super(table);
21236
21237
this.allowedTypes = ["", "data", "edit", "clipboard"]; //list of mutation types
21238
this.enabled = true;
21239
21240
this.registerColumnOption("mutator");
21241
this.registerColumnOption("mutatorParams");
21242
this.registerColumnOption("mutatorData");
21243
this.registerColumnOption("mutatorDataParams");
21244
this.registerColumnOption("mutatorEdit");
21245
this.registerColumnOption("mutatorEditParams");
21246
this.registerColumnOption("mutatorClipboard");
21247
this.registerColumnOption("mutatorClipboardParams");
21248
this.registerColumnOption("mutateLink");
21249
}
21250
21251
initialize(){
21252
this.subscribe("cell-value-changing", this.transformCell.bind(this));
21253
this.subscribe("cell-value-changed", this.mutateLink.bind(this));
21254
this.subscribe("column-layout", this.initializeColumn.bind(this));
21255
this.subscribe("row-data-init-before", this.rowDataChanged.bind(this));
21256
this.subscribe("row-data-changing", this.rowDataChanged.bind(this));
21257
}
21258
21259
rowDataChanged(row, tempData, updatedData){
21260
return this.transformRow(tempData, "data", updatedData);
21261
}
21262
21263
//initialize column mutator
21264
initializeColumn(column){
21265
var match = false,
21266
config = {};
21267
21268
this.allowedTypes.forEach((type) => {
21269
var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)),
21270
mutator;
21271
21272
if(column.definition[key]){
21273
mutator = this.lookupMutator(column.definition[key]);
21274
21275
if(mutator){
21276
match = true;
21277
21278
config[key] = {
21279
mutator:mutator,
21280
params: column.definition[key + "Params"] || {},
21281
};
21282
}
21283
}
21284
});
21285
21286
if(match){
21287
column.modules.mutate = config;
21288
}
21289
}
21290
21291
lookupMutator(value){
21292
var mutator = false;
21293
21294
//set column mutator
21295
switch(typeof value){
21296
case "string":
21297
if(Mutator.mutators[value]){
21298
mutator = Mutator.mutators[value];
21299
}else {
21300
console.warn("Mutator Error - No such mutator found, ignoring: ", value);
21301
}
21302
break;
21303
21304
case "function":
21305
mutator = value;
21306
break;
21307
}
21308
21309
return mutator;
21310
}
21311
21312
//apply mutator to row
21313
transformRow(data, type, updatedData){
21314
var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)),
21315
value;
21316
21317
if(this.enabled){
21318
21319
this.table.columnManager.traverse((column) => {
21320
var mutator, params, component;
21321
21322
if(column.modules.mutate){
21323
mutator = column.modules.mutate[key] || column.modules.mutate.mutator || false;
21324
21325
if(mutator){
21326
value = column.getFieldValue(typeof updatedData !== "undefined" ? updatedData : data);
21327
21328
if((type == "data" && !updatedData)|| typeof value !== "undefined"){
21329
component = column.getComponent();
21330
params = typeof mutator.params === "function" ? mutator.params(value, data, type, component) : mutator.params;
21331
column.setFieldValue(data, mutator.mutator(value, data, type, params, component));
21332
}
21333
}
21334
}
21335
});
21336
}
21337
21338
return data;
21339
}
21340
21341
//apply mutator to new cell value
21342
transformCell(cell, value){
21343
if(cell.column.modules.mutate){
21344
var mutator = cell.column.modules.mutate.mutatorEdit || cell.column.modules.mutate.mutator || false,
21345
tempData = {};
21346
21347
if(mutator){
21348
tempData = Object.assign(tempData, cell.row.getData());
21349
cell.column.setFieldValue(tempData, value);
21350
return mutator.mutator(value, tempData, "edit", mutator.params, cell.getComponent());
21351
}
21352
}
21353
21354
return value;
21355
}
21356
21357
mutateLink(cell){
21358
var links = cell.column.definition.mutateLink;
21359
21360
if(links){
21361
if(!Array.isArray(links)){
21362
links = [links];
21363
}
21364
21365
links.forEach((link) => {
21366
var linkCell = cell.row.getCell(link);
21367
21368
if(linkCell){
21369
linkCell.setValue(linkCell.getValue(), true, true);
21370
}
21371
});
21372
}
21373
}
21374
21375
enable(){
21376
this.enabled = true;
21377
}
21378
21379
disable(){
21380
this.enabled = false;
21381
}
21382
}
21383
21384
Mutator.moduleName = "mutator";
21385
21386
//load defaults
21387
Mutator.mutators = defaultMutators;
21388
21389
function rows(pageSize, currentRow, currentPage, totalRows, totalPages){
21390
var el = document.createElement("span"),
21391
showingEl = document.createElement("span"),
21392
valueEl = document.createElement("span"),
21393
ofEl = document.createElement("span"),
21394
totalEl = document.createElement("span"),
21395
rowsEl = document.createElement("span");
21396
21397
this.table.modules.localize.langBind("pagination|counter|showing", (value) => {
21398
showingEl.innerHTML = value;
21399
});
21400
21401
this.table.modules.localize.langBind("pagination|counter|of", (value) => {
21402
ofEl.innerHTML = value;
21403
});
21404
21405
this.table.modules.localize.langBind("pagination|counter|rows", (value) => {
21406
rowsEl.innerHTML = value;
21407
});
21408
21409
if(totalRows){
21410
valueEl.innerHTML = " " + currentRow + "-" + Math.min((currentRow + pageSize - 1), totalRows) + " ";
21411
21412
totalEl.innerHTML = " " + totalRows + " ";
21413
21414
el.appendChild(showingEl);
21415
el.appendChild(valueEl);
21416
el.appendChild(ofEl);
21417
el.appendChild(totalEl);
21418
el.appendChild(rowsEl);
21419
}else {
21420
valueEl.innerHTML = " 0 ";
21421
21422
el.appendChild(showingEl);
21423
el.appendChild(valueEl);
21424
el.appendChild(rowsEl);
21425
}
21426
21427
return el;
21428
}
21429
21430
function pages(pageSize, currentRow, currentPage, totalRows, totalPages){
21431
21432
var el = document.createElement("span"),
21433
showingEl = document.createElement("span"),
21434
valueEl = document.createElement("span"),
21435
ofEl = document.createElement("span"),
21436
totalEl = document.createElement("span"),
21437
rowsEl = document.createElement("span");
21438
21439
this.table.modules.localize.langBind("pagination|counter|showing", (value) => {
21440
showingEl.innerHTML = value;
21441
});
21442
21443
valueEl.innerHTML = " " + currentPage + " ";
21444
21445
this.table.modules.localize.langBind("pagination|counter|of", (value) => {
21446
ofEl.innerHTML = value;
21447
});
21448
21449
totalEl.innerHTML = " " + totalPages + " ";
21450
21451
this.table.modules.localize.langBind("pagination|counter|pages", (value) => {
21452
rowsEl.innerHTML = value;
21453
});
21454
21455
el.appendChild(showingEl);
21456
el.appendChild(valueEl);
21457
el.appendChild(ofEl);
21458
el.appendChild(totalEl);
21459
el.appendChild(rowsEl);
21460
21461
return el;
21462
}
21463
21464
var defaultPageCounters = {
21465
rows:rows,
21466
pages:pages,
21467
};
21468
21469
class Page extends Module{
21470
21471
constructor(table){
21472
super(table);
21473
21474
this.mode = "local";
21475
this.progressiveLoad = false;
21476
21477
this.element = null;
21478
this.pageCounterElement = null;
21479
this.pageCounter = null;
21480
21481
this.size = 0;
21482
this.page = 1;
21483
this.count = 5;
21484
this.max = 1;
21485
21486
this.remoteRowCountEstimate = null;
21487
21488
this.initialLoad = true;
21489
this.dataChanging = false; //flag to check if data is being changed by this module
21490
21491
this.pageSizes = [];
21492
21493
this.registerTableOption("pagination", false); //set pagination type
21494
this.registerTableOption("paginationMode", "local"); //local or remote pagination
21495
this.registerTableOption("paginationSize", false); //set number of rows to a page
21496
this.registerTableOption("paginationInitialPage", 1); //initial page to show on load
21497
this.registerTableOption("paginationCounter", false); // set pagination counter
21498
this.registerTableOption("paginationCounterElement", false); // set pagination counter
21499
this.registerTableOption("paginationButtonCount", 5); // set count of page button
21500
this.registerTableOption("paginationSizeSelector", false); //add pagination size selector element
21501
this.registerTableOption("paginationElement", false); //element to hold pagination numbers
21502
// this.registerTableOption("paginationDataSent", {}); //pagination data sent to the server
21503
// this.registerTableOption("paginationDataReceived", {}); //pagination data received from the server
21504
this.registerTableOption("paginationAddRow", "page"); //add rows on table or page
21505
21506
this.registerTableOption("progressiveLoad", false); //progressive loading
21507
this.registerTableOption("progressiveLoadDelay", 0); //delay between requests
21508
this.registerTableOption("progressiveLoadScrollMargin", 0); //margin before scroll begins
21509
21510
this.registerTableFunction("setMaxPage", this.setMaxPage.bind(this));
21511
this.registerTableFunction("setPage", this.setPage.bind(this));
21512
this.registerTableFunction("setPageToRow", this.userSetPageToRow.bind(this));
21513
this.registerTableFunction("setPageSize", this.userSetPageSize.bind(this));
21514
this.registerTableFunction("getPageSize", this.getPageSize.bind(this));
21515
this.registerTableFunction("previousPage", this.previousPage.bind(this));
21516
this.registerTableFunction("nextPage", this.nextPage.bind(this));
21517
this.registerTableFunction("getPage", this.getPage.bind(this));
21518
this.registerTableFunction("getPageMax", this.getPageMax.bind(this));
21519
21520
//register component functions
21521
this.registerComponentFunction("row", "pageTo", this.setPageToRow.bind(this));
21522
}
21523
21524
initialize(){
21525
if(this.table.options.pagination){
21526
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
21527
this.subscribe("row-added", this.rowsUpdated.bind(this));
21528
this.subscribe("data-processed", this.initialLoadComplete.bind(this));
21529
this.subscribe("table-built", this.calculatePageSizes.bind(this));
21530
this.subscribe("footer-redraw", this.footerRedraw.bind(this));
21531
21532
if(this.table.options.paginationAddRow == "page"){
21533
this.subscribe("row-adding-position", this.rowAddingPosition.bind(this));
21534
}
21535
21536
if(this.table.options.paginationMode === "remote"){
21537
this.subscribe("data-params", this.remotePageParams.bind(this));
21538
this.subscribe("data-loaded", this._parseRemoteData.bind(this));
21539
}
21540
21541
if(this.table.options.progressiveLoad){
21542
console.error("Progressive Load Error - Pagination and progressive load cannot be used at the same time");
21543
}
21544
21545
this.registerDisplayHandler(this.restOnRenderBefore.bind(this), 40);
21546
this.registerDisplayHandler(this.getRows.bind(this), 50);
21547
21548
this.createElements();
21549
this.initializePageCounter();
21550
this.initializePaginator();
21551
}else if(this.table.options.progressiveLoad){
21552
this.subscribe("data-params", this.remotePageParams.bind(this));
21553
this.subscribe("data-loaded", this._parseRemoteData.bind(this));
21554
this.subscribe("table-built", this.calculatePageSizes.bind(this));
21555
this.subscribe("data-processed", this.initialLoadComplete.bind(this));
21556
21557
this.initializeProgressive(this.table.options.progressiveLoad);
21558
21559
if(this.table.options.progressiveLoad === "scroll"){
21560
this.subscribe("scroll-vertical", this.scrollVertical.bind(this));
21561
}
21562
}
21563
}
21564
21565
rowAddingPosition(row, top){
21566
var rowManager = this.table.rowManager,
21567
displayRows = rowManager.getDisplayRows(),
21568
index;
21569
21570
if(top){
21571
if(displayRows.length){
21572
index = displayRows[0];
21573
}else {
21574
if(rowManager.activeRows.length){
21575
index = rowManager.activeRows[rowManager.activeRows.length-1];
21576
top = false;
21577
}
21578
}
21579
}else {
21580
if(displayRows.length){
21581
index = displayRows[displayRows.length - 1];
21582
top = displayRows.length < this.size ? false : true;
21583
}
21584
}
21585
21586
return {index, top};
21587
}
21588
21589
calculatePageSizes(){
21590
var testElRow, testElCell;
21591
21592
if(this.table.options.paginationSize){
21593
this.size = this.table.options.paginationSize;
21594
}else {
21595
testElRow = document.createElement("div");
21596
testElRow.classList.add("tabulator-row");
21597
testElRow.style.visibility = "hidden";
21598
21599
testElCell = document.createElement("div");
21600
testElCell.classList.add("tabulator-cell");
21601
testElCell.innerHTML = "Page Row Test";
21602
21603
testElRow.appendChild(testElCell);
21604
21605
this.table.rowManager.getTableElement().appendChild(testElRow);
21606
21607
this.size = Math.floor(this.table.rowManager.getElement().clientHeight / testElRow.offsetHeight);
21608
21609
this.table.rowManager.getTableElement().removeChild(testElRow);
21610
}
21611
21612
this.dispatchExternal("pageSizeChanged", this.size);
21613
21614
this.generatePageSizeSelectList();
21615
}
21616
21617
initialLoadComplete(){
21618
this.initialLoad = false;
21619
}
21620
21621
remotePageParams(data, config, silent, params){
21622
if(!this.initialLoad){
21623
if((this.progressiveLoad && !silent) || (!this.progressiveLoad && !this.dataChanging)){
21624
this.reset(true);
21625
}
21626
}
21627
21628
//configure request params
21629
params.page = this.page;
21630
21631
//set page size if defined
21632
if(this.size){
21633
params.size = this.size;
21634
}
21635
21636
return params;
21637
}
21638
21639
///////////////////////////////////
21640
///////// Table Functions /////////
21641
///////////////////////////////////
21642
21643
userSetPageToRow(row){
21644
if(this.table.options.pagination){
21645
row = this.rowManager.findRow(row);
21646
21647
if(row){
21648
return this.setPageToRow(row);
21649
}
21650
}
21651
21652
return Promise.reject();
21653
}
21654
21655
userSetPageSize(size){
21656
if(this.table.options.pagination){
21657
this.setPageSize(size);
21658
return this.setPage(1);
21659
}else {
21660
return false;
21661
}
21662
}
21663
///////////////////////////////////
21664
///////// Internal Logic //////////
21665
///////////////////////////////////
21666
21667
scrollVertical(top, dir){
21668
var element, diff, margin;
21669
if(!dir && !this.table.dataLoader.loading){
21670
element = this.table.rowManager.getElement();
21671
diff = element.scrollHeight - element.clientHeight - top;
21672
margin = this.table.options.progressiveLoadScrollMargin || (element.clientHeight * 2);
21673
21674
if(diff < margin){
21675
this.nextPage()
21676
.catch(() => {}); //consume the exception thrown when on the last page
21677
}
21678
}
21679
}
21680
21681
restOnRenderBefore(rows, renderInPosition){
21682
if(!renderInPosition){
21683
if(this.mode === "local"){
21684
this.reset();
21685
}
21686
}
21687
21688
return rows;
21689
}
21690
21691
rowsUpdated(){
21692
this.refreshData(true, "all");
21693
}
21694
21695
createElements(){
21696
var button;
21697
21698
this.element = document.createElement("span");
21699
this.element.classList.add("tabulator-paginator");
21700
21701
this.pagesElement = document.createElement("span");
21702
this.pagesElement.classList.add("tabulator-pages");
21703
21704
button = document.createElement("button");
21705
button.classList.add("tabulator-page");
21706
button.setAttribute("type", "button");
21707
button.setAttribute("role", "button");
21708
button.setAttribute("aria-label", "");
21709
button.setAttribute("title", "");
21710
21711
this.firstBut = button.cloneNode(true);
21712
this.firstBut.setAttribute("data-page", "first");
21713
21714
this.prevBut = button.cloneNode(true);
21715
this.prevBut.setAttribute("data-page", "prev");
21716
21717
this.nextBut = button.cloneNode(true);
21718
this.nextBut.setAttribute("data-page", "next");
21719
21720
this.lastBut = button.cloneNode(true);
21721
this.lastBut.setAttribute("data-page", "last");
21722
21723
if(this.table.options.paginationSizeSelector){
21724
this.pageSizeSelect = document.createElement("select");
21725
this.pageSizeSelect.classList.add("tabulator-page-size");
21726
}
21727
}
21728
21729
generatePageSizeSelectList(){
21730
var pageSizes = [];
21731
21732
if(this.pageSizeSelect){
21733
21734
if(Array.isArray(this.table.options.paginationSizeSelector)){
21735
pageSizes = this.table.options.paginationSizeSelector;
21736
this.pageSizes = pageSizes;
21737
21738
if(this.pageSizes.indexOf(this.size) == -1){
21739
pageSizes.unshift(this.size);
21740
}
21741
}else {
21742
21743
if(this.pageSizes.indexOf(this.size) == -1){
21744
pageSizes = [];
21745
21746
for (let i = 1; i < 5; i++){
21747
pageSizes.push(this.size * i);
21748
}
21749
21750
this.pageSizes = pageSizes;
21751
}else {
21752
pageSizes = this.pageSizes;
21753
}
21754
}
21755
21756
while(this.pageSizeSelect.firstChild) this.pageSizeSelect.removeChild(this.pageSizeSelect.firstChild);
21757
21758
pageSizes.forEach((item) => {
21759
var itemEl = document.createElement("option");
21760
itemEl.value = item;
21761
21762
if(item === true){
21763
this.langBind("pagination|all", function(value){
21764
itemEl.innerHTML = value;
21765
});
21766
}else {
21767
itemEl.innerHTML = item;
21768
}
21769
21770
21771
21772
this.pageSizeSelect.appendChild(itemEl);
21773
});
21774
21775
this.pageSizeSelect.value = this.size;
21776
}
21777
}
21778
21779
initializePageCounter(){
21780
var counter = this.table.options.paginationCounter,
21781
pageCounter = null;
21782
21783
if(counter){
21784
if(typeof counter === "function"){
21785
pageCounter = counter;
21786
}else {
21787
pageCounter = Page.pageCounters[counter];
21788
}
21789
21790
if(pageCounter){
21791
this.pageCounter = pageCounter;
21792
21793
this.pageCounterElement = document.createElement("span");
21794
this.pageCounterElement.classList.add("tabulator-page-counter");
21795
}else {
21796
console.warn("Pagination Error - No such page counter found: ", counter);
21797
}
21798
}
21799
}
21800
21801
//setup pagination
21802
initializePaginator(hidden){
21803
var pageSelectLabel, paginationCounterHolder;
21804
21805
if(!hidden){
21806
//build pagination element
21807
21808
//bind localizations
21809
this.langBind("pagination|first", (value) => {
21810
this.firstBut.innerHTML = value;
21811
});
21812
21813
this.langBind("pagination|first_title", (value) => {
21814
this.firstBut.setAttribute("aria-label", value);
21815
this.firstBut.setAttribute("title", value);
21816
});
21817
21818
this.langBind("pagination|prev", (value) => {
21819
this.prevBut.innerHTML = value;
21820
});
21821
21822
this.langBind("pagination|prev_title", (value) => {
21823
this.prevBut.setAttribute("aria-label", value);
21824
this.prevBut.setAttribute("title", value);
21825
});
21826
21827
this.langBind("pagination|next", (value) => {
21828
this.nextBut.innerHTML = value;
21829
});
21830
21831
this.langBind("pagination|next_title", (value) => {
21832
this.nextBut.setAttribute("aria-label", value);
21833
this.nextBut.setAttribute("title", value);
21834
});
21835
21836
this.langBind("pagination|last", (value) => {
21837
this.lastBut.innerHTML = value;
21838
});
21839
21840
this.langBind("pagination|last_title", (value) => {
21841
this.lastBut.setAttribute("aria-label", value);
21842
this.lastBut.setAttribute("title", value);
21843
});
21844
21845
//click bindings
21846
this.firstBut.addEventListener("click", () => {
21847
this.setPage(1);
21848
});
21849
21850
this.prevBut.addEventListener("click", () => {
21851
this.previousPage();
21852
});
21853
21854
this.nextBut.addEventListener("click", () => {
21855
this.nextPage();
21856
});
21857
21858
this.lastBut.addEventListener("click", () => {
21859
this.setPage(this.max);
21860
});
21861
21862
if(this.table.options.paginationElement){
21863
this.element = this.table.options.paginationElement;
21864
}
21865
21866
if(this.pageSizeSelect){
21867
pageSelectLabel = document.createElement("label");
21868
21869
this.langBind("pagination|page_size", (value) => {
21870
this.pageSizeSelect.setAttribute("aria-label", value);
21871
this.pageSizeSelect.setAttribute("title", value);
21872
pageSelectLabel.innerHTML = value;
21873
});
21874
21875
this.element.appendChild(pageSelectLabel);
21876
this.element.appendChild(this.pageSizeSelect);
21877
21878
this.pageSizeSelect.addEventListener("change", (e) => {
21879
this.setPageSize(this.pageSizeSelect.value == "true" ? true : this.pageSizeSelect.value);
21880
this.setPage(1);
21881
});
21882
}
21883
21884
//append to DOM
21885
this.element.appendChild(this.firstBut);
21886
this.element.appendChild(this.prevBut);
21887
this.element.appendChild(this.pagesElement);
21888
this.element.appendChild(this.nextBut);
21889
this.element.appendChild(this.lastBut);
21890
21891
if(!this.table.options.paginationElement){
21892
if(this.table.options.paginationCounter){
21893
21894
if(this.table.options.paginationCounterElement){
21895
if(this.table.options.paginationCounterElement instanceof HTMLElement){
21896
this.table.options.paginationCounterElement.appendChild(this.pageCounterElement);
21897
}else if(typeof this.table.options.paginationCounterElement === "string"){
21898
paginationCounterHolder = document.querySelector(this.table.options.paginationCounterElement);
21899
21900
if(paginationCounterHolder){
21901
paginationCounterHolder.appendChild(this.pageCounterElement);
21902
}else {
21903
console.warn("Pagination Error - Unable to find element matching paginationCounterElement selector:", this.table.options.paginationCounterElement);
21904
}
21905
}
21906
}else {
21907
this.footerAppend(this.pageCounterElement);
21908
}
21909
21910
}
21911
21912
this.footerAppend(this.element);
21913
}
21914
21915
this.page = this.table.options.paginationInitialPage;
21916
this.count = this.table.options.paginationButtonCount;
21917
}
21918
21919
//set default values
21920
this.mode = this.table.options.paginationMode;
21921
}
21922
21923
initializeProgressive(mode){
21924
this.initializePaginator(true);
21925
this.mode = "progressive_" + mode;
21926
this.progressiveLoad = true;
21927
}
21928
21929
trackChanges(){
21930
this.dispatch("page-changed");
21931
}
21932
21933
//calculate maximum page from number of rows
21934
setMaxRows(rowCount){
21935
if(!rowCount){
21936
this.max = 1;
21937
}else {
21938
this.max = this.size === true ? 1 : Math.ceil(rowCount/this.size);
21939
}
21940
21941
if(this.page > this.max){
21942
this.page = this.max;
21943
}
21944
}
21945
21946
//reset to first page without triggering action
21947
reset(force){
21948
if(!this.initialLoad){
21949
if(this.mode == "local" || force){
21950
this.page = 1;
21951
this.trackChanges();
21952
}
21953
}
21954
}
21955
21956
//set the maximum page
21957
setMaxPage(max){
21958
21959
max = parseInt(max);
21960
21961
this.max = max || 1;
21962
21963
if(this.page > this.max){
21964
this.page = this.max;
21965
this.trigger();
21966
}
21967
}
21968
21969
//set current page number
21970
setPage(page){
21971
switch(page){
21972
case "first":
21973
return this.setPage(1);
21974
21975
case "prev":
21976
return this.previousPage();
21977
21978
case "next":
21979
return this.nextPage();
21980
21981
case "last":
21982
return this.setPage(this.max);
21983
}
21984
21985
page = parseInt(page);
21986
21987
if((page > 0 && page <= this.max) || this.mode !== "local"){
21988
this.page = page;
21989
21990
this.trackChanges();
21991
21992
return this.trigger();
21993
}else {
21994
console.warn("Pagination Error - Requested page is out of range of 1 - " + this.max + ":", page);
21995
return Promise.reject();
21996
}
21997
}
21998
21999
setPageToRow(row){
22000
var rows = this.displayRows(-1);
22001
var index = rows.indexOf(row);
22002
22003
if(index > -1){
22004
var page = this.size === true ? 1 : Math.ceil((index + 1) / this.size);
22005
22006
return this.setPage(page);
22007
}else {
22008
console.warn("Pagination Error - Requested row is not visible");
22009
return Promise.reject();
22010
}
22011
}
22012
22013
setPageSize(size){
22014
if(size !== true){
22015
size = parseInt(size);
22016
}
22017
22018
if(size > 0){
22019
this.size = size;
22020
this.dispatchExternal("pageSizeChanged", size);
22021
}
22022
22023
if(this.pageSizeSelect){
22024
// this.pageSizeSelect.value = size;
22025
this.generatePageSizeSelectList();
22026
}
22027
22028
this.trackChanges();
22029
}
22030
22031
_setPageCounter(totalRows, size, currentRow){
22032
var content;
22033
22034
if(this.pageCounter){
22035
22036
if(this.mode === "remote"){
22037
size = this.size;
22038
currentRow = ((this.page - 1) * this.size) + 1;
22039
totalRows = this.remoteRowCountEstimate;
22040
}
22041
22042
content = this.pageCounter.call(this, size, currentRow, this.page, totalRows, this.max);
22043
22044
switch(typeof content){
22045
case "object":
22046
if(content instanceof Node){
22047
22048
//clear previous cell contents
22049
while(this.pageCounterElement.firstChild) this.pageCounterElement.removeChild(this.pageCounterElement.firstChild);
22050
22051
this.pageCounterElement.appendChild(content);
22052
}else {
22053
this.pageCounterElement.innerHTML = "";
22054
22055
if(content != null){
22056
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);
22057
}
22058
}
22059
break;
22060
case "undefined":
22061
this.pageCounterElement.innerHTML = "";
22062
break;
22063
default:
22064
this.pageCounterElement.innerHTML = content;
22065
}
22066
}
22067
}
22068
22069
//setup the pagination buttons
22070
_setPageButtons(){
22071
let leftSize = Math.floor((this.count-1) / 2);
22072
let rightSize = Math.ceil((this.count-1) / 2);
22073
let min = this.max - this.page + leftSize + 1 < this.count ? this.max-this.count+1: Math.max(this.page-leftSize,1);
22074
let max = this.page <= rightSize? Math.min(this.count, this.max) :Math.min(this.page+rightSize, this.max);
22075
22076
while(this.pagesElement.firstChild) this.pagesElement.removeChild(this.pagesElement.firstChild);
22077
22078
if(this.page == 1){
22079
this.firstBut.disabled = true;
22080
this.prevBut.disabled = true;
22081
}else {
22082
this.firstBut.disabled = false;
22083
this.prevBut.disabled = false;
22084
}
22085
22086
if(this.page == this.max){
22087
this.lastBut.disabled = true;
22088
this.nextBut.disabled = true;
22089
}else {
22090
this.lastBut.disabled = false;
22091
this.nextBut.disabled = false;
22092
}
22093
22094
for(let i = min; i <= max; i++){
22095
if(i>0 && i <= this.max){
22096
this.pagesElement.appendChild(this._generatePageButton(i));
22097
}
22098
}
22099
22100
this.footerRedraw();
22101
}
22102
22103
_generatePageButton(page){
22104
var button = document.createElement("button");
22105
22106
button.classList.add("tabulator-page");
22107
if(page == this.page){
22108
button.classList.add("active");
22109
}
22110
22111
button.setAttribute("type", "button");
22112
button.setAttribute("role", "button");
22113
22114
this.langBind("pagination|page_title", (value) => {
22115
button.setAttribute("aria-label", value + " " + page);
22116
button.setAttribute("title", value + " " + page);
22117
});
22118
22119
button.setAttribute("data-page", page);
22120
button.textContent = page;
22121
22122
button.addEventListener("click", (e) => {
22123
this.setPage(page);
22124
});
22125
22126
return button;
22127
}
22128
22129
//previous page
22130
previousPage(){
22131
if(this.page > 1){
22132
this.page--;
22133
22134
this.trackChanges();
22135
22136
return this.trigger();
22137
22138
}else {
22139
console.warn("Pagination Error - Previous page would be less than page 1:", 0);
22140
return Promise.reject();
22141
}
22142
}
22143
22144
//next page
22145
nextPage(){
22146
if(this.page < this.max){
22147
this.page++;
22148
22149
this.trackChanges();
22150
22151
return this.trigger();
22152
22153
}else {
22154
if(!this.progressiveLoad){
22155
console.warn("Pagination Error - Next page would be greater than maximum page of " + this.max + ":", this.max + 1);
22156
}
22157
return Promise.reject();
22158
}
22159
}
22160
22161
//return current page number
22162
getPage(){
22163
return this.page;
22164
}
22165
22166
//return max page number
22167
getPageMax(){
22168
return this.max;
22169
}
22170
22171
getPageSize(size){
22172
return this.size;
22173
}
22174
22175
getMode(){
22176
return this.mode;
22177
}
22178
22179
//return appropriate rows for current page
22180
getRows(data){
22181
var actualRowPageSize = 0,
22182
output, start, end, actualStartRow;
22183
22184
var actualRows = data.filter((row) => {
22185
return row.type === "row";
22186
});
22187
22188
if(this.mode == "local"){
22189
output = [];
22190
22191
this.setMaxRows(data.length);
22192
22193
if(this.size === true){
22194
start = 0;
22195
end = data.length;
22196
}else {
22197
start = this.size * (this.page - 1);
22198
end = start + parseInt(this.size);
22199
}
22200
22201
this._setPageButtons();
22202
22203
for(let i = start; i < end; i++){
22204
let row = data[i];
22205
22206
if(row){
22207
output.push(row);
22208
22209
if(row.type === "row"){
22210
if(!actualStartRow){
22211
actualStartRow = row;
22212
}
22213
22214
actualRowPageSize++;
22215
}
22216
}
22217
}
22218
22219
this._setPageCounter(actualRows.length, actualRowPageSize, actualStartRow ? (actualRows.indexOf(actualStartRow) + 1) : 0);
22220
22221
return output;
22222
}else {
22223
this._setPageButtons();
22224
this._setPageCounter(actualRows.length);
22225
22226
return data.slice(0);
22227
}
22228
}
22229
22230
trigger(){
22231
var left;
22232
22233
switch(this.mode){
22234
case "local":
22235
left = this.table.rowManager.scrollLeft;
22236
22237
this.refreshData();
22238
this.table.rowManager.scrollHorizontal(left);
22239
22240
this.dispatchExternal("pageLoaded", this.getPage());
22241
22242
return Promise.resolve();
22243
22244
case "remote":
22245
this.dataChanging = true;
22246
return this.reloadData(null)
22247
.finally(() => {
22248
this.dataChanging = false;
22249
});
22250
22251
case "progressive_load":
22252
case "progressive_scroll":
22253
return this.reloadData(null, true);
22254
22255
default:
22256
console.warn("Pagination Error - no such pagination mode:", this.mode);
22257
return Promise.reject();
22258
}
22259
}
22260
22261
_parseRemoteData(data){
22262
var margin;
22263
22264
if(typeof data.last_page === "undefined"){
22265
console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").last_page || "last_page") + "' property");
22266
}
22267
22268
if(data.data){
22269
this.max = parseInt(data.last_page) || 1;
22270
22271
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));
22272
22273
if(this.progressiveLoad){
22274
switch(this.mode){
22275
case "progressive_load":
22276
22277
if(this.page == 1){
22278
this.table.rowManager.setData(data.data, false, this.page == 1);
22279
}else {
22280
this.table.rowManager.addRows(data.data);
22281
}
22282
22283
if(this.page < this.max){
22284
setTimeout(() => {
22285
this.nextPage();
22286
}, this.table.options.progressiveLoadDelay);
22287
}
22288
break;
22289
22290
case "progressive_scroll":
22291
data = this.page === 1 ? data.data : this.table.rowManager.getData().concat(data.data);
22292
22293
this.table.rowManager.setData(data, this.page !== 1, this.page == 1);
22294
22295
margin = this.table.options.progressiveLoadScrollMargin || (this.table.rowManager.element.clientHeight * 2);
22296
22297
if(this.table.rowManager.element.scrollHeight <= (this.table.rowManager.element.clientHeight + margin)){
22298
if(this.page < this.max){
22299
setTimeout(() => {
22300
this.nextPage();
22301
});
22302
}
22303
}
22304
break;
22305
}
22306
22307
return false;
22308
}else {
22309
// left = this.table.rowManager.scrollLeft;
22310
this.dispatchExternal("pageLoaded", this.getPage());
22311
// this.table.rowManager.scrollHorizontal(left);
22312
// this.table.columnManager.scrollHorizontal(left);
22313
}
22314
22315
}else {
22316
console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").data || "data") + "' property");
22317
}
22318
22319
return data.data;
22320
}
22321
22322
//handle the footer element being redrawn
22323
footerRedraw(){
22324
var footer = this.table.footerManager.containerElement;
22325
22326
if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){
22327
this.pagesElement.style.display = 'none';
22328
}else {
22329
this.pagesElement.style.display = '';
22330
22331
if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){
22332
this.pagesElement.style.display = 'none';
22333
}
22334
}
22335
}
22336
}
22337
22338
Page.moduleName = "page";
22339
22340
//load defaults
22341
Page.pageCounters = defaultPageCounters;
22342
22343
// read persistance information from storage
22344
var defaultReaders = {
22345
local:function(id, type){
22346
var data = localStorage.getItem(id + "-" + type);
22347
22348
return data ? JSON.parse(data) : false;
22349
},
22350
cookie:function(id, type){
22351
var cookie = document.cookie,
22352
key = id + "-" + type,
22353
cookiePos = cookie.indexOf(key + "="),
22354
end, data;
22355
22356
//if cookie exists, decode and load column data into tabulator
22357
if(cookiePos > -1){
22358
cookie = cookie.slice(cookiePos);
22359
22360
end = cookie.indexOf(";");
22361
22362
if(end > -1){
22363
cookie = cookie.slice(0, end);
22364
}
22365
22366
data = cookie.replace(key + "=", "");
22367
}
22368
22369
return data ? JSON.parse(data) : false;
22370
}
22371
};
22372
22373
//write persistence information to storage
22374
var defaultWriters = {
22375
local:function(id, type, data){
22376
localStorage.setItem(id + "-" + type, JSON.stringify(data));
22377
},
22378
cookie:function(id, type, data){
22379
var expireDate = new Date();
22380
22381
expireDate.setDate(expireDate.getDate() + 10000);
22382
22383
document.cookie = id + "-" + type + "=" + JSON.stringify(data) + "; expires=" + expireDate.toUTCString();
22384
}
22385
};
22386
22387
class Persistence extends Module{
22388
22389
constructor(table){
22390
super(table);
22391
22392
this.mode = "";
22393
this.id = "";
22394
// this.persistProps = ["field", "width", "visible"];
22395
this.defWatcherBlock = false;
22396
this.config = {};
22397
this.readFunc = false;
22398
this.writeFunc = false;
22399
22400
this.registerTableOption("persistence", false);
22401
this.registerTableOption("persistenceID", ""); //key for persistent storage
22402
this.registerTableOption("persistenceMode", true); //mode for storing persistence information
22403
this.registerTableOption("persistenceReaderFunc", false); //function for handling persistence data reading
22404
this.registerTableOption("persistenceWriterFunc", false); //function for handling persistence data writing
22405
}
22406
22407
// Test for whether localStorage is available for use.
22408
localStorageTest() {
22409
var testKey = "_tabulator_test";
22410
22411
try {
22412
window.localStorage.setItem( testKey, testKey);
22413
window.localStorage.removeItem( testKey );
22414
return true;
22415
} catch(e) {
22416
return false;
22417
}
22418
}
22419
22420
//setup parameters
22421
initialize(){
22422
if(this.table.options.persistence){
22423
//determine persistent layout storage type
22424
var mode = this.table.options.persistenceMode,
22425
id = this.table.options.persistenceID,
22426
retrievedData;
22427
22428
this.mode = mode !== true ? mode : (this.localStorageTest() ? "local" : "cookie");
22429
22430
if(this.table.options.persistenceReaderFunc){
22431
if(typeof this.table.options.persistenceReaderFunc === "function"){
22432
this.readFunc = this.table.options.persistenceReaderFunc;
22433
}else {
22434
if(Persistence.readers[this.table.options.persistenceReaderFunc]){
22435
this.readFunc = Persistence.readers[this.table.options.persistenceReaderFunc];
22436
}else {
22437
console.warn("Persistence Read Error - invalid reader set", this.table.options.persistenceReaderFunc);
22438
}
22439
}
22440
}else {
22441
if(Persistence.readers[this.mode]){
22442
this.readFunc = Persistence.readers[this.mode];
22443
}else {
22444
console.warn("Persistence Read Error - invalid reader set", this.mode);
22445
}
22446
}
22447
22448
if(this.table.options.persistenceWriterFunc){
22449
if(typeof this.table.options.persistenceWriterFunc === "function"){
22450
this.writeFunc = this.table.options.persistenceWriterFunc;
22451
}else {
22452
if(Persistence.writers[this.table.options.persistenceWriterFunc]){
22453
this.writeFunc = Persistence.writers[this.table.options.persistenceWriterFunc];
22454
}else {
22455
console.warn("Persistence Write Error - invalid reader set", this.table.options.persistenceWriterFunc);
22456
}
22457
}
22458
}else {
22459
if(Persistence.writers[this.mode]){
22460
this.writeFunc = Persistence.writers[this.mode];
22461
}else {
22462
console.warn("Persistence Write Error - invalid writer set", this.mode);
22463
}
22464
}
22465
22466
//set storage tag
22467
this.id = "tabulator-" + (id || (this.table.element.getAttribute("id") || ""));
22468
22469
this.config = {
22470
sort:this.table.options.persistence === true || this.table.options.persistence.sort,
22471
filter:this.table.options.persistence === true || this.table.options.persistence.filter,
22472
headerFilter:this.table.options.persistence === true || this.table.options.persistence.headerFilter,
22473
group:this.table.options.persistence === true || this.table.options.persistence.group,
22474
page:this.table.options.persistence === true || this.table.options.persistence.page,
22475
columns:this.table.options.persistence === true ? ["title", "width", "visible"] : this.table.options.persistence.columns,
22476
};
22477
22478
//load pagination data if needed
22479
if(this.config.page){
22480
retrievedData = this.retrieveData("page");
22481
22482
if(retrievedData){
22483
if(typeof retrievedData.paginationSize !== "undefined" && (this.config.page === true || this.config.page.size)){
22484
this.table.options.paginationSize = retrievedData.paginationSize;
22485
}
22486
22487
if(typeof retrievedData.paginationInitialPage !== "undefined" && (this.config.page === true || this.config.page.page)){
22488
this.table.options.paginationInitialPage = retrievedData.paginationInitialPage;
22489
}
22490
}
22491
}
22492
22493
//load group data if needed
22494
if(this.config.group){
22495
retrievedData = this.retrieveData("group");
22496
22497
if(retrievedData){
22498
if(typeof retrievedData.groupBy !== "undefined" && (this.config.group === true || this.config.group.groupBy)){
22499
this.table.options.groupBy = retrievedData.groupBy;
22500
}
22501
if(typeof retrievedData.groupStartOpen !== "undefined" && (this.config.group === true || this.config.group.groupStartOpen)){
22502
this.table.options.groupStartOpen = retrievedData.groupStartOpen;
22503
}
22504
if(typeof retrievedData.groupHeader !== "undefined" && (this.config.group === true || this.config.group.groupHeader)){
22505
this.table.options.groupHeader = retrievedData.groupHeader;
22506
}
22507
}
22508
}
22509
22510
if(this.config.columns){
22511
this.table.options.columns = this.load("columns", this.table.options.columns);
22512
this.subscribe("column-init", this.initializeColumn.bind(this));
22513
this.subscribe("column-show", this.save.bind(this, "columns"));
22514
this.subscribe("column-hide", this.save.bind(this, "columns"));
22515
this.subscribe("column-moved", this.save.bind(this, "columns"));
22516
}
22517
22518
this.subscribe("table-built", this.tableBuilt.bind(this), 0);
22519
22520
this.subscribe("table-redraw", this.tableRedraw.bind(this));
22521
22522
this.subscribe("filter-changed", this.eventSave.bind(this, "filter"));
22523
this.subscribe("filter-changed", this.eventSave.bind(this, "headerFilter"));
22524
this.subscribe("sort-changed", this.eventSave.bind(this, "sort"));
22525
this.subscribe("group-changed", this.eventSave.bind(this, "group"));
22526
this.subscribe("page-changed", this.eventSave.bind(this, "page"));
22527
this.subscribe("column-resized", this.eventSave.bind(this, "columns"));
22528
this.subscribe("column-width", this.eventSave.bind(this, "columns"));
22529
this.subscribe("layout-refreshed", this.eventSave.bind(this, "columns"));
22530
}
22531
22532
this.registerTableFunction("getColumnLayout", this.getColumnLayout.bind(this));
22533
this.registerTableFunction("setColumnLayout", this.setColumnLayout.bind(this));
22534
}
22535
22536
eventSave(type){
22537
if(this.config[type]){
22538
this.save(type);
22539
}
22540
}
22541
22542
tableBuilt(){
22543
var sorters, filters, headerFilters;
22544
22545
if(this.config.sort){
22546
sorters = this.load("sort");
22547
22548
if(!sorters === false){
22549
this.table.options.initialSort = sorters;
22550
}
22551
}
22552
22553
if(this.config.filter){
22554
filters = this.load("filter");
22555
22556
if(!filters === false){
22557
this.table.options.initialFilter = filters;
22558
}
22559
}
22560
if(this.config.headerFilter){
22561
headerFilters = this.load("headerFilter");
22562
22563
if(!headerFilters === false){
22564
this.table.options.initialHeaderFilter = headerFilters;
22565
}
22566
}
22567
22568
}
22569
22570
tableRedraw(force){
22571
if(force && this.config.columns){
22572
this.save("columns");
22573
}
22574
}
22575
22576
///////////////////////////////////
22577
///////// Table Functions /////////
22578
///////////////////////////////////
22579
22580
getColumnLayout(){
22581
return this.parseColumns(this.table.columnManager.getColumns());
22582
}
22583
22584
setColumnLayout(layout){
22585
this.table.columnManager.setColumns(this.mergeDefinition(this.table.options.columns, layout));
22586
return true;
22587
}
22588
22589
///////////////////////////////////
22590
///////// Internal Logic //////////
22591
///////////////////////////////////
22592
22593
initializeColumn(column){
22594
var def, keys;
22595
22596
if(this.config.columns){
22597
this.defWatcherBlock = true;
22598
22599
def = column.getDefinition();
22600
22601
keys = this.config.columns === true ? Object.keys(def) : this.config.columns;
22602
22603
keys.forEach((key)=>{
22604
var props = Object.getOwnPropertyDescriptor(def, key);
22605
var value = def[key];
22606
22607
if(props){
22608
Object.defineProperty(def, key, {
22609
set: (newValue) => {
22610
value = newValue;
22611
22612
if(!this.defWatcherBlock){
22613
this.save("columns");
22614
}
22615
22616
if(props.set){
22617
props.set(newValue);
22618
}
22619
},
22620
get:() => {
22621
if(props.get){
22622
props.get();
22623
}
22624
return value;
22625
}
22626
});
22627
}
22628
});
22629
22630
this.defWatcherBlock = false;
22631
}
22632
}
22633
22634
//load saved definitions
22635
load(type, current){
22636
var data = this.retrieveData(type);
22637
22638
if(current){
22639
data = data ? this.mergeDefinition(current, data) : current;
22640
}
22641
22642
return data;
22643
}
22644
22645
//retrieve data from memory
22646
retrieveData(type){
22647
return this.readFunc ? this.readFunc(this.id, type) : false;
22648
}
22649
22650
//merge old and new column definitions
22651
mergeDefinition(oldCols, newCols){
22652
var output = [];
22653
22654
newCols = newCols || [];
22655
22656
newCols.forEach((column, to) => {
22657
var from = this._findColumn(oldCols, column),
22658
keys;
22659
22660
if(from){
22661
if(this.config.columns === true || this.config.columns == undefined){
22662
keys = Object.keys(from);
22663
keys.push("width");
22664
}else {
22665
keys = this.config.columns;
22666
}
22667
22668
keys.forEach((key)=>{
22669
if(key !== "columns" && typeof column[key] !== "undefined"){
22670
from[key] = column[key];
22671
}
22672
});
22673
22674
if(from.columns){
22675
from.columns = this.mergeDefinition(from.columns, column.columns);
22676
}
22677
22678
output.push(from);
22679
}
22680
});
22681
22682
oldCols.forEach((column, i) => {
22683
var from = this._findColumn(newCols, column);
22684
22685
if (!from) {
22686
if(output.length>i){
22687
output.splice(i, 0, column);
22688
}else {
22689
output.push(column);
22690
}
22691
}
22692
});
22693
22694
return output;
22695
}
22696
22697
//find matching columns
22698
_findColumn(columns, subject){
22699
var type = subject.columns ? "group" : (subject.field ? "field" : "object");
22700
22701
return columns.find(function(col){
22702
switch(type){
22703
case "group":
22704
return col.title === subject.title && col.columns.length === subject.columns.length;
22705
22706
case "field":
22707
return col.field === subject.field;
22708
22709
case "object":
22710
return col === subject;
22711
}
22712
});
22713
}
22714
22715
//save data
22716
save(type){
22717
var data = {};
22718
22719
switch(type){
22720
case "columns":
22721
data = this.parseColumns(this.table.columnManager.getColumns());
22722
break;
22723
22724
case "filter":
22725
data = this.table.modules.filter.getFilters();
22726
break;
22727
22728
case "headerFilter":
22729
data = this.table.modules.filter.getHeaderFilters();
22730
break;
22731
22732
case "sort":
22733
data = this.validateSorters(this.table.modules.sort.getSort());
22734
break;
22735
22736
case "group":
22737
data = this.getGroupConfig();
22738
break;
22739
22740
case "page":
22741
data = this.getPageConfig();
22742
break;
22743
}
22744
22745
if(this.writeFunc){
22746
this.writeFunc(this.id, type, data);
22747
}
22748
22749
}
22750
22751
//ensure sorters contain no function data
22752
validateSorters(data){
22753
data.forEach(function(item){
22754
item.column = item.field;
22755
delete item.field;
22756
});
22757
22758
return data;
22759
}
22760
22761
getGroupConfig(){
22762
var data = {};
22763
22764
if(this.config.group){
22765
if(this.config.group === true || this.config.group.groupBy){
22766
data.groupBy = this.table.options.groupBy;
22767
}
22768
22769
if(this.config.group === true || this.config.group.groupStartOpen){
22770
data.groupStartOpen = this.table.options.groupStartOpen;
22771
}
22772
22773
if(this.config.group === true || this.config.group.groupHeader){
22774
data.groupHeader = this.table.options.groupHeader;
22775
}
22776
}
22777
22778
return data;
22779
}
22780
22781
getPageConfig(){
22782
var data = {};
22783
22784
if(this.config.page){
22785
if(this.config.page === true || this.config.page.size){
22786
data.paginationSize = this.table.modules.page.getPageSize();
22787
}
22788
22789
if(this.config.page === true || this.config.page.page){
22790
data.paginationInitialPage = this.table.modules.page.getPage();
22791
}
22792
}
22793
22794
return data;
22795
}
22796
22797
22798
//parse columns for data to store
22799
parseColumns(columns){
22800
var definitions = [],
22801
excludedKeys = ["headerContextMenu", "headerMenu", "contextMenu", "clickMenu"];
22802
22803
columns.forEach((column) => {
22804
var defStore = {},
22805
colDef = column.getDefinition(),
22806
keys;
22807
22808
if(column.isGroup){
22809
defStore.title = colDef.title;
22810
defStore.columns = this.parseColumns(column.getColumns());
22811
}else {
22812
defStore.field = column.getField();
22813
22814
if(this.config.columns === true || this.config.columns == undefined){
22815
keys = Object.keys(colDef);
22816
keys.push("width");
22817
keys.push("visible");
22818
}else {
22819
keys = this.config.columns;
22820
}
22821
22822
keys.forEach((key)=>{
22823
switch(key){
22824
case "width":
22825
defStore.width = column.getWidth();
22826
break;
22827
case "visible":
22828
defStore.visible = column.visible;
22829
break;
22830
22831
default:
22832
if(typeof colDef[key] !== "function" && excludedKeys.indexOf(key) === -1){
22833
defStore[key] = colDef[key];
22834
}
22835
}
22836
});
22837
}
22838
22839
definitions.push(defStore);
22840
});
22841
22842
return definitions;
22843
}
22844
}
22845
22846
Persistence.moduleName = "persistence";
22847
22848
Persistence.moduleInitOrder = -10;
22849
22850
//load defaults
22851
Persistence.readers = defaultReaders;
22852
Persistence.writers = defaultWriters;
22853
22854
class Popup$1 extends Module{
22855
22856
constructor(table){
22857
super(table);
22858
22859
this.columnSubscribers = {};
22860
22861
this.registerTableOption("rowContextPopup", false);
22862
this.registerTableOption("rowClickPopup", false);
22863
this.registerTableOption("rowDblClickPopup", false);
22864
this.registerTableOption("groupContextPopup", false);
22865
this.registerTableOption("groupClickPopup", false);
22866
this.registerTableOption("groupDblClickPopup", false);
22867
22868
this.registerColumnOption("headerContextPopup");
22869
this.registerColumnOption("headerClickPopup");
22870
this.registerColumnOption("headerDblClickPopup");
22871
this.registerColumnOption("headerPopup");
22872
this.registerColumnOption("headerPopupIcon");
22873
this.registerColumnOption("contextPopup");
22874
this.registerColumnOption("clickPopup");
22875
this.registerColumnOption("dblClickPopup");
22876
22877
this.registerComponentFunction("cell", "popup", this._componentPopupCall.bind(this));
22878
this.registerComponentFunction("column", "popup", this._componentPopupCall.bind(this));
22879
this.registerComponentFunction("row", "popup", this._componentPopupCall.bind(this));
22880
this.registerComponentFunction("group", "popup", this._componentPopupCall.bind(this));
22881
22882
}
22883
22884
initialize(){
22885
this.initializeRowWatchers();
22886
this.initializeGroupWatchers();
22887
22888
this.subscribe("column-init", this.initializeColumn.bind(this));
22889
}
22890
22891
_componentPopupCall(component, contents, position){
22892
this.loadPopupEvent(contents, null, component, position);
22893
}
22894
22895
initializeRowWatchers(){
22896
if(this.table.options.rowContextPopup){
22897
this.subscribe("row-contextmenu", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup));
22898
this.table.on("rowTapHold", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup));
22899
}
22900
22901
if(this.table.options.rowClickPopup){
22902
this.subscribe("row-click", this.loadPopupEvent.bind(this, this.table.options.rowClickPopup));
22903
}
22904
22905
if(this.table.options.rowDblClickPopup){
22906
this.subscribe("row-dblclick", this.loadPopupEvent.bind(this, this.table.options.rowDblClickPopup));
22907
}
22908
}
22909
22910
initializeGroupWatchers(){
22911
if(this.table.options.groupContextPopup){
22912
this.subscribe("group-contextmenu", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup));
22913
this.table.on("groupTapHold", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup));
22914
}
22915
22916
if(this.table.options.groupClickPopup){
22917
this.subscribe("group-click", this.loadPopupEvent.bind(this, this.table.options.groupClickPopup));
22918
}
22919
22920
if(this.table.options.groupDblClickPopup){
22921
this.subscribe("group-dblclick", this.loadPopupEvent.bind(this, this.table.options.groupDblClickPopup));
22922
}
22923
}
22924
22925
initializeColumn(column){
22926
var def = column.definition;
22927
22928
//handle column events
22929
if(def.headerContextPopup && !this.columnSubscribers.headerContextPopup){
22930
this.columnSubscribers.headerContextPopup = this.loadPopupTableColumnEvent.bind(this, "headerContextPopup");
22931
this.subscribe("column-contextmenu", this.columnSubscribers.headerContextPopup);
22932
this.table.on("headerTapHold", this.loadPopupTableColumnEvent.bind(this, "headerContextPopup"));
22933
}
22934
22935
if(def.headerClickPopup && !this.columnSubscribers.headerClickPopup){
22936
this.columnSubscribers.headerClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerClickPopup");
22937
this.subscribe("column-click", this.columnSubscribers.headerClickPopup);
22938
22939
22940
}if(def.headerDblClickPopup && !this.columnSubscribers.headerDblClickPopup){
22941
this.columnSubscribers.headerDblClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerDblClickPopup");
22942
this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickPopup);
22943
}
22944
22945
if(def.headerPopup){
22946
this.initializeColumnHeaderPopup(column);
22947
}
22948
22949
//handle cell events
22950
if(def.contextPopup && !this.columnSubscribers.contextPopup){
22951
this.columnSubscribers.contextPopup = this.loadPopupTableCellEvent.bind(this, "contextPopup");
22952
this.subscribe("cell-contextmenu", this.columnSubscribers.contextPopup);
22953
this.table.on("cellTapHold", this.loadPopupTableCellEvent.bind(this, "contextPopup"));
22954
}
22955
22956
if(def.clickPopup && !this.columnSubscribers.clickPopup){
22957
this.columnSubscribers.clickPopup = this.loadPopupTableCellEvent.bind(this, "clickPopup");
22958
this.subscribe("cell-click", this.columnSubscribers.clickPopup);
22959
}
22960
22961
if(def.dblClickPopup && !this.columnSubscribers.dblClickPopup){
22962
this.columnSubscribers.dblClickPopup = this.loadPopupTableCellEvent.bind(this, "dblClickPopup");
22963
this.subscribe("cell-click", this.columnSubscribers.dblClickPopup);
22964
}
22965
}
22966
22967
initializeColumnHeaderPopup(column){
22968
var icon = column.definition.headerPopupIcon,
22969
headerPopupEl;
22970
22971
headerPopupEl = document.createElement("span");
22972
headerPopupEl.classList.add("tabulator-header-popup-button");
22973
22974
if(icon){
22975
if(typeof icon === "function"){
22976
icon = icon(column.getComponent());
22977
}
22978
22979
if(icon instanceof HTMLElement){
22980
headerPopupEl.appendChild(icon);
22981
}else {
22982
headerPopupEl.innerHTML = icon;
22983
}
22984
}else {
22985
headerPopupEl.innerHTML = "&vellip;";
22986
}
22987
22988
headerPopupEl.addEventListener("click", (e) => {
22989
e.stopPropagation();
22990
e.preventDefault();
22991
22992
this.loadPopupEvent(column.definition.headerPopup, e, column);
22993
});
22994
22995
column.titleElement.insertBefore(headerPopupEl, column.titleElement.firstChild);
22996
}
22997
22998
loadPopupTableCellEvent(option, e, cell){
22999
if(cell._cell){
23000
cell = cell._cell;
23001
}
23002
23003
if(cell.column.definition[option]){
23004
this.loadPopupEvent(cell.column.definition[option], e, cell);
23005
}
23006
}
23007
23008
loadPopupTableColumnEvent(option, e, column){
23009
if(column._column){
23010
column = column._column;
23011
}
23012
23013
if(column.definition[option]){
23014
this.loadPopupEvent(column.definition[option], e, column);
23015
}
23016
}
23017
23018
loadPopupEvent(contents, e, component, position){
23019
var renderedCallback;
23020
23021
function onRendered(callback){
23022
renderedCallback = callback;
23023
}
23024
23025
if(component._group){
23026
component = component._group;
23027
}else if(component._row){
23028
component = component._row;
23029
}
23030
23031
contents = typeof contents == "function" ? contents.call(this.table, e, component.getComponent(), onRendered) : contents;
23032
23033
this.loadPopup(e, component, contents, renderedCallback, position);
23034
}
23035
23036
loadPopup(e, component, contents, renderedCallback, position){
23037
var touch = !(e instanceof MouseEvent),
23038
contentsEl, popup;
23039
23040
if(contents instanceof HTMLElement){
23041
contentsEl = contents;
23042
}else {
23043
contentsEl = document.createElement("div");
23044
contentsEl.innerHTML = contents;
23045
}
23046
23047
contentsEl.classList.add("tabulator-popup");
23048
23049
contentsEl.addEventListener("click", (e) =>{
23050
e.stopPropagation();
23051
});
23052
23053
if(!touch){
23054
e.preventDefault();
23055
}
23056
23057
popup = this.popup(contentsEl);
23058
23059
if(typeof renderedCallback === "function"){
23060
popup.renderCallback(renderedCallback);
23061
}
23062
23063
if(e){
23064
popup.show(e);
23065
}else {
23066
popup.show(component.getElement(), position || "center");
23067
}
23068
23069
23070
popup.hideOnBlur(() => {
23071
this.dispatchExternal("popupClosed", component.getComponent());
23072
});
23073
23074
23075
23076
this.dispatchExternal("popupOpened", component.getComponent());
23077
}
23078
}
23079
23080
Popup$1.moduleName = "popup";
23081
23082
class Print extends Module{
23083
23084
constructor(table){
23085
super(table);
23086
23087
this.element = false;
23088
this.manualBlock = false;
23089
this.beforeprintEventHandler = null;
23090
this.afterprintEventHandler = null;
23091
23092
this.registerTableOption("printAsHtml", false); //enable print as html
23093
this.registerTableOption("printFormatter", false); //printing page formatter
23094
this.registerTableOption("printHeader", false); //page header contents
23095
this.registerTableOption("printFooter", false); //page footer contents
23096
this.registerTableOption("printStyled", true); //enable print as html styling
23097
this.registerTableOption("printRowRange", "visible"); //restrict print to visible rows only
23098
this.registerTableOption("printConfig", {}); //print config options
23099
23100
this.registerColumnOption("print");
23101
this.registerColumnOption("titlePrint");
23102
}
23103
23104
initialize(){
23105
if(this.table.options.printAsHtml){
23106
this.beforeprintEventHandler = this.replaceTable.bind(this);
23107
this.afterprintEventHandler = this.cleanup.bind(this);
23108
23109
window.addEventListener("beforeprint", this.beforeprintEventHandler );
23110
window.addEventListener("afterprint", this.afterprintEventHandler);
23111
this.subscribe("table-destroy", this.destroy.bind(this));
23112
}
23113
23114
this.registerTableFunction("print", this.printFullscreen.bind(this));
23115
}
23116
23117
destroy(){
23118
if(this.table.options.printAsHtml){
23119
window.removeEventListener( "beforeprint", this.beforeprintEventHandler );
23120
window.removeEventListener( "afterprint", this.afterprintEventHandler );
23121
}
23122
}
23123
23124
///////////////////////////////////
23125
///////// Table Functions /////////
23126
///////////////////////////////////
23127
23128
///////////////////////////////////
23129
///////// Internal Logic //////////
23130
///////////////////////////////////
23131
23132
replaceTable(){
23133
if(!this.manualBlock){
23134
this.element = document.createElement("div");
23135
this.element.classList.add("tabulator-print-table");
23136
23137
this.element.appendChild(this.table.modules.export.generateTable(this.table.options.printConfig, this.table.options.printStyled, this.table.options.printRowRange, "print"));
23138
23139
this.table.element.style.display = "none";
23140
23141
this.table.element.parentNode.insertBefore(this.element, this.table.element);
23142
}
23143
}
23144
23145
cleanup(){
23146
document.body.classList.remove("tabulator-print-fullscreen-hide");
23147
23148
if(this.element && this.element.parentNode){
23149
this.element.parentNode.removeChild(this.element);
23150
this.table.element.style.display = "";
23151
}
23152
}
23153
23154
printFullscreen(visible, style, config){
23155
var scrollX = window.scrollX,
23156
scrollY = window.scrollY,
23157
headerEl = document.createElement("div"),
23158
footerEl = document.createElement("div"),
23159
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"),
23160
headerContent, footerContent;
23161
23162
this.manualBlock = true;
23163
23164
this.element = document.createElement("div");
23165
this.element.classList.add("tabulator-print-fullscreen");
23166
23167
if(this.table.options.printHeader){
23168
headerEl.classList.add("tabulator-print-header");
23169
23170
headerContent = typeof this.table.options.printHeader == "function" ? this.table.options.printHeader.call(this.table) : this.table.options.printHeader;
23171
23172
if(typeof headerContent == "string"){
23173
headerEl.innerHTML = headerContent;
23174
}else {
23175
headerEl.appendChild(headerContent);
23176
}
23177
23178
this.element.appendChild(headerEl);
23179
}
23180
23181
this.element.appendChild(tableEl);
23182
23183
if(this.table.options.printFooter){
23184
footerEl.classList.add("tabulator-print-footer");
23185
23186
footerContent = typeof this.table.options.printFooter == "function" ? this.table.options.printFooter.call(this.table) : this.table.options.printFooter;
23187
23188
23189
if(typeof footerContent == "string"){
23190
footerEl.innerHTML = footerContent;
23191
}else {
23192
footerEl.appendChild(footerContent);
23193
}
23194
23195
this.element.appendChild(footerEl);
23196
}
23197
23198
document.body.classList.add("tabulator-print-fullscreen-hide");
23199
document.body.appendChild(this.element);
23200
23201
if(this.table.options.printFormatter){
23202
this.table.options.printFormatter(this.element, tableEl);
23203
}
23204
23205
window.print();
23206
23207
this.cleanup();
23208
23209
window.scrollTo(scrollX, scrollY);
23210
23211
this.manualBlock = false;
23212
}
23213
}
23214
23215
Print.moduleName = "print";
23216
23217
class ReactiveData extends Module{
23218
23219
constructor(table){
23220
super(table);
23221
23222
this.data = false;
23223
this.blocked = false; //block reactivity while performing update
23224
this.origFuncs = {}; // hold original data array functions to allow replacement after data is done with
23225
this.currentVersion = 0;
23226
23227
this.registerTableOption("reactiveData", false); //enable data reactivity
23228
}
23229
23230
initialize(){
23231
if(this.table.options.reactiveData){
23232
this.subscribe("cell-value-save-before", this.block.bind(this, "cellsave"));
23233
this.subscribe("cell-value-save-after", this.unblock.bind(this, "cellsave"));
23234
this.subscribe("row-data-save-before", this.block.bind(this, "rowsave"));
23235
this.subscribe("row-data-save-after", this.unblock.bind(this, "rowsave"));
23236
this.subscribe("row-data-init-after", this.watchRow.bind(this));
23237
this.subscribe("data-processing", this.watchData.bind(this));
23238
this.subscribe("table-destroy", this.unwatchData.bind(this));
23239
}
23240
}
23241
23242
watchData(data){
23243
var self = this,
23244
version;
23245
23246
this.currentVersion ++;
23247
23248
version = this.currentVersion;
23249
23250
this.unwatchData();
23251
23252
this.data = data;
23253
23254
//override array push function
23255
this.origFuncs.push = data.push;
23256
23257
Object.defineProperty(this.data, "push", {
23258
enumerable: false,
23259
configurable: true,
23260
value: function(){
23261
var args = Array.from(arguments),
23262
result;
23263
23264
if(!self.blocked && version === self.currentVersion){
23265
self.block("data-push");
23266
23267
args.forEach((arg) => {
23268
self.table.rowManager.addRowActual(arg, false);
23269
});
23270
23271
result = self.origFuncs.push.apply(data, arguments);
23272
23273
self.unblock("data-push");
23274
}
23275
23276
return result;
23277
}
23278
});
23279
23280
//override array unshift function
23281
this.origFuncs.unshift = data.unshift;
23282
23283
Object.defineProperty(this.data, "unshift", {
23284
enumerable: false,
23285
configurable: true,
23286
value: function(){
23287
var args = Array.from(arguments),
23288
result;
23289
23290
if(!self.blocked && version === self.currentVersion){
23291
self.block("data-unshift");
23292
23293
args.forEach((arg) => {
23294
self.table.rowManager.addRowActual(arg, true);
23295
});
23296
23297
result = self.origFuncs.unshift.apply(data, arguments);
23298
23299
self.unblock("data-unshift");
23300
}
23301
23302
return result;
23303
}
23304
});
23305
23306
23307
//override array shift function
23308
this.origFuncs.shift = data.shift;
23309
23310
Object.defineProperty(this.data, "shift", {
23311
enumerable: false,
23312
configurable: true,
23313
value: function(){
23314
var row, result;
23315
23316
if(!self.blocked && version === self.currentVersion){
23317
self.block("data-shift");
23318
23319
if(self.data.length){
23320
row = self.table.rowManager.getRowFromDataObject(self.data[0]);
23321
23322
if(row){
23323
row.deleteActual();
23324
}
23325
}
23326
23327
result = self.origFuncs.shift.call(data);
23328
23329
self.unblock("data-shift");
23330
}
23331
23332
return result;
23333
}
23334
});
23335
23336
//override array pop function
23337
this.origFuncs.pop = data.pop;
23338
23339
Object.defineProperty(this.data, "pop", {
23340
enumerable: false,
23341
configurable: true,
23342
value: function(){
23343
var row, result;
23344
23345
if(!self.blocked && version === self.currentVersion){
23346
self.block("data-pop");
23347
23348
if(self.data.length){
23349
row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]);
23350
23351
if(row){
23352
row.deleteActual();
23353
}
23354
}
23355
23356
result = self.origFuncs.pop.call(data);
23357
23358
self.unblock("data-pop");
23359
}
23360
23361
return result;
23362
}
23363
});
23364
23365
23366
//override array splice function
23367
this.origFuncs.splice = data.splice;
23368
23369
Object.defineProperty(this.data, "splice", {
23370
enumerable: false,
23371
configurable: true,
23372
value: function(){
23373
var args = Array.from(arguments),
23374
start = args[0] < 0 ? data.length + args[0] : args[0],
23375
end = args[1],
23376
newRows = args[2] ? args.slice(2) : false,
23377
startRow, result;
23378
23379
if(!self.blocked && version === self.currentVersion){
23380
self.block("data-splice");
23381
//add new rows
23382
if(newRows){
23383
startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false;
23384
23385
if(startRow){
23386
newRows.forEach((rowData) => {
23387
self.table.rowManager.addRowActual(rowData, true, startRow, true);
23388
});
23389
}else {
23390
newRows = newRows.slice().reverse();
23391
23392
newRows.forEach((rowData) => {
23393
self.table.rowManager.addRowActual(rowData, true, false, true);
23394
});
23395
}
23396
}
23397
23398
//delete removed rows
23399
if(end !== 0){
23400
var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end);
23401
23402
oldRows.forEach((rowData, i) => {
23403
var row = self.table.rowManager.getRowFromDataObject(rowData);
23404
23405
if(row){
23406
row.deleteActual(i !== oldRows.length - 1);
23407
}
23408
});
23409
}
23410
23411
if(newRows || end !== 0){
23412
self.table.rowManager.reRenderInPosition();
23413
}
23414
23415
result = self.origFuncs.splice.apply(data, arguments);
23416
23417
self.unblock("data-splice");
23418
}
23419
23420
return result ;
23421
}
23422
});
23423
}
23424
23425
unwatchData(){
23426
if(this.data !== false){
23427
for(var key in this.origFuncs){
23428
Object.defineProperty(this.data, key, {
23429
enumerable: true,
23430
configurable:true,
23431
writable:true,
23432
value: this.origFuncs.key,
23433
});
23434
}
23435
}
23436
}
23437
23438
watchRow(row){
23439
var data = row.getData();
23440
23441
for(var key in data){
23442
this.watchKey(row, data, key);
23443
}
23444
23445
if(this.table.options.dataTree){
23446
this.watchTreeChildren(row);
23447
}
23448
}
23449
23450
watchTreeChildren (row){
23451
var self = this,
23452
childField = row.getData()[this.table.options.dataTreeChildField],
23453
origFuncs = {};
23454
23455
if(childField){
23456
23457
origFuncs.push = childField.push;
23458
23459
Object.defineProperty(childField, "push", {
23460
enumerable: false,
23461
configurable: true,
23462
value: () => {
23463
if(!self.blocked){
23464
self.block("tree-push");
23465
23466
var result = origFuncs.push.apply(childField, arguments);
23467
this.rebuildTree(row);
23468
23469
self.unblock("tree-push");
23470
}
23471
23472
return result;
23473
}
23474
});
23475
23476
origFuncs.unshift = childField.unshift;
23477
23478
Object.defineProperty(childField, "unshift", {
23479
enumerable: false,
23480
configurable: true,
23481
value: () => {
23482
if(!self.blocked){
23483
self.block("tree-unshift");
23484
23485
var result = origFuncs.unshift.apply(childField, arguments);
23486
this.rebuildTree(row);
23487
23488
self.unblock("tree-unshift");
23489
}
23490
23491
return result;
23492
}
23493
});
23494
23495
origFuncs.shift = childField.shift;
23496
23497
Object.defineProperty(childField, "shift", {
23498
enumerable: false,
23499
configurable: true,
23500
value: () => {
23501
if(!self.blocked){
23502
self.block("tree-shift");
23503
23504
var result = origFuncs.shift.call(childField);
23505
this.rebuildTree(row);
23506
23507
self.unblock("tree-shift");
23508
}
23509
23510
return result;
23511
}
23512
});
23513
23514
origFuncs.pop = childField.pop;
23515
23516
Object.defineProperty(childField, "pop", {
23517
enumerable: false,
23518
configurable: true,
23519
value: () => {
23520
if(!self.blocked){
23521
self.block("tree-pop");
23522
23523
var result = origFuncs.pop.call(childField);
23524
this.rebuildTree(row);
23525
23526
self.unblock("tree-pop");
23527
}
23528
23529
return result;
23530
}
23531
});
23532
23533
origFuncs.splice = childField.splice;
23534
23535
Object.defineProperty(childField, "splice", {
23536
enumerable: false,
23537
configurable: true,
23538
value: () => {
23539
if(!self.blocked){
23540
self.block("tree-splice");
23541
23542
var result = origFuncs.splice.apply(childField, arguments);
23543
this.rebuildTree(row);
23544
23545
self.unblock("tree-splice");
23546
}
23547
23548
return result;
23549
}
23550
});
23551
}
23552
}
23553
23554
rebuildTree(row){
23555
this.table.modules.dataTree.initializeRow(row);
23556
this.table.modules.dataTree.layoutRow(row);
23557
this.table.rowManager.refreshActiveData("tree", false, true);
23558
}
23559
23560
watchKey(row, data, key){
23561
var self = this,
23562
props = Object.getOwnPropertyDescriptor(data, key),
23563
value = data[key],
23564
version = this.currentVersion;
23565
23566
Object.defineProperty(data, key, {
23567
set: (newValue) => {
23568
value = newValue;
23569
if(!self.blocked && version === self.currentVersion){
23570
self.block("key");
23571
23572
var update = {};
23573
update[key] = newValue;
23574
row.updateData(update);
23575
23576
self.unblock("key");
23577
}
23578
23579
if(props.set){
23580
props.set(newValue);
23581
}
23582
},
23583
get:() => {
23584
23585
if(props.get){
23586
props.get();
23587
}
23588
23589
return value;
23590
}
23591
});
23592
}
23593
23594
unwatchRow(row){
23595
var data = row.getData();
23596
23597
for(var key in data){
23598
Object.defineProperty(data, key, {
23599
value:data[key],
23600
});
23601
}
23602
}
23603
23604
block(key){
23605
if(!this.blocked){
23606
this.blocked = key;
23607
}
23608
}
23609
23610
unblock(key){
23611
if(this.blocked === key){
23612
this.blocked = false;
23613
}
23614
}
23615
}
23616
23617
ReactiveData.moduleName = "reactiveData";
23618
23619
class ResizeColumns extends Module{
23620
23621
constructor(table){
23622
super(table);
23623
23624
this.startColumn = false;
23625
this.startX = false;
23626
this.startWidth = false;
23627
this.latestX = false;
23628
this.handle = null;
23629
this.initialNextColumn = null;
23630
this.nextColumn = null;
23631
23632
this.initialized = false;
23633
this.registerColumnOption("resizable", true);
23634
this.registerTableOption("resizableColumnFit", false);
23635
}
23636
23637
initialize(){
23638
this.subscribe("column-rendered", this.layoutColumnHeader.bind(this));
23639
}
23640
23641
initializeEventWatchers(){
23642
if(!this.initialized){
23643
23644
this.subscribe("cell-rendered", this.layoutCellHandles.bind(this));
23645
this.subscribe("cell-delete", this.deInitializeComponent.bind(this));
23646
23647
this.subscribe("cell-height", this.resizeHandle.bind(this));
23648
this.subscribe("column-moved", this.columnLayoutUpdated.bind(this));
23649
23650
this.subscribe("column-hide", this.deInitializeColumn.bind(this));
23651
this.subscribe("column-show", this.columnLayoutUpdated.bind(this));
23652
this.subscribe("column-width", this.columnWidthUpdated.bind(this));
23653
23654
this.subscribe("column-delete", this.deInitializeComponent.bind(this));
23655
this.subscribe("column-height", this.resizeHandle.bind(this));
23656
23657
this.initialized = true;
23658
}
23659
}
23660
23661
23662
layoutCellHandles(cell){
23663
if(cell.row.type === "row"){
23664
this.deInitializeComponent(cell);
23665
this.initializeColumn("cell", cell, cell.column, cell.element);
23666
}
23667
}
23668
23669
layoutColumnHeader(column){
23670
if(column.definition.resizable){
23671
this.initializeEventWatchers();
23672
this.deInitializeComponent(column);
23673
this.initializeColumn("header", column, column, column.element);
23674
}
23675
}
23676
23677
columnLayoutUpdated(column){
23678
var prev = column.prevColumn();
23679
23680
this.reinitializeColumn(column);
23681
23682
if(prev){
23683
this.reinitializeColumn(prev);
23684
}
23685
}
23686
23687
columnWidthUpdated(column){
23688
if(column.modules.frozen){
23689
if(this.table.modules.frozenColumns.leftColumns.includes(column)){
23690
this.table.modules.frozenColumns.leftColumns.forEach((col) => {
23691
this.reinitializeColumn(col);
23692
});
23693
}else if(this.table.modules.frozenColumns.rightColumns.includes(column)){
23694
this.table.modules.frozenColumns.rightColumns.forEach((col) => {
23695
this.reinitializeColumn(col);
23696
});
23697
}
23698
}
23699
}
23700
23701
frozenColumnOffset(column){
23702
var offset = false;
23703
23704
if(column.modules.frozen){
23705
offset = column.modules.frozen.marginValue;
23706
23707
if(column.modules.frozen.position === "left"){
23708
offset += column.getWidth() - 3;
23709
}else {
23710
if(offset){
23711
offset -= 3;
23712
}
23713
}
23714
}
23715
23716
return offset !== false ? offset + "px" : false;
23717
}
23718
23719
reinitializeColumn(column){
23720
var frozenOffset = this.frozenColumnOffset(column);
23721
23722
column.cells.forEach((cell) => {
23723
if(cell.modules.resize && cell.modules.resize.handleEl){
23724
if(frozenOffset){
23725
cell.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset;
23726
cell.modules.resize.handleEl.style["z-index"] = 11;
23727
}
23728
23729
cell.element.after(cell.modules.resize.handleEl);
23730
}
23731
});
23732
23733
if(column.modules.resize && column.modules.resize.handleEl){
23734
if(frozenOffset){
23735
column.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset;
23736
}
23737
23738
column.element.after(column.modules.resize.handleEl);
23739
}
23740
}
23741
23742
initializeColumn(type, component, column, element){
23743
var self = this,
23744
variableHeight = false,
23745
mode = column.definition.resizable,
23746
config = {},
23747
nearestColumn = column.getLastColumn();
23748
23749
//set column resize mode
23750
if(type === "header"){
23751
variableHeight = column.definition.formatter == "textarea" || column.definition.variableHeight;
23752
config = {variableHeight:variableHeight};
23753
}
23754
23755
if((mode === true || mode == type) && this._checkResizability(nearestColumn)){
23756
23757
var handle = document.createElement('span');
23758
handle.className = "tabulator-col-resize-handle";
23759
23760
handle.addEventListener("click", function(e){
23761
e.stopPropagation();
23762
});
23763
23764
var handleDown = function(e){
23765
self.startColumn = column;
23766
self.initialNextColumn = self.nextColumn = nearestColumn.nextColumn();
23767
self._mouseDown(e, nearestColumn, handle);
23768
};
23769
23770
handle.addEventListener("mousedown", handleDown);
23771
handle.addEventListener("touchstart", handleDown, {passive: true});
23772
23773
//resize column on double click
23774
handle.addEventListener("dblclick", (e) => {
23775
var oldWidth = nearestColumn.getWidth();
23776
23777
e.stopPropagation();
23778
nearestColumn.reinitializeWidth(true);
23779
23780
if(oldWidth !== nearestColumn.getWidth()){
23781
self.dispatch("column-resized", nearestColumn);
23782
self.table.externalEvents.dispatch("columnResized", nearestColumn.getComponent());
23783
}
23784
});
23785
23786
if(column.modules.frozen){
23787
handle.style.position = "sticky";
23788
handle.style[column.modules.frozen.position] = this.frozenColumnOffset(column);
23789
}
23790
23791
config.handleEl = handle;
23792
23793
if(element.parentNode && column.visible){
23794
element.after(handle);
23795
}
23796
}
23797
23798
component.modules.resize = config;
23799
}
23800
23801
deInitializeColumn(column){
23802
this.deInitializeComponent(column);
23803
23804
column.cells.forEach((cell) => {
23805
this.deInitializeComponent(cell);
23806
});
23807
}
23808
23809
deInitializeComponent(component){
23810
var handleEl;
23811
23812
if(component.modules.resize){
23813
handleEl = component.modules.resize.handleEl;
23814
23815
if(handleEl && handleEl.parentElement){
23816
handleEl.parentElement.removeChild(handleEl);
23817
}
23818
}
23819
}
23820
23821
resizeHandle(component, height){
23822
if(component.modules.resize && component.modules.resize.handleEl){
23823
component.modules.resize.handleEl.style.height = height;
23824
}
23825
}
23826
23827
_checkResizability(column){
23828
return column.definition.resizable;
23829
}
23830
23831
_mouseDown(e, column, handle){
23832
var self = this;
23833
23834
self.table.element.classList.add("tabulator-block-select");
23835
23836
function mouseMove(e){
23837
var x = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX,
23838
startDiff = x - self.startX,
23839
moveDiff = x - self.latestX,
23840
blockedBefore, blockedAfter;
23841
23842
self.latestX = x;
23843
23844
if(self.table.rtl){
23845
startDiff = -startDiff;
23846
moveDiff = -moveDiff;
23847
}
23848
23849
blockedBefore = column.width == column.minWidth || column.width == column.maxWidth;
23850
23851
column.setWidth(self.startWidth + startDiff);
23852
23853
blockedAfter = column.width == column.minWidth || column.width == column.maxWidth;
23854
23855
if(moveDiff < 0){
23856
self.nextColumn = self.initialNextColumn;
23857
}
23858
23859
if(self.table.options.resizableColumnFit && self.nextColumn && !(blockedBefore && blockedAfter)){
23860
let colWidth = self.nextColumn.getWidth();
23861
23862
if(moveDiff > 0){
23863
if(colWidth <= self.nextColumn.minWidth){
23864
self.nextColumn = self.nextColumn.nextColumn();
23865
}
23866
}
23867
23868
if(self.nextColumn){
23869
self.nextColumn.setWidth(self.nextColumn.getWidth() - moveDiff);
23870
}
23871
}
23872
23873
self.table.columnManager.rerenderColumns(true);
23874
23875
if(!self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){
23876
column.checkCellHeights();
23877
}
23878
}
23879
23880
function mouseUp(e){
23881
23882
//block editor from taking action while resizing is taking place
23883
if(self.startColumn.modules.edit){
23884
self.startColumn.modules.edit.blocked = false;
23885
}
23886
23887
if(self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){
23888
column.checkCellHeights();
23889
}
23890
23891
document.body.removeEventListener("mouseup", mouseUp);
23892
document.body.removeEventListener("mousemove", mouseMove);
23893
23894
handle.removeEventListener("touchmove", mouseMove);
23895
handle.removeEventListener("touchend", mouseUp);
23896
23897
self.table.element.classList.remove("tabulator-block-select");
23898
23899
if(self.startWidth !== column.getWidth()){
23900
self.table.columnManager.verticalAlignHeaders();
23901
23902
self.dispatch("column-resized", column);
23903
self.table.externalEvents.dispatch("columnResized", column.getComponent());
23904
}
23905
}
23906
23907
e.stopPropagation(); //prevent resize from interfering with movable columns
23908
23909
//block editor from taking action while resizing is taking place
23910
if(self.startColumn.modules.edit){
23911
self.startColumn.modules.edit.blocked = true;
23912
}
23913
23914
self.startX = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX;
23915
self.latestX = self.startX;
23916
self.startWidth = column.getWidth();
23917
23918
document.body.addEventListener("mousemove", mouseMove);
23919
document.body.addEventListener("mouseup", mouseUp);
23920
handle.addEventListener("touchmove", mouseMove, {passive: true});
23921
handle.addEventListener("touchend", mouseUp);
23922
}
23923
}
23924
23925
ResizeColumns.moduleName = "resizeColumns";
23926
23927
class ResizeRows extends Module{
23928
23929
constructor(table){
23930
super(table);
23931
23932
this.startColumn = false;
23933
this.startY = false;
23934
this.startHeight = false;
23935
this.handle = null;
23936
this.prevHandle = null;
23937
23938
this.registerTableOption("resizableRows", false); //resizable rows
23939
}
23940
23941
initialize(){
23942
if(this.table.options.resizableRows){
23943
this.subscribe("row-layout-after", this.initializeRow.bind(this));
23944
}
23945
}
23946
23947
initializeRow(row){
23948
var self = this,
23949
rowEl = row.getElement();
23950
23951
var handle = document.createElement('div');
23952
handle.className = "tabulator-row-resize-handle";
23953
23954
var prevHandle = document.createElement('div');
23955
prevHandle.className = "tabulator-row-resize-handle prev";
23956
23957
handle.addEventListener("click", function(e){
23958
e.stopPropagation();
23959
});
23960
23961
var handleDown = function(e){
23962
self.startRow = row;
23963
self._mouseDown(e, row, handle);
23964
};
23965
23966
handle.addEventListener("mousedown", handleDown);
23967
handle.addEventListener("touchstart", handleDown, {passive: true});
23968
23969
prevHandle.addEventListener("click", function(e){
23970
e.stopPropagation();
23971
});
23972
23973
var prevHandleDown = function(e){
23974
var prevRow = self.table.rowManager.prevDisplayRow(row);
23975
23976
if(prevRow){
23977
self.startRow = prevRow;
23978
self._mouseDown(e, prevRow, prevHandle);
23979
}
23980
};
23981
23982
prevHandle.addEventListener("mousedown",prevHandleDown);
23983
prevHandle.addEventListener("touchstart",prevHandleDown, {passive: true});
23984
23985
rowEl.appendChild(handle);
23986
rowEl.appendChild(prevHandle);
23987
}
23988
23989
_mouseDown(e, row, handle){
23990
var self = this;
23991
23992
self.table.element.classList.add("tabulator-block-select");
23993
23994
function mouseMove(e){
23995
row.setHeight(self.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - self.startY));
23996
}
23997
23998
function mouseUp(e){
23999
24000
// //block editor from taking action while resizing is taking place
24001
// if(self.startColumn.modules.edit){
24002
// self.startColumn.modules.edit.blocked = false;
24003
// }
24004
24005
document.body.removeEventListener("mouseup", mouseMove);
24006
document.body.removeEventListener("mousemove", mouseMove);
24007
24008
handle.removeEventListener("touchmove", mouseMove);
24009
handle.removeEventListener("touchend", mouseUp);
24010
24011
self.table.element.classList.remove("tabulator-block-select");
24012
24013
self.dispatchExternal("rowResized", row.getComponent());
24014
}
24015
24016
e.stopPropagation(); //prevent resize from interfering with movable columns
24017
24018
//block editor from taking action while resizing is taking place
24019
// if(self.startColumn.modules.edit){
24020
// self.startColumn.modules.edit.blocked = true;
24021
// }
24022
24023
self.startY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY;
24024
self.startHeight = row.getHeight();
24025
24026
document.body.addEventListener("mousemove", mouseMove);
24027
document.body.addEventListener("mouseup", mouseUp);
24028
24029
handle.addEventListener("touchmove", mouseMove, {passive: true});
24030
handle.addEventListener("touchend", mouseUp);
24031
}
24032
}
24033
24034
ResizeRows.moduleName = "resizeRows";
24035
24036
class ResizeTable extends Module{
24037
24038
constructor(table){
24039
super(table);
24040
24041
this.binding = false;
24042
this.visibilityObserver = false;
24043
this.resizeObserver = false;
24044
this.containerObserver = false;
24045
24046
this.tableHeight = 0;
24047
this.tableWidth = 0;
24048
this.containerHeight = 0;
24049
this.containerWidth = 0;
24050
24051
this.autoResize = false;
24052
24053
this.visible = false;
24054
24055
this.initialized = false;
24056
this.initialRedraw = false;
24057
24058
this.registerTableOption("autoResize", true); //auto resize table
24059
}
24060
24061
initialize(){
24062
if(this.table.options.autoResize){
24063
var table = this.table,
24064
tableStyle;
24065
24066
this.tableHeight = table.element.clientHeight;
24067
this.tableWidth = table.element.clientWidth;
24068
24069
if(table.element.parentNode){
24070
this.containerHeight = table.element.parentNode.clientHeight;
24071
this.containerWidth = table.element.parentNode.clientWidth;
24072
}
24073
24074
if(typeof IntersectionObserver !== "undefined" && typeof ResizeObserver !== "undefined" && table.rowManager.getRenderMode() === "virtual"){
24075
24076
this.initializeVisibilityObserver();
24077
24078
this.autoResize = true;
24079
24080
this.resizeObserver = new ResizeObserver((entry) => {
24081
if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){
24082
24083
var nodeHeight = Math.floor(entry[0].contentRect.height);
24084
var nodeWidth = Math.floor(entry[0].contentRect.width);
24085
24086
if(this.tableHeight != nodeHeight || this.tableWidth != nodeWidth){
24087
this.tableHeight = nodeHeight;
24088
this.tableWidth = nodeWidth;
24089
24090
if(table.element.parentNode){
24091
this.containerHeight = table.element.parentNode.clientHeight;
24092
this.containerWidth = table.element.parentNode.clientWidth;
24093
}
24094
24095
this.redrawTable();
24096
}
24097
}
24098
});
24099
24100
this.resizeObserver.observe(table.element);
24101
24102
tableStyle = window.getComputedStyle(table.element);
24103
24104
if(this.table.element.parentNode && !this.table.rowManager.fixedHeight && (tableStyle.getPropertyValue("max-height") || tableStyle.getPropertyValue("min-height"))){
24105
24106
this.containerObserver = new ResizeObserver((entry) => {
24107
if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){
24108
24109
var nodeHeight = Math.floor(entry[0].contentRect.height);
24110
var nodeWidth = Math.floor(entry[0].contentRect.width);
24111
24112
if(this.containerHeight != nodeHeight || this.containerWidth != nodeWidth){
24113
this.containerHeight = nodeHeight;
24114
this.containerWidth = nodeWidth;
24115
this.tableHeight = table.element.clientHeight;
24116
this.tableWidth = table.element.clientWidth;
24117
}
24118
24119
this.redrawTable();
24120
}
24121
});
24122
24123
this.containerObserver.observe(this.table.element.parentNode);
24124
}
24125
24126
this.subscribe("table-resize", this.tableResized.bind(this));
24127
24128
}else {
24129
this.binding = function(){
24130
if(!table.browserMobile || (table.browserMobile && !table.modules.edit.currentCell)){
24131
table.columnManager.rerenderColumns(true);
24132
table.redraw();
24133
}
24134
};
24135
24136
window.addEventListener("resize", this.binding);
24137
}
24138
24139
this.subscribe("table-destroy", this.clearBindings.bind(this));
24140
}
24141
}
24142
24143
initializeVisibilityObserver(){
24144
this.visibilityObserver = new IntersectionObserver((entries) => {
24145
this.visible = entries[0].isIntersecting;
24146
24147
if(!this.initialized){
24148
this.initialized = true;
24149
this.initialRedraw = !this.visible;
24150
}else {
24151
if(this.visible){
24152
this.redrawTable(this.initialRedraw);
24153
this.initialRedraw = false;
24154
}
24155
}
24156
});
24157
24158
this.visibilityObserver.observe(this.table.element);
24159
}
24160
24161
redrawTable(force){
24162
if(this.initialized && this.visible){
24163
this.table.columnManager.rerenderColumns(true);
24164
this.table.redraw(force);
24165
}
24166
}
24167
24168
tableResized(){
24169
this.table.rowManager.redraw();
24170
}
24171
24172
clearBindings(){
24173
if(this.binding){
24174
window.removeEventListener("resize", this.binding);
24175
}
24176
24177
if(this.resizeObserver){
24178
this.resizeObserver.unobserve(this.table.element);
24179
}
24180
24181
if(this.visibilityObserver){
24182
this.visibilityObserver.unobserve(this.table.element);
24183
}
24184
24185
if(this.containerObserver){
24186
this.containerObserver.unobserve(this.table.element.parentNode);
24187
}
24188
}
24189
}
24190
24191
ResizeTable.moduleName = "resizeTable";
24192
24193
class ResponsiveLayout extends Module{
24194
24195
constructor(table){
24196
super(table);
24197
24198
this.columns = [];
24199
this.hiddenColumns = [];
24200
this.mode = "";
24201
this.index = 0;
24202
this.collapseFormatter = [];
24203
this.collapseStartOpen = true;
24204
this.collapseHandleColumn = false;
24205
24206
this.registerTableOption("responsiveLayout", false); //responsive layout flags
24207
this.registerTableOption("responsiveLayoutCollapseStartOpen", true); //start showing collapsed data
24208
this.registerTableOption("responsiveLayoutCollapseUseFormatters", true); //responsive layout collapse formatter
24209
this.registerTableOption("responsiveLayoutCollapseFormatter", false); //responsive layout collapse formatter
24210
24211
this.registerColumnOption("responsive");
24212
}
24213
24214
//generate responsive columns list
24215
initialize(){
24216
if(this.table.options.responsiveLayout){
24217
this.subscribe("column-layout", this.initializeColumn.bind(this));
24218
this.subscribe("column-show", this.updateColumnVisibility.bind(this));
24219
this.subscribe("column-hide", this.updateColumnVisibility.bind(this));
24220
this.subscribe("columns-loaded", this.initializeResponsivity.bind(this));
24221
this.subscribe("column-moved", this.initializeResponsivity.bind(this));
24222
this.subscribe("column-add", this.initializeResponsivity.bind(this));
24223
this.subscribe("column-delete", this.initializeResponsivity.bind(this));
24224
24225
this.subscribe("table-redrawing", this.tableRedraw.bind(this));
24226
24227
if(this.table.options.responsiveLayout === "collapse"){
24228
this.subscribe("row-data-changed", this.generateCollapsedRowContent.bind(this));
24229
this.subscribe("row-init", this.initializeRow.bind(this));
24230
this.subscribe("row-layout", this.layoutRow.bind(this));
24231
}
24232
}
24233
}
24234
24235
tableRedraw(force){
24236
if(["fitColumns", "fitDataStretch"].indexOf(this.layoutMode()) === -1){
24237
if(!force){
24238
this.update();
24239
}
24240
}
24241
}
24242
24243
initializeResponsivity(){
24244
var columns = [];
24245
24246
this.mode = this.table.options.responsiveLayout;
24247
this.collapseFormatter = this.table.options.responsiveLayoutCollapseFormatter || this.formatCollapsedData;
24248
this.collapseStartOpen = this.table.options.responsiveLayoutCollapseStartOpen;
24249
this.hiddenColumns = [];
24250
24251
//determine level of responsivity for each column
24252
this.table.columnManager.columnsByIndex.forEach((column, i) => {
24253
if(column.modules.responsive){
24254
if(column.modules.responsive.order && column.modules.responsive.visible){
24255
column.modules.responsive.index = i;
24256
columns.push(column);
24257
24258
if(!column.visible && this.mode === "collapse"){
24259
this.hiddenColumns.push(column);
24260
}
24261
}
24262
}
24263
});
24264
24265
//sort list by responsivity
24266
columns = columns.reverse();
24267
columns = columns.sort((a, b) => {
24268
var diff = b.modules.responsive.order - a.modules.responsive.order;
24269
return diff || (b.modules.responsive.index - a.modules.responsive.index);
24270
});
24271
24272
this.columns = columns;
24273
24274
if(this.mode === "collapse"){
24275
this.generateCollapsedContent();
24276
}
24277
24278
//assign collapse column
24279
for (let col of this.table.columnManager.columnsByIndex){
24280
if(col.definition.formatter == "responsiveCollapse"){
24281
this.collapseHandleColumn = col;
24282
break;
24283
}
24284
}
24285
24286
if(this.collapseHandleColumn){
24287
if(this.hiddenColumns.length){
24288
this.collapseHandleColumn.show();
24289
}else {
24290
this.collapseHandleColumn.hide();
24291
}
24292
}
24293
}
24294
24295
//define layout information
24296
initializeColumn(column){
24297
var def = column.getDefinition();
24298
24299
column.modules.responsive = {order: typeof def.responsive === "undefined" ? 1 : def.responsive, visible:def.visible === false ? false : true};
24300
}
24301
24302
initializeRow(row){
24303
var el;
24304
24305
if(row.type !== "calc"){
24306
el = document.createElement("div");
24307
el.classList.add("tabulator-responsive-collapse");
24308
24309
row.modules.responsiveLayout = {
24310
element:el,
24311
open:this.collapseStartOpen,
24312
};
24313
24314
if(!this.collapseStartOpen){
24315
el.style.display = 'none';
24316
}
24317
}
24318
}
24319
24320
layoutRow(row){
24321
var rowEl = row.getElement();
24322
24323
if(row.modules.responsiveLayout){
24324
rowEl.appendChild(row.modules.responsiveLayout.element);
24325
this.generateCollapsedRowContent(row);
24326
}
24327
}
24328
24329
//update column visibility
24330
updateColumnVisibility(column, responsiveToggle){
24331
if(!responsiveToggle && column.modules.responsive){
24332
column.modules.responsive.visible = column.visible;
24333
this.initializeResponsivity();
24334
}
24335
}
24336
24337
hideColumn(column){
24338
var colCount = this.hiddenColumns.length;
24339
24340
column.hide(false, true);
24341
24342
if(this.mode === "collapse"){
24343
this.hiddenColumns.unshift(column);
24344
this.generateCollapsedContent();
24345
24346
if(this.collapseHandleColumn && !colCount){
24347
this.collapseHandleColumn.show();
24348
}
24349
}
24350
}
24351
24352
showColumn(column){
24353
var index;
24354
24355
column.show(false, true);
24356
//set column width to prevent calculation loops on uninitialized columns
24357
column.setWidth(column.getWidth());
24358
24359
if(this.mode === "collapse"){
24360
index = this.hiddenColumns.indexOf(column);
24361
24362
if(index > -1){
24363
this.hiddenColumns.splice(index, 1);
24364
}
24365
24366
this.generateCollapsedContent();
24367
24368
if(this.collapseHandleColumn && !this.hiddenColumns.length){
24369
this.collapseHandleColumn.hide();
24370
}
24371
}
24372
}
24373
24374
//redraw columns to fit space
24375
update(){
24376
var working = true;
24377
24378
while(working){
24379
24380
let width = this.table.modules.layout.getMode() == "fitColumns" ? this.table.columnManager.getFlexBaseWidth() : this.table.columnManager.getWidth();
24381
24382
let diff = (this.table.options.headerVisible ? this.table.columnManager.element.clientWidth : this.table.element.clientWidth) - width;
24383
24384
if(diff < 0){
24385
//table is too wide
24386
let column = this.columns[this.index];
24387
24388
if(column){
24389
this.hideColumn(column);
24390
this.index ++;
24391
}else {
24392
working = false;
24393
}
24394
24395
}else {
24396
24397
//table has spare space
24398
let column = this.columns[this.index -1];
24399
24400
if(column){
24401
if(diff > 0){
24402
if(diff >= column.getWidth()){
24403
this.showColumn(column);
24404
this.index --;
24405
}else {
24406
working = false;
24407
}
24408
}else {
24409
working = false;
24410
}
24411
}else {
24412
working = false;
24413
}
24414
}
24415
24416
if(!this.table.rowManager.activeRowsCount){
24417
this.table.rowManager.renderEmptyScroll();
24418
}
24419
}
24420
}
24421
24422
generateCollapsedContent(){
24423
var rows = this.table.rowManager.getDisplayRows();
24424
24425
rows.forEach((row) => {
24426
this.generateCollapsedRowContent(row);
24427
});
24428
}
24429
24430
generateCollapsedRowContent(row){
24431
var el, contents;
24432
24433
if(row.modules.responsiveLayout){
24434
el = row.modules.responsiveLayout.element;
24435
24436
while(el.firstChild) el.removeChild(el.firstChild);
24437
24438
contents = this.collapseFormatter(this.generateCollapsedRowData(row));
24439
if(contents){
24440
el.appendChild(contents);
24441
}
24442
}
24443
}
24444
24445
generateCollapsedRowData(row){
24446
var data = row.getData(),
24447
output = [],
24448
mockCellComponent;
24449
24450
this.hiddenColumns.forEach((column) => {
24451
var value = column.getFieldValue(data);
24452
24453
if(column.definition.title && column.field){
24454
if(column.modules.format && this.table.options.responsiveLayoutCollapseUseFormatters){
24455
24456
mockCellComponent = {
24457
value:false,
24458
data:{},
24459
getValue:function(){
24460
return value;
24461
},
24462
getData:function(){
24463
return data;
24464
},
24465
getType:function(){
24466
return "cell";
24467
},
24468
getElement:function(){
24469
return document.createElement("div");
24470
},
24471
getRow:function(){
24472
return row.getComponent();
24473
},
24474
getColumn:function(){
24475
return column.getComponent();
24476
},
24477
getTable:() => {
24478
return this.table;
24479
},
24480
};
24481
24482
function onRendered(callback){
24483
callback();
24484
}
24485
24486
output.push({
24487
field: column.field,
24488
title: column.definition.title,
24489
value: column.modules.format.formatter.call(this.table.modules.format, mockCellComponent, column.modules.format.params, onRendered)
24490
});
24491
}else {
24492
output.push({
24493
field: column.field,
24494
title: column.definition.title,
24495
value: value
24496
});
24497
}
24498
}
24499
});
24500
24501
return output;
24502
}
24503
24504
formatCollapsedData(data){
24505
var list = document.createElement("table");
24506
24507
data.forEach(function(item){
24508
var row = document.createElement("tr");
24509
var titleData = document.createElement("td");
24510
var valueData = document.createElement("td");
24511
var node_content;
24512
24513
var titleHighlight = document.createElement("strong");
24514
titleData.appendChild(titleHighlight);
24515
this.langBind("columns|" + item.field, function(text){
24516
titleHighlight.innerHTML = text || item.title;
24517
});
24518
24519
if(item.value instanceof Node){
24520
node_content = document.createElement("div");
24521
node_content.appendChild(item.value);
24522
valueData.appendChild(node_content);
24523
}else {
24524
valueData.innerHTML = item.value;
24525
}
24526
24527
row.appendChild(titleData);
24528
row.appendChild(valueData);
24529
list.appendChild(row);
24530
}, this);
24531
24532
return Object.keys(data).length ? list : "";
24533
}
24534
}
24535
24536
ResponsiveLayout.moduleName = "responsiveLayout";
24537
24538
class SelectRow extends Module{
24539
24540
constructor(table){
24541
super(table);
24542
24543
this.selecting = false; //flag selecting in progress
24544
this.lastClickedRow = false; //last clicked row
24545
this.selectPrev = []; //hold previously selected element for drag drop selection
24546
this.selectedRows = []; //hold selected rows
24547
this.headerCheckboxElement = null; // hold header select element
24548
24549
this.registerTableOption("selectable", "highlight"); //highlight rows on hover
24550
this.registerTableOption("selectableRangeMode", "drag"); //highlight rows on hover
24551
this.registerTableOption("selectableRollingSelection", true); //roll selection once maximum number of selectable rows is reached
24552
this.registerTableOption("selectablePersistence", true); // maintain selection when table view is updated
24553
this.registerTableOption("selectableCheck", function(data, row){return true;}); //check whether row is selectable
24554
24555
this.registerTableFunction("selectRow", this.selectRows.bind(this));
24556
this.registerTableFunction("deselectRow", this.deselectRows.bind(this));
24557
this.registerTableFunction("toggleSelectRow", this.toggleRow.bind(this));
24558
this.registerTableFunction("getSelectedRows", this.getSelectedRows.bind(this));
24559
this.registerTableFunction("getSelectedData", this.getSelectedData.bind(this));
24560
24561
//register component functions
24562
this.registerComponentFunction("row", "select", this.selectRows.bind(this));
24563
this.registerComponentFunction("row", "deselect", this.deselectRows.bind(this));
24564
this.registerComponentFunction("row", "toggleSelect", this.toggleRow.bind(this));
24565
this.registerComponentFunction("row", "isSelected", this.isRowSelected.bind(this));
24566
}
24567
24568
initialize(){
24569
if(this.table.options.selectable !== false){
24570
this.subscribe("row-init", this.initializeRow.bind(this));
24571
this.subscribe("row-deleting", this.rowDeleted.bind(this));
24572
this.subscribe("rows-wipe", this.clearSelectionData.bind(this));
24573
this.subscribe("rows-retrieve", this.rowRetrieve.bind(this));
24574
24575
if(this.table.options.selectable && !this.table.options.selectablePersistence){
24576
this.subscribe("data-refreshing", this.deselectRows.bind(this));
24577
}
24578
}
24579
}
24580
24581
rowRetrieve(type, prevValue){
24582
return type === "selected" ? this.selectedRows : prevValue;
24583
}
24584
24585
rowDeleted(row){
24586
this._deselectRow(row, true);
24587
}
24588
24589
clearSelectionData(silent){
24590
var prevSelected = this.selectedRows.length;
24591
24592
this.selecting = false;
24593
this.lastClickedRow = false;
24594
this.selectPrev = [];
24595
this.selectedRows = [];
24596
24597
if(prevSelected && silent !== true){
24598
this._rowSelectionChanged();
24599
}
24600
}
24601
24602
initializeRow(row){
24603
var self = this,
24604
element = row.getElement();
24605
24606
// trigger end of row selection
24607
var endSelect = function(){
24608
24609
setTimeout(function(){
24610
self.selecting = false;
24611
}, 50);
24612
24613
document.body.removeEventListener("mouseup", endSelect);
24614
};
24615
24616
row.modules.select = {selected:false};
24617
24618
//set row selection class
24619
if(self.checkRowSelectability(row)){
24620
element.classList.add("tabulator-selectable");
24621
element.classList.remove("tabulator-unselectable");
24622
24623
if(self.table.options.selectable && self.table.options.selectable != "highlight"){
24624
if(self.table.options.selectableRangeMode === "click"){
24625
element.addEventListener("click", this.handleComplexRowClick.bind(this, row));
24626
}else {
24627
element.addEventListener("click", function(e){
24628
if(!self.table.modExists("edit") || !self.table.modules.edit.getCurrentCell()){
24629
self.table._clearSelection();
24630
}
24631
24632
if(!self.selecting){
24633
self.toggleRow(row);
24634
}
24635
});
24636
24637
element.addEventListener("mousedown", function(e){
24638
if(e.shiftKey){
24639
self.table._clearSelection();
24640
24641
self.selecting = true;
24642
24643
self.selectPrev = [];
24644
24645
document.body.addEventListener("mouseup", endSelect);
24646
document.body.addEventListener("keyup", endSelect);
24647
24648
self.toggleRow(row);
24649
24650
return false;
24651
}
24652
});
24653
24654
element.addEventListener("mouseenter", function(e){
24655
if(self.selecting){
24656
self.table._clearSelection();
24657
self.toggleRow(row);
24658
24659
if(self.selectPrev[1] == row){
24660
self.toggleRow(self.selectPrev[0]);
24661
}
24662
}
24663
});
24664
24665
element.addEventListener("mouseout", function(e){
24666
if(self.selecting){
24667
self.table._clearSelection();
24668
self.selectPrev.unshift(row);
24669
}
24670
});
24671
}
24672
}
24673
24674
}else {
24675
element.classList.add("tabulator-unselectable");
24676
element.classList.remove("tabulator-selectable");
24677
}
24678
}
24679
24680
handleComplexRowClick(row, e){
24681
if(e.shiftKey){
24682
this.table._clearSelection();
24683
this.lastClickedRow = this.lastClickedRow || row;
24684
24685
var lastClickedRowIdx = this.table.rowManager.getDisplayRowIndex(this.lastClickedRow);
24686
var rowIdx = this.table.rowManager.getDisplayRowIndex(row);
24687
24688
var fromRowIdx = lastClickedRowIdx <= rowIdx ? lastClickedRowIdx : rowIdx;
24689
var toRowIdx = lastClickedRowIdx >= rowIdx ? lastClickedRowIdx : rowIdx;
24690
24691
var rows = this.table.rowManager.getDisplayRows().slice(0);
24692
var toggledRows = rows.splice(fromRowIdx, toRowIdx - fromRowIdx + 1);
24693
24694
if(e.ctrlKey || e.metaKey){
24695
toggledRows.forEach((toggledRow)=>{
24696
if(toggledRow !== this.lastClickedRow){
24697
24698
if(this.table.options.selectable !== true && !this.isRowSelected(row)){
24699
if(this.selectedRows.length < this.table.options.selectable){
24700
this.toggleRow(toggledRow);
24701
}
24702
}else {
24703
this.toggleRow(toggledRow);
24704
}
24705
}
24706
});
24707
this.lastClickedRow = row;
24708
}else {
24709
this.deselectRows(undefined, true);
24710
24711
if(this.table.options.selectable !== true){
24712
if(toggledRows.length > this.table.options.selectable){
24713
toggledRows = toggledRows.slice(0, this.table.options.selectable);
24714
}
24715
}
24716
24717
this.selectRows(toggledRows);
24718
}
24719
this.table._clearSelection();
24720
}
24721
else if(e.ctrlKey || e.metaKey){
24722
this.toggleRow(row);
24723
this.lastClickedRow = row;
24724
}else {
24725
this.deselectRows(undefined, true);
24726
this.selectRows(row);
24727
this.lastClickedRow = row;
24728
}
24729
}
24730
24731
checkRowSelectability(row){
24732
if(row && row.type === "row"){
24733
return this.table.options.selectableCheck.call(this.table, row.getComponent());
24734
}
24735
24736
return false;
24737
}
24738
24739
//toggle row selection
24740
toggleRow(row){
24741
if(this.checkRowSelectability(row)){
24742
if(row.modules.select && row.modules.select.selected){
24743
this._deselectRow(row);
24744
}else {
24745
this._selectRow(row);
24746
}
24747
}
24748
}
24749
24750
//select a number of rows
24751
selectRows(rows){
24752
var changes = [],
24753
rowMatch, change;
24754
24755
switch(typeof rows){
24756
case "undefined":
24757
rowMatch = this.table.rowManager.rows;
24758
break;
24759
24760
case "string":
24761
rowMatch = this.table.rowManager.findRow(rows);
24762
24763
if(!rowMatch){
24764
rowMatch = this.table.rowManager.getRows(rows);
24765
}
24766
break;
24767
24768
default:
24769
rowMatch = rows;
24770
break;
24771
}
24772
24773
if(Array.isArray(rowMatch)){
24774
if(rowMatch.length){
24775
rowMatch.forEach((row) => {
24776
change = this._selectRow(row, true, true);
24777
24778
if(change){
24779
changes.push(change);
24780
}
24781
});
24782
24783
this._rowSelectionChanged(false, changes);
24784
}
24785
}else {
24786
if(rowMatch){
24787
this._selectRow(rowMatch, false, true);
24788
}
24789
}
24790
}
24791
24792
//select an individual row
24793
_selectRow(rowInfo, silent, force){
24794
//handle max row count
24795
if(!isNaN(this.table.options.selectable) && this.table.options.selectable !== true && !force){
24796
if(this.selectedRows.length >= this.table.options.selectable){
24797
if(this.table.options.selectableRollingSelection){
24798
this._deselectRow(this.selectedRows[0]);
24799
}else {
24800
return false;
24801
}
24802
}
24803
}
24804
24805
var row = this.table.rowManager.findRow(rowInfo);
24806
24807
if(row){
24808
if(this.selectedRows.indexOf(row) == -1){
24809
row.getElement().classList.add("tabulator-selected");
24810
if(!row.modules.select){
24811
row.modules.select = {};
24812
}
24813
24814
row.modules.select.selected = true;
24815
if(row.modules.select.checkboxEl){
24816
row.modules.select.checkboxEl.checked = true;
24817
}
24818
24819
this.selectedRows.push(row);
24820
24821
if(this.table.options.dataTreeSelectPropagate){
24822
this.childRowSelection(row, true);
24823
}
24824
24825
this.dispatchExternal("rowSelected", row.getComponent());
24826
24827
this._rowSelectionChanged(silent, row);
24828
24829
return row;
24830
}
24831
}else {
24832
if(!silent){
24833
console.warn("Selection Error - No such row found, ignoring selection:" + rowInfo);
24834
}
24835
}
24836
}
24837
24838
isRowSelected(row){
24839
return this.selectedRows.indexOf(row) !== -1;
24840
}
24841
24842
//deselect a number of rows
24843
deselectRows(rows, silent){
24844
var changes = [],
24845
rowMatch, change;
24846
24847
switch(typeof rows){
24848
case "undefined":
24849
rowMatch = Object.assign([], this.selectedRows);
24850
break;
24851
24852
case "string":
24853
rowMatch = this.table.rowManager.findRow(rows);
24854
24855
if(!rowMatch){
24856
rowMatch = this.table.rowManager.getRows(rows);
24857
}
24858
break;
24859
24860
default:
24861
rowMatch = rows;
24862
break;
24863
}
24864
24865
if(Array.isArray(rowMatch)){
24866
if(rowMatch.length){
24867
rowMatch.forEach((row) => {
24868
change = this._deselectRow(row, true, true);
24869
24870
if(change){
24871
changes.push(change);
24872
}
24873
});
24874
24875
this._rowSelectionChanged(silent, [], changes);
24876
}
24877
}else {
24878
if(rowMatch){
24879
this._deselectRow(rowMatch, silent, true);
24880
}
24881
}
24882
}
24883
24884
//deselect an individual row
24885
_deselectRow(rowInfo, silent){
24886
var self = this,
24887
row = self.table.rowManager.findRow(rowInfo),
24888
index, element;
24889
24890
if(row){
24891
index = self.selectedRows.findIndex(function(selectedRow){
24892
return selectedRow == row;
24893
});
24894
24895
if(index > -1){
24896
24897
element = row.getElement();
24898
24899
if(element){
24900
element.classList.remove("tabulator-selected");
24901
}
24902
24903
if(!row.modules.select){
24904
row.modules.select = {};
24905
}
24906
24907
row.modules.select.selected = false;
24908
if(row.modules.select.checkboxEl){
24909
row.modules.select.checkboxEl.checked = false;
24910
}
24911
self.selectedRows.splice(index, 1);
24912
24913
if(this.table.options.dataTreeSelectPropagate){
24914
this.childRowSelection(row, false);
24915
}
24916
24917
this.dispatchExternal("rowDeselected", row.getComponent());
24918
24919
self._rowSelectionChanged(silent, undefined, row);
24920
24921
return row;
24922
}
24923
}else {
24924
if(!silent){
24925
console.warn("Deselection Error - No such row found, ignoring selection:" + rowInfo);
24926
}
24927
}
24928
}
24929
24930
getSelectedData(){
24931
var data = [];
24932
24933
this.selectedRows.forEach(function(row){
24934
data.push(row.getData());
24935
});
24936
24937
return data;
24938
}
24939
24940
getSelectedRows(){
24941
var rows = [];
24942
24943
this.selectedRows.forEach(function(row){
24944
rows.push(row.getComponent());
24945
});
24946
24947
return rows;
24948
}
24949
24950
_rowSelectionChanged(silent, selected = [], deselected = []){
24951
if(this.headerCheckboxElement){
24952
if(this.selectedRows.length === 0){
24953
this.headerCheckboxElement.checked = false;
24954
this.headerCheckboxElement.indeterminate = false;
24955
} else if(this.table.rowManager.rows.length === this.selectedRows.length){
24956
this.headerCheckboxElement.checked = true;
24957
this.headerCheckboxElement.indeterminate = false;
24958
} else {
24959
this.headerCheckboxElement.indeterminate = true;
24960
this.headerCheckboxElement.checked = false;
24961
}
24962
}
24963
24964
if(!silent){
24965
if(!Array.isArray(selected)){
24966
selected = [selected];
24967
}
24968
24969
selected = selected.map(row => row.getComponent());
24970
24971
if(!Array.isArray(deselected)){
24972
deselected = [deselected];
24973
}
24974
24975
deselected = deselected.map(row => row.getComponent());
24976
24977
this.dispatchExternal("rowSelectionChanged", this.getSelectedData(), this.getSelectedRows(), selected, deselected);
24978
}
24979
}
24980
24981
registerRowSelectCheckbox (row, element) {
24982
if(!row._row.modules.select){
24983
row._row.modules.select = {};
24984
}
24985
24986
row._row.modules.select.checkboxEl = element;
24987
}
24988
24989
registerHeaderSelectCheckbox (element) {
24990
this.headerCheckboxElement = element;
24991
}
24992
24993
childRowSelection(row, select){
24994
var children = this.table.modules.dataTree.getChildren(row, true);
24995
24996
if(select){
24997
for(let child of children){
24998
this._selectRow(child, true);
24999
}
25000
}else {
25001
for(let child of children){
25002
this._deselectRow(child, true);
25003
}
25004
}
25005
}
25006
}
25007
25008
SelectRow.moduleName = "selectRow";
25009
25010
//sort numbers
25011
function number$1(a, b, aRow, bRow, column, dir, params){
25012
var alignEmptyValues = params.alignEmptyValues;
25013
var decimal = params.decimalSeparator;
25014
var thousand = params.thousandSeparator;
25015
var emptyAlign = 0;
25016
25017
a = String(a);
25018
b = String(b);
25019
25020
if(thousand){
25021
a = a.split(thousand).join("");
25022
b = b.split(thousand).join("");
25023
}
25024
25025
if(decimal){
25026
a = a.split(decimal).join(".");
25027
b = b.split(decimal).join(".");
25028
}
25029
25030
a = parseFloat(a);
25031
b = parseFloat(b);
25032
25033
//handle non numeric values
25034
if(isNaN(a)){
25035
emptyAlign = isNaN(b) ? 0 : -1;
25036
}else if(isNaN(b)){
25037
emptyAlign = 1;
25038
}else {
25039
//compare valid values
25040
return a - b;
25041
}
25042
25043
//fix empty values in position
25044
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
25045
emptyAlign *= -1;
25046
}
25047
25048
return emptyAlign;
25049
}
25050
25051
//sort strings
25052
function string(a, b, aRow, bRow, column, dir, params){
25053
var alignEmptyValues = params.alignEmptyValues;
25054
var emptyAlign = 0;
25055
var locale;
25056
25057
//handle empty values
25058
if(!a){
25059
emptyAlign = !b ? 0 : -1;
25060
}else if(!b){
25061
emptyAlign = 1;
25062
}else {
25063
//compare valid values
25064
switch(typeof params.locale){
25065
case "boolean":
25066
if(params.locale){
25067
locale = this.langLocale();
25068
}
25069
break;
25070
case "string":
25071
locale = params.locale;
25072
break;
25073
}
25074
25075
return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale);
25076
}
25077
25078
//fix empty values in position
25079
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
25080
emptyAlign *= -1;
25081
}
25082
25083
return emptyAlign;
25084
}
25085
25086
//sort datetime
25087
function datetime$2(a, b, aRow, bRow, column, dir, params){
25088
var DT = window.DateTime || luxon.DateTime;
25089
var format = params.format || "dd/MM/yyyy HH:mm:ss",
25090
alignEmptyValues = params.alignEmptyValues,
25091
emptyAlign = 0;
25092
25093
if(typeof DT != "undefined"){
25094
if(!DT.isDateTime(a)){
25095
if(format === "iso"){
25096
a = DT.fromISO(String(a));
25097
}else {
25098
a = DT.fromFormat(String(a), format);
25099
}
25100
}
25101
25102
if(!DT.isDateTime(b)){
25103
if(format === "iso"){
25104
b = DT.fromISO(String(b));
25105
}else {
25106
b = DT.fromFormat(String(b), format);
25107
}
25108
}
25109
25110
if(!a.isValid){
25111
emptyAlign = !b.isValid ? 0 : -1;
25112
}else if(!b.isValid){
25113
emptyAlign = 1;
25114
}else {
25115
//compare valid values
25116
return a - b;
25117
}
25118
25119
//fix empty values in position
25120
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
25121
emptyAlign *= -1;
25122
}
25123
25124
return emptyAlign;
25125
25126
}else {
25127
console.error("Sort Error - 'datetime' sorter is dependant on luxon.js");
25128
}
25129
}
25130
25131
//sort date
25132
function date$1(a, b, aRow, bRow, column, dir, params){
25133
if(!params.format){
25134
params.format = "dd/MM/yyyy";
25135
}
25136
25137
return datetime$2.call(this, a, b, aRow, bRow, column, dir, params);
25138
}
25139
25140
//sort times
25141
function time$1(a, b, aRow, bRow, column, dir, params){
25142
if(!params.format){
25143
params.format = "HH:mm";
25144
}
25145
25146
return datetime$2.call(this, a, b, aRow, bRow, column, dir, params);
25147
}
25148
25149
//sort booleans
25150
function boolean(a, b, aRow, bRow, column, dir, params){
25151
var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0;
25152
var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0;
25153
25154
return el1 - el2;
25155
}
25156
25157
//sort if element contains any data
25158
function array(a, b, aRow, bRow, column, dir, params){
25159
var type = params.type || "length",
25160
alignEmptyValues = params.alignEmptyValues,
25161
emptyAlign = 0;
25162
25163
function calc(value){
25164
var result;
25165
25166
switch(type){
25167
case "length":
25168
result = value.length;
25169
break;
25170
25171
case "sum":
25172
result = value.reduce(function(c, d){
25173
return c + d;
25174
});
25175
break;
25176
25177
case "max":
25178
result = Math.max.apply(null, value) ;
25179
break;
25180
25181
case "min":
25182
result = Math.min.apply(null, value) ;
25183
break;
25184
25185
case "avg":
25186
result = value.reduce(function(c, d){
25187
return c + d;
25188
}) / value.length;
25189
break;
25190
}
25191
25192
return result;
25193
}
25194
25195
//handle non array values
25196
if(!Array.isArray(a)){
25197
emptyAlign = !Array.isArray(b) ? 0 : -1;
25198
}else if(!Array.isArray(b)){
25199
emptyAlign = 1;
25200
}else {
25201
return calc(b) - calc(a);
25202
}
25203
25204
//fix empty values in position
25205
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
25206
emptyAlign *= -1;
25207
}
25208
25209
return emptyAlign;
25210
}
25211
25212
//sort if element contains any data
25213
function exists(a, b, aRow, bRow, column, dir, params){
25214
var el1 = typeof a == "undefined" ? 0 : 1;
25215
var el2 = typeof b == "undefined" ? 0 : 1;
25216
25217
return el1 - el2;
25218
}
25219
25220
//sort alpha numeric strings
25221
function alphanum(as, bs, aRow, bRow, column, dir, params){
25222
var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
25223
var alignEmptyValues = params.alignEmptyValues;
25224
var emptyAlign = 0;
25225
25226
//handle empty values
25227
if(!as && as!== 0){
25228
emptyAlign = !bs && bs!== 0 ? 0 : -1;
25229
}else if(!bs && bs!== 0){
25230
emptyAlign = 1;
25231
}else {
25232
25233
if(isFinite(as) && isFinite(bs)) return as - bs;
25234
a = String(as).toLowerCase();
25235
b = String(bs).toLowerCase();
25236
if(a === b) return 0;
25237
if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
25238
a = a.match(rx);
25239
b = b.match(rx);
25240
L = a.length > b.length ? b.length : a.length;
25241
while(i < L){
25242
a1= a[i];
25243
b1= b[i++];
25244
if(a1 !== b1){
25245
if(isFinite(a1) && isFinite(b1)){
25246
if(a1.charAt(0) === "0") a1 = "." + a1;
25247
if(b1.charAt(0) === "0") b1 = "." + b1;
25248
return a1 - b1;
25249
}
25250
else return a1 > b1 ? 1 : -1;
25251
}
25252
}
25253
25254
return a.length > b.length;
25255
}
25256
25257
//fix empty values in position
25258
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
25259
emptyAlign *= -1;
25260
}
25261
25262
return emptyAlign;
25263
}
25264
25265
var defaultSorters = {
25266
number:number$1,
25267
string:string,
25268
date:date$1,
25269
time:time$1,
25270
datetime:datetime$2,
25271
boolean:boolean,
25272
array:array,
25273
exists:exists,
25274
alphanum:alphanum
25275
};
25276
25277
class Sort extends Module{
25278
25279
constructor(table){
25280
super(table);
25281
25282
this.sortList = []; //holder current sort
25283
this.changed = false; //has the sort changed since last render
25284
25285
this.registerTableOption("sortMode", "local"); //local or remote sorting
25286
25287
this.registerTableOption("initialSort", false); //initial sorting criteria
25288
this.registerTableOption("columnHeaderSortMulti", true); //multiple or single column sorting
25289
this.registerTableOption("sortOrderReverse", false); //reverse internal sort ordering
25290
this.registerTableOption("headerSortElement", "<div class='tabulator-arrow'></div>"); //header sort element
25291
this.registerTableOption("headerSortClickElement", "header"); //element which triggers sort when clicked
25292
25293
this.registerColumnOption("sorter");
25294
this.registerColumnOption("sorterParams");
25295
25296
this.registerColumnOption("headerSort", true);
25297
this.registerColumnOption("headerSortStartingDir");
25298
this.registerColumnOption("headerSortTristate");
25299
25300
}
25301
25302
initialize(){
25303
this.subscribe("column-layout", this.initializeColumn.bind(this));
25304
this.subscribe("table-built", this.tableBuilt.bind(this));
25305
this.registerDataHandler(this.sort.bind(this), 20);
25306
25307
this.registerTableFunction("setSort", this.userSetSort.bind(this));
25308
this.registerTableFunction("getSorters", this.getSort.bind(this));
25309
this.registerTableFunction("clearSort", this.clearSort.bind(this));
25310
25311
if(this.table.options.sortMode === "remote"){
25312
this.subscribe("data-params", this.remoteSortParams.bind(this));
25313
}
25314
}
25315
25316
tableBuilt(){
25317
if(this.table.options.initialSort){
25318
this.setSort(this.table.options.initialSort);
25319
}
25320
}
25321
25322
remoteSortParams(data, config, silent, params){
25323
var sorters = this.getSort();
25324
25325
sorters.forEach((item) => {
25326
delete item.column;
25327
});
25328
25329
params.sort = sorters;
25330
25331
return params;
25332
}
25333
25334
25335
///////////////////////////////////
25336
///////// Table Functions /////////
25337
///////////////////////////////////
25338
25339
userSetSort(sortList, dir){
25340
this.setSort(sortList, dir);
25341
// this.table.rowManager.sorterRefresh();
25342
this.refreshSort();
25343
}
25344
25345
clearSort(){
25346
this.clear();
25347
// this.table.rowManager.sorterRefresh();
25348
this.refreshSort();
25349
}
25350
25351
25352
///////////////////////////////////
25353
///////// Internal Logic //////////
25354
///////////////////////////////////
25355
25356
//initialize column header for sorting
25357
initializeColumn(column){
25358
var sorter = false,
25359
colEl,
25360
arrowEl;
25361
25362
switch(typeof column.definition.sorter){
25363
case "string":
25364
if(Sort.sorters[column.definition.sorter]){
25365
sorter = Sort.sorters[column.definition.sorter];
25366
}else {
25367
console.warn("Sort Error - No such sorter found: ", column.definition.sorter);
25368
}
25369
break;
25370
25371
case "function":
25372
sorter = column.definition.sorter;
25373
break;
25374
}
25375
25376
column.modules.sort = {
25377
sorter:sorter, dir:"none",
25378
params:column.definition.sorterParams || {},
25379
startingDir:column.definition.headerSortStartingDir || "asc",
25380
tristate: column.definition.headerSortTristate,
25381
};
25382
25383
if(column.definition.headerSort !== false){
25384
25385
colEl = column.getElement();
25386
25387
colEl.classList.add("tabulator-sortable");
25388
25389
arrowEl = document.createElement("div");
25390
arrowEl.classList.add("tabulator-col-sorter");
25391
25392
switch(this.table.options.headerSortClickElement){
25393
case "icon":
25394
arrowEl.classList.add("tabulator-col-sorter-element");
25395
break;
25396
case "header":
25397
colEl.classList.add("tabulator-col-sorter-element");
25398
break;
25399
default:
25400
colEl.classList.add("tabulator-col-sorter-element");
25401
break;
25402
}
25403
25404
switch(this.table.options.headerSortElement){
25405
case "function":
25406
//do nothing
25407
break;
25408
25409
case "object":
25410
arrowEl.appendChild(this.table.options.headerSortElement);
25411
break;
25412
25413
default:
25414
arrowEl.innerHTML = this.table.options.headerSortElement;
25415
}
25416
25417
//create sorter arrow
25418
column.titleHolderElement.appendChild(arrowEl);
25419
25420
column.modules.sort.element = arrowEl;
25421
25422
this.setColumnHeaderSortIcon(column, "none");
25423
25424
//sort on click
25425
(this.table.options.headerSortClickElement === "icon" ? arrowEl : colEl).addEventListener("click", (e) => {
25426
var dir = "",
25427
sorters=[],
25428
match = false;
25429
25430
if(column.modules.sort){
25431
if(column.modules.sort.tristate){
25432
if(column.modules.sort.dir == "none"){
25433
dir = column.modules.sort.startingDir;
25434
}else {
25435
if(column.modules.sort.dir == column.modules.sort.startingDir){
25436
dir = column.modules.sort.dir == "asc" ? "desc" : "asc";
25437
}else {
25438
dir = "none";
25439
}
25440
}
25441
}else {
25442
switch(column.modules.sort.dir){
25443
case "asc":
25444
dir = "desc";
25445
break;
25446
25447
case "desc":
25448
dir = "asc";
25449
break;
25450
25451
default:
25452
dir = column.modules.sort.startingDir;
25453
}
25454
}
25455
25456
if (this.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) {
25457
sorters = this.getSort();
25458
25459
match = sorters.findIndex((sorter) => {
25460
return sorter.field === column.getField();
25461
});
25462
25463
if(match > -1){
25464
sorters[match].dir = dir;
25465
25466
match = sorters.splice(match, 1)[0];
25467
if(dir != "none"){
25468
sorters.push(match);
25469
}
25470
}else {
25471
if(dir != "none"){
25472
sorters.push({column:column, dir:dir});
25473
}
25474
}
25475
25476
//add to existing sort
25477
this.setSort(sorters);
25478
}else {
25479
if(dir == "none"){
25480
this.clear();
25481
}else {
25482
//sort by column only
25483
this.setSort(column, dir);
25484
}
25485
25486
}
25487
25488
// this.table.rowManager.sorterRefresh(!this.sortList.length);
25489
this.refreshSort();
25490
}
25491
});
25492
}
25493
}
25494
25495
refreshSort(){
25496
if(this.table.options.sortMode === "remote"){
25497
this.reloadData(null, false, false);
25498
}else {
25499
this.refreshData(true);
25500
}
25501
25502
//TODO - Persist left position of row manager
25503
// left = this.scrollLeft;
25504
// this.scrollHorizontal(left);
25505
}
25506
25507
//check if the sorters have changed since last use
25508
hasChanged(){
25509
var changed = this.changed;
25510
this.changed = false;
25511
return changed;
25512
}
25513
25514
//return current sorters
25515
getSort(){
25516
var self = this,
25517
sorters = [];
25518
25519
self.sortList.forEach(function(item){
25520
if(item.column){
25521
sorters.push({column:item.column.getComponent(), field:item.column.getField(), dir:item.dir});
25522
}
25523
});
25524
25525
return sorters;
25526
}
25527
25528
//change sort list and trigger sort
25529
setSort(sortList, dir){
25530
var self = this,
25531
newSortList = [];
25532
25533
if(!Array.isArray(sortList)){
25534
sortList = [{column: sortList, dir:dir}];
25535
}
25536
25537
sortList.forEach(function(item){
25538
var column;
25539
25540
column = self.table.columnManager.findColumn(item.column);
25541
25542
if(column){
25543
item.column = column;
25544
newSortList.push(item);
25545
self.changed = true;
25546
}else {
25547
console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column);
25548
}
25549
25550
});
25551
25552
self.sortList = newSortList;
25553
25554
this.dispatch("sort-changed");
25555
}
25556
25557
//clear sorters
25558
clear(){
25559
this.setSort([]);
25560
}
25561
25562
//find appropriate sorter for column
25563
findSorter(column){
25564
var row = this.table.rowManager.activeRows[0],
25565
sorter = "string",
25566
field, value;
25567
25568
if(row){
25569
row = row.getData();
25570
field = column.getField();
25571
25572
if(field){
25573
25574
value = column.getFieldValue(row);
25575
25576
switch(typeof value){
25577
case "undefined":
25578
sorter = "string";
25579
break;
25580
25581
case "boolean":
25582
sorter = "boolean";
25583
break;
25584
25585
default:
25586
if(!isNaN(value) && value !== ""){
25587
sorter = "number";
25588
}else {
25589
if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
25590
sorter = "alphanum";
25591
}
25592
}
25593
break;
25594
}
25595
}
25596
}
25597
25598
return Sort.sorters[sorter];
25599
}
25600
25601
//work through sort list sorting data
25602
sort(data){
25603
var self = this,
25604
sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList,
25605
sortListActual = [],
25606
rowComponents = [];
25607
25608
if(this.subscribedExternal("dataSorting")){
25609
this.dispatchExternal("dataSorting", self.getSort());
25610
}
25611
25612
self.clearColumnHeaders();
25613
25614
if(this.table.options.sortMode !== "remote"){
25615
25616
//build list of valid sorters and trigger column specific callbacks before sort begins
25617
sortList.forEach(function(item, i){
25618
var sortObj;
25619
25620
if(item.column){
25621
sortObj = item.column.modules.sort;
25622
25623
if(sortObj){
25624
25625
//if no sorter has been defined, take a guess
25626
if(!sortObj.sorter){
25627
sortObj.sorter = self.findSorter(item.column);
25628
}
25629
25630
item.params = typeof sortObj.params === "function" ? sortObj.params(item.column.getComponent(), item.dir) : sortObj.params;
25631
25632
sortListActual.push(item);
25633
}
25634
25635
self.setColumnHeader(item.column, item.dir);
25636
}
25637
});
25638
25639
//sort data
25640
if (sortListActual.length) {
25641
self._sortItems(data, sortListActual);
25642
}
25643
25644
}else {
25645
sortList.forEach(function(item, i){
25646
self.setColumnHeader(item.column, item.dir);
25647
});
25648
}
25649
25650
if(this.subscribedExternal("dataSorted")){
25651
data.forEach((row) => {
25652
rowComponents.push(row.getComponent());
25653
});
25654
25655
this.dispatchExternal("dataSorted", self.getSort(), rowComponents);
25656
}
25657
25658
return data;
25659
}
25660
25661
//clear sort arrows on columns
25662
clearColumnHeaders(){
25663
this.table.columnManager.getRealColumns().forEach((column) => {
25664
if(column.modules.sort){
25665
column.modules.sort.dir = "none";
25666
column.getElement().setAttribute("aria-sort", "none");
25667
this.setColumnHeaderSortIcon(column, "none");
25668
}
25669
});
25670
}
25671
25672
//set the column header sort direction
25673
setColumnHeader(column, dir){
25674
column.modules.sort.dir = dir;
25675
column.getElement().setAttribute("aria-sort", dir === "asc" ? "ascending" : "descending");
25676
this.setColumnHeaderSortIcon(column, dir);
25677
}
25678
25679
setColumnHeaderSortIcon(column, dir){
25680
var sortEl = column.modules.sort.element,
25681
arrowEl;
25682
25683
if(column.definition.headerSort && typeof this.table.options.headerSortElement === "function"){
25684
while(sortEl.firstChild) sortEl.removeChild(sortEl.firstChild);
25685
25686
arrowEl = this.table.options.headerSortElement.call(this.table, column.getComponent(), dir);
25687
25688
if(typeof arrowEl === "object"){
25689
sortEl.appendChild(arrowEl);
25690
}else {
25691
sortEl.innerHTML = arrowEl;
25692
}
25693
}
25694
}
25695
25696
//sort each item in sort list
25697
_sortItems(data, sortList){
25698
var sorterCount = sortList.length - 1;
25699
25700
data.sort((a, b) => {
25701
var result;
25702
25703
for(var i = sorterCount; i>= 0; i--){
25704
let sortItem = sortList[i];
25705
25706
result = this._sortRow(a, b, sortItem.column, sortItem.dir, sortItem.params);
25707
25708
if(result !== 0){
25709
break;
25710
}
25711
}
25712
25713
return result;
25714
});
25715
}
25716
25717
//process individual rows for a sort function on active data
25718
_sortRow(a, b, column, dir, params){
25719
var el1Comp, el2Comp;
25720
25721
//switch elements depending on search direction
25722
var el1 = dir == "asc" ? a : b;
25723
var el2 = dir == "asc" ? b : a;
25724
25725
a = column.getFieldValue(el1.getData());
25726
b = column.getFieldValue(el2.getData());
25727
25728
a = typeof a !== "undefined" ? a : "";
25729
b = typeof b !== "undefined" ? b : "";
25730
25731
el1Comp = el1.getComponent();
25732
el2Comp = el2.getComponent();
25733
25734
return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params);
25735
}
25736
}
25737
25738
Sort.moduleName = "sort";
25739
25740
//load defaults
25741
Sort.sorters = defaultSorters;
25742
25743
class Tooltip extends Module{
25744
25745
constructor(table){
25746
super(table);
25747
25748
this.tooltipSubscriber = null,
25749
this.headerSubscriber = null,
25750
25751
this.timeout = null;
25752
this.popupInstance = null;
25753
25754
this.registerTableOption("tooltipGenerationMode", undefined); //deprecated
25755
this.registerTableOption("tooltipDelay", 300);
25756
25757
this.registerColumnOption("tooltip");
25758
this.registerColumnOption("headerTooltip");
25759
}
25760
25761
initialize(){
25762
this.deprecatedOptionsCheck();
25763
25764
this.subscribe("column-init", this.initializeColumn.bind(this));
25765
}
25766
25767
deprecatedOptionsCheck(){
25768
this.deprecationCheckMsg("tooltipGenerationMode", "This option is no longer needed as tooltips are always generated on hover now");
25769
}
25770
25771
initializeColumn(column){
25772
if(column.definition.headerTooltip && !this.headerSubscriber){
25773
this.headerSubscriber = true;
25774
25775
this.subscribe("column-mousemove", this.mousemoveCheck.bind(this, "headerTooltip"));
25776
this.subscribe("column-mouseout", this.mouseoutCheck.bind(this, "headerTooltip"));
25777
}
25778
25779
if(column.definition.tooltip && !this.tooltipSubscriber){
25780
this.tooltipSubscriber = true;
25781
25782
this.subscribe("cell-mousemove", this.mousemoveCheck.bind(this, "tooltip"));
25783
this.subscribe("cell-mouseout", this.mouseoutCheck.bind(this, "tooltip"));
25784
}
25785
}
25786
25787
mousemoveCheck(action, e, component){
25788
var tooltip = action === "tooltip" ? component.column.definition.tooltip : component.definition.headerTooltip;
25789
25790
if(tooltip){
25791
this.clearPopup();
25792
this.timeout = setTimeout(this.loadTooltip.bind(this, e, component, tooltip), this.table.options.tooltipDelay);
25793
}
25794
}
25795
25796
mouseoutCheck(action, e, component){
25797
if(!this.popupInstance){
25798
this.clearPopup();
25799
}
25800
}
25801
25802
clearPopup(action, e, component){
25803
clearTimeout(this.timeout);
25804
this.timeout = null;
25805
25806
if(this.popupInstance){
25807
this.popupInstance.hide();
25808
}
25809
}
25810
25811
loadTooltip(e, component, tooltip){
25812
var contentsEl, renderedCallback, coords;
25813
25814
function onRendered(callback){
25815
renderedCallback = callback;
25816
}
25817
25818
if(typeof tooltip === "function"){
25819
tooltip = tooltip(e, component.getComponent(), onRendered);
25820
}
25821
25822
if(tooltip instanceof HTMLElement){
25823
contentsEl = tooltip;
25824
}else {
25825
contentsEl = document.createElement("div");
25826
25827
if(tooltip === true){
25828
if(component instanceof Cell){
25829
tooltip = component.value;
25830
}else {
25831
if(component.definition.field){
25832
this.langBind("columns|" + component.definition.field, (value) => {
25833
contentsEl.innerHTML = tooltip = value || component.definition.title;
25834
});
25835
}else {
25836
tooltip = component.definition.title;
25837
}
25838
}
25839
}
25840
25841
contentsEl.innerHTML = tooltip;
25842
}
25843
25844
if(tooltip || tooltip === 0 || tooltip === false){
25845
contentsEl.classList.add("tabulator-tooltip");
25846
25847
contentsEl.addEventListener("mousemove", e => e.preventDefault());
25848
25849
this.popupInstance = this.popup(contentsEl);
25850
25851
if(typeof renderedCallback === "function"){
25852
this.popupInstance.renderCallback(renderedCallback);
25853
}
25854
25855
coords = this.popupInstance.containerEventCoords(e);
25856
25857
this.popupInstance.show(coords.x + 15, coords.y + 15).hideOnBlur(() => {
25858
this.dispatchExternal("TooltipClosed", component.getComponent());
25859
this.popupInstance = null;
25860
});
25861
25862
this.dispatchExternal("TooltipOpened", component.getComponent());
25863
}
25864
}
25865
}
25866
25867
Tooltip.moduleName = "tooltip";
25868
25869
var defaultValidators = {
25870
//is integer
25871
integer: function(cell, value, parameters){
25872
if(value === "" || value === null || typeof value === "undefined"){
25873
return true;
25874
}
25875
25876
value = Number(value);
25877
25878
return !isNaN(value) && isFinite(value) && Math.floor(value) === value;
25879
},
25880
25881
//is float
25882
float: function(cell, value, parameters){
25883
if(value === "" || value === null || typeof value === "undefined"){
25884
return true;
25885
}
25886
25887
value = Number(value);
25888
25889
return !isNaN(value) && isFinite(value) && value % 1 !== 0;
25890
},
25891
25892
//must be a number
25893
numeric: function(cell, value, parameters){
25894
if(value === "" || value === null || typeof value === "undefined"){
25895
return true;
25896
}
25897
return !isNaN(value);
25898
},
25899
25900
//must be a string
25901
string: function(cell, value, parameters){
25902
if(value === "" || value === null || typeof value === "undefined"){
25903
return true;
25904
}
25905
return isNaN(value);
25906
},
25907
25908
//maximum value
25909
max: function(cell, value, parameters){
25910
if(value === "" || value === null || typeof value === "undefined"){
25911
return true;
25912
}
25913
return parseFloat(value) <= parameters;
25914
},
25915
25916
//minimum value
25917
min: function(cell, value, parameters){
25918
if(value === "" || value === null || typeof value === "undefined"){
25919
return true;
25920
}
25921
return parseFloat(value) >= parameters;
25922
},
25923
25924
//starts with value
25925
starts: function(cell, value, parameters){
25926
if(value === "" || value === null || typeof value === "undefined"){
25927
return true;
25928
}
25929
return String(value).toLowerCase().startsWith(String(parameters).toLowerCase());
25930
},
25931
25932
//ends with value
25933
ends: function(cell, value, parameters){
25934
if(value === "" || value === null || typeof value === "undefined"){
25935
return true;
25936
}
25937
return String(value).toLowerCase().endsWith(String(parameters).toLowerCase());
25938
},
25939
25940
25941
//minimum string length
25942
minLength: function(cell, value, parameters){
25943
if(value === "" || value === null || typeof value === "undefined"){
25944
return true;
25945
}
25946
return String(value).length >= parameters;
25947
},
25948
25949
//maximum string length
25950
maxLength: function(cell, value, parameters){
25951
if(value === "" || value === null || typeof value === "undefined"){
25952
return true;
25953
}
25954
return String(value).length <= parameters;
25955
},
25956
25957
//in provided value list
25958
in: function(cell, value, parameters){
25959
if(value === "" || value === null || typeof value === "undefined"){
25960
return true;
25961
}
25962
25963
if(typeof parameters == "string"){
25964
parameters = parameters.split("|");
25965
}
25966
25967
return parameters.indexOf(value) > -1;
25968
},
25969
25970
//must match provided regex
25971
regex: function(cell, value, parameters){
25972
if(value === "" || value === null || typeof value === "undefined"){
25973
return true;
25974
}
25975
var reg = new RegExp(parameters);
25976
25977
return reg.test(value);
25978
},
25979
25980
//value must be unique in this column
25981
unique: function(cell, value, parameters){
25982
if(value === "" || value === null || typeof value === "undefined"){
25983
return true;
25984
}
25985
var unique = true;
25986
25987
var cellData = cell.getData();
25988
var column = cell.getColumn()._getSelf();
25989
25990
this.table.rowManager.rows.forEach(function(row){
25991
var data = row.getData();
25992
25993
if(data !== cellData){
25994
if(value == column.getFieldValue(data)){
25995
unique = false;
25996
}
25997
}
25998
});
25999
26000
return unique;
26001
},
26002
26003
//must have a value
26004
required:function(cell, value, parameters){
26005
return value !== "" && value !== null && typeof value !== "undefined";
26006
},
26007
};
26008
26009
class Validate extends Module{
26010
26011
constructor(table){
26012
super(table);
26013
26014
this.invalidCells = [];
26015
26016
this.registerTableOption("validationMode", "blocking");
26017
26018
this.registerColumnOption("validator");
26019
26020
this.registerTableFunction("getInvalidCells", this.getInvalidCells.bind(this));
26021
this.registerTableFunction("clearCellValidation", this.userClearCellValidation.bind(this));
26022
this.registerTableFunction("validate", this.userValidate.bind(this));
26023
26024
this.registerComponentFunction("cell", "isValid", this.cellIsValid.bind(this));
26025
this.registerComponentFunction("cell", "clearValidation", this.clearValidation.bind(this));
26026
this.registerComponentFunction("cell", "validate", this.cellValidate.bind(this));
26027
26028
this.registerComponentFunction("column", "validate", this.columnValidate.bind(this));
26029
this.registerComponentFunction("row", "validate", this.rowValidate.bind(this));
26030
}
26031
26032
26033
initialize(){
26034
this.subscribe("cell-delete", this.clearValidation.bind(this));
26035
this.subscribe("column-layout", this.initializeColumnCheck.bind(this));
26036
26037
this.subscribe("edit-success", this.editValidate.bind(this));
26038
this.subscribe("edit-editor-clear", this.editorClear.bind(this));
26039
this.subscribe("edit-edited-clear", this.editedClear.bind(this));
26040
}
26041
26042
///////////////////////////////////
26043
///////// Event Handling //////////
26044
///////////////////////////////////
26045
26046
editValidate(cell, value, previousValue){
26047
var valid = this.table.options.validationMode !== "manual" ? this.validate(cell.column.modules.validate, cell, value) : true;
26048
26049
// allow time for editor to make render changes then style cell
26050
if(valid !== true){
26051
setTimeout(() => {
26052
cell.getElement().classList.add("tabulator-validation-fail");
26053
this.dispatchExternal("validationFailed", cell.getComponent(), value, valid);
26054
});
26055
}
26056
26057
return valid;
26058
}
26059
26060
editorClear(cell, cancelled){
26061
if(cancelled){
26062
if(cell.column.modules.validate){
26063
this.cellValidate(cell);
26064
}
26065
}
26066
26067
cell.getElement().classList.remove("tabulator-validation-fail");
26068
}
26069
26070
editedClear(cell){
26071
if(cell.modules.validate){
26072
cell.modules.validate.invalid = false;
26073
}
26074
}
26075
26076
///////////////////////////////////
26077
////////// Cell Functions /////////
26078
///////////////////////////////////
26079
26080
cellIsValid(cell){
26081
return cell.modules.validate ? (cell.modules.validate.invalid || true) : true;
26082
}
26083
26084
cellValidate(cell){
26085
return this.validate(cell.column.modules.validate, cell, cell.getValue());
26086
}
26087
26088
///////////////////////////////////
26089
///////// Column Functions ////////
26090
///////////////////////////////////
26091
26092
columnValidate(column){
26093
var invalid = [];
26094
26095
column.cells.forEach((cell) => {
26096
if(this.cellValidate(cell) !== true){
26097
invalid.push(cell.getComponent());
26098
}
26099
});
26100
26101
return invalid.length ? invalid : true;
26102
}
26103
26104
///////////////////////////////////
26105
////////// Row Functions //////////
26106
///////////////////////////////////
26107
26108
rowValidate(row){
26109
var invalid = [];
26110
26111
row.cells.forEach((cell) => {
26112
if(this.cellValidate(cell) !== true){
26113
invalid.push(cell.getComponent());
26114
}
26115
});
26116
26117
return invalid.length ? invalid : true;
26118
}
26119
26120
///////////////////////////////////
26121
///////// Table Functions /////////
26122
///////////////////////////////////
26123
26124
26125
userClearCellValidation(cells){
26126
if(!cells){
26127
cells = this.getInvalidCells();
26128
}
26129
26130
if(!Array.isArray(cells)){
26131
cells = [cells];
26132
}
26133
26134
cells.forEach((cell) => {
26135
this.clearValidation(cell._getSelf());
26136
});
26137
}
26138
26139
userValidate(cells){
26140
var output = [];
26141
26142
//clear row data
26143
this.table.rowManager.rows.forEach((row) => {
26144
row = row.getComponent();
26145
26146
var valid = row.validate();
26147
26148
if(valid !== true){
26149
output = output.concat(valid);
26150
}
26151
});
26152
26153
return output.length ? output : true;
26154
}
26155
26156
///////////////////////////////////
26157
///////// Internal Logic //////////
26158
///////////////////////////////////
26159
26160
initializeColumnCheck(column){
26161
if(typeof column.definition.validator !== "undefined"){
26162
this.initializeColumn(column);
26163
}
26164
}
26165
26166
//validate
26167
initializeColumn(column){
26168
var self = this,
26169
config = [],
26170
validator;
26171
26172
if(column.definition.validator){
26173
26174
if(Array.isArray(column.definition.validator)){
26175
column.definition.validator.forEach((item) => {
26176
validator = self._extractValidator(item);
26177
26178
if(validator){
26179
config.push(validator);
26180
}
26181
});
26182
26183
}else {
26184
validator = this._extractValidator(column.definition.validator);
26185
26186
if(validator){
26187
config.push(validator);
26188
}
26189
}
26190
26191
column.modules.validate = config.length ? config : false;
26192
}
26193
}
26194
26195
_extractValidator(value){
26196
var type, params, pos;
26197
26198
switch(typeof value){
26199
case "string":
26200
pos = value.indexOf(':');
26201
26202
if(pos > -1){
26203
type = value.substring(0,pos);
26204
params = value.substring(pos+1);
26205
}else {
26206
type = value;
26207
}
26208
26209
return this._buildValidator(type, params);
26210
26211
case "function":
26212
return this._buildValidator(value);
26213
26214
case "object":
26215
return this._buildValidator(value.type, value.parameters);
26216
}
26217
}
26218
26219
_buildValidator(type, params){
26220
26221
var func = typeof type == "function" ? type : Validate.validators[type];
26222
26223
if(!func){
26224
console.warn("Validator Setup Error - No matching validator found:", type);
26225
return false;
26226
}else {
26227
return {
26228
type:typeof type == "function" ? "function" : type,
26229
func:func,
26230
params:params,
26231
};
26232
}
26233
}
26234
26235
validate(validators, cell, value){
26236
var self = this,
26237
failedValidators = [],
26238
invalidIndex = this.invalidCells.indexOf(cell);
26239
26240
if(validators){
26241
validators.forEach((item) => {
26242
if(!item.func.call(self, cell.getComponent(), value, item.params)){
26243
failedValidators.push({
26244
type:item.type,
26245
parameters:item.params
26246
});
26247
}
26248
});
26249
}
26250
26251
if(!cell.modules.validate){
26252
cell.modules.validate = {};
26253
}
26254
26255
if(!failedValidators.length){
26256
cell.modules.validate.invalid = false;
26257
cell.getElement().classList.remove("tabulator-validation-fail");
26258
26259
if(invalidIndex > -1){
26260
this.invalidCells.splice(invalidIndex, 1);
26261
}
26262
}else {
26263
cell.modules.validate.invalid = failedValidators;
26264
26265
if(this.table.options.validationMode !== "manual"){
26266
cell.getElement().classList.add("tabulator-validation-fail");
26267
}
26268
26269
if(invalidIndex == -1){
26270
this.invalidCells.push(cell);
26271
}
26272
}
26273
26274
return failedValidators.length ? failedValidators : true;
26275
}
26276
26277
getInvalidCells(){
26278
var output = [];
26279
26280
this.invalidCells.forEach((cell) => {
26281
output.push(cell.getComponent());
26282
});
26283
26284
return output;
26285
}
26286
26287
clearValidation(cell){
26288
var invalidIndex;
26289
26290
if(cell.modules.validate && cell.modules.validate.invalid){
26291
26292
cell.getElement().classList.remove("tabulator-validation-fail");
26293
cell.modules.validate.invalid = false;
26294
26295
invalidIndex = this.invalidCells.indexOf(cell);
26296
26297
if(invalidIndex > -1){
26298
this.invalidCells.splice(invalidIndex, 1);
26299
}
26300
}
26301
}
26302
}
26303
26304
Validate.moduleName = "validate";
26305
26306
//load defaults
26307
Validate.validators = defaultValidators;
26308
26309
var modules = /*#__PURE__*/Object.freeze({
26310
__proto__: null,
26311
AccessorModule: Accessor,
26312
AjaxModule: Ajax,
26313
ClipboardModule: Clipboard,
26314
ColumnCalcsModule: ColumnCalcs,
26315
DataTreeModule: DataTree,
26316
DownloadModule: Download,
26317
EditModule: Edit$1,
26318
ExportModule: Export,
26319
FilterModule: Filter,
26320
FormatModule: Format,
26321
FrozenColumnsModule: FrozenColumns,
26322
FrozenRowsModule: FrozenRows,
26323
GroupRowsModule: GroupRows,
26324
HistoryModule: History,
26325
HtmlTableImportModule: HtmlTableImport,
26326
ImportModule: Import,
26327
InteractionModule: Interaction,
26328
KeybindingsModule: Keybindings,
26329
MenuModule: Menu,
26330
MoveColumnsModule: MoveColumns,
26331
MoveRowsModule: MoveRows,
26332
MutatorModule: Mutator,
26333
PageModule: Page,
26334
PersistenceModule: Persistence,
26335
PopupModule: Popup$1,
26336
PrintModule: Print,
26337
ReactiveDataModule: ReactiveData,
26338
ResizeColumnsModule: ResizeColumns,
26339
ResizeRowsModule: ResizeRows,
26340
ResizeTableModule: ResizeTable,
26341
ResponsiveLayoutModule: ResponsiveLayout,
26342
SelectRowModule: SelectRow,
26343
SortModule: Sort,
26344
TooltipModule: Tooltip,
26345
ValidateModule: Validate
26346
});
26347
26348
//tabulator with all modules installed
26349
26350
class TabulatorFull extends Tabulator {}
26351
26352
//bind modules and static functionality
26353
new ModuleBinder(TabulatorFull, modules);
26354
26355
return TabulatorFull;
26356
26357
})));
26358
//# sourceMappingURL=tabulator.js.map
26359
26360