├── .gitignore
├── changes.txt
├── example00
├── index.html
├── script.js
└── style.css
├── example01
├── index.html
├── script.js
└── style.css
├── example02
├── index.html
├── script.js
└── style.css
├── header.js
├── index.html
├── readme.md
├── redips-table-min.js
├── redips-table-source.js
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | /.project
2 |
--------------------------------------------------------------------------------
/changes.txt:
--------------------------------------------------------------------------------
1 | Changes for REDIPS.table library
2 |
3 | 1.2.0 (2020-03-23)
4 | - REDIPS.table is checked with ESLint - cleaning
5 |
6 | 1.1.0
7 | - if table cell contains "ignore" class name then onmousedown event listener will not be attached to this table cell
8 | - in case of inserting table row, REDIPS.table.row() method will return row reference (otherwise it will return NULL)
9 | - added cell_ignore() method
10 | - fixed cell_index() method (tr reference is table.rows instead of table.getElementsByTagName('TR') - simpler solution and immune to inner tables)
11 | - added REDIPS.table.mark_nonempty public property (if set to false then not empty cells could not be marked)
12 |
13 | 1.0.4
14 | - added control to disable deleting last row or last column in the table
15 | - added minimized version redips-table-min.js
16 |
17 | 1.0.3
18 | - if onmousedown is not attached to table cells then appended row shouldn't listen onmousedown event also
19 |
20 | 1.0.2
21 | - added option to merge() method to leave or clear cells marked after merging (added boolean "clear" optional parameter)
--------------------------------------------------------------------------------
/example00/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Example 0: Table demo
12 |
13 |
14 |
15 |
16 |
17 |
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 |
--------------------------------------------------------------------------------
/example00/script.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 | /* eslint
3 | semi: ["error", "always"],
4 | indent: [2, "tab"],
5 | no-tabs: 0,
6 | no-multiple-empty-lines: ["error", {"max": 2, "maxEOF": 1}],
7 | one-var: ["error", "always"] */
8 | /* global REDIPS */
9 |
10 | /* enable strict mode */
11 | 'use strict';
12 |
13 | // create redips container
14 | let redips = {};
15 |
16 |
17 | // REDIPS.table initialization
18 | redips.init = function () {
19 | // define reference to the REDIPS.table object
20 | var rt = REDIPS.table;
21 | // activate onmousedown event listener on cells within table with id="mainTable"
22 | rt.onMouseDown('mainTable', true);
23 | // show cellIndex (it is nice for debugging)
24 | rt.cellIndex(true);
25 | // define background color for marked cell
26 | rt.color.cell = '#9BB3DA';
27 | };
28 |
29 |
30 | // function merges table cells
31 | redips.merge = function () {
32 | // first merge cells horizontally and leave cells marked
33 | REDIPS.table.merge('h', false);
34 | // and then merge cells vertically and clear cells (second parameter is true by default)
35 | REDIPS.table.merge('v');
36 | };
37 |
38 |
39 | // function splits table cells if colspan/rowspan is greater then 1
40 | // mode is 'h' or 'v' (cells should be marked before)
41 | redips.split = function (mode) {
42 | REDIPS.table.split(mode);
43 | };
44 |
45 |
46 | // insert/delete table row
47 | redips.row = function (type) {
48 | REDIPS.table.row('mainTable', type);
49 | };
50 |
51 |
52 | // insert/delete table column
53 | redips.column = function (type) {
54 | REDIPS.table.column('mainTable', type);
55 | };
56 |
57 |
58 | // add onload event listener
59 | if (window.addEventListener) {
60 | window.addEventListener('load', redips.init, false);
61 | }
62 | else if (window.attachEvent) {
63 | window.attachEvent('onload', redips.init);
64 | }
65 |
--------------------------------------------------------------------------------
/example00/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Darko Bunic
4 | http://www.redips.net/
5 | Mar, 2012.
6 |
7 | */
8 |
9 | body {
10 | font-family: arial;
11 | margin: 0px;
12 | }
13 |
14 | /* container */
15 | #myContainer {
16 | margin: 20px auto;
17 | border: 2px dashed LightBlue;
18 | font-size: 10pt;
19 | display: table;
20 | }
21 |
22 | /* both tables */
23 | div#myContainer table {
24 | /*border-collapse: collapse;*/
25 | border-collapse: separate;
26 | border-spacing: 1px;
27 | }
28 |
29 | /* toolbox table */
30 | #toolbox {
31 | background-color: #ccc;
32 | margin: 7px 7px 15px 7px;
33 | }
34 |
35 | /* main table */
36 | #mainTable {
37 | color: #777;
38 | background-color: #eee;
39 | margin: 7px;
40 | }
41 |
42 | /* table cells */
43 | div#myContainer td, div#myContainer th {
44 | border: 1px solid navy;
45 | height: 40px;
46 | width: 70px;
47 | text-align: center;
48 | padding: 2px;
49 | }
50 |
51 | /* button styles */
52 | .button {
53 | margin: 5px 10px;
54 | background-color: #6A93D4;
55 | color: white;
56 | border-width: 1px;
57 | width: 50px;
58 | padding: 0px;
59 | font-size: 12px;
60 | }
61 |
--------------------------------------------------------------------------------
/example01/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Example 1: 8 tables
12 |
13 |
14 |
15 |
16 |
17 |
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 |
--------------------------------------------------------------------------------
/example01/script.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 | /* eslint
3 | semi: ["error", "always"],
4 | indent: [2, "tab"],
5 | no-tabs: 0,
6 | no-multiple-empty-lines: ["error", {"max": 2, "maxEOF": 1}],
7 | one-var: ["error", "always"] */
8 | /* global REDIPS */
9 |
10 | /* enable strict mode */
11 | 'use strict';
12 |
13 | // create redips container
14 | let redips = {};
15 |
16 |
17 | // initialization
18 | redips.init = function () {
19 | // define reference to the REDIPS.table object
20 | var rt = REDIPS.table;
21 | // activate onmousedown event listener on cells for tables with class="blue"
22 | rt.onMouseDown('blue', true, 'classname');
23 | // show cellIndex (it is nice for debugging)
24 | rt.cellIndex(true);
25 | // define background color for marked cell
26 | rt.color.cell = '#32568E';
27 | };
28 |
29 |
30 | // function merges table cells
31 | redips.merge = function (mode) {
32 | REDIPS.table.merge(mode);
33 | };
34 |
35 |
36 | // function splits table cells if colspan/rowspan is greater then 1
37 | redips.split = function (mode) {
38 | REDIPS.table.split(mode);
39 | };
40 |
41 |
42 | // add onload event listener
43 | if (window.addEventListener) {
44 | window.addEventListener('load', redips.init, false);
45 | }
46 | else if (window.attachEvent) {
47 | window.attachEvent('onload', redips.init);
48 | }
49 |
--------------------------------------------------------------------------------
/example01/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Darko Bunic
4 | http://www.redips.net/
5 | Mar, 2012.
6 |
7 | */
8 |
9 | body {
10 | font-family: arial;
11 | margin: 0px;
12 | }
13 |
14 | /* container */
15 | #container {
16 | margin: 20px auto;
17 | border: 2px dashed LightBlue;
18 | font-size: 10pt;
19 | width: 400px;
20 | display: table;
21 | }
22 |
23 | /* styles for all tables */
24 | div#container table {
25 | /*border-collapse: collapse;*/
26 | border-collapse: separate;
27 | border-spacing: 1px;
28 | color: white;
29 | }
30 |
31 | /* place tables side by side */
32 | div#row1 table,
33 | div#row2 table {
34 | float: left;
35 | margin: 7px;
36 | }
37 |
38 | /* define background color for blue & orange tables */
39 | .blue {
40 | background-color: #C7D5ED;
41 | }
42 | .orange {
43 | background-color: #FFEFD2;
44 | }
45 |
46 | /* toolbox table */
47 | #toolbox {
48 | background-color: #ccc;
49 | margin: 7px 7px 15px 7px;
50 | }
51 |
52 |
53 | /* table cells */
54 | div#container td {
55 | border: 1px solid navy;
56 | height: 30px;
57 | width: 50px;
58 | text-align: center;
59 | padding: 2px;
60 | }
61 |
62 | /* button styles */
63 | .button {
64 | margin: 5px 10px;
65 | background-color: #6A93D4;
66 | color: white;
67 | border-width: 1px;
68 | width: 60px;
69 | padding: 0px;
70 | }
71 |
--------------------------------------------------------------------------------
/example02/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Example 2: Merge / split table cells
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 |
--------------------------------------------------------------------------------
/example02/script.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 | /* eslint
3 | semi: ["error", "always"],
4 | indent: [2, "tab"],
5 | no-tabs: 0,
6 | no-multiple-empty-lines: ["error", {"max": 2, "maxEOF": 1}],
7 | one-var: ["error", "always"] */
8 | /* global REDIPS */
9 |
10 | /* enable strict mode */
11 | 'use strict';
12 |
13 | // create redips container
14 | let redips = {};
15 |
16 |
17 | // merge cells in first table in second row
18 | redips.merge1 = function () {
19 | // mark cells for merging (cells should be marked in a sequence)
20 | REDIPS.table.mark(true, 'table1', 1, 1);
21 | REDIPS.table.mark(true, 'table1', 1, 2);
22 | REDIPS.table.mark(true, 'table1', 1, 3);
23 | // merge cells:
24 | // 'h' - horizontally
25 | // true - clear mark after merging
26 | // 'table1' - table id
27 | REDIPS.table.merge('h', true, 'table1');
28 | };
29 |
30 |
31 | // function splits cell with defined id
32 | redips.split1 = function () {
33 | // first mark cell with id="c1"
34 | REDIPS.table.mark(true, 'c1');
35 | // and then split marked cell in table2
36 | REDIPS.table.split('v', 'table2');
37 | };
38 |
--------------------------------------------------------------------------------
/example02/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Darko Bunic
4 | http://www.redips.net/
5 | Mar, 2012.
6 |
7 | */
8 |
9 | body {
10 | font-family: arial;
11 | margin: 0px;
12 | }
13 |
14 | /* container */
15 | #container {
16 | margin: 20px auto;
17 | border: 2px dashed LightBlue;
18 | font-size: 10pt;
19 | display: table;
20 | }
21 |
22 | /* styles for all tables */
23 | div#container table {
24 | /*border-collapse: collapse;*/
25 | border-collapse: separate;
26 | border-spacing: 1px;
27 | background-color: #eee;
28 | margin: 7px;
29 | }
30 |
31 | /* toolbox table */
32 | .toolbox {
33 | background-color: #ccc;
34 | margin: 7px 7px 15px 7px;
35 | }
36 |
37 | /* table cells */
38 | div#container td {
39 | border: 1px solid navy;
40 | height: 50px;
41 | width: 80px;
42 | text-align: center;
43 | padding: 2px;
44 | }
45 |
46 | /* button styles */
47 | .button {
48 | margin: 5px 10px;
49 | background-color: #6A93D4;
50 | color: white;
51 | border-width: 1px;
52 | width: 60px;
53 | padding: 0px;
54 | }
55 |
56 | /* trash cell */
57 | .trash {
58 | color: white;
59 | background-color: #6386BD;
60 | }
--------------------------------------------------------------------------------
/header.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 | /* eslint
3 | semi: ["error", "always"],
4 | indent: [2, "tab"],
5 | no-tabs: 0,
6 | no-multiple-empty-lines: ["error", {"max": 2, "maxEOF": 1}],
7 | one-var: ["error", "always"],
8 | no-trailing-spaces: ["error", { "ignoreComments": true }] */
9 |
10 | /* enable strict mode */
11 | 'use strict';
12 |
13 | // global variables
14 | var redipsURL = redipsURL || '/javascript/table-td-merge-split/', // eslint-disable-line no-use-before-define
15 | headerInit;
16 |
17 | // header initialization
18 | headerInit = function () {
19 | let header = document.createElement('div'),
20 | title = document.title,
21 | href = window.location.href.split('/'),
22 | indexLink = 'index';
23 | // index link is not needed for main page
24 | if (href[href.length - 2].startsWith('REDIPS')) {
25 | indexLink = '';
26 | }
27 | // add "header" DIV element
28 | document.body.insertBefore(header, document.body.firstChild);
29 | // apply inner HTML
30 | header.innerHTML = '' + title + '
' +
31 | '' +
32 | '' + indexLink + '
';
33 | };
34 |
35 | // add onload event listener
36 | if (window.addEventListener) {
37 | window.addEventListener('load', headerInit, false);
38 | }
39 | else if (window.attachEvent) {
40 | window.attachEvent('onload', headerInit);
41 | }
42 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | www.redips.net
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | REDIPS.table 1.2.0
2 | ============
3 |
4 | ## What's REDIPS.table?
5 |
6 | REDIPS.table is a JavaScript library which enables dynamic merging and splitting table cells.
7 | It is possible to activate onMouseDown event listeners on TD element to interactively mark cells with mouse button.
8 |
9 | ## Features
10 |
11 | * merge / split table cells
12 | * add / remove table row
13 | * add / remove table column
14 | * enable / disable marking not empty table cells
15 |
16 | ## Public methods
17 |
18 | * REDIPS.table.onMouseDown() - activate onMouseDown event listener on table cells
19 | * REDIPS.table.mark() - select / deselect table cell
20 | * REDIPS.table.merge() - merge horizontally / vertically marked table cells in a sequence
21 | * REDIPS.table.split() - split horizontally / vertically marked table cells (only cells with colspan / rowspan greater than 1)
22 | * REDIPS.table.row() - add / remove table row
23 | * REDIPS.table.column() - add / remove table column
24 | * REDIPS.table.cellIndex() - display cell index (useful for demo / debugging)
25 | * REDIPS.table.cellIgnore() - remove onMouseDown even listener from table cell in case of active REDIPS.table.onMouseDown mode
26 |
27 | ## Documentation
28 |
29 | Reference documentation with list of public properties and methods contained in REDIPS.table library.
30 |
31 | * [http://www.redips.net/javascript/redips-table-documentation/](http://www.redips.net/javascript/redips-table-documentation/)
32 |
33 | ## Demo
34 |
35 | Live demo shows REDIPS.table library in action:
36 |
37 | * [http://www.redips.net/javascript/table-td-merge-split/](http://www.redips.net/javascript/table-td-merge-split/)
38 |
39 |
--------------------------------------------------------------------------------
/redips-table-min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2020, www.redips.net All rights reserved.
3 | Code licensed under the BSD License: http://www.redips.net/license/
4 | http://www.redips.net/javascript/table-td-merge-split/
5 | Version 1.2.0
6 | Mar 23, 2020.
7 | */
8 | var REDIPS=REDIPS||{};
9 | REDIPS.table=function(){var e=[],q,m;var t=function(a){-1d&&(d=a)}return d};var C=function(a){var b=
11 | [];void 0!==a&&("string"===typeof a&&(a=document.getElementById(a)),a&&"object"===typeof a&&"TABLE"===a.nodeName&&(b[0]=a));return b};var u=function(a,b,d,c){if("boolean"===typeof a){if("string"===typeof b)b=document.getElementById(b);else if("object"!==typeof b)return;"TABLE"===b.nodeName&&(b=v(b),b=b[d+"-"+c]);!b||"TD"!==b.nodeName&&"TH"!==b.nodeName||(b.redips=b.redips||{},"string"===typeof REDIPS.table.color.cell&&(!0===a?(b.redips.background_old=b.style.backgroundColor,b.style.backgroundColor=
12 | REDIPS.table.color.cell):b.style.backgroundColor=b.redips.background_old),b.redips.selected=a)}};var y=function(){if(window.getSelection)window.getSelection().removeAllRanges();else if(document.selection&&"Text"===document.selection.type)try{document.selection.empty()}catch(a){}};var v=function(a){var b=[],d={},c;var f=a.rows;for(var e=0;eMerge and split table cells with JavaScript
33 | * @version 1.2.0
34 | */
35 | REDIPS.table = (function () {
36 | // methods declaration
37 | var onMouseDown, // method attaches onMouseDown event listener to table cells
38 | handlerOnMouseDown, // onMouseDown handler
39 | merge, // method merges marked cells
40 | mergeAuto, // method to auto merge cells with coordinates as parameters (Vertical only!).
41 | mergeCells, // method merges/deletes table cells (used by merge_h & merge_v)
42 | checkMerged, // method to check which cells are merged (Vertical only!).
43 | maxCols, // method returns maximum number of columns in a table
44 | split, // method splits merged cells (if cell has colspan/rowspan greater then 1)
45 | setColor, // method to set cell colors
46 | autoSetColor, // method to auto set cell colors with coordinates as parameters
47 | getColor, // method to get coordinates and colors of colored cells
48 | resetColor, //method to reset color of all cells
49 | getTable, // method sets reference to the table (it's used in "merge" and "split" public methods)
50 | mark, // method marks table cell
51 | cellInit, // method attaches "mousedown" event listener and creates "redips" property to the newly created table cell
52 | row, // method adds/deletes table row
53 | column, // method adds/deletes table column
54 | cellList, // method returns cell list with new coordinates
55 | relocate, // relocate element nodes from source cell to the target cell
56 | removeSelection, // method removes text selection
57 | cellIndex, // method displays cellIndex (debug mode)
58 | cellIgnore, // method removes onMouseDown even listener in case of active REDIPS.table.onMouseDown mode
59 | getParentCell, // method returns first parent in tree what is TD or TH
60 | getRowSpan, // method returns number of rowspan cells before current cell (in a row)
61 | testFunction, // test method for debugging
62 |
63 | // private properties
64 | tables = [], // table collection
65 | tdEvent, // (boolean) if set to true then cellInit will attach event listener to the table cell
66 | showIndex, // (boolean) show cell index
67 |
68 | // variables in the private scope revealed as public properties
69 | color = {
70 | cell: false, // color of marked cell
71 | row: false, // color of marked row
72 | column: false}, // color of marked column
73 | markNonEmpty = true; // enable / disable marking not empty table cells.
74 |
75 |
76 | /**
77 | * Method attaches or removes onMouseDown event listener on TD elements depending on second parameter value (default is true).
78 | * If third parameter is set to "classname" then tables will be selected by class name (named in first parameter).
79 | * All found tables will be saved in internal array.
80 | * Sending reference in this case will not be needed when calling merge or split method.
81 | * Table cells marked with class name "ignore" will not have attached onMouseDown event listener (in short, these table cells will be ignored).
82 | * @param {String|HTMLElement} el Container Id. TD elements within container will have added onMouseDown event listener.
83 | * @param {Boolean} [flag] If set to true then onMouseDown event listener will be attached to every table cell.
84 | * @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.
85 | * @example
86 | * // activate onMouseDown event listener on cells within table with id="mainTable"
87 | * REDIPS.table.onMouseDown('mainTable', true);
88 | *
89 | * // activate onMouseDown event listener on cells for tables with class="blue"
90 | * REDIPS.table.onMouseDown('blue', true, 'classname');
91 | * @public
92 | * @function
93 | * @name REDIPS.table#onMouseDown
94 | */
95 | onMouseDown = function (el, flag, type) {
96 | let td, // collection of table cells within container
97 | th, // collection of table header cells within container
98 | i, t, // loop variables
99 | getTables; // private method returns array
100 | // method returns array with table nodes for a DOM node
101 | getTables = function (el) {
102 | let arr = [], // result array
103 | nodes, // node collection
104 | i; // loop variable
105 | // collect table nodes
106 | nodes = el.getElementsByTagName('table');
107 | // open node loop and push to array
108 | for (i = 0; i < nodes.length; i++) {
109 | arr.push(nodes[i]);
110 | }
111 | // return result array
112 | return arr;
113 | };
114 | // save event parameter to tdEvent private property
115 | tdEvent = flag;
116 | // if third parameter is set to "classname" then select tables by given class name (first parameter is considered as class name)
117 | if (typeof (el) === 'string') {
118 | if (type === 'classname') {
119 | // collect all tables on the page
120 | tables = getTables(document);
121 | // open loop
122 | for (i = 0; i < tables.length; i++) {
123 | // if class name is not found then cut out table from tables collection
124 | if (tables[i].className.indexOf(el) === -1) {
125 | tables.splice(i, 1);
126 | i--;
127 | }
128 | }
129 | }
130 | // first parameter is string and that should be id of container or id of a table
131 | else {
132 | // set object reference (overwrite el parameter)
133 | el = document.getElementById(el);
134 | }
135 | }
136 | // el is object
137 | if (el && typeof (el) === 'object') {
138 | // if container is already a table
139 | if (el.nodeName === 'TABLE') {
140 | tables[0] = el;
141 | }
142 | // else collect tables within container
143 | else {
144 | tables = getTables(el);
145 | }
146 | }
147 | // at this point tables should contain one or more tables
148 | for (t = 0; t < tables.length; t++) {
149 | // collect table header cells from the selected table
150 | th = tables[t].getElementsByTagName('th');
151 | // loop goes through every collected TH
152 | for (i = 0; i < th.length; i++) {
153 | // add or remove event listener
154 | cellInit(th[i]);
155 | }
156 |
157 | // collect table cells from the selected table
158 | td = tables[t].getElementsByTagName('td');
159 | // loop goes through every collected TD
160 | for (i = 0; i < td.length; i++) {
161 | // add or remove event listener
162 | cellInit(td[i]);
163 | }
164 | }
165 | // show cell index (if showIndex public property is set to true)
166 | cellIndex();
167 | };
168 |
169 |
170 | /**
171 | * Method attaches "mousedown" event listener to the newly created table cell or removes event listener if needed.
172 | * @param {HTMLElement} c Table cell element.
173 | * @private
174 | * @memberOf REDIPS.table#
175 | */
176 | cellInit = function (c) {
177 | // if cell contains "ignore" class name then ignore this table cell
178 | if (c.className.indexOf('ignore') > -1) {
179 | return;
180 | }
181 | // if tdEvent is set to true then onMouseDown event listener will be attached to table cells
182 | if (tdEvent === true) {
183 | REDIPS.event.add(c, 'mousedown', handlerOnMouseDown);
184 | }
185 | else {
186 | REDIPS.event.remove(c, 'mousedown', handlerOnMouseDown);
187 | }
188 | };
189 |
190 |
191 | /**
192 | * Method removes attached onMouseDown event listener.
193 | * Sometimes is needed to manually ignore some cells in table after row/column is dynamically added.
194 | * @param {HTMLElement|String} c Cell id or cell reference of table that should be ignored (onMouseDown event listener will be removed).
195 | * @public
196 | * @function
197 | * @name REDIPS.table#cellIgnore
198 | */
199 | cellIgnore = function (c) {
200 | // if input parameter is string then overwrite it with cell reference
201 | if (typeof (c) === 'string') {
202 | c = document.getElementById(c);
203 | }
204 | // remove onMouseDown event listener
205 | REDIPS.event.remove(c, 'mousedown', handlerOnMouseDown);
206 | };
207 |
208 |
209 | /**
210 | * 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.
211 | * This event handler is attached to every TD element.
212 | * @param {Event} e Event information.
213 | * @private
214 | * @memberOf REDIPS.table#
215 | */
216 | handlerOnMouseDown = function (e) {
217 | let evt = e || window.event,
218 | td = getParentCell(evt.target || evt.srcElement),
219 | mouseButton,
220 | empty;
221 | // return if td is not defined
222 | if (!td) {
223 | return;
224 | }
225 | // set empty flag for clicked TD element
226 | // http://forums.asp.net/t/1409248.aspx/1
227 | empty = !!(/^\s*$/.test(td.innerHTML));
228 | // if "markNonEmpty" is set to false and current cell is not empty then do nothing (just return from the event handler)
229 | if (REDIPS.table.markNonEmpty === false && empty === false) {
230 | return;
231 | }
232 | // define which mouse button was pressed
233 | if (evt.which) {
234 | mouseButton = evt.which;
235 | }
236 | else {
237 | mouseButton = evt.button;
238 | }
239 | // if left mouse button is pressed and target cell is empty
240 | if (mouseButton === 1 /* && td.childNodes.length === 0 */) {
241 | // if custom property "redips" doesn't exist then create custom property
242 | td.redips = td.redips || {};
243 | // cell is already marked
244 | if (td.redips.selected === true) {
245 | // return original background color and reset selected flag
246 | mark(false, td);
247 | }
248 | // cell is not marked
249 | else {
250 | mark(true, td);
251 | }
252 | }
253 | };
254 |
255 | /**
256 | * Method returns first parent in DOM tree and that is TD or TH.
257 | * Needed when there is rich content inside cell and selection onMouseDown event is triggered on cell content.
258 | * @param {HTMLElement} node Node
259 | * @private
260 | * @function
261 | * @memberOf REDIPS.table#
262 | */
263 | getParentCell = function (node) {
264 | if (!node) {
265 | return null;
266 | }
267 | if (node.nodeName === 'TD' || node.nodeName === 'TH') {
268 | return node;
269 | }
270 | return getParentCell(node.parentNode);
271 | };
272 |
273 | /**
274 | * Method merges marked table cells horizontally or vertically.
275 | * @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
276 | * @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
277 | * @param {HTMLElement|String} [table] Table id or table reference.
278 | * @public
279 | * @function
280 | * @name REDIPS.table#merge
281 | */
282 | merge = function (mode, clear, table) {
283 | let tbl, // table array (loaded from tables array or from table input parameter)
284 | tr, // row reference in table
285 | c, // current cell
286 | rc1, // row/column maximum value for first loop
287 | rc2, // row/column maximum value for second loop
288 | marked, // (boolean) marked flag of current cell
289 | span, // (integer) rowspan/colspan value
290 | id, // cell id in format "1-2", "1-4" ...
291 | cl, // cell list with new coordinates
292 | j, // loop variable
293 | mergedArr, // array of merged coordinates
294 | first = {
295 | index: -1, // index of first cell in sequence
296 | span: -1}; // span value (colspan / rowspan) of first cell in sequence
297 | // remove text selection
298 | removeSelection();
299 | mergedArr = [];
300 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
301 | tbl = (table === undefined) ? tables : getTable(table);
302 | // open loop for each table inside container
303 | for (let t = 0; t < tbl.length; t++) {
304 | // define cell list with new coordinates
305 | cl = cellList(tbl[t]);
306 | // define row number in current table
307 | tr = tbl[t].rows;
308 | // define maximum value for first loop (depending on mode)
309 | rc1 = (mode === 'v') ? maxCols(tbl[t]) : tr.length;
310 | // define maximum value for second loop (depending on mode)
311 | rc2 = (mode === 'v') ? tr.length : maxCols(tbl[t]);
312 | // first loop
313 | for (let i = 0; i < rc1; i++) {
314 | // reset marked cell index and span value
315 | first.index = first.span = -1;
316 | // second loop
317 | for (j = 0; j <= rc2; j++) {
318 | // set cell id (depending on horizontal/verical merging)
319 | id = (mode === 'v') ? (j + '-' + i) : (i + '-' + j);
320 | // if cell with given coordinates (in form like "1-2") exists, then process this cell
321 | if (cl[id]) {
322 | // set current cell
323 | c = cl[id];
324 | // if custom property "redips" doesn't exist then create custom property
325 | c.redips = c.redips || {};
326 | // set marked flag for current cell
327 | marked = c ? c.redips.selected : false;
328 | // set opposite span value
329 | span = (mode === 'v') ? c.colSpan : c.rowSpan;
330 | }
331 | else {
332 | marked = false;
333 | }
334 | // if first marked cell in sequence is found then remember index of first marked cell and span value
335 | if (marked === true && first.index === -1) {
336 | first.index = j;
337 | first.span = span;
338 | }
339 | // sequence of marked cells is finished (naturally or next cell has different span value)
340 | else if ((marked !== true && first.index > -1) || (first.span > -1 && first.span !== span)) {
341 | // merge cells in a sequence (cell list, row/column, sequence start, sequence end, horizontal/vertical mode)
342 | mergedArr.push([i, first.index, (j-1)]);
343 | mergeCells(cl, i, first.index, j, mode, clear);
344 | // reset marked cell index and span value
345 | first.index = first.span = -1;
346 | // if cell is selected then unmark and reset marked flag
347 | // reseting marked flag is needed in case for last cell in column/row (so mergeCells () outside for loop will not execute)
348 | if (marked === true) {
349 | // if clear flag is set to true (or undefined) then clear marked cell after merging
350 | if (clear === true || clear === undefined) {
351 | mark(false, c);
352 | }
353 | marked = false;
354 | }
355 | }
356 | // increase "j" counter for span value (needed for merging spanned cell and cell after when index is not in sequence)
357 | if (cl[id]) {
358 | j += (mode === 'v') ? c.rowSpan - 1 : c.colSpan - 1;
359 | }
360 | }
361 | // if loop is finished and last cell is marked (needed in case when TD sequence include last cell in table row)
362 | if (marked === true) {
363 | mergedArr.push([i, first.index, j]);
364 | mergeCells(cl, i, first.index, j, mode, clear);
365 | }
366 | }
367 | }
368 | // show cell index (if showIndex public property is set to true)
369 | cellIndex();
370 | return mergedArr;
371 | };
372 |
373 | /**
374 | * Method to auto merge cells with coordinates as parameters (Vertical only!).
375 | * @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
376 | * @param {Array} coords An array of cell coordinates to be merged, in the format [col, rowStart, rowEnd].
377 | * @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
378 | * @param {HTMLElement|String} [table] Table id or table reference.
379 | * @public
380 | * @function
381 | * @name REDIPS.table#mergeAuto
382 | */
383 |
384 | mergeAuto = function (mode, coords, clear, table) {
385 | let tbl, // table array (loaded from tables array or from table input parameter)
386 | tr, // row reference in table
387 | c, // current cell
388 | rc1, // row/column maximum value for first loop
389 | rc2, // row/column maximum value for second loop
390 | marked, // (boolean) marked flag of current cell
391 | span, // (integer) rowspan/colspan value
392 | id, // cell id in format "1-2", "1-4" ...
393 | cl, // cell list with new coordinates
394 | j, // loop variable
395 | first = {
396 | index: -1, // index of first cell in sequence
397 | span: -1}; // span value (colspan / rowspan) of first cell in sequence
398 | // remove text selection
399 | removeSelection();
400 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
401 | tbl = (table === undefined) ? tables : getTable(table);
402 | // open loop for each table inside container
403 | for (let t = 0; t < tbl.length; t++) {
404 | // define cell list with new coordinates
405 | cl = cellList(tbl[t]);
406 | // define row number in current table
407 | tr = tbl[t].rows;
408 | coords.forEach((list, ind) => {
409 | mergeCells(cl, list[0], list[1], (list[2]+1), mode, clear);
410 | });
411 | }
412 | // show cell index (if showIndex public property is set to true)
413 | cellIndex();
414 | };
415 |
416 | /**
417 | * Method merges and deletes table cells in sequence (horizontally or vertically).
418 | * @param {Object} cl Cell list (output from cellList method)
419 | * @param {Integer} idx Row/column index in which cells will be merged.
420 | * @param {Integer} pos1 Cell sequence start in row/column.
421 | * @param {Integer} pos2 Cell sequence end in row/column.
422 | * @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
423 | * @param {Boolean} [clear] true - cells will be clean (without mark) after merging, false - cells will remain marked after merging. Default is "true".
424 | * @private
425 | * @function
426 | * @name REDIPS.table#mergeCells
427 | */
428 | mergeCells = function (cl, idx, pos1, pos2, mode, clear) {
429 | let span = 0, // set initial span value to 0
430 | id, // cell id in format "1-2", "1-4" ...
431 | fc, // reference of first cell in sequence
432 | c; // reference of current cell
433 | // set reference of first cell in sequence
434 | fc = (mode === 'v') ? cl[pos1 + '-' + idx] : cl[idx + '-' + pos1];
435 | // delete table cells and sum their colspans
436 | for (let i = pos1 + 1; i < pos2; i++) {
437 | // set cell id (depending on horizontal/verical merging)
438 | id = (mode === 'v') ? (i + '-' + idx) : (idx + '-' + i);
439 | // if cell with given coordinates (in form like "1-2") exists, then process this cell
440 | if (cl[id]) {
441 | // define next cell in column/row
442 | c = cl[id];
443 | // add colSpan/rowSpan value
444 | span += (mode === 'v') ? c.rowSpan : c.colSpan;
445 | // relocate content before deleting cell in merging process
446 | relocate(c, fc);
447 | // delete cell
448 | c.parentNode.deleteCell(c.cellIndex);
449 | }
450 | }
451 | // if cell exists
452 | if (fc !== undefined) {
453 | // vertical merging
454 | if (mode === 'v') {
455 | fc.rowSpan += span; // set new rowspan value
456 | }
457 | // horizontal merging
458 | else {
459 | fc.colSpan += span; // set new rowspan value
460 | }
461 | // if clear flag is set to true (or undefined) then set original background color and reset selected flag
462 | if (clear === true || clear === undefined) {
463 | mark(false, fc);
464 | }
465 | }
466 | };
467 |
468 | /**
469 | * Method to check which cells are merged (Vertical only!).
470 | * @param {String} mode Merge type: h - horizontally, v - vertically. Default is "h".
471 | * @param {HTMLElement|String} [table] Table id or table reference.
472 | * @public
473 | * @function
474 | * @name REDIPS.table#checkMerged
475 | */
476 |
477 | checkMerged = function (mode, table) {
478 | let tbl, // table array (loaded from tables array or from table input parameter)
479 | tr, // row reference in table
480 | c, // current table cell
481 | cl, // cell list with new coordinates
482 | rs, // rowspan cells before
483 | n, // reference of inserted table cell
484 | cols, // number of columns (used in TD loop)
485 | max, // maximum number of columns
486 | results, // object containing new cell and new cell coordinates in the format [col, rowStart, rowEnd]
487 | resultsArr; // array of results
488 |
489 | resultsArr = [];
490 | // remove text selection
491 | removeSelection();
492 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
493 | tbl = (table === undefined) ? tables : getTable(table);
494 | // TABLE loop
495 | for (let t = 0; t < tbl.length; t++) {
496 | // define cell list with new coordinates
497 | cl = cellList(tbl[t]);
498 | // define maximum number of columns in table
499 | max = maxCols(tbl[t]);
500 | // define row number in current table
501 | tr = tbl[t].rows;
502 | // loop TR
503 | for (let i = 0; i < tr.length; i++) {
504 | // define column number (depending on mode)
505 | cols = (mode === 'v') ? max : tr[i].cells.length;
506 | // loop TD
507 | for (let j = 0; j < cols; j++) {
508 | // split vertically
509 | if (mode === 'v') {
510 | // define current table cell
511 | c = cl[i + '-' + j];
512 | // if custom property "redips" doesn't exist then create custom property
513 | if (c !== undefined) {
514 | c.redips = c.redips || {};
515 | }
516 | // if marked cell is found and rowspan property is greater then 1
517 | if (c !== undefined && c.rowSpan > 1) {
518 | // get rowspaned cells before current cell (in a row)
519 | rs = getRowSpan(cl, c, i, j);
520 | cl = cellList(tbl[t]);
521 | resultsArr.push([j,i,i+c.rowSpan-1]);
522 | }
523 | }
524 | // split horizontally
525 | else {
526 | // define current table cell
527 | c = tr[i].cells[j];
528 | // if custom property "redips" doesn't exist then create custom property
529 | c.redips = c.redips || {};
530 | // if marked cell is found and cell has colspan property greater then 1
531 | if (c.colSpan > 1) {
532 | resultsArr.push([i,j,j+c.colSpan-1]);
533 | }
534 | }
535 | // return original background color and reset selected flag (if cell exists)
536 | if (c !== undefined) {
537 | mark(false, c);
538 | }
539 | }
540 | }
541 | }
542 | // show cell index (if showIndex public property is set to true)
543 | cellIndex();
544 | return resultsArr;
545 | };
546 |
547 | /**
548 | * Method returns number of maximum columns in table (some row may contain merged cells).
549 | * @param {HTMLElement|String} table TABLE element.
550 | * @private
551 | * @memberOf REDIPS.table#
552 | */
553 | maxCols = function (table) {
554 | let tr = table.rows, // define number of rows in current table
555 | span, // sum of colSpan values
556 | max = 0; // maximum number of columns
557 | // if input parameter is string then overwrite it with table reference
558 | if (typeof (table) === 'string') {
559 | table = document.getElementById(table);
560 | }
561 | // open loop for each TR within table
562 | for (let i = 0; i < tr.length; i++) {
563 | // reset span value
564 | span = 0;
565 | // sum colspan value for each table cell
566 | for (let j = 0; j < tr[i].cells.length; j++) {
567 | span += tr[i].cells[j].colSpan || 1;
568 | }
569 | // set maximum value
570 | if (span > max) {
571 | max = span;
572 | }
573 | }
574 | // return maximum value
575 | return max;
576 | };
577 |
578 |
579 | /**
580 | * Method splits marked table cell only if cell has colspan/rowspan greater then 1.
581 | * @param {String} mode Split type: h - horizontally, v - vertically. Default is "h".
582 | * @param {HTMLElement|String} [table] Table id or table reference.
583 | * @public
584 | * @function
585 | * @name REDIPS.table#split
586 | */
587 | split = function (mode, table) {
588 | let tbl, // table array (loaded from tables array or from table input parameter)
589 | tr, // row reference in table
590 | c, // current table cell
591 | cl, // cell list with new coordinates
592 | rs, // rowspan cells before
593 | n, // reference of inserted table cell
594 | cols, // number of columns (used in TD loop)
595 | max, // maximum number of columns
596 | results, // object containing new cell and new cell coordinates
597 | resultsArr; // array of results
598 |
599 | results = {};
600 | resultsArr = [];
601 | // remove text selection
602 | removeSelection();
603 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
604 | tbl = (table === undefined) ? tables : getTable(table);
605 | // TABLE loop
606 | for (let t = 0; t < tbl.length; t++) {
607 | // define cell list with new coordinates
608 | cl = cellList(tbl[t]);
609 | // define maximum number of columns in table
610 | max = maxCols(tbl[t]);
611 | // define row number in current table
612 | tr = tbl[t].rows;
613 | // loop TR
614 | for (let i = 0; i < tr.length; i++) {
615 | // define column number (depending on mode)
616 | cols = (mode === 'v') ? max : tr[i].cells.length;
617 | // loop TD
618 | for (let j = 0; j < cols; j++) {
619 | // split vertically
620 | if (mode === 'v') {
621 | // define current table cell
622 | c = cl[i + '-' + j];
623 | // if custom property "redips" doesn't exist then create custom property
624 | if (c !== undefined) {
625 | c.redips = c.redips || {};
626 | }
627 | // if marked cell is found and rowspan property is greater then 1
628 | if (c !== undefined && c.redips.selected === true && c.rowSpan > 1) {
629 | // get rowspaned cells before current cell (in a row)
630 | rs = getRowSpan(cl, c, i, j);
631 | // insert new cell at last position of rowspan (consider rowspan cells before)
632 | n = tr[i + c.rowSpan - 1].insertCell(j - rs);
633 | results.cell = n;
634 | results.coords = [i,j+c.rowSpan-1];
635 | resultsArr.push(results);
636 | results = {};
637 | // set the same colspan value as it has current cell
638 | n.colSpan = c.colSpan;
639 | // decrease rowspan of marked cell
640 | c.rowSpan -= 1;
641 | // add "redips" property to the table cell and optionally event listener
642 | cellInit(n);
643 | // recreate cell list after vertical split (new cell is inserted)
644 | cl = cellList(tbl[t]);
645 | }
646 | }
647 | // split horizontally
648 | else {
649 | // define current table cell
650 | c = tr[i].cells[j];
651 | // if custom property "redips" doesn't exist then create custom property
652 | c.redips = c.redips || {};
653 | // if marked cell is found and cell has colspan property greater then 1
654 | if (c.redips.selected === true && c.colSpan > 1) {
655 | // increase cols (because new cell is inserted)
656 | cols++;
657 | // insert cell after current cell
658 | n = tr[i].insertCell(j + 1);
659 | results.cell = n;
660 | results.coords = [i,j+c.colSpan-1];
661 | resultsArr.push(results);
662 | results = {};
663 | // set the same rowspan value as it has current cell
664 | n.rowSpan = c.rowSpan;
665 | // decrease colspan of marked cell
666 | c.colSpan -= 1;
667 | // add "redips" property to the table cell and optionally event listener
668 | cellInit(n);
669 | }
670 | }
671 | // return original background color and reset selected flag (if cell exists)
672 | if (c !== undefined) {
673 | mark(false, c);
674 | }
675 | }
676 | }
677 | }
678 | // show cell index (if showIndex public property is set to true)
679 | cellIndex();
680 | return resultsArr;
681 | };
682 |
683 | /**
684 | * Method to set cell colors.
685 | * @param {String} color Hex code of target color
686 | * @param {HTMLElement|String} [table] Table id or table reference.
687 | * @public
688 | * @function
689 | * @name REDIPS.table#setColor
690 | */
691 | setColor = function (color, table) {
692 | let tbl, // table array (loaded from tables array or from table input parameter)
693 | tr, // row reference in table
694 | c, // current table cell
695 | cl, // cell list with new coordinates
696 | rs, // rowspan cells before
697 | n, // reference of inserted table cell
698 | cols, // number of columns (used in TD loop)
699 | max; // maximum number of columns
700 |
701 | // remove text selection
702 | removeSelection();
703 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
704 | tbl = (table === undefined) ? tables : getTable(table);
705 | // TABLE loop
706 | for (let t = 0; t < tbl.length; t++) {
707 | // define cell list with new coordinates
708 | cl = cellList(tbl[t]);
709 | // define maximum number of columns in table
710 | max = maxCols(tbl[t]);
711 | // define row number in current table
712 | tr = tbl[t].rows;
713 | // loop TR
714 | for (let i = 0; i < tr.length; i++) {
715 | // define column number (depending on mode)
716 | cols = max;
717 | // loop TD
718 | for (let j = 0; j < cols; j++) {
719 | // define current table cell
720 | c = cl[i + '-' + j];
721 | if (c !== undefined) {
722 | // if custom property "redips" doesn't exist then create custom property
723 | c.redips = c.redips || {};
724 | if (c.redips.selected === true) {
725 | // return original background color and reset selected flag (if cell exists)
726 | c.redips.background_old = color;
727 | mark(false, c);
728 | }
729 | }
730 | }
731 | }
732 | }
733 | // show cell index (if showIndex public property is set to true)
734 | cellIndex();
735 | };
736 |
737 | /**
738 | * Method to auto set cell colors with coordinates as parameters.
739 | * @param {colorArr} colorArr Array of coordinates and colours in the format of [col, row, color].
740 | * @param {HTMLElement|String} [table] Table id or table reference.
741 | * @public
742 | * @function
743 | * @name REDIPS.table#autoSetColor
744 | */
745 | autoSetColor = function (colorArr, table) {
746 | let tbl, // table array (loaded from tables array or from table input parameter)
747 | tr, // row reference in table
748 | c, // current table cell
749 | cl, // cell list with new coordinates
750 | rs, // rowspan cells before
751 | n, // reference of inserted table cell
752 | cols, // number of columns (used in TD loop)
753 | max; // maximum number of columns
754 |
755 | // remove text selection
756 | removeSelection();
757 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
758 | tbl = (table === undefined) ? tables : getTable(table);
759 | // TABLE loop
760 | for (let t = 0; t < tbl.length; t++) {
761 | // define cell list with new coordinates
762 | cl = cellList(tbl[t]);
763 | colorArr.forEach((list, ind) => {
764 | c = cl[list[1] + '-' + list[0]];
765 | if (c !== undefined) {
766 | c.redips = c.redips || {};
767 | // return original background color and reset selected flag (if cell exists)
768 | c.redips.background_old = list[2];
769 | mark(false, c);
770 | }
771 | });
772 | }
773 | // show cell index (if showIndex public property is set to true)
774 | cellIndex();
775 | };
776 |
777 | /**
778 | * Method to get coordinates and colors of colored cells.
779 | * @param {HTMLElement|String} [table] Table id or table reference.
780 | * @public
781 | * @function
782 | * @name REDIPS.table#getColor
783 | */
784 | getColor = function (table) {
785 | let tbl, // table array (loaded from tables array or from table input parameter)
786 | tr, // row reference in table
787 | c, // current table cell
788 | cl, // cell list with new coordinates
789 | rs, // rowspan cells before
790 | n, // reference of inserted table cell
791 | cols, // number of columns (used in TD loop)
792 | max, // maximum number of columns
793 | results, // object containing new cell and new cell coordinates
794 | resultsArr; // array of results
795 |
796 | resultsArr = [];
797 | // remove text selection
798 | removeSelection();
799 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
800 | tbl = (table === undefined) ? tables : getTable(table);
801 | // TABLE loop
802 | for (let t = 0; t < tbl.length; t++) {
803 | // define cell list with new coordinates
804 | cl = cellList(tbl[t]);
805 | // define maximum number of columns in table
806 | max = maxCols(tbl[t]);
807 | // define row number in current table
808 | tr = tbl[t].rows;
809 | // loop TR
810 | for (let i = 0; i < tr.length; i++) {
811 | // define column number (depending on mode)
812 | cols = max;
813 | // loop TD
814 | for (let j = 0; j < cols; j++) {
815 | // define current table cell
816 | c = cl[i + '-' + j];
817 | // return original background color and reset selected flag (if cell exists)
818 | if (c !== undefined) {
819 | // if custom property "redips" doesn't exist then create custom property
820 |
821 | if (c.redips.background_old !== undefined && c.redips.background_old !== ""){
822 | results = [j, i, c.redips.background_old];
823 | resultsArr.push(results);
824 | results = [];
825 | }
826 | }
827 | }
828 | }
829 | }
830 | // show cell index (if showIndex public property is set to true)
831 | cellIndex();
832 | return resultsArr;
833 | };
834 |
835 | /**
836 | * Method to reset color of selected or all cells.
837 | * @param {Boolean} [flag] if set to true, then all cells will be reset, if set to false, only selected cells will be reset.
838 | * @param {HTMLElement|String} [table] Table id or table reference.
839 | * @public
840 | * @function
841 | * @name REDIPS.table#resetColor
842 | */
843 | resetColor = function (flag, table) {
844 | let tbl, // table array (loaded from tables array or from table input parameter)
845 | tr, // row reference in table
846 | c, // current table cell
847 | cl, // cell list with new coordinates
848 | rs, // rowspan cells before
849 | n, // reference of inserted table cell
850 | cols, // number of columns (used in TD loop)
851 | max; // maximum number of columns
852 |
853 | // remove text selection
854 | removeSelection();
855 | // if table input parameter is undefined then use "tables" private property (table array) or set table reference from getTable method
856 | tbl = (table === undefined) ? tables : getTable(table);
857 | // TABLE loop
858 | for (let t = 0; t < tbl.length; t++) {
859 | // define cell list with new coordinates
860 | cl = cellList(tbl[t]);
861 | // define maximum number of columns in table
862 | max = maxCols(tbl[t]);
863 | // define row number in current table
864 | tr = tbl[t].rows;
865 | // loop TR
866 | for (let i = 0; i < tr.length; i++) {
867 | // define column number (depending on mode)
868 | cols = max;
869 | // loop TD
870 | for (let j = 0; j < cols; j++) {
871 | // define current table cell
872 | c = cl[i + '-' + j];
873 | if (c !== undefined) {
874 | // if custom property "redips" doesn't exist then create custom property
875 | c.redips = c.redips || {};
876 | if (flag){
877 | // return original background color and reset selected flag (if cell exists)
878 | c.redips.background_old = "";
879 | mark(false, c);
880 | } else {
881 | if (c.redips.selected === true) {
882 | // return original background color and reset selected flag (if cell exists)
883 | c.redips.background_old = "";
884 | mark(false, c);
885 | }
886 | }
887 | }
888 | }
889 | }
890 | }
891 | // show cell index (if showIndex public property is set to true)
892 | cellIndex();
893 | };
894 |
895 | /**
896 | * Method sets reference to table. It is used in "merge" and "split" public methods.
897 | * @param {HTMLElement|String} table Table id or table reference.
898 | * @return {Array} Returns empty array or array with one member (table node).
899 | * @private
900 | * @memberOf REDIPS.table#
901 | */
902 | getTable = function (table) {
903 | // define output array
904 | let tbl = [];
905 | // input parameter should exits
906 | if (table !== undefined) {
907 | // if table parameter is string then set reference and overwrite input parameter
908 | if (typeof (table) === 'string') {
909 | table = document.getElementById(table);
910 | }
911 | // set table reference if table is not null and table is object and node is TABLE
912 | if (table && typeof (table) === 'object' && table.nodeName === 'TABLE') {
913 | tbl[0] = table;
914 | }
915 | }
916 | // return table reference as array
917 | return tbl;
918 | };
919 |
920 |
921 | /**
922 | * Add or delete table row. If index is omitted then index of last row will be used.
923 | * @param {HTMLElement|String} table Table id or table reference.
924 | * @param {String} mode Insert/delete table row
925 | * @param {Integer} [index] Index where row will be inserted or deleted. Last row will be assumed if index is not defined.
926 | * @return {HTMLElement} Returns reference of inserted row or NULL (in case of deleting row).
927 | * @public
928 | * @function
929 | * @name REDIPS.table#row
930 | */
931 | row = function (table, mode, index) {
932 | let nc, // new cell
933 | nr = null, // new row
934 | fr, // reference of first row
935 | c, // current cell reference
936 | cl, // cell list
937 | cols = 0, // number of columns
938 | i, j, k; // loop variables
939 | // remove text selection
940 | removeSelection();
941 | // if table is not object then input parameter is id and table parameter will be overwritten with table reference
942 | if (typeof (table) !== 'object') {
943 | table = document.getElementById(table);
944 | }
945 | // if index is not defined then index of the last row
946 | if (index === undefined) {
947 | index = -1;
948 | }
949 | // insert table row
950 | if (mode === 'insert') {
951 | // set reference of first row
952 | fr = table.rows[0];
953 | // define number of columns (it is colspan sum)
954 | for (i = 0; i < fr.cells.length; i++) {
955 | cols += fr.cells[i].colSpan;
956 | }
957 | // insert table row (insertRow returns reference to the newly created row)
958 | nr = table.insertRow(index);
959 | // insert table cells to the new row
960 | for (i = 0; i < cols; i++) {
961 | nc = nr.insertCell(i);
962 | // add "redips" property to the table cell and optionally event listener
963 | cellInit(nc);
964 | }
965 | // show cell index (if showIndex public property is set to true)
966 | cellIndex();
967 | }
968 | // delete table row and update rowspan for cells in upper rows if needed
969 | else {
970 | // last row should not be deleted
971 | if (table.rows.length === 1) {
972 | return;
973 | }
974 | // delete last row
975 | table.deleteRow(index);
976 | // prepare cell list
977 | cl = cellList(table);
978 | // set new index for last row
979 | index = table.rows.length - 1;
980 | // set maximum number of columns that table has
981 | cols = maxCols(table);
982 | // open loop for each cell in last row
983 | for (i = 0; i < cols; i++) {
984 | // try to find cell in last row
985 | c = cl[index + '-' + i];
986 | // if cell doesn't exist then update colspan in upper cells
987 | if (c === undefined) {
988 | // open loop for cells up in column
989 | for (j = index, k = 1; j >= 0; j--, k++) {
990 | // try to find cell upper cell with rowspan value
991 | c = cl[j + '-' + i];
992 | // if cell is found then update rowspan value
993 | if (c !== undefined) {
994 | c.rowSpan = k;
995 | break;
996 | }
997 | }
998 | }
999 | // if cell in last row has rowspan greater then 1
1000 | else if (c.rowSpan > 1) {
1001 | c.rowSpan -= 1;
1002 | }
1003 | // increase loop variable "i" for colspan value
1004 | i += c.colSpan - 1;
1005 | }
1006 | }
1007 | // in case of inserting new table row method will return TR reference (otherwise it will return NULL)
1008 | return nr;
1009 | };
1010 |
1011 |
1012 | /**
1013 | * Add or delete table column. Last column will be assumed if index is omitted.
1014 | * @param {HTMLElement|String} table Table id or table reference.
1015 | * @param {String} mode Insert / delete table column
1016 | * @param {Integer} [index] Index where column will be inserted or deleted. Last column will be assumed if index is not defined.
1017 | * @public
1018 | * @function
1019 | * @name REDIPS.table#column
1020 | */
1021 | column = function (table, mode, index) {
1022 | let c, // current cell
1023 | idx, // cell index needed when column is deleted
1024 | nc, // new cell
1025 | i; // loop variable
1026 | // remove text selection
1027 | removeSelection();
1028 | // if table is not object then input parameter is id and table parameter will be overwritten with table reference
1029 | if (typeof (table) !== 'object') {
1030 | table = document.getElementById(table);
1031 | }
1032 | // if index is not defined then index will be set to special value -1 (means to remove the very last column of a table or add column to the table end)
1033 | if (index === undefined) {
1034 | index = -1;
1035 | }
1036 | // insert table column
1037 | if (mode === 'insert') {
1038 | // loop iterates through each table row
1039 | for (i = 0; i < table.rows.length; i++) {
1040 | // insert cell
1041 | nc = table.rows[i].insertCell(index);
1042 | // add "redips" property to the table cell and optionally event listener
1043 | cellInit(nc);
1044 | }
1045 | // show cell index (if showIndex public property is set to true)
1046 | cellIndex();
1047 | }
1048 | // delete table column
1049 | else {
1050 | // set reference to the first row
1051 | c = table.rows[0].cells;
1052 | // test column number and prevent deleting last column
1053 | if (c.length === 1 && (c[0].colSpan === 1 || c[0].colSpan === undefined)) {
1054 | return;
1055 | }
1056 | // row loop
1057 | for (i = 0; i < table.rows.length; i++) {
1058 | // define cell index for last column
1059 | if (index === -1) {
1060 | idx = table.rows[i].cells.length - 1;
1061 | }
1062 | // if index is defined then use "index" value
1063 | else {
1064 | idx = index;
1065 | }
1066 | // define current cell (it can't use special value -1)
1067 | c = table.rows[i].cells[idx];
1068 | // if cell has colspan value then decrease colspan value
1069 | if (c.colSpan > 1) {
1070 | c.colSpan -= 1;
1071 | }
1072 | // else delete cell
1073 | else {
1074 | table.rows[i].deleteCell(index);
1075 | }
1076 | // increase loop variable "i" for rowspan value
1077 | i += c.rowSpan - 1;
1078 | }
1079 | }
1080 | };
1081 |
1082 |
1083 | /**
1084 | * Method sets or removes mark from table cell. It can be called on several ways:
1085 | * with direct cell address (cell reference or cell id) or with cell coordinates (row and column).
1086 | * @param {Boolean} flag If set to true then TD will be marked, otherwise table cell will be cleaned.
1087 | * @param {HTMLElement|String} el Cell reference or id of table cell. Or it can be table reference or id of the table.
1088 | * @param {Integer} [row] Row of the cell.
1089 | * @param {Integer} [col] Column of the cell.
1090 | * @example
1091 | * // set mark to the cell with "mycell" reference
1092 | * REDIPS.table.mark(true, mycell);
1093 | *
1094 | * // remove mark from the cell with id "a1"
1095 | * REDIPS.table.mark(false, 'a1');
1096 | *
1097 | * // set mark to the cell with coordinates (1,2) on table with reference "mytable"
1098 | * REDIPS.table.mark(true, mytable, 1, 2);
1099 | *
1100 | * // remove mark from the cell with coordinates (4,5) on table with id "t3"
1101 | * REDIPS.table.mark(false, 't3', 4, 5);
1102 | * @public
1103 | * @function
1104 | * @name REDIPS.table#mark
1105 | */
1106 | mark = function (flag, el, row, col) {
1107 | // cell list with new coordinates
1108 | let cl;
1109 | // first parameter "flag" should be boolean (if not, then return from method
1110 | if (typeof (flag) !== 'boolean') {
1111 | return;
1112 | }
1113 | // test type of the second parameter (it can be string or object)
1114 | if (typeof (el) === 'string') {
1115 | // set reference to table or table cell (overwrite input el parameter)
1116 | el = document.getElementById(el);
1117 | }
1118 | // if el is not string and is not an object then return from the method
1119 | else if (typeof (el) !== 'object') {
1120 | return;
1121 | }
1122 | // at this point, el should be an object - so test if it's TD or TABLE
1123 | if (el.nodeName === 'TABLE') {
1124 | // prepare cell list
1125 | cl = cellList(el);
1126 | // set reference to the cell (overwrite input el parameter)
1127 | el = cl[row + '-' + col];
1128 | }
1129 | // if el doesn't exist (el is not set in previous step) or el is not table cell or table header cell either then return from method
1130 | if (!el || (el.nodeName !== 'TD' && el.nodeName !== 'TH')) {
1131 | return;
1132 | }
1133 | // if custom property "redips" doesn't exist then create custom property
1134 | el.redips = el.redips || {};
1135 | // if color property is string, then TD background color will be changed (REDIPS.table.color.cell can be set to false)
1136 | if (typeof (REDIPS.table.color.cell) === 'string') {
1137 | // mark table cell
1138 | if (flag === true) {
1139 | // remember old color
1140 | el.redips.background_old = el.style.backgroundColor;
1141 | // set background color
1142 | el.style.backgroundColor = REDIPS.table.color.cell;
1143 | }
1144 | // umark table cell
1145 | else {
1146 | // return original background color and reset selected flag
1147 | el.style.backgroundColor = el.redips.background_old;
1148 | }
1149 | }
1150 | // set flag (true/false) to the cell "selected" property
1151 | el.redips.selected = flag;
1152 | };
1153 |
1154 |
1155 | /**
1156 | * Method removes text selection.
1157 | * @private
1158 | * @memberOf REDIPS.table#
1159 | */
1160 | removeSelection = function () {
1161 | // remove text selection (Chrome, FF, Opera, Safari)
1162 | if (window.getSelection) {
1163 | window.getSelection().removeAllRanges();
1164 | }
1165 | // IE8
1166 | else if (document.selection && document.selection.type === 'Text') {
1167 | try {
1168 | document.selection.empty();
1169 | }
1170 | catch (error) {
1171 | // ignore error (if there is any)
1172 | }
1173 | }
1174 | };
1175 |
1176 |
1177 | /**
1178 | * Determining X and Y position/index of table cell.
1179 | * @see http://www.javascripttoolbox.com/temp/table_cellindex.html
1180 | * @see http://www.barryvan.com.au/2012/03/determining-a-table-cells-x-and-y-positionindex/
1181 | * @private
1182 | * @memberOf REDIPS.table#
1183 | */
1184 | cellList = function (table) {
1185 | let matrix = [],
1186 | matrixrow,
1187 | lookup = {},
1188 | c, // current cell
1189 | ri, // row index
1190 | rowspan,
1191 | colspan,
1192 | firstAvailCol,
1193 | tr, // TR collection
1194 | k; // loop variables
1195 | // set HTML collection of table rows
1196 | tr = table.rows;
1197 | // open loop for each TR element
1198 | for (let i = 0; i < tr.length; i++) {
1199 | // open loop for each cell within current row
1200 | for (let j = 0; j < tr[i].cells.length; j++) {
1201 | // define current cell
1202 | c = tr[i].cells[j];
1203 | // set row index
1204 | ri = c.parentNode.rowIndex;
1205 | // define cell rowspan and colspan values
1206 | rowspan = c.rowSpan || 1;
1207 | colspan = c.colSpan || 1;
1208 | // if matrix for row index is not defined then initialize array
1209 | matrix[ri] = matrix[ri] || [];
1210 | // find first available column in the first row
1211 | for (k = 0; k < matrix[ri].length + 1; k++) {
1212 | if (typeof (matrix[ri][k]) === 'undefined') {
1213 | firstAvailCol = k;
1214 | break;
1215 | }
1216 | }
1217 | // set cell coordinates and reference to the table cell
1218 | lookup[ri + '-' + firstAvailCol] = c;
1219 | for (k = ri; k < ri + rowspan; k++) {
1220 | matrix[k] = matrix[k] || [];
1221 | matrixrow = matrix[k];
1222 | for (let l = firstAvailCol; l < firstAvailCol + colspan; l++) {
1223 | matrixrow[l] = 'x';
1224 | }
1225 | }
1226 | }
1227 | }
1228 | return lookup;
1229 | };
1230 |
1231 |
1232 | /**
1233 | * Method relocates element nodes from source cell to target table cell.
1234 | * It is used in case of merging table cells.
1235 | * @param {HTMLElement} from Source table cell.
1236 | * @param {HTMLElement} to Target table cell.
1237 | * @private
1238 | * @memberOf REDIPS.table#
1239 | */
1240 | relocate = function (from, to) {
1241 | let cn; // number of child nodes
1242 | // test if "from" cell is equal to "to" cell then do nothing
1243 | if (from === to) {
1244 | return;
1245 | }
1246 | // define childnodes length before loop
1247 | cn = from.childNodes.length;
1248 | // loop through all child nodes in table cell
1249 | // 'j', not 'i' because NodeList objects in the DOM are live
1250 | for (let i = 0, j = 0; i < cn; i++) {
1251 | // relocate only element nodes
1252 | if (from.childNodes[j].nodeType === 1) {
1253 | to.appendChild(from.childNodes[j]);
1254 | }
1255 | // skip text nodes, attribute nodes ...
1256 | else {
1257 | j++;
1258 | }
1259 | }
1260 | };
1261 |
1262 |
1263 | /**
1264 | * Display cellIndex for each cell in tables. It is useful in debuging process.
1265 | * @param {Boolean} flag If set to true then cell content will be replaced with cell index.
1266 | * @public
1267 | * @function
1268 | * @name REDIPS.table#cellIndex
1269 | */
1270 | cellIndex = function (flag) {
1271 | // if input parameter isn't set and showIndex private property is'nt true, then return
1272 | // input parameter "flag" can be undefined in case of internal calls
1273 | if (flag === undefined && showIndex !== true) {
1274 | return;
1275 | }
1276 | // if input parameter is set, then save parameter to the private property showIndex
1277 | if (flag !== undefined) {
1278 | // save flag to the showIndex private parameter
1279 | showIndex = flag;
1280 | }
1281 | // variable declaration
1282 | let tr, // number of rows in a table
1283 | c, // current cell
1284 | cl, // cell list
1285 | cols; // maximum number of columns that table contains
1286 | // open loop for each table inside container
1287 | for (let t = 0; t < tables.length; t++) {
1288 | // define row number in current table
1289 | tr = tables[t].rows;
1290 | // define maximum number of columns (table row may contain merged table cells)
1291 | cols = maxCols(tables[t]);
1292 | // define cell list
1293 | cl = cellList(tables[t]);
1294 | // open loop for each row
1295 | for (let i = 0; i < tr.length; i++) {
1296 | // open loop for every TD element in current row
1297 | for (let j = 0; j < cols; j++) {
1298 | // if cell exists then display cell index
1299 | if (cl[i + '-' + j]) {
1300 | // set reference to the current cell
1301 | c = cl[i + '-' + j];
1302 | // set innerHTML with cellIndex property
1303 | c.innerHTML = (showIndex) ? i + '-' + j : '';
1304 | }
1305 | }
1306 | }
1307 | }
1308 | };
1309 |
1310 | // method returns number of rowspan cells before current cell (in a row)
1311 |
1312 | getRowSpan = function (cl, c, row, col) {
1313 | let rs,
1314 | last,
1315 | i;
1316 | // set rs
1317 | rs = 0;
1318 | // set row index of bottom row for the current cell with rowspan value
1319 | last = row + c.rowSpan - 1;
1320 | // go through every cell before current cell in a row
1321 | for (i = col - 1; i >= 0; i--) {
1322 | // if cell doesn't exist then rowspan cell exists before
1323 | if (cl[last + '-' + i] === undefined) {
1324 | rs++;
1325 | }
1326 | }
1327 | return rs;
1328 | };
1329 |
1330 | return {
1331 | /* public properties */
1332 | /**
1333 | * color.cell defines background color for marked table cell. If not set then background color will not be changed.
1334 | * @type Object
1335 | * @name REDIPS.table#color
1336 | * @default null
1337 | * @example
1338 | * // set "#9BB3DA" as color for marked cell
1339 | * REDIPS.table.color.cell = '#9BB3DA';
1340 | */
1341 | color: color,
1342 | /**
1343 | * Enable / disable marking not empty table cells.
1344 | * @type Boolean
1345 | * @name REDIPS.table#markNonEmpty
1346 | * @default true
1347 | * @example
1348 | * // allow marking only empty cells
1349 | * REDIPS.table.markNonEmpty = false;
1350 | */
1351 | markNonEmpty: markNonEmpty,
1352 | /* public methods are documented in main code */
1353 | onMouseDown: onMouseDown,
1354 | mark: mark,
1355 | merge: merge,
1356 | mergeAuto: mergeAuto,
1357 | checkMerged: checkMerged,
1358 | split: split,
1359 | setColor: setColor,
1360 | autoSetColor: autoSetColor,
1361 | getColor: getColor,
1362 | resetColor: resetColor,
1363 | row: row,
1364 | column: column,
1365 | cellIndex: cellIndex,
1366 | cellIgnore: cellIgnore,
1367 | testFunction: testFunction
1368 | };
1369 | }());
1370 |
1371 |
1372 | // if REDIPS.event isn't already defined (from other REDIPS file)
1373 | if (!REDIPS.event) {
1374 | REDIPS.event = (function () {
1375 | var add, // add event listener
1376 | remove; // remove event listener
1377 |
1378 | // http://msdn.microsoft.com/en-us/scriptjunkie/ff728624
1379 | // http://www.javascriptrules.com/2009/07/22/cross-browser-event-listener-with-design-patterns/
1380 | // http://www.quirksmode.org/js/events_order.html
1381 |
1382 | // add event listener
1383 | add = function (obj, eventName, handler) {
1384 | if (obj.addEventListener) {
1385 | // (false) register event in bubble phase (event propagates from from target element up to the DOM root)
1386 | obj.addEventListener(eventName, handler, false);
1387 | }
1388 | else if (obj.attachEvent) {
1389 | obj.attachEvent('on' + eventName, handler);
1390 | }
1391 | else {
1392 | obj['on' + eventName] = handler;
1393 | }
1394 | };
1395 |
1396 | // remove event listener
1397 | remove = function (obj, eventName, handler) {
1398 | if (obj.removeEventListener) {
1399 | obj.removeEventListener(eventName, handler, false);
1400 | }
1401 | else if (obj.detachEvent) {
1402 | obj.detachEvent('on' + eventName, handler);
1403 | }
1404 | else {
1405 | obj['on' + eventName] = null;
1406 | }
1407 | };
1408 |
1409 | return {
1410 | add: add,
1411 | remove: remove
1412 | }; // end of public (return statement)
1413 | }());
1414 | }
1415 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Darko Bunic
4 | http://www.redips.net/
5 | Mar, 2020.
6 |
7 | */
8 | body {
9 | font-family: arial;
10 | margin: 0px;
11 | }
12 |
13 | /* header */
14 | div#header {
15 | text-align: center;
16 | margin-top: 50px;
17 | }
18 |
19 | /* container with ol element in center */
20 | #container {
21 | margin: auto;
22 | width: 400px;
23 | }
24 |
25 | /* ordered list */
26 | ol#list li {
27 | margin-bottom: 10px;
28 | }
--------------------------------------------------------------------------------