dynamic_table 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/app/assets/javascripts/dynamic_table.js +930 -0
- data/app/assets/stylesheets/dynamic_table.scss +56 -0
- data/lib/dynamic_table/engine.rb +9 -0
- data/lib/dynamic_table/version.rb +5 -0
- data/lib/dynamic_table.rb +11 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 19f5fe5410456117b2cf8996ed417d1a796a0d7c0562644285d917ccb9e47fa2
|
4
|
+
data.tar.gz: abf278075e228753dfa36b64758887f5bb4db3e80ab4df23af44b9441e7f0d2b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: da0cb55a7774f75d8f5d77384f58c1cd509bfa2170fb837d989e629797ca8d47c6fa0a5587e090e10291c04e94d59219eea410da5568dd65987547323a598f98
|
7
|
+
data.tar.gz: 1fe4951f818c140b8ee3d2d224a697351c737c06fd2c45785bdacc3459cfd25427afdb796ab29a81ea148ea33cb5b8c62891bde3ff8837acc28ed559420b5731
|
@@ -0,0 +1,930 @@
|
|
1
|
+
(function(root, factory) {
|
2
|
+
if (typeof define === 'function' && define.amd) {
|
3
|
+
define([], factory);
|
4
|
+
} else if (typeof module === 'object' && module.exports) {
|
5
|
+
module.exports = factory();
|
6
|
+
} else {
|
7
|
+
root.DynamicTable = factory();
|
8
|
+
}
|
9
|
+
}(this, function() {
|
10
|
+
function DynamicTable(element, options) {
|
11
|
+
this.element = element; //
|
12
|
+
this.rows = options.rows || 3;
|
13
|
+
this.cols = options.cols || 3;
|
14
|
+
this.columnOptions = options.columnOptions || {};
|
15
|
+
this.appliedClasses = options.appliedClasses || {};
|
16
|
+
this.tableId = options.tableId || 'dynamicTable'; // ID for the table element
|
17
|
+
this.prefix = options.prefix || this.tableId;
|
18
|
+
this.inputType = options.inputType || 'text'; // Input type: 'text' or 'decimal'
|
19
|
+
this.dataMap = new Map();
|
20
|
+
this.headerNames = [];
|
21
|
+
this.addStyles();
|
22
|
+
this.specialControlFunctions = new Map();
|
23
|
+
this.renderTable(); // Generate the table when instantiated
|
24
|
+
this.selectionMap = Array.from({ length: this.rows }, () => Array(this.cols).fill(0));
|
25
|
+
this.pivotRow = -1;
|
26
|
+
this.pivotCol = -1;
|
27
|
+
this.isMouseDown = false; // Track mouse down state for drag selection
|
28
|
+
this.initClickOutsideListener(); // Detect clicks outside the table
|
29
|
+
this.initAllSpecialControls(); //controls such as dates needs datepicker
|
30
|
+
this.initiateDecimalRelatedVars();
|
31
|
+
}
|
32
|
+
|
33
|
+
DynamicTable.prototype.initiateDecimalRelatedVars = function() {
|
34
|
+
this.controlKeys = ["Backspace", "ArrowLeft", "ArrowRight", "Delete", "Tab"];
|
35
|
+
this.decimalKeyRange = [...Array.from({ length: 10 }, (_, i) => "" + i), ...this.controlKeys];
|
36
|
+
this.decimalKeyRange.push("."); //the dot operator to signify decimals
|
37
|
+
};
|
38
|
+
|
39
|
+
DynamicTable.prototype.initAllSpecialControls = function() {
|
40
|
+
for (const [kls,func] of this.specialControlFunctions) {
|
41
|
+
func();
|
42
|
+
}
|
43
|
+
};
|
44
|
+
|
45
|
+
//can add rows if there are pre existing rows. Else use table definition
|
46
|
+
DynamicTable.prototype.addRow = function(rowNo = null, options = null) {
|
47
|
+
let tbody = $('#'+this.tableId).find('tbody')[0];
|
48
|
+
|
49
|
+
let tr = document.createElement('tr');
|
50
|
+
let row = this.rows + 1;
|
51
|
+
//apply classes to tr
|
52
|
+
if (options != null) {
|
53
|
+
tr.classList.add(...options);
|
54
|
+
}
|
55
|
+
else if (this.appliedClasses['tr']?.[''+row]) {
|
56
|
+
tr.classList.add(...this.appliedClasses['tr'][''+row]);
|
57
|
+
} else if (this.appliedClasses['tr']?.[''+(row-1)]) { //else apply from previous row if unavailable
|
58
|
+
tr.classList.add(...this.appliedClasses['tr'][''+(row-1)]);
|
59
|
+
this.appliedClasses['tr'][''+(row)] = this.appliedClasses['tr'][''+(row-1)]
|
60
|
+
}
|
61
|
+
|
62
|
+
//add each column
|
63
|
+
for (var col = 1; col <= this.cols; col++) {
|
64
|
+
let td = document.createElement('td');
|
65
|
+
|
66
|
+
//apply classes to td
|
67
|
+
if (this.appliedClasses['td']?.[''+col]) {
|
68
|
+
td.classList.add(...this.appliedClasses['td'][''+col]);
|
69
|
+
}
|
70
|
+
|
71
|
+
var input = this.setInput(this.columnOptions[`${col}`]||{},row, col);
|
72
|
+
|
73
|
+
//add event listeners
|
74
|
+
this.initEventListeners(input);
|
75
|
+
this.element.addEventListener('keydown', this.handleKeyDown.bind(this,this));
|
76
|
+
input.addEventListener('keydown', this.handleKeyDown.bind(this, input));
|
77
|
+
input.addEventListener('mousedown', this.handleInputMouseDown.bind(this, input));
|
78
|
+
input.addEventListener('mouseover', this.handleMouseOver.bind(this, input)); // To handle dragging
|
79
|
+
input.addEventListener('change', this.addToDataMap.bind(this, input));
|
80
|
+
|
81
|
+
td.appendChild(input);
|
82
|
+
tr.appendChild(td);
|
83
|
+
}
|
84
|
+
|
85
|
+
tbody.appendChild(tr);
|
86
|
+
this.rows += 1; //on success add the row numbers
|
87
|
+
this.resizeSelectionMap();
|
88
|
+
}
|
89
|
+
|
90
|
+
DynamicTable.prototype.removeRow = function(rowNo = null) {
|
91
|
+
if (this.rows === 0) {
|
92
|
+
console.log("No more rows to remove in table.");
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
|
96
|
+
let table = $('#'+this.tableId)[0];
|
97
|
+
if (rowNo === null) {
|
98
|
+
rowNo = this.rows;
|
99
|
+
}
|
100
|
+
|
101
|
+
table.deleteRow(rowNo);
|
102
|
+
this.rows -= 1;
|
103
|
+
this.resizeSelectionMap();
|
104
|
+
|
105
|
+
}
|
106
|
+
|
107
|
+
DynamicTable.prototype.addCol = function(colOptions) {
|
108
|
+
for (let key in colOptions) {
|
109
|
+
this.columnOptions[key] = colOptions[key];
|
110
|
+
|
111
|
+
//adding header
|
112
|
+
let tr = $(`#${this.tableId} thead tr`)[0];
|
113
|
+
// let tr = thead.find('tr')[0];
|
114
|
+
var th = document.createElement('th');
|
115
|
+
th.classList.add(...this.appliedClasses['thead']);
|
116
|
+
let colName = colOptions[key]?.['headerName'] || 'Column ' + key;
|
117
|
+
if (this.headerNames.includes(colName)) {
|
118
|
+
let index = 1;
|
119
|
+
let testName = colName;
|
120
|
+
while(this.headerNames.includes(testName)){testName = colName+index; ++index;}
|
121
|
+
this.headerNames.push(testName);th.innerText = testName;
|
122
|
+
}
|
123
|
+
else {this.headerNames.push(colName);th.innerText = colName;}
|
124
|
+
|
125
|
+
tr.appendChild(th);
|
126
|
+
|
127
|
+
//adding columns to rows
|
128
|
+
var parentObj = this;
|
129
|
+
$(`#${this.tableId} tbody tr`).each(function(i, tr) {
|
130
|
+
var row = i + 1; //row index starts from 1 rather than 0
|
131
|
+
var td = document.createElement('td');
|
132
|
+
//apply classes to td
|
133
|
+
if (parentObj.appliedClasses['td']?.[''+key]) {
|
134
|
+
td.classList.add(...parentObj.appliedClasses['td'][''+key]);
|
135
|
+
}
|
136
|
+
|
137
|
+
var input = parentObj.setInput(parentObj.columnOptions[key]||{},row, parentObj.cols+1);
|
138
|
+
//add event listeners
|
139
|
+
parentObj.initEventListeners(input);
|
140
|
+
parentObj.element.addEventListener('keydown', parentObj.handleKeyDown.bind(parentObj,parentObj));
|
141
|
+
input.addEventListener('keydown', parentObj.handleKeyDown.bind(parentObj, input));
|
142
|
+
input.addEventListener('mousedown', parentObj.handleInputMouseDown.bind(parentObj, input));
|
143
|
+
input.addEventListener('mouseover', parentObj.handleMouseOver.bind(parentObj, input)); // To handle dragging
|
144
|
+
input.addEventListener('change', parentObj.addToDataMap.bind(parentObj, input));
|
145
|
+
|
146
|
+
td.appendChild(input);
|
147
|
+
tr.appendChild(td);
|
148
|
+
});
|
149
|
+
|
150
|
+
// let table = document.getElementById('#'+this.tableId);
|
151
|
+
// for (var row = 1; row <= this.rows; row++) {
|
152
|
+
// var tr = table.rows[row];
|
153
|
+
// var td = document.createElement('td');
|
154
|
+
// //apply classes to td
|
155
|
+
// if (this.appliedClasses['td']?.[''+col]) {
|
156
|
+
// td.classList.add(...this.appliedClasses['td'][''+col]);
|
157
|
+
// }
|
158
|
+
|
159
|
+
// var input = this.setInput(this.columnOptions[key]||{},row, this.cols+1);
|
160
|
+
// //add event listeners
|
161
|
+
// this.initEventListeners(input);
|
162
|
+
// this.element.addEventListener('keydown', this.handleKeyDown.bind(this,this));
|
163
|
+
// input.addEventListener('keydown', this.handleKeyDown.bind(this, input));
|
164
|
+
// input.addEventListener('mousedown', this.handleInputMouseDown.bind(this, input));
|
165
|
+
// input.addEventListener('mouseover', this.handleMouseOver.bind(this, input)); // To handle dragging
|
166
|
+
// input.addEventListener('change', this.addToDataMap.bind(this, input));
|
167
|
+
|
168
|
+
// td.appendChild(input);
|
169
|
+
// tr.appendChild(td);
|
170
|
+
|
171
|
+
this.cols += 1; //on successful addition across all rows
|
172
|
+
this.resizeSelectionMap();
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
DynamicTable.prototype.removeCol = function() {
|
177
|
+
let columnIndex = this.cols - 1; //pick the last column
|
178
|
+
let tableId = '#' + this.tableId;
|
179
|
+
|
180
|
+
$(tableId + ' tr').each(function() {
|
181
|
+
// Find the column at the given index and remove it
|
182
|
+
$(this).find('th, td').eq(columnIndex).remove();
|
183
|
+
});
|
184
|
+
|
185
|
+
this.cols -= 1;
|
186
|
+
this.headerNames.pop(); //remove last column
|
187
|
+
this.resizeSelectionMap();
|
188
|
+
}
|
189
|
+
|
190
|
+
DynamicTable.prototype.resizeSelectionMap = function() {
|
191
|
+
|
192
|
+
this.selectionMap = Array.from({ length: this.rows }, () => Array(this.cols).fill(0));
|
193
|
+
this.repaintRange(-1,-1,-1,-1);
|
194
|
+
this.pivotRow = this.pivotCol = -1;
|
195
|
+
|
196
|
+
}
|
197
|
+
|
198
|
+
DynamicTable.prototype.renderTable = function() {
|
199
|
+
// Create table element
|
200
|
+
var table = document.createElement('table');
|
201
|
+
table.id = this.tableId;
|
202
|
+
table.className = 'dynamic-table';
|
203
|
+
|
204
|
+
//apply classes to table
|
205
|
+
if (this.appliedClasses['table']) {
|
206
|
+
table.classList.add(...this.appliedClasses['table']);
|
207
|
+
}
|
208
|
+
|
209
|
+
// Create the table header row
|
210
|
+
var thead = document.createElement('thead');
|
211
|
+
var headerRow = document.createElement('tr');
|
212
|
+
for (var col = 1; col <= this.cols; col++) {
|
213
|
+
var th = document.createElement('th');
|
214
|
+
let colName = this.columnOptions[`${col}`]?.['headerName'] || 'Column ' + col;
|
215
|
+
th.classList.add(...this.appliedClasses['thead']);
|
216
|
+
|
217
|
+
if (this.headerNames.includes(colName)) {
|
218
|
+
let index = 1;
|
219
|
+
let testName = colName;
|
220
|
+
while(this.headerNames.includes(testName)){testName = colName+index; ++index;}
|
221
|
+
this.headerNames.push(testName);th.innerText = testName;
|
222
|
+
}
|
223
|
+
else {this.headerNames.push(colName);th.innerText = colName;}
|
224
|
+
|
225
|
+
headerRow.appendChild(th);
|
226
|
+
}
|
227
|
+
thead.appendChild(headerRow);
|
228
|
+
table.appendChild(thead);
|
229
|
+
|
230
|
+
// Create the table body
|
231
|
+
var tbody = document.createElement('tbody');
|
232
|
+
for (var row = 1; row <= this.rows; row++) {
|
233
|
+
var tr = document.createElement('tr');
|
234
|
+
|
235
|
+
//apply classes to tr
|
236
|
+
if (this.appliedClasses['tr']?.[''+row]) {
|
237
|
+
tr.classList.add(...this.appliedClasses['tr'][''+row]);
|
238
|
+
}
|
239
|
+
|
240
|
+
for (var col = 1; col <= this.cols; col++) {
|
241
|
+
var td = document.createElement('td');
|
242
|
+
|
243
|
+
//apply classes to td
|
244
|
+
if (this.appliedClasses['td']?.[''+col]) {
|
245
|
+
td.classList.add(...this.appliedClasses['td'][''+col]);
|
246
|
+
}
|
247
|
+
|
248
|
+
var input = this.setInput(this.columnOptions[`${col}`]||{},row, col);
|
249
|
+
|
250
|
+
//add event listeners
|
251
|
+
this.initEventListeners(input);
|
252
|
+
this.element.addEventListener('keydown', this.handleKeyDown.bind(this,this));
|
253
|
+
input.addEventListener('keydown', this.handleKeyDown.bind(this, input));
|
254
|
+
input.addEventListener('mousedown', this.handleInputMouseDown.bind(this, input));
|
255
|
+
input.addEventListener('mouseover', this.handleMouseOver.bind(this, input)); // To handle dragging
|
256
|
+
input.addEventListener('change', this.addToDataMap.bind(this, input));
|
257
|
+
|
258
|
+
td.appendChild(input);
|
259
|
+
tr.appendChild(td);
|
260
|
+
}
|
261
|
+
tbody.appendChild(tr);
|
262
|
+
}
|
263
|
+
table.appendChild(tbody);
|
264
|
+
|
265
|
+
// Append the table to the specified element
|
266
|
+
this.element.appendChild(table);
|
267
|
+
};
|
268
|
+
|
269
|
+
DynamicTable.prototype.addToDataMap = function(input) {
|
270
|
+
const [row,col] = this.getRowCol(input);
|
271
|
+
const colName = this.headerNames[col];
|
272
|
+
value = input.value;
|
273
|
+
if (!this.dataMap.get(row)) {
|
274
|
+
this.dataMap.set(row,new Map());
|
275
|
+
}
|
276
|
+
let x = this.dataMap.get(row);
|
277
|
+
x.set(colName,value);
|
278
|
+
};
|
279
|
+
|
280
|
+
function mapToObject(map) {
|
281
|
+
let obj = {};
|
282
|
+
map.forEach((value, key) => {
|
283
|
+
// If value is a Map, convert it to an object recursively
|
284
|
+
if (value instanceof Map) {
|
285
|
+
obj[key] = mapToObject(value);
|
286
|
+
} else {
|
287
|
+
obj[key] = value;
|
288
|
+
}
|
289
|
+
});
|
290
|
+
return obj;
|
291
|
+
}
|
292
|
+
|
293
|
+
|
294
|
+
|
295
|
+
DynamicTable.prototype.getValue = function() {
|
296
|
+
|
297
|
+
return JSON.stringify(this.getValueAsObject());
|
298
|
+
|
299
|
+
};
|
300
|
+
|
301
|
+
DynamicTable.prototype.getValueAsObject = function() {
|
302
|
+
|
303
|
+
let obj = {};
|
304
|
+
|
305
|
+
for(i = 1; i <= this.rows; i++) {
|
306
|
+
obj[i] = {};
|
307
|
+
for (j = 1; j <= this.cols; j++) {
|
308
|
+
const val = document.getElementById(`${this.prefix}_r${i}c${j}`).value;
|
309
|
+
if (val) {obj[i][this.headerNames[j-1]] = val};
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
return obj; //return object itself to be able to process the values in their original form
|
314
|
+
};
|
315
|
+
|
316
|
+
DynamicTable.prototype.addStyles = function() {
|
317
|
+
let style = document.createElement('style');
|
318
|
+
style.type = 'text/css';
|
319
|
+
style.innerHTML = `
|
320
|
+
#${this.element.id} input[type=number]::-webkit-outer-spin-button,
|
321
|
+
#${this.element.id} input[type=number]::-webkit-inner-spin-button {
|
322
|
+
-webkit-appearance: none; /* Hides the spin buttons in Chrome, Safari, and Edge */
|
323
|
+
margin: 0; /* Remove default margin */
|
324
|
+
}
|
325
|
+
#${this.element.id} input[type=number] {
|
326
|
+
-moz-appearance: textfield; /* Hides the spin buttons in Firefox */
|
327
|
+
}
|
328
|
+
`;
|
329
|
+
document.head.appendChild(style); // Append the <style> to the <head>
|
330
|
+
};
|
331
|
+
|
332
|
+
DynamicTable.prototype.setValue = function(dyntable, values) {
|
333
|
+
Object.entries(values).forEach(
|
334
|
+
([key,colValues]) => {
|
335
|
+
Object.entries(colValues).forEach(
|
336
|
+
([k,v]) => {
|
337
|
+
const colIndex = dyntable.headerNames.indexOf(k);
|
338
|
+
if (colIndex != -1) {
|
339
|
+
const cellIndex = this.prefix + '_r' + key + 'c' + (colIndex+1);
|
340
|
+
cell = document.getElementById(cellIndex);
|
341
|
+
cell.value = v;
|
342
|
+
}
|
343
|
+
}
|
344
|
+
);
|
345
|
+
}
|
346
|
+
);
|
347
|
+
};
|
348
|
+
|
349
|
+
DynamicTable.prototype.handleKeyDownForInput = function(input, event) {
|
350
|
+
|
351
|
+
let [row,col] = this.getRowCol(input);
|
352
|
+
switch (event.keyCode) {
|
353
|
+
case 37:
|
354
|
+
event.preventDefault();
|
355
|
+
if (col > 0) {
|
356
|
+
col = col - 1;
|
357
|
+
this.onFirstCellSelection(input, row, col);
|
358
|
+
}
|
359
|
+
break;
|
360
|
+
case 38:
|
361
|
+
event.preventDefault();
|
362
|
+
if (row > 0 ) {
|
363
|
+
row = row - 1;
|
364
|
+
this.onFirstCellSelection(input, row, col);
|
365
|
+
}
|
366
|
+
break;
|
367
|
+
case 39:
|
368
|
+
event.preventDefault();
|
369
|
+
if (col < (this.cols - 1)) {
|
370
|
+
col = col + 1;
|
371
|
+
this.onFirstCellSelection(input, row, col);
|
372
|
+
}
|
373
|
+
break;
|
374
|
+
case 40:
|
375
|
+
event.preventDefault();
|
376
|
+
if (row < (this.rows - 1)) {
|
377
|
+
row = row + 1;
|
378
|
+
this.onFirstCellSelection(input, row, col);
|
379
|
+
}
|
380
|
+
break;
|
381
|
+
}
|
382
|
+
|
383
|
+
|
384
|
+
};
|
385
|
+
|
386
|
+
DynamicTable.prototype.onFirstCellSelection = function(input, newRow, newCol) {
|
387
|
+
let targetId = `${this.prefix}_r${newRow+1}c${newCol+1}`;
|
388
|
+
let cell = document.getElementById(targetId);
|
389
|
+
if (input.classList.contains('selected')) {
|
390
|
+
input.classList.remove('selected');
|
391
|
+
}
|
392
|
+
if (!cell.classList.contains('selected')) {
|
393
|
+
cell.classList.add('selected');
|
394
|
+
cell.focus();
|
395
|
+
}
|
396
|
+
|
397
|
+
this.initiateSelectionMap(cell);
|
398
|
+
[this.pivotRow, this.pivotCol] = this.getRowCol(cell);
|
399
|
+
const [lowestRow,highestRow,lowestCol,highestCol] = this.selectionMapRange();
|
400
|
+
this.repaintRange(lowestRow,highestRow,lowestCol,highestCol);
|
401
|
+
};
|
402
|
+
|
403
|
+
DynamicTable.prototype.handleKeyDown = function(input, event) {
|
404
|
+
if (input.classList && input.classList.contains('table-input')) {
|
405
|
+
if (!(event.metaKey || event.ctrlKey) && !event.shiftKey && ['ArrowDown','ArrowUp'].includes(event.key)) {
|
406
|
+
event.preventDefault(); // Prevents the default action
|
407
|
+
this.handleKeyDownForInput(input,event);
|
408
|
+
}
|
409
|
+
|
410
|
+
if ((!navigator) && (event.metaKey || event.ctrlKey) && !event.shiftKey && ['C','c'].includes(event.key)) {
|
411
|
+
this.handleCopy(event);
|
412
|
+
event.stopPropagation();
|
413
|
+
}
|
414
|
+
|
415
|
+
if ((!navigator) && (event.metaKey || event.ctrlKey) && !event.shiftKey && ['V','v'].includes(event.key)) {
|
416
|
+
if (input.type === 'date' || input.type === 'select-one') {
|
417
|
+
this.handlePaste(event);
|
418
|
+
event.stopPropagation();
|
419
|
+
}
|
420
|
+
|
421
|
+
}
|
422
|
+
|
423
|
+
}
|
424
|
+
|
425
|
+
|
426
|
+
// Detect if Cmd (macOS) or Ctrl (Windows/Linux), Shift, and ArrowDown are pressed together
|
427
|
+
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'ArrowDown') {
|
428
|
+
const [lowestRow,highestRow,lowestCol,highestCol] = this.selectionMapRange();
|
429
|
+
this.repaintRange(lowestRow, this.rows, lowestCol, highestCol);
|
430
|
+
event.preventDefault();
|
431
|
+
}
|
432
|
+
|
433
|
+
if (event.key === 'Delete') {
|
434
|
+
let twoDArray = [];
|
435
|
+
twoDArray.push(['']);
|
436
|
+
this.pasteDataToTable(twoDArray); // Prevents the default action
|
437
|
+
}
|
438
|
+
};
|
439
|
+
|
440
|
+
// Detect if someone clicks outside the table and remove the table selection
|
441
|
+
DynamicTable.prototype.initClickOutsideListener = function() {
|
442
|
+
const tableElement = this.element; // Table element
|
443
|
+
const dynTable = this;
|
444
|
+
|
445
|
+
document.addEventListener('click', function(event) {
|
446
|
+
// Check if the click was outside the table and clears selection
|
447
|
+
if (!tableElement.contains(event.target)) {
|
448
|
+
dynTable.resetSelectionMap();
|
449
|
+
dynTable.repaintRange(-1,-1,-1,-1);
|
450
|
+
}
|
451
|
+
});
|
452
|
+
};
|
453
|
+
|
454
|
+
|
455
|
+
DynamicTable.prototype.handleInputMouseDown = function(input, event) {
|
456
|
+
event.stopPropagation();
|
457
|
+
this.isMouseDown = true; // Start selection
|
458
|
+
this.initiateSelectionMap(input);
|
459
|
+
[this.pivotRow, this.pivotCol] = this.getRowCol(input);
|
460
|
+
const [lowestRow,highestRow,lowestCol,highestCol] = this.selectionMapRange();
|
461
|
+
this.repaintRange(lowestRow,highestRow,lowestCol,highestCol);
|
462
|
+
document.addEventListener('mouseup', this.handleMouseUp.bind(this,input)); // Attach mouseup listener
|
463
|
+
};
|
464
|
+
|
465
|
+
// Handle mouse over event to select cells while dragging
|
466
|
+
DynamicTable.prototype.handleMouseOver = function(input, event) {
|
467
|
+
|
468
|
+
if (this.isMouseDown) {
|
469
|
+
this.addToSelectionMap(input);
|
470
|
+
}
|
471
|
+
};
|
472
|
+
|
473
|
+
// Handle mouse up event to stop selecting
|
474
|
+
DynamicTable.prototype.handleMouseUp = function(input,event) {
|
475
|
+
this.isMouseDown = false; // Stop selection
|
476
|
+
document.removeEventListener('mouseup',this.handleMouseUp.bind(this,input)); // Remove mouseup listener
|
477
|
+
};
|
478
|
+
|
479
|
+
DynamicTable.prototype.resetSelectionMap = function() {
|
480
|
+
// Set all values to zero using forEach
|
481
|
+
this.selectionMap.forEach((row, i) => {
|
482
|
+
row.forEach((_, j) => {
|
483
|
+
this.selectionMap[i][j] = 0;
|
484
|
+
});
|
485
|
+
});
|
486
|
+
};
|
487
|
+
|
488
|
+
DynamicTable.prototype.initiateSelectionMap = function(input) {
|
489
|
+
this.resetSelectionMap();
|
490
|
+
const regex = new RegExp(this.prefix + "_r(\\d+)c(\\d+)");
|
491
|
+
let cellId = input.id;
|
492
|
+
const match = cellId.match(regex);
|
493
|
+
if (match) {
|
494
|
+
const row = parseInt(match[1], 10); // Extracted row value as an integer
|
495
|
+
const col = parseInt(match[2], 10); // Extracted column value as an integer
|
496
|
+
this.selectionMap[row-1][col-1] = 1;
|
497
|
+
} else {
|
498
|
+
console.log("No match found.");
|
499
|
+
}
|
500
|
+
};
|
501
|
+
|
502
|
+
DynamicTable.prototype.getRowCol = function(input) {
|
503
|
+
const regex = new RegExp(this.prefix + "_r(\\d+)c(\\d+)");
|
504
|
+
let cellId = input.id;
|
505
|
+
const match = cellId.match(regex);
|
506
|
+
|
507
|
+
if (match) {
|
508
|
+
const row = parseInt(match[1], 10); // Extracted row value as an integer
|
509
|
+
const col = parseInt(match[2], 10); // Extracted column value as an integer
|
510
|
+
return [row-1,col-1];
|
511
|
+
} else {
|
512
|
+
return [-1,-1];
|
513
|
+
}
|
514
|
+
};
|
515
|
+
|
516
|
+
DynamicTable.prototype.addToSelectionMap = function(input) {
|
517
|
+
let [row, col] = this.getRowCol(input);
|
518
|
+
|
519
|
+
lowestRow = Math.min(this.pivotRow,row);
|
520
|
+
lowestCol = Math.min(this.pivotCol,col);
|
521
|
+
highestRow = Math.max(this.pivotRow,row);
|
522
|
+
highestCol = Math.max(this.pivotCol,col);
|
523
|
+
|
524
|
+
this.repaintRange(lowestRow,highestRow,lowestCol,highestCol);
|
525
|
+
|
526
|
+
};
|
527
|
+
|
528
|
+
DynamicTable.prototype.selectionMapRange = function() {
|
529
|
+
let lowestRow = -1;
|
530
|
+
let highestRow = -1;
|
531
|
+
let lowestCol = -1;
|
532
|
+
let highestCol = -1;
|
533
|
+
|
534
|
+
for (let i = 0; i < this.rows; i++) {
|
535
|
+
for (let j = 0; j < this.cols; j++) {
|
536
|
+
if (this.selectionMap[i][j] === 1) {
|
537
|
+
if (lowestRow===-1) {
|
538
|
+
lowestRow = i;
|
539
|
+
highestRow = i;
|
540
|
+
lowestCol = j;
|
541
|
+
highestCol = j;
|
542
|
+
}
|
543
|
+
|
544
|
+
if (lowestRow > i) {
|
545
|
+
lowestRow = i;
|
546
|
+
}
|
547
|
+
|
548
|
+
if (highestRow < i) {
|
549
|
+
highestRow = i;
|
550
|
+
}
|
551
|
+
|
552
|
+
if (lowestCol > j) {
|
553
|
+
lowestCol = j;
|
554
|
+
}
|
555
|
+
|
556
|
+
if (highestCol < j) {
|
557
|
+
highestCol = j;
|
558
|
+
}
|
559
|
+
}
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
|
564
|
+
|
565
|
+
return [lowestRow,highestRow,lowestCol,highestCol];
|
566
|
+
}
|
567
|
+
|
568
|
+
DynamicTable.prototype.repaintRange = function(lowestRow,highestRow,lowestCol,highestCol) {
|
569
|
+
this.resetSelectionMap();
|
570
|
+
for (let i = 0; i < this.rows; i++) {
|
571
|
+
for (let j = 0; j < this.cols; j++) {
|
572
|
+
if ((i >= lowestRow && i <= highestRow) && (j >= lowestCol && j <= highestCol)) {
|
573
|
+
this.selectionMap[i][j] = 1;
|
574
|
+
const cellId = this.prefix + "_r" + (i+1) + "c" + (j+1);
|
575
|
+
input = document.getElementById(cellId);
|
576
|
+
if (input && !input.classList.contains('selected')) {
|
577
|
+
input.classList.add('selected');
|
578
|
+
}
|
579
|
+
} else {
|
580
|
+
this.selectionMap[i][j] = 0;
|
581
|
+
const cellId = this.prefix + "_r" + (i+1) + "c" + (j+1);
|
582
|
+
input = document.getElementById(cellId);
|
583
|
+
if (input && input.classList.contains('selected')) {
|
584
|
+
input.classList.remove('selected');
|
585
|
+
}
|
586
|
+
}
|
587
|
+
}
|
588
|
+
}
|
589
|
+
};
|
590
|
+
|
591
|
+
|
592
|
+
|
593
|
+
// Initialize event listeners for copy/paste
|
594
|
+
DynamicTable.prototype.initEventListeners = function(input) {
|
595
|
+
|
596
|
+
input.addEventListener('copy', this.handleCopy.bind(this));
|
597
|
+
// Handle paste functionality
|
598
|
+
input.addEventListener('paste', this.handlePaste.bind(this),false);
|
599
|
+
|
600
|
+
};
|
601
|
+
|
602
|
+
DynamicTable.prototype.getSelectionArray = function() {
|
603
|
+
let twoDArray = [];
|
604
|
+
const [lowestRow,highestRow,lowestCol,highestCol] = this.selectionMapRange();
|
605
|
+
|
606
|
+
for (let i = lowestRow; i <= highestRow; i++ ) {
|
607
|
+
rowArray = [];
|
608
|
+
for (let j = lowestCol; j <= highestCol; j++) {
|
609
|
+
let element = document.getElementById(this.prefix+'_r'+(i+1)+'c'+(j+1));
|
610
|
+
rowArray.push(element.value);
|
611
|
+
}
|
612
|
+
twoDArray.push(rowArray);
|
613
|
+
}
|
614
|
+
|
615
|
+
return twoDArray;
|
616
|
+
};
|
617
|
+
|
618
|
+
DynamicTable.prototype.copyToClipboard = function(event, text) {
|
619
|
+
if (navigator.clipboard) {
|
620
|
+
navigator.clipboard.writeText(text)
|
621
|
+
.then(() => {
|
622
|
+
console.log('Text copied to clipboard');
|
623
|
+
})
|
624
|
+
.catch(err => {
|
625
|
+
console.error('Failed to copy text: ', err);
|
626
|
+
});
|
627
|
+
} else {
|
628
|
+
event.clipboardData?.setData("text/plain", text);
|
629
|
+
console.warn('Navigator API not supported in this browser');
|
630
|
+
}
|
631
|
+
}
|
632
|
+
|
633
|
+
// Handle copy functionality
|
634
|
+
DynamicTable.prototype.handleCopy = function(event) {
|
635
|
+
var copiedData = this.getSelectionArray();
|
636
|
+
var clipboardPasteDataFormat = copiedData.map(row => row.join('\t')).join('\n');
|
637
|
+
this.copyToClipboard(event, clipboardPasteDataFormat);
|
638
|
+
event.preventDefault();
|
639
|
+
};
|
640
|
+
|
641
|
+
|
642
|
+
// Handle paste functionality
|
643
|
+
DynamicTable.prototype.handlePaste = function(event) {
|
644
|
+
|
645
|
+
event.preventDefault();
|
646
|
+
let pastedData = "";
|
647
|
+
|
648
|
+
if (event.clipboardData || window.clipboardData) {
|
649
|
+
pastedData = (event.clipboardData || window.clipboardData).getData("text");
|
650
|
+
} else {
|
651
|
+
if (navigator.clipboard) {
|
652
|
+
navigator.clipboard.readText()
|
653
|
+
.then(text => {
|
654
|
+
console.log('Pasted content from clipboard:', text);
|
655
|
+
pastedData = text;
|
656
|
+
// Convert the pasted data into a 2D array
|
657
|
+
|
658
|
+
})
|
659
|
+
.catch(err => {
|
660
|
+
console.error('Failed to read clipboard contents:', err);
|
661
|
+
});
|
662
|
+
}
|
663
|
+
}
|
664
|
+
|
665
|
+
const rows = pastedData.trim().split('\n').map(row => row.trim()) ; // Split into rows by newline
|
666
|
+
|
667
|
+
const twoDArray = rows.map(row => row.split('\t')); // Split each row by tab (\t)
|
668
|
+
|
669
|
+
this.pasteDataToTable(twoDArray);
|
670
|
+
|
671
|
+
};
|
672
|
+
|
673
|
+
DynamicTable.prototype.pasteDataToTable = function(twoDArray) {
|
674
|
+
const rowStart = this.pivotRow;
|
675
|
+
const colStart = this.pivotCol;
|
676
|
+
|
677
|
+
const [_, highestRow, __, highestCol] = this.selectionMapRange();
|
678
|
+
|
679
|
+
if (twoDArray && twoDArray.length > 0) {
|
680
|
+
|
681
|
+
let [maxRows,maxCols,isSingle] = (twoDArray.length === 1 && twoDArray[0].length ===1)?
|
682
|
+
[highestRow + 1 - rowStart,highestCol + 1 - colStart,true] : [twoDArray.length,twoDArray[0].length,false];
|
683
|
+
|
684
|
+
let addRows = Math.min(this.rows - rowStart, maxRows);
|
685
|
+
let addCols = Math.min(this.cols - colStart, maxCols);
|
686
|
+
|
687
|
+
for (let i = rowStart; i < rowStart + addRows; i++) {
|
688
|
+
for (let j = colStart; j < colStart + addCols; j++) {
|
689
|
+
let element = document.getElementById(this.prefix+'_r'+(i+1)+'c'+(j+1));
|
690
|
+
if (element.readOnly) {
|
691
|
+
continue;
|
692
|
+
}
|
693
|
+
let valueToPaste = isSingle ? twoDArray[0][0] : twoDArray[i-rowStart][j-colStart];
|
694
|
+
if (element.writeFunction) { //if there are specific limitations or exceptions to manage
|
695
|
+
element.writeFunction(element, valueToPaste);
|
696
|
+
} else {
|
697
|
+
element.value = valueToPaste;
|
698
|
+
}
|
699
|
+
}
|
700
|
+
}
|
701
|
+
|
702
|
+
this.repaintRange(rowStart, rowStart + addRows-1, colStart, colStart + addCols -1 );
|
703
|
+
|
704
|
+
}
|
705
|
+
|
706
|
+
};
|
707
|
+
|
708
|
+
//helper functions
|
709
|
+
|
710
|
+
DynamicTable.prototype.addSpecialControls = function(input, options) {
|
711
|
+
const specialKlass = options['specialControl']?.['class'];
|
712
|
+
if (specialKlass) {
|
713
|
+
input.classList.add(specialKlass);
|
714
|
+
}
|
715
|
+
let targetFunc = options['specialControl']?.['initializingFunction'];
|
716
|
+
if (targetFunc) {
|
717
|
+
this.specialControlFunctions.set(specialKlass,targetFunc);
|
718
|
+
}
|
719
|
+
};
|
720
|
+
|
721
|
+
DynamicTable.prototype.setInput = function(columnOptions,row,col) {
|
722
|
+
switch (columnOptions['input']) {
|
723
|
+
case 'text':
|
724
|
+
var input = document.createElement('input');
|
725
|
+
input.type = 'text';
|
726
|
+
break;
|
727
|
+
case 'decimal':
|
728
|
+
var input = document.createElement('input');
|
729
|
+
input.type = 'text';
|
730
|
+
input.precision = columnOptions['precision']+''||2;
|
731
|
+
this.addDecimalEvents(input);
|
732
|
+
break;
|
733
|
+
case 'date':
|
734
|
+
var input = document.createElement('input');
|
735
|
+
input.type = 'date';
|
736
|
+
input.value = '';
|
737
|
+
input.placeholder = '';
|
738
|
+
break;
|
739
|
+
case 'select':
|
740
|
+
var input = document.createElement('select');
|
741
|
+
addValuesToSelect(input,columnOptions);
|
742
|
+
break;
|
743
|
+
case 'div':
|
744
|
+
var input = document.createElement('div');
|
745
|
+
// var child = document.createElement('div');
|
746
|
+
// input.appendChild(child);
|
747
|
+
break;
|
748
|
+
default:
|
749
|
+
var input = document.createElement('input');
|
750
|
+
input.type = 'text';
|
751
|
+
}
|
752
|
+
setInputStandardOptions(this.prefix,input,row,col,columnOptions);
|
753
|
+
if (typeof child !== 'undefined') {
|
754
|
+
this.addSpecialControls(child, columnOptions);
|
755
|
+
}
|
756
|
+
else {
|
757
|
+
this.addSpecialControls(input, columnOptions);
|
758
|
+
}
|
759
|
+
|
760
|
+
addInputEventHooks(input,columnOptions);
|
761
|
+
return input;
|
762
|
+
}
|
763
|
+
|
764
|
+
function addInputEventHooks(input,options) {
|
765
|
+
Object.entries(options['eventHook']||{}).forEach(
|
766
|
+
function([event,funcHook]) {
|
767
|
+
if (isValidEvent(input,event)) {
|
768
|
+
//the first argument to bind, binds the 'this' element. The remaining arguments if any added,
|
769
|
+
//then are prepended to the function params. if you need event object as a param, add this in your definition post
|
770
|
+
//any planned prepended arguments. JS automatically adds the event object at the end
|
771
|
+
input.addEventListener(event,funcHook.bind(input));
|
772
|
+
}
|
773
|
+
}
|
774
|
+
);
|
775
|
+
}
|
776
|
+
|
777
|
+
function setInputStandardOptions(prefix, input,row,col,options) {
|
778
|
+
input.id = `${prefix}_r${row}c${col}`; // Assign ID in <row><col> format
|
779
|
+
input.name = `${prefix}_r${row}c${col}`; // Assign name so it can be passed in form
|
780
|
+
input.className = 'table-input'; // Class for styling the input
|
781
|
+
|
782
|
+
if (options['readonly']) {
|
783
|
+
input.setAttribute('readonly', options['readonly']);
|
784
|
+
}
|
785
|
+
}
|
786
|
+
|
787
|
+
function addValuesToSelect(input, options) {
|
788
|
+
let values = options['values'] || [];
|
789
|
+
values.forEach(function(val)
|
790
|
+
{
|
791
|
+
const newOption = document.createElement("option");
|
792
|
+
newOption.value = val;
|
793
|
+
newOption.text = val;
|
794
|
+
input.appendChild(newOption);
|
795
|
+
});
|
796
|
+
}
|
797
|
+
|
798
|
+
function isValidEvent(element,eventName) {
|
799
|
+
return typeof element[`on${eventName}`] !== 'undefined';
|
800
|
+
}
|
801
|
+
|
802
|
+
//adding decimal field related event functions
|
803
|
+
|
804
|
+
DynamicTable.prototype.addDecimalEvents = function(input) {
|
805
|
+
input.addEventListener("input",this.onDecimalFocus.bind(this));
|
806
|
+
input.addEventListener("blur",this.onDecimalBlur.bind(this));
|
807
|
+
input.addEventListener("keydown",this.onDecimalKeydownEvent.bind(this));
|
808
|
+
input.addEventListener("paste",this.onDecimalChangeEvent.bind(this));
|
809
|
+
input.writeFunction = writeValueToDecimal;
|
810
|
+
input.value = getInitialValue(input.precision);
|
811
|
+
}
|
812
|
+
|
813
|
+
DynamicTable.prototype.onDecimalChangeEvent = function(event) {
|
814
|
+
const input = event.target;
|
815
|
+
const regex = new RegExp("^\\d+(\\.\\d{0," + input.precision + "})?$");// Regex to allow up to precision decimal places
|
816
|
+
if (!regex.test(input.value)) {
|
817
|
+
input.value = getInitialValue(input.precision);
|
818
|
+
}
|
819
|
+
|
820
|
+
}
|
821
|
+
|
822
|
+
DynamicTable.prototype.onDecimalBlur = function(event) {
|
823
|
+
const input = event.target;
|
824
|
+
let value = input.value;
|
825
|
+
if (!value) return;
|
826
|
+
|
827
|
+
input.value = formatInputValue(value, input.precision);
|
828
|
+
}
|
829
|
+
|
830
|
+
// Function to remove commas when the input is being given
|
831
|
+
DynamicTable.prototype.onDecimalFocus = function(event) {
|
832
|
+
const input = event.target;
|
833
|
+
input.value = input.value.replace(/,/g, '');
|
834
|
+
}
|
835
|
+
|
836
|
+
DynamicTable.prototype.onDecimalKeydownEvent = function(event) {
|
837
|
+
// Get the value of the input field and simulate the new input if the key were added
|
838
|
+
const input = event.target;
|
839
|
+
if ((event.metaKey || event.ctrlKey) && !event.shiftKey) {
|
840
|
+
if ((['C','c'].includes(event.key))
|
841
|
+
||(['V','v'].includes(event.key))) {
|
842
|
+
return;
|
843
|
+
}
|
844
|
+
}
|
845
|
+
|
846
|
+
// Allow control keys (e.g., backspace, delete, arrow keys)
|
847
|
+
if (!this.decimalKeyRange.includes(event.key)) {
|
848
|
+
event.preventDefault();
|
849
|
+
} else {
|
850
|
+
// Get the current caret position
|
851
|
+
const caretPosition = input.selectionStart;
|
852
|
+
// Get the current value of the input field
|
853
|
+
const currentValue = input.value;
|
854
|
+
const key = event.key;
|
855
|
+
const newValue = currentValue.slice(0, caretPosition) + key + currentValue.slice(caretPosition);
|
856
|
+
if (!this.controlKeys.includes(key) ) {
|
857
|
+
const regex = new RegExp("^\\d+(\\.\\d{0," + input.precision + "})?$");// Regex to allow up to precision decimal places
|
858
|
+
if (!regex.test(newValue)) {
|
859
|
+
event.preventDefault();
|
860
|
+
}
|
861
|
+
}
|
862
|
+
|
863
|
+
}
|
864
|
+
|
865
|
+
}
|
866
|
+
|
867
|
+
function formatInputValue(value, precision) {
|
868
|
+
// Remove any existing commas
|
869
|
+
value = value.replace(/,/g, '');
|
870
|
+
|
871
|
+
let parts = value.split('.');
|
872
|
+
//remove all leading zeros
|
873
|
+
parts[0] = parts[0].replace(/^0+/, '');
|
874
|
+
parts[0] = parts[0] === ''? '0': parts[0];
|
875
|
+
// Add commas to every third digit before the decimal point
|
876
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
877
|
+
|
878
|
+
//in case of missing decimal point
|
879
|
+
if (parts.length === 1 && precision > 0) {
|
880
|
+
parts[1] = appendDecimalZeros(precision);
|
881
|
+
}
|
882
|
+
|
883
|
+
//if zeros are less then precision, replace remaining zeros
|
884
|
+
if (parts[1].length < precision) {
|
885
|
+
parts[1] = parts[1] + appendDecimalZeros(precision - parts[1].length);
|
886
|
+
}
|
887
|
+
|
888
|
+
return parts.join('.');
|
889
|
+
}
|
890
|
+
|
891
|
+
function appendDecimalZeros(precision) {
|
892
|
+
if (precision && precision > 0) {
|
893
|
+
let precision_arr = Array.from({ length: precision }, (_, i) => 0);
|
894
|
+
if (precision_arr.length > 0) {
|
895
|
+
return precision_arr.join('');
|
896
|
+
}
|
897
|
+
}
|
898
|
+
return '';
|
899
|
+
}
|
900
|
+
|
901
|
+
function adjustToPrecision(value, targetPrecision) {
|
902
|
+
let parts = value.split('.');
|
903
|
+
if (parts[1] && parts[1].length > targetPrecision) {
|
904
|
+
parts[1] = parts[1].slice(0,targetPrecision);
|
905
|
+
}
|
906
|
+
return parts.join(".");
|
907
|
+
}
|
908
|
+
|
909
|
+
function writeValueToDecimal(input, value) {
|
910
|
+
//remove any commas for testing with regexpression
|
911
|
+
value = value.replace(/,/g, '');
|
912
|
+
value = adjustToPrecision(value, input.precision);
|
913
|
+
const regex = new RegExp("^\\d+(\\.\\d{0," + input.precision + "})?$");// Regex to allow up to precision decimal places
|
914
|
+
if (!regex.test(value)) {
|
915
|
+
input.value = getInitialValue(input.precision);
|
916
|
+
} else {
|
917
|
+
input.value = formatInputValue(value, input.precision);
|
918
|
+
}
|
919
|
+
}
|
920
|
+
|
921
|
+
function getInitialValue(precision) {
|
922
|
+
if (precision && precision > 0) {
|
923
|
+
return "0." + appendDecimalZeros(precision);
|
924
|
+
}
|
925
|
+
return "0";
|
926
|
+
}
|
927
|
+
|
928
|
+
|
929
|
+
return DynamicTable;
|
930
|
+
}));
|
@@ -0,0 +1,56 @@
|
|
1
|
+
.dynamic-table {
|
2
|
+
border-collapse: collapse;
|
3
|
+
overflow: auto;
|
4
|
+
/* width: 100%; */
|
5
|
+
/* table-layout: fixed; Ensures all columns are the same width */
|
6
|
+
}
|
7
|
+
.dynamic-table th, .dynamic-table td {
|
8
|
+
border: 1px solid #000;
|
9
|
+
padding: 0;
|
10
|
+
text-align: center;
|
11
|
+
position: relative; /* Needed for absolute positioning of the input */
|
12
|
+
}
|
13
|
+
|
14
|
+
.table-container {
|
15
|
+
overflow:scroll;
|
16
|
+
width: 100%;
|
17
|
+
}
|
18
|
+
|
19
|
+
.table-input {
|
20
|
+
width: 100%; /* Makes the input field fill the width of the table cell */
|
21
|
+
height: 100%; /* Makes the input field fill the height of the table cell */
|
22
|
+
box-sizing: border-box; /* Ensures padding and border are included in the width/height */
|
23
|
+
border: none; /* Removes the default input field border */
|
24
|
+
text-align: center;
|
25
|
+
}
|
26
|
+
.table-input:focus {
|
27
|
+
outline: none; /* Removes the focus outline to give a clean look */
|
28
|
+
}
|
29
|
+
.dynamic-table td .table-input.selected {
|
30
|
+
background-color: #d1e0e0; /* Highlight selected cells */
|
31
|
+
}
|
32
|
+
.dynamic-table td {
|
33
|
+
height: 40px; /* Set a fixed height for cells */
|
34
|
+
}
|
35
|
+
|
36
|
+
.dynamic-table th, .dynamic-table td {
|
37
|
+
width: 10rem;
|
38
|
+
}
|
39
|
+
|
40
|
+
.uneditable .table-input {
|
41
|
+
user-select: none; /* Prevent text selection */
|
42
|
+
pointer-events: none; /* Disable interaction */
|
43
|
+
color: gray; /* Change text color */
|
44
|
+
background-color: lightgray;
|
45
|
+
}
|
46
|
+
|
47
|
+
.selectize-dropdown {
|
48
|
+
position: absolute; /* Position it outside of normal flow */
|
49
|
+
z-index: 9999; /* Make sure it appears above other elements */
|
50
|
+
}
|
51
|
+
|
52
|
+
td {
|
53
|
+
position: relative; /* The parent <td> needs to be relative for absolute child positioning */
|
54
|
+
width: 150px;
|
55
|
+
height: 50px;
|
56
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module DynamicTable
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
initializer "dynamic_table.assets.precompile" do |app|
|
4
|
+
puts "DynamicTable Engine Loaded" # Debugging statemen
|
5
|
+
app.config.assets.precompile += %w(dynamic_table.scss dynamic_table.js)
|
6
|
+
end
|
7
|
+
puts "DynamicTable Engine Loaded2" # Debugging statemen
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/dynamic_table.rb
|
4
|
+
require "dynamic_table/version"
|
5
|
+
require "dynamic_table/engine" # This line loads the engine
|
6
|
+
|
7
|
+
module DynamicTable
|
8
|
+
puts "DynamicTable Module Loaded"
|
9
|
+
# class Error < StandardError; end
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamic_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jayanth
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-30 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |-
|
14
|
+
This GEM is an attempt to create editable tables wherein cells may contain text, dates, select,decimals and so on.
|
15
|
+
You can select a set of cells and paste data. Paste from excel or other spreadsheets on to the same. You can add styles at a column
|
16
|
+
or row level as well.
|
17
|
+
email:
|
18
|
+
- jayanth.ravindran@gmail.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- app/assets/javascripts/dynamic_table.js
|
24
|
+
- app/assets/stylesheets/dynamic_table.scss
|
25
|
+
- lib/dynamic_table.rb
|
26
|
+
- lib/dynamic_table/engine.rb
|
27
|
+
- lib/dynamic_table/version.rb
|
28
|
+
homepage:
|
29
|
+
licenses: []
|
30
|
+
metadata:
|
31
|
+
allowed_push_host: https://rubygems.org
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubygems_version: 3.5.9
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Gem to create editable tables dynamically using javascript
|
51
|
+
test_files: []
|