PluginProbe
TablePress – Tables in WordPress made easy / 3.3.1
TablePress – Tables in WordPress made easy v3.3.1
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 3.3.1, at admin/js/jspreadsheet.js

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