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

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

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