-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathredips-table-source.js
More file actions
1414 lines (1354 loc) · 49.5 KB
/
redips-table-source.js
File metadata and controls
1414 lines (1354 loc) · 49.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright (c) 2008-2020, www.redips.net All rights reserved.
Code licensed under the BSD License: http://www.redips.net/license/
http://www.redips.net/javascript/table-td-merge-split/
Version 1.2.0
Mar 23, 2020.
*/
/* eslint-env browser */
/* eslint
semi: ["error", "always"],
indent: [2, "tab"],
no-tabs: 0,
no-multiple-empty-lines: ["error", {"max": 2, "maxEOF": 1}],
one-var: ["error", "always"] */
/* enable strict mode */
'use strict';
/**
* @name REDIPS
* @description create REDIPS namespace (if is not already defined in another REDIPS package)
*/
var REDIPS = REDIPS || {}; // eslint-disable-line no-use-before-define
/**
* @namespace
* @description REDIPS.table is a JavaScript library which enables dynamically merging and splitting table cells.
* @name REDIPS.table
* @author Darko Bunic
* @see
* <a href="http://www.redips.net/javascript/table-td-merge-split/">Merge and split table cells with JavaScript</a>
* @version 1.2.0
*/
REDIPS.table = (function () {
// methods declaration
var onMouseDown, // method attaches onMouseDown event listener to table cells
handlerOnMouseDown, // onMouseDown handler
merge, // method merges marked cells
mergeAuto, // method to auto merge cells with coordinates as parameters (Vertical only!).
mergeCells, // method merges/deletes table cells (used by merge_h & merge_v)
checkMerged, // method to check which cells are merged (Vertical only!).
maxCols, // method returns maximum number of columns in a table
split, // method splits merged cells (if cell has colspan/rowspan greater then 1)
setColor, // method to set cell colors
autoSetColor, // method to auto set cell colors with coordinates as parameters
getColor, // method to get coordinates and colors of colored cells
resetColor, //method to reset color of all cells
getTable, // method sets reference to the table (it's used in "merge" and "split" public methods)
mark, // method marks table cell
cellInit, // method attaches "mousedown" event listener and creates "redips" property to the newly created table cell
row, // method adds/deletes table row
column, // method adds/deletes table column
cellList, // method returns cell list with new coordinates
relocate, // relocate element nodes from source cell to the target cell
removeSelection, // method removes text selection
cellIndex, // method displays cellIndex (debug mode)
cellIgnore, // method removes onMouseDown even listener in case of active REDIPS.table.onMouseDown mode
getParentCell, // method returns first parent in tree what is TD or TH
getRowSpan, // method returns number of rowspan cells before current cell (in a row)
testFunction, // test method for debugging
// private properties
tables = [], // table collection
tdEvent, // (boolean) if set to true then cellInit will attach event listener to the table cell
showIndex, // (boolean) show cell index
// variables in the private scope revealed as public properties
color = {
cell: false, // color of marked cell
row: false, // color of marked row
column: false}, // color of marked column
markNonEmpty = true; // enable / disable marking not empty table cells.
/**
* Method attaches or removes onMouseDown event listener on TD elements depending on second parameter value (default is true).
* If third parameter is set to "classname" then tables will be selected by class name (named in first parameter).
* All found tables will be saved in internal array.
* Sending reference in this case will not be needed when calling merge or split method.
* Table cells marked with class name "ignore" will not have attached onMouseDown event listener (in short, these table cells will be ignored).
* @param {String|HTMLElement} el Container Id. TD elements within container will have added onMouseDown event listener.
* @param {Boolean} [flag] If set to true then onMouseDown event listener will be attached to every table cell.
* @param {String} [type] If set to "class name" then all tables with a given class name (first parameter is considered as class name) will be initialized. Default is container/table reference or container/table id.
* @example
* // activate onMouseDown event listener on cells within table with id="mainTable"
* REDIPS.table.onMouseDown('mainTable', true);
* <nl/>
* // activate onMouseDown event listener on cells for tables with class="blue"
* REDIPS.table.onMouseDown('blue', true, 'classname');
* @public
* @function
* @name REDIPS.table#onMouseDown
*/
onMouseDown = function (el, flag, type) {
let td, // collection of table cells within container
th, // collection of table header cells within container
i, t, // loop variables
getTables; // private method returns array
// method returns array with table nodes for a DOM node
getTables = function (el) {
let arr = [], // result array
nodes, // node collection
i; // loop variable
// collect table nodes
nodes = el.getElementsByTagName('table');
// open node loop and push to array
for (i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
}
// return result array
return arr;
};
// save event parameter to tdEvent private property
tdEvent = flag;
// if third parameter is set to "classname" then select tables by given class name (first parameter is considered as class name)
if (typeof (el) === 'string') {
if (type === 'classname') {
// collect all tables on the page
tables = getTables(document);
// open loop
for (i = 0; i < tables.length; i++) {
// if class name is not found then cut out table from tables collection
if (tables[i].className.indexOf(el) === -1) {
tables.splice(i, 1);
i--;
}
}
}
// first parameter is string and that should be id of container or id of a table
else {
// set object reference (overwrite el parameter)
el = document.getElementById(el);
}
}
// el is object
if (el && typeof (el) === 'object') {
// if container is already a table
if (el.nodeName === 'TABLE') {
tables[0] = el;
}
// else collect tables within container
else {
tables = getTables(el);
}
}
// at this point tables should contain one or more tables
for (t = 0; t < tables.length; t++) {
// collect table header cells from the selected table
th = tables[t].getElementsByTagName('th');
// loop goes through every collected TH
for (i = 0; i < th.length; i++) {
// add or remove event listener
cellInit(th[i]);
}
// collect table cells from the selected table
td = tables[t].getElementsByTagName('td');
// loop goes through every collected TD
for (i = 0; i < td.length; i++) {
// add or remove event listener
cellInit(td[i]);
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
};
/**
* Method attaches "mousedown" event listener to the newly created table cell or removes event listener if needed.
* @param {HTMLElement} c Table cell element.
* @private
* @memberOf REDIPS.table#
*/
cellInit = function (c) {
// if cell contains "ignore" class name then ignore this table cell
if (c.className.indexOf('ignore') > -1) {
return;
}
// if tdEvent is set to true then onMouseDown event listener will be attached to table cells
if (tdEvent === true) {
REDIPS.event.add(c, 'mousedown', handlerOnMouseDown);
}
else {
REDIPS.event.remove(c, 'mousedown', handlerOnMouseDown);
}
};
/**
* Method removes attached onMouseDown event listener.
* Sometimes is needed to manually ignore some cells in table after row/column is dynamically added.
* @param {HTMLElement|String} c Cell id or cell reference of table that should be ignored (onMouseDown event listener will be removed).
* @public
* @function
* @name REDIPS.table#cellIgnore
*/
cellIgnore = function (c) {
// if input parameter is string then overwrite it with cell reference
if (typeof (c) === 'string') {
c = document.getElementById(c);
}
// remove onMouseDown event listener
REDIPS.event.remove(c, 'mousedown', handlerOnMouseDown);
};
/**
* On mouseDown event attached to the table cell. If left mouse button is clicked and table cell is empty then cell will be marked or cleaned.
* This event handler is attached to every TD element.
* @param {Event} e Event information.
* @private
* @memberOf REDIPS.table#
*/
handlerOnMouseDown = function (e) {
let evt = e || window.event,
td = getParentCell(evt.target || evt.srcElement),
mouseButton,
empty;
// return if td is not defined
if (!td) {
return;
}
// set empty flag for clicked TD element
// http://forums.asp.net/t/1409248.aspx/1
empty = !!(/^\s*$/.test(td.innerHTML));
// if "markNonEmpty" is set to false and current cell is not empty then do nothing (just return from the event handler)
if (REDIPS.table.markNonEmpty === false && empty === false) {
return;
}
// define which mouse button was pressed
if (evt.which) {
mouseButton = evt.which;
}
else {
mouseButton = evt.button;
}
// if left mouse button is pressed and target cell is empty
if (mouseButton === 1 /* && td.childNodes.length === 0 */) {
// if custom property "redips" doesn't exist then create custom property
td.redips = td.redips || {};
// cell is already marked
if (td.redips.selected === true) {
// return original background color and reset selected flag
mark(false, td);
}
// cell is not marked
else {
mark(true, td);
}
}
};
/**
* Method returns first parent in DOM tree and that is TD or TH.
* Needed when there is rich content inside cell and selection onMouseDown event is triggered on cell content.
* @param {HTMLElement} node Node
* @private
* @function
* @memberOf REDIPS.table#
*/
getParentCell = function (node) {
if (!node) {
return null;
}
if (node.nodeName === 'TD' || node.nodeName === 'TH') {
return node;
}
return getParentCell(node.parentNode);
};
/**
* Method merges marked table cells horizontally or vertically.
* @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
* @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#merge
*/
merge = function (mode, clear, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current cell
rc1, // row/column maximum value for first loop
rc2, // row/column maximum value for second loop
marked, // (boolean) marked flag of current cell
span, // (integer) rowspan/colspan value
id, // cell id in format "1-2", "1-4" ...
cl, // cell list with new coordinates
j, // loop variable
mergedArr, // array of merged coordinates
first = {
index: -1, // index of first cell in sequence
span: -1}; // span value (colspan / rowspan) of first cell in sequence
// remove text selection
removeSelection();
mergedArr = [];
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// open loop for each table inside container
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// define maximum value for first loop (depending on mode)
rc1 = (mode === 'v') ? maxCols(tbl[t]) : tr.length;
// define maximum value for second loop (depending on mode)
rc2 = (mode === 'v') ? tr.length : maxCols(tbl[t]);
// first loop
for (let i = 0; i < rc1; i++) {
// reset marked cell index and span value
first.index = first.span = -1;
// second loop
for (j = 0; j <= rc2; j++) {
// set cell id (depending on horizontal/verical merging)
id = (mode === 'v') ? (j + '-' + i) : (i + '-' + j);
// if cell with given coordinates (in form like "1-2") exists, then process this cell
if (cl[id]) {
// set current cell
c = cl[id];
// if custom property "redips" doesn't exist then create custom property
c.redips = c.redips || {};
// set marked flag for current cell
marked = c ? c.redips.selected : false;
// set opposite span value
span = (mode === 'v') ? c.colSpan : c.rowSpan;
}
else {
marked = false;
}
// if first marked cell in sequence is found then remember index of first marked cell and span value
if (marked === true && first.index === -1) {
first.index = j;
first.span = span;
}
// sequence of marked cells is finished (naturally or next cell has different span value)
else if ((marked !== true && first.index > -1) || (first.span > -1 && first.span !== span)) {
// merge cells in a sequence (cell list, row/column, sequence start, sequence end, horizontal/vertical mode)
mergedArr.push([i, first.index, (j-1)]);
mergeCells(cl, i, first.index, j, mode, clear);
// reset marked cell index and span value
first.index = first.span = -1;
// if cell is selected then unmark and reset marked flag
// reseting marked flag is needed in case for last cell in column/row (so mergeCells () outside for loop will not execute)
if (marked === true) {
// if clear flag is set to true (or undefined) then clear marked cell after merging
if (clear === true || clear === undefined) {
mark(false, c);
}
marked = false;
}
}
// increase "j" counter for span value (needed for merging spanned cell and cell after when index is not in sequence)
if (cl[id]) {
j += (mode === 'v') ? c.rowSpan - 1 : c.colSpan - 1;
}
}
// if loop is finished and last cell is marked (needed in case when TD sequence include last cell in table row)
if (marked === true) {
mergedArr.push([i, first.index, j]);
mergeCells(cl, i, first.index, j, mode, clear);
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
return mergedArr;
};
/**
* Method to auto merge cells with coordinates as parameters (Vertical only!).
* @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
* @param {Array} coords An array of cell coordinates to be merged, in the format [col, rowStart, rowEnd].
* @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#mergeAuto
*/
mergeAuto = function (mode, coords, clear, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current cell
rc1, // row/column maximum value for first loop
rc2, // row/column maximum value for second loop
marked, // (boolean) marked flag of current cell
span, // (integer) rowspan/colspan value
id, // cell id in format "1-2", "1-4" ...
cl, // cell list with new coordinates
j, // loop variable
first = {
index: -1, // index of first cell in sequence
span: -1}; // span value (colspan / rowspan) of first cell in sequence
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// open loop for each table inside container
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
coords.forEach((list, ind) => {
mergeCells(cl, list[0], list[1], (list[2]+1), mode, clear);
});
}
// show cell index (if showIndex public property is set to true)
cellIndex();
};
/**
* Method merges and deletes table cells in sequence (horizontally or vertically).
* @param {Object} cl Cell list (output from cellList method)
* @param {Integer} idx Row/column index in which cells will be merged.
* @param {Integer} pos1 Cell sequence start in row/column.
* @param {Integer} pos2 Cell sequence end in row/column.
* @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
* @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
* @private
* @function
* @name REDIPS.table#mergeCells
*/
mergeCells = function (cl, idx, pos1, pos2, mode, clear) {
let span = 0, // set initial span value to 0
id, // cell id in format "1-2", "1-4" ...
fc, // reference of first cell in sequence
c; // reference of current cell
// set reference of first cell in sequence
fc = (mode === 'v') ? cl[pos1 + '-' + idx] : cl[idx + '-' + pos1];
// delete table cells and sum their colspans
for (let i = pos1 + 1; i < pos2; i++) {
// set cell id (depending on horizontal/verical merging)
id = (mode === 'v') ? (i + '-' + idx) : (idx + '-' + i);
// if cell with given coordinates (in form like "1-2") exists, then process this cell
if (cl[id]) {
// define next cell in column/row
c = cl[id];
// add colSpan/rowSpan value
span += (mode === 'v') ? c.rowSpan : c.colSpan;
// relocate content before deleting cell in merging process
relocate(c, fc);
// delete cell
c.parentNode.deleteCell(c.cellIndex);
}
}
// if cell exists
if (fc !== undefined) {
// vertical merging
if (mode === 'v') {
fc.rowSpan += span; // set new rowspan value
}
// horizontal merging
else {
fc.colSpan += span; // set new rowspan value
}
// if clear flag is set to true (or undefined) then set original background color and reset selected flag
if (clear === true || clear === undefined) {
mark(false, fc);
}
}
};
/**
* Method to check which cells are merged (Vertical only!).
* @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#checkMerged
*/
checkMerged = function (mode, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max, // maximum number of columns
results, // object containing new cell and new cell coordinates in the format [col, rowStart, rowEnd]
resultsArr; // array of results
resultsArr = [];
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define maximum number of columns in table
max = maxCols(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// loop TR
for (let i = 0; i < tr.length; i++) {
// define column number (depending on mode)
cols = (mode === 'v') ? max : tr[i].cells.length;
// loop TD
for (let j = 0; j < cols; j++) {
// split vertically
if (mode === 'v') {
// define current table cell
c = cl[i + '-' + j];
// if custom property "redips" doesn't exist then create custom property
if (c !== undefined) {
c.redips = c.redips || {};
}
// if marked cell is found and rowspan property is greater then 1
if (c !== undefined && c.rowSpan > 1) {
// get rowspaned cells before current cell (in a row)
rs = getRowSpan(cl, c, i, j);
cl = cellList(tbl[t]);
resultsArr.push([j,i,i+c.rowSpan-1]);
}
}
// split horizontally
else {
// define current table cell
c = tr[i].cells[j];
// if custom property "redips" doesn't exist then create custom property
c.redips = c.redips || {};
// if marked cell is found and cell has colspan property greater then 1
if (c.colSpan > 1) {
resultsArr.push([i,j,j+c.colSpan-1]);
}
}
// return original background color and reset selected flag (if cell exists)
if (c !== undefined) {
mark(false, c);
}
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
return resultsArr;
};
/**
* Method returns number of maximum columns in table (some row may contain merged cells).
* @param {HTMLElement|String} table TABLE element.
* @private
* @memberOf REDIPS.table#
*/
maxCols = function (table) {
let tr = table.rows, // define number of rows in current table
span, // sum of colSpan values
max = 0; // maximum number of columns
// if input parameter is string then overwrite it with table reference
if (typeof (table) === 'string') {
table = document.getElementById(table);
}
// open loop for each TR within table
for (let i = 0; i < tr.length; i++) {
// reset span value
span = 0;
// sum colspan value for each table cell
for (let j = 0; j < tr[i].cells.length; j++) {
span += tr[i].cells[j].colSpan || 1;
}
// set maximum value
if (span > max) {
max = span;
}
}
// return maximum value
return max;
};
/**
* Method splits marked table cell only if cell has colspan/rowspan greater then 1.
* @param {String} mode Split type: h - horizontally, v - vertically. Default is "h".
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#split
*/
split = function (mode, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max, // maximum number of columns
results, // object containing new cell and new cell coordinates
resultsArr; // array of results
results = {};
resultsArr = [];
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define maximum number of columns in table
max = maxCols(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// loop TR
for (let i = 0; i < tr.length; i++) {
// define column number (depending on mode)
cols = (mode === 'v') ? max : tr[i].cells.length;
// loop TD
for (let j = 0; j < cols; j++) {
// split vertically
if (mode === 'v') {
// define current table cell
c = cl[i + '-' + j];
// if custom property "redips" doesn't exist then create custom property
if (c !== undefined) {
c.redips = c.redips || {};
}
// if marked cell is found and rowspan property is greater then 1
if (c !== undefined && c.redips.selected === true && c.rowSpan > 1) {
// get rowspaned cells before current cell (in a row)
rs = getRowSpan(cl, c, i, j);
// insert new cell at last position of rowspan (consider rowspan cells before)
n = tr[i + c.rowSpan - 1].insertCell(j - rs);
results.cell = n;
results.coords = [i,j+c.rowSpan-1];
resultsArr.push(results);
results = {};
// set the same colspan value as it has current cell
n.colSpan = c.colSpan;
// decrease rowspan of marked cell
c.rowSpan -= 1;
// add "redips" property to the table cell and optionally event listener
cellInit(n);
// recreate cell list after vertical split (new cell is inserted)
cl = cellList(tbl[t]);
}
}
// split horizontally
else {
// define current table cell
c = tr[i].cells[j];
// if custom property "redips" doesn't exist then create custom property
c.redips = c.redips || {};
// if marked cell is found and cell has colspan property greater then 1
if (c.redips.selected === true && c.colSpan > 1) {
// increase cols (because new cell is inserted)
cols++;
// insert cell after current cell
n = tr[i].insertCell(j + 1);
results.cell = n;
results.coords = [i,j+c.colSpan-1];
resultsArr.push(results);
results = {};
// set the same rowspan value as it has current cell
n.rowSpan = c.rowSpan;
// decrease colspan of marked cell
c.colSpan -= 1;
// add "redips" property to the table cell and optionally event listener
cellInit(n);
}
}
// return original background color and reset selected flag (if cell exists)
if (c !== undefined) {
mark(false, c);
}
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
return resultsArr;
};
/**
* Method to set cell colors.
* @param {String} color Hex code of target color
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#setColor
*/
setColor = function (color, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max; // maximum number of columns
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define maximum number of columns in table
max = maxCols(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// loop TR
for (let i = 0; i < tr.length; i++) {
// define column number (depending on mode)
cols = max;
// loop TD
for (let j = 0; j < cols; j++) {
// define current table cell
c = cl[i + '-' + j];
if (c !== undefined) {
// if custom property "redips" doesn't exist then create custom property
c.redips = c.redips || {};
if (c.redips.selected === true) {
// return original background color and reset selected flag (if cell exists)
c.redips.background_old = color;
mark(false, c);
}
}
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
};
/**
* Method to auto set cell colors with coordinates as parameters.
* @param {colorArr} colorArr Array of coordinates and colours in the format of [col, row, color].
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#autoSetColor
*/
autoSetColor = function (colorArr, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max; // maximum number of columns
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
colorArr.forEach((list, ind) => {
c = cl[list[1] + '-' + list[0]];
if (c !== undefined) {
c.redips = c.redips || {};
// return original background color and reset selected flag (if cell exists)
c.redips.background_old = list[2];
mark(false, c);
}
});
}
// show cell index (if showIndex public property is set to true)
cellIndex();
};
/**
* Method to get coordinates and colors of colored cells.
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#getColor
*/
getColor = function (table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max, // maximum number of columns
results, // object containing new cell and new cell coordinates
resultsArr; // array of results
resultsArr = [];
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define maximum number of columns in table
max = maxCols(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// loop TR
for (let i = 0; i < tr.length; i++) {
// define column number (depending on mode)
cols = max;
// loop TD
for (let j = 0; j < cols; j++) {
// define current table cell
c = cl[i + '-' + j];
// return original background color and reset selected flag (if cell exists)
if (c !== undefined) {
// if custom property "redips" doesn't exist then create custom property
if (c.redips.background_old !== undefined && c.redips.background_old !== ""){
results = [j, i, c.redips.background_old];
resultsArr.push(results);
results = [];
}
}
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
return resultsArr;
};
/**
* Method to reset color of selected or all cells.
* @param {Boolean} [flag] if set to true, then all cells will be reset, if set to false, only selected cells will be reset.
* @param {HTMLElement|String} [table] Table id or table reference.
* @public
* @function
* @name REDIPS.table#resetColor
*/
resetColor = function (flag, table) {
let tbl, // table array (loaded from tables array or from table input parameter)
tr, // row reference in table
c, // current table cell
cl, // cell list with new coordinates
rs, // rowspan cells before
n, // reference of inserted table cell
cols, // number of columns (used in TD loop)
max; // maximum number of columns
// remove text selection
removeSelection();
// if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
tbl = (table === undefined) ? tables : getTable(table);
// TABLE loop
for (let t = 0; t < tbl.length; t++) {
// define cell list with new coordinates
cl = cellList(tbl[t]);
// define maximum number of columns in table
max = maxCols(tbl[t]);
// define row number in current table
tr = tbl[t].rows;
// loop TR
for (let i = 0; i < tr.length; i++) {
// define column number (depending on mode)
cols = max;
// loop TD
for (let j = 0; j < cols; j++) {
// define current table cell
c = cl[i + '-' + j];
if (c !== undefined) {
// if custom property "redips" doesn't exist then create custom property
c.redips = c.redips || {};
if (flag){
// return original background color and reset selected flag (if cell exists)
c.redips.background_old = "";
mark(false, c);
} else {
if (c.redips.selected === true) {
// return original background color and reset selected flag (if cell exists)
c.redips.background_old = "";
mark(false, c);
}
}
}
}
}
}
// show cell index (if showIndex public property is set to true)
cellIndex();
};
/**
* Method sets reference to table. It is used in "merge" and "split" public methods.
* @param {HTMLElement|String} table Table id or table reference.
* @return {Array} Returns empty array or array with one member (table node).
* @private
* @memberOf REDIPS.table#
*/
getTable = function (table) {
// define output array
let tbl = [];
// input parameter should exits
if (table !== undefined) {
// if table parameter is string then set reference and overwrite input parameter
if (typeof (table) === 'string') {
table = document.getElementById(table);
}
// set table reference if table is not null and table is object and node is TABLE
if (table && typeof (table) === 'object' && table.nodeName === 'TABLE') {
tbl[0] = table;
}
}
// return table reference as array
return tbl;
};
/**
* Add or delete table row. If index is omitted then index of last row will be used.
* @param {HTMLElement|String} table Table id or table reference.
* @param {String} mode Insert/delete table row
* @param {Integer} [index] Index where row will be inserted or deleted. Last row will be assumed if index is not defined.
* @return {HTMLElement} Returns reference of inserted row or NULL (in case of deleting row).
* @public
* @function
* @name REDIPS.table#row
*/
row = function (table, mode, index) {
let nc, // new cell
nr = null, // new row
fr, // reference of first row
c, // current cell reference
cl, // cell list
cols = 0, // number of columns
i, j, k; // loop variables
// remove text selection
removeSelection();
// if table is not object then input parameter is id and table parameter will be overwritten with table reference
if (typeof (table) !== 'object') {
table = document.getElementById(table);
}
// if index is not defined then index of the last row
if (index === undefined) {
index = -1;
}
// insert table row
if (mode === 'insert') {
// set reference of first row
fr = table.rows[0];
// define number of columns (it is colspan sum)
for (i = 0; i < fr.cells.length; i++) {
cols += fr.cells[i].colSpan;
}
// insert table row (insertRow returns reference to the newly created row)
nr = table.insertRow(index);
// insert table cells to the new row
for (i = 0; i < cols; i++) {
nc = nr.insertCell(i);
// add "redips" property to the table cell and optionally event listener
cellInit(nc);
}
// show cell index (if showIndex public property is set to true)
cellIndex();
}
// delete table row and update rowspan for cells in upper rows if needed
else {
// last row should not be deleted
if (table.rows.length === 1) {
return;
}
// delete last row
table.deleteRow(index);
// prepare cell list
cl = cellList(table);
// set new index for last row
index = table.rows.length - 1;
// set maximum number of columns that table has
cols = maxCols(table);
// open loop for each cell in last row
for (i = 0; i < cols; i++) {
// try to find cell in last row
c = cl[index + '-' + i];
// if cell doesn't exist then update colspan in upper cells
if (c === undefined) {
// open loop for cells up in column
for (j = index, k = 1; j >= 0; j--, k++) {
// try to find cell upper cell with rowspan value
c = cl[j + '-' + i];
// if cell is found then update rowspan value
if (c !== undefined) {
c.rowSpan = k;
break;
}
}
}
// if cell in last row has rowspan greater then 1
else if (c.rowSpan > 1) {