├── .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 | 18 | 19 | 20 | 23 | 27 | 31 | 35 | 36 | 37 |
21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 |
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 | 18 | 19 | 20 | 24 | 28 | 29 | 30 |
21 | 22 | 23 | 25 | 26 | 27 |
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 | '
www.redips.net
' + 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 |
18 |
    19 |
  1. Table demo
  2. 20 |
  3. 8 tables
  4. 21 |
  5. Merge / split table cells
  6. 22 |
23 |
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 | } --------------------------------------------------------------------------------