PluginProbe
TablePress – Tables in WordPress made easy / 2.3.2
TablePress – Tables in WordPress made easy v2.3.2
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / admin / js / jspreadsheet.js

jspreadsheet.js in TablePress – Tables in WordPress made easy 2.3.2, at admin/js/jspreadsheet.js

9,320 lines 264.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Jspreadsheet v4.2.1
3 *
4 * Website: https://bossanova.uk/jspreadsheet/
5 * Description: Create amazing web based spreadsheets.
6 *
7 * This software is distributed under MIT License
8 */
9
10 // TablePress: var formula = ... removed.
11
12 ;(function (global, factory) {
13 // TablePress: Comment out next to lines to force creation of a global jspreadsheet object.
14 // typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15 // typeof define === 'function' && define.amd ? define(factory) :
16 global.jspreadsheet = global.jexcel = factory();
17 }(window, (function () { // TablePress: Use `window` instead of `this` to get a global jspreadsheet object.
18
19 'use strict';
20
21 // Basic version information
22 var Version = function() {
23 // Information
24 var info = {
25 title: 'Jspreadsheet',
26 version: '4.2.1',
27 type: 'CE',
28 host: 'https://bossanova.uk/jspreadsheet',
29 license: 'MIT',
30 print: function() {
31 return [ this.title + ' ' + this.type + ' ' + this.version, this.host, this.license ].join('\r\n');
32 }
33 }
34
35 return function() {
36 return info;
37 };
38 }();
39
40 /**
41 * The value is a formula
42 */
43 var isFormula = function(value) {
44 var v = (''+value)[0];
45 return v == '=' || v == '#' ? true : false;
46 }
47
48 /**
49 * Get the mask in the jSuites.mask format
50 */
51 var getMask = function(o) {
52 if (o.format || o.mask || o.locale) {
53 var opt = {};
54 if (o.mask) {
55 opt.mask = o.mask;
56 } else if (o.format) {
57 opt.mask = o.format;
58 } else {
59 opt.locale = o.locale;
60 opt.options = o.options;
61 }
62
63 if (o.decimal) {
64 if (! opt.options) {
65 opt.options = {};
66 }
67 opt.options = { decimal: o.decimal };
68 }
69 return opt;
70 }
71
72 return null;
73 }
74
75 // Jspreadsheet core object
76 var jexcel = (function(el, options) {
77 // Create jspreadsheet object
78 var obj = {};
79 obj.options = {};
80
81 if (! (el instanceof Element || el instanceof HTMLDocument)) {
82 console.error('Jspreadsheet: el is not a valid DOM element');
83 return false;
84 } else if (el.tagName == 'TABLE') {
85 if (options = jexcel.createFromTable(el, options)) {
86 var div = document.createElement('div');
87 el.parentNode.insertBefore(div, el);
88 el.remove();
89 el = div;
90 } else {
91 console.error('Jspreadsheet: el is not a valid DOM element');
92 return false;
93 }
94 }
95
96 // Loading default configuration
97 var defaults = {
98 // External data
99 url:null,
100 // Ajax options
101 method: 'GET',
102 requestVariables: null,
103 // Data
104 data:null,
105 // Custom sorting handler
106 sorting:null,
107 // Copy behavior
108 copyCompatibility:false,
109 root:null,
110 // Rows and columns definitions
111 rows:[],
112 columns:[],
113 // Deprected legacy options
114 colHeaders:[],
115 colWidths:[],
116 colAlignments:[],
117 nestedHeaders:null,
118 // Column width that is used by default
119 defaultColWidth:50,
120 defaultColAlign:'center',
121 // Rows height default
122 defaultRowHeight: null,
123 // Spare rows and columns
124 minSpareRows:0,
125 minSpareCols:0,
126 // Minimal table dimensions
127 minDimensions:[0,0],
128 // Allow Export
129 allowExport:true,
130 // @type {boolean} - Include the header titles on download
131 includeHeadersOnDownload:false,
132 // @type {boolean} - Include the header titles on copy
133 includeHeadersOnCopy:false,
134 // Allow column sorting
135 columnSorting:true,
136 // Allow column dragging
137 columnDrag:false,
138 // Allow column resizing
139 columnResize:true,
140 // Allow row resizing
141 rowResize:false,
142 // Allow row dragging
143 rowDrag:true,
144 // Allow table edition
145 editable:true,
146 // Allow new rows
147 allowInsertRow:true,
148 // Allow new rows
149 allowManualInsertRow:true,
150 // Allow new columns
151 allowInsertColumn:true,
152 // Allow new rows
153 allowManualInsertColumn:true,
154 // Allow row delete
155 allowDeleteRow:true,
156 // Allow deleting of all rows
157 allowDeletingAllRows:false,
158 // Allow column delete
159 allowDeleteColumn:true,
160 // Allow rename column
161 allowRenameColumn:true,
162 // Allow comments
163 allowComments:false,
164 // Global wrap
165 wordWrap:false,
166 // Image options
167 imageOptions: null,
168 // CSV source
169 csv:null,
170 // Filename
171 csvFileName:'jspreadsheet',
172 // Consider first line as header
173 csvHeaders:true,
174 // Delimiters
175 csvDelimiter:',',
176 // First row as header
177 parseTableFirstRowAsHeader:false,
178 parseTableAutoCellType:false,
179 // Disable corner selection
180 selectionCopy:true,
181 // Merged cells
182 mergeCells:{},
183 // Create toolbar
184 toolbar:null,
185 // Allow search
186 search:false,
187 // Create pagination
188 pagination:false,
189 paginationOptions:null,
190 // Full screen
191 fullscreen:false,
192 // Lazy loading
193 lazyLoading:false,
194 loadingSpin:false,
195 // Table overflow
196 tableOverflow:false,
197 tableHeight:'300px',
198 tableWidth:null,
199 textOverflow:false,
200 // Meta
201 meta: null,
202 // Style
203 style:null,
204 classes:null,
205 // Execute formulas
206 parseFormulas:true,
207 autoIncrement:true,
208 autoCasting:true,
209 // Security
210 secureFormulas:true,
211 stripHTML:true,
212 stripHTMLOnCopy:false,
213 // Filters
214 filters:false,
215 footers:null,
216 // Event handles
217 onundo:null,
218 onredo:null,
219 onload:null,
220 onchange:null,
221 oncomments:null,
222 onbeforechange:null,
223 onafterchanges:null,
224 onbeforeinsertrow: null,
225 oninsertrow:null,
226 onbeforeinsertcolumn: null,
227 oninsertcolumn:null,
228 onbeforedeleterow:null,
229 ondeleterow:null,
230 onbeforedeletecolumn:null,
231 ondeletecolumn:null,
232 onmoverow:null,
233 onmovecolumn:null,
234 onresizerow:null,
235 onresizecolumn:null,
236 onsort:null,
237 onselection:null,
238 oncopy:null,
239 onpaste:null,
240 onbeforepaste:null,
241 onmerge:null,
242 onfocus:null,
243 onblur:null,
244 onchangeheader:null,
245 oncreateeditor:null,
246 oneditionstart:null,
247 oneditionend:null,
248 onchangestyle:null,
249 onchangemeta:null,
250 onchangepage:null,
251 onbeforesave:null,
252 onsave:null,
253 // Global event dispatcher
254 onevent:null,
255 // Persistance
256 persistance:false,
257 // Customize any cell behavior
258 updateTable:null,
259 // Detach the HTML table when calling updateTable
260 detachForUpdates: false,
261 freezeColumns:null,
262 // Texts
263 text:{
264 noRecordsFound: 'No records found',
265 showingPage: 'Showing page {0} of {1} entries',
266 show: 'Show ',
267 search: 'Search',
268 entries: ' entries',
269 columnName: 'Column name',
270 insertANewColumnBefore: 'Insert a new column before',
271 insertANewColumnAfter: 'Insert a new column after',
272 deleteSelectedColumns: 'Delete selected columns',
273 renameThisColumn: 'Rename this column',
274 orderAscending: 'Order ascending',
275 orderDescending: 'Order descending',
276 insertANewRowBefore: 'Insert a new row before',
277 insertANewRowAfter: 'Insert a new row after',
278 deleteSelectedRows: 'Delete selected rows',
279 editComments: 'Edit comments',
280 addComments: 'Add comments',
281 comments: 'Comments',
282 clearComments: 'Clear comments',
283 copy: 'Copy...',
284 paste: 'Paste...',
285 saveAs: 'Save as...',
286 about: 'About',
287 areYouSureToDeleteTheSelectedRows: 'Are you sure to delete the selected rows?',
288 areYouSureToDeleteTheSelectedColumns: 'Are you sure to delete the selected columns?',
289 thisActionWillDestroyAnyExistingMergedCellsAreYouSure: 'This action will destroy any existing merged cells. Are you sure?',
290 thisActionWillClearYourSearchResultsAreYouSure: 'This action will clear your search results. Are you sure?',
291 thereIsAConflictWithAnotherMergedCell: 'There is a conflict with another merged cell',
292 invalidMergeProperties: 'Invalid merged properties',
293 cellAlreadyMerged: 'Cell already merged',
294 noCellsSelected: 'No cells selected',
295 },
296 // About message
297 about: true,
298 };
299
300 // Loading initial configuration from user
301 for (var property in defaults) {
302 if (options && options.hasOwnProperty(property)) {
303 if (property === 'text') {
304 obj.options[property] = defaults[property];
305 for (var textKey in options[property]) {
306 if (options[property].hasOwnProperty(textKey)){
307 obj.options[property][textKey] = options[property][textKey];
308 }
309 }
310 } else {
311 obj.options[property] = options[property];
312 }
313 } else {
314 obj.options[property] = defaults[property];
315 }
316 }
317
318 // Global elements
319 obj.el = el;
320 obj.corner = null;
321 obj.contextMenu = null;
322 obj.textarea = null;
323 obj.ads = null;
324 obj.content = null;
325 obj.table = null;
326 obj.thead = null;
327 obj.tbody = null;
328 obj.rows = [];
329 obj.results = null;
330 obj.searchInput = null;
331 obj.toolbar = null;
332 obj.pagination = null;
333 obj.pageNumber = null;
334 obj.headerContainer = null;
335 obj.colgroupContainer = null;
336
337 // Containers
338 obj.headers = [];
339 obj.records = [];
340 obj.history = [];
341 obj.formula = [];
342 obj.colgroup = [];
343 obj.selection = [];
344 obj.highlighted = [];
345 obj.selectedCell = null;
346 obj.selectedContainer = null;
347 obj.style = [];
348 obj.data = null;
349 obj.filter = null;
350 obj.filters = [];
351
352 // Internal controllers
353 obj.cursor = null;
354 obj.historyIndex = -1;
355 obj.ignoreEvents = false;
356 obj.ignoreHistory = false;
357 obj.edition = null;
358 obj.hashString = null;
359 obj.resizing = null;
360 obj.dragging = null;
361
362 // Lazy loading
363 if (obj.options.lazyLoading == true && (obj.options.tableOverflow == false && obj.options.fullscreen == false)) {
364 console.error('Jspreadsheet: The lazyloading only works when tableOverflow = yes or fullscreen = yes');
365 obj.options.lazyLoading = false;
366 }
367
368 /**
369 * Activate/Disable fullscreen
370 * use programmatically : table.fullscreen(); or table.fullscreen(true); or table.fullscreen(false);
371 * @Param {boolean} activate
372 */
373 obj.fullscreen = function(activate) {
374 // If activate not defined, get reverse options.fullscreen
375 if (activate == null) {
376 activate = ! obj.options.fullscreen;
377 }
378
379 // If change
380 if (obj.options.fullscreen != activate) {
381 obj.options.fullscreen = activate;
382
383 // Test LazyLoading conflict
384 if (activate == true) {
385 el.classList.add('fullscreen');
386 } else {
387 el.classList.remove('fullscreen');
388 }
389 }
390 }
391
392 /**
393 * Trigger events
394 */
395 obj.dispatch = function(event) {
396 // Dispatch events
397 if (! obj.ignoreEvents) {
398 // Call global event
399 if (typeof(obj.options.onevent) == 'function') {
400 var ret = obj.options.onevent.apply(this, arguments);
401 }
402 // Call specific events
403 if (typeof(obj.options[event]) == 'function') {
404 var ret = obj.options[event].apply(this, Array.prototype.slice.call(arguments, 1));
405 }
406 }
407
408 // Persistance
409 if (event == 'onafterchanges' && obj.options.persistance) {
410 var url = obj.options.persistance == true ? obj.options.url : obj.options.persistance;
411 var data = obj.prepareJson(arguments[2]);
412 obj.save(url, data);
413 }
414
415 return ret;
416 }
417
418 /**
419 * Prepare the jspreadsheet table
420 *
421 * @Param config
422 */
423 obj.prepareTable = function() {
424 // Loading initial data from remote sources
425 var results = [];
426
427 // Number of columns
428 var size = obj.options.columns.length;
429
430 if (obj.options.data && typeof(obj.options.data[0]) !== 'undefined') {
431 // Data keys
432 var keys = Object.keys(obj.options.data[0]);
433
434 if (keys.length > size) {
435 size = keys.length;
436 }
437 }
438
439 // Minimal dimensions
440 if (obj.options.minDimensions[0] > size) {
441 size = obj.options.minDimensions[0];
442 }
443
444 // Requests
445 var multiple = [];
446
447 // Preparations
448 for (var i = 0; i < size; i++) {
449 // Deprected options. You should use only columns
450 if (! obj.options.colHeaders[i]) {
451 obj.options.colHeaders[i] = '';
452 }
453 if (! obj.options.colWidths[i]) {
454 obj.options.colWidths[i] = obj.options.defaultColWidth;
455 }
456 if (! obj.options.colAlignments[i]) {
457 obj.options.colAlignments[i] = obj.options.defaultColAlign;
458 }
459
460 // Default column description
461 if (! obj.options.columns[i]) {
462 obj.options.columns[i] = { type:'text' };
463 } else if (! obj.options.columns[i].type) {
464 obj.options.columns[i].type = 'text';
465 }
466 if (! obj.options.columns[i].name) {
467 obj.options.columns[i].name = keys && keys[i] ? keys[i] : i;
468 }
469 if (! obj.options.columns[i].source) {
470 obj.options.columns[i].source = [];
471 }
472 if (! obj.options.columns[i].options) {
473 obj.options.columns[i].options = [];
474 }
475 if (! obj.options.columns[i].editor) {
476 obj.options.columns[i].editor = null;
477 }
478 if (! obj.options.columns[i].allowEmpty) {
479 obj.options.columns[i].allowEmpty = false;
480 }
481 if (! obj.options.columns[i].title) {
482 obj.options.columns[i].title = obj.options.colHeaders[i] ? obj.options.colHeaders[i] : '';
483 }
484 if (! obj.options.columns[i].width) {
485 obj.options.columns[i].width = obj.options.colWidths[i] ? obj.options.colWidths[i] : obj.options.defaultColWidth;
486 }
487 if (! obj.options.columns[i].align) {
488 obj.options.columns[i].align = obj.options.colAlignments[i] ? obj.options.colAlignments[i] : 'center';
489 }
490
491 // Pre-load initial source for json autocomplete
492 if (obj.options.columns[i].type == 'autocomplete' || obj.options.columns[i].type == 'dropdown') {
493 // if remote content
494 if (obj.options.columns[i].url) {
495 multiple.push({
496 url: obj.options.columns[i].url,
497 index: i,
498 method: 'GET',
499 dataType: 'json',
500 success: function(data) {
501 var source = [];
502 for (var i = 0; i < data.length; i++) {
503 obj.options.columns[this.index].source.push(data[i]);
504 }
505 }
506 });
507 }
508 } else if (obj.options.columns[i].type == 'calendar') {
509 // Default format for date columns
510 if (! obj.options.columns[i].options.format) {
511 obj.options.columns[i].options.format = 'DD/MM/YYYY';
512 }
513 }
514 }
515 // Create the table when is ready
516 if (! multiple.length) {
517 obj.createTable();
518 } else {
519 jSuites.ajax(multiple, function() {
520 obj.createTable();
521 });
522 }
523 }
524
525 obj.createTable = function() {
526 // Elements
527 obj.table = document.createElement('table');
528 obj.thead = document.createElement('thead');
529 obj.tbody = document.createElement('tbody');
530
531 // Create headers controllers
532 obj.headers = [];
533 obj.colgroup = [];
534
535 // Create table container
536 obj.content = document.createElement('div');
537 obj.content.classList.add('jexcel_content');
538 obj.content.onscroll = function(e) {
539 obj.scrollControls(e);
540 }
541 obj.content.onwheel = function(e) {
542 obj.wheelControls(e);
543 }
544
545 // Create toolbar object
546 obj.toolbar = document.createElement('div');
547 obj.toolbar.classList.add('jexcel_toolbar');
548
549 // Search
550 var searchContainer = document.createElement('div');
551 var searchText = document.createTextNode((obj.options.text.search) + ': ');
552 obj.searchInput = document.createElement('input');
553 obj.searchInput.classList.add('jexcel_search');
554 searchContainer.appendChild(searchText);
555 searchContainer.appendChild(obj.searchInput);
556 obj.searchInput.onfocus = function() {
557 obj.resetSelection();
558 }
559
560 // Pagination select option
561 var paginationUpdateContainer = document.createElement('div');
562
563 if (obj.options.pagination > 0 && obj.options.paginationOptions && obj.options.paginationOptions.length > 0) {
564 obj.paginationDropdown = document.createElement('select');
565 obj.paginationDropdown.classList.add('jexcel_pagination_dropdown');
566 obj.paginationDropdown.onchange = function() {
567 obj.options.pagination = parseInt(this.value);
568 obj.page(0);
569 }
570
571 for (var i = 0; i < obj.options.paginationOptions.length; i++) {
572 var temp = document.createElement('option');
573 temp.value = obj.options.paginationOptions[i];
574 temp.innerHTML = obj.options.paginationOptions[i];
575 obj.paginationDropdown.appendChild(temp);
576 }
577
578 // Set initial pagination value
579 obj.paginationDropdown.value = obj.options.pagination;
580
581 paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.show));
582 paginationUpdateContainer.appendChild(obj.paginationDropdown);
583 paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.entries));
584 }
585
586 // Filter and pagination container
587 var filter = document.createElement('div');
588 filter.classList.add('jexcel_filter');
589 filter.appendChild(paginationUpdateContainer);
590 filter.appendChild(searchContainer);
591
592 // Colsgroup
593 obj.colgroupContainer = document.createElement('colgroup');
594 var tempCol = document.createElement('col');
595 tempCol.setAttribute('width', '50');
596 obj.colgroupContainer.appendChild(tempCol);
597
598 // Nested
599 if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
600 // Flexible way to handle nestedheaders
601 if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
602 for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
603 obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders[j]));
604 }
605 } else {
606 obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders));
607 }
608 }
609
610 // Row
611 obj.headerContainer = document.createElement('tr');
612 var tempCol = document.createElement('td');
613 tempCol.classList.add('jexcel_selectall');
614 obj.headerContainer.appendChild(tempCol);
615
616 for (var i = 0; i < obj.options.columns.length; i++) {
617 // Create header
618 obj.createCellHeader(i);
619 // Append cell to the container
620 obj.headerContainer.appendChild(obj.headers[i]);
621 obj.colgroupContainer.appendChild(obj.colgroup[i]);
622 }
623
624 obj.thead.appendChild(obj.headerContainer);
625
626 // Filters
627 if (obj.options.filters == true) {
628 obj.filter = document.createElement('tr');
629 var td = document.createElement('td');
630 obj.filter.appendChild(td);
631
632 for (var i = 0; i < obj.options.columns.length; i++) {
633 var td = document.createElement('td');
634 td.innerHTML = '&nbsp;';
635 td.setAttribute('data-x', i);
636 td.className = 'jexcel_column_filter';
637 if (obj.options.columns[i].type == 'hidden') {
638 td.style.display = 'none';
639 }
640 obj.filter.appendChild(td);
641 }
642
643 obj.thead.appendChild(obj.filter);
644 }
645
646 // Content table
647 obj.table = document.createElement('table');
648 obj.table.classList.add('jexcel');
649 obj.table.setAttribute('cellpadding', '0');
650 obj.table.setAttribute('cellspacing', '0');
651 obj.table.setAttribute('unselectable', 'yes');
652 //obj.table.setAttribute('onselectstart', 'return false');
653 obj.table.appendChild(obj.colgroupContainer);
654 obj.table.appendChild(obj.thead);
655 obj.table.appendChild(obj.tbody);
656
657 if (! obj.options.textOverflow) {
658 obj.table.classList.add('jexcel_overflow');
659 }
660
661 // Spreadsheet corner
662 obj.corner = document.createElement('div');
663 obj.corner.className = 'jexcel_corner';
664 obj.corner.setAttribute('unselectable', 'on');
665 obj.corner.setAttribute('onselectstart', 'return false');
666
667 if (obj.options.selectionCopy == false) {
668 obj.corner.style.display = 'none';
669 }
670
671 // Textarea helper
672 obj.textarea = document.createElement('textarea');
673 obj.textarea.className = 'jexcel_textarea';
674 obj.textarea.id = 'jexcel_textarea';
675 obj.textarea.tabIndex = '-1';
676
677 // Contextmenu container
678 obj.contextMenu = document.createElement('div');
679 obj.contextMenu.className = 'jexcel_contextmenu';
680
681 // Create element
682 jSuites.contextmenu(obj.contextMenu, {
683 onclick:function() {
684 obj.contextMenu.contextmenu.close(false);
685 }
686 });
687
688 // Powered by Jspreadsheet
689 var ads = document.createElement('a');
690 ads.setAttribute('href', 'https://bossanova.uk/jspreadsheet/');
691 obj.ads = document.createElement('div');
692 obj.ads.className = 'jexcel_about';
693 /* // TablePress: Remove this as it loads a file from the Jspreadsheet server.
694 try {
695 if (typeof(sessionStorage) !== "undefined" && ! sessionStorage.getItem('jexcel')) {
696 sessionStorage.setItem('jexcel', true);
697 var img = document.createElement('img');
698 img.src = '//bossanova.uk/jspreadsheet/logo.png';
699 ads.appendChild(img);
700 }
701 } catch (exception) {
702 }
703 */
704 var span = document.createElement('span');
705 span.innerHTML = 'Jspreadsheet CE';
706 ads.appendChild(span);
707 obj.ads.appendChild(ads);
708
709 // Create table container TODO: frozen columns
710 var container = document.createElement('div');
711 container.classList.add('jexcel_table');
712
713 // Pagination
714 obj.pagination = document.createElement('div');
715 obj.pagination.classList.add('jexcel_pagination');
716 var paginationInfo = document.createElement('div');
717 var paginationPages = document.createElement('div');
718 obj.pagination.appendChild(paginationInfo);
719 obj.pagination.appendChild(paginationPages);
720
721 // Hide pagination if not in use
722 if (! obj.options.pagination) {
723 obj.pagination.style.display = 'none';
724 }
725
726 // Append containers to the table
727 if (obj.options.search == true) {
728 el.appendChild(filter);
729 }
730
731 // Elements
732 obj.content.appendChild(obj.table);
733 obj.content.appendChild(obj.corner);
734 obj.content.appendChild(obj.textarea);
735
736 el.appendChild(obj.toolbar);
737 el.appendChild(obj.content);
738 el.appendChild(obj.pagination);
739 el.appendChild(obj.contextMenu);
740 el.appendChild(obj.ads);
741 el.classList.add('jexcel_container');
742
743 // Create toolbar
744 if (obj.options.toolbar && obj.options.toolbar.length) {
745 obj.createToolbar();
746 }
747
748 // Fullscreen
749 if (obj.options.fullscreen == true) {
750 el.classList.add('fullscreen');
751 } else {
752 // Overflow
753 if (obj.options.tableOverflow == true) {
754 if (obj.options.tableHeight) {
755 obj.content.style['overflow-y'] = 'auto';
756 obj.content.style['box-shadow'] = 'rgb(221 221 221) 2px 2px 5px 0.1px';
757 obj.content.style.maxHeight = obj.options.tableHeight;
758 }
759 if (obj.options.tableWidth) {
760 obj.content.style['overflow-x'] = 'auto';
761 obj.content.style.width = obj.options.tableWidth;
762 }
763 }
764 }
765
766 // With toolbars
767 if (obj.options.tableOverflow != true && obj.options.toolbar) {
768 el.classList.add('with-toolbar');
769 }
770
771 // Actions
772 if (obj.options.columnDrag == true) {
773 obj.thead.classList.add('draggable');
774 }
775 if (obj.options.columnResize == true) {
776 obj.thead.classList.add('resizable');
777 }
778 if (obj.options.rowDrag == true) {
779 obj.tbody.classList.add('draggable');
780 }
781 if (obj.options.rowResize == true) {
782 obj.tbody.classList.add('resizable');
783 }
784
785 // Load data
786 obj.setData();
787
788 // Style
789 if (obj.options.style) {
790 obj.setStyle(obj.options.style, null, null, 1, 1);
791 }
792
793 // Classes
794 if (obj.options.classes) {
795 var k = Object.keys(obj.options.classes);
796 for (var i = 0; i < k.length; i++) {
797 var cell = jexcel.getIdFromColumnName(k[i], true);
798 obj.records[cell[1]][cell[0]].classList.add(obj.options.classes[k[i]]);
799 }
800 }
801 }
802
803 /**
804 * Refresh the data
805 *
806 * @return void
807 */
808 obj.refresh = function() {
809 if (obj.options.url) {
810 // Loading
811 if (obj.options.loadingSpin == true) {
812 jSuites.loading.show();
813 }
814
815 jSuites.ajax({
816 url: obj.options.url,
817 method: obj.options.method,
818 data: obj.options.requestVariables,
819 dataType: 'json',
820 success: function(result) {
821 // Data
822 obj.options.data = (result.data) ? result.data : result;
823 // Prepare table
824 obj.setData();
825 // Hide spin
826 if (obj.options.loadingSpin == true) {
827 jSuites.loading.hide();
828 }
829 }
830 });
831 } else {
832 obj.setData();
833 }
834 }
835
836 /**
837 * Set data
838 *
839 * @param array data In case no data is sent, default is reloaded
840 * @return void
841 */
842 obj.setData = function(data) {
843 // Update data
844 if (data) {
845 if (typeof(data) == 'string') {
846 data = JSON.parse(data);
847 }
848
849 obj.options.data = data;
850 }
851
852 // Data
853 if (! obj.options.data) {
854 obj.options.data = [];
855 }
856
857 // Prepare data
858 if (obj.options.data && obj.options.data[0]) {
859 if (! Array.isArray(obj.options.data[0])) {
860 var data = [];
861 for (var j = 0; j < obj.options.data.length; j++) {
862 var row = [];
863 for (var i = 0; i < obj.options.columns.length; i++) {
864 row[i] = obj.options.data[j][obj.options.columns[i].name];
865 }
866 data.push(row);
867 }
868
869 obj.options.data = data;
870 }
871 }
872
873 // Adjust minimal dimensions
874 var j = 0;
875 var i = 0;
876 var size_i = obj.options.columns.length;
877 var size_j = obj.options.data.length;
878 var min_i = obj.options.minDimensions[0];
879 var min_j = obj.options.minDimensions[1];
880 var max_i = min_i > size_i ? min_i : size_i;
881 var max_j = min_j > size_j ? min_j : size_j;
882
883 for (j = 0; j < max_j; j++) {
884 for (i = 0; i < max_i; i++) {
885 if (obj.options.data[j] == undefined) {
886 obj.options.data[j] = [];
887 }
888
889 if (obj.options.data[j][i] == undefined) {
890 obj.options.data[j][i] = '';
891 }
892 }
893 }
894
895 // Reset containers
896 obj.rows = [];
897 obj.results = null;
898 obj.records = [];
899 obj.history = [];
900
901 // Reset internal controllers
902 obj.historyIndex = -1;
903
904 // Reset data
905 obj.tbody.innerHTML = '';
906
907 // Lazy loading
908 if (obj.options.lazyLoading == true) {
909 // Load only 100 records
910 var startNumber = 0
911 var finalNumber = obj.options.data.length < 100 ? obj.options.data.length : 100;
912
913 if (obj.options.pagination) {
914 obj.options.pagination = false;
915 console.error('Jspreadsheet: Pagination will be disable due the lazyLoading');
916 }
917 } else if (obj.options.pagination) {
918 // Pagination
919 if (! obj.pageNumber) {
920 obj.pageNumber = 0;
921 }
922 var quantityPerPage = obj.options.pagination;
923 startNumber = (obj.options.pagination * obj.pageNumber);
924 finalNumber = (obj.options.pagination * obj.pageNumber) + obj.options.pagination;
925
926 if (obj.options.data.length < finalNumber) {
927 finalNumber = obj.options.data.length;
928 }
929 } else {
930 var startNumber = 0;
931 var finalNumber = obj.options.data.length;
932 }
933
934 // Append nodes to the HTML
935 for (j = 0; j < obj.options.data.length; j++) {
936 // Create row
937 var tr = obj.createRow(j, obj.options.data[j]);
938 // Append line to the table
939 if (j >= startNumber && j < finalNumber) {
940 obj.tbody.appendChild(tr);
941 }
942 }
943
944 if (obj.options.lazyLoading == true) {
945 // Do not create pagination with lazyloading activated
946 } else if (obj.options.pagination) {
947 obj.updatePagination();
948 }
949
950 // Merge cells
951 if (obj.options.mergeCells) {
952 var keys = Object.keys(obj.options.mergeCells);
953 for (var i = 0; i < keys.length; i++) {
954 var num = obj.options.mergeCells[keys[i]];
955 obj.setMerge(keys[i], num[0], num[1], 1);
956 }
957 }
958
959 // Update table with custom configurations if applicable
960 obj.updateTable();
961
962 // Onload
963 obj.dispatch('onload', el, obj);
964 }
965
966 /**
967 * Get the whole table data
968 *
969 * @param bool get highlighted cells only
970 * @return array data
971 */
972 obj.getData = function(highlighted, dataOnly) {
973 // Control vars
974 var dataset = [];
975 var px = 0;
976 var py = 0;
977
978 // Data type
979 var dataType = dataOnly == true || obj.options.copyCompatibility == false ? true : false;
980
981 // Column and row length
982 var x = obj.options.columns.length
983 var y = obj.options.data.length
984
985 // Go through the columns to get the data
986 for (var j = 0; j < y; j++) {
987 px = 0;
988 for (var i = 0; i < x; i++) {
989 // Cell selected or fullset
990 if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
991 // Get value
992 if (! dataset[py]) {
993 dataset[py] = [];
994 }
995 if (! dataType) {
996 dataset[py][px] = obj.records[j][i].innerHTML;
997 } else {
998 dataset[py][px] = obj.options.data[j][i];
999 }
1000 px++;
1001 }
1002 }
1003 if (px > 0) {
1004 py++;
1005 }
1006 }
1007
1008 return dataset;
1009 }
1010
1011 /**
1012 * Get json data by row number
1013 *
1014 * @param integer row number
1015 * @return object
1016 */
1017 obj.getJsonRow = function(rowNumber) {
1018 var rowData = obj.options.data[rowNumber];
1019 var x = obj.options.columns.length
1020
1021 var row = {};
1022 for (var i = 0; i < x; i++) {
1023 if (! obj.options.columns[i].name) {
1024 obj.options.columns[i].name = i;
1025 }
1026 row[obj.options.columns[i].name] = rowData[i];
1027 }
1028
1029 return row;
1030 }
1031
1032 /**
1033 * Get the whole table data
1034 *
1035 * @param bool highlighted cells only
1036 * @return string value
1037 */
1038 obj.getJson = function(highlighted) {
1039 // Control vars
1040 var data = [];
1041
1042 // Column and row length
1043 var x = obj.options.columns.length
1044 var y = obj.options.data.length
1045
1046 // Go through the columns to get the data
1047 for (var j = 0; j < y; j++) {
1048 var row = null;
1049 for (var i = 0; i < x; i++) {
1050 if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
1051 if (row == null) {
1052 row = {};
1053 }
1054 if (! obj.options.columns[i].name) {
1055 obj.options.columns[i].name = i;
1056 }
1057 row[obj.options.columns[i].name] = obj.options.data[j][i];
1058 }
1059 }
1060
1061 if (row != null) {
1062 data.push(row);
1063 }
1064 }
1065
1066 return data;
1067 }
1068
1069 /**
1070 * Prepare JSON in the correct format
1071 */
1072 obj.prepareJson = function(data) {
1073 var rows = [];
1074 for (var i = 0; i < data.length; i++) {
1075 var x = data[i].x;
1076 var y = data[i].y;
1077 var k = obj.options.columns[x].name ? obj.options.columns[x].name : x;
1078
1079 // Create row
1080 if (! rows[y]) {
1081 rows[y] = {
1082 row: y,
1083 data: {},
1084 };
1085 }
1086 rows[y].data[k] = data[i].newValue;
1087 }
1088
1089 // Filter rows
1090 return rows.filter(function (el) {
1091 return el != null;
1092 });
1093 }
1094
1095 /**
1096 * Post json to a remote server
1097 */
1098 obj.save = function(url, data) {
1099 // Parse anything in the data before sending to the server
1100 var ret = obj.dispatch('onbeforesave', el, obj, data);
1101 if (ret) {
1102 var data = ret;
1103 } else {
1104 if (ret === false) {
1105 return false;
1106 }
1107 }
1108
1109 // Remove update
1110 jSuites.ajax({
1111 url: url,
1112 method: 'POST',
1113 dataType: 'json',
1114 data: { data: JSON.stringify(data) },
1115 success: function(result) {
1116 // Event
1117 obj.dispatch('onsave', el, obj, data);
1118 }
1119 });
1120 }
1121
1122 /**
1123 * Get a row data by rowNumber
1124 */
1125 obj.getRowData = function(rowNumber) {
1126 return obj.options.data[rowNumber];
1127 }
1128
1129 /**
1130 * Set a row data by rowNumber
1131 */
1132 obj.setRowData = function(rowNumber, data) {
1133 for (var i = 0; i < obj.headers.length; i++) {
1134 // Update cell
1135 var columnName = jexcel.getColumnNameFromId([ i, rowNumber ]);
1136 // Set value
1137 if (data[i] != null) {
1138 obj.setValue(columnName, data[i]);
1139 }
1140 }
1141 }
1142
1143 /**
1144 * Get a column data by columnNumber
1145 */
1146 obj.getColumnData = function(columnNumber) {
1147 var dataset = [];
1148 // Go through the rows to get the data
1149 for (var j = 0; j < obj.options.data.length; j++) {
1150 dataset.push(obj.options.data[j][columnNumber]);
1151 }
1152 return dataset;
1153 }
1154
1155 /**
1156 * Set a column data by colNumber
1157 */
1158 obj.setColumnData = function(colNumber, data) {
1159 for (var j = 0; j < obj.rows.length; j++) {
1160 // Update cell
1161 var columnName = jexcel.getColumnNameFromId([ colNumber, j ]);
1162 // Set value
1163 if (data[j] != null) {
1164 obj.setValue(columnName, data[j]);
1165 }
1166 }
1167 }
1168
1169 /**
1170 * Create row
1171 */
1172 obj.createRow = function(j, data) {
1173 // Create container
1174 if (! obj.records[j]) {
1175 obj.records[j] = [];
1176 }
1177 // Default data
1178 if (! data) {
1179 var data = obj.options.data[j];
1180 }
1181 // New line of data to be append in the table
1182 obj.rows[j] = document.createElement('tr');
1183 obj.rows[j].setAttribute('data-y', j);
1184 // Index
1185 var index = null;
1186
1187 // Set default row height
1188 if (obj.options.defaultRowHeight) {
1189 obj.rows[j].style.height = obj.options.defaultRowHeight + 'px'
1190 }
1191
1192 // Definitions
1193 if (obj.options.rows[j]) {
1194 if (obj.options.rows[j].height) {
1195 obj.rows[j].style.height = obj.options.rows[j].height;
1196 }
1197 if (obj.options.rows[j].title) {
1198 index = obj.options.rows[j].title;
1199 }
1200 }
1201 if (! index) {
1202 index = parseInt(j + 1);
1203 }
1204 // Row number label
1205 var td = document.createElement('td');
1206 td.innerHTML = index;
1207 td.setAttribute('data-y', j);
1208 td.className = 'jexcel_row';
1209 obj.rows[j].appendChild(td);
1210
1211 // Data columns
1212 for (var i = 0; i < obj.options.columns.length; i++) {
1213 // New column of data to be append in the line
1214 obj.records[j][i] = obj.createCell(i, j, data[i]);
1215 // Add column to the row
1216 obj.rows[j].appendChild(obj.records[j][i]);
1217 }
1218
1219 // Add row to the table body
1220 return obj.rows[j];
1221 }
1222
1223 obj.parseValue = function(i, j, value, cell) {
1224 if ((''+value).substr(0,1) == '=' && obj.options.parseFormulas == true) {
1225 value = obj.executeFormula(value, i, j)
1226 }
1227
1228 // Column options
1229 var options = obj.options.columns[i];
1230 if (options && ! isFormula(value)) {
1231 // Mask options
1232 var opt = null;
1233 if (opt = getMask(options)) {
1234 if (value && value == Number(value)) {
1235 value = Number(value);
1236 }
1237 // Process the decimals to match the mask
1238 var masked = jSuites.mask.render(value, opt, true);
1239 // Negative indication
1240 if (cell) {
1241 if (opt.mask) {
1242 var t = opt.mask.split(';');
1243 if (t[1]) {
1244 var t1 = t[1].match(new RegExp('\\[Red\\]', 'gi'));
1245 if (t1) {
1246 if (value < 0) {
1247 cell.classList.add('red');
1248 } else {
1249 cell.classList.remove('red');
1250 }
1251 }
1252 var t2 = t[1].match(new RegExp('\\(', 'gi'));
1253 if (t2) {
1254 if (value < 0) {
1255 masked = '(' + masked + ')';
1256 }
1257 }
1258 }
1259 }
1260 }
1261
1262 if (masked) {
1263 value = masked;
1264 }
1265 }
1266 }
1267
1268 return value;
1269 }
1270
1271 var validDate = function(date) {
1272 date = ''+date;
1273 if (date.substr(4,1) == '-' && date.substr(7,1) == '-') {
1274 return true;
1275 } else {
1276 date = date.split('-');
1277 if ((date[0].length == 4 && date[0] == Number(date[0]) && date[1].length == 2 && date[1] == Number(date[1]))) {
1278 return true;
1279 }
1280 }
1281 return false;
1282 }
1283
1284 /**
1285 * Create cell
1286 */
1287 obj.createCell = function(i, j, value) {
1288 // Create cell and properties
1289 var td = document.createElement('td');
1290 td.setAttribute('data-x', i);
1291 td.setAttribute('data-y', j);
1292
1293 // Security
1294 if ((''+value).substr(0,1) == '=' && obj.options.secureFormulas == true) {
1295 var val = secureFormula(value);
1296 if (val != value) {
1297 // Update the data container
1298 value = val;
1299 }
1300 }
1301
1302 // Custom column
1303 if (obj.options.columns[i].editor) {
1304 if (obj.options.stripHTML === false || obj.options.columns[i].stripHTML === false) {
1305 td.innerHTML = value;
1306 } else {
1307 td.textContent = value;
1308 }
1309 if (typeof(obj.options.columns[i].editor.createCell) == 'function') {
1310 td = obj.options.columns[i].editor.createCell(td);
1311 }
1312 } else {
1313 // Hidden column
1314 if (obj.options.columns[i].type == 'hidden') {
1315 td.style.display = 'none';
1316 td.textContent = value;
1317 } else if (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio') {
1318 // Create input
1319 var element = document.createElement('input');
1320 element.type = obj.options.columns[i].type;
1321 element.name = 'c' + i;
1322 element.checked = (value == 1 || value == true || value == 'true') ? true : false;
1323 element.onclick = function() {
1324 obj.setValue(td, this.checked);
1325 }
1326
1327 if (obj.options.columns[i].readOnly == true || obj.options.editable == false) {
1328 element.setAttribute('disabled', 'disabled');
1329 }
1330
1331 // Append to the table
1332 td.appendChild(element);
1333 // Make sure the values are correct
1334 obj.options.data[j][i] = element.checked;
1335 } else if (obj.options.columns[i].type == 'calendar') {
1336 // Try formatted date
1337 var formatted = null;
1338 if (! validDate(value)) {
1339 var tmp = jSuites.calendar.extractDateFromString(value, obj.options.columns[i].options.format);
1340 if (tmp) {
1341 formatted = tmp;
1342 }
1343 }
1344 // Create calendar cell
1345 td.textContent = jSuites.calendar.getDateString(formatted ? formatted : value, obj.options.columns[i].options.format);
1346 } else if (obj.options.columns[i].type == 'dropdown' || obj.options.columns[i].type == 'autocomplete') {
1347 // Create dropdown cell
1348 td.classList.add('jexcel_dropdown');
1349 td.textContent = obj.getDropDownValue(i, value);
1350 } else if (obj.options.columns[i].type == 'color') {
1351 if (obj.options.columns[i].render == 'square') {
1352 var color = document.createElement('div');
1353 color.className = 'color';
1354 color.style.backgroundColor = value;
1355 td.appendChild(color);
1356 } else {
1357 td.style.color = value;
1358 td.textContent = value;
1359 }
1360 } else if (obj.options.columns[i].type == 'image') {
1361 if (value && value.substr(0, 10) == 'data:image') {
1362 var img = document.createElement('img');
1363 img.src = value;
1364 td.appendChild(img);
1365 }
1366 } else {
1367 if (obj.options.columns[i].type == 'html') {
1368 td.innerHTML = stripScript(obj.parseValue(i, j, value, td));
1369 } else {
1370 if (obj.options.stripHTML === false || obj.options.columns[i].stripHTML === false) {
1371 td.innerHTML = stripScript(obj.parseValue(i, j, value, td));
1372 } else {
1373 // TablePress: Add a div container inside each cell.
1374 // td.textContent = obj.parseValue(i, j, value, td);
1375 var div = document.createElement('div');
1376 div.textContent = obj.parseValue(i, j, value, td);
1377 td.appendChild(div);
1378 }
1379 }
1380 }
1381 }
1382
1383 // Readonly
1384 if (obj.options.columns[i].readOnly == true) {
1385 td.className = 'readonly';
1386 }
1387
1388 // Text align
1389 var colAlign = obj.options.columns[i].align ? obj.options.columns[i].align : 'center';
1390 td.style.textAlign = colAlign;
1391
1392 // Wrap option
1393 if (obj.options.columns[i].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[i].wordWrap == true || td.innerHTML.length > 200)) {
1394 td.style.whiteSpace = 'pre-wrap';
1395 }
1396
1397 // Overflow
1398 if (i > 0) {
1399 if (this.options.textOverflow == true) {
1400 if (value || td.innerHTML) {
1401 obj.records[j][i-1].style.overflow = 'hidden';
1402 } else {
1403 if (i == obj.options.columns.length - 1) {
1404 td.style.overflow = 'hidden';
1405 }
1406 }
1407 }
1408 }
1409 return td;
1410 }
1411
1412 obj.createCellHeader = function(colNumber) {
1413 // Create col global control
1414 var colWidth = obj.options.columns[colNumber].width ? obj.options.columns[colNumber].width : obj.options.defaultColWidth;
1415 var colAlign = obj.options.columns[colNumber].align ? obj.options.columns[colNumber].align : obj.options.defaultColAlign;
1416
1417 // Create header cell
1418 obj.headers[colNumber] = document.createElement('td');
1419 if (obj.options.stripHTML) {
1420 obj.headers[colNumber].textContent = obj.options.columns[colNumber].title ? obj.options.columns[colNumber].title : jexcel.getColumnName(colNumber);
1421 } else {
1422 obj.headers[colNumber].innerHTML = obj.options.columns[colNumber].title ? obj.options.columns[colNumber].title : jexcel.getColumnName(colNumber);
1423 }
1424 obj.headers[colNumber].setAttribute('data-x', colNumber);
1425 obj.headers[colNumber].style.textAlign = colAlign;
1426 if (obj.options.columns[colNumber].title) {
1427 obj.headers[colNumber].setAttribute('title', obj.headers[colNumber].innerText);
1428 }
1429 if (obj.options.columns[colNumber].id) {
1430 obj.headers[colNumber].setAttribute('id', obj.options.columns[colNumber].id);
1431 }
1432
1433 // Width control
1434 obj.colgroup[colNumber] = document.createElement('col');
1435 obj.colgroup[colNumber].setAttribute('width', colWidth);
1436
1437 // Hidden column
1438 if (obj.options.columns[colNumber].type == 'hidden') {
1439 obj.headers[colNumber].style.display = 'none';
1440 obj.colgroup[colNumber].style.display = 'none';
1441 }
1442 }
1443
1444 /**
1445 * Update a nested header title
1446 */
1447 obj.updateNestedHeader = function(x, y, title) {
1448 if (obj.options.nestedHeaders[y][x].title) {
1449 obj.options.nestedHeaders[y][x].title = title;
1450 obj.options.nestedHeaders[y].element.children[x+1].textContent = title;
1451 }
1452 }
1453
1454 /**
1455 * Create a nested header object
1456 */
1457 obj.createNestedHeader = function(nestedInformation) {
1458 var tr = document.createElement('tr');
1459 tr.classList.add('jexcel_nested');
1460 var td = document.createElement('td');
1461 tr.appendChild(td);
1462 // Element
1463 nestedInformation.element = tr;
1464
1465 var headerIndex = 0;
1466 for (var i = 0; i < nestedInformation.length; i++) {
1467 // Default values
1468 if (! nestedInformation[i].colspan) {
1469 nestedInformation[i].colspan = 1;
1470 }
1471 if (! nestedInformation[i].align) {
1472 nestedInformation[i].align = 'center';
1473 }
1474 if (! nestedInformation[i].title) {
1475 nestedInformation[i].title = '';
1476 }
1477 if (! nestedInformation[i].id) {
1478 nestedInformation[i].id = '';
1479 }
1480
1481 // Number of columns
1482 var numberOfColumns = nestedInformation[i].colspan;
1483
1484 // Classes container
1485 var column = [];
1486 // Header classes for this cell
1487 for (var x = 0; x < numberOfColumns; x++) {
1488 if (obj.options.columns[headerIndex] && obj.options.columns[headerIndex].type == 'hidden') {
1489 numberOfColumns++;
1490 }
1491 column.push(headerIndex);
1492 headerIndex++;
1493 }
1494
1495 // Created the nested cell
1496 var td = document.createElement('td');
1497 td.setAttribute('data-column', column.join(','));
1498 td.setAttribute('colspan', nestedInformation[i].colspan);
1499 td.setAttribute('align', nestedInformation[i].align);
1500 td.setAttribute('id', nestedInformation[i].id);
1501 td.textContent = nestedInformation[i].title;
1502 tr.appendChild(td);
1503 }
1504
1505 return tr;
1506 }
1507
1508 /**
1509 * Create toolbar
1510 */
1511 obj.createToolbar = function(toolbar) {
1512 if (toolbar) {
1513 obj.options.toolbar = toolbar;
1514 } else {
1515 var toolbar = obj.options.toolbar;
1516 }
1517 for (var i = 0; i < toolbar.length; i++) {
1518 if (toolbar[i].type == 'i') {
1519 var toolbarItem = document.createElement('i');
1520 toolbarItem.classList.add('jexcel_toolbar_item');
1521 toolbarItem.classList.add('material-icons');
1522 toolbarItem.setAttribute('data-k', toolbar[i].k);
1523 toolbarItem.setAttribute('data-v', toolbar[i].v);
1524 toolbarItem.setAttribute('id', toolbar[i].id);
1525
1526 // Tooltip
1527 if (toolbar[i].tooltip) {
1528 toolbarItem.setAttribute('title', toolbar[i].tooltip);
1529 }
1530 // Handle click
1531 if (toolbar[i].onclick && typeof(toolbar[i].onclick)) {
1532 toolbarItem.onclick = (function (a) {
1533 var b = a;
1534 return function () {
1535 toolbar[b].onclick(el, obj, this);
1536 };
1537 })(i);
1538 } else {
1539 toolbarItem.onclick = function() {
1540 var k = this.getAttribute('data-k');
1541 var v = this.getAttribute('data-v');
1542 obj.setStyle(obj.highlighted, k, v);
1543 }
1544 }
1545 // Append element
1546 toolbarItem.textContent = toolbar[i].content;
1547 obj.toolbar.appendChild(toolbarItem);
1548 } else if (toolbar[i].type == 'select') {
1549 var toolbarItem = document.createElement('select');
1550 var raiseInitialOnChange = false;
1551 toolbarItem.classList.add('jexcel_toolbar_item');
1552 toolbarItem.setAttribute('data-k', toolbar[i].k);
1553 // Tooltip
1554 if (toolbar[i].tooltip) {
1555 toolbarItem.setAttribute('title', toolbar[i].tooltip);
1556 }
1557 // Handle onchange
1558 if (toolbar[i].onchange && typeof(toolbar[i].onchange)) {
1559 toolbarItem.onchange = toolbar[i].onchange;
1560 raiseInitialOnChange = true;
1561 } else {
1562 toolbarItem.onchange = function() {
1563 var k = this.getAttribute('data-k');
1564 obj.setStyle(obj.highlighted, k, this.value);
1565 }
1566 }
1567 // Add options to the dropdown
1568 for(var j = 0; j < toolbar[i].v.length; j++) {
1569 var toolbarDropdownOption = document.createElement('option');
1570 toolbarDropdownOption.value = toolbar[i].v[j];
1571 toolbarDropdownOption.textContent = toolbar[i].v[j];
1572 if (toolbar[i].selectedValue && toolbarDropdownOption.value === toolbar[i].selectedValue) {
1573 toolbarDropdownOption.selected = true;
1574 }
1575 toolbarItem.appendChild(toolbarDropdownOption);
1576 }
1577 if (raiseInitialOnChange) {
1578 toolbarItem.dispatchEvent(new Event('change'));
1579 }
1580 obj.toolbar.appendChild(toolbarItem);
1581 } else if (toolbar[i].type == 'color') {
1582 var toolbarItem = document.createElement('i');
1583 toolbarItem.classList.add('jexcel_toolbar_item');
1584 toolbarItem.classList.add('material-icons');
1585 toolbarItem.setAttribute('data-k', toolbar[i].k);
1586 toolbarItem.setAttribute('data-v', '');
1587 // Tooltip
1588 if (toolbar[i].tooltip) {
1589 toolbarItem.setAttribute('title', toolbar[i].tooltip);
1590 }
1591 obj.toolbar.appendChild(toolbarItem);
1592 toolbarItem.textContent = toolbar[i].content;
1593 jSuites.color(toolbarItem, {
1594 onchange:function(o, v) {
1595 var k = o.getAttribute('data-k');
1596 obj.setStyle(obj.highlighted, k, v);
1597 }
1598 });
1599 }
1600 }
1601 }
1602
1603 /**
1604 * Merge cells
1605 * @param cellName
1606 * @param colspan
1607 * @param rowspan
1608 * @param ignoreHistoryAndEvents
1609 */
1610 obj.setMerge = function(cellName, colspan, rowspan, ignoreHistoryAndEvents) {
1611 var test = false;
1612
1613 if (! cellName) {
1614 if (! obj.highlighted.length) {
1615 alert(obj.options.text.noCellsSelected);
1616 return null;
1617 } else {
1618 var x1 = parseInt(obj.highlighted[0].getAttribute('data-x'));
1619 var y1 = parseInt(obj.highlighted[0].getAttribute('data-y'));
1620 var x2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-x'));
1621 var y2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-y'));
1622 var cellName = jexcel.getColumnNameFromId([ x1, y1 ]);
1623 var colspan = (x2 - x1) + 1;
1624 var rowspan = (y2 - y1) + 1;
1625 }
1626 }
1627
1628 var cell = jexcel.getIdFromColumnName(cellName, true);
1629
1630 if (obj.options.mergeCells[cellName]) {
1631 if (obj.records[cell[1]][cell[0]].getAttribute('data-merged')) {
1632 test = obj.options.text.cellAlreadyMerged;
1633 }
1634 } else if ((! colspan || colspan < 2) && (! rowspan || rowspan < 2)) {
1635 test = obj.options.text.invalidMergeProperties;
1636 } else {
1637 var cells = [];
1638 for (var j = cell[1]; j < cell[1] + rowspan; j++) {
1639 for (var i = cell[0]; i < cell[0] + colspan; i++) {
1640 var columnName = jexcel.getColumnNameFromId([i, j]);
1641 if (obj.records[j][i].getAttribute('data-merged')) {
1642 test = obj.options.text.thereIsAConflictWithAnotherMergedCell;
1643 }
1644 }
1645 }
1646 }
1647
1648 if (test) {
1649 alert(test);
1650 } else {
1651 // Add property
1652 if (colspan > 1) {
1653 obj.records[cell[1]][cell[0]].setAttribute('colspan', colspan);
1654 } else {
1655 colspan = 1;
1656 }
1657 if (rowspan > 1) {
1658 obj.records[cell[1]][cell[0]].setAttribute('rowspan', rowspan);
1659 } else {
1660 rowspan = 1;
1661 }
1662 // Keep links to the existing nodes
1663 obj.options.mergeCells[cellName] = [ colspan, rowspan, [] ];
1664 // Mark cell as merged
1665 obj.records[cell[1]][cell[0]].setAttribute('data-merged', 'true');
1666 // Overflow
1667 obj.records[cell[1]][cell[0]].style.overflow = 'hidden';
1668 // History data
1669 var data = [];
1670 // Adjust the nodes
1671 for (var y = cell[1]; y < cell[1] + rowspan; y++) {
1672 for (var x = cell[0]; x < cell[0] + colspan; x++) {
1673 if (! (cell[0] == x && cell[1] == y)) {
1674 data.push(obj.options.data[y][x]);
1675 obj.updateCell(x, y, '', true);
1676 obj.options.mergeCells[cellName][2].push(obj.records[y][x]);
1677 obj.records[y][x].style.display = 'none';
1678 obj.records[y][x] = obj.records[cell[1]][cell[0]];
1679 }
1680 }
1681 }
1682 // In the initialization is not necessary keep the history
1683 obj.updateSelection(obj.records[cell[1]][cell[0]]);
1684
1685 if (! ignoreHistoryAndEvents) {
1686 obj.setHistory({
1687 action:'setMerge',
1688 column:cellName,
1689 colspan:colspan,
1690 rowspan:rowspan,
1691 data:data,
1692 });
1693
1694 obj.dispatch('onmerge', el, cellName, colspan, rowspan);
1695 }
1696 }
1697 }
1698
1699 /**
1700 * Merge cells
1701 * @param cellName
1702 * @param colspan
1703 * @param rowspan
1704 * @param ignoreHistoryAndEvents
1705 */
1706 obj.getMerge = function(cellName) {
1707 var data = {};
1708 if (cellName) {
1709 if (obj.options.mergeCells[cellName]) {
1710 data = [ obj.options.mergeCells[cellName][0], obj.options.mergeCells[cellName][1] ];
1711 } else {
1712 data = null;
1713 }
1714 } else {
1715 if (obj.options.mergeCells) {
1716 var mergedCells = obj.options.mergeCells;
1717 var keys = Object.keys(obj.options.mergeCells);
1718 for (var i = 0; i < keys.length; i++) {
1719 data[keys[i]] = [ obj.options.mergeCells[keys[i]][0], obj.options.mergeCells[keys[i]][1] ];
1720 }
1721 }
1722 }
1723
1724 return data;
1725 }
1726
1727 /**
1728 * Remove merge by cellname
1729 * @param cellName
1730 */
1731 obj.removeMerge = function(cellName, data, keepOptions) {
1732 if (obj.options.mergeCells[cellName]) {
1733 var cell = jexcel.getIdFromColumnName(cellName, true);
1734 obj.records[cell[1]][cell[0]].removeAttribute('colspan');
1735 obj.records[cell[1]][cell[0]].removeAttribute('rowspan');
1736 obj.records[cell[1]][cell[0]].removeAttribute('data-merged');
1737 var info = obj.options.mergeCells[cellName];
1738
1739 var index = 0;
1740 for (var j = 0; j < info[1]; j++) {
1741 for (var i = 0; i < info[0]; i++) {
1742 if (j > 0 || i > 0) {
1743 obj.records[cell[1]+j][cell[0]+i] = info[2][index];
1744 obj.records[cell[1]+j][cell[0]+i].style.display = '';
1745 // Recover data
1746 if (data && data[index]) {
1747 obj.updateCell(cell[0]+i, cell[1]+j, data[index]);
1748 }
1749 index++;
1750 }
1751 }
1752 }
1753
1754 // Update selection
1755 obj.updateSelection(obj.records[cell[1]][cell[0]], obj.records[cell[1]+j-1][cell[0]+i-1]);
1756
1757 if (! keepOptions) {
1758 delete(obj.options.mergeCells[cellName]);
1759 }
1760 }
1761 }
1762
1763 /**
1764 * Remove all merged cells
1765 */
1766 obj.destroyMerged = function(keepOptions) {
1767 // Remove any merged cells
1768 if (obj.options.mergeCells) {
1769 var mergedCells = obj.options.mergeCells;
1770 var keys = Object.keys(obj.options.mergeCells);
1771 for (var i = 0; i < keys.length; i++) {
1772 obj.removeMerge(keys[i], null, keepOptions);
1773 }
1774 }
1775 }
1776
1777 /**
1778 * Is column merged
1779 */
1780 obj.isColMerged = function(x, insertBefore) {
1781 var cols = [];
1782 // Remove any merged cells
1783 if (obj.options.mergeCells) {
1784 var keys = Object.keys(obj.options.mergeCells);
1785 for (var i = 0; i < keys.length; i++) {
1786 var info = jexcel.getIdFromColumnName(keys[i], true);
1787 var colspan = obj.options.mergeCells[keys[i]][0];
1788 var x1 = info[0];
1789 var x2 = info[0] + (colspan > 1 ? colspan - 1 : 0);
1790
1791 if (insertBefore == null) {
1792 if ((x1 <= x && x2 >= x)) {
1793 cols.push(keys[i]);
1794 }
1795 } else {
1796 if (insertBefore) {
1797 if ((x1 < x && x2 >= x)) {
1798 cols.push(keys[i]);
1799 }
1800 } else {
1801 if ((x1 <= x && x2 > x)) {
1802 cols.push(keys[i]);
1803 }
1804 }
1805 }
1806 }
1807 }
1808
1809 return cols;
1810 }
1811
1812 /**
1813 * Is rows merged
1814 */
1815 obj.isRowMerged = function(y, insertBefore) {
1816 var rows = [];
1817 // Remove any merged cells
1818 if (obj.options.mergeCells) {
1819 var keys = Object.keys(obj.options.mergeCells);
1820 for (var i = 0; i < keys.length; i++) {
1821 var info = jexcel.getIdFromColumnName(keys[i], true);
1822 var rowspan = obj.options.mergeCells[keys[i]][1];
1823 var y1 = info[1];
1824 var y2 = info[1] + (rowspan > 1 ? rowspan - 1 : 0);
1825
1826 if (insertBefore == null) {
1827 if ((y1 <= y && y2 >= y)) {
1828 rows.push(keys[i]);
1829 }
1830 } else {
1831 if (insertBefore) {
1832 if ((y1 < y && y2 >= y)) {
1833 rows.push(keys[i]);
1834 }
1835 } else {
1836 if ((y1 <= y && y2 > y)) {
1837 rows.push(keys[i]);
1838 }
1839 }
1840 }
1841 }
1842 }
1843
1844 return rows;
1845 }
1846
1847 /**
1848 * Open the column filter
1849 */
1850 obj.openFilter = function(columnId) {
1851 if (! obj.options.filters) {
1852 console.log('Jspreadsheet: filters not enabled.');
1853 } else {
1854 // Make sure is integer
1855 columnId = parseInt(columnId);
1856 // Reset selection
1857 obj.resetSelection();
1858 // Load options
1859 var optionsFiltered = [];
1860 if (obj.options.columns[columnId].type == 'checkbox') {
1861 optionsFiltered.push({ id: 'true', name: 'True' });
1862 optionsFiltered.push({ id: 'false', name: 'False' });
1863 } else {
1864 var options = [];
1865 var hasBlanks = false;
1866 for (var j = 0; j < obj.options.data.length; j++) {
1867 var k = obj.options.data[j][columnId];
1868 var v = obj.records[j][columnId].innerHTML;
1869 if (k && v) {
1870 options[k] = v;
1871 } else {
1872 var hasBlanks = true;
1873 }
1874 }
1875 var keys = Object.keys(options);
1876 var optionsFiltered = [];
1877 for (var j = 0; j < keys.length; j++) {
1878 optionsFiltered.push({ id: keys[j], name: options[keys[j]] });
1879 }
1880 // Has blank options
1881 if (hasBlanks) {
1882 optionsFiltered.push({ value: '', id: '', name: '(Blanks)' });
1883 }
1884 }
1885
1886 // Create dropdown
1887 var div = document.createElement('div');
1888 obj.filter.children[columnId + 1].innerHTML = '';
1889 obj.filter.children[columnId + 1].appendChild(div);
1890 obj.filter.children[columnId + 1].style.paddingLeft = '0px';
1891 obj.filter.children[columnId + 1].style.paddingRight = '0px';
1892 obj.filter.children[columnId + 1].style.overflow = 'initial';
1893
1894 var opt = {
1895 data: optionsFiltered,
1896 multiple: true,
1897 autocomplete: true,
1898 opened: true,
1899 value: obj.filters[columnId] !== undefined ? obj.filters[columnId] : null,
1900 width:'100%',
1901 position: (obj.options.tableOverflow == true || obj.options.fullscreen == true) ? true : false,
1902 onclose: function(o) {
1903 obj.resetFilters();
1904 obj.filters[columnId] = o.dropdown.getValue(true);
1905 obj.filter.children[columnId + 1].innerHTML = o.dropdown.getText();
1906 obj.filter.children[columnId + 1].style.paddingLeft = '';
1907 obj.filter.children[columnId + 1].style.paddingRight = '';
1908 obj.filter.children[columnId + 1].style.overflow = '';
1909 obj.closeFilter(columnId);
1910 obj.refreshSelection();
1911 }
1912 };
1913
1914 // Dynamic dropdown
1915 jSuites.dropdown(div, opt);
1916 }
1917 }
1918
1919 obj.resetFilters = function() {
1920 if (obj.options.filters) {
1921 for (var i = 0; i < obj.filter.children.length; i++) {
1922 obj.filter.children[i].innerHTML = '&nbsp;';
1923 obj.filters[i] = null;
1924 }
1925 }
1926
1927 obj.results = null;
1928 obj.updateResult();
1929 }
1930
1931 obj.closeFilter = function(columnId) {
1932 if (! columnId) {
1933 for (var i = 0; i < obj.filter.children.length; i++) {
1934 if (obj.filters[i]) {
1935 columnId = i;
1936 }
1937 }
1938 }
1939
1940 // Search filter
1941 var search = function(query, x, y) {
1942 for (var i = 0; i < query.length; i++) {
1943 var value = ''+obj.options.data[y][x];
1944 var label = ''+obj.records[y][x].innerHTML;
1945 if (query[i] == value || query[i] == label) {
1946 return true;
1947 }
1948 }
1949 return false;
1950 }
1951
1952 var query = obj.filters[columnId];
1953 obj.results = [];
1954 for (var j = 0; j < obj.options.data.length; j++) {
1955 if (search(query, columnId, j)) {
1956 obj.results.push(j);
1957 }
1958 }
1959 if (! obj.results.length) {
1960 obj.results = null;
1961 }
1962
1963 obj.updateResult();
1964 }
1965
1966 /**
1967 * Open the editor
1968 *
1969 * @param object cell
1970 * @return void
1971 */
1972 obj.openEditor = function(cell, empty, e) {
1973 // Get cell position
1974 var y = cell.getAttribute('data-y');
1975 var x = cell.getAttribute('data-x');
1976
1977 // On edition start
1978 obj.dispatch('oneditionstart', el, cell, x, y);
1979
1980 // Overflow
1981 if (x > 0) {
1982 obj.records[y][x-1].style.overflow = 'hidden';
1983 }
1984
1985 // Create editor
1986 var createEditor = function(type) {
1987 // Cell information
1988 var info = cell.getBoundingClientRect();
1989
1990 // Create dropdown
1991 var editor = document.createElement(type);
1992 /* TablePress: The values in the differences were adjusted to make the textarea fit. */
1993 editor.style.width = (info.width - 6) + 'px';
1994 editor.style.height = (info.height - 6) + 'px';
1995 editor.style.minHeight = (info.height - 6) + 'px';
1996
1997 // Edit cell
1998 cell.classList.add('editor');
1999 cell.innerHTML = '';
2000 cell.appendChild(editor);
2001
2002 // On edition start
2003 obj.dispatch('oncreateeditor', el, cell, x, y, editor);
2004
2005 return editor;
2006 }
2007
2008 // Readonly
2009 if (cell.classList.contains('readonly') == true) {
2010 // Do nothing
2011 } else {
2012 // Holder
2013 obj.edition = [ obj.records[y][x], obj.records[y][x].innerHTML, x, y ];
2014
2015 // If there is a custom editor for it
2016 if (obj.options.columns[x].editor) {
2017 // Custom editors
2018 obj.options.columns[x].editor.openEditor(cell, el, empty, e);
2019 } else {
2020 // Native functions
2021 if (obj.options.columns[x].type == 'hidden') {
2022 // Do nothing
2023 } else if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
2024 // Get value
2025 var value = cell.children[0].checked ? false : true;
2026 // Toogle value
2027 obj.setValue(cell, value);
2028 // Do not keep edition open
2029 obj.edition = null;
2030 } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
2031 // Get current value
2032 var value = obj.options.data[y][x];
2033 if (obj.options.columns[x].multiple && !Array.isArray(value)) {
2034 value = value.split(';');
2035 }
2036
2037 // Create dropdown
2038 if (typeof(obj.options.columns[x].filter) == 'function') {
2039 var source = obj.options.columns[x].filter(el, cell, x, y, obj.options.columns[x].source);
2040 } else {
2041 var source = obj.options.columns[x].source;
2042 }
2043
2044 // Do not change the original source
2045 var data = [];
2046 for (var j = 0; j < source.length; j++) {
2047 data.push(source[j]);
2048 }
2049
2050 // Create editor
2051 var editor = createEditor('div');
2052 var options = {
2053 data: data,
2054 multiple: obj.options.columns[x].multiple ? true : false,
2055 autocomplete: obj.options.columns[x].autocomplete || obj.options.columns[x].type == 'autocomplete' ? true : false,
2056 opened:true,
2057 value: value,
2058 width:'100%',
2059 height:editor.style.minHeight,
2060 position: (obj.options.tableOverflow == true || obj.options.fullscreen == true) ? true : false,
2061 onclose:function() {
2062 obj.closeEditor(cell, true);
2063 }
2064 };
2065 if (obj.options.columns[x].options && obj.options.columns[x].options.type) {
2066 options.type = obj.options.columns[x].options.type;
2067 }
2068 jSuites.dropdown(editor, options);
2069 } else if (obj.options.columns[x].type == 'calendar' || obj.options.columns[x].type == 'color') {
2070 // Value
2071 var value = obj.options.data[y][x];
2072 // Create editor
2073 var editor = createEditor('input');
2074 editor.value = value;
2075
2076 if (obj.options.tableOverflow == true || obj.options.fullscreen == true) {
2077 obj.options.columns[x].options.position = true;
2078 }
2079 obj.options.columns[x].options.value = obj.options.data[y][x];
2080 obj.options.columns[x].options.opened = true;
2081 obj.options.columns[x].options.onclose = function(el, value) {
2082 obj.closeEditor(cell, true);
2083 }
2084 // Current value
2085 if (obj.options.columns[x].type == 'color') {
2086 jSuites.color(editor, obj.options.columns[x].options);
2087 } else {
2088 jSuites.calendar(editor, obj.options.columns[x].options);
2089 }
2090 // Focus on editor
2091 editor.focus();
2092 } else if (obj.options.columns[x].type == 'html') {
2093 var value = obj.options.data[y][x];
2094 // Create editor
2095 var editor = createEditor('div');
2096 editor.style.position = 'relative';
2097 var div = document.createElement('div');
2098 div.classList.add('jexcel_richtext');
2099 editor.appendChild(div);
2100 jSuites.editor(div, {
2101 focus: true,
2102 value: value,
2103 });
2104 var rect = cell.getBoundingClientRect();
2105 var rectContent = div.getBoundingClientRect();
2106 if (window.innerHeight < rect.bottom + rectContent.height) {
2107 div.style.top = (rect.top - (rectContent.height + 2)) + 'px';
2108 } else {
2109 div.style.top = (rect.top) + 'px';
2110 }
2111 } else if (obj.options.columns[x].type == 'image') {
2112 // Value
2113 var img = cell.children[0];
2114 // Create editor
2115 var editor = createEditor('div');
2116 editor.style.position = 'relative';
2117 var div = document.createElement('div');
2118 div.classList.add('jclose');
2119 if (img && img.src) {
2120 div.appendChild(img);
2121 }
2122 editor.appendChild(div);
2123 jSuites.image(div, obj.options.imageOptions);
2124 var rect = cell.getBoundingClientRect();
2125 var rectContent = div.getBoundingClientRect();
2126 if (window.innerHeight < rect.bottom + rectContent.height) {
2127 div.style.top = (rect.top - (rectContent.height + 2)) + 'px';
2128 } else {
2129 div.style.top = (rect.top) + 'px';
2130 }
2131 } else {
2132 // TablePress: Show all lines of text in an edited cell, and show at least that number of lines in other cells of the row.
2133 cell.classList.add('editing');
2134 var cell_height = cell.getBoundingClientRect().height;
2135 obj.rows[y].style.setProperty( '--table-editor-line-clamp-editing', Math.floor( (cell_height-10) / 14 ) );
2136 obj.updateCornerPosition();
2137
2138 // Value
2139 var value = empty == true ? '' : obj.options.data[y][x];
2140
2141 // Basic editor
2142 if (obj.options.columns[x].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[x].wordWrap == true)) {
2143 var editor = createEditor('textarea');
2144 } else {
2145 var editor = createEditor('input');
2146 }
2147
2148 editor.focus();
2149 editor.value = value;
2150
2151 // Column options
2152 var options = obj.options.columns[x];
2153 // Format
2154 var opt = null;
2155
2156 // Apply format when is not a formula
2157 if (! isFormula(value)) {
2158 // Format
2159 if (opt = getMask(options)) {
2160 // Masking
2161 if (! options.disabledMaskOnEdition) {
2162 if (options.mask) {
2163 var m = options.mask.split(';')
2164 editor.setAttribute('data-mask', m[0]);
2165 } else if (options.locale) {
2166 editor.setAttribute('data-locale', options.locale);
2167 }
2168 }
2169 // Input
2170 opt.input = editor;
2171 // Configuration
2172 editor.mask = opt;
2173 // Do not treat the decimals
2174 jSuites.mask.render(value, opt, false);
2175 }
2176 }
2177
2178 editor.onblur = function() {
2179 obj.closeEditor(cell, true);
2180 };
2181 editor.scrollLeft = editor.scrollWidth;
2182 }
2183 }
2184 }
2185 }
2186
2187 /**
2188 * Close the editor and save the information
2189 *
2190 * @param object cell
2191 * @param boolean save
2192 * @return void
2193 */
2194 obj.closeEditor = function(cell, save) {
2195 var x = parseInt(cell.getAttribute('data-x'));
2196 var y = parseInt(cell.getAttribute('data-y'));
2197
2198 // TablePress: Reset cell/row heights to previous values.
2199 cell.classList.remove('editing');
2200 obj.rows[y].style.removeProperty( '--table-editor-line-clamp-editing' );
2201 obj.updateCornerPosition();
2202
2203 // Get cell properties
2204 if (save == true) {
2205 // If custom editor
2206 if (obj.options.columns[x].editor) {
2207 // Custom editor
2208 var value = obj.options.columns[x].editor.closeEditor(cell, save);
2209 } else {
2210 // Native functions
2211 if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio' || obj.options.columns[x].type == 'hidden') {
2212 // Do nothing
2213 } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
2214 var value = cell.children[0].dropdown.close(true);
2215 } else if (obj.options.columns[x].type == 'calendar') {
2216 var value = cell.children[0].calendar.close(true);
2217 } else if (obj.options.columns[x].type == 'color') {
2218 var value = cell.children[0].color.close(true);
2219 } else if (obj.options.columns[x].type == 'html') {
2220 var value = cell.children[0].children[0].editor.getData();
2221 } else if (obj.options.columns[x].type == 'image') {
2222 var img = cell.children[0].children[0].children[0];
2223 var value = img && img.tagName == 'IMG' ? img.src : '';
2224 } else if (obj.options.columns[x].type == 'numeric') {
2225 var value = cell.children[0].value;
2226 if ((''+value).substr(0,1) != '=') {
2227 if (value == '') {
2228 value = obj.options.columns[x].allowEmpty ? '' : 0;
2229 }
2230 }
2231 cell.children[0].onblur = null;
2232 } else {
2233 var value = cell.children[0].value;
2234 cell.children[0].onblur = null;
2235
2236 // Column options
2237 var options = obj.options.columns[x];
2238 // Format
2239 var opt = null;
2240 if (opt = getMask(options)) {
2241 // Keep numeric in the raw data
2242 if (value !== '' && ! isFormula(value) && typeof(value) !== 'number') {
2243 var t = jSuites.mask.extract(value, opt, true);
2244 if (t && t.value !== '') {
2245 value = t.value;
2246 }
2247 }
2248 }
2249 }
2250 }
2251
2252 // Ignore changes if the value is the same
2253 if (obj.options.data[y][x] == value) {
2254 cell.innerHTML = obj.edition[1];
2255 } else {
2256 obj.setValue(cell, value);
2257 }
2258 } else {
2259 if (obj.options.columns[x].editor) {
2260 // Custom editor
2261 obj.options.columns[x].editor.closeEditor(cell, save);
2262 } else {
2263 if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
2264 cell.children[0].dropdown.close(true);
2265 } else if (obj.options.columns[x].type == 'calendar') {
2266 cell.children[0].calendar.close(true);
2267 } else if (obj.options.columns[x].type == 'color') {
2268 cell.children[0].color.close(true);
2269 } else {
2270 cell.children[0].onblur = null;
2271 }
2272 }
2273
2274 // Restore value
2275 cell.innerHTML = obj.edition && obj.edition[1] ? obj.edition[1] : '';
2276 }
2277
2278 // On edition end
2279 obj.dispatch('oneditionend', el, cell, x, y, value, save);
2280
2281 // Remove editor class
2282 cell.classList.remove('editor');
2283
2284 // Finish edition
2285 obj.edition = null;
2286 }
2287
2288 /**
2289 * Get the cell object
2290 *
2291 * @param object cell
2292 * @return string value
2293 */
2294 obj.getCell = function(cell) {
2295 // Convert in case name is excel liked ex. A10, BB92
2296 cell = jexcel.getIdFromColumnName(cell, true);
2297 var x = cell[0];
2298 var y = cell[1];
2299
2300 return obj.records[y][x];
2301 }
2302
2303 /**
2304 * Get the column options
2305 * @param x
2306 * @param y
2307 * @returns {{type: string}}
2308 */
2309 obj.getColumnOptions = function(x, y) {
2310 // Type
2311 var options = obj.options.columns[x];
2312
2313 // Cell type
2314 if (! options) {
2315 options = { type: 'text' };
2316 }
2317
2318 return options;
2319 }
2320
2321 /**
2322 * Get the cell object from coords
2323 *
2324 * @param object cell
2325 * @return string value
2326 */
2327 obj.getCellFromCoords = function(x, y) {
2328 return obj.records[y][x];
2329 }
2330
2331 /**
2332 * Get label
2333 *
2334 * @param object cell
2335 * @return string value
2336 */
2337 obj.getLabel = function(cell) {
2338 // Convert in case name is excel liked ex. A10, BB92
2339 cell = jexcel.getIdFromColumnName(cell, true);
2340 var x = cell[0];
2341 var y = cell[1];
2342
2343 return obj.records[y][x].innerHTML;
2344 }
2345
2346 /**
2347 * Get labelfrom coords
2348 *
2349 * @param object cell
2350 * @return string value
2351 */
2352 obj.getLabelFromCoords = function(x, y) {
2353 return obj.records[y][x].innerHTML;
2354 }
2355
2356 /**
2357 * Get the value from a cell
2358 *
2359 * @param object cell
2360 * @return string value
2361 */
2362 obj.getValue = function(cell, processedValue) {
2363 if (typeof(cell) == 'object') {
2364 var x = cell.getAttribute('data-x');
2365 var y = cell.getAttribute('data-y');
2366 } else {
2367 cell = jexcel.getIdFromColumnName(cell, true);
2368 var x = cell[0];
2369 var y = cell[1];
2370 }
2371
2372 var value = null;
2373
2374 if (x != null && y != null) {
2375 if (obj.records[y] && obj.records[y][x] && (processedValue || obj.options.copyCompatibility == true)) {
2376 value = obj.records[y][x].innerHTML;
2377 } else {
2378 if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') {
2379 value = obj.options.data[y][x];
2380 }
2381 }
2382 }
2383
2384 return value;
2385 }
2386
2387 /**
2388 * Get the value from a coords
2389 *
2390 * @param int x
2391 * @param int y
2392 * @return string value
2393 */
2394 obj.getValueFromCoords = function(x, y, processedValue) {
2395 var value = null;
2396
2397 if (x != null && y != null) {
2398 if ((obj.records[y] && obj.records[y][x]) && processedValue || obj.options.copyCompatibility == true) {
2399 value = obj.records[y][x].innerHTML;
2400 } else {
2401 if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') {
2402 value = obj.options.data[y][x];
2403 }
2404 }
2405 }
2406
2407 return value;
2408 }
2409
2410 /**
2411 * Set a cell value
2412 *
2413 * @param mixed cell destination cell
2414 * @param string value value
2415 * @return void
2416 */
2417 obj.setValue = function(cell, value, force) {
2418 var records = [];
2419
2420 if (typeof(cell) == 'string') {
2421 var columnId = jexcel.getIdFromColumnName(cell, true);
2422 var x = columnId[0];
2423 var y = columnId[1];
2424
2425 // Update cell
2426 records.push(obj.updateCell(x, y, value, force));
2427
2428 // Update all formulas in the chain
2429 obj.updateFormulaChain(x, y, records);
2430 } else {
2431 var x = null;
2432 var y = null;
2433 if (cell && cell.getAttribute) {
2434 var x = cell.getAttribute('data-x');
2435 var y = cell.getAttribute('data-y');
2436 }
2437
2438 // Update cell
2439 if (x != null && y != null) {
2440 records.push(obj.updateCell(x, y, value, force));
2441
2442 // Update all formulas in the chain
2443 obj.updateFormulaChain(x, y, records);
2444 } else {
2445 var keys = Object.keys(cell);
2446 if (keys.length > 0) {
2447 for (var i = 0; i < keys.length; i++) {
2448 if (typeof(cell[i]) == 'string') {
2449 var columnId = jexcel.getIdFromColumnName(cell[i], true);
2450 var x = columnId[0];
2451 var y = columnId[1];
2452 } else {
2453 if (cell[i].x != null && cell[i].y != null) {
2454 var x = cell[i].x;
2455 var y = cell[i].y;
2456 // Flexible setup
2457 if (cell[i].newValue != null) {
2458 value = cell[i].newValue;
2459 } else if (cell[i].value != null) {
2460 value = cell[i].value;
2461 }
2462 } else {
2463 var x = cell[i].getAttribute('data-x');
2464 var y = cell[i].getAttribute('data-y');
2465 }
2466 }
2467
2468 // Update cell
2469 if (x != null && y != null) {
2470 records.push(obj.updateCell(x, y, value, force));
2471
2472 // Update all formulas in the chain
2473 obj.updateFormulaChain(x, y, records);
2474 }
2475 }
2476 }
2477 }
2478 }
2479
2480 // Update history
2481 obj.setHistory({
2482 action:'setValue',
2483 records:records,
2484 selection:obj.selectedCell,
2485 });
2486
2487 // Update table with custom configurations if applicable
2488 obj.updateTable();
2489
2490 // On after changes
2491 obj.onafterchanges(el, records);
2492 }
2493
2494 /**
2495 * Set a cell value based on coordinates
2496 *
2497 * @param int x destination cell
2498 * @param int y destination cell
2499 * @param string value
2500 * @return void
2501 */
2502 obj.setValueFromCoords = function(x, y, value, force) {
2503 var records = [];
2504 records.push(obj.updateCell(x, y, value, force));
2505
2506 // Update all formulas in the chain
2507 obj.updateFormulaChain(x, y, records);
2508
2509 // Update history
2510 obj.setHistory({
2511 action:'setValue',
2512 records:records,
2513 selection:obj.selectedCell,
2514 });
2515
2516 // Update table with custom configurations if applicable
2517 obj.updateTable();
2518
2519 // On after changes
2520 obj.onafterchanges(el, records);
2521 }
2522
2523 /**
2524 * Toogle
2525 */
2526 obj.setCheckRadioValue = function() {
2527 var records = [];
2528 var keys = Object.keys(obj.highlighted);
2529 for (var i = 0; i < keys.length; i++) {
2530 var x = obj.highlighted[i].getAttribute('data-x');
2531 var y = obj.highlighted[i].getAttribute('data-y');
2532
2533 if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
2534 // Update cell
2535 records.push(obj.updateCell(x, y, ! obj.options.data[y][x]));
2536 }
2537 }
2538
2539 if (records.length) {
2540 // Update history
2541 obj.setHistory({
2542 action:'setValue',
2543 records:records,
2544 selection:obj.selectedCell,
2545 });
2546
2547 // On after changes
2548 obj.onafterchanges(el, records);
2549 }
2550 }
2551 /**
2552 * Strip tags
2553 */
2554 var stripScript = function(a) {
2555 var b = new Option;
2556 b.innerHTML = a;
2557 var c = null;
2558 for (a = b.getElementsByTagName('script'); c=a[0];) c.parentNode.removeChild(c);
2559 return b.innerHTML;
2560 }
2561
2562 /**
2563 * Update cell content
2564 *
2565 * @param object cell
2566 * @return void
2567 */
2568 obj.updateCell = function(x, y, value, force) {
2569 // Changing value depending on the column type
2570 if (obj.records[y][x].classList.contains('readonly') == true && ! force) {
2571 // Do nothing
2572 var record = {
2573 x: x,
2574 y: y,
2575 col: x,
2576 row: y
2577 }
2578 } else {
2579 // Security
2580 if ((''+value).substr(0,1) == '=' && obj.options.secureFormulas == true) {
2581 var val = secureFormula(value);
2582 if (val != value) {
2583 // Update the data container
2584 value = val;
2585 }
2586 }
2587
2588 // On change
2589 var val = obj.dispatch('onbeforechange', el, obj.records[y][x], x, y, value);
2590
2591 // If you return something this will overwrite the value
2592 if (val != undefined) {
2593 value = val;
2594 }
2595
2596 if (obj.options.columns[x].editor && typeof(obj.options.columns[x].editor.updateCell) == 'function') {
2597 value = obj.options.columns[x].editor.updateCell(obj.records[y][x], value, force);
2598 }
2599
2600 // History format
2601 var record = {
2602 x: x,
2603 y: y,
2604 col: x,
2605 row: y,
2606 newValue: value,
2607 oldValue: obj.options.data[y][x],
2608 }
2609
2610 let editor = obj.options.columns[x].editor;
2611 if (editor) {
2612 // Update data and cell
2613 obj.options.data[y][x] = value;
2614 if (typeof(editor.setValue) === 'function') {
2615 editor.setValue(obj.records[y][x], value);
2616 }
2617 } else {
2618 // Native functions
2619 if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') {
2620 // Unchecked all options
2621 if (obj.options.columns[x].type == 'radio') {
2622 for (var j = 0; j < obj.options.data.length; j++) {
2623 obj.options.data[j][x] = false;
2624 }
2625 }
2626
2627 // Update data and cell
2628 obj.records[y][x].children[0].checked = (value == 1 || value == true || value == 'true' || value == 'TRUE') ? true : false;
2629 obj.options.data[y][x] = obj.records[y][x].children[0].checked;
2630 } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') {
2631 // Update data and cell
2632 obj.options.data[y][x] = value;
2633 obj.records[y][x].textContent = obj.getDropDownValue(x, value);
2634 } else if (obj.options.columns[x].type == 'calendar') {
2635 // Try formatted date
2636 var formatted = null;
2637 if (! validDate(value)) {
2638 var tmp = jSuites.calendar.extractDateFromString(value, obj.options.columns[x].options.format);
2639 if (tmp) {
2640 formatted = tmp;
2641 }
2642 }
2643 // Update data and cell
2644 obj.options.data[y][x] = value;
2645 obj.records[y][x].textContent = jSuites.calendar.getDateString(formatted ? formatted : value, obj.options.columns[x].options.format);
2646 } else if (obj.options.columns[x].type == 'color') {
2647 // Update color
2648 obj.options.data[y][x] = value;
2649 // Render
2650 if (obj.options.columns[x].render == 'square') {
2651 var color = document.createElement('div');
2652 color.className = 'color';
2653 color.style.backgroundColor = value;
2654 obj.records[y][x].textContent = '';
2655 obj.records[y][x].appendChild(color);
2656 } else {
2657 obj.records[y][x].style.color = value;
2658 obj.records[y][x].textContent = value;
2659 }
2660 } else if (obj.options.columns[x].type == 'image') {
2661 value = ''+value;
2662 obj.options.data[y][x] = value;
2663 obj.records[y][x].innerHTML = '';
2664 if (value && value.substr(0, 10) == 'data:image') {
2665 var img = document.createElement('img');
2666 img.src = value;
2667 obj.records[y][x].appendChild(img);
2668 }
2669 } else {
2670 // Update data and cell
2671 obj.options.data[y][x] = value;
2672 // Label
2673 if (obj.options.columns[x].type == 'html') {
2674 obj.records[y][x].innerHTML = stripScript(obj.parseValue(x, y, value));
2675 } else {
2676 if (obj.options.stripHTML === false || obj.options.columns[x].stripHTML === false) {
2677 obj.records[y][x].innerHTML = stripScript(obj.parseValue(x, y, value, obj.records[y][x]));
2678 } else {
2679 // TablePress: Add a div container inside each cell.
2680 // obj.records[y][x].textContent = obj.parseValue(x, y, value, obj.records[y][x]);
2681 var div = document.createElement('div');
2682 div.textContent = obj.parseValue(x, y, value, obj.records[y][x]);
2683 obj.records[y][x].textContent = '';
2684 obj.records[y][x].appendChild(div);
2685 }
2686 }
2687 // Handle big text inside a cell
2688 if (obj.options.columns[x].wordWrap != false && (obj.options.wordWrap == true || obj.options.columns[x].wordWrap == true || obj.records[y][x].innerHTML.length > 200)) {
2689 obj.records[y][x].style.whiteSpace = 'pre-wrap';
2690 } else {
2691 obj.records[y][x].style.whiteSpace = '';
2692 }
2693 }
2694 }
2695
2696 // Overflow
2697 if (x > 0) {
2698 if (value) {
2699 obj.records[y][x-1].style.overflow = 'hidden';
2700 } else {
2701 obj.records[y][x-1].style.overflow = '';
2702 }
2703 }
2704
2705 // On change
2706 obj.dispatch('onchange', el, (obj.records[y] && obj.records[y][x] ? obj.records[y][x] : null), x, y, value, record.oldValue);
2707 }
2708
2709 return record;
2710 }
2711
2712 /**
2713 * Helper function to copy data using the corner icon
2714 */
2715 obj.copyData = function(o, d) {
2716 // Get data from all selected cells
2717 var data = obj.getData(true, true);
2718
2719 // Selected cells
2720 var h = obj.selectedContainer;
2721
2722 // Cells
2723 var x1 = parseInt(o.getAttribute('data-x'));
2724 var y1 = parseInt(o.getAttribute('data-y'));
2725 var x2 = parseInt(d.getAttribute('data-x'));
2726 var y2 = parseInt(d.getAttribute('data-y'));
2727
2728 // Records
2729 var records = [];
2730 var breakControl = false;
2731
2732 if (h[0] == x1) {
2733 // Vertical copy
2734 if (y1 < h[1]) {
2735 var rowNumber = y1 - h[1];
2736 } else {
2737 var rowNumber = 1;
2738 }
2739 var colNumber = 0;
2740 } else {
2741 if (x1 < h[0]) {
2742 var colNumber = x1 - h[0];
2743 } else {
2744 var colNumber = 1;
2745 }
2746 var rowNumber = 0;
2747 }
2748
2749 // Copy data procedure
2750 var posx = 0;
2751 var posy = 0;
2752
2753 for (var j = y1; j <= y2; j++) {
2754 // Skip hidden rows
2755 if (obj.rows[j] && obj.rows[j].style.display == 'none') {
2756 continue;
2757 }
2758
2759 // Controls
2760 if (data[posy] == undefined) {
2761 posy = 0;
2762 }
2763 posx = 0;
2764
2765 // Data columns
2766 if (h[0] != x1) {
2767 if (x1 < h[0]) {
2768 var colNumber = x1 - h[0];
2769 } else {
2770 var colNumber = 1;
2771 }
2772 }
2773 // Data columns
2774 for (var i = x1; i <= x2; i++) {
2775 // Update non-readonly
2776 if (obj.records[j][i] && ! obj.records[j][i].classList.contains('readonly') && obj.records[j][i].style.display != 'none' && breakControl == false) {
2777 // Stop if contains value
2778 if (! obj.selection.length) {
2779 if (obj.options.data[j][i] != '') {
2780 breakControl = true;
2781 continue;
2782 }
2783 }
2784
2785 // Column
2786 if (data[posy] == undefined) {
2787 posx = 0;
2788 } else if (data[posy][posx] == undefined) {
2789 posx = 0;
2790 }
2791
2792 // Value
2793 var value = data[posy][posx];
2794
2795 if (value && ! data[1] && obj.options.autoIncrement == true) {
2796 if (obj.options.columns[i].type == 'text' || obj.options.columns[i].type == 'number') {
2797 if ((''+value).substr(0,1) == '=') {
2798 var tokens = value.match(/([A-Z]+[0-9]+)/g);
2799
2800 if (tokens) {
2801 var affectedTokens = [];
2802 for (var index = 0; index < tokens.length; index++) {
2803 var position = jexcel.getIdFromColumnName(tokens[index], 1);
2804 position[0] += colNumber;
2805 position[1] += rowNumber;
2806 if (position[1] < 0) {
2807 position[1] = 0;
2808 }
2809 var token = jexcel.getColumnNameFromId([position[0], position[1]]);
2810
2811 if (token != tokens[index]) {
2812 affectedTokens[tokens[index]] = token;
2813 }
2814 }
2815 // Update formula
2816 if (affectedTokens) {
2817 value = obj.updateFormula(value, affectedTokens)
2818 }
2819 }
2820 } else {
2821 if (value == Number(value)) {
2822 value = Number(value) + rowNumber;
2823 }
2824 }
2825 } else if (obj.options.columns[i].type == 'calendar') {
2826 var date = new Date(value);
2827 date.setDate(date.getDate() + rowNumber);
2828 value = date.getFullYear() + '-' + jexcel.doubleDigitFormat(parseInt(date.getMonth() + 1)) + '-' + jexcel.doubleDigitFormat(date.getDate()) + ' ' + '00:00:00';
2829 }
2830 }
2831
2832 records.push(obj.updateCell(i, j, value));
2833
2834 // Update all formulas in the chain
2835 obj.updateFormulaChain(i, j, records);
2836 }
2837 posx++;
2838 if (h[0] != x1) {
2839 colNumber++;
2840 }
2841 }
2842 posy++;
2843 rowNumber++;
2844 }
2845
2846 // Update history
2847 obj.setHistory({
2848 action:'setValue',
2849 records:records,
2850 selection:obj.selectedCell,
2851 });
2852
2853 // Update table with custom configuration if applicable
2854 obj.updateTable();
2855
2856 // On after changes
2857 obj.onafterchanges(el, records);
2858 }
2859
2860 /**
2861 * Refresh current selection
2862 */
2863 obj.refreshSelection = function() {
2864 if (obj.selectedCell) {
2865 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
2866 }
2867 }
2868
2869 /**
2870 * Move coords to A1 in case overlaps with an excluded cell
2871 */
2872 obj.conditionalSelectionUpdate = function(type, o, d) {
2873 if (type == 1) {
2874 if (obj.selectedCell && ((o >= obj.selectedCell[1] && o <= obj.selectedCell[3]) || (d >= obj.selectedCell[1] && d <= obj.selectedCell[3]))) {
2875 obj.resetSelection();
2876 return;
2877 }
2878 } else {
2879 if (obj.selectedCell && ((o >= obj.selectedCell[0] && o <= obj.selectedCell[2]) || (d >= obj.selectedCell[0] && d <= obj.selectedCell[2]))) {
2880 obj.resetSelection();
2881 return;
2882 }
2883 }
2884 }
2885
2886 /**
2887 * Clear table selection
2888 */
2889 obj.resetSelection = function(blur) {
2890 // Remove style
2891 if (! obj.highlighted.length) {
2892 var previousStatus = 0;
2893 } else {
2894 var previousStatus = 1;
2895
2896 for (var i = 0; i < obj.highlighted.length; i++) {
2897 obj.highlighted[i].classList.remove('highlight');
2898 obj.highlighted[i].classList.remove('highlight-left');
2899 obj.highlighted[i].classList.remove('highlight-right');
2900 obj.highlighted[i].classList.remove('highlight-top');
2901 obj.highlighted[i].classList.remove('highlight-bottom');
2902 obj.highlighted[i].classList.remove('highlight-selected');
2903
2904 var px = parseInt(obj.highlighted[i].getAttribute('data-x'));
2905 var py = parseInt(obj.highlighted[i].getAttribute('data-y'));
2906
2907 // Check for merged cells
2908 if (obj.highlighted[i].getAttribute('data-merged')) {
2909 var colspan = parseInt(obj.highlighted[i].getAttribute('colspan'));
2910 var rowspan = parseInt(obj.highlighted[i].getAttribute('rowspan'));
2911 var ux = colspan > 0 ? px + (colspan - 1) : px;
2912 var uy = rowspan > 0 ? py + (rowspan - 1): py;
2913 } else {
2914 var ux = px;
2915 var uy = py;
2916 }
2917
2918 // Remove selected from headers
2919 for (var j = px; j <= ux; j++) {
2920 if (obj.headers[j]) {
2921 obj.headers[j].classList.remove('selected');
2922 }
2923 }
2924
2925 // Remove selected from rows
2926 for (var j = py; j <= uy; j++) {
2927 if (obj.rows[j]) {
2928 obj.rows[j].classList.remove('selected');
2929 }
2930 }
2931 }
2932 }
2933
2934 // Reset highlighted cells
2935 obj.highlighted = [];
2936
2937 // Reset
2938 obj.selectedCell = null;
2939
2940 // Hide corner
2941 obj.corner.style.top = '-2000px';
2942 obj.corner.style.left = '-2000px';
2943
2944 if (blur == true && previousStatus == 1) {
2945 obj.dispatch('onblur', el);
2946 }
2947
2948 return previousStatus;
2949 }
2950
2951 /**
2952 * Update selection based on two cells
2953 */
2954 obj.updateSelection = function(el1, el2, origin) {
2955 var x1 = el1.getAttribute('data-x');
2956 var y1 = el1.getAttribute('data-y');
2957 if (el2) {
2958 var x2 = el2.getAttribute('data-x');
2959 var y2 = el2.getAttribute('data-y');
2960 } else {
2961 var x2 = x1;
2962 var y2 = y1;
2963 }
2964
2965 obj.updateSelectionFromCoords(x1, y1, x2, y2, origin);
2966 }
2967
2968 /**
2969 * Update selection from coords
2970 */
2971 obj.updateSelectionFromCoords = function(x1, y1, x2, y2, origin) {
2972 // Reset Selection
2973 var updated = null;
2974 var previousState = obj.resetSelection();
2975
2976 // select column
2977 if (y1 == null) {
2978 y1 = 0;
2979 y2 = obj.rows.length - 1;
2980 }
2981
2982 // Same element
2983 if (x2 == null) {
2984 x2 = x1;
2985 }
2986 if (y2 == null) {
2987 y2 = y1;
2988 }
2989
2990 // Selection must be within the existing data
2991 if (x1 >= obj.headers.length) {
2992 x1 = obj.headers.length - 1;
2993 }
2994 if (y1 >= obj.rows.length) {
2995 y1 = obj.rows.length - 1;
2996 }
2997 if (x2 >= obj.headers.length) {
2998 x2 = obj.headers.length - 1;
2999 }
3000 if (y2 >= obj.rows.length) {
3001 y2 = obj.rows.length - 1;
3002 }
3003
3004 // Keep selected cell
3005 obj.selectedCell = [x1, y1, x2, y2];
3006
3007 // Select cells
3008 if (x1 != null) {
3009 // Add selected cell
3010 if (obj.records[y1][x1]) {
3011 obj.records[y1][x1].classList.add('highlight-selected');
3012 }
3013
3014 // Origin & Destination
3015 if (parseInt(x1) < parseInt(x2)) {
3016 var px = parseInt(x1);
3017 var ux = parseInt(x2);
3018 } else {
3019 var px = parseInt(x2);
3020 var ux = parseInt(x1);
3021 }
3022
3023 if (parseInt(y1) < parseInt(y2)) {
3024 var py = parseInt(y1);
3025 var uy = parseInt(y2);
3026 } else {
3027 var py = parseInt(y2);
3028 var uy = parseInt(y1);
3029 }
3030
3031 // Verify merged columns
3032 for (var i = px; i <= ux; i++) {
3033 for (var j = py; j <= uy; j++) {
3034 if (obj.records[j][i] && obj.records[j][i].getAttribute('data-merged')) {
3035 var x = parseInt(obj.records[j][i].getAttribute('data-x'));
3036 var y = parseInt(obj.records[j][i].getAttribute('data-y'));
3037 var colspan = parseInt(obj.records[j][i].getAttribute('colspan'));
3038 var rowspan = parseInt(obj.records[j][i].getAttribute('rowspan'));
3039
3040 if (colspan > 1) {
3041 if (x < px) {
3042 px = x;
3043 }
3044 if (x + colspan > ux) {
3045 ux = x + colspan - 1;
3046 }
3047 }
3048
3049 if (rowspan) {
3050 if (y < py) {
3051 py = y;
3052
3053 }
3054 if (y + rowspan > uy) {
3055 uy = y + rowspan - 1;
3056 }
3057 }
3058 }
3059 }
3060 }
3061
3062 // Limits
3063 var borderLeft = null;
3064 var borderRight = null;
3065 var borderTop = null;
3066 var borderBottom = null;
3067
3068 // Vertical limits
3069 for (var j = py; j <= uy; j++) {
3070 if (obj.rows[j].style.display != 'none') {
3071 if (borderTop == null) {
3072 borderTop = j;
3073 }
3074 borderBottom = j;
3075 }
3076 }
3077
3078 // Redefining styles
3079 for (var i = px; i <= ux; i++) {
3080 for (var j = py; j <= uy; j++) {
3081 if (obj.rows[j].style.display != 'none' && obj.records[j][i].style.display != 'none') {
3082 obj.records[j][i].classList.add('highlight');
3083 obj.highlighted.push(obj.records[j][i]);
3084 }
3085 }
3086
3087 // Horizontal limits
3088 if (obj.options.columns[i].type != 'hidden') {
3089 if (borderLeft == null) {
3090 borderLeft = i;
3091 }
3092 borderRight = i;
3093 }
3094 }
3095
3096 // Create borders
3097 if (! borderLeft) {
3098 borderLeft = 0;
3099 }
3100 if (! borderRight) {
3101 borderRight = 0;
3102 }
3103 for (var i = borderLeft; i <= borderRight; i++) {
3104 if (obj.options.columns[i].type != 'hidden' && obj.colgroup[i].style && obj.colgroup[i].style.display != 'none') {
3105 // Top border
3106 if (obj.records[borderTop] && obj.records[borderTop][i]) {
3107 obj.records[borderTop][i].classList.add('highlight-top');
3108 }
3109 // Bottom border
3110 if (obj.records[borderBottom] && obj.records[borderBottom][i]) {
3111 obj.records[borderBottom][i].classList.add('highlight-bottom');
3112 }
3113 // Add selected from headers
3114 obj.headers[i].classList.add('selected');
3115 }
3116 }
3117
3118 for (var j = borderTop; j <= borderBottom; j++) {
3119 if (obj.rows[j] && obj.rows[j].style.display != 'none') {
3120 // Left border
3121 obj.records[j][borderLeft].classList.add('highlight-left');
3122 // Right border
3123 obj.records[j][borderRight].classList.add('highlight-right');
3124 // Add selected from rows
3125 obj.rows[j].classList.add('selected');
3126 }
3127 }
3128
3129 obj.selectedContainer = [ borderLeft, borderTop, borderRight, borderBottom ];
3130 }
3131
3132 // Handle events
3133 if (previousState == 0) {
3134 obj.dispatch('onfocus', el);
3135
3136 obj.removeCopyingSelection();
3137 }
3138
3139 obj.dispatch('onselection', el, borderLeft, borderTop, borderRight, borderBottom, origin);
3140
3141 // Find corner cell
3142 obj.updateCornerPosition();
3143 }
3144
3145 /**
3146 * Remove copy selection
3147 *
3148 * @return void
3149 */
3150 obj.removeCopySelection = function() {
3151 // Remove current selection
3152 for (var i = 0; i < obj.selection.length; i++) {
3153 obj.selection[i].classList.remove('selection');
3154 obj.selection[i].classList.remove('selection-left');
3155 obj.selection[i].classList.remove('selection-right');
3156 obj.selection[i].classList.remove('selection-top');
3157 obj.selection[i].classList.remove('selection-bottom');
3158 }
3159
3160 obj.selection = [];
3161 }
3162
3163 /**
3164 * Update copy selection
3165 *
3166 * @param int x, y
3167 * @return void
3168 */
3169 obj.updateCopySelection = function(x3, y3) {
3170 // Remove selection
3171 obj.removeCopySelection();
3172
3173 // Get elements first and last
3174 var x1 = obj.selectedContainer[0];
3175 var y1 = obj.selectedContainer[1];
3176 var x2 = obj.selectedContainer[2];
3177 var y2 = obj.selectedContainer[3];
3178
3179 if (x3 != null && y3 != null) {
3180 if (x3 - x2 > 0) {
3181 var px = parseInt(x2) + 1;
3182 var ux = parseInt(x3);
3183 } else {
3184 var px = parseInt(x3);
3185 var ux = parseInt(x1) - 1;
3186 }
3187
3188 if (y3 - y2 > 0) {
3189 var py = parseInt(y2) + 1;
3190 var uy = parseInt(y3);
3191 } else {
3192 var py = parseInt(y3);
3193 var uy = parseInt(y1) - 1;
3194 }
3195
3196 if (ux - px <= uy - py) {
3197 var px = parseInt(x1);
3198 var ux = parseInt(x2);
3199 } else {
3200 var py = parseInt(y1);
3201 var uy = parseInt(y2);
3202 }
3203
3204 for (var j = py; j <= uy; j++) {
3205 for (var i = px; i <= ux; i++) {
3206 if (obj.records[j][i] && obj.rows[j].style.display != 'none' && obj.records[j][i].style.display != 'none') {
3207 obj.records[j][i].classList.add('selection');
3208 obj.records[py][i].classList.add('selection-top');
3209 obj.records[uy][i].classList.add('selection-bottom');
3210 obj.records[j][px].classList.add('selection-left');
3211 obj.records[j][ux].classList.add('selection-right');
3212
3213 // Persist selected elements
3214 obj.selection.push(obj.records[j][i]);
3215 }
3216 }
3217 }
3218 }
3219 }
3220
3221 /**
3222 * Update corner position
3223 *
3224 * @return void
3225 */
3226 obj.updateCornerPosition = function() {
3227 // If any selected cells
3228 if (! obj.highlighted.length) {
3229 obj.corner.style.top = '-2000px';
3230 obj.corner.style.left = '-2000px';
3231 } else {
3232 // Get last cell
3233 var last = obj.highlighted[obj.highlighted.length-1];
3234 var lastX = last.getAttribute('data-x');
3235
3236 var contentRect = obj.content.getBoundingClientRect();
3237 var x1 = contentRect.left;
3238 var y1 = contentRect.top;
3239
3240 var lastRect = last.getBoundingClientRect();
3241 var x2 = lastRect.left;
3242 var y2 = lastRect.top;
3243 var w2 = lastRect.width;
3244 var h2 = lastRect.height;
3245
3246 var x = (x2 - x1) + obj.content.scrollLeft + w2 - 4;
3247 var y = (y2 - y1) + obj.content.scrollTop + h2 - 4;
3248
3249 // Place the corner in the correct place
3250 obj.corner.style.top = y + 'px';
3251 obj.corner.style.left = x + 'px';
3252
3253 if (obj.options.freezeColumns) {
3254 var width = obj.getFreezeWidth();
3255 // Only check if the last column is not part of the merged cells
3256 if (lastX > obj.options.freezeColumns-1 && x2 - x1 + w2 < width) {
3257 obj.corner.style.display = 'none';
3258 } else {
3259 if (obj.options.selectionCopy == true) {
3260 obj.corner.style.display = '';
3261 }
3262 }
3263 } else {
3264 if (obj.options.selectionCopy == true) {
3265 obj.corner.style.display = '';
3266 }
3267 }
3268 }
3269 }
3270
3271 /**
3272 * Update scroll position based on the selection
3273 */
3274 obj.updateScroll = function(direction) {
3275 // Jspreadsheet Container information
3276 var contentRect = obj.content.getBoundingClientRect();
3277 var x1 = contentRect.left;
3278 var y1 = contentRect.top;
3279 var w1 = contentRect.width;
3280 var h1 = contentRect.height;
3281
3282 // Direction Left or Up
3283 var reference = obj.records[obj.selectedCell[3]][obj.selectedCell[2]];
3284
3285 // Reference
3286 var referenceRect = reference.getBoundingClientRect();
3287 var x2 = referenceRect.left;
3288 var y2 = referenceRect.top;
3289 var w2 = referenceRect.width;
3290 var h2 = referenceRect.height;
3291
3292 // Direction
3293 if (direction == 0 || direction == 1) {
3294 var x = (x2 - x1) + obj.content.scrollLeft;
3295 var y = (y2 - y1) + obj.content.scrollTop - 2;
3296 } else {
3297 var x = (x2 - x1) + obj.content.scrollLeft + w2;
3298 var y = (y2 - y1) + obj.content.scrollTop + h2;
3299 }
3300
3301 // Top position check
3302 if (y > (obj.content.scrollTop + 30) && y < (obj.content.scrollTop + h1)) {
3303 // In the viewport
3304 } else {
3305 // Out of viewport
3306 if (y < obj.content.scrollTop + 30) {
3307 obj.content.scrollTop = y - h2;
3308 } else {
3309 obj.content.scrollTop = y - (h1 - 2);
3310 }
3311 }
3312
3313 // Freeze columns?
3314 var freezed = obj.getFreezeWidth();
3315
3316 // Left position check - TODO: change that to the bottom border of the element
3317 if (x > (obj.content.scrollLeft + freezed) && x < (obj.content.scrollLeft + w1)) {
3318 // In the viewport
3319 } else {
3320 // Out of viewport
3321 if (x < obj.content.scrollLeft + 30) {
3322 obj.content.scrollLeft = x;
3323 if (obj.content.scrollLeft < 50) {
3324 obj.content.scrollLeft = 0;
3325 }
3326 } else if (x < obj.content.scrollLeft + freezed) {
3327 obj.content.scrollLeft = x - freezed - 1;
3328 } else {
3329 obj.content.scrollLeft = x - (w1 - 20);
3330 }
3331 }
3332 }
3333
3334 /**
3335 * Get the column width
3336 *
3337 * @param int column column number (first column is: 0)
3338 * @return int current width
3339 */
3340 obj.getWidth = function(column) {
3341 if (typeof column === 'undefined') {
3342 // Get all headers
3343 var data = [];
3344 for (var i = 0; i < obj.headers.length; i++) {
3345 data.push(obj.options.columns[i].width);
3346 }
3347 } else {
3348 // In case the column is an object
3349 if (typeof(column) == 'object') {
3350 column = column.getAttribute('data-x');
3351 }
3352
3353 data = obj.colgroup[column].getAttribute('width')
3354 }
3355
3356 return data;
3357 }
3358
3359
3360 /**
3361 * Set the column width
3362 *
3363 * @param int column number (first column is: 0)
3364 * @param int new column width
3365 * @param int old column width
3366 */
3367 obj.setWidth = function (column, width, oldWidth) {
3368 if (width) {
3369 if (Array.isArray(column)) {
3370 // Oldwidth
3371 if (! oldWidth) {
3372 var oldWidth = [];
3373 }
3374 // Set width
3375 for (var i = 0; i < column.length; i++) {
3376 if (! oldWidth[i]) {
3377 oldWidth[i] = obj.colgroup[column[i]].getAttribute('width');
3378 }
3379 var w = Array.isArray(width) && width[i] ? width[i] : width;
3380 obj.colgroup[column[i]].setAttribute('width', w);
3381 obj.options.columns[column[i]].width = w;
3382 }
3383 } else {
3384 // Oldwidth
3385 if (! oldWidth) {
3386 oldWidth = obj.colgroup[column].getAttribute('width');
3387 }
3388 // Set width
3389 obj.colgroup[column].setAttribute('width', width);
3390 obj.options.columns[column].width = width;
3391 }
3392
3393 // Keeping history of changes
3394 obj.setHistory({
3395 action:'setWidth',
3396 column:column,
3397 oldValue:oldWidth,
3398 newValue:width,
3399 });
3400
3401 // On resize column
3402 obj.dispatch('onresizecolumn', el, column, width, oldWidth);
3403
3404 // Update corner position
3405 obj.updateCornerPosition();
3406 }
3407 }
3408
3409 /**
3410 * Set the row height
3411 *
3412 * @param row - row number (first row is: 0)
3413 * @param height - new row height
3414 * @param oldHeight - old row height
3415 */
3416 obj.setHeight = function (row, height, oldHeight) {
3417 if (height > 0) {
3418 // In case the column is an object
3419 if (typeof(row) == 'object') {
3420 row = row.getAttribute('data-y');
3421 }
3422
3423 // Oldwidth
3424 if (! oldHeight) {
3425 oldHeight = obj.rows[row].getAttribute('height');
3426
3427 if (! oldHeight) {
3428 var rect = obj.rows[row].getBoundingClientRect();
3429 oldHeight = rect.height;
3430 }
3431 }
3432
3433 // Integer
3434 height = parseInt(height);
3435
3436 // TablePress: Add a custom CSS variable to adjust the number of shown lines of text to the row height.
3437 obj.rows[row].style.setProperty( '--table-editor-line-clamp', Math.floor( (height-10) / 14 ) );
3438
3439 // Set width
3440 // TablePress: Don't set the row height as a CSS property. This makes the resizing behavior lose its preview when making a row smaller.
3441 //obj.rows[row].style.height = height + 'px';
3442
3443 // Keep options updated
3444 if (! obj.options.rows[row]) {
3445 obj.options.rows[row] = {};
3446 }
3447 obj.options.rows[row].height = height;
3448
3449 // Keeping history of changes
3450 obj.setHistory({
3451 action:'setHeight',
3452 row:row,
3453 oldValue:oldHeight,
3454 newValue:height,
3455 });
3456
3457 // On resize column
3458 obj.dispatch('onresizerow', el, row, height, oldHeight);
3459
3460 // Update corner position
3461 obj.updateCornerPosition();
3462 }
3463 }
3464
3465 /**
3466 * Get the row height
3467 *
3468 * @param row - row number (first row is: 0)
3469 * @return height - current row height
3470 */
3471 obj.getHeight = function(row) {
3472 if (typeof row === 'undefined') {
3473 // Get height of all rows
3474 var data = [];
3475 for (var j = 0; j < obj.rows.length; j++) {
3476 var h = obj.rows[j].style.height;
3477 if (h) {
3478 data[j] = h;
3479 }
3480 }
3481 } else {
3482 // In case the row is an object
3483 if (typeof(row) == 'object') {
3484 row = $(row).getAttribute('data-y');
3485 }
3486
3487 var data = obj.rows[row].style.height;
3488 }
3489
3490 return data;
3491 }
3492
3493 obj.setFooter = function(data) {
3494 if (data) {
3495 obj.options.footers = data;
3496 }
3497
3498 if (obj.options.footers) {
3499 if (! obj.tfoot) {
3500 obj.tfoot = document.createElement('tfoot');
3501 obj.table.appendChild(obj.tfoot);
3502 }
3503
3504 for (var j = 0; j < obj.options.footers.length; j++) {
3505 if (obj.tfoot.children[j]) {
3506 var tr = obj.tfoot.children[j];
3507 } else {
3508 var tr = document.createElement('tr');
3509 var td = document.createElement('td');
3510 tr.appendChild(td);
3511 obj.tfoot.appendChild(tr);
3512 }
3513 for (var i = 0; i < obj.headers.length; i++) {
3514 if (! obj.options.footers[j][i]) {
3515 obj.options.footers[j][i] = '';
3516 }
3517 if (obj.tfoot.children[j].children[i+1]) {
3518 var td = obj.tfoot.children[j].children[i+1];
3519 } else {
3520 var td = document.createElement('td');
3521 tr.appendChild(td);
3522
3523 // Text align
3524 var colAlign = obj.options.columns[i].align ? obj.options.columns[i].align : 'center';
3525 td.style.textAlign = colAlign;
3526 }
3527 td.textContent = obj.parseValue(+obj.records.length + i, j, obj.options.footers[j][i]);
3528
3529 // Hide/Show with hideColumn()/showColumn()
3530 td.style.display = obj.colgroup[i].style.display;
3531 }
3532 }
3533 }
3534 }
3535
3536 /**
3537 * Get the column title
3538 *
3539 * @param column - column number (first column is: 0)
3540 * @param title - new column title
3541 */
3542 obj.getHeader = function(column) {
3543 return obj.headers[column].textContent;
3544 }
3545
3546 /**
3547 * Set the column title
3548 *
3549 * @param column - column number (first column is: 0)
3550 * @param title - new column title
3551 */
3552 obj.setHeader = function(column, newValue) {
3553 if (obj.headers[column]) {
3554 var oldValue = obj.headers[column].textContent;
3555
3556 if (! newValue) {
3557 newValue = prompt(obj.options.text.columnName, oldValue)
3558 }
3559
3560 if (newValue) {
3561 obj.headers[column].textContent = newValue;
3562 // Keep the title property
3563 obj.headers[column].setAttribute('title', newValue);
3564 // Update title
3565 obj.options.columns[column].title = newValue;
3566 }
3567
3568 obj.setHistory({
3569 action: 'setHeader',
3570 column: column,
3571 oldValue: oldValue,
3572 newValue: newValue
3573 });
3574
3575 // On onchange header
3576 obj.dispatch('onchangeheader', el, column, oldValue, newValue);
3577 }
3578 }
3579
3580 /**
3581 * Get the headers
3582 *
3583 * @param asArray
3584 * @return mixed
3585 */
3586 obj.getHeaders = function (asArray) {
3587 var title = [];
3588
3589 for (var i = 0; i < obj.headers.length; i++) {
3590 title.push(obj.getHeader(i));
3591 }
3592
3593 return asArray ? title : title.join(obj.options.csvDelimiter);
3594 }
3595
3596 /**
3597 * Get meta information from cell(s)
3598 *
3599 * @return integer
3600 */
3601 obj.getMeta = function(cell, key) {
3602 if (! cell) {
3603 return obj.options.meta;
3604 } else {
3605 if (key) {
3606 return obj.options.meta[cell] && obj.options.meta[cell][key] ? obj.options.meta[cell][key] : null;
3607 } else {
3608 return obj.options.meta[cell] ? obj.options.meta[cell] : null;
3609 }
3610 }
3611 }
3612
3613 /**
3614 * Set meta information to cell(s)
3615 *
3616 * @return integer
3617 */
3618 obj.setMeta = function(o, k, v) {
3619 if (! obj.options.meta) {
3620 obj.options.meta = {}
3621 }
3622
3623 if (k && v) {
3624 // Set data value
3625 if (! obj.options.meta[o]) {
3626 obj.options.meta[o] = {};
3627 }
3628 obj.options.meta[o][k] = v;
3629 } else {
3630 // Apply that for all cells
3631 var keys = Object.keys(o);
3632 for (var i = 0; i < keys.length; i++) {
3633 if (! obj.options.meta[keys[i]]) {
3634 obj.options.meta[keys[i]] = {};
3635 }
3636
3637 var prop = Object.keys(o[keys[i]]);
3638 for (var j = 0; j < prop.length; j++) {
3639 obj.options.meta[keys[i]][prop[j]] = o[keys[i]][prop[j]];
3640 }
3641 }
3642 }
3643
3644 obj.dispatch('onchangemeta', el, o, k, v);
3645 }
3646
3647 /**
3648 * Update meta information
3649 *
3650 * @return integer
3651 */
3652 obj.updateMeta = function(affectedCells) {
3653 if (obj.options.meta) {
3654 var newMeta = {};
3655 var keys = Object.keys(obj.options.meta);
3656 for (var i = 0; i < keys.length; i++) {
3657 if (affectedCells[keys[i]]) {
3658 newMeta[affectedCells[keys[i]]] = obj.options.meta[keys[i]];
3659 } else {
3660 newMeta[keys[i]] = obj.options.meta[keys[i]];
3661 }
3662 }
3663 // Update meta information
3664 obj.options.meta = newMeta;
3665 }
3666 }
3667
3668 /**
3669 * Get style information from cell(s)
3670 *
3671 * @return integer
3672 */
3673 obj.getStyle = function(cell, key) {
3674 // Cell
3675 if (! cell) {
3676 // Control vars
3677 var data = {};
3678
3679 // Column and row length
3680 var x = obj.options.data[0].length;
3681 var y = obj.options.data.length;
3682
3683 // Go through the columns to get the data
3684 for (var j = 0; j < y; j++) {
3685 for (var i = 0; i < x; i++) {
3686 // Value
3687 var v = key ? obj.records[j][i].style[key] : obj.records[j][i].getAttribute('style');
3688
3689 // Any meta data for this column?
3690 if (v) {
3691 // Column name
3692 var k = jexcel.getColumnNameFromId([i, j]);
3693 // Value
3694 data[k] = v;
3695 }
3696 }
3697 }
3698
3699 return data;
3700 } else {
3701 cell = jexcel.getIdFromColumnName(cell, true);
3702
3703 return key ? obj.records[cell[1]][cell[0]].style[key] : obj.records[cell[1]][cell[0]].getAttribute('style');
3704 }
3705 },
3706
3707 obj.resetStyle = function(o, ignoreHistoryAndEvents) {
3708 var keys = Object.keys(o);
3709 for (var i = 0; i < keys.length; i++) {
3710 // Position
3711 var cell = jexcel.getIdFromColumnName(keys[i], true);
3712 if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]]) {
3713 obj.records[cell[1]][cell[0]].setAttribute('style', '');
3714 }
3715 }
3716 obj.setStyle(o, null, null, null, ignoreHistoryAndEvents);
3717 }
3718
3719 /**
3720 * Set meta information to cell(s)
3721 *
3722 * @return integer
3723 */
3724 obj.setStyle = function(o, k, v, force, ignoreHistoryAndEvents) {
3725 var newValue = {};
3726 var oldValue = {};
3727
3728 // Apply style
3729 var applyStyle = function(cellId, key, value) {
3730 // Position
3731 var cell = jexcel.getIdFromColumnName(cellId, true);
3732
3733 if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]] && (obj.records[cell[1]][cell[0]].classList.contains('readonly')==false || force)) {
3734 // Current value
3735 var currentValue = obj.records[cell[1]][cell[0]].style[key];
3736
3737 // Change layout
3738 if (currentValue == value && ! force) {
3739 value = '';
3740 obj.records[cell[1]][cell[0]].style[key] = '';
3741 } else {
3742 obj.records[cell[1]][cell[0]].style[key] = value;
3743 }
3744
3745 // History
3746 if (! oldValue[cellId]) {
3747 oldValue[cellId] = [];
3748 }
3749 if (! newValue[cellId]) {
3750 newValue[cellId] = [];
3751 }
3752
3753 oldValue[cellId].push([key + ':' + currentValue]);
3754 newValue[cellId].push([key + ':' + value]);
3755 }
3756 }
3757
3758 if (k && v) {
3759 // Get object from string
3760 if (typeof(o) == 'string') {
3761 applyStyle(o, k, v);
3762 } else {
3763 // Avoid duplications
3764 var oneApplication = [];
3765 // Apply that for all cells
3766 for (var i = 0; i < o.length; i++) {
3767 var x = o[i].getAttribute('data-x');
3768 var y = o[i].getAttribute('data-y');
3769 var cellName = jexcel.getColumnNameFromId([x, y]);
3770 // This happens when is a merged cell
3771 if (! oneApplication[cellName]) {
3772 applyStyle(cellName, k, v);
3773 oneApplication[cellName] = true;
3774 }
3775 }
3776 }
3777 } else {
3778 var keys = Object.keys(o);
3779 for (var i = 0; i < keys.length; i++) {
3780 var style = o[keys[i]];
3781 if (typeof(style) == 'string') {
3782 style = style.split(';');
3783 }
3784 for (var j = 0; j < style.length; j++) {
3785 if (typeof(style[j]) == 'string') {
3786 style[j] = style[j].split(':');
3787 }
3788 // Apply value
3789 if (style[j][0].trim()) {
3790 applyStyle(keys[i], style[j][0].trim(), style[j][1]);
3791 }
3792 }
3793 }
3794 }
3795
3796 var keys = Object.keys(oldValue);
3797 for (var i = 0; i < keys.length; i++) {
3798 oldValue[keys[i]] = oldValue[keys[i]].join(';');
3799 }
3800 var keys = Object.keys(newValue);
3801 for (var i = 0; i < keys.length; i++) {
3802 newValue[keys[i]] = newValue[keys[i]].join(';');
3803 }
3804
3805 if (! ignoreHistoryAndEvents) {
3806 // Keeping history of changes
3807 obj.setHistory({
3808 action: 'setStyle',
3809 oldValue: oldValue,
3810 newValue: newValue,
3811 });
3812 }
3813
3814 obj.dispatch('onchangestyle', el, o, k, v);
3815 }
3816
3817 /**
3818 * Get cell comments, null cell for all
3819 */
3820 obj.getComments = function(cell, withAuthor) {
3821 if (cell) {
3822 if (typeof(cell) == 'string') {
3823 var cell = jexcel.getIdFromColumnName(cell, true);
3824 }
3825
3826 if (withAuthor) {
3827 return [obj.records[cell[1]][cell[0]].getAttribute('title'), obj.records[cell[1]][cell[0]].getAttribute('author')];
3828 } else {
3829 return obj.records[cell[1]][cell[0]].getAttribute('title') || '';
3830 }
3831 } else {
3832 var data = {};
3833 for (var j = 0; j < obj.options.data.length; j++) {
3834 for (var i = 0; i < obj.options.columns.length; i++) {
3835 var comments = obj.records[j][i].getAttribute('title');
3836 if (comments) {
3837 var cell = jexcel.getColumnNameFromId([i, j]);
3838 data[cell] = comments;
3839 }
3840 }
3841 }
3842 return data;
3843 }
3844 }
3845
3846 /**
3847 * Set cell comments
3848 */
3849 obj.setComments = function(cellId, comments, author) {
3850 if (typeof(cellId) == 'string') {
3851 var cell = jexcel.getIdFromColumnName(cellId, true);
3852 } else {
3853 var cell = cellId;
3854 }
3855
3856 // Keep old value
3857 var title = obj.records[cell[1]][cell[0]].getAttribute('title');
3858 var author = obj.records[cell[1]][cell[0]].getAttribute('data-author');
3859 var oldValue = [ title, author ];
3860
3861 // Set new values
3862 obj.records[cell[1]][cell[0]].setAttribute('title', comments ? comments : '');
3863 obj.records[cell[1]][cell[0]].setAttribute('data-author', author ? author : '');
3864
3865 // Remove class if there is no comment
3866 if (comments) {
3867 obj.records[cell[1]][cell[0]].classList.add('jexcel_comments');
3868 } else {
3869 obj.records[cell[1]][cell[0]].classList.remove('jexcel_comments');
3870 }
3871
3872 // Save history
3873 obj.setHistory({
3874 action:'setComments',
3875 column: cellId,
3876 newValue: [ comments, author ],
3877 oldValue: oldValue,
3878 });
3879 // Set comments
3880 obj.dispatch('oncomments', el, comments, title, cell, cell[0], cell[1]);
3881 }
3882
3883 /**
3884 * Get table config information
3885 */
3886 obj.getConfig = function() {
3887 var options = obj.options;
3888 options.style = obj.getStyle();
3889 options.mergeCells = obj.getMerge();
3890 options.comments = obj.getComments();
3891
3892 return options;
3893 }
3894
3895 /**
3896 * Sort data and reload table
3897 */
3898 obj.orderBy = function(column, order) {
3899 if (column >= 0) {
3900 // Merged cells
3901 if (Object.keys(obj.options.mergeCells).length > 0) {
3902 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
3903 return false;
3904 } else {
3905 // Remove merged cells
3906 obj.destroyMerged();
3907 }
3908 }
3909
3910 // Direction
3911 if (order == null) {
3912 order = obj.headers[column].classList.contains('arrow-down') ? 1 : 0;
3913 } else {
3914 order = order ? 1 : 0;
3915 }
3916
3917 // Test order
3918 var temp = [];
3919 if (obj.options.columns[column].type == 'number' || obj.options.columns[column].type == 'numeric' || obj.options.columns[column].type == 'percentage' || obj.options.columns[column].type == 'autonumber' || obj.options.columns[column].type == 'color') {
3920 for (var j = 0; j < obj.options.data.length; j++) {
3921 temp[j] = [ j, Number(obj.options.data[j][column]) ];
3922 }
3923 } else if (obj.options.columns[column].type == 'calendar' || obj.options.columns[column].type == 'checkbox' || obj.options.columns[column].type == 'radio') {
3924 for (var j = 0; j < obj.options.data.length; j++) {
3925 temp[j] = [ j, obj.options.data[j][column] ];
3926 }
3927 } else {
3928 for (var j = 0; j < obj.options.data.length; j++) {
3929 temp[j] = [ j, obj.records[j][column].textContent.toLowerCase() ];
3930 }
3931 }
3932
3933 // Default sorting method
3934 if (typeof(obj.options.sorting) !== 'function') {
3935 obj.options.sorting = function(direction) {
3936 return function(a, b) {
3937 var valueA = a[1];
3938 var valueB = b[1];
3939
3940 if (! direction) {
3941 return (valueA === '' && valueB !== '') ? 1 : (valueA !== '' && valueB === '') ? -1 : (valueA > valueB) ? 1 : (valueA < valueB) ? -1 : 0;
3942 } else {
3943 return (valueA === '' && valueB !== '') ? 1 : (valueA !== '' && valueB === '') ? -1 : (valueA > valueB) ? -1 : (valueA < valueB) ? 1 : 0;
3944 }
3945 }
3946 }
3947 }
3948
3949 temp = temp.sort(obj.options.sorting(order));
3950
3951 // Save history
3952 var newValue = [];
3953 for (var j = 0; j < temp.length; j++) {
3954 newValue[j] = temp[j][0];
3955 }
3956
3957 // TablePress: Keep Table Head and Table Foot row at the top/bottom, if used.
3958 if ( window?.tp?.table?.options?.table_head ) {
3959 const first_row_idx = 0;
3960 newValue = newValue.filter( ( row_idx ) => ( row_idx > first_row_idx ) );
3961 newValue.unshift( first_row_idx );
3962 }
3963 if ( window?.tp?.table?.options?.table_foot ) {
3964 const last_row_idx = newValue.length - 1;
3965 newValue = newValue.filter( ( row_idx ) => ( row_idx < last_row_idx ) );
3966 newValue.push( last_row_idx );
3967 }
3968
3969 // Save history
3970 obj.setHistory({
3971 action: 'orderBy',
3972 rows: newValue,
3973 column: column,
3974 order: order,
3975 });
3976
3977 // Update order
3978 obj.updateOrderArrow(column, order);
3979 obj.updateOrder(newValue);
3980
3981 // On sort event
3982 obj.dispatch('onsort', el, column, order);
3983
3984 return true;
3985 }
3986 }
3987
3988 /**
3989 * Update order arrow
3990 */
3991 obj.updateOrderArrow = function(column, order) {
3992 // Remove order
3993 for (var i = 0; i < obj.headers.length; i++) {
3994 obj.headers[i].classList.remove('arrow-up');
3995 obj.headers[i].classList.remove('arrow-down');
3996 }
3997
3998 // No order specified then toggle order
3999 if (order) {
4000 obj.headers[column].classList.add('arrow-up');
4001 } else {
4002 obj.headers[column].classList.add('arrow-down');
4003 }
4004 }
4005
4006 /**
4007 * Update rows position
4008 */
4009 obj.updateOrder = function(rows) {
4010 // History
4011 var data = []
4012 for (var j = 0; j < rows.length; j++) {
4013 data[j] = obj.options.data[rows[j]];
4014 }
4015 obj.options.data = data;
4016
4017 var data = []
4018 for (var j = 0; j < rows.length; j++) {
4019 data[j] = obj.records[rows[j]];
4020 }
4021 obj.records = data;
4022
4023 var data = []
4024 for (var j = 0; j < rows.length; j++) {
4025 data[j] = obj.rows[rows[j]];
4026 }
4027 obj.rows = data;
4028
4029 // Update references
4030 obj.updateTableReferences();
4031
4032 // Redo search
4033 if (obj.results && obj.results.length) {
4034 if (obj.searchInput.value) {
4035 obj.search(obj.searchInput.value);
4036 } else {
4037 obj.closeFilter();
4038 }
4039 } else {
4040 // Create page
4041 obj.results = null;
4042 obj.pageNumber = 0;
4043
4044 if (obj.options.pagination > 0) {
4045 obj.page(0);
4046 } else if (obj.options.lazyLoading == true) {
4047 obj.loadPage(0);
4048 } else {
4049 for (var j = 0; j < obj.rows.length; j++) {
4050 obj.tbody.appendChild(obj.rows[j]);
4051 }
4052 }
4053 }
4054 }
4055
4056 /**
4057 * Move row
4058 *
4059 * @return void
4060 */
4061 obj.moveRow = function(o, d, ignoreDom) {
4062 if (Object.keys(obj.options.mergeCells).length > 0) {
4063 if (o > d) {
4064 var insertBefore = 1;
4065 } else {
4066 var insertBefore = 0;
4067 }
4068
4069 if (obj.isRowMerged(o).length || obj.isRowMerged(d, insertBefore).length) {
4070 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4071 return false;
4072 } else {
4073 obj.destroyMerged();
4074 }
4075 }
4076 }
4077
4078 if (obj.options.search == true) {
4079 if (obj.results && obj.results.length != obj.rows.length) {
4080 if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
4081 obj.resetSearch();
4082 } else {
4083 return false;
4084 }
4085 }
4086
4087 obj.results = null;
4088 }
4089
4090 if (! ignoreDom) {
4091 if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[d]) >= 0) {
4092 if (o > d) {
4093 obj.tbody.insertBefore(obj.rows[o], obj.rows[d]);
4094 } else {
4095 obj.tbody.insertBefore(obj.rows[o], obj.rows[d].nextSibling);
4096 }
4097 } else {
4098 obj.tbody.removeChild(obj.rows[o]);
4099 }
4100 }
4101
4102 // Place references in the correct position
4103 obj.rows.splice(d, 0, obj.rows.splice(o, 1)[0]);
4104 obj.records.splice(d, 0, obj.records.splice(o, 1)[0]);
4105 obj.options.data.splice(d, 0, obj.options.data.splice(o, 1)[0]);
4106
4107 // Respect pagination
4108 if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) {
4109 obj.page(obj.pageNumber);
4110 }
4111
4112 // Keeping history of changes
4113 obj.setHistory({
4114 action:'moveRow',
4115 oldValue: o,
4116 newValue: d,
4117 });
4118
4119 // Update table references
4120 obj.updateTableReferences();
4121
4122 // Events
4123 obj.dispatch('onmoverow', el, o, d);
4124 }
4125
4126 /**
4127 * Insert a new row
4128 *
4129 * @param mixed - number of blank lines to be insert or a single array with the data of the new row
4130 * @param rowNumber
4131 * @param insertBefore
4132 * @return void
4133 */
4134 obj.insertRow = function(mixed, rowNumber, insertBefore) {
4135 // Configuration
4136 if (obj.options.allowInsertRow == true) {
4137 // Records
4138 var records = [];
4139
4140 // Data to be insert
4141 var data = [];
4142
4143 // The insert could be lead by number of rows or the array of data
4144 if (mixed > 0) {
4145 var numOfRows = mixed;
4146 } else {
4147 var numOfRows = 1;
4148
4149 if (mixed) {
4150 data = mixed;
4151 }
4152 }
4153
4154 // Direction
4155 var insertBefore = insertBefore ? true : false;
4156
4157 // Current column number
4158 var lastRow = obj.options.data.length - 1;
4159
4160 if (rowNumber == undefined || rowNumber >= parseInt(lastRow) || rowNumber < 0) {
4161 rowNumber = lastRow;
4162 }
4163
4164 // Onbeforeinsertrow
4165 if (obj.dispatch('onbeforeinsertrow', el, rowNumber, numOfRows, insertBefore) === false) {
4166 return false;
4167 }
4168
4169 // Merged cells
4170 if (Object.keys(obj.options.mergeCells).length > 0) {
4171 if (obj.isRowMerged(rowNumber, insertBefore).length) {
4172 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4173 return false;
4174 } else {
4175 obj.destroyMerged();
4176 }
4177 }
4178 }
4179
4180 // Clear any search
4181 if (obj.options.search == true) {
4182 if (obj.results && obj.results.length != obj.rows.length) {
4183 if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
4184 obj.resetSearch();
4185 } else {
4186 return false;
4187 }
4188 }
4189
4190 obj.results = null;
4191 }
4192
4193 // Insertbefore
4194 var rowIndex = (! insertBefore) ? rowNumber + 1 : rowNumber;
4195
4196 // Keep the current data
4197 var currentRecords = obj.records.splice(rowIndex);
4198 var currentData = obj.options.data.splice(rowIndex);
4199 var currentRows = obj.rows.splice(rowIndex);
4200
4201 // Adding lines
4202 var rowRecords = [];
4203 var rowData = [];
4204 var rowNode = [];
4205
4206 for (var row = rowIndex; row < (numOfRows + rowIndex); row++) {
4207 // Push data to the data container
4208 obj.options.data[row] = [];
4209 for (var col = 0; col < obj.options.columns.length; col++) {
4210 obj.options.data[row][col] = data[col] ? data[col] : '';
4211 }
4212 // Create row
4213 var tr = obj.createRow(row, obj.options.data[row]);
4214 // Append node
4215 if (currentRows[0]) {
4216 if (Array.prototype.indexOf.call(obj.tbody.children, currentRows[0]) >= 0) {
4217 obj.tbody.insertBefore(tr, currentRows[0]);
4218 }
4219 } else {
4220 if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[rowNumber]) >= 0) {
4221 obj.tbody.appendChild(tr);
4222 }
4223 }
4224 // Record History
4225 rowRecords.push(obj.records[row]);
4226 rowData.push(obj.options.data[row]);
4227 rowNode.push(tr);
4228 }
4229
4230 // Copy the data back to the main data
4231 Array.prototype.push.apply(obj.records, currentRecords);
4232 Array.prototype.push.apply(obj.options.data, currentData);
4233 Array.prototype.push.apply(obj.rows, currentRows);
4234
4235 // Respect pagination
4236 if (obj.options.pagination > 0) {
4237 obj.page(obj.pageNumber);
4238 }
4239
4240 // Keep history
4241 obj.setHistory({
4242 action: 'insertRow',
4243 rowNumber: rowNumber,
4244 numOfRows: numOfRows,
4245 insertBefore: insertBefore,
4246 rowRecords: rowRecords,
4247 rowData: rowData,
4248 rowNode: rowNode,
4249 });
4250
4251 // Remove table references
4252 obj.updateTableReferences();
4253
4254 // Events
4255 obj.dispatch('oninsertrow', el, rowNumber, numOfRows, rowRecords, insertBefore);
4256 }
4257 }
4258
4259 /**
4260 * Delete a row by number
4261 *
4262 * @param integer rowNumber - row number to be excluded
4263 * @param integer numOfRows - number of lines
4264 * @return void
4265 */
4266 obj.deleteRow = function(rowNumber, numOfRows) {
4267 // Global Configuration
4268 if (obj.options.allowDeleteRow == true) {
4269 if (obj.options.allowDeletingAllRows == true || obj.options.data.length > 1) {
4270 // Delete row definitions
4271 if (rowNumber == undefined) {
4272 var number = obj.getSelectedRows();
4273
4274 if (! number[0]) {
4275 rowNumber = obj.options.data.length - 1;
4276 numOfRows = 1;
4277 } else {
4278 rowNumber = parseInt(number[0].getAttribute('data-y'));
4279 numOfRows = number.length;
4280 }
4281 }
4282
4283 // Last column
4284 var lastRow = obj.options.data.length - 1;
4285
4286 if (rowNumber == undefined || rowNumber > lastRow || rowNumber < 0) {
4287 rowNumber = lastRow;
4288 }
4289
4290 if (! numOfRows) {
4291 numOfRows = 1;
4292 }
4293
4294 // Do not delete more than the number of records
4295 if (rowNumber + numOfRows >= obj.options.data.length) {
4296 numOfRows = obj.options.data.length - rowNumber;
4297 }
4298
4299 // Onbeforedeleterow
4300 if (obj.dispatch('onbeforedeleterow', el, rowNumber, numOfRows) === false) {
4301 return false;
4302 }
4303
4304 if (parseInt(rowNumber) > -1) {
4305 // Merged cells
4306 var mergeExists = false;
4307 if (Object.keys(obj.options.mergeCells).length > 0) {
4308 for (var row = rowNumber; row < rowNumber + numOfRows; row++) {
4309 if (obj.isRowMerged(row, false).length) {
4310 mergeExists = true;
4311 }
4312 }
4313 }
4314 if (mergeExists) {
4315 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4316 return false;
4317 } else {
4318 obj.destroyMerged();
4319 }
4320 }
4321
4322 // Clear any search
4323 if (obj.options.search == true) {
4324 if (obj.results && obj.results.length != obj.rows.length) {
4325 if (confirm(obj.options.text.thisActionWillClearYourSearchResultsAreYouSure)) {
4326 obj.resetSearch();
4327 } else {
4328 return false;
4329 }
4330 }
4331
4332 obj.results = null;
4333 }
4334
4335 // If delete all rows, and set allowDeletingAllRows false, will stay one row
4336 if (obj.options.allowDeletingAllRows == false && lastRow + 1 === numOfRows) {
4337 numOfRows--;
4338 console.error('Jspreadsheet: It is not possible to delete the last row');
4339 }
4340
4341 // Remove node
4342 for (var row = rowNumber; row < rowNumber + numOfRows; row++) {
4343 if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[row]) >= 0) {
4344 obj.rows[row].className = '';
4345 obj.rows[row].parentNode.removeChild(obj.rows[row]);
4346 }
4347 }
4348
4349 // Remove data
4350 var rowRecords = obj.records.splice(rowNumber, numOfRows);
4351 var rowData = obj.options.data.splice(rowNumber, numOfRows);
4352 var rowNode = obj.rows.splice(rowNumber, numOfRows);
4353
4354 // Respect pagination
4355 if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) {
4356 obj.page(obj.pageNumber);
4357 }
4358
4359 // Remove selection
4360 obj.conditionalSelectionUpdate(1, rowNumber, (rowNumber + numOfRows) - 1);
4361
4362 // Keep history
4363 obj.setHistory({
4364 action: 'deleteRow',
4365 rowNumber: rowNumber,
4366 numOfRows: numOfRows,
4367 insertBefore: 1,
4368 rowRecords: rowRecords,
4369 rowData: rowData,
4370 rowNode: rowNode
4371 });
4372
4373 // Remove table references
4374 obj.updateTableReferences();
4375
4376 // Events
4377 obj.dispatch('ondeleterow', el, rowNumber, numOfRows, rowRecords);
4378 }
4379 } else {
4380 console.error('Jspreadsheet: It is not possible to delete the last row');
4381 }
4382 }
4383 }
4384
4385
4386 /**
4387 * Move column
4388 *
4389 * @return void
4390 */
4391 obj.moveColumn = function(o, d) {
4392 if (Object.keys(obj.options.mergeCells).length > 0) {
4393 if (o > d) {
4394 var insertBefore = 1;
4395 } else {
4396 var insertBefore = 0;
4397 }
4398
4399 if (obj.isColMerged(o).length || obj.isColMerged(d, insertBefore).length) {
4400 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4401 return false;
4402 } else {
4403 obj.destroyMerged();
4404 }
4405 }
4406 }
4407
4408 var o = parseInt(o);
4409 var d = parseInt(d);
4410
4411 if (o > d) {
4412 obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d]);
4413 obj.colgroupContainer.insertBefore(obj.colgroup[o], obj.colgroup[d]);
4414
4415 for (var j = 0; j < obj.rows.length; j++) {
4416 obj.rows[j].insertBefore(obj.records[j][o], obj.records[j][d]);
4417 }
4418 } else {
4419 obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d].nextSibling);
4420 obj.colgroupContainer.insertBefore(obj.colgroup[o], obj.colgroup[d].nextSibling);
4421
4422 for (var j = 0; j < obj.rows.length; j++) {
4423 obj.rows[j].insertBefore(obj.records[j][o], obj.records[j][d].nextSibling);
4424 }
4425 }
4426
4427 obj.options.columns.splice(d, 0, obj.options.columns.splice(o, 1)[0]);
4428 obj.headers.splice(d, 0, obj.headers.splice(o, 1)[0]);
4429 obj.colgroup.splice(d, 0, obj.colgroup.splice(o, 1)[0]);
4430
4431 for (var j = 0; j < obj.rows.length; j++) {
4432 obj.options.data[j].splice(d, 0, obj.options.data[j].splice(o, 1)[0]);
4433 obj.records[j].splice(d, 0, obj.records[j].splice(o, 1)[0]);
4434 }
4435
4436 // Update footers position
4437 if (obj.options.footers) {
4438 for (var j = 0; j < obj.options.footers.length; j++) {
4439 obj.options.footers[j].splice(d, 0, obj.options.footers[j].splice(o, 1)[0]);
4440 }
4441 }
4442
4443 // Keeping history of changes
4444 obj.setHistory({
4445 action:'moveColumn',
4446 oldValue: o,
4447 newValue: d,
4448 });
4449
4450 // Update table references
4451 obj.updateTableReferences();
4452
4453 // Events
4454 obj.dispatch('onmovecolumn', el, o, d);
4455 }
4456
4457 /**
4458 * Insert a new column
4459 *
4460 * @param mixed - num of columns to be added or data to be added in one single column
4461 * @param int columnNumber - number of columns to be created
4462 * @param bool insertBefore
4463 * @param object properties - column properties
4464 * @return void
4465 */
4466 obj.insertColumn = function(mixed, columnNumber, insertBefore, properties) {
4467 // Configuration
4468 if (obj.options.allowInsertColumn == true) {
4469 // Records
4470 var records = [];
4471
4472 // Data to be insert
4473 var data = [];
4474
4475 // The insert could be lead by number of rows or the array of data
4476 if (mixed > 0) {
4477 var numOfColumns = mixed;
4478 } else {
4479 var numOfColumns = 1;
4480
4481 if (mixed) {
4482 data = mixed;
4483 }
4484 }
4485
4486 // Direction
4487 var insertBefore = insertBefore ? true : false;
4488
4489 // Current column number
4490 var lastColumn = obj.options.columns.length - 1;
4491
4492 // Confirm position
4493 if (columnNumber == undefined || columnNumber >= parseInt(lastColumn) || columnNumber < 0) {
4494 columnNumber = lastColumn;
4495 }
4496
4497 // Onbeforeinsertcolumn
4498 if (obj.dispatch('onbeforeinsertcolumn', el, columnNumber, numOfColumns, insertBefore) === false) {
4499 return false;
4500 }
4501
4502 // Merged cells
4503 if (Object.keys(obj.options.mergeCells).length > 0) {
4504 if (obj.isColMerged(columnNumber, insertBefore).length) {
4505 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4506 return false;
4507 } else {
4508 obj.destroyMerged();
4509 }
4510 }
4511 }
4512
4513 // Create default properties
4514 if (! properties) {
4515 properties = [];
4516 }
4517
4518 for (var i = 0; i < numOfColumns; i++) {
4519 if (! properties[i]) {
4520 properties[i] = { type:'text', source:[], options:[], width:obj.options.defaultColWidth, align:obj.options.defaultColAlign };
4521 }
4522 }
4523
4524 // Insert before
4525 var columnIndex = (! insertBefore) ? columnNumber + 1 : columnNumber;
4526 obj.options.columns = jexcel.injectArray(obj.options.columns, columnIndex, properties);
4527
4528 // Open space in the containers
4529 var currentHeaders = obj.headers.splice(columnIndex);
4530 var currentColgroup = obj.colgroup.splice(columnIndex);
4531
4532 // History
4533 var historyHeaders = [];
4534 var historyColgroup = [];
4535 var historyRecords = [];
4536 var historyData = [];
4537 var historyFooters = [];
4538
4539 // Add new headers
4540 for (var col = columnIndex; col < (numOfColumns + columnIndex); col++) {
4541 obj.createCellHeader(col);
4542 obj.headerContainer.insertBefore(obj.headers[col], obj.headerContainer.children[col+1]);
4543 obj.colgroupContainer.insertBefore(obj.colgroup[col], obj.colgroupContainer.children[col+1]);
4544
4545 historyHeaders.push(obj.headers[col]);
4546 historyColgroup.push(obj.colgroup[col]);
4547 }
4548
4549 // Add new footer cells
4550 if (obj.options.footers) {
4551 for (var j = 0; j < obj.options.footers.length; j++) {
4552 historyFooters[j] = [];
4553 for (var i = 0; i < numOfColumns; i++) {
4554 historyFooters[j].push('');
4555 }
4556 obj.options.footers[j].splice(columnIndex, 0, historyFooters[j]);
4557 }
4558 }
4559
4560 // Adding visual columns
4561 for (var row = 0; row < obj.options.data.length; row++) {
4562 // Keep the current data
4563 var currentData = obj.options.data[row].splice(columnIndex);
4564 var currentRecord = obj.records[row].splice(columnIndex);
4565
4566 // History
4567 historyData[row] = [];
4568 historyRecords[row] = [];
4569
4570 for (var col = columnIndex; col < (numOfColumns + columnIndex); col++) {
4571 // New value
4572 var value = data[row] ? data[row] : '';
4573 obj.options.data[row][col] = value;
4574 // New cell
4575 var td = obj.createCell(col, row, obj.options.data[row][col]);
4576 obj.records[row][col] = td;
4577 // Add cell to the row
4578 if (obj.rows[row]) {
4579 obj.rows[row].insertBefore(td, obj.rows[row].children[col+1]);
4580 }
4581
4582 // Record History
4583 historyData[row].push(value);
4584 historyRecords[row].push(td);
4585 }
4586
4587 // Copy the data back to the main data
4588 Array.prototype.push.apply(obj.options.data[row], currentData);
4589 Array.prototype.push.apply(obj.records[row], currentRecord);
4590 }
4591
4592 Array.prototype.push.apply(obj.headers, currentHeaders);
4593 Array.prototype.push.apply(obj.colgroup, currentColgroup);
4594
4595 // Adjust nested headers
4596 if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
4597 // Flexible way to handle nestedheaders
4598 if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
4599 for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
4600 var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) + numOfColumns;
4601 obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
4602 obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
4603 var o = obj.thead.children[j].children[obj.thead.children[j].children.length-1].getAttribute('data-column');
4604 o = o.split(',');
4605 for (var col = columnIndex; col < (numOfColumns + columnIndex); col++) {
4606 o.push(col);
4607 }
4608 obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('data-column', o);
4609 }
4610 } else {
4611 var colspan = parseInt(obj.options.nestedHeaders[0].colspan) + numOfColumns;
4612 obj.options.nestedHeaders[0].colspan = colspan;
4613 obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
4614 }
4615 }
4616
4617 // Keep history
4618 obj.setHistory({
4619 action: 'insertColumn',
4620 columnNumber:columnNumber,
4621 numOfColumns:numOfColumns,
4622 insertBefore:insertBefore,
4623 columns:properties,
4624 headers:historyHeaders,
4625 colgroup:historyColgroup,
4626 records:historyRecords,
4627 footers:historyFooters,
4628 data:historyData,
4629 });
4630
4631 // Remove table references
4632 obj.updateTableReferences();
4633
4634 // Events
4635 obj.dispatch('oninsertcolumn', el, columnNumber, numOfColumns, historyRecords, insertBefore);
4636 }
4637 }
4638
4639 /**
4640 * Delete a column by number
4641 *
4642 * @param integer columnNumber - reference column to be excluded
4643 * @param integer numOfColumns - number of columns to be excluded from the reference column
4644 * @return void
4645 */
4646 obj.deleteColumn = function(columnNumber, numOfColumns) {
4647 // Global Configuration
4648 if (obj.options.allowDeleteColumn == true) {
4649 if (obj.headers.length > 1) {
4650 // Delete column definitions
4651 if (columnNumber == undefined) {
4652 var number = obj.getSelectedColumns(true);
4653
4654 if (! number.length) {
4655 // Remove last column
4656 columnNumber = obj.headers.length - 1;
4657 numOfColumns = 1;
4658 } else {
4659 // Remove selected
4660 columnNumber = parseInt(number[0]);
4661 numOfColumns = parseInt(number.length);
4662 }
4663 }
4664
4665 // Lasat column
4666 var lastColumn = obj.options.data[0].length - 1;
4667
4668 if (columnNumber == undefined || columnNumber > lastColumn || columnNumber < 0) {
4669 columnNumber = lastColumn;
4670 }
4671
4672 // Minimum of columns to be delete is 1
4673 if (! numOfColumns) {
4674 numOfColumns = 1;
4675 }
4676
4677 // Can't delete more than the limit of the table
4678 if (numOfColumns > obj.options.data[0].length - columnNumber) {
4679 numOfColumns = obj.options.data[0].length - columnNumber;
4680 }
4681
4682 // onbeforedeletecolumn
4683 if (obj.dispatch('onbeforedeletecolumn', el, columnNumber, numOfColumns) === false) {
4684 return false;
4685 }
4686
4687 // Can't remove the last column
4688 if (parseInt(columnNumber) > -1) {
4689 // Merged cells
4690 var mergeExists = false;
4691 if (Object.keys(obj.options.mergeCells).length > 0) {
4692 for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
4693 if (obj.isColMerged(col, false).length) {
4694 mergeExists = true;
4695 }
4696 }
4697 }
4698 if (mergeExists) {
4699 if (! confirm(obj.options.text.thisActionWillDestroyAnyExistingMergedCellsAreYouSure)) {
4700 return false;
4701 } else {
4702 obj.destroyMerged();
4703 }
4704 }
4705
4706 // Delete the column properties
4707 var columns = obj.options.columns.splice(columnNumber, numOfColumns);
4708
4709 for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
4710 obj.colgroup[col].className = '';
4711 obj.headers[col].className = '';
4712 obj.colgroup[col].parentNode.removeChild(obj.colgroup[col]);
4713 obj.headers[col].parentNode.removeChild(obj.headers[col]);
4714 }
4715
4716 var historyHeaders = obj.headers.splice(columnNumber, numOfColumns);
4717 var historyColgroup = obj.colgroup.splice(columnNumber, numOfColumns);
4718 var historyRecords = [];
4719 var historyData = [];
4720 var historyFooters = [];
4721
4722 for (var row = 0; row < obj.options.data.length; row++) {
4723 for (var col = columnNumber; col < columnNumber + numOfColumns; col++) {
4724 obj.records[row][col].className = '';
4725 obj.records[row][col].parentNode.removeChild(obj.records[row][col]);
4726 }
4727 }
4728
4729 // Delete headers
4730 for (var row = 0; row < obj.options.data.length; row++) {
4731 // History
4732 historyData[row] = obj.options.data[row].splice(columnNumber, numOfColumns);
4733 historyRecords[row] = obj.records[row].splice(columnNumber, numOfColumns);
4734 }
4735
4736 // Delete footers
4737 if (obj.options.footers) {
4738 for (var row = 0; row < obj.options.footers.length; row++) {
4739 historyFooters[row] = obj.options.footers[row].splice(columnNumber, numOfColumns);
4740 }
4741 }
4742
4743 // Remove selection
4744 obj.conditionalSelectionUpdate(0, columnNumber, (columnNumber + numOfColumns) - 1);
4745
4746 // Adjust nested headers
4747 if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
4748 // Flexible way to handle nestedheaders
4749 if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
4750 for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
4751 var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) - numOfColumns;
4752 obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
4753 obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
4754 }
4755 } else {
4756 var colspan = parseInt(obj.options.nestedHeaders[0].colspan) - numOfColumns;
4757 obj.options.nestedHeaders[0].colspan = colspan;
4758 obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
4759 }
4760 }
4761
4762 // Keeping history of changes
4763 obj.setHistory({
4764 action:'deleteColumn',
4765 columnNumber:columnNumber,
4766 numOfColumns:numOfColumns,
4767 insertBefore: 1,
4768 columns:columns,
4769 headers:historyHeaders,
4770 colgroup:historyColgroup,
4771 records:historyRecords,
4772 footers:historyFooters,
4773 data:historyData,
4774 });
4775
4776 // Update table references
4777 obj.updateTableReferences();
4778
4779 // Delete
4780 obj.dispatch('ondeletecolumn', el, columnNumber, numOfColumns, historyRecords);
4781 }
4782 } else {
4783 console.error('Jspreadsheet: It is not possible to delete the last column');
4784 }
4785 }
4786 }
4787
4788 /**
4789 * Get selected rows numbers
4790 *
4791 * @return array
4792 */
4793 obj.getSelectedRows = function(asIds) {
4794 var rows = [];
4795 // Get all selected rows
4796 for (var j = 0; j < obj.rows.length; j++) {
4797 if (obj.rows[j].classList.contains('selected')) {
4798 if (asIds) {
4799 rows.push(j);
4800 } else {
4801 rows.push(obj.rows[j]);
4802 }
4803 }
4804 }
4805
4806 return rows;
4807 },
4808
4809 /**
4810 * Get selected column numbers
4811 *
4812 * @return array
4813 */
4814 obj.getSelectedColumns = function() {
4815 var cols = [];
4816 // Get all selected cols
4817 for (var i = 0; i < obj.headers.length; i++) {
4818 if (obj.headers[i].classList.contains('selected')) {
4819 cols.push(i);
4820 }
4821 }
4822
4823 return cols;
4824 }
4825
4826 /**
4827 * Get highlighted
4828 *
4829 * @return array
4830 */
4831 obj.getHighlighted = function() {
4832 return obj.highlighted;
4833 }
4834
4835 /**
4836 * Update cell references
4837 *
4838 * @return void
4839 */
4840 obj.updateTableReferences = function() {
4841 // Update headers
4842 for (var i = 0; i < obj.headers.length; i++) {
4843 var x = obj.headers[i].getAttribute('data-x');
4844
4845 if (x != i) {
4846 // Update coords
4847 obj.headers[i].setAttribute('data-x', i);
4848 // Title
4849 if (! obj.headers[i].getAttribute('title')) {
4850 obj.headers[i].innerHTML = jexcel.getColumnName(i);
4851 }
4852 }
4853 }
4854
4855 // Update all rows
4856 for (var j = 0; j < obj.rows.length; j++) {
4857 if (obj.rows[j]) {
4858 var y = obj.rows[j].getAttribute('data-y');
4859
4860 if (y != j) {
4861 // Update coords
4862 obj.rows[j].setAttribute('data-y', j);
4863 obj.rows[j].children[0].setAttribute('data-y', j);
4864 // Row number
4865 obj.rows[j].children[0].innerHTML = j + 1;
4866 }
4867 }
4868 }
4869
4870 // Regular cells affected by this change
4871 var affectedTokens = [];
4872 var mergeCellUpdates = [];
4873
4874 // Update cell
4875 var updatePosition = function(x,y,i,j) {
4876 if (x != i) {
4877 obj.records[j][i].setAttribute('data-x', i);
4878 }
4879 if (y != j) {
4880 obj.records[j][i].setAttribute('data-y', j);
4881 }
4882
4883 // Other updates
4884 if (x != i || y != j) {
4885 var columnIdFrom = jexcel.getColumnNameFromId([x, y]);
4886 var columnIdTo = jexcel.getColumnNameFromId([i, j]);
4887 affectedTokens[columnIdFrom] = columnIdTo;
4888 }
4889 }
4890
4891 for (var j = 0; j < obj.records.length; j++) {
4892 for (var i = 0; i < obj.records[0].length; i++) {
4893 if (obj.records[j][i]) {
4894 // Current values
4895 var x = obj.records[j][i].getAttribute('data-x');
4896 var y = obj.records[j][i].getAttribute('data-y');
4897
4898 // Update column
4899 if (obj.records[j][i].getAttribute('data-merged')) {
4900 var columnIdFrom = jexcel.getColumnNameFromId([x, y]);
4901 var columnIdTo = jexcel.getColumnNameFromId([i, j]);
4902 if (mergeCellUpdates[columnIdFrom] == null) {
4903 if (columnIdFrom == columnIdTo) {
4904 mergeCellUpdates[columnIdFrom] = false;
4905 } else {
4906 var totalX = parseInt(i - x);
4907 var totalY = parseInt(j - y);
4908 mergeCellUpdates[columnIdFrom] = [ columnIdTo, totalX, totalY ];
4909 }
4910 }
4911 } else {
4912 updatePosition(x,y,i,j);
4913 }
4914 }
4915 }
4916 }
4917
4918 // Update merged if applicable
4919 var keys = Object.keys(mergeCellUpdates);
4920 if (keys.length) {
4921 for (var i = 0; i < keys.length; i++) {
4922 if (mergeCellUpdates[keys[i]]) {
4923 var info = jexcel.getIdFromColumnName(keys[i], true)
4924 var x = info[0];
4925 var y = info[1];
4926 updatePosition(x,y,x + mergeCellUpdates[keys[i]][1],y + mergeCellUpdates[keys[i]][2]);
4927
4928 var columnIdFrom = keys[i];
4929 var columnIdTo = mergeCellUpdates[keys[i]][0];
4930 for (var j = 0; j < obj.options.mergeCells[columnIdFrom][2].length; j++) {
4931 var x = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-x'));
4932 var y = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-y'));
4933 obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-x', x + mergeCellUpdates[keys[i]][1]);
4934 obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-y', y + mergeCellUpdates[keys[i]][2]);
4935 }
4936
4937 obj.options.mergeCells[columnIdTo] = obj.options.mergeCells[columnIdFrom];
4938 delete(obj.options.mergeCells[columnIdFrom]);
4939 }
4940 }
4941 }
4942
4943 // Update formulas
4944 obj.updateFormulas(affectedTokens);
4945
4946 // Update meta data
4947 obj.updateMeta(affectedTokens);
4948
4949 // Refresh selection
4950 obj.refreshSelection();
4951
4952 // Update table with custom configuration if applicable
4953 obj.updateTable();
4954 }
4955
4956 /**
4957 * Custom settings for the cells
4958 */
4959 obj.updateTable = function() {
4960 // Check for spare
4961 if (obj.options.minSpareRows > 0) {
4962 var numBlankRows = 0;
4963 for (var j = obj.rows.length - 1; j >= 0; j--) {
4964 var test = false;
4965 for (var i = 0; i < obj.headers.length; i++) {
4966 if (obj.options.data[j][i]) {
4967 test = true;
4968 }
4969 }
4970 if (test) {
4971 break;
4972 } else {
4973 numBlankRows++;
4974 }
4975 }
4976
4977 if (obj.options.minSpareRows - numBlankRows > 0) {
4978 obj.insertRow(obj.options.minSpareRows - numBlankRows)
4979 }
4980 }
4981
4982 if (obj.options.minSpareCols > 0) {
4983 var numBlankCols = 0;
4984 for (var i = obj.headers.length - 1; i >= 0 ; i--) {
4985 var test = false;
4986 for (var j = 0; j < obj.rows.length; j++) {
4987 if (obj.options.data[j][i]) {
4988 test = true;
4989 }
4990 }
4991 if (test) {
4992 break;
4993 } else {
4994 numBlankCols++;
4995 }
4996 }
4997
4998 if (obj.options.minSpareCols - numBlankCols > 0) {
4999 obj.insertColumn(obj.options.minSpareCols - numBlankCols)
5000 }
5001 }
5002
5003 // Customizations by the developer
5004 if (typeof(obj.options.updateTable) == 'function') {
5005 if (obj.options.detachForUpdates) {
5006 el.removeChild(obj.content);
5007 }
5008
5009 for (var j = 0; j < obj.rows.length; j++) {
5010 for (var i = 0; i < obj.headers.length; i++) {
5011 obj.options.updateTable(el, obj.records[j][i], i, j, obj.options.data[j][i], obj.records[j][i].textContent, jexcel.getColumnNameFromId([i, j]));
5012 }
5013 }
5014
5015 if (obj.options.detachForUpdates) {
5016 el.insertBefore(obj.content, obj.pagination);
5017 }
5018 }
5019
5020 // Update footers
5021 if (obj.options.footers) {
5022 obj.setFooter();
5023 }
5024
5025 // Update corner position
5026 setTimeout(function() {
5027 obj.updateCornerPosition();
5028 },0);
5029 }
5030
5031 /**
5032 * Readonly
5033 */
5034 obj.isReadOnly = function(cell) {
5035 if (cell = obj.getCell(cell)) {
5036 return cell.classList.contains('readonly') ? true : false;
5037 }
5038 }
5039
5040 /**
5041 * Readonly
5042 */
5043 obj.setReadOnly = function(cell, state) {
5044 if (cell = obj.getCell(cell)) {
5045 if (state) {
5046 cell.classList.add('readonly');
5047 } else {
5048 cell.classList.remove('readonly');
5049 }
5050 }
5051 }
5052
5053 /**
5054 * Show row
5055 */
5056 obj.showRow = function(rowNumber) {
5057 obj.rows[rowNumber].style.display = '';
5058 }
5059
5060 /**
5061 * Hide row
5062 */
5063 obj.hideRow = function(rowNumber) {
5064 obj.rows[rowNumber].style.display = 'none';
5065 }
5066
5067 /**
5068 * Show column
5069 */
5070 obj.showColumn = function(colNumber) {
5071 obj.headers[colNumber].style.display = '';
5072 obj.colgroup[colNumber].style.display = '';
5073 if (obj.filter && obj.filter.children.length > colNumber + 1) {
5074 obj.filter.children[colNumber + 1].style.display = '';
5075 }
5076 for (var j = 0; j < obj.options.data.length; j++) {
5077 obj.records[j][colNumber].style.display = '';
5078 }
5079
5080 // Update footers
5081 if (obj.options.footers) {
5082 obj.setFooter();
5083 }
5084
5085 obj.resetSelection();
5086 }
5087
5088 /**
5089 * Hide column
5090 */
5091 obj.hideColumn = function(colNumber) {
5092 obj.headers[colNumber].style.display = 'none';
5093 obj.colgroup[colNumber].style.display = 'none';
5094 if (obj.filter && obj.filter.children.length > colNumber + 1) {
5095 obj.filter.children[colNumber + 1].style.display = 'none';
5096 }
5097 for (var j = 0; j < obj.options.data.length; j++) {
5098 obj.records[j][colNumber].style.display = 'none';
5099 }
5100
5101 // Update footers
5102 if (obj.options.footers) {
5103 obj.setFooter();
5104 }
5105
5106 obj.resetSelection();
5107 }
5108
5109 /**
5110 * Show index column
5111 */
5112 obj.showIndex = function() {
5113 obj.table.classList.remove('jexcel_hidden_index');
5114 }
5115
5116 /**
5117 * Hide index column
5118 */
5119 obj.hideIndex = function() {
5120 obj.table.classList.add('jexcel_hidden_index');
5121 }
5122
5123 /**
5124 * Update all related cells in the chain
5125 */
5126 var chainLoopProtection = [];
5127
5128 obj.updateFormulaChain = function(x, y, records) {
5129 var cellId = jexcel.getColumnNameFromId([x, y]);
5130 if (obj.formula[cellId] && obj.formula[cellId].length > 0) {
5131 if (chainLoopProtection[cellId]) {
5132 obj.records[y][x].innerHTML = '#ERROR';
5133 obj.formula[cellId] = '';
5134 } else {
5135 // Protection
5136 chainLoopProtection[cellId] = true;
5137
5138 for (var i = 0; i < obj.formula[cellId].length; i++) {
5139 var cell = jexcel.getIdFromColumnName(obj.formula[cellId][i], true);
5140 // Update cell
5141 var value = ''+obj.options.data[cell[1]][cell[0]];
5142 if (value.substr(0,1) == '=') {
5143 records.push(obj.updateCell(cell[0], cell[1], value, true));
5144 } else {
5145 // No longer a formula, remove from the chain
5146 Object.keys(obj.formula)[i] = null;
5147 }
5148 obj.updateFormulaChain(cell[0], cell[1], records);
5149 }
5150 }
5151 }
5152
5153 chainLoopProtection = [];
5154 }
5155
5156 /**
5157 * Update formulas
5158 */
5159 obj.updateFormulas = function(referencesToUpdate) {
5160 // Update formulas
5161 for (var j = 0; j < obj.options.data.length; j++) {
5162 for (var i = 0; i < obj.options.data[0].length; i++) {
5163 var value = '' + obj.options.data[j][i];
5164 // Is formula
5165 if (value.substr(0,1) == '=') {
5166 // Replace tokens
5167 var newFormula = obj.updateFormula(value, referencesToUpdate);
5168 if (newFormula != value) {
5169 /* TablePress: Only setting the value is not enough. updateCell is needed to update the DOM. */
5170 //obj.options.data[j][i] = newFormula;
5171 obj.updateCell( i, j, newFormula );
5172 }
5173 }
5174 }
5175 }
5176
5177 // Update formula chain
5178 var formula = [];
5179 var keys = Object.keys(obj.formula);
5180 for (var j = 0; j < keys.length; j++) {
5181 // Current key and values
5182 var key = keys[j];
5183 var value = obj.formula[key];
5184 // Update key
5185 if (referencesToUpdate[key]) {
5186 key = referencesToUpdate[key];
5187 }
5188 // Update values
5189 formula[key] = [];
5190 for (var i = 0; i < value.length; i++) {
5191 var letter = value[i];
5192 if (referencesToUpdate[letter]) {
5193 letter = referencesToUpdate[letter];
5194 }
5195 formula[key].push(letter);
5196 }
5197 }
5198 obj.formula = formula;
5199 }
5200
5201 /**
5202 * Update formula
5203 *
5204 * TablePress @TODO: This currently updates all references, including those outside of {} delimiters.
5205 * Replace this with the code of the existing implementation.
5206 */
5207 obj.updateFormula = function(formula, referencesToUpdate) {
5208 var testLetter = /[A-Z]/;
5209 var testNumber = /[0-9]/;
5210
5211 var newFormula = '';
5212 var letter = null;
5213 var number = null;
5214 var token = '';
5215
5216 for (var index = 0; index < formula.length; index++) {
5217 if (testLetter.exec(formula[index])) {
5218 letter = 1;
5219 number = 0;
5220 token += formula[index];
5221 } else if (testNumber.exec(formula[index])) {
5222 number = letter ? 1 : 0;
5223 token += formula[index];
5224 } else {
5225 if (letter && number) {
5226 token = referencesToUpdate[token] ? referencesToUpdate[token] : token;
5227 }
5228 newFormula += token;
5229 newFormula += formula[index];
5230 letter = 0;
5231 number = 0;
5232 token = '';
5233 }
5234 }
5235
5236 if (token) {
5237 if (letter && number) {
5238 token = referencesToUpdate[token] ? referencesToUpdate[token] : token;
5239 }
5240 newFormula += token;
5241 }
5242
5243 return newFormula;
5244 }
5245
5246 /**
5247 * Secure formula
5248 */
5249 var secureFormula = function(oldValue) {
5250 var newValue = '';
5251 var inside = 0;
5252
5253 for (var i = 0; i < oldValue.length; i++) {
5254 if (oldValue[i] == '"') {
5255 if (inside == 0) {
5256 inside = 1;
5257 } else {
5258 inside = 0;
5259 }
5260 }
5261
5262 if (inside == 1) {
5263 newValue += oldValue[i];
5264 } else {
5265 newValue += oldValue[i].toUpperCase();
5266 }
5267 }
5268
5269 return newValue;
5270 }
5271
5272 /**
5273 * Parse formulas
5274 */
5275 obj.executeFormula = function(expression, x, y) {
5276
5277 var formulaResults = [];
5278 var formulaLoopProtection = [];
5279
5280 // Execute formula with loop protection
5281 var execute = function(expression, x, y) {
5282 // Parent column identification
5283 var parentId = jexcel.getColumnNameFromId([x, y]);
5284
5285 // Code protection
5286 if (formulaLoopProtection[parentId]) {
5287 console.error('Reference loop detected');
5288 return '#ERROR';
5289 }
5290
5291 formulaLoopProtection[parentId] = true;
5292
5293 // Convert range tokens
5294 var tokensUpdate = function(tokens) {
5295 for (var index = 0; index < tokens.length; index++) {
5296 var f = [];
5297 var token = tokens[index].split(':');
5298 var e1 = jexcel.getIdFromColumnName(token[0], true);
5299 var e2 = jexcel.getIdFromColumnName(token[1], true);
5300
5301 if (e1[0] <= e2[0]) {
5302 var x1 = e1[0];
5303 var x2 = e2[0];
5304 } else {
5305 var x1 = e2[0];
5306 var x2 = e1[0];
5307 }
5308
5309 if (e1[1] <= e2[1]) {
5310 var y1 = e1[1];
5311 var y2 = e2[1];
5312 } else {
5313 var y1 = e2[1];
5314 var y2 = e1[1];
5315 }
5316
5317 for (var j = y1; j <= y2; j++) {
5318 for (var i = x1; i <= x2; i++) {
5319 f.push(jexcel.getColumnNameFromId([i, j]));
5320 }
5321 }
5322
5323 expression = expression.replace(tokens[index], f.join(','));
5324 }
5325 }
5326
5327 // Range with $ remove $
5328 expression = expression.replace(/\$?([A-Z]+)\$?([0-9]+)/g, "$1$2");
5329
5330 var tokens = expression.match(/([A-Z]+[0-9]+)\:([A-Z]+[0-9]+)/g);
5331 if (tokens && tokens.length) {
5332 tokensUpdate(tokens);
5333 }
5334
5335 // Get tokens
5336 var tokens = expression.match(/([A-Z]+[0-9]+)/g);
5337
5338 // Direct self-reference protection
5339 if (tokens && tokens.indexOf(parentId) > -1) {
5340 console.error('Self Reference detected');
5341 return '#ERROR';
5342 } else {
5343 // Expressions to be used in the parsing
5344 var formulaExpressions = {};
5345
5346 if (tokens) {
5347 for (var i = 0; i < tokens.length; i++) {
5348 // Keep chain
5349 if (! obj.formula[tokens[i]]) {
5350 obj.formula[tokens[i]] = [];
5351 }
5352 // Is already in the register
5353 if (obj.formula[tokens[i]].indexOf(parentId) < 0) {
5354 obj.formula[tokens[i]].push(parentId);
5355 }
5356
5357 // Do not calculate again
5358 if (eval('typeof(' + tokens[i] + ') == "undefined"')) {
5359 // Coords
5360 var position = jexcel.getIdFromColumnName(tokens[i], 1);
5361 // Get value
5362 if (typeof(obj.options.data[position[1]]) != 'undefined' && typeof(obj.options.data[position[1]][position[0]]) != 'undefined') {
5363 var value = obj.options.data[position[1]][position[0]];
5364 } else {
5365 var value = '';
5366 }
5367 // Get column data
5368 if ((''+value).substr(0,1) == '=') {
5369 if (typeof formulaResults[tokens[i]] !== 'undefined') {
5370 value = formulaResults[tokens[i]];
5371 } else {
5372 value = execute(value, position[0], position[1]);
5373 formulaResults[tokens[i]] = value;
5374 }
5375 }
5376 // Type!
5377 if ((''+value).trim() == '') {
5378 // Null
5379 formulaExpressions[tokens[i]] = null;
5380 } else {
5381 if (value == Number(value) && obj.options.autoCasting == true) {
5382 // Number
5383 formulaExpressions[tokens[i]] = Number(value);
5384 } else {
5385 // Trying any formatted number
5386 var number = obj.parseNumber(value, position[0])
5387 if (obj.options.autoCasting == true && number) {
5388 formulaExpressions[tokens[i]] = number;
5389 } else {
5390 formulaExpressions[tokens[i]] = '"' + value + '"';
5391 }
5392 }
5393 }
5394 }
5395 }
5396 }
5397
5398 // Convert formula to javascript
5399 try {
5400 var res = jexcel.formula(expression.substr(1), formulaExpressions, x, y, obj);
5401 } catch (e) {
5402 var res = '#ERROR';
5403 console.log(e)
5404 }
5405
5406 return res;
5407 }
5408 }
5409
5410 return execute(expression, x, y);
5411 }
5412
5413 /**
5414 * Trying to extract a number from a string
5415 */
5416 obj.parseNumber = function(value, columnNumber) {
5417 // Decimal point
5418 var decimal = columnNumber && obj.options.columns[columnNumber].decimal ? obj.options.columns[columnNumber].decimal : '.';
5419
5420 // Parse both parts of the number
5421 var number = ('' + value);
5422 number = number.split(decimal);
5423 number[0] = number[0].match(/[+-]?[0-9]/g);
5424 if (number[0]) {
5425 number[0] = number[0].join('');
5426 }
5427 if (number[1]) {
5428 number[1] = number[1].match(/[0-9]*/g).join('');
5429 }
5430
5431 // Is a valid number
5432 if (number[0] && Number.isInteger(Number(number[0]))) {
5433 if (! number[1]) {
5434 var value = Number(number[0] + '.00');
5435 } else {
5436 var value = Number(number[0] + '.' + number[1]);
5437 }
5438 } else {
5439 var value = null;
5440 }
5441
5442 return value;
5443 }
5444
5445 /**
5446 * Get row number
5447 */
5448 obj.row = function(cell) {
5449 }
5450
5451 /**
5452 * Get col number
5453 */
5454 obj.col = function(cell) {
5455 }
5456
5457 obj.up = function(shiftKey, ctrlKey) {
5458 if (shiftKey) {
5459 if (obj.selectedCell[3] > 0) {
5460 obj.up.visible(1, ctrlKey ? 0 : 1)
5461 }
5462 } else {
5463 if (obj.selectedCell[1] > 0) {
5464 obj.up.visible(0, ctrlKey ? 0 : 1)
5465 }
5466 obj.selectedCell[2] = obj.selectedCell[0];
5467 obj.selectedCell[3] = obj.selectedCell[1];
5468 }
5469
5470 // Update selection
5471 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5472
5473 // Change page
5474 if (obj.options.lazyLoading == true) {
5475 if (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0) {
5476 obj.loadPage(0);
5477 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5478 } else {
5479 if (obj.loadValidation()) {
5480 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5481 } else {
5482 var item = parseInt(obj.tbody.firstChild.getAttribute('data-y'));
5483 if (obj.selectedCell[1] - item < 30) {
5484 obj.loadUp();
5485 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5486 }
5487 }
5488 }
5489 } else if (obj.options.pagination > 0) {
5490 var pageNumber = obj.whichPage(obj.selectedCell[3]);
5491 if (pageNumber != obj.pageNumber) {
5492 obj.page(pageNumber);
5493 }
5494 }
5495
5496 obj.updateScroll(1);
5497 }
5498
5499 obj.up.visible = function(group, direction) {
5500 if (group == 0) {
5501 var x = parseInt(obj.selectedCell[0]);
5502 var y = parseInt(obj.selectedCell[1]);
5503 } else {
5504 var x = parseInt(obj.selectedCell[2]);
5505 var y = parseInt(obj.selectedCell[3]);
5506 }
5507
5508 if (direction == 0) {
5509 for (var j = 0; j < y; j++) {
5510 if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
5511 y = j;
5512 break;
5513 }
5514 }
5515 } else {
5516 y = obj.up.get(x, y);
5517 }
5518
5519 if (group == 0) {
5520 obj.selectedCell[0] = x;
5521 obj.selectedCell[1] = y;
5522 } else {
5523 obj.selectedCell[2] = x;
5524 obj.selectedCell[3] = y;
5525 }
5526 }
5527
5528 obj.up.get = function(x, y) {
5529 var x = parseInt(x);
5530 var y = parseInt(y);
5531 for (var j = (y - 1); j >= 0; j--) {
5532 if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
5533 if (obj.records[j][x].getAttribute('data-merged')) {
5534 if (obj.records[j][x] == obj.records[y][x]) {
5535 continue;
5536 }
5537 }
5538 y = j;
5539 break;
5540 }
5541 }
5542
5543 return y;
5544 }
5545
5546 obj.down = function(shiftKey, ctrlKey) {
5547 if (shiftKey) {
5548 if (obj.selectedCell[3] < obj.records.length - 1) {
5549 obj.down.visible(1, ctrlKey ? 0 : 1)
5550 }
5551 } else {
5552 if (obj.selectedCell[1] < obj.records.length - 1) {
5553 obj.down.visible(0, ctrlKey ? 0 : 1)
5554 }
5555 obj.selectedCell[2] = obj.selectedCell[0];
5556 obj.selectedCell[3] = obj.selectedCell[1];
5557 }
5558
5559 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5560
5561 // Change page
5562 if (obj.options.lazyLoading == true) {
5563 if ((obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1)) {
5564 obj.loadPage(-1);
5565 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5566 } else {
5567 if (obj.loadValidation()) {
5568 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5569 } else {
5570 var item = parseInt(obj.tbody.lastChild.getAttribute('data-y'));
5571 if (item - obj.selectedCell[3] < 30) {
5572 obj.loadDown();
5573 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5574 }
5575 }
5576 }
5577 } else if (obj.options.pagination > 0) {
5578 var pageNumber = obj.whichPage(obj.selectedCell[3]);
5579 if (pageNumber != obj.pageNumber) {
5580 obj.page(pageNumber);
5581 }
5582 }
5583
5584 obj.updateScroll(3);
5585 }
5586
5587 obj.down.visible = function(group, direction) {
5588 if (group == 0) {
5589 var x = parseInt(obj.selectedCell[0]);
5590 var y = parseInt(obj.selectedCell[1]);
5591 } else {
5592 var x = parseInt(obj.selectedCell[2]);
5593 var y = parseInt(obj.selectedCell[3]);
5594 }
5595
5596 if (direction == 0) {
5597 for (var j = obj.rows.length - 1; j > y; j--) {
5598 if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
5599 y = j;
5600 break;
5601 }
5602 }
5603 } else {
5604 y = obj.down.get(x, y);
5605 }
5606
5607 if (group == 0) {
5608 obj.selectedCell[0] = x;
5609 obj.selectedCell[1] = y;
5610 } else {
5611 obj.selectedCell[2] = x;
5612 obj.selectedCell[3] = y;
5613 }
5614 }
5615
5616 obj.down.get = function(x, y) {
5617 var x = parseInt(x);
5618 var y = parseInt(y);
5619 for (var j = (y + 1); j < obj.rows.length; j++) {
5620 if (obj.records[j][x].style.display != 'none' && obj.rows[j].style.display != 'none') {
5621 if (obj.records[j][x].getAttribute('data-merged')) {
5622 if (obj.records[j][x] == obj.records[y][x]) {
5623 continue;
5624 }
5625 }
5626 y = j;
5627 break;
5628 }
5629 }
5630
5631 return y;
5632 }
5633
5634 obj.right = function(shiftKey, ctrlKey) {
5635 if (shiftKey) {
5636 if (obj.selectedCell[2] < obj.headers.length - 1) {
5637 obj.right.visible(1, ctrlKey ? 0 : 1)
5638 }
5639 } else {
5640 if (obj.selectedCell[0] < obj.headers.length - 1) {
5641 obj.right.visible(0, ctrlKey ? 0 : 1)
5642 }
5643 obj.selectedCell[2] = obj.selectedCell[0];
5644 obj.selectedCell[3] = obj.selectedCell[1];
5645 }
5646
5647 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5648 obj.updateScroll(2);
5649 }
5650
5651 obj.right.visible = function(group, direction) {
5652 if (group == 0) {
5653 var x = parseInt(obj.selectedCell[0]);
5654 var y = parseInt(obj.selectedCell[1]);
5655 } else {
5656 var x = parseInt(obj.selectedCell[2]);
5657 var y = parseInt(obj.selectedCell[3]);
5658 }
5659
5660 if (direction == 0) {
5661 for (var i = obj.headers.length - 1; i > x; i--) {
5662 if (obj.records[y][i].style.display != 'none') {
5663 x = i;
5664 break;
5665 }
5666 }
5667 } else {
5668 x = obj.right.get(x, y);
5669 }
5670
5671 if (group == 0) {
5672 obj.selectedCell[0] = x;
5673 obj.selectedCell[1] = y;
5674 } else {
5675 obj.selectedCell[2] = x;
5676 obj.selectedCell[3] = y;
5677 }
5678 }
5679
5680 obj.right.get = function(x, y) {
5681 var x = parseInt(x);
5682 var y = parseInt(y);
5683
5684 for (var i = (x + 1); i < obj.headers.length; i++) {
5685 if (obj.records[y][i].style.display != 'none') {
5686 if (obj.records[y][i].getAttribute('data-merged')) {
5687 if (obj.records[y][i] == obj.records[y][x]) {
5688 continue;
5689 }
5690 }
5691 x = i;
5692 break;
5693 }
5694 }
5695
5696 return x;
5697 }
5698
5699 obj.left = function(shiftKey, ctrlKey) {
5700 if (shiftKey) {
5701 if (obj.selectedCell[2] > 0) {
5702 obj.left.visible(1, ctrlKey ? 0 : 1)
5703 }
5704 } else {
5705 if (obj.selectedCell[0] > 0) {
5706 obj.left.visible(0, ctrlKey ? 0 : 1)
5707 }
5708 obj.selectedCell[2] = obj.selectedCell[0];
5709 obj.selectedCell[3] = obj.selectedCell[1];
5710 }
5711
5712 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5713 obj.updateScroll(0);
5714 }
5715
5716 obj.left.visible = function(group, direction) {
5717 if (group == 0) {
5718 var x = parseInt(obj.selectedCell[0]);
5719 var y = parseInt(obj.selectedCell[1]);
5720 } else {
5721 var x = parseInt(obj.selectedCell[2]);
5722 var y = parseInt(obj.selectedCell[3]);
5723 }
5724
5725 if (direction == 0) {
5726 for (var i = 0; i < x; i++) {
5727 if (obj.records[y][i].style.display != 'none') {
5728 x = i;
5729 break;
5730 }
5731 }
5732 } else {
5733 x = obj.left.get(x, y);
5734 }
5735
5736 if (group == 0) {
5737 obj.selectedCell[0] = x;
5738 obj.selectedCell[1] = y;
5739 } else {
5740 obj.selectedCell[2] = x;
5741 obj.selectedCell[3] = y;
5742 }
5743 }
5744
5745 obj.left.get = function(x, y) {
5746 var x = parseInt(x);
5747 var y = parseInt(y);
5748 for (var i = (x - 1); i >= 0; i--) {
5749 if (obj.records[y][i].style.display != 'none') {
5750 if (obj.records[y][i].getAttribute('data-merged')) {
5751 if (obj.records[y][i] == obj.records[y][x]) {
5752 continue;
5753 }
5754 }
5755 x = i;
5756 break;
5757 }
5758 }
5759
5760 return x;
5761 }
5762
5763 obj.first = function(shiftKey, ctrlKey) {
5764 if (shiftKey) {
5765 if (ctrlKey) {
5766 obj.selectedCell[3] = 0;
5767 } else {
5768 obj.left.visible(1, 0);
5769 }
5770 } else {
5771 if (ctrlKey) {
5772 obj.selectedCell[1] = 0;
5773 } else {
5774 obj.left.visible(0, 0);
5775 }
5776 obj.selectedCell[2] = obj.selectedCell[0];
5777 obj.selectedCell[3] = obj.selectedCell[1];
5778 }
5779
5780 // Change page
5781 if (obj.options.lazyLoading == true && (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0)) {
5782 obj.loadPage(0);
5783 } else if (obj.options.pagination > 0) {
5784 var pageNumber = obj.whichPage(obj.selectedCell[3]);
5785 if (pageNumber != obj.pageNumber) {
5786 obj.page(pageNumber);
5787 }
5788 }
5789
5790 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5791 obj.updateScroll(1);
5792 }
5793
5794 obj.last = function(shiftKey, ctrlKey) {
5795 if (shiftKey) {
5796 if (ctrlKey) {
5797 obj.selectedCell[3] = obj.records.length - 1;
5798 } else {
5799 obj.right.visible(1, 0);
5800 }
5801 } else {
5802 if (ctrlKey) {
5803 obj.selectedCell[1] = obj.records.length - 1;
5804 } else {
5805 obj.right.visible(0, 0);
5806 }
5807 obj.selectedCell[2] = obj.selectedCell[0];
5808 obj.selectedCell[3] = obj.selectedCell[1];
5809 }
5810
5811 // Change page
5812 if (obj.options.lazyLoading == true && (obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1)) {
5813 obj.loadPage(-1);
5814 } else if (obj.options.pagination > 0) {
5815 var pageNumber = obj.whichPage(obj.selectedCell[3]);
5816 if (pageNumber != obj.pageNumber) {
5817 obj.page(pageNumber);
5818 }
5819 }
5820
5821 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5822 obj.updateScroll(3);
5823 }
5824
5825 obj.selectAll = function() {
5826 if (! obj.selectedCell) {
5827 obj.selectedCell = [];
5828 }
5829
5830 obj.selectedCell[0] = 0;
5831 obj.selectedCell[1] = 0;
5832 obj.selectedCell[2] = obj.headers.length - 1;
5833 obj.selectedCell[3] = obj.records.length - 1;
5834
5835 obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]);
5836 }
5837
5838 /**
5839 * Go to a page in a lazyLoading
5840 */
5841 obj.loadPage = function(pageNumber) {
5842 // Search
5843 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5844 var results = obj.results;
5845 } else {
5846 var results = obj.rows;
5847 }
5848
5849 // Per page
5850 var quantityPerPage = 100;
5851
5852 // pageNumber
5853 if (pageNumber == null || pageNumber == -1) {
5854 // Last page
5855 pageNumber = Math.ceil(results.length / quantityPerPage) - 1;
5856 }
5857
5858 var startRow = (pageNumber * quantityPerPage);
5859 var finalRow = (pageNumber * quantityPerPage) + quantityPerPage;
5860 if (finalRow > results.length) {
5861 finalRow = results.length;
5862 }
5863 startRow = finalRow - 100;
5864 if (startRow < 0) {
5865 startRow = 0;
5866 }
5867
5868 // Appeding items
5869 for (var j = startRow; j < finalRow; j++) {
5870 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5871 obj.tbody.appendChild(obj.rows[results[j]]);
5872 } else {
5873 obj.tbody.appendChild(obj.rows[j]);
5874 }
5875
5876 if (obj.tbody.children.length > quantityPerPage) {
5877 obj.tbody.removeChild(obj.tbody.firstChild);
5878 }
5879 }
5880 }
5881
5882 obj.loadUp = function() {
5883 // Search
5884 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5885 var results = obj.results;
5886 } else {
5887 var results = obj.rows;
5888 }
5889 var test = 0;
5890 if (results.length > 100) {
5891 // Get the first element in the page
5892 var item = parseInt(obj.tbody.firstChild.getAttribute('data-y'));
5893 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5894 item = results.indexOf(item);
5895 }
5896 if (item > 0) {
5897 for (var j = 0; j < 30; j++) {
5898 item = item - 1;
5899 if (item > -1) {
5900 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5901 obj.tbody.insertBefore(obj.rows[results[item]], obj.tbody.firstChild);
5902 } else {
5903 obj.tbody.insertBefore(obj.rows[item], obj.tbody.firstChild);
5904 }
5905 if (obj.tbody.children.length > 100) {
5906 obj.tbody.removeChild(obj.tbody.lastChild);
5907 test = 1;
5908 }
5909 }
5910 }
5911 }
5912 }
5913 return test;
5914 }
5915
5916 obj.loadDown = function() {
5917 // Search
5918 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5919 var results = obj.results;
5920 } else {
5921 var results = obj.rows;
5922 }
5923 var test = 0;
5924 if (results.length > 100) {
5925 // Get the last element in the page
5926 var item = parseInt(obj.tbody.lastChild.getAttribute('data-y'));
5927 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5928 item = results.indexOf(item);
5929 }
5930 if (item < obj.rows.length - 1) {
5931 for (var j = 0; j <= 30; j++) {
5932 if (item < results.length) {
5933 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
5934 obj.tbody.appendChild(obj.rows[results[item]]);
5935 } else {
5936 obj.tbody.appendChild(obj.rows[item]);
5937 }
5938 if (obj.tbody.children.length > 100) {
5939 obj.tbody.removeChild(obj.tbody.firstChild);
5940 test = 1;
5941 }
5942 }
5943 item = item + 1;
5944 }
5945 }
5946 }
5947
5948 return test;
5949 }
5950
5951 obj.loadValidation = function() {
5952 if (obj.selectedCell) {
5953 var currentPage = parseInt(obj.tbody.firstChild.getAttribute('data-y')) / 100;
5954 var selectedPage = parseInt(obj.selectedCell[3] / 100);
5955 var totalPages = parseInt(obj.rows.length / 100);
5956
5957 if (currentPage != selectedPage && selectedPage <= totalPages) {
5958 if (! Array.prototype.indexOf.call(obj.tbody.children, obj.rows[obj.selectedCell[3]])) {
5959 obj.loadPage(selectedPage);
5960 return true;
5961 }
5962 }
5963 }
5964
5965 return false;
5966 }
5967
5968 /**
5969 * Reset search
5970 */
5971 obj.resetSearch = function() {
5972 obj.searchInput.value = '';
5973 obj.search('');
5974 obj.results = null;
5975 }
5976
5977 /**
5978 * Search
5979 */
5980 obj.search = function(query) {
5981 // Reset any filter
5982 if (obj.options.filters) {
5983 obj.resetFilters();
5984 }
5985
5986 // Reset selection
5987 obj.resetSelection();
5988
5989 // Total of results
5990 obj.pageNumber = 0;
5991 obj.results = [];
5992
5993 if (query) {
5994 // Search filter
5995 var search = function(item, query, index) {
5996 for (var i = 0; i < item.length; i++) {
5997 if ((''+item[i]).toLowerCase().search(query) >= 0 ||
5998 (''+obj.records[index][i].innerHTML).toLowerCase().search(query) >= 0) {
5999 return true;
6000 }
6001 }
6002 return false;
6003 }
6004
6005 // Result
6006 var addToResult = function(k) {
6007 if (obj.results.indexOf(k) == -1) {
6008 obj.results.push(k);
6009 }
6010 }
6011
6012 var parsedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
6013 parsedQuery = new RegExp(parsedQuery, "i");
6014
6015 // Filter
6016 obj.options.data.forEach(function(v, k) {
6017 if (search(v, parsedQuery, k)) {
6018 // Merged rows found
6019 var rows = obj.isRowMerged(k);
6020 if (rows.length) {
6021 for (var i = 0; i < rows.length; i++) {
6022 var row = jexcel.getIdFromColumnName(rows[i], true);
6023 for (var j = 0; j < obj.options.mergeCells[rows[i]][1]; j++) {
6024 addToResult(row[1]+j);
6025 }
6026 }
6027 } else {
6028 // Normal row found
6029 addToResult(k);
6030 }
6031 }
6032 });
6033 } else {
6034 obj.results = null;
6035 }
6036
6037 return obj.updateResult();
6038 }
6039
6040 obj.updateResult = function() {
6041 var total = 0;
6042 var index = 0;
6043
6044 // Page 1
6045 if (obj.options.lazyLoading == true) {
6046 total = 100;
6047 } else if (obj.options.pagination > 0) {
6048 total = obj.options.pagination;
6049 } else {
6050 if (obj.results) {
6051 total = obj.results.length;
6052 } else {
6053 total = obj.rows.length;
6054 }
6055 }
6056
6057 // Reset current nodes
6058 while (obj.tbody.firstChild) {
6059 obj.tbody.removeChild(obj.tbody.firstChild);
6060 }
6061
6062 // Hide all records from the table
6063 for (var j = 0; j < obj.rows.length; j++) {
6064 if (! obj.results || obj.results.indexOf(j) > -1) {
6065 if (index < total) {
6066 obj.tbody.appendChild(obj.rows[j]);
6067 index++;
6068 }
6069 obj.rows[j].style.display = '';
6070 } else {
6071 obj.rows[j].style.display = 'none';
6072 }
6073 }
6074
6075 // Update pagination
6076 if (obj.options.pagination > 0) {
6077 obj.updatePagination();
6078 }
6079
6080 obj.updateCornerPosition();
6081
6082 return total;
6083 }
6084
6085 /**
6086 * Which page the cell is
6087 */
6088 obj.whichPage = function(cell) {
6089 // Search
6090 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
6091 cell = obj.results.indexOf(cell);
6092 }
6093
6094 return (Math.ceil((parseInt(cell) + 1) / parseInt(obj.options.pagination))) - 1;
6095 }
6096
6097 /**
6098 * Go to page
6099 */
6100 obj.page = function(pageNumber) {
6101 var oldPage = obj.pageNumber;
6102
6103 // Search
6104 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
6105 var results = obj.results;
6106 } else {
6107 var results = obj.rows;
6108 }
6109
6110 // Per page
6111 var quantityPerPage = parseInt(obj.options.pagination);
6112
6113 // pageNumber
6114 if (pageNumber == null || pageNumber == -1) {
6115 // Last page
6116 pageNumber = Math.ceil(results.length / quantityPerPage) - 1;
6117 }
6118
6119 // Page number
6120 obj.pageNumber = pageNumber;
6121
6122 var startRow = (pageNumber * quantityPerPage);
6123 var finalRow = (pageNumber * quantityPerPage) + quantityPerPage;
6124 if (finalRow > results.length) {
6125 finalRow = results.length;
6126 }
6127 if (startRow < 0) {
6128 startRow = 0;
6129 }
6130
6131 // Reset container
6132 while (obj.tbody.firstChild) {
6133 obj.tbody.removeChild(obj.tbody.firstChild);
6134 }
6135
6136 // Appeding items
6137 for (var j = startRow; j < finalRow; j++) {
6138 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
6139 obj.tbody.appendChild(obj.rows[results[j]]);
6140 } else {
6141 obj.tbody.appendChild(obj.rows[j]);
6142 }
6143 }
6144
6145 if (obj.options.pagination > 0) {
6146 obj.updatePagination();
6147 }
6148
6149 // Update corner position
6150 obj.updateCornerPosition();
6151
6152 // Events
6153 obj.dispatch('onchangepage', el, pageNumber, oldPage);
6154 }
6155
6156 /**
6157 * Update the pagination
6158 */
6159 obj.updatePagination = function() {
6160 // Reset container
6161 obj.pagination.children[0].innerHTML = '';
6162 obj.pagination.children[1].innerHTML = '';
6163
6164 // Start pagination
6165 if (obj.options.pagination) {
6166 // Searchable
6167 if ((obj.options.search == true || obj.options.filters == true) && obj.results) {
6168 var results = obj.results.length;
6169 } else {
6170 var results = obj.rows.length;
6171 }
6172
6173 if (! results) {
6174 // No records found
6175 obj.pagination.children[0].innerHTML = obj.options.text.noRecordsFound;
6176 } else {
6177 // Pagination container
6178 var quantyOfPages = Math.ceil(results / obj.options.pagination);
6179
6180 if (obj.pageNumber < 6) {
6181 var startNumber = 1;
6182 var finalNumber = quantyOfPages < 10 ? quantyOfPages : 10;
6183 } else if (quantyOfPages - obj.pageNumber < 5) {
6184 var startNumber = quantyOfPages - 9;
6185 var finalNumber = quantyOfPages;
6186 if (startNumber < 1) {
6187 startNumber = 1;
6188 }
6189 } else {
6190 var startNumber = obj.pageNumber - 4;
6191 var finalNumber = obj.pageNumber + 5;
6192 }
6193
6194 // First
6195 if (startNumber > 1) {
6196 var paginationItem = document.createElement('div');
6197 paginationItem.className = 'jexcel_page';
6198 paginationItem.innerHTML = '<';
6199 paginationItem.title = 1;
6200 obj.pagination.children[1].appendChild(paginationItem);
6201 }
6202
6203 // Get page links
6204 for (var i = startNumber; i <= finalNumber; i++) {
6205 var paginationItem = document.createElement('div');
6206 paginationItem.className = 'jexcel_page';
6207 paginationItem.innerHTML = i;
6208 obj.pagination.children[1].appendChild(paginationItem);
6209
6210 if (obj.pageNumber == (i-1)) {
6211 paginationItem.classList.add('jexcel_page_selected');
6212 }
6213 }
6214
6215 // Last
6216 if (finalNumber < quantyOfPages) {
6217 var paginationItem = document.createElement('div');
6218 paginationItem.className = 'jexcel_page';
6219 paginationItem.innerHTML = '>';
6220 paginationItem.title = quantyOfPages;
6221 obj.pagination.children[1].appendChild(paginationItem);
6222 }
6223
6224 // Text
6225 var format = function(format) {
6226 var args = Array.prototype.slice.call(arguments, 1);
6227 return format.replace(/{(\d+)}/g, function(match, number) {
6228 return typeof args[number] != 'undefined'
6229 ? args[number]
6230 : match
6231 ;
6232 });
6233 };
6234
6235 obj.pagination.children[0].innerHTML = format(obj.options.text.showingPage, obj.pageNumber + 1, quantyOfPages)
6236 }
6237 }
6238 }
6239
6240 /**
6241 * Download CSV table
6242 *
6243 * @return null
6244 */
6245 obj.download = function(includeHeaders) {
6246 if (obj.options.allowExport == false) {
6247 console.error('Export not allowed');
6248 } else {
6249 // Data
6250 var data = '';
6251
6252 // Get data
6253 data += obj.copy(false, obj.options.csvDelimiter, true, includeHeaders, true);
6254
6255 // Download element
6256 var blob = new Blob(["\uFEFF"+data], {type: 'text/csv;charset=utf-8;'});
6257
6258 // IE Compatibility
6259 if (window.navigator && window.navigator.msSaveOrOpenBlob) {
6260 window.navigator.msSaveOrOpenBlob(blob, obj.options.csvFileName + '.csv');
6261 } else {
6262 // Download element
6263 var pom = document.createElement('a');
6264 var url = URL.createObjectURL(blob);
6265 pom.href = url;
6266 pom.setAttribute('download', obj.options.csvFileName + '.csv');
6267 document.body.appendChild(pom);
6268 pom.click();
6269 pom.parentNode.removeChild(pom);
6270 }
6271 }
6272 }
6273
6274 /**
6275 * Initializes a new history record for undo/redo
6276 *
6277 * @return null
6278 */
6279 obj.setHistory = function(changes) {
6280 if (obj.ignoreHistory != true) {
6281 // Increment and get the current history index
6282 var index = ++obj.historyIndex;
6283
6284 // Slice the array to discard undone changes
6285 obj.history = (obj.history = obj.history.slice(0, index + 1));
6286
6287 // Keep history
6288 obj.history[index] = changes;
6289 }
6290 }
6291
6292 /**
6293 * Copy method
6294 *
6295 * @param bool highlighted - Get only highlighted cells
6296 * @param delimiter - \t default to keep compatibility with excel
6297 * @return string value
6298 */
6299 obj.copy = function(highlighted, delimiter, returnData, includeHeaders, download) {
6300 if (! delimiter) {
6301 delimiter = "\t";
6302 }
6303
6304 var div = new RegExp(delimiter, 'ig');
6305
6306 // Controls
6307 var header = [];
6308 var col = [];
6309 var colLabel = [];
6310 var row = [];
6311 var rowLabel = [];
6312 var x = obj.options.data[0].length;
6313 var y = obj.options.data.length;
6314 var tmp = '';
6315 var copyHeader = false;
6316 var headers = '';
6317 var nestedHeaders = '';
6318 var numOfCols = 0;
6319 var numOfRows = 0;
6320
6321 // Partial copy
6322 var copyX = 0;
6323 var copyY = 0;
6324 var isPartialCopy = true;
6325 // Go through the columns to get the data
6326 for (var j = 0; j < y; j++) {
6327 for (var i = 0; i < x; i++) {
6328 // If cell is highlighted
6329 if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
6330 if (copyX <= i) {
6331 copyX = i;
6332 }
6333 if (copyY <= j) {
6334 copyY = j;
6335 }
6336 }
6337 }
6338 }
6339 if (x === copyX+1 && y === copyY+1) {
6340 isPartialCopy = false;
6341 }
6342
6343 if ((download && obj.options.includeHeadersOnDownload == true) ||
6344 (! download && obj.options.includeHeadersOnCopy == true && ! isPartialCopy) || (includeHeaders)) {
6345 // Nested headers
6346 if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
6347 // Flexible way to handle nestedheaders
6348 if (! (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0])) {
6349 tmp = [obj.options.nestedHeaders];
6350 } else {
6351 tmp = obj.options.nestedHeaders;
6352 }
6353
6354 for (var j = 0; j < tmp.length; j++) {
6355 var nested = [];
6356 for (var i = 0; i < tmp[j].length; i++) {
6357 var colspan = parseInt(tmp[j][i].colspan);
6358 nested.push(tmp[j][i].title);
6359 for (var c = 0; c < colspan - 1; c++) {
6360 nested.push('');
6361 }
6362 }
6363 nestedHeaders += nested.join(delimiter) + "\r\n";
6364 }
6365 }
6366
6367 copyHeader = true;
6368 }
6369
6370 // Reset container
6371 obj.style = [];
6372
6373 // Go through the columns to get the data
6374 for (var j = 0; j < y; j++) {
6375 col = [];
6376 colLabel = [];
6377
6378 for (var i = 0; i < x; i++) {
6379 // If cell is highlighted
6380 if (! highlighted || obj.records[j][i].classList.contains('highlight')) {
6381 if (copyHeader == true) {
6382 header.push(obj.headers[i].textContent);
6383 }
6384 // Values
6385 var value = obj.options.data[j][i];
6386 // TablePress: Only escape quotation marks in cells if the cell has to be CSV escaped due to other characters, and make finding such characters more robust.
6387 /*
6388 if (value.match && (value.match(div) || value.match(/,/g) || value.match(/\n/) || value.match(/\"/))) {
6389 value = value.replace(new RegExp('"', 'g'), '""');
6390 value = '"' + value + '"';
6391 }
6392 */
6393 if ( value.match && ( value.match( div ) || value.includes( delimiter ) || value.includes( "\n" ) ) ) {
6394 if ( value.includes( '"' ) ) {
6395 value = value.replace(new RegExp('"', 'g'), '""');
6396 }
6397 value = '"' + value + '"';
6398 }
6399 col.push(value);
6400
6401 // Labels
6402 if (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio') {
6403 var label = value;
6404 } else {
6405 if (obj.options.stripHTMLOnCopy == true) {
6406 var label = obj.records[j][i].textContent;
6407 } else {
6408 var label = obj.records[j][i].innerHTML;
6409 }
6410 if (label.match && (label.match(div) || label.match(/,/g) || label.match(/\n/) || label.match(/\"/))) {
6411 // Scape double quotes
6412 label = label.replace(new RegExp('"', 'g'), '""');
6413 label = '"' + label + '"';
6414 }
6415 }
6416 colLabel.push(label);
6417
6418 // Get style
6419 tmp = obj.records[j][i].getAttribute('style');
6420 tmp = tmp.replace('display: none;', '');
6421 obj.style.push(tmp ? tmp : '');
6422 }
6423 }
6424
6425 if (col.length) {
6426 if (copyHeader) {
6427 numOfCols = col.length;
6428 row.push(header.join(delimiter));
6429 }
6430 row.push(col.join(delimiter));
6431 }
6432 if (colLabel.length) {
6433 numOfRows++;
6434 if (copyHeader) {
6435 rowLabel.push(header.join(delimiter));
6436 copyHeader = false;
6437 }
6438 rowLabel.push(colLabel.join(delimiter));
6439 }
6440 }
6441
6442 if (x == numOfCols && y == numOfRows) {
6443 headers = nestedHeaders;
6444 }
6445
6446 // Final string
6447 var str = headers + row.join("\r\n");
6448 var strLabel = headers + rowLabel.join("\r\n");
6449
6450 // Create a hidden textarea to copy the values
6451 if (! returnData) {
6452 if (obj.options.copyCompatibility == true) {
6453 obj.textarea.value = strLabel;
6454 } else {
6455 obj.textarea.value = str;
6456 }
6457 obj.textarea.select();
6458 document.execCommand("copy");
6459 }
6460
6461 // Keep data
6462 if (obj.options.copyCompatibility == true) {
6463 obj.data = strLabel;
6464 } else {
6465 obj.data = str;
6466 }
6467 // Keep non visible information
6468 obj.hashString = obj.hash(obj.data);
6469
6470 // Any exiting border should go
6471 if (! returnData) {
6472 obj.removeCopyingSelection();
6473
6474 // Border
6475 if (obj.highlighted) {
6476 for (var i = 0; i < obj.highlighted.length; i++) {
6477 obj.highlighted[i].classList.add('copying');
6478 if (obj.highlighted[i].classList.contains('highlight-left')) {
6479 obj.highlighted[i].classList.add('copying-left');
6480 }
6481 if (obj.highlighted[i].classList.contains('highlight-right')) {
6482 obj.highlighted[i].classList.add('copying-right');
6483 }
6484 if (obj.highlighted[i].classList.contains('highlight-top')) {
6485 obj.highlighted[i].classList.add('copying-top');
6486 }
6487 if (obj.highlighted[i].classList.contains('highlight-bottom')) {
6488 obj.highlighted[i].classList.add('copying-bottom');
6489 }
6490 }
6491 }
6492
6493 // Paste event
6494 obj.dispatch('oncopy', el, obj.options.copyCompatibility == true ? rowLabel : row, obj.hashString);
6495 }
6496
6497 return obj.data;
6498 }
6499
6500 /**
6501 * Jspreadsheet paste method
6502 *
6503 * @param integer row number
6504 * @return string value
6505 */
6506 obj.paste = function(x, y, data) {
6507 var x_1 = parseInt(x),
6508 y_1 = parseInt(y),
6509 x_2 = parseInt(obj.selectedCell[2]),
6510 y_2 = parseInt(obj.selectedCell[3]),
6511 w = x_2 - x_1 + 1,
6512 h = y_2 - y_1 + 1;
6513
6514 // change paste range if select is from right to left
6515 if (x_2 < x_1){
6516 x = x_2.toString();
6517 w = x_1 - x_2 + 1;
6518 }
6519 // change paste range if select is from down to up
6520 if (y_2 < y_1){
6521 y = y_2.toString();
6522 h = y_1 - y_2 + 1;
6523 }
6524 // Paste filter
6525 var ret = obj.dispatch('onbeforepaste', el, data, x, y);
6526
6527 if (ret === false) {
6528 return false;
6529 } else if (ret) {
6530 var data = ret;
6531 }
6532
6533 // Controls
6534 var hash = obj.hash(data);
6535 var style = (hash == obj.hashString) ? obj.style : null;
6536
6537 // Depending on the behavior
6538 if (obj.options.copyCompatibility == true && hash == obj.hashString) {
6539 var data = obj.data;
6540 }
6541
6542 // Split new line
6543 var data = obj.parseCSV(data, "\t");
6544 // Modify data to allow wor extending paste range in multiples of input range
6545 if ((w > 1) & Number.isInteger(w / data[0].length)) {
6546 style = null;
6547 repeats = w / data[0].length;
6548
6549 var arrayB = data.map(function (row, i) {
6550 var arrayC = Array.apply(null, { length: repeats * row.length }).map(
6551 function (e, i) { return row[i % row.length]; }
6552 );
6553 return arrayC;
6554 });
6555 data = arrayB;
6556 }
6557 if ((h > 1) & Number.isInteger(h / data.length)) {
6558 style = null;
6559 var repeats = h / data.length;
6560 var arrayB = Array.apply(null, { length: repeats * data.length }).map(
6561 function (e, i) { return data[i % data.length]; }
6562 );
6563 data = arrayB;
6564 }
6565
6566 if (x != null && y != null && data) {
6567 // Records
6568 var i = 0;
6569 var j = 0;
6570 var records = [];
6571 var newStyle = {};
6572 var oldStyle = {};
6573 var styleIndex = 0;
6574
6575 // Index
6576 var colIndex = parseInt(x);
6577 var rowIndex = parseInt(y);
6578 var row = null;
6579
6580 // Go through the columns to get the data
6581 while (row = data[j]) {
6582 i = 0;
6583 colIndex = parseInt(x);
6584
6585 while (row[i] != null) {
6586 // Update and keep history
6587 var record = obj.updateCell(colIndex, rowIndex, row[i]);
6588 // Keep history
6589 records.push(record);
6590 // Update all formulas in the chain
6591 obj.updateFormulaChain(colIndex, rowIndex, records);
6592 // Style
6593 if (style && style[styleIndex]) {
6594 var columnName = jexcel.getColumnNameFromId([colIndex, rowIndex]);
6595 newStyle[columnName] = style[styleIndex];
6596 oldStyle[columnName] = obj.getStyle(columnName);
6597 obj.records[rowIndex][colIndex].setAttribute('style', style[styleIndex]);
6598 styleIndex++
6599 }
6600 i++;
6601 if (row[i] != null) {
6602 if (colIndex >= obj.headers.length - 1) {
6603 // If the pasted column is out of range, create it if possible
6604 if (obj.options.allowInsertColumn == true) {
6605 obj.insertColumn();
6606 // Otherwise skip the pasted data that overflows
6607 } else {
6608 break;
6609 }
6610 }
6611 colIndex = obj.right.get(colIndex, rowIndex);
6612 }
6613 }
6614
6615 j++;
6616 if (data[j]) {
6617 if (rowIndex >= obj.rows.length-1) {
6618 // If the pasted row is out of range, create it if possible
6619 if (obj.options.allowInsertRow == true) {
6620 obj.insertRow();
6621 // Otherwise skip the pasted data that overflows
6622 } else {
6623 break;
6624 }
6625 }
6626 rowIndex = obj.down.get(x, rowIndex);
6627 }
6628 }
6629
6630 // Select the new cells
6631 obj.updateSelectionFromCoords(x, y, colIndex, rowIndex);
6632
6633 // Update history
6634 obj.setHistory({
6635 action:'setValue',
6636 records:records,
6637 selection:obj.selectedCell,
6638 newStyle:newStyle,
6639 oldStyle:oldStyle,
6640 });
6641
6642 // Update table
6643 obj.updateTable();
6644
6645 // Paste event
6646 obj.dispatch('onpaste', el, data);
6647
6648 // On after changes
6649 obj.onafterchanges(el, records);
6650 }
6651
6652 obj.removeCopyingSelection();
6653 }
6654
6655 /**
6656 * Remove copying border
6657 */
6658 obj.removeCopyingSelection = function() {
6659 var copying = document.querySelectorAll('.jexcel .copying');
6660 for (var i = 0; i < copying.length; i++) {
6661 copying[i].classList.remove('copying');
6662 copying[i].classList.remove('copying-left');
6663 copying[i].classList.remove('copying-right');
6664 copying[i].classList.remove('copying-top');
6665 copying[i].classList.remove('copying-bottom');
6666 }
6667 }
6668
6669 /**
6670 * Process row
6671 */
6672 obj.historyProcessRow = function(type, historyRecord) {
6673 var rowIndex = (! historyRecord.insertBefore) ? historyRecord.rowNumber + 1 : +historyRecord.rowNumber;
6674
6675 if (obj.options.search == true) {
6676 if (obj.results && obj.results.length != obj.rows.length) {
6677 obj.resetSearch();
6678 }
6679 }
6680
6681 // Remove row
6682 if (type == 1) {
6683 var numOfRows = historyRecord.numOfRows;
6684 // Remove nodes
6685 for (var j = rowIndex; j < (numOfRows + rowIndex); j++) {
6686 obj.rows[j].parentNode.removeChild(obj.rows[j]);
6687 }
6688 // Remove references
6689 obj.records.splice(rowIndex, numOfRows);
6690 obj.options.data.splice(rowIndex, numOfRows);
6691 obj.rows.splice(rowIndex, numOfRows);
6692
6693 obj.conditionalSelectionUpdate(1, rowIndex, (numOfRows + rowIndex) - 1);
6694 } else {
6695 // Insert data
6696 obj.records = jexcel.injectArray(obj.records, rowIndex, historyRecord.rowRecords);
6697 obj.options.data = jexcel.injectArray(obj.options.data, rowIndex, historyRecord.rowData);
6698 obj.rows = jexcel.injectArray(obj.rows, rowIndex, historyRecord.rowNode);
6699 // Insert nodes
6700 var index = 0
6701 for (var j = rowIndex; j < (historyRecord.numOfRows + rowIndex); j++) {
6702 obj.tbody.insertBefore(historyRecord.rowNode[index], obj.tbody.children[j]);
6703 index++;
6704 }
6705 }
6706
6707 // Respect pagination
6708 if (obj.options.pagination > 0) {
6709 obj.page(obj.pageNumber);
6710 }
6711
6712 obj.updateTableReferences();
6713 }
6714
6715 /**
6716 * Process column
6717 */
6718 obj.historyProcessColumn = function(type, historyRecord) {
6719 var columnIndex = (! historyRecord.insertBefore) ? historyRecord.columnNumber + 1 : historyRecord.columnNumber;
6720
6721 // Remove column
6722 if (type == 1) {
6723 var numOfColumns = historyRecord.numOfColumns;
6724
6725 obj.options.columns.splice(columnIndex, numOfColumns);
6726 for (var i = columnIndex; i < (numOfColumns + columnIndex); i++) {
6727 obj.headers[i].parentNode.removeChild(obj.headers[i]);
6728 obj.colgroup[i].parentNode.removeChild(obj.colgroup[i]);
6729 }
6730 obj.headers.splice(columnIndex, numOfColumns);
6731 obj.colgroup.splice(columnIndex, numOfColumns);
6732 for (var j = 0; j < historyRecord.data.length; j++) {
6733 for (var i = columnIndex; i < (numOfColumns + columnIndex); i++) {
6734 obj.records[j][i].parentNode.removeChild(obj.records[j][i]);
6735 }
6736 obj.records[j].splice(columnIndex, numOfColumns);
6737 obj.options.data[j].splice(columnIndex, numOfColumns);
6738 }
6739 // Process footers
6740 if (obj.options.footers) {
6741 for (var j = 0; j < obj.options.footers.length; j++) {
6742 obj.options.footers[j].splice(columnIndex, numOfColumns);
6743 }
6744 }
6745 } else {
6746 // Insert data
6747 obj.options.columns = jexcel.injectArray(obj.options.columns, columnIndex, historyRecord.columns);
6748 obj.headers = jexcel.injectArray(obj.headers, columnIndex, historyRecord.headers);
6749 obj.colgroup = jexcel.injectArray(obj.colgroup, columnIndex, historyRecord.colgroup);
6750
6751 var index = 0
6752 for (var i = columnIndex; i < (historyRecord.numOfColumns + columnIndex); i++) {
6753 obj.headerContainer.insertBefore(historyRecord.headers[index], obj.headerContainer.children[i+1]);
6754 obj.colgroupContainer.insertBefore(historyRecord.colgroup[index], obj.colgroupContainer.children[i+1]);
6755 index++;
6756 }
6757
6758 for (var j = 0; j < historyRecord.data.length; j++) {
6759 obj.options.data[j] = jexcel.injectArray(obj.options.data[j], columnIndex, historyRecord.data[j]);
6760 obj.records[j] = jexcel.injectArray(obj.records[j], columnIndex, historyRecord.records[j]);
6761 var index = 0
6762 for (var i = columnIndex; i < (historyRecord.numOfColumns + columnIndex); i++) {
6763 obj.rows[j].insertBefore(historyRecord.records[j][index], obj.rows[j].children[i+1]);
6764 index++;
6765 }
6766 }
6767 // Process footers
6768 if (obj.options.footers) {
6769 for (var j = 0; j < obj.options.footers.length; j++) {
6770 obj.options.footers[j] = jexcel.injectArray(obj.options.footers[j], columnIndex, historyRecord.footers[j]);
6771 }
6772 }
6773 }
6774
6775 // Adjust nested headers
6776 if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) {
6777 // Flexible way to handle nestedheaders
6778 if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) {
6779 for (var j = 0; j < obj.options.nestedHeaders.length; j++) {
6780 if (type == 1) {
6781 var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) - historyRecord.numOfColumns;
6782 } else {
6783 var colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan) + historyRecord.numOfColumns;
6784 }
6785 obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length-1].colspan = colspan;
6786 obj.thead.children[j].children[obj.thead.children[j].children.length-1].setAttribute('colspan', colspan);
6787 }
6788 } else {
6789 if (type == 1) {
6790 var colspan = parseInt(obj.options.nestedHeaders[0].colspan) - historyRecord.numOfColumns;
6791 } else {
6792 var colspan = parseInt(obj.options.nestedHeaders[0].colspan) + historyRecord.numOfColumns;
6793 }
6794 obj.options.nestedHeaders[0].colspan = colspan;
6795 obj.thead.children[0].children[obj.thead.children[0].children.length-1].setAttribute('colspan', colspan);
6796 }
6797 }
6798
6799 obj.updateTableReferences();
6800 }
6801
6802 /**
6803 * Undo last action
6804 */
6805 obj.undo = function() {
6806 // Ignore events and history
6807 var ignoreEvents = obj.ignoreEvents ? true : false;
6808 var ignoreHistory = obj.ignoreHistory ? true : false;
6809
6810 obj.ignoreEvents = true;
6811 obj.ignoreHistory = true;
6812
6813 // Records
6814 var records = [];
6815
6816 // Update cells
6817 if (obj.historyIndex >= 0) {
6818 // History
6819 var historyRecord = obj.history[obj.historyIndex--];
6820
6821 if (historyRecord.action == 'insertRow') {
6822 obj.historyProcessRow(1, historyRecord);
6823 } else if (historyRecord.action == 'deleteRow') {
6824 obj.historyProcessRow(0, historyRecord);
6825 } else if (historyRecord.action == 'insertColumn') {
6826 obj.historyProcessColumn(1, historyRecord);
6827 } else if (historyRecord.action == 'deleteColumn') {
6828 obj.historyProcessColumn(0, historyRecord);
6829 } else if (historyRecord.action == 'moveRow') {
6830 obj.moveRow(historyRecord.newValue, historyRecord.oldValue);
6831 } else if (historyRecord.action == 'moveColumn') {
6832 obj.moveColumn(historyRecord.newValue, historyRecord.oldValue);
6833 } else if (historyRecord.action == 'setMerge') {
6834 obj.removeMerge(historyRecord.column, historyRecord.data);
6835 } else if (historyRecord.action == 'setStyle') {
6836 obj.setStyle(historyRecord.oldValue, null, null, 1);
6837 } else if (historyRecord.action == 'setWidth') {
6838 obj.setWidth(historyRecord.column, historyRecord.oldValue);
6839 } else if (historyRecord.action == 'setHeight') {
6840 obj.setHeight(historyRecord.row, historyRecord.oldValue);
6841 } else if (historyRecord.action == 'setHeader') {
6842 obj.setHeader(historyRecord.column, historyRecord.oldValue);
6843 } else if (historyRecord.action == 'setComments') {
6844 obj.setComments(historyRecord.column, historyRecord.oldValue[0], historyRecord.oldValue[1]);
6845 } else if (historyRecord.action == 'orderBy') {
6846 var rows = [];
6847 for (var j = 0; j < historyRecord.rows.length; j++) {
6848 rows[historyRecord.rows[j]] = j;
6849 }
6850 obj.updateOrderArrow(historyRecord.column, historyRecord.order ? 0 : 1);
6851 obj.updateOrder(rows);
6852 } else if (historyRecord.action == 'setValue') {
6853 // Redo for changes in cells
6854 for (var i = 0; i < historyRecord.records.length; i++) {
6855 records.push({
6856 x: historyRecord.records[i].x,
6857 y: historyRecord.records[i].y,
6858 newValue: historyRecord.records[i].oldValue,
6859 });
6860
6861 if (historyRecord.oldStyle) {
6862 obj.resetStyle(historyRecord.oldStyle);
6863 }
6864 }
6865 // Update records
6866 obj.setValue(records);
6867
6868 // Update selection
6869 if (historyRecord.selection) {
6870 obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]);
6871 }
6872 }
6873 }
6874 obj.ignoreEvents = ignoreEvents;
6875 obj.ignoreHistory = ignoreHistory;
6876
6877 // Events
6878 obj.dispatch('onundo', el, historyRecord);
6879 }
6880
6881 /**
6882 * Redo previously undone action
6883 */
6884 obj.redo = function() {
6885 // Ignore events and history
6886 var ignoreEvents = obj.ignoreEvents ? true : false;
6887 var ignoreHistory = obj.ignoreHistory ? true : false;
6888
6889 obj.ignoreEvents = true;
6890 obj.ignoreHistory = true;
6891
6892 // Records
6893 var records = [];
6894
6895 // Update cells
6896 if (obj.historyIndex < obj.history.length - 1) {
6897 // History
6898 var historyRecord = obj.history[++obj.historyIndex];
6899
6900 if (historyRecord.action == 'insertRow') {
6901 obj.historyProcessRow(0, historyRecord);
6902 } else if (historyRecord.action == 'deleteRow') {
6903 obj.historyProcessRow(1, historyRecord);
6904 } else if (historyRecord.action == 'insertColumn') {
6905 obj.historyProcessColumn(0, historyRecord);
6906 } else if (historyRecord.action == 'deleteColumn') {
6907 obj.historyProcessColumn(1, historyRecord);
6908 } else if (historyRecord.action == 'moveRow') {
6909 obj.moveRow(historyRecord.oldValue, historyRecord.newValue);
6910 } else if (historyRecord.action == 'moveColumn') {
6911 obj.moveColumn(historyRecord.oldValue, historyRecord.newValue);
6912 } else if (historyRecord.action == 'setMerge') {
6913 obj.setMerge(historyRecord.column, historyRecord.colspan, historyRecord.rowspan, 1);
6914 } else if (historyRecord.action == 'setStyle') {
6915 obj.setStyle(historyRecord.newValue, null, null, 1);
6916 } else if (historyRecord.action == 'setWidth') {
6917 obj.setWidth(historyRecord.column, historyRecord.newValue);
6918 } else if (historyRecord.action == 'setHeight') {
6919 obj.setHeight(historyRecord.row, historyRecord.newValue);
6920 } else if (historyRecord.action == 'setHeader') {
6921 obj.setHeader(historyRecord.column, historyRecord.newValue);
6922 } else if (historyRecord.action == 'setComments') {
6923 obj.setComments(historyRecord.column, historyRecord.newValue[0], historyRecord.newValue[1]);
6924 } else if (historyRecord.action == 'orderBy') {
6925 obj.updateOrderArrow(historyRecord.column, historyRecord.order);
6926 obj.updateOrder(historyRecord.rows);
6927 } else if (historyRecord.action == 'setValue') {
6928 obj.setValue(historyRecord.records);
6929 // Redo for changes in cells
6930 for (var i = 0; i < historyRecord.records.length; i++) {
6931 if (historyRecord.oldStyle) {
6932 obj.resetStyle(historyRecord.newStyle);
6933 }
6934 }
6935 // Update selection
6936 if (historyRecord.selection) {
6937 obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]);
6938 }
6939 }
6940 }
6941 obj.ignoreEvents = ignoreEvents;
6942 obj.ignoreHistory = ignoreHistory;
6943
6944 // Events
6945 obj.dispatch('onredo', el, historyRecord);
6946 }
6947
6948 /**
6949 * Get dropdown value from key
6950 */
6951 obj.getDropDownValue = function(column, key) {
6952 var value = [];
6953
6954 if (obj.options.columns[column] && obj.options.columns[column].source) {
6955 // Create array from source
6956 var combo = [];
6957 var source = obj.options.columns[column].source;
6958
6959 for (var i = 0; i < source.length; i++) {
6960 if (typeof(source[i]) == 'object') {
6961 combo[source[i].id] = source[i].name;
6962 } else {
6963 combo[source[i]] = source[i];
6964 }
6965 }
6966
6967 // Guarantee single multiple compatibility
6968 var keys = Array.isArray(key) ? key : ('' + key).split(';');
6969
6970 for (var i = 0; i < keys.length; i++) {
6971 if (typeof(keys[i]) === 'object') {
6972 value.push(combo[keys[i].id]);
6973 } else {
6974 if (combo[keys[i]]) {
6975 value.push(combo[keys[i]]);
6976 }
6977 }
6978 }
6979 } else {
6980 console.error('Invalid column');
6981 }
6982
6983 return (value.length > 0) ? value.join('; ') : '';
6984 }
6985
6986 /**
6987 * From stack overflow contributions
6988 */
6989 /** // TablePress: Replaced with more reliable parseCSV function below.
6990 obj.parseCSV = function(str, delimiter) {
6991 // Remove last line break
6992 str = str.replace(/\r?\n$|\r$|\n$/g, "");
6993 // Last caracter is the delimiter
6994 if (str.charCodeAt(str.length-1) == 9) {
6995 str += "\0";
6996 }
6997 // user-supplied delimeter or default comma
6998 delimiter = (delimiter || ",");
6999
7000 var arr = [];
7001 var quote = false; // true means we're inside a quoted field
7002 // iterate over each character, keep track of current row and column (of the returned array)
7003 for (var row = 0, col = 0, c = 0; c < str.length; c++) {
7004 var cc = str[c], nc = str[c+1];
7005 arr[row] = arr[row] || [];
7006 arr[row][col] = arr[row][col] || '';
7007
7008 // If the current character is a quotation mark, and we're inside a quoted field, and the next character is also a quotation mark, add a quotation mark to the current column and skip the next character
7009 if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
7010
7011 // If it's just one quotation mark, begin/end quoted field
7012 if (cc == '"') { quote = !quote; continue; }
7013
7014 // If it's a comma and we're not in a quoted field, move on to the next column
7015 if (cc == delimiter && !quote) { ++col; continue; }
7016
7017 // If it's a newline (CRLF) and we're not in a quoted field, skip the next character and move on to the next row and move to column 0 of that new row
7018 if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
7019
7020 // If it's a newline (LF or CR) and we're not in a quoted field, move on to the next row and move to column 0 of that new row
7021 if (cc == '\n' && !quote) { ++row; col = 0; continue; }
7022 if (cc == '\r' && !quote) { ++row; col = 0; continue; }
7023
7024 // Otherwise, append the current character to the current column
7025 arr[row][col] += cc;
7026 }
7027 return arr;
7028 }
7029 */
7030 // TablePress: This new function replaces the original parseCSV function above, which has problems with quotation marks.
7031 // This implementation was ported from the PHP implementation in /libraries/csv-parser.class.php to JS.
7032 obj.parseCSV = function(str, delimiter) {
7033 // Ensure that the string has a line break at the end, by first removing all line breaks, and then adding one.
7034 str = str.replace( /\r?\n$|\r$|\n$/g, '' );
7035 str += "\n";
7036
7037 // Use user-supplied delimiter or use a comma as the default.
7038 delimiter = delimiter || ',';
7039
7040 const white_spaces = new RegExp( '^' + " \t\x0B\0".replace( delimiter, '' ) + '*' );
7041
7042 let rows = []; // Complete rows.
7043 let row = []; // Row that is currently built.
7044 let column = 0; // Current column index.
7045 let cell_content = ''; // Content of the currently processed cell.
7046 let enclosed = false;
7047 let was_enclosed = false; // To determine if the cell content will be trimmed of whitespace (only for enclosed cells).
7048
7049 // Walk through each character in the CSV string.
7050 const data_length = str.length;
7051 for ( let i = 0; i < data_length; i++ ) {
7052 let curr_char = str[ i ];
7053 let next_char = ( i + 1 < data_length ) ? str[ i + 1 ] : '';
7054
7055 if ( curr_char === '"' ) {
7056 // Open/close quotes, and inline quotes.
7057 if ( ! enclosed ) {
7058 if ( '' === cell_content.replace( white_spaces, '' ) ) {
7059 enclosed = true;
7060 was_enclosed = true;
7061 } else {
7062 // Technically, there's a CSV syntax here now, but we ignore it.
7063 cell_content += curr_char;
7064 }
7065 } else if ( next_char === '"' ) {
7066 // Enclosure character within enclosed cell (" encoded as "").
7067 cell_content += curr_char;
7068 ++i; // Skip next character.
7069 } else if ( next_char !== delimiter && "\r" !== next_char && "\n" !== next_char ) {
7070 // for-loop (instead of while-loop) that skips whitespace.
7071 let x = i + 1;
7072 for ( ; undefined !== str[ x ] && '' === str[ x ].replace( white_spaces, '' ); x++ ) {
7073 // Action is in iterator check.
7074 }
7075 if ( str[ x ] === delimiter ) {
7076 enclosed = false;
7077 i = x;
7078 } else {
7079 // Technically, there's a CSV syntax here now, but we ignore it.
7080 cell_content += curr_char;
7081 enclosed = false;
7082 }
7083 } else {
7084 // The " was the closing one for the cell.
7085 enclosed = false;
7086 }
7087 } else if ( ( curr_char === delimiter || "\n" === curr_char || "\r" === curr_char ) && ! enclosed ) {
7088 // End of cell (by delimiter), or end of line (by line break, and not enclosed!).
7089 row[ column ] = was_enclosed ? cell_content : cell_content.trim();
7090 cell_content = '';
7091 was_enclosed = false;
7092 ++column;
7093
7094 // End of line.
7095 if ( "\n" === curr_char || "\r" === curr_char ) {
7096 // Append completed row.
7097 rows.push( row );
7098 row = [];
7099 column = 0;
7100 if ( "\r" === curr_char && "\n" === next_char ) {
7101 // Skip next character in \r\n line breaks.
7102 ++i;
7103 }
7104 }
7105 } else {
7106 // Append character to current cell.
7107 cell_content += curr_char;
7108 }
7109 }
7110
7111 return rows;
7112 }
7113
7114 obj.hash = function(str) {
7115 var hash = 0, i, chr;
7116
7117 if (str.length === 0) {
7118 return hash;
7119 } else {
7120 for (i = 0; i < str.length; i++) {
7121 chr = str.charCodeAt(i);
7122 hash = ((hash << 5) - hash) + chr;
7123 hash |= 0;
7124 }
7125 }
7126 return hash;
7127 }
7128
7129 obj.onafterchanges = function(el, records) {
7130 // Events
7131 obj.dispatch('onafterchanges', el, records);
7132 }
7133
7134 obj.destroy = function() {
7135 jexcel.destroy(el);
7136 }
7137
7138 /**
7139 * Initialization method
7140 */
7141 obj.init = function() {
7142 jexcel.current = obj;
7143
7144 // Build handlers
7145 if (typeof(jexcel.build) == 'function') {
7146 if (obj.options.root) {
7147 jexcel.build(obj.options.root);
7148 } else {
7149 jexcel.build(document);
7150 jexcel.build = null;
7151 }
7152 }
7153
7154 // Event
7155 el.setAttribute('tabindex', 1);
7156 el.addEventListener('focus', function(e) {
7157 if (jexcel.current && ! obj.selectedCell) {
7158 obj.updateSelectionFromCoords(0,0,0,0);
7159 obj.left();
7160 }
7161 });
7162
7163 // Load the table data based on an CSV file
7164 if (obj.options.csv) {
7165 // Loading
7166 if (obj.options.loadingSpin == true) {
7167 jSuites.loading.show();
7168 }
7169
7170 // Load CSV file
7171 jSuites.ajax({
7172 url: obj.options.csv,
7173 method: obj.options.method,
7174 data: obj.options.requestVariables,
7175 dataType: 'text',
7176 success: function(result) {
7177 // Convert data
7178 var newData = obj.parseCSV(result, obj.options.csvDelimiter)
7179
7180 // Headers
7181 if (obj.options.csvHeaders == true && newData.length > 0) {
7182 var headers = newData.shift();
7183 for(var i = 0; i < headers.length; i++) {
7184 if (! obj.options.columns[i]) {
7185 obj.options.columns[i] = { type:'text', align:obj.options.defaultColAlign, width:obj.options.defaultColWidth };
7186 }
7187 // Precedence over pre-configurated titles
7188 if (typeof obj.options.columns[i].title === 'undefined') {
7189 obj.options.columns[i].title = headers[i];
7190 }
7191 }
7192 }
7193 // Data
7194 obj.options.data = newData;
7195 // Prepare table
7196 obj.prepareTable();
7197 // Hide spin
7198 if (obj.options.loadingSpin == true) {
7199 jSuites.loading.hide();
7200 }
7201 }
7202 });
7203 } else if (obj.options.url) {
7204 // Loading
7205 if (obj.options.loadingSpin == true) {
7206 jSuites.loading.show();
7207 }
7208
7209 jSuites.ajax({
7210 url: obj.options.url,
7211 method: obj.options.method,
7212 data: obj.options.requestVariables,
7213 dataType: 'json',
7214 success: function(result) {
7215 // Data
7216 obj.options.data = (result.data) ? result.data : result;
7217 // Prepare table
7218 obj.prepareTable();
7219 // Hide spin
7220 if (obj.options.loadingSpin == true) {
7221 jSuites.loading.hide();
7222 }
7223 }
7224 });
7225 } else {
7226 // Prepare table
7227 obj.prepareTable();
7228 }
7229 }
7230
7231 // Context menu
7232 if (options && options.contextMenu != null) {
7233 obj.options.contextMenu = options.contextMenu;
7234 } else {
7235 obj.options.contextMenu = function(el, x, y, e) {
7236 var items = [];
7237
7238 if (y == null) {
7239 // Insert a new column
7240 if (obj.options.allowInsertColumn == true) {
7241 items.push({
7242 title:obj.options.text.insertANewColumnBefore,
7243 onclick:function() {
7244 obj.insertColumn(1, parseInt(x), 1);
7245 }
7246 });
7247 }
7248
7249 if (obj.options.allowInsertColumn == true) {
7250 items.push({
7251 title:obj.options.text.insertANewColumnAfter,
7252 onclick:function() {
7253 obj.insertColumn(1, parseInt(x), 0);
7254 }
7255 });
7256 }
7257
7258 // Delete a column
7259 if (obj.options.allowDeleteColumn == true) {
7260 items.push({
7261 title:obj.options.text.deleteSelectedColumns,
7262 onclick:function() {
7263 obj.deleteColumn(obj.getSelectedColumns().length ? undefined : parseInt(x));
7264 }
7265 });
7266 }
7267
7268 // Rename column
7269 if (obj.options.allowRenameColumn == true) {
7270 items.push({
7271 title:obj.options.text.renameThisColumn,
7272 onclick:function() {
7273 obj.setHeader(x);
7274 }
7275 });
7276 }
7277
7278 // Sorting
7279 if (obj.options.columnSorting == true) {
7280 // Line
7281 items.push({ type:'line' });
7282
7283 items.push({
7284 title:obj.options.text.orderAscending,
7285 onclick:function() {
7286 obj.orderBy(x, 0);
7287 }
7288 });
7289 items.push({
7290 title:obj.options.text.orderDescending,
7291 onclick:function() {
7292 obj.orderBy(x, 1);
7293 }
7294 });
7295 }
7296 } else {
7297 // Insert new row
7298 if (obj.options.allowInsertRow == true) {
7299 items.push({
7300 title:obj.options.text.insertANewRowBefore,
7301 onclick:function() {
7302 obj.insertRow(1, parseInt(y), 1);
7303 }
7304 });
7305
7306 items.push({
7307 title:obj.options.text.insertANewRowAfter,
7308 onclick:function() {
7309 obj.insertRow(1, parseInt(y));
7310 }
7311 });
7312 }
7313
7314 if (obj.options.allowDeleteRow == true) {
7315 items.push({
7316 title:obj.options.text.deleteSelectedRows,
7317 onclick:function() {
7318 obj.deleteRow(obj.getSelectedRows().length ? undefined : parseInt(y));
7319 }
7320 });
7321 }
7322
7323 if (x) {
7324 if (obj.options.allowComments == true) {
7325 items.push({ type:'line' });
7326
7327 var title = obj.records[y][x].getAttribute('title') || '';
7328
7329 items.push({
7330 title: title ? obj.options.text.editComments : obj.options.text.addComments,
7331 onclick:function() {
7332 var comment = prompt(obj.options.text.comments, title);
7333 if (comment) {
7334 obj.setComments([ x, y ], comment);
7335 }
7336 }
7337 });
7338
7339 if (title) {
7340 items.push({
7341 title:obj.options.text.clearComments,
7342 onclick:function() {
7343 obj.setComments([ x, y ], '');
7344 }
7345 });
7346 }
7347 }
7348 }
7349 }
7350
7351 // Line
7352 items.push({ type:'line' });
7353
7354 // Copy
7355 items.push({
7356 title:obj.options.text.copy,
7357 shortcut:'Ctrl + C',
7358 onclick:function() {
7359 obj.copy(true);
7360 }
7361 });
7362
7363 // Paste
7364 if (navigator && navigator.clipboard) {
7365 items.push({
7366 title:obj.options.text.paste,
7367 shortcut:'Ctrl + V',
7368 onclick:function() {
7369 if (obj.selectedCell) {
7370 navigator.clipboard.readText().then(function(text) {
7371 if (text) {
7372 jexcel.current.paste(obj.selectedCell[0], obj.selectedCell[1], text);
7373 }
7374 });
7375 }
7376 }
7377 });
7378 }
7379
7380 // Save
7381 if (obj.options.allowExport) {
7382 items.push({
7383 title: obj.options.text.saveAs,
7384 shortcut: 'Ctrl + S',
7385 onclick: function () {
7386 obj.download();
7387 }
7388 });
7389 }
7390
7391 // About
7392 if (obj.options.about) {
7393 items.push({
7394 title:obj.options.text.about,
7395 onclick:function() {
7396 if (obj.options.about === true) {
7397 alert(Version().print());
7398 } else {
7399 alert(obj.options.about);
7400 }
7401 }
7402 });
7403 }
7404
7405 return items;
7406 }
7407 }
7408
7409 obj.scrollControls = function(e) {
7410 obj.wheelControls();
7411
7412 if (obj.options.freezeColumns > 0 && obj.content.scrollLeft != scrollLeft) {
7413 obj.updateFreezePosition();
7414 }
7415
7416 // Close editor
7417 if (obj.options.lazyLoading == true || obj.options.tableOverflow == true) {
7418 if (obj.edition && e.target.className.substr(0,9) != 'jdropdown') {
7419 obj.closeEditor(obj.edition[0], true);
7420 }
7421 }
7422 }
7423
7424 obj.wheelControls = function(e) {
7425 if (obj.options.lazyLoading == true) {
7426 if (jexcel.timeControlLoading == null) {
7427 jexcel.timeControlLoading = setTimeout(function() {
7428 if (obj.content.scrollTop + obj.content.clientHeight >= obj.content.scrollHeight - 10) {
7429 if (obj.loadDown()) {
7430 if (obj.content.scrollTop + obj.content.clientHeight > obj.content.scrollHeight - 10) {
7431 obj.content.scrollTop = obj.content.scrollTop - obj.content.clientHeight;
7432 }
7433 obj.updateCornerPosition();
7434 }
7435 } else if (obj.content.scrollTop <= obj.content.clientHeight) {
7436 if (obj.loadUp()) {
7437 if (obj.content.scrollTop < 10) {
7438 obj.content.scrollTop = obj.content.scrollTop + obj.content.clientHeight;
7439 }
7440 obj.updateCornerPosition();
7441 }
7442 }
7443
7444 jexcel.timeControlLoading = null;
7445 }, 100);
7446 }
7447 }
7448 }
7449
7450 // Get width of all freezed cells together
7451 obj.getFreezeWidth = function() {
7452 var width = 0;
7453 if (obj.options.freezeColumns > 0) {
7454 for (var i = 0; i < obj.options.freezeColumns; i++) {
7455 width += parseInt(obj.options.columns[i].width);
7456 }
7457 }
7458 return width;
7459 }
7460
7461 var scrollLeft = 0;
7462
7463 obj.updateFreezePosition = function() {
7464 scrollLeft = obj.content.scrollLeft;
7465 var width = 0;
7466 if (scrollLeft > 50) {
7467 for (var i = 0; i < obj.options.freezeColumns; i++) {
7468 if (i > 0) {
7469 // Must check if the previous column is hidden or not to determin whether the width shoule be added or not!
7470 if (obj.options.columns[i-1].type !== "hidden") {
7471 width += parseInt(obj.options.columns[i-1].width);
7472 }
7473 }
7474 obj.headers[i].classList.add('jexcel_freezed');
7475 obj.headers[i].style.left = width + 'px';
7476 for (var j = 0; j < obj.rows.length; j++) {
7477 if (obj.rows[j] && obj.records[j][i]) {
7478 var shifted = (scrollLeft + (i > 0 ? obj.records[j][i-1].style.width : 0)) - 51 + 'px';
7479 obj.records[j][i].classList.add('jexcel_freezed');
7480 obj.records[j][i].style.left = shifted;
7481 }
7482 }
7483 }
7484 } else {
7485 for (var i = 0; i < obj.options.freezeColumns; i++) {
7486 obj.headers[i].classList.remove('jexcel_freezed');
7487 obj.headers[i].style.left = '';
7488 for (var j = 0; j < obj.rows.length; j++) {
7489 if (obj.records[j][i]) {
7490 obj.records[j][i].classList.remove('jexcel_freezed');
7491 obj.records[j][i].style.left = '';
7492 }
7493 }
7494 }
7495 }
7496
7497 // Place the corner in the correct place
7498 obj.updateCornerPosition();
7499 }
7500
7501 el.addEventListener("DOMMouseScroll", obj.wheelControls);
7502 el.addEventListener("mousewheel", obj.wheelControls);
7503
7504 el.jexcel = obj;
7505 el.jspreadsheet = obj;
7506
7507 obj.init();
7508
7509 return obj;
7510 });
7511
7512 // Define dictionary
7513 jexcel.setDictionary = function(o) {
7514 jSuites.setDictionary(o);
7515 }
7516
7517 // Define extensions
7518 jexcel.setExtensions = function(o) {
7519 var k = Object.keys(o);
7520 for (var i = 0; i < k.length; i++) {
7521 if (typeof(o[k[i]]) === 'function') {
7522 jexcel[k[i]] = o[k[i]];
7523 if (jexcel.license && typeof(o[k[i]].license) == 'function') {
7524 o[k[i]].license(jexcel.license);
7525 }
7526 }
7527 }
7528 }
7529
7530 /**
7531 * Formulas
7532 */
7533 if (typeof(formula) !== 'undefined') {
7534 jexcel.formula = formula;
7535 }
7536 jexcel.version = Version;
7537
7538 jexcel.current = null;
7539 jexcel.timeControl = null;
7540 jexcel.timeControlLoading = null;
7541
7542 const destroyEvents = function(root) {
7543 root.removeEventListener("mouseup", jexcel.mouseUpControls);
7544 root.removeEventListener("mousedown", jexcel.mouseDownControls);
7545 root.removeEventListener("mousemove", jexcel.mouseMoveControls);
7546 root.removeEventListener("mouseover", jexcel.mouseOverControls);
7547 root.removeEventListener("dblclick", jexcel.doubleClickControls);
7548 root.removeEventListener("paste", jexcel.pasteControls);
7549 root.removeEventListener("contextmenu", jexcel.contextMenuControls);
7550 root.removeEventListener("touchstart", jexcel.touchStartControls);
7551 root.removeEventListener("touchend", jexcel.touchEndControls);
7552 root.removeEventListener("touchcancel", jexcel.touchEndControls);
7553 document.removeEventListener("keydown", jexcel.keyDownControls);
7554 }
7555
7556 jexcel.destroy = function(element, destroyEventHandlers) {
7557 if (element.jexcel) {
7558 var root = element.jexcel.options.root ? element.jexcel.options.root : document;
7559 element.removeEventListener("DOMMouseScroll", element.jexcel.scrollControls);
7560 element.removeEventListener("mousewheel", element.jexcel.scrollControls);
7561 element.jexcel = null;
7562 element.innerHTML = '';
7563
7564 if (destroyEventHandlers) {
7565 destroyEvents(root);
7566 jexcel = null;
7567 }
7568 }
7569 }
7570
7571 jexcel.build = function(root) {
7572 destroyEvents(root);
7573 root.addEventListener("mouseup", jexcel.mouseUpControls);
7574 root.addEventListener("mousedown", jexcel.mouseDownControls);
7575 root.addEventListener("mousemove", jexcel.mouseMoveControls);
7576 root.addEventListener("mouseover", jexcel.mouseOverControls);
7577 root.addEventListener("dblclick", jexcel.doubleClickControls);
7578 root.addEventListener("paste", jexcel.pasteControls);
7579 root.addEventListener("contextmenu", jexcel.contextMenuControls);
7580 root.addEventListener("touchstart", jexcel.touchStartControls);
7581 root.addEventListener("touchend", jexcel.touchEndControls);
7582 root.addEventListener("touchcancel", jexcel.touchEndControls);
7583 root.addEventListener("touchmove", jexcel.touchEndControls);
7584 document.addEventListener("keydown", jexcel.keyDownControls);
7585 }
7586
7587 /**
7588 * Events
7589 */
7590 jexcel.keyDownControls = function(e) {
7591 if (jexcel.current) {
7592 if (jexcel.current.edition) {
7593 if (e.which == 27) {
7594 // Escape
7595 if (jexcel.current.edition) {
7596 // Exit without saving
7597 jexcel.current.closeEditor(jexcel.current.edition[0], false);
7598 }
7599 e.preventDefault();
7600 } else if (e.which == 13) {
7601 // Enter
7602 if (jexcel.current.options.columns[jexcel.current.edition[2]].type == 'calendar') {
7603 jexcel.current.closeEditor(jexcel.current.edition[0], true);
7604 } else if (jexcel.current.options.columns[jexcel.current.edition[2]].type == 'dropdown' ||
7605 jexcel.current.options.columns[jexcel.current.edition[2]].type == 'autocomplete') {
7606 // Do nothing
7607 } else {
7608 // Alt enter -> do not close editor
7609 if ((jexcel.current.options.wordWrap == true ||
7610 jexcel.current.options.columns[jexcel.current.edition[2]].wordWrap == true ||
7611 jexcel.current.options.data[jexcel.current.edition[3]][jexcel.current.edition[2]].length > 200) && e.altKey) {
7612 // Add new line to the editor
7613 var editorTextarea = jexcel.current.edition[0].children[0];
7614 var editorValue = jexcel.current.edition[0].children[0].value;
7615 var editorIndexOf = editorTextarea.selectionStart;
7616 editorValue = editorValue.slice(0, editorIndexOf) + "\n" + editorValue.slice(editorIndexOf);
7617 editorTextarea.value = editorValue;
7618 editorTextarea.focus();
7619 editorTextarea.selectionStart = editorIndexOf + 1;
7620 editorTextarea.selectionEnd = editorIndexOf + 1;
7621 } else {
7622 // TablePress: Comment out next line (leaving the cell when hitting Enter), so that instead a line break is inserted.
7623 // jexcel.current.edition[0].children[0].blur();
7624 }
7625 }
7626 } else if (e.which == 9) {
7627 // Tab
7628 if (['calendar', 'html'].includes(
7629 jexcel.current.options.columns[jexcel.current.edition[2]].type)) {
7630 jexcel.current.closeEditor(jexcel.current.edition[0], true);
7631 } else {
7632 jexcel.current.edition[0].children[0].blur();
7633 }
7634 }
7635 }
7636
7637 if (! jexcel.current.edition && jexcel.current.selectedCell) {
7638 // Which key
7639 if (e.which == 37) {
7640 jexcel.current.left(e.shiftKey, e.ctrlKey);
7641 e.preventDefault();
7642 } else if (e.which == 39) {
7643 jexcel.current.right(e.shiftKey, e.ctrlKey);
7644 e.preventDefault();
7645 } else if (e.which == 38) {
7646 jexcel.current.up(e.shiftKey, e.ctrlKey);
7647 e.preventDefault();
7648 } else if (e.which == 40) {
7649 jexcel.current.down(e.shiftKey, e.ctrlKey);
7650 e.preventDefault();
7651 } else if (e.which == 36) {
7652 jexcel.current.first(e.shiftKey, e.ctrlKey);
7653 e.preventDefault();
7654 } else if (e.which == 35) {
7655 jexcel.current.last(e.shiftKey, e.ctrlKey);
7656 e.preventDefault();
7657 } else if (e.which == 46) {
7658 // Delete
7659 if (jexcel.current.options.editable == true) {
7660 if (jexcel.current.selectedRow) {
7661 if (jexcel.current.options.allowDeleteRow == true) {
7662 if (confirm(jexcel.current.options.text.areYouSureToDeleteTheSelectedRows)) {
7663 jexcel.current.deleteRow();
7664 }
7665 }
7666 } else if (jexcel.current.selectedHeader) {
7667 if (jexcel.current.options.allowDeleteColumn == true) {
7668 if (confirm(jexcel.current.options.text.areYouSureToDeleteTheSelectedColumns)) {
7669 jexcel.current.deleteColumn();
7670 }
7671 }
7672 } else {
7673 // Change value
7674 jexcel.current.setValue(jexcel.current.highlighted, '');
7675 }
7676 }
7677 } else if (e.which == 13) {
7678 // Move cursor
7679 if (e.shiftKey) {
7680 jexcel.current.up();
7681 } else {
7682 if (jexcel.current.options.allowInsertRow == true) {
7683 if (jexcel.current.options.allowManualInsertRow == true) {
7684 if (jexcel.current.selectedCell[1] == jexcel.current.options.data.length - 1) {
7685 // New record in case selectedCell in the last row
7686 jexcel.current.insertRow();
7687 }
7688 }
7689 }
7690
7691 jexcel.current.down();
7692 }
7693 e.preventDefault();
7694 } else if (e.which == 9) {
7695 // Tab
7696 if (e.shiftKey) {
7697 // TablePress: Instead of stopping at the left edge when tabbing, go to the previous row's last cell.
7698 // jexcel.current.left();
7699 if ( jexcel.current.selectedCell[0] == 0 ) {
7700 jexcel.current.up();
7701 jexcel.current.last();
7702 } else {
7703 jexcel.current.left();
7704 }
7705 } else {
7706 if (jexcel.current.options.allowInsertColumn == true) {
7707 if (jexcel.current.options.allowManualInsertColumn == true) {
7708 if (jexcel.current.selectedCell[0] == jexcel.current.options.data[0].length - 1) {
7709 // New record in case selectedCell in the last column
7710 jexcel.current.insertColumn();
7711 }
7712 }
7713 }
7714
7715 // TablePress: Instead of stopping at the right edge when tabbing, go to the next row's first cell.
7716 // jexcel.current.right();
7717 if ( jexcel.current.selectedCell[0] == jexcel.current.options.data[0].length - 1 ) {
7718 jexcel.current.down();
7719 jexcel.current.first();
7720 } else {
7721 jexcel.current.right();
7722 }
7723 }
7724 e.preventDefault();
7725 } else {
7726 if ((e.ctrlKey || e.metaKey) && ! e.shiftKey) {
7727 if (e.which == 65) {
7728 // Ctrl + A
7729 jexcel.current.selectAll();
7730 e.preventDefault();
7731 } else if (e.which == 83) {
7732 // Ctrl + S
7733 jexcel.current.download();
7734 e.preventDefault();
7735 } else if (e.which == 89) {
7736 // Ctrl + Y
7737 jexcel.current.redo();
7738 e.preventDefault();
7739 } else if (e.which == 90) {
7740 // Ctrl + Z
7741 jexcel.current.undo();
7742 e.preventDefault();
7743 } else if (e.which == 67) {
7744 // Ctrl + C
7745 jexcel.current.copy(true);
7746 e.preventDefault();
7747 } else if (e.which == 88) {
7748 // Ctrl + X
7749 if (jexcel.current.options.editable == true) {
7750 jexcel.cutControls();
7751 } else {
7752 jexcel.copyControls();
7753 }
7754 e.preventDefault();
7755 } else if (e.which == 86) {
7756 // Ctrl + V
7757 jexcel.pasteControls();
7758 }
7759 } else {
7760 if (jexcel.current.selectedCell) {
7761 if (jexcel.current.options.editable == true) {
7762 var rowId = jexcel.current.selectedCell[1];
7763 var columnId = jexcel.current.selectedCell[0];
7764
7765 // If is not readonly
7766 if (jexcel.current.options.columns[columnId].type != 'readonly') {
7767 // Characters able to start a edition
7768 if (e.keyCode == 32) {
7769 // Space
7770 e.preventDefault()
7771 if (jexcel.current.options.columns[columnId].type == 'checkbox' ||
7772 jexcel.current.options.columns[columnId].type == 'radio') {
7773 jexcel.current.setCheckRadioValue();
7774 } else {
7775 // Start edition
7776 jexcel.current.openEditor(jexcel.current.records[rowId][columnId], true);
7777 }
7778 } else if (e.keyCode == 113) {
7779 // Start edition with current content F2
7780 jexcel.current.openEditor(jexcel.current.records[rowId][columnId], false);
7781 } else if ((e.keyCode == 8) ||
7782 (e.keyCode >= 48 && e.keyCode <= 57) ||
7783 (e.keyCode >= 96 && e.keyCode <= 111) ||
7784 (e.keyCode >= 187 && e.keyCode <= 190) ||
7785 ((String.fromCharCode(e.keyCode) == e.key || String.fromCharCode(e.keyCode).toLowerCase() == e.key.toLowerCase()) && jexcel.validLetter(String.fromCharCode(e.keyCode)))) {
7786 // Start edition
7787 jexcel.current.openEditor(jexcel.current.records[rowId][columnId], true);
7788 // Prevent entries in the calendar
7789 if (jexcel.current.options.columns[columnId].type == 'calendar') {
7790 e.preventDefault();
7791 }
7792 }
7793 }
7794 }
7795 }
7796 }
7797 }
7798 } else {
7799 if (e.target.classList.contains('jexcel_search')) {
7800 if (jexcel.timeControl) {
7801 clearTimeout(jexcel.timeControl);
7802 }
7803
7804 jexcel.timeControl = setTimeout(function() {
7805 jexcel.current.search(e.target.value);
7806 }, 200);
7807 }
7808 }
7809 }
7810 }
7811
7812 jexcel.isMouseAction = false;
7813
7814 jexcel.mouseDownControls = function(e) {
7815 e = e || window.event;
7816 if (e.buttons) {
7817 var mouseButton = e.buttons;
7818 } else if (e.button) {
7819 var mouseButton = e.button;
7820 } else {
7821 var mouseButton = e.which;
7822 }
7823
7824 // Get elements
7825 var jexcelTable = jexcel.getElement(e.target);
7826
7827 if (jexcelTable[0]) {
7828 if (jexcel.current != jexcelTable[0].jexcel) {
7829 if (jexcel.current) {
7830 if (jexcel.current.edition) {
7831 jexcel.current.closeEditor(jexcel.current.edition[0], true);
7832 }
7833 jexcel.current.resetSelection();
7834 }
7835 jexcel.current = jexcelTable[0].jexcel;
7836 }
7837 } else {
7838 if (jexcel.current) {
7839 if (jexcel.current.edition) {
7840 jexcel.current.closeEditor(jexcel.current.edition[0], true);
7841 }
7842
7843 jexcel.current.resetSelection(true);
7844 jexcel.current = null;
7845 }
7846 }
7847
7848 if (jexcel.current && mouseButton == 1) {
7849 if (e.target.classList.contains('jexcel_selectall')) {
7850 if (jexcel.current) {
7851 jexcel.current.selectAll();
7852 }
7853 } else if (e.target.classList.contains('jexcel_corner')) {
7854 if (jexcel.current.options.editable == true) {
7855 jexcel.current.selectedCorner = true;
7856 }
7857 } else {
7858 // Header found
7859 if (jexcelTable[1] == 1) {
7860 var columnId = e.target.getAttribute('data-x');
7861 if (columnId) {
7862 // Update cursor
7863 var info = e.target.getBoundingClientRect();
7864 if (jexcel.current.options.columnResize == true && info.width - e.offsetX < 12) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region.
7865 // Resize helper
7866 jexcel.current.resizing = {
7867 mousePosition: e.pageX,
7868 column: columnId,
7869 width: info.width,
7870 };
7871
7872 // Border indication
7873 jexcel.current.headers[columnId].classList.add('resizing');
7874 for (var j = 0; j < jexcel.current.records.length; j++) {
7875 if (jexcel.current.records[j][columnId]) {
7876 jexcel.current.records[j][columnId].classList.add('resizing');
7877 }
7878 }
7879 } else if (jexcel.current.options.columnDrag == true && info.height - e.offsetY < 12) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region.
7880 if (jexcel.current.isColMerged(columnId).length) {
7881 console.error('Jspreadsheet: This column is part of a merged cell.');
7882 } else {
7883 // Reset selection
7884 jexcel.current.resetSelection();
7885 // Drag helper
7886 jexcel.current.dragging = {
7887 element: e.target,
7888 column: columnId,
7889 destination: columnId,
7890 };
7891 // Border indication
7892 jexcel.current.headers[columnId].classList.add('dragging');
7893 for (var j = 0; j < jexcel.current.records.length; j++) {
7894 if (jexcel.current.records[j][columnId]) {
7895 jexcel.current.records[j][columnId].classList.add('dragging');
7896 }
7897 }
7898 }
7899 } else {
7900 if (jexcel.current.selectedHeader && (e.shiftKey || e.ctrlKey)) {
7901 var o = jexcel.current.selectedHeader;
7902 var d = columnId;
7903 } else {
7904 // Press to rename
7905 if (jexcel.current.selectedHeader == columnId && jexcel.current.options.allowRenameColumn == true) {
7906 jexcel.timeControl = setTimeout(function() {
7907 jexcel.current.setHeader(columnId);
7908 }, 800);
7909 }
7910
7911 // Keep track of which header was selected first
7912 jexcel.current.selectedHeader = columnId;
7913
7914 // Update selection single column
7915 var o = columnId;
7916 var d = columnId;
7917 }
7918
7919 // Update selection
7920 jexcel.current.updateSelectionFromCoords(o, 0, d, jexcel.current.options.data.length - 1);
7921 }
7922 } else {
7923 if (e.target.parentNode.classList.contains('jexcel_nested')) {
7924 if (e.target.getAttribute('data-column')) {
7925 var column = e.target.getAttribute('data-column').split(',');
7926 var c1 = parseInt(column[0]);
7927 var c2 = parseInt(column[column.length-1]);
7928 } else {
7929 var c1 = 0;
7930 var c2 = jexcel.current.options.columns.length - 1;
7931 }
7932 jexcel.current.updateSelectionFromCoords(c1, 0, c2, jexcel.current.options.data.length - 1);
7933 }
7934 }
7935 } else {
7936 jexcel.current.selectedHeader = false;
7937 }
7938
7939 // Body found
7940 if (jexcelTable[1] == 2) {
7941 var rowId = e.target.getAttribute('data-y');
7942
7943 if (e.target.classList.contains('jexcel_row')) {
7944 var info = e.target.getBoundingClientRect();
7945 if (jexcel.current.options.rowResize == true && info.height - e.offsetY < 12) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region.
7946 // Resize helper
7947 jexcel.current.resizing = {
7948 element: e.target.parentNode,
7949 mousePosition: e.pageY,
7950 row: rowId,
7951 height: info.height,
7952 };
7953 // Border indication
7954 e.target.parentNode.classList.add('resizing');
7955 } else if (jexcel.current.options.rowDrag == true && info.width - e.offsetX < 18) { // TablePress: Changed from 6 to 18 pixels to increase draggable mouse region.
7956 if (jexcel.current.isRowMerged(rowId).length) {
7957 console.error('Jspreadsheet: This row is part of a merged cell');
7958 } else if (jexcel.current.options.search == true && jexcel.current.results) {
7959 console.error('Jspreadsheet: Please clear your search before perform this action');
7960 } else {
7961 // Reset selection
7962 jexcel.current.resetSelection();
7963 // Drag helper
7964 jexcel.current.dragging = {
7965 element: e.target.parentNode,
7966 row:rowId,
7967 destination:rowId,
7968 };
7969 // Border indication
7970 e.target.parentNode.classList.add('dragging');
7971 }
7972 } else {
7973 if (jexcel.current.selectedRow && (e.shiftKey || e.ctrlKey)) {
7974 var o = jexcel.current.selectedRow;
7975 var d = rowId;
7976 } else {
7977 // Keep track of which header was selected first
7978 jexcel.current.selectedRow = rowId;
7979
7980 // Update selection single column
7981 var o = rowId;
7982 var d = rowId;
7983 }
7984
7985 // Update selection
7986 jexcel.current.updateSelectionFromCoords(0, o, jexcel.current.options.data[0].length - 1, d);
7987 }
7988 } else {
7989 // Jclose
7990 if (e.target.classList.contains('jclose') && e.target.clientWidth - e.offsetX < 50 && e.offsetY < 50) {
7991 jexcel.current.closeEditor(jexcel.current.edition[0], true);
7992 } else {
7993 var getCellCoords = function(element) {
7994 var x = element.getAttribute('data-x');
7995 var y = element.getAttribute('data-y');
7996 if (x && y) {
7997 return [x, y];
7998 } else {
7999 if (element.parentNode) {
8000 return getCellCoords(element.parentNode);
8001 }
8002 }
8003 };
8004
8005 var position = getCellCoords(e.target);
8006 if (position) {
8007
8008 var columnId = position[0];
8009 var rowId = position[1];
8010 // Close edition
8011 if (jexcel.current.edition) {
8012 if (jexcel.current.edition[2] != columnId || jexcel.current.edition[3] != rowId) {
8013 jexcel.current.closeEditor(jexcel.current.edition[0], true);
8014 }
8015 }
8016
8017 if (! jexcel.current.edition) {
8018 // Update cell selection
8019 if (e.shiftKey) {
8020 jexcel.current.updateSelectionFromCoords(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], columnId, rowId);
8021 } else {
8022 jexcel.current.updateSelectionFromCoords(columnId, rowId);
8023 }
8024 }
8025
8026 // No full row selected
8027 jexcel.current.selectedHeader = null;
8028 jexcel.current.selectedRow = null;
8029 }
8030 }
8031 }
8032 } else {
8033 jexcel.current.selectedRow = false;
8034 }
8035
8036 // Pagination
8037 if (e.target.classList.contains('jexcel_page')) {
8038 if (e.target.textContent == '<') {
8039 jexcel.current.page(0);
8040 } else if (e.target.textContent == '>') {
8041 jexcel.current.page(e.target.getAttribute('title') - 1);
8042 } else {
8043 jexcel.current.page(e.target.textContent - 1);
8044 }
8045 }
8046 }
8047
8048 if (jexcel.current.edition) {
8049 jexcel.isMouseAction = false;
8050 } else {
8051 jexcel.isMouseAction = true;
8052 }
8053 } else {
8054 jexcel.isMouseAction = false;
8055 }
8056 }
8057
8058 jexcel.mouseUpControls = function(e) {
8059 if (jexcel.current) {
8060 // Update cell size
8061 if (jexcel.current.resizing) {
8062 // Columns to be updated
8063 if (jexcel.current.resizing.column) {
8064 // New width
8065 var newWidth = jexcel.current.colgroup[jexcel.current.resizing.column].getAttribute('width');
8066 // Columns
8067 var columns = jexcel.current.getSelectedColumns();
8068 if (columns.length > 1) {
8069 var currentWidth = [];
8070 for (var i = 0; i < columns.length; i++) {
8071 currentWidth.push(parseInt(jexcel.current.colgroup[columns[i]].getAttribute('width')));
8072 }
8073 // Previous width
8074 var index = columns.indexOf(parseInt(jexcel.current.resizing.column));
8075 currentWidth[index] = jexcel.current.resizing.width;
8076 jexcel.current.setWidth(columns, newWidth, currentWidth);
8077 } else {
8078 jexcel.current.setWidth(jexcel.current.resizing.column, newWidth, jexcel.current.resizing.width);
8079 }
8080 // Remove border
8081 jexcel.current.headers[jexcel.current.resizing.column].classList.remove('resizing');
8082 for (var j = 0; j < jexcel.current.records.length; j++) {
8083 if (jexcel.current.records[j][jexcel.current.resizing.column]) {
8084 jexcel.current.records[j][jexcel.current.resizing.column].classList.remove('resizing');
8085 }
8086 }
8087 } else {
8088 // Remove Class
8089 jexcel.current.rows[jexcel.current.resizing.row].children[0].classList.remove('resizing');
8090 var newHeight = jexcel.current.rows[jexcel.current.resizing.row].getAttribute('height');
8091 jexcel.current.setHeight(jexcel.current.resizing.row, newHeight, jexcel.current.resizing.height);
8092 // Remove border
8093 jexcel.current.resizing.element.classList.remove('resizing');
8094 }
8095 // Reset resizing helper
8096 jexcel.current.resizing = null;
8097 } else if (jexcel.current.dragging) {
8098 // Reset dragging helper
8099 if (jexcel.current.dragging) {
8100 if (jexcel.current.dragging.column) {
8101 // Target
8102 var columnId = e.target.getAttribute('data-x');
8103 // Remove move style
8104 jexcel.current.headers[jexcel.current.dragging.column].classList.remove('dragging');
8105 for (var j = 0; j < jexcel.current.rows.length; j++) {
8106 if (jexcel.current.records[j][jexcel.current.dragging.column]) {
8107 jexcel.current.records[j][jexcel.current.dragging.column].classList.remove('dragging');
8108 }
8109 }
8110 for (var i = 0; i < jexcel.current.headers.length; i++) {
8111 jexcel.current.headers[i].classList.remove('dragging-left');
8112 jexcel.current.headers[i].classList.remove('dragging-right');
8113 }
8114 // Update position
8115 if (columnId) {
8116 if (jexcel.current.dragging.column != jexcel.current.dragging.destination) {
8117 jexcel.current.moveColumn(jexcel.current.dragging.column, jexcel.current.dragging.destination);
8118 }
8119 }
8120 } else {
8121 if (jexcel.current.dragging.element.nextSibling) {
8122 var position = parseInt(jexcel.current.dragging.element.nextSibling.getAttribute('data-y'));
8123 if (jexcel.current.dragging.row < position) {
8124 position -= 1;
8125 }
8126 } else {
8127 var position = parseInt(jexcel.current.dragging.element.previousSibling.getAttribute('data-y'));
8128 }
8129 if (jexcel.current.dragging.row != jexcel.current.dragging.destination) {
8130 jexcel.current.moveRow(jexcel.current.dragging.row, position, true);
8131 }
8132 jexcel.current.dragging.element.classList.remove('dragging');
8133 }
8134 jexcel.current.dragging = null;
8135 }
8136 } else {
8137 // Close any corner selection
8138 if (jexcel.current.selectedCorner) {
8139 jexcel.current.selectedCorner = false;
8140
8141 // Data to be copied
8142 if (jexcel.current.selection.length > 0) {
8143 // Copy data
8144 jexcel.current.copyData(jexcel.current.selection[0], jexcel.current.selection[jexcel.current.selection.length - 1]);
8145
8146 // Remove selection
8147 jexcel.current.removeCopySelection();
8148 }
8149 }
8150 }
8151 }
8152
8153 // Clear any time control
8154 if (jexcel.timeControl) {
8155 clearTimeout(jexcel.timeControl);
8156 jexcel.timeControl = null;
8157 }
8158
8159 // Mouse up
8160 jexcel.isMouseAction = false;
8161 }
8162
8163 // Mouse move controls
8164 jexcel.mouseMoveControls = function(e) {
8165 e = e || window.event;
8166 if (e.buttons) {
8167 var mouseButton = e.buttons;
8168 } else if (e.button) {
8169 var mouseButton = e.button;
8170 } else {
8171 var mouseButton = e.which;
8172 }
8173
8174 if (! mouseButton) {
8175 jexcel.isMouseAction = false;
8176 }
8177
8178 if (jexcel.current) {
8179 if (jexcel.isMouseAction == true) {
8180 // Resizing is ongoing
8181 if (jexcel.current.resizing) {
8182 if (jexcel.current.resizing.column) {
8183 var width = e.pageX - jexcel.current.resizing.mousePosition;
8184
8185 if (jexcel.current.resizing.width + width > 0) {
8186 var tempWidth = jexcel.current.resizing.width + width;
8187 jexcel.current.colgroup[jexcel.current.resizing.column].setAttribute('width', tempWidth);
8188
8189 jexcel.current.updateCornerPosition();
8190 }
8191 } else {
8192 var height = e.pageY - jexcel.current.resizing.mousePosition;
8193
8194 if (jexcel.current.resizing.height + height > 0) {
8195 var tempHeight = jexcel.current.resizing.height + height;
8196 jexcel.current.rows[jexcel.current.resizing.row].setAttribute('height', tempHeight);
8197 // TablePress: Add a custom CSS variable to adjust the number of shown lines of text to the row height.
8198 jexcel.current.rows[jexcel.current.resizing.row].style.setProperty( '--table-editor-line-clamp', Math.floor( (tempHeight-10) / 14 ) );
8199 jexcel.current.updateCornerPosition();
8200 }
8201 }
8202 } else if (jexcel.current.dragging) {
8203 if (jexcel.current.dragging.column) {
8204 var columnId = e.target.getAttribute('data-x');
8205 if (columnId) {
8206
8207 if (jexcel.current.isColMerged(columnId).length) {
8208 console.error('Jspreadsheet: This column is part of a merged cell.');
8209 } else {
8210 for (var i = 0; i < jexcel.current.headers.length; i++) {
8211 jexcel.current.headers[i].classList.remove('dragging-left');
8212 jexcel.current.headers[i].classList.remove('dragging-right');
8213 }
8214
8215 if (jexcel.current.dragging.column == columnId) {
8216 jexcel.current.dragging.destination = parseInt(columnId);
8217 } else {
8218 if (e.target.clientWidth / 2 > e.offsetX) {
8219 if (jexcel.current.dragging.column < columnId) {
8220 jexcel.current.dragging.destination = parseInt(columnId) - 1;
8221 } else {
8222 jexcel.current.dragging.destination = parseInt(columnId);
8223 }
8224 jexcel.current.headers[columnId].classList.add('dragging-left');
8225 } else {
8226 if (jexcel.current.dragging.column < columnId) {
8227 jexcel.current.dragging.destination = parseInt(columnId);
8228 } else {
8229 jexcel.current.dragging.destination = parseInt(columnId) + 1;
8230 }
8231 jexcel.current.headers[columnId].classList.add('dragging-right');
8232 }
8233 }
8234 }
8235 }
8236 } else {
8237 var rowId = e.target.getAttribute('data-y');
8238 if (rowId) {
8239 if (jexcel.current.isRowMerged(rowId).length) {
8240 console.error('Jspreadsheet: This row is part of a merged cell.');
8241 } else {
8242 var target = (e.target.clientHeight / 2 > e.offsetY) ? e.target.parentNode.nextSibling : e.target.parentNode;
8243 if (jexcel.current.dragging.element != target) {
8244 e.target.parentNode.parentNode.insertBefore(jexcel.current.dragging.element, target);
8245 jexcel.current.dragging.destination = Array.prototype.indexOf.call(jexcel.current.dragging.element.parentNode.children, jexcel.current.dragging.element);
8246 }
8247 }
8248 }
8249 }
8250 }
8251 } else {
8252 var x = e.target.getAttribute('data-x');
8253 var y = e.target.getAttribute('data-y');
8254 var rect = e.target.getBoundingClientRect();
8255
8256 if (jexcel.current.cursor) {
8257 jexcel.current.cursor.style.cursor = '';
8258 jexcel.current.cursor = null;
8259 }
8260
8261 if (e.target.parentNode.parentNode && e.target.parentNode.parentNode.className) {
8262 if (e.target.parentNode.parentNode.classList.contains('resizable')) {
8263 if (e.target && x && ! y && (rect.width - (e.clientX - rect.left) < 12)) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region.
8264 jexcel.current.cursor = e.target;
8265 jexcel.current.cursor.style.cursor = 'col-resize';
8266 } else if (e.target && ! x && y && (rect.height - (e.clientY - rect.top) < 12)) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region.
8267 jexcel.current.cursor = e.target;
8268 jexcel.current.cursor.style.cursor = 'row-resize';
8269 }
8270 }
8271
8272 if (e.target.parentNode.parentNode.classList.contains('draggable')) {
8273 if (e.target && ! x && y && (rect.width - (e.clientX - rect.left) < 18)) { // TablePress: Changed from 6 to 18 pixels to increase draggable mouse region.
8274 jexcel.current.cursor = e.target;
8275 jexcel.current.cursor.style.cursor = 'move';
8276 } else if (e.target && x && ! y && (rect.height - (e.clientY - rect.top) < 12) && (e.clientX - rect.left < rect.width - 24)) { // TablePress: Changed from 6 to 12 pixels to increase draggable mouse region, and extended to not span full width.
8277 jexcel.current.cursor = e.target;
8278 jexcel.current.cursor.style.cursor = 'move';
8279 }
8280 }
8281 }
8282 }
8283 }
8284 }
8285
8286 jexcel.mouseOverControls = function(e) {
8287 e = e || window.event;
8288 if (e.buttons) {
8289 var mouseButton = e.buttons;
8290 } else if (e.button) {
8291 var mouseButton = e.button;
8292 } else {
8293 var mouseButton = e.which;
8294 }
8295
8296 if (! mouseButton) {
8297 jexcel.isMouseAction = false;
8298 }
8299
8300 if (jexcel.current && jexcel.isMouseAction == true) {
8301 // Get elements
8302 var jexcelTable = jexcel.getElement(e.target);
8303
8304 if (jexcelTable[0]) {
8305 // Avoid cross reference
8306 if (jexcel.current != jexcelTable[0].jexcel) {
8307 if (jexcel.current) {
8308 return false;
8309 }
8310 }
8311
8312 var columnId = e.target.getAttribute('data-x');
8313 var rowId = e.target.getAttribute('data-y');
8314 if (jexcel.current.resizing || jexcel.current.dragging) {
8315 } else {
8316 // Header found
8317 if (jexcelTable[1] == 1) {
8318 if (jexcel.current.selectedHeader) {
8319 var columnId = e.target.getAttribute('data-x');
8320 var o = jexcel.current.selectedHeader;
8321 var d = columnId;
8322 // Update selection
8323 jexcel.current.updateSelectionFromCoords(o, 0, d, jexcel.current.options.data.length - 1);
8324 }
8325 }
8326
8327 // Body found
8328 if (jexcelTable[1] == 2) {
8329 if (e.target.classList.contains('jexcel_row')) {
8330 if (jexcel.current.selectedRow) {
8331 var o = jexcel.current.selectedRow;
8332 var d = rowId;
8333 // Update selection
8334 jexcel.current.updateSelectionFromCoords(0, o, jexcel.current.options.data[0].length - 1, d);
8335 }
8336 } else {
8337 // Do not select edtion is in progress
8338 if (! jexcel.current.edition) {
8339 if (columnId && rowId) {
8340 if (jexcel.current.selectedCorner) {
8341 jexcel.current.updateCopySelection(columnId, rowId);
8342 } else {
8343 if (jexcel.current.selectedCell) {
8344 jexcel.current.updateSelectionFromCoords(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], columnId, rowId);
8345 }
8346 }
8347 }
8348 }
8349 }
8350 }
8351 }
8352 }
8353 }
8354
8355 // Clear any time control
8356 if (jexcel.timeControl) {
8357 clearTimeout(jexcel.timeControl);
8358 jexcel.timeControl = null;
8359 }
8360 }
8361
8362 /**
8363 * Double click event handler: controls the double click in the corner, cell edition or column re-ordering.
8364 */
8365 jexcel.doubleClickControls = function(e) {
8366 // Jexcel is selected
8367 if (jexcel.current) {
8368 // Corner action
8369 if (e.target.classList.contains('jexcel_corner')) {
8370 // Any selected cells
8371 if (jexcel.current.highlighted.length > 0) {
8372 // Copy from this
8373 var x1 = jexcel.current.highlighted[0].getAttribute('data-x');
8374 var y1 = parseInt(jexcel.current.highlighted[jexcel.current.highlighted.length - 1].getAttribute('data-y')) + 1;
8375 // Until this
8376 var x2 = jexcel.current.highlighted[jexcel.current.highlighted.length - 1].getAttribute('data-x');
8377 var y2 = jexcel.current.records.length - 1
8378 // Execute copy
8379 jexcel.current.copyData(jexcel.current.records[y1][x1], jexcel.current.records[y2][x2]);
8380 }
8381 } else if (e.target.classList.contains('jexcel_column_filter')) {
8382 // Column
8383 var columnId = e.target.getAttribute('data-x');
8384 // Open filter
8385 jexcel.current.openFilter(columnId);
8386
8387 } else {
8388 // Get table
8389 var jexcelTable = jexcel.getElement(e.target);
8390
8391 // Double click over header
8392 if (jexcelTable[1] == 1 && jexcel.current.options.columnSorting == true) {
8393 // Check valid column header coords
8394 var columnId = e.target.getAttribute('data-x');
8395 if (columnId) {
8396 jexcel.current.orderBy(columnId);
8397 }
8398 }
8399
8400 // Double click over body
8401 if (jexcelTable[1] == 2 && jexcel.current.options.editable == true) {
8402 if (! jexcel.current.edition) {
8403 var getCellCoords = function(element) {
8404 if (element.parentNode) {
8405 var x = element.getAttribute('data-x');
8406 var y = element.getAttribute('data-y');
8407 if (x && y) {
8408 return element;
8409 } else {
8410 return getCellCoords(element.parentNode);
8411 }
8412 }
8413 }
8414 var cell = getCellCoords(e.target);
8415 if (cell && cell.classList.contains('highlight')) {
8416 jexcel.current.openEditor(cell);
8417 }
8418 }
8419 }
8420 }
8421 }
8422 }
8423
8424 jexcel.copyControls = function(e) {
8425 if (jexcel.current && jexcel.copyControls.enabled) {
8426 if (! jexcel.current.edition) {
8427 jexcel.current.copy(true);
8428 }
8429 }
8430 }
8431
8432 jexcel.cutControls = function(e) {
8433 if (jexcel.current) {
8434 if (! jexcel.current.edition) {
8435 jexcel.current.copy(true);
8436 if (jexcel.current.options.editable == true) {
8437 jexcel.current.setValue(jexcel.current.highlighted, '');
8438 }
8439 }
8440 }
8441 }
8442
8443 jexcel.pasteControls = function(e) {
8444 if (jexcel.current && jexcel.current.selectedCell) {
8445 if (! jexcel.current.edition) {
8446 if (jexcel.current.options.editable == true) {
8447 if (e && e.clipboardData) {
8448 jexcel.current.paste(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], e.clipboardData.getData('text'));
8449 e.preventDefault();
8450 } else if (window.clipboardData) {
8451 jexcel.current.paste(jexcel.current.selectedCell[0], jexcel.current.selectedCell[1], window.clipboardData.getData('text'));
8452 }
8453 }
8454 }
8455 }
8456 }
8457
8458 jexcel.contextMenuControls = function(e) {
8459 e = e || window.event;
8460 if ("buttons" in e) {
8461 var mouseButton = e.buttons;
8462 } else {
8463 var mouseButton = e.which || e.button;
8464 }
8465
8466 if (jexcel.current) {
8467 // TablePress: Don't prevent showing the context menu when editing a focussed cell.
8468 //if (jexcel.current.edition) {
8469 //e.preventDefault();
8470 //} else
8471 if (jexcel.current.options.contextMenu) {
8472 jexcel.current.contextMenu.contextmenu.close();
8473
8474 if (jexcel.current) {
8475 // TablePress: Take into account the inserted DIV element and the editor TEXTAREA when opening the context menu.
8476 var target = (e.target.tagName === 'DIV' || e.target.tagName === 'TEXTAREA') ? e.target.parentNode : e.target;
8477 var x = target.getAttribute('data-x');
8478 var y = target.getAttribute('data-y');
8479
8480 if (x || y) {
8481 /* // TablePress: Fix the comparison of the clicked cell with the selected cell range (e.g. when a right click happens outside of the selected range).
8482 if ((x < parseInt(jexcel.current.selectedCell[0])) || (x > parseInt(jexcel.current.selectedCell[2])) ||
8483 (y < parseInt(jexcel.current.selectedCell[1])) || (y > parseInt(jexcel.current.selectedCell[3])))
8484 */
8485 const selection = jexcel.current.selectedCell;
8486 const selected_cell = {
8487 l: Math.min( parseInt( selection[0] ), parseInt( selection[2] ) ),
8488 r: Math.max( parseInt( selection[0] ), parseInt( selection[2] ) ),
8489 t: Math.min( parseInt( selection[1] ), parseInt( selection[3] ) ),
8490 b: Math.max( parseInt( selection[1] ), parseInt( selection[3] ) ),
8491 };
8492 if ( x < selected_cell.l || x > selected_cell.r || y < selected_cell.t || y > selected_cell.b ) {
8493 jexcel.current.updateSelectionFromCoords(x, y, x, y);
8494 }
8495 // TablePress: End of change.
8496
8497 // Table found
8498 var items = jexcel.current.options.contextMenu(jexcel.current, x, y, e);
8499 // The id is depending on header and body
8500 jexcel.current.contextMenu.contextmenu.open(e, items);
8501 // Avoid the real one
8502 e.preventDefault();
8503 }
8504 }
8505 }
8506 }
8507 }
8508
8509 jexcel.touchStartControls = function(e) {
8510 var jexcelTable = jexcel.getElement(e.target);
8511
8512 if (jexcelTable[0]) {
8513 if (jexcel.current != jexcelTable[0].jexcel) {
8514 if (jexcel.current) {
8515 jexcel.current.resetSelection();
8516 }
8517 jexcel.current = jexcelTable[0].jexcel;
8518 }
8519 } else {
8520 if (jexcel.current) {
8521 jexcel.current.resetSelection();
8522 jexcel.current = null;
8523 }
8524 }
8525
8526 if (jexcel.current) {
8527 if (! jexcel.current.edition) {
8528 var columnId = e.target.getAttribute('data-x');
8529 var rowId = e.target.getAttribute('data-y');
8530
8531 if (columnId && rowId) {
8532 jexcel.current.updateSelectionFromCoords(columnId, rowId);
8533
8534 jexcel.timeControl = setTimeout(function() {
8535 // Keep temporary reference to the element
8536 if (jexcel.current.options.columns[columnId].type == 'color') {
8537 jexcel.tmpElement = null;
8538 } else {
8539 jexcel.tmpElement = e.target;
8540 }
8541 jexcel.current.openEditor(e.target, false, e);
8542 }, 500);
8543 }
8544 }
8545 }
8546 }
8547
8548 jexcel.touchEndControls = function(e) {
8549 // Clear any time control
8550 if (jexcel.timeControl) {
8551 clearTimeout(jexcel.timeControl);
8552 jexcel.timeControl = null;
8553 // Element
8554 if (jexcel.tmpElement && jexcel.tmpElement.children[0].tagName == 'INPUT') {
8555 jexcel.tmpElement.children[0].focus();
8556 }
8557 jexcel.tmpElement = null;
8558 }
8559 }
8560
8561 /**
8562 * Jexcel extensions
8563 */
8564
8565 jexcel.tabs = function(tabs, result) {
8566 var instances = [];
8567 // Create tab container
8568 if (! tabs.classList.contains('jexcel_tabs')) {
8569 tabs.innerHTML = '';
8570 tabs.classList.add('jexcel_tabs')
8571 tabs.jexcel = [];
8572
8573 var div = document.createElement('div');
8574 var headers = tabs.appendChild(div);
8575 var div = document.createElement('div');
8576 var content = tabs.appendChild(div);
8577 } else {
8578 var headers = tabs.children[0];
8579 var content = tabs.children[1];
8580 }
8581
8582 var spreadsheet = []
8583 var link = [];
8584 for (var i = 0; i < result.length; i++) {
8585 // Spreadsheet container
8586 spreadsheet[i] = document.createElement('div');
8587 spreadsheet[i].classList.add('jexcel_tab');
8588 var worksheet = jexcel(spreadsheet[i], result[i]);
8589 content.appendChild(spreadsheet[i]);
8590 instances[i] = tabs.jexcel.push(worksheet);
8591
8592 // Tab link
8593 link[i] = document.createElement('div');
8594 link[i].classList.add('jexcel_tab_link');
8595 link[i].setAttribute('data-spreadsheet', tabs.jexcel.length-1);
8596 link[i].innerHTML = result[i].sheetName;
8597 link[i].onclick = function() {
8598 for (var j = 0; j < headers.children.length; j++) {
8599 headers.children[j].classList.remove('selected');
8600 content.children[j].style.display = 'none';
8601 }
8602 var i = this.getAttribute('data-spreadsheet');
8603 content.children[i].style.display = 'block';
8604 headers.children[i].classList.add('selected')
8605 }
8606 headers.appendChild(link[i]);
8607 }
8608
8609 // First tab
8610 for (var j = 0; j < headers.children.length; j++) {
8611 headers.children[j].classList.remove('selected');
8612 content.children[j].style.display = 'none';
8613 }
8614 headers.children[headers.children.length - 1].classList.add('selected');
8615 content.children[headers.children.length - 1].style.display = 'block';
8616
8617 return instances;
8618 }
8619
8620 // Compability to older versions
8621 jexcel.createTabs = jexcel.tabs;
8622
8623 jexcel.fromSpreadsheet = function(file, __callback) {
8624 var convert = function(workbook) {
8625 var spreadsheets = [];
8626 workbook.SheetNames.forEach(function(sheetName) {
8627 var spreadsheet = {};
8628 spreadsheet.rows = [];
8629 spreadsheet.columns = [];
8630 spreadsheet.data = [];
8631 spreadsheet.style = {};
8632 spreadsheet.sheetName = sheetName;
8633
8634 // Column widths
8635 var temp = workbook.Sheets[sheetName]['!cols'];
8636 if (temp && temp.length) {
8637 for (var i = 0; i < temp.length; i++) {
8638 spreadsheet.columns[i] = {};
8639 if (temp[i] && temp[i].wpx) {
8640 spreadsheet.columns[i].width = temp[i].wpx + 'px';
8641 }
8642 }
8643 }
8644 // Rows heights
8645 var temp = workbook.Sheets[sheetName]['!rows'];
8646 if (temp && temp.length) {
8647 for (var i = 0; i < temp.length; i++) {
8648 if (temp[i] && temp[i].hpx) {
8649 spreadsheet.rows[i] = {};
8650 spreadsheet.rows[i].height = temp[i].hpx + 'px';
8651 }
8652 }
8653 }
8654 // Merge cells
8655 var temp = workbook.Sheets[sheetName]['!merges'];
8656 if (temp && temp.length > 0) {
8657 spreadsheet.mergeCells = [];
8658 for (var i = 0; i < temp.length; i++) {
8659 var x1 = temp[i].s.c;
8660 var y1 = temp[i].s.r;
8661 var x2 = temp[i].e.c;
8662 var y2 = temp[i].e.r;
8663 var key = jexcel.getColumnNameFromId([x1,y1]);
8664 spreadsheet.mergeCells[key] = [ x2-x1+1, y2-y1+1 ];
8665 }
8666 }
8667 // Data container
8668 var max_x = 0;
8669 var max_y = 0;
8670 var temp = Object.keys(workbook.Sheets[sheetName]);
8671 for (var i = 0; i < temp.length; i++) {
8672 if (temp[i].substr(0,1) != '!') {
8673 var cell = workbook.Sheets[sheetName][temp[i]];
8674 var info = jexcel.getIdFromColumnName(temp[i], true);
8675 if (! spreadsheet.data[info[1]]) {
8676 spreadsheet.data[info[1]] = [];
8677 }
8678 spreadsheet.data[info[1]][info[0]] = cell.f ? '=' + cell.f : cell.w;
8679 if (max_x < info[0]) {
8680 max_x = info[0];
8681 }
8682 if (max_y < info[1]) {
8683 max_y = info[1];
8684 }
8685 // Style
8686 if (cell.style && Object.keys(cell.style).length > 0) {
8687 spreadsheet.style[temp[i]] = cell.style;
8688 }
8689 if (cell.s && cell.s.fgColor) {
8690 if (spreadsheet.style[temp[i]]) {
8691 spreadsheet.style[temp[i]] += ';';
8692 }
8693 spreadsheet.style[temp[i]] += 'background-color:#' + cell.s.fgColor.rgb;
8694 }
8695 }
8696 }
8697 var numColumns = spreadsheet.columns;
8698 for (var j = 0; j <= max_y; j++) {
8699 for (var i = 0; i <= max_x; i++) {
8700 if (! spreadsheet.data[j]) {
8701 spreadsheet.data[j] = [];
8702 }
8703 if (! spreadsheet.data[j][i]) {
8704 if (numColumns < i) {
8705 spreadsheet.data[j][i] = '';
8706 }
8707 }
8708 }
8709 }
8710 spreadsheets.push(spreadsheet);
8711 });
8712
8713 return spreadsheets;
8714 }
8715
8716 var oReq;
8717 oReq = new XMLHttpRequest();
8718 oReq.open("GET", file, true);
8719
8720 if(typeof Uint8Array !== 'undefined') {
8721 oReq.responseType = "arraybuffer";
8722 oReq.onload = function(e) {
8723 var arraybuffer = oReq.response;
8724 var data = new Uint8Array(arraybuffer);
8725 var wb = XLSX.read(data, {type:"array", cellFormula:true, cellStyles:true });
8726 __callback(convert(wb))
8727 };
8728 } else {
8729 oReq.setRequestHeader("Accept-Charset", "x-user-defined");
8730 oReq.onreadystatechange = function() { if(oReq.readyState == 4 && oReq.status == 200) {
8731 var ff = convertResponseBodyToText(oReq.responseBody);
8732 var wb = XLSX.read(ff, {type:"binary", cellFormula:true, cellStyles:true });
8733 __callback(convert(wb))
8734 }};
8735 }
8736
8737 oReq.send();
8738 }
8739
8740 /**
8741 * Valid international letter
8742 */
8743
8744 jexcel.validLetter = function (text) {
8745 var regex = /([\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC-\u0400-\u04FF']+)/g;
8746 return text.match(regex) ? 1 : 0;
8747 }
8748
8749 /**
8750 * Helper injectArray
8751 */
8752 jexcel.injectArray = function(o, idx, arr) {
8753 return o.slice(0, idx).concat(arr).concat(o.slice(idx));
8754 }
8755
8756 /**
8757 * Get letter based on a number
8758 *
8759 * @param {number} columnNumber
8760 * @return string letter
8761 */
8762 jexcel.getColumnName = function(columnNumber) {
8763 return jexcel.helpers.getColumnName(columnNumber);
8764 }
8765
8766 /**
8767 * Convert excel like column to jexcel id
8768 *
8769 * @param string id
8770 * @return string id
8771 */
8772 jexcel.getIdFromColumnName = function (id, arr) {
8773 // Get the letters
8774 var t = /^[a-zA-Z]+/.exec(id);
8775
8776 if (t) {
8777 // Base 26 calculation
8778 var code = 0;
8779 for (var i = 0; i < t[0].length; i++) {
8780 code += parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, (t[0].length - 1 - i));
8781 }
8782 code--;
8783 // Make sure jexcel starts on zero
8784 if (code < 0) {
8785 code = 0;
8786 }
8787
8788 // Number
8789 var number = parseInt(/[0-9]+$/.exec(id));
8790 if (number > 0) {
8791 number--;
8792 }
8793
8794 if (arr == true) {
8795 id = [ code, number ];
8796 } else {
8797 id = code + '-' + number;
8798 }
8799 }
8800
8801 return id;
8802 }
8803
8804 /**
8805 * Convert jexcel id to excel like column name
8806 *
8807 * @param string id
8808 * @return string id
8809 */
8810 jexcel.getColumnNameFromId = function (cellId) {
8811 if (! Array.isArray(cellId)) {
8812 cellId = cellId.split('-');
8813 }
8814
8815 return jexcel.getColumnName(parseInt(cellId[0])) + (parseInt(cellId[1]) + 1);
8816 }
8817
8818 /**
8819 * Verify element inside jexcel table
8820 *
8821 * @param string id
8822 * @return string id
8823 */
8824 jexcel.getElement = function(element) {
8825 var jexcelSection = 0;
8826 var jexcelElement = 0;
8827
8828 function path (element) {
8829 if (element.className) {
8830 if (element.classList.contains('jexcel_container')) {
8831 jexcelElement = element;
8832 }
8833 }
8834
8835 if (element.tagName == 'THEAD') {
8836 jexcelSection = 1;
8837 } else if (element.tagName == 'TBODY') {
8838 jexcelSection = 2;
8839 }
8840
8841 if (element.parentNode) {
8842 if (! jexcelElement) {
8843 path(element.parentNode);
8844 }
8845 }
8846 }
8847
8848 path(element);
8849
8850 return [ jexcelElement, jexcelSection ];
8851 }
8852
8853 jexcel.doubleDigitFormat = function(v) {
8854 v = ''+v;
8855 if (v.length == 1) {
8856 v = '0'+v;
8857 }
8858 return v;
8859 }
8860
8861 jexcel.createFromTable = function(el, options) {
8862 if (el.tagName != 'TABLE') {
8863 console.log('Element is not a table');
8864 } else {
8865 // Configuration
8866 if (! options) {
8867 options = {};
8868 }
8869 options.columns = [];
8870 options.data = [];
8871
8872 // Colgroup
8873 var colgroup = el.querySelectorAll('colgroup > col');
8874 if (colgroup.length) {
8875 // Get column width
8876 for (var i = 0; i < colgroup.length; i++) {
8877 var width = colgroup[i].style.width;
8878 if (! width) {
8879 var width = colgroup[i].getAttribute('width');
8880 }
8881 // Set column width
8882 if (width) {
8883 if (! options.columns[i]) {
8884 options.columns[i] = {}
8885 }
8886 options.columns[i].width = width;
8887 }
8888 }
8889 }
8890
8891 // Parse header
8892 var parseHeader = function(header) {
8893 // Get width information
8894 var info = header.getBoundingClientRect();
8895 var width = info.width > 50 ? info.width : 50;
8896
8897 // Create column option
8898 if (! options.columns[i]) {
8899 options.columns[i] = {};
8900 }
8901 if (header.getAttribute('data-celltype')) {
8902 options.columns[i].type = header.getAttribute('data-celltype');
8903 } else {
8904 options.columns[i].type = 'text';
8905 }
8906 options.columns[i].width = width + 'px';
8907 options.columns[i].title = header.innerHTML;
8908 options.columns[i].align = header.style.textAlign || 'center';
8909
8910 if (info = header.getAttribute('name')) {
8911 options.columns[i].name = info;
8912 }
8913 if (info = header.getAttribute('id')) {
8914 options.columns[i].id = info;
8915 }
8916 if (info = header.getAttribute('data-mask')) {
8917 options.columns[i].mask = info;
8918 }
8919 }
8920
8921 // Headers
8922 var nested = [];
8923 var headers = el.querySelectorAll(':scope > thead > tr');
8924 if (headers.length) {
8925 for (var j = 0; j < headers.length - 1; j++) {
8926 var cells = [];
8927 for (var i = 0; i < headers[j].children.length; i++) {
8928 var row = {
8929 title: headers[j].children[i].textContent,
8930 colspan: headers[j].children[i].getAttribute('colspan') || 1,
8931 };
8932 cells.push(row);
8933 }
8934 nested.push(cells);
8935 }
8936 // Get the last row in the thead
8937 headers = headers[headers.length-1].children;
8938 // Go though the headers
8939 for (var i = 0; i < headers.length; i++) {
8940 parseHeader(headers[i]);
8941 }
8942 }
8943
8944 // Content
8945 var rowNumber = 0;
8946 var mergeCells = {};
8947 var rows = {};
8948 var style = {};
8949 var classes = {};
8950
8951 var content = el.querySelectorAll(':scope > tr, :scope > tbody > tr');
8952 for (var j = 0; j < content.length; j++) {
8953 options.data[rowNumber] = [];
8954 if (options.parseTableFirstRowAsHeader == true && ! headers.length && j == 0) {
8955 for (var i = 0; i < content[j].children.length; i++) {
8956 parseHeader(content[j].children[i]);
8957 }
8958 } else {
8959 for (var i = 0; i < content[j].children.length; i++) {
8960 // WickedGrid formula compatibility
8961 var value = content[j].children[i].getAttribute('data-formula');
8962 if (value) {
8963 if (value.substr(0,1) != '=') {
8964 value = '=' + value;
8965 }
8966 } else {
8967 var value = content[j].children[i].innerHTML;
8968 }
8969 options.data[rowNumber].push(value);
8970
8971 // Key
8972 var cellName = jexcel.getColumnNameFromId([ i, j ]);
8973
8974 // Classes
8975 var tmp = content[j].children[i].getAttribute('class');
8976 if (tmp) {
8977 classes[cellName] = tmp;
8978 }
8979
8980 // Merged cells
8981 var mergedColspan = parseInt(content[j].children[i].getAttribute('colspan')) || 0;
8982 var mergedRowspan = parseInt(content[j].children[i].getAttribute('rowspan')) || 0;
8983 if (mergedColspan || mergedRowspan) {
8984 mergeCells[cellName] = [ mergedColspan || 1, mergedRowspan || 1 ];
8985 }
8986
8987 // Avoid problems with hidden cells
8988 if (s = content[j].children[i].style && content[j].children[i].style.display == 'none') {
8989 content[j].children[i].style.display = '';
8990 }
8991 // Get style
8992 var s = content[j].children[i].getAttribute('style');
8993 if (s) {
8994 style[cellName] = s;
8995 }
8996 // Bold
8997 if (content[j].children[i].classList.contains('styleBold')) {
8998 if (style[cellName]) {
8999 style[cellName] += '; font-weight:bold;';
9000 } else {
9001 style[cellName] = 'font-weight:bold;';
9002 }
9003 }
9004 }
9005
9006 // Row Height
9007 if (content[j].style && content[j].style.height) {
9008 rows[j] = { height: content[j].style.height };
9009 }
9010
9011 // Index
9012 rowNumber++;
9013 }
9014 }
9015
9016 // Nested
9017 if (Object.keys(nested).length > 0) {
9018 options.nestedHeaders = nested;
9019 }
9020 // Style
9021 if (Object.keys(style).length > 0) {
9022 options.style = style;
9023 }
9024 // Merged
9025 if (Object.keys(mergeCells).length > 0) {
9026 options.mergeCells = mergeCells;
9027 }
9028 // Row height
9029 if (Object.keys(rows).length > 0) {
9030 options.rows = rows;
9031 }
9032 // Classes
9033 if (Object.keys(classes).length > 0) {
9034 options.classes = classes;
9035 }
9036
9037 var content = el.querySelectorAll('tfoot tr');
9038 if (content.length) {
9039 var footers = [];
9040 for (var j = 0; j < content.length; j++) {
9041 var footer = [];
9042 for (var i = 0; i < content[j].children.length; i++) {
9043 footer.push(content[j].children[i].textContent);
9044 }
9045 footers.push(footer);
9046 }
9047 if (Object.keys(footers).length > 0) {
9048 options.footers = footers;
9049 }
9050 }
9051 // TODO: data-hiddencolumns="3,4"
9052
9053 // I guess in terms the better column type
9054 if (options.parseTableAutoCellType == true) {
9055 var pattern = [];
9056 for (var i = 0; i < options.columns.length; i++) {
9057 var test = true;
9058 var testCalendar = true;
9059 pattern[i] = [];
9060 for (var j = 0; j < options.data.length; j++) {
9061 var value = options.data[j][i];
9062 if (! pattern[i][value]) {
9063 pattern[i][value] = 0;
9064 }
9065 pattern[i][value]++;
9066 if (value.length > 25) {
9067 test = false;
9068 }
9069 if (value.length == 10) {
9070 if (! (value.substr(4,1) == '-' && value.substr(7,1) == '-')) {
9071 testCalendar = false;
9072 }
9073 } else {
9074 testCalendar = false;
9075 }
9076 }
9077
9078 var keys = Object.keys(pattern[i]).length;
9079 if (testCalendar) {
9080 options.columns[i].type = 'calendar';
9081 } else if (test == true && keys > 1 && keys <= parseInt(options.data.length * 0.1)) {
9082 options.columns[i].type = 'dropdown';
9083 options.columns[i].source = Object.keys(pattern[i]);
9084 }
9085 }
9086 }
9087
9088 return options;
9089 }
9090 }
9091
9092 // Helpers
9093 jexcel.helpers = (function() {
9094 var component = {};
9095
9096 /**
9097 * Get carret position for one element
9098 */
9099 component.getCaretIndex = function(e) {
9100 if (this.config.root) {
9101 var d = this.config.root;
9102 } else {
9103 var d = window;
9104 }
9105 var pos = 0;
9106 var s = d.getSelection();
9107 if (s) {
9108 if (s.rangeCount !== 0) {
9109 var r = s.getRangeAt(0);
9110 var p = r.cloneRange();
9111 p.selectNodeContents(e);
9112 p.setEnd(r.endContainer, r.endOffset);
9113 pos = p.toString().length;
9114 }
9115 }
9116 return pos;
9117 }
9118
9119 /**
9120 * Invert keys and values
9121 */
9122 component.invert = function(o) {
9123 var d = [];
9124 var k = Object.keys(o);
9125 for (var i = 0; i < k.length; i++) {
9126 d[o[k[i]]] = k[i];
9127 }
9128 return d;
9129 }
9130
9131 /**
9132 * Get letter based on a number
9133 *
9134 * @param {number} columnNumber
9135 * @return string letter
9136 */
9137 component.getColumnName = function(columnNumber) {
9138 let dividend = columnNumber+1;
9139 let columnName = "";
9140 let modulo;
9141 while (dividend > 0) {
9142 modulo = (dividend - 1) % 26;
9143 columnName = String.fromCharCode(65 + modulo).toString() + columnName;
9144 dividend = parseInt((dividend - modulo) / 26);
9145 }
9146 return columnName;
9147 }
9148
9149 /**
9150 * Get column name from coords
9151 */
9152 component.getColumnNameFromCoords = function(x, y) {
9153 return component.getColumnName(parseInt(x)) + (parseInt(y) + 1);
9154 }
9155
9156 component.getCoordsFromColumnName = function(columnName) {
9157 // Get the letters
9158 var t = /^[a-zA-Z]+/.exec(columnName);
9159
9160 if (t) {
9161 // Base 26 calculation
9162 var code = 0;
9163 for (var i = 0; i < t[0].length; i++) {
9164 code += parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, (t[0].length - 1 - i));
9165 }
9166 code--;
9167 // Make sure jspreadsheet starts on zero
9168 if (code < 0) {
9169 code = 0;
9170 }
9171
9172 // Number
9173 var number = parseInt(/[0-9]+$/.exec(columnName)) || null;
9174 if (number > 0) {
9175 number--;
9176 }
9177
9178 return [ code, number ];
9179 }
9180 }
9181
9182 /**
9183 * Extract json configuration from a TABLE DOM tag
9184 */
9185 component.createFromTable = function() {}
9186
9187 /**
9188 * Helper injectArray
9189 */
9190 component.injectArray = function(o, idx, arr) {
9191 return o.slice(0, idx).concat(arr).concat(o.slice(idx));
9192 }
9193
9194 /**
9195 * Parse CSV string to JS array
9196 */
9197 component.parseCSV = function(str, delimiter) {
9198 // user-supplied delimeter or default comma
9199 delimiter = (delimiter || ",");
9200
9201 // Final data
9202 var col = 0;
9203 var row = 0;
9204 var num = 0;
9205 var data = [[]];
9206 var limit = 0;
9207 var flag = null;
9208 var inside = false;
9209 var closed = false;
9210
9211 // Go over all chars
9212 for (var i = 0; i < str.length; i++) {
9213 // Create new row
9214 if (! data[row]) {
9215 data[row] = [];
9216 }
9217 // Create new column
9218 if (! data[row][col]) {
9219 data[row][col] = '';
9220 }
9221
9222 // Ignore
9223 if (str[i] == '\r') {
9224 continue;
9225 }
9226
9227 // New row
9228 if ((str[i] == '\n' || str[i] == delimiter) && (inside == false || closed == true || ! flag)) {
9229 // Restart flags
9230 flag = null;
9231 inside = false;
9232 closed = false;
9233
9234 if (data[row][col][0] == '"') {
9235 var val = data[row][col].trim();
9236 if (val[val.length-1] == '"') {
9237 data[row][col] = val.substr(1, val.length-2);
9238 }
9239 }
9240
9241 // Go to the next cell
9242 if (str[i] == '\n') {
9243 // New line
9244 col = 0;
9245 row++;
9246 } else {
9247 // New column
9248 col++;
9249 if (col > limit) {
9250 // Keep the reference of max column
9251 limit = col;
9252 }
9253 }
9254 } else {
9255 // Inside quotes
9256 if (str[i] == '"') {
9257 inside = ! inside;
9258 }
9259
9260 if (flag === null) {
9261 flag = inside;
9262 if (flag == true) {
9263 continue;
9264 }
9265 } else if (flag === true && ! closed) {
9266 if (str[i] == '"') {
9267 if (str[i+1] == '"') {
9268 inside = true;
9269 data[row][col] += str[i];
9270 i++;
9271 } else {
9272 closed = true;
9273 }
9274 continue;
9275 }
9276 }
9277
9278 data[row][col] += str[i];
9279 }
9280 }
9281
9282 // Make sure a square matrix is generated
9283 for (var j = 0; j < data.length; j++) {
9284 for (var i = 0; i <= limit; i++) {
9285 if (data[j][i] === undefined) {
9286 data[j][i] = '';
9287 }
9288 }
9289 }
9290
9291 return data;
9292 }
9293
9294 return component;
9295 })();
9296
9297 /**
9298 * Jquery Support
9299 */
9300 if (typeof(jQuery) != 'undefined') {
9301 (function($){
9302 $.fn.jspreadsheet = $.fn.jexcel = function(mixed) {
9303 var spreadsheetContainer = $(this).get(0);
9304 if (! spreadsheetContainer.jexcel) {
9305 return jexcel($(this).get(0), arguments[0]);
9306 } else {
9307 if (Array.isArray(spreadsheetContainer.jexcel)) {
9308 return spreadsheetContainer.jexcel[mixed][arguments[1]].apply(this, Array.prototype.slice.call( arguments, 2 ));
9309 } else {
9310 return spreadsheetContainer.jexcel[mixed].apply(this, Array.prototype.slice.call( arguments, 1 ));
9311 }
9312 }
9313 };
9314
9315 })(jQuery);
9316 }
9317
9318 return jexcel;
9319 })));
9320