UNPKG

jexcel

Version:

jExcel is a lightweight, vanilla javascript plugin to create amazing web-based interactive tables and spreadsheets compatible with Excel, Google Spreadsheets and any other spreadsheet software.

1,333 lines (1,210 loc) 430 kB
/** * (c) jExcel v3.0.1 * * Author: Paul Hodel <paul.hodel@gmail.com> * Website: https://bossanova.uk/jexcel/ * Description: Create amazing web based spreadsheets. * * This software is distribute under MIT License */ var jexcel = (function(el, options) { // Create jexcel object var obj = {}; obj.options = {}; // Loading default configuration var defaults = { // External data url:null, // Data data:[[]], // Rows and columns definitions rows:[], columns:[], // Deprected legacy options colHeaders:[], colWidths:[], colAlignments:[], nestedHeaders:null, // Column width that is used by default defaultColWidth:50, // Spare rows and columns minSpareRows:0, minSpareCols:0, // Minimal table dimensions minDimensions:[0,0], // Allow column sorting columnSorting:true, // Allow column resizing columnDrag:false, // Allow column resizing columnResize:true, // Allow row resizing rowResize:false, // Allow row dragging rowDrag:true, // Allow table edition editable:true, // Allow new rows allowInsertRow:true, // Allow new rows allowManualInsertRow:true, // Allow new columns allowInsertColumn:true, // Allow new rows allowManualInsertColumn:true, // Allow row delete allowDeleteRow:true, // Allow column delete allowDeleteColumn:true, // Allow rename column allowRenameColumn:true, // Allow comments allowComments:false, // Global wrap wordWrap:false, // CSV source csv:null, // Filename csvFileName:'jexcel', // Consider first line as header csvHeaders:true, // Delimiters csvDelimiter:',', // Disable corner selection selectionCopy:true, // Merged cells mergeCells:[], // Create toolbar toolbar:null, // Allow search search:false, // Create pagination pagination:false, paginationOptions:null, // Full screen fullscreen:false, // Lazy loading lazyLoading:false, loadingSpin:false, // Table overflow tableOverflow:false, tableHeight:'300px', tableWidth:null, // Style style:null, // Event handles onload:null, onchange:null, onbeforechange:null, oninsertrow:null, oninsertcolumn:null, ondeleterow:null, ondeletecolumn:null, onmoverow:null, onmovecolumn:null, onresizerow:null, onresizecolumn:null, onsort:null, onselection:null, onpaste:null, onmerge:null, onfocus:null, onblur:null, // Customize any cell behavior updateTable:null, // Texts text:{ noRecordsFound: 'No records found', showingPage: 'Showing page {0} of {1} entries', show: 'Show ', entries: ' entries', insertANewColumnBefore: 'Insert a new column before', insertANewColumnAfter: 'Insert a new column after', deleteSelectedColumns: 'Delete selected columns', renameThisColumn: 'Rename this column', orderAscending: 'Order ascending', orderDescending: 'Order descending', insertANewRowBefore: 'Insert a new row before', insertANewRowAfter: 'Insert a new row after', deleteSelectedRows: 'Delete selected rows', editComments: 'Edit comments', addComments: 'Add comments', comments: 'Comments', clearComments: 'Clear comments', copy: 'Copy...', paste: 'Paste...', saveAs: 'Save as...', about: 'About', areYouSureToDeleteTheSelectedRows: 'Are you sure to delete the selected rows?', areYouSureToDeleteTheSelectedColumns: 'Are you sure to delete the selected columns?', thisActionWillDestroyAnyExistingMergedCellsAreYouSure: 'This action will destroy any existing merged cells. Are you sure?', thisActionWillClearYourSearchResultsAreYouSure: 'This action will clear your search results. Are you sure?', thereIsAConflictWithAnotherMergedCell: 'There is a conflict with another merged cell', invalidMergeProperties: 'Invalid merged properties', cellAlreadyMerged: 'Cell already merged', noCellsSelected: 'No cells selected', }, // About message about:"jExcel Pro Spreadsheet\nVersion 3.0.1\nAuthor: Paul Hodel <paul.hodel@gmail.com>\nWebsite: https://jexcel.net/v3", }; // Loading initial configuration from user for (var property in defaults) { if (options.hasOwnProperty(property)) { obj.options[property] = options[property]; } else { obj.options[property] = defaults[property]; } } // Global elements obj.el = el; obj.corner = null; obj.contextMenu = null; obj.textarea = null; obj.ads = null; obj.content = null; obj.table = null; obj.thead = null; obj.tbody = null; obj.rows = []; obj.results = null; obj.searchInput = null; obj.toolbar = null; obj.pagination = null; obj.pageNumber = null; obj.headerContainer = null; obj.colgroupContainer = null; // Containers obj.headers = []; obj.records = []; obj.history = []; obj.formula = []; obj.formulaStack = 0; obj.colgroup = []; obj.selection = []; obj.highlighted = []; obj.selectedCell = null; obj.selectedContainer = null; obj.style = []; obj.meta = []; // Internal controllers obj.cursor = null; obj.historyIndex = -1; obj.ignoreEvents = false; obj.ignoreHistory = false; obj.edition = null; obj.hashString = null; obj.resizing = null; obj.dragging = null; // Lazy loading if (obj.options.lazyLoading == true && (obj.options.tableOverflow == false && obj.options.fullscreen == false)) { console.error('JEXCEL: The lazyloading only works when tableOverflow = yes or fullscreen = yes'); obj.options.lazyLoading = false; } /** * Prepare the jexcel table * * @Param config */ obj.prepareTable = function() { // Loading initial data from remote sources var results = []; // Number of columns var size = obj.options.columns.length; if (obj.options.data[0].length > size) { size = obj.options.data[0].length; } // Minimal dimensions if (obj.options.minDimensions[0] > size) { size = obj.options.minDimensions[0]; } // Requests var requests = []; var requestsIndex = []; // Preparations for (var i = 0; i < size; i++) { // Deprected options. You should use only columns if (! obj.options.colHeaders[i]) { obj.options.colHeaders[i] = ''; } if (! obj.options.colWidths[i]) { obj.options.colWidths[i] = obj.options.defaultColWidth || '50'; } if (! obj.options.colAlignments[i]) { obj.options.colAlignments[i] = 'center'; } // Default column description if (! obj.options.columns[i]) { obj.options.columns[i] = { type:'text' }; } else if (! obj.options.columns[i]) { obj.options.columns[i].type = 'text'; } if (! obj.options.columns[i].source) { obj.options.columns[i].source = []; } if (! obj.options.columns[i].options) { obj.options.columns[i].options = []; } if (! obj.options.columns[i].editor) { obj.options.columns[i].editor = null; } if (! obj.options.columns[i].allowEmpty) { obj.options.columns[i].allowEmpty = false; } if (! obj.options.columns[i].title) { obj.options.columns[i].title = obj.options.colHeaders[i] ? obj.options.colHeaders[i] : ''; } if (! obj.options.columns[i].width) { obj.options.columns[i].width = obj.options.colWidths[i] ? obj.options.colWidths[i] : '50'; } if (! obj.options.columns[i].align) { obj.options.columns[i].align = obj.options.colAlignments[i] ? obj.options.colAlignments[i] : 'center'; } // Pre-load initial source for json autocomplete if (obj.options.columns[i].type == 'autocomplete' || obj.options.columns[i].type == 'dropdown') { // if remote content if (obj.options.columns[i].url) { requestsIndex.push(i); requests.push(fetch(obj.options.columns[i].url, { headers: new Headers({ 'content-type': 'text/json' }) }) .then(function(data) { return data.json(); })); } } else if (obj.options.columns[i].type == 'calendar') { // Default format for date columns if (! obj.options.columns[i].options.format) { obj.options.columns[i].options.format = 'DD/MM/YYYY'; } } } if (requests.length) { Promise.all(requests).then(function(data) { for (var i = 0; i < data.length; i++) { obj.options.columns[i].source = data[i]; } obj.createTable(); }); } else { // Create table obj.createTable(); } } obj.createTable = function() { // Elements obj.table = document.createElement('table'); obj.thead = document.createElement('thead'); obj.tbody = document.createElement('tbody'); // Create headers controllers obj.headers = []; obj.colgroup = []; // Create table container obj.content = document.createElement('div'); obj.content.classList.add('jexcel_content'); // Create toolbar object obj.toolbar = document.createElement('div'); obj.toolbar.classList.add('jexcel_toolbar'); // Search var searchContainer = document.createElement('div'); var searchText = document.createTextNode('Search: '); obj.searchInput = document.createElement('input'); obj.searchInput.classList.add('jexcel_search'); searchContainer.appendChild(searchText); searchContainer.appendChild(obj.searchInput); obj.searchInput.onfocus = function() { obj.resetSelection(); } // Pagination select option var paginationUpdateContainer = document.createElement('div'); if (obj.options.pagination > 0 && obj.options.paginationOptions && obj.options.paginationOptions.length > 0) { obj.paginationDropdown = document.createElement('select'); obj.paginationDropdown.classList.add('jexcel_pagination_dropdown'); obj.paginationDropdown.onchange = function() { obj.options.pagination = parseInt(this.value); obj.page(0); } for (var i = 0; i < obj.options.paginationOptions.length; i++) { var temp = document.createElement('option'); temp.value = obj.options.paginationOptions[i]; temp.innerHTML = obj.options.paginationOptions[i]; obj.paginationDropdown.appendChild(temp); } paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.show)); paginationUpdateContainer.appendChild(obj.paginationDropdown); paginationUpdateContainer.appendChild(document.createTextNode(obj.options.text.entries)); } // Filter and pagination container obj.filter = document.createElement('div'); obj.filter.classList.add('jexcel_filter'); obj.filter.appendChild(paginationUpdateContainer); obj.filter.appendChild(searchContainer); // Colsgroup obj.colgroupContainer = document.createElement('colgroup'); var tempCol = document.createElement('col'); tempCol.setAttribute('width', 50); obj.colgroupContainer.appendChild(tempCol); // Nested if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { // Flexible way to handle nestedheaders if (obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { for (var j = 0; j < obj.options.nestedHeaders.length; j++) { obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders[j])); } } else { obj.thead.appendChild(obj.createNestedHeader(obj.options.nestedHeaders)); } } // Row obj.headerContainer = document.createElement('tr'); var tempCol = document.createElement('td'); obj.headerContainer.appendChild(tempCol); for (var i = 0; i < obj.options.columns.length; i++) { // Create header obj.createCellHeader(i); // Append cell to the container obj.headerContainer.appendChild(obj.headers[i]); obj.colgroupContainer.appendChild(obj.colgroup[i]); } obj.thead.appendChild(obj.headerContainer); // Content table obj.table = document.createElement('table'); obj.table.classList.add('jexcel'); obj.table.setAttribute('cellpadding', '0'); obj.table.setAttribute('cellspacing', '0'); obj.table.setAttribute('unselectable', 'yes'); obj.table.setAttribute('onselectstart', 'return false'); obj.table.appendChild(obj.colgroupContainer); obj.table.appendChild(obj.thead); obj.table.appendChild(obj.tbody); // Spreasheet corner obj.corner = document.createElement('div'); obj.corner.className = 'jexcel_corner'; obj.corner.setAttribute('unselectable', 'on'); obj.corner.setAttribute('onselectstart', 'return false'); if (obj.selectionCopy == false) { obj.corner.style.display = 'none'; } // Textarea helper obj.textarea = document.createElement('textarea'); obj.textarea.className = 'jexcel_textarea'; obj.textarea.id = 'jexcel_textarea'; // Contextmenu container obj.contextMenu = document.createElement('div'); obj.contextMenu.className = 'jexcel_contextmenu'; // Create element jApp.contextmenu(obj.contextMenu, { onclick:function() { obj.contextMenu.contextmenu.close(false); } }); // Powered by jExcel var ads = '<a href="https://bossanova.uk/jexcel/"><img src="//bossanova.uk/jexcel/logo.png">jExcel Spreadsheet</a>'; obj.ads = document.createElement('div'); obj.ads.className = 'jexcel_about'; if (typeof(sessionStorage) !== "undefined") { if (! sessionStorage.getItem('jexcel')) { sessionStorage.setItem('jexcel', true); obj.ads.innerHTML = ads; } } else { obj.ads.innerHTML = ads; } // Create table container TODO: frozen columns var container = document.createElement('div'); container.classList.add('jexcel_table'); // Pagination obj.pagination = document.createElement('div'); obj.pagination.classList.add('jexcel_pagination'); var paginationInfo = document.createElement('div'); var paginationPages = document.createElement('div'); obj.pagination.appendChild(paginationInfo); obj.pagination.appendChild(paginationPages); // Append containers to the table if (obj.options.search == true) { el.appendChild(obj.filter); } // Elements obj.content.appendChild(obj.table); obj.content.appendChild(obj.corner); obj.content.appendChild(obj.textarea); el.appendChild(obj.toolbar); el.appendChild(obj.content); el.appendChild(obj.pagination); el.appendChild(obj.contextMenu); el.appendChild(obj.ads); el.classList.add('jexcel_container'); // Create toolbar if (obj.options.toolbar && obj.options.toolbar.length) { obj.createToolbar(); } // Fullscreen if (obj.options.fullscreen == true) { el.classList.add('fullscreen'); if (obj.options.toolbar) { el.classList.add('with-toolbar'); } } else { // Overflow if (obj.options.tableOverflow == true) { if (obj.options.tableHeight) { obj.content.style['overflow-y'] = 'auto'; obj.content.style.height = obj.options.tableHeight; } if (obj.options.tableWidth) { el.content.style['overflow-x'] = 'auto'; el.content.width = obj.options.tableWidth; } } } // Actions if (obj.options.columnDrag == true) { obj.thead.classList.add('draggable'); } if (obj.options.columnResize == true) { obj.thead.classList.add('resizable'); } if (obj.options.rowDrag == true) { obj.tbody.classList.add('draggable'); } if (obj.options.rowResize == true) { obj.tbody.classList.add('resizable'); } // Load data obj.setData(); // Style if (obj.options.style) { obj.setStyle(obj.options.style, null, null, 1, 1); } } /** * Set data * * @param array data In case no data is sent, default is reloaded * @return void */ obj.setData = function(data) { // Update data if (data) { if (typeof(data) == 'string') { data = JSON.parse(data); } obj.options.data = data; } // Adjust minimal dimensions var j = 0; var i = 0; var size_i = obj.options.columns.length; var size_j = obj.options.data.length; var min_i = obj.options.minDimensions[0]; var min_j = obj.options.minDimensions[1]; var max_i = min_i > size_i ? min_i : size_i; var max_j = min_j > size_j ? min_j : size_j; for (j = 0; j < max_j; j++) { for (i = 0; i < max_i; i++) { if (obj.options.data[j] == undefined) { obj.options.data[j] = []; } if (obj.options.data[j][i] == undefined) { obj.options.data[j][i] = ''; } } } // Reset containers obj.rows = []; obj.results = null; obj.records = []; obj.history = []; // Reset internal controllers obj.historyIndex = -1; // Reset data obj.tbody.innerHTML = ''; // Lazy loading if (obj.options.lazyLoading == true) { // Load only 100 records var startNumber = 0 var finalNumber = obj.options.data.length < 100 ? obj.options.data.length : 100; if (obj.options.pagination) { obj.options.pagination = false; console.error('JEXCEL: Pagination will be disable due the lazyLoading'); } } else if (obj.options.pagination) { // Pagination if (! obj.pageNumber) { obj.pageNumber = 0; } var quantityPerPage = obj.options.pagination; startNumber = (obj.options.pagination * obj.pageNumber); finalNumber = (obj.options.pagination * obj.pageNumber) + obj.options.pagination; if (obj.options.data.length < finalNumber) { finalNumber = obj.options.data.length; } } else { var startNumber = 0; var finalNumber = obj.options.data.length; } // Append nodes to the HTML for (j = 0; j < obj.options.data.length; j++) { // Create row var tr = obj.createRow(j, obj.options.data[j]); // Append line to the table if (j >= startNumber && j < finalNumber) { obj.tbody.appendChild(tr); } } if (obj.options.lazyLoading == true) { // Do not create pagination with lazyloading activated } else if (obj.options.pagination) { obj.updatePagination(); } // Merge cells if (obj.options.mergeCells) { var keys = Object.keys(obj.options.mergeCells); for (var i = 0; i < keys.length; i++) { var num = obj.options.mergeCells[keys[i]]; obj.setMerge(keys[i], num[0], num[1], 1); } } // Updata table with custom configurations if applicable obj.updateTable(); // Onload if (! obj.ignoreEvents) { if (typeof(obj.options.onload) == 'function') { obj.options.onload(el); } } } /** * Get the whole table data * * @param integer row number * @return string value */ obj.getData = function(highlighted) { // Control vars var dataset = []; var px = 0; var py = 0; // Column and row length var x = obj.options.data[0].length var y = obj.options.data.length // Go through the columns to get the data for (var j = 0; j < y; j++) { px = 0; for (var i = 0; i < x; i++) { // Cell selected or fullset if (! highlighted || obj.records[j][i].classList.contains('highlight')) { // Get value if (! dataset[py]) { dataset[py] = []; } dataset[py][px] = obj.options.data[j][i]; px++; } } if (px > 0) { py++; } } return dataset; } /** * Get a row data by rowNumber */ obj.getRowData = function(rowNumber) { return obj.options.data[rowNumber]; } /** * Get a column data by columnNumber */ obj.getColumnData = function(columnNumber) { var dataset = []; // Go through the rows to get the data for (var j = 0; j < obj.options.data.length; j++) { dataset.push(obj.options.data[j][columnNumber]); } return dataset; } /** * Create row */ obj.createRow = function(j, data) { // Create container if (! obj.records[j]) { obj.records[j] = []; } // New line of data to be append in the table obj.rows[j] = document.createElement('tr'); obj.rows[j].setAttribute('data-y', j); // Definitions if (obj.options.rows[j]) { if (obj.options.rows[j].height) { obj.rows[j].style.height = obj.options.rows[j].height; } } // Row number label var td = document.createElement('td'); td.innerHTML = parseInt(j + 1); td.setAttribute('data-y', j); td.className = 'jexcel_row'; obj.rows[j].appendChild(td); // Data columns for (i = 0; i < obj.options.columns.length; i++) { // New column of data to be append in the line obj.records[j][i] = obj.createCell(i, j, data[i]); // Add column to the row obj.rows[j].appendChild(obj.records[j][i]); } // Add row to the table body return obj.rows[j]; } /** * Create cell */ obj.createCell = function(i, j, value) { // Create cell and properties var td = document.createElement('td'); td.setAttribute('data-x', i); td.setAttribute('data-y', j); // Hidden column if (obj.options.columns[i].type == 'hidden') { td.style.display = 'none'; td.innerHTML = value; } else if (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio') { // Create input var element = document.createElement('input'); element.type = obj.options.columns[i].type; element.name = 'c' + i; element.checked = (value == 1 || value == true || value == 'true') ? true : false; element.onclick = function() { obj.setValue(td, this.checked); } if (obj.options.columns[i].readOnly == true) { element.setAttribute('disabled', 'disabled'); } // Append to the table td.appendChild(element); // Make sure the values are correct obj.options.data[j][i] = element.checked; } else if (obj.options.columns[i].type == 'calendar') { // Try formatted date var formatted = jApp.calendar.extractDateFromString(value, obj.options.columns[i].options.format); // Create calendar cell td.innerHTML = jApp.calendar.getDateString(formatted ? formatted : value, obj.options.columns[i].options.format); } else if (obj.options.columns[i].type == 'dropdown' || obj.options.columns[i].type == 'autocomplete') { // Create dropdown cell td.classList.add('dropdown'); td.innerHTML = obj.getDropDownValue(i, value); } else if (obj.options.columns[i].type == 'color') { if (obj.options.columns[i].render == 'square') { var color = document.createElement('div'); color.className = 'color'; color.style.backgroundColor = value; td.appendChild(color); } else { td.style.color = value; td.innerHTML = value; } } else if (obj.options.columns[i].type == 'image') { if (value && value.substr(0, 10) == 'data:image') { var img = document.createElement('img'); img.src = value; td.appendChild(img); } } else { if ((''+value).substr(0,1) == '=') { value = obj.executeFormula(value, i, j) } if (obj.options.columns[i].mask) { var decimal = obj.options.columns[i].decimal || '.'; value = '' + jApp.mask.run(value, obj.options.columns[i].mask, decimal); } td.innerHTML = value; } // Readonly if (obj.options.columns[i].readOnly == true) { td.className = 'readonly'; } // Text align var colAlign = obj.options.columns[i].align ? obj.options.columns[i].align : 'center'; td.style.textAlign = colAlign; // Wrap option if (obj.options.wordWrap == true || obj.options.columns[i].wordWrap == true || td.innerHTML.length > 200) { td.style.whiteSpace = 'pre-wrap'; } // Overflow if (i > 0) { if (value || td.innerHTML) { obj.records[j][i-1].style.overflow = 'hidden'; } else { if (i == obj.options.columns.length - 1) { td.style.overflow = 'hidden'; } } } return td; } obj.createCellHeader = function(colNumber) { // Create col global control var colWidth = obj.options.columns[colNumber].width ? obj.options.columns[colNumber].width : obj.options.defaultColWidth; var colAlign = obj.options.columns[colNumber].align ? obj.options.columns[colNumber].align : 'center'; // Create header cell obj.headers[colNumber] = document.createElement('td'); obj.headers[colNumber].innerHTML = obj.options.columns[colNumber].title ? obj.options.columns[colNumber].title : jexcel.getColumnName(colNumber); obj.headers[colNumber].setAttribute('data-x', colNumber); obj.headers[colNumber].style.textAlign = colAlign; if (obj.options.columns[colNumber].title) { obj.headers[colNumber].setAttribute('title', obj.options.columns[colNumber].title); } // Width control obj.colgroup[colNumber] = document.createElement('col'); obj.colgroup[colNumber].setAttribute('width', colWidth); // Hidden column if (obj.options.columns[colNumber].type == 'hidden') { obj.headers[colNumber].style.display = 'none'; obj.colgroup[colNumber].style.display = 'none'; } } obj.createNestedHeader = function(nestedInformation) { var tr = document.createElement('tr'); tr.classList.add('jexcel_nested'); var td = document.createElement('td'); tr.appendChild(td); var headerIndex = 0; for (var i = 0; i < nestedInformation.length; i++) { // Default values if (! nestedInformation[i].colspan) { nestedInformation[i].colspan = 1; } if (! nestedInformation[i].align) { nestedInformation[i].align = 'center'; } if (! nestedInformation[i].title) { nestedInformation[i].title = ''; } // Classes container var column = []; // Header classes for this cell for (var x = 0; x < nestedInformation[i].colspan; x++) { column.push(headerIndex); headerIndex++; } // Created the nested cell var td = document.createElement('td'); td.setAttribute('data-column', column.join(',')); td.setAttribute('colspan', nestedInformation[i].colspan); td.setAttribute('align', nestedInformation[i].align); td.innerHTML = nestedInformation[i].title; tr.appendChild(td); } return tr; } /** * Create toolbar */ obj.createToolbar = function(toolbar) { if (toolbar) { obj.options.toolbar = toolbar; } else { var toolbar = obj.options.toolbar; } for (var i = 0; i < toolbar.length; i++) { if (toolbar[i].type == 'i') { var toolbarItem = document.createElement('i'); toolbarItem.classList.add('jexcel_toolbar_item'); toolbarItem.classList.add('material-icons'); toolbarItem.setAttribute('data-k', toolbar[i].k); toolbarItem.setAttribute('data-v', toolbar[i].v); // Handle click if (toolbar[i].onclick && typeof(toolbar[i].onclick)) { toolbarItem.onclick = toolbar[i].onclick; } else { toolbarItem.onclick = function() { var k = this.getAttribute('data-k'); var v = this.getAttribute('data-v'); obj.setStyle(obj.highlighted, k, v); } } // Append element toolbarItem.innerHTML = toolbar[i].content; obj.toolbar.appendChild(toolbarItem); } else if (toolbar[i].type == 'select') { var toolbarItem = document.createElement('select'); toolbarItem.classList.add('jexcel_toolbar_item'); toolbarItem.setAttribute('data-k', toolbar[i].k); // Handle onchange if (toolbar[i].onchange && typeof(toolbar[i].onchange)) { toolbarItem.onchange = toolbar[i].onchange; } else { toolbarItem.onchange = function() { var k = this.getAttribute('data-k'); obj.setStyle(obj.highlighted, k, this.value); } } // Add options to the dropdown for(var j = 0; j < toolbar[i].v.length; j++) { var toolbarDropdownOption = document.createElement('option'); toolbarDropdownOption.value = toolbar[i].v[j]; toolbarDropdownOption.innerHTML = toolbar[i].v[j]; toolbarItem.appendChild(toolbarDropdownOption); } obj.toolbar.appendChild(toolbarItem); } else if (toolbar[i].type == 'color') { var toolbarItem = document.createElement('i'); toolbarItem.classList.add('jexcel_toolbar_item'); toolbarItem.classList.add('material-icons'); toolbarItem.setAttribute('data-k', toolbar[i].k); toolbarItem.setAttribute('data-v', ''); obj.toolbar.appendChild(toolbarItem); toolbarItem.onclick = function() { this.color.open(); } toolbarItem.innerHTML = toolbar[i].content; jApp.color(toolbarItem, { onchange:function(o, v) { var k = o.getAttribute('data-k'); obj.setStyle(obj.highlighted, k, v); } }); } } } /** * Merge cells * @param cellName * @param colspan * @param rowspan * @param ignoreHistoryAndEvents */ obj.setMerge = function(cellName, colspan, rowspan, ignoreHistoryAndEvents) { var test = false; if (! cellName) { if (! obj.highlighted.length) { alert(obj.options.text.noCellsSelected); return null; } else { var x1 = parseInt(obj.highlighted[0].getAttribute('data-x')); var y1 = parseInt(obj.highlighted[0].getAttribute('data-y')); var x2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-x')); var y2 = parseInt(obj.highlighted[obj.highlighted.length-1].getAttribute('data-y')); var cellName = jexcel.getColumnNameFromId([ x1, y1 ]); var colspan = (x2 - x1) + 1; var rowspan = (y2 - y1) + 1; } } var cell = jexcel.getIdFromColumnName(cellName, true); if (obj.options.mergeCells[cellName] && obj.options.mergeCells[cellName][2]) { test = obj.options.text.cellAlreadyMerged; } else if ((! colspan || colspan < 2) && (! rowspan || rowspan < 2)) { test = obj.options.text.invalidMergeProperties; } else { var cells = []; for (var j = cell[1]; j < cell[1] + rowspan; j++) { for (var i = cell[0]; i < cell[0] + colspan; i++) { var columnName = jexcel.getColumnNameFromId([i, j]); if (obj.options.mergeCells[columnName] && obj.options.mergeCells[columnName][2]) { test = obj.options.text.thereIsAConflictWithAnotherMergedCell; } } } } if (test) { alert(test); } else { // Add property if (colspan > 1) { obj.records[cell[1]][cell[0]].setAttribute('colspan', colspan); } else { colspan = 1; } if (rowspan > 1) { obj.records[cell[1]][cell[0]].setAttribute('rowspan', rowspan); } else { rowspan = 1; } // Keep links to the existing nodes obj.options.mergeCells[cellName] = [ colspan, rowspan, [] ]; // Mark cell as merged obj.records[cell[1]][cell[0]].setAttribute('data-merged', 'true'); // Overflow obj.records[cell[1]][cell[0]].style.overflow = 'hidden'; // History data var data = []; // Adjust the nodes for (var y = cell[1]; y < cell[1] + rowspan; y++) { for (var x = cell[0]; x < cell[0] + colspan; x++) { if (! (cell[0] == x && cell[1] == y)) { data.push(obj.options.data[y][x]); obj.updateCell(x, y, '', true); obj.options.mergeCells[cellName][2].push(obj.records[y][x]); obj.records[y][x].style.display = 'none'; obj.records[y][x] = obj.records[cell[1]][cell[0]]; } } } // In the initialization is not necessary keep the history obj.updateSelection(obj.records[cell[1]][cell[0]]); if (! ignoreHistoryAndEvents) { obj.setHistory({ action:'setMerge', column:cellName, colspan:colspan, rowspan:rowspan, data:data, }); if (typeof(obj.options.onmerge) == 'function') { obj.options.onmerge(el, column, width, oldWidth); } } } } /** * Merge cells * @param cellName * @param colspan * @param rowspan * @param ignoreHistoryAndEvents */ obj.getMerge = function(cellName) { var data = {}; if (cellName) { if (obj.options.mergeCells[cellName]) { data = [ obj.options.mergeCells[cellName][0], obj.options.mergeCells[cellName][1] ]; } else { data = null; } } else { if (obj.options.mergeCells) { var mergedCells = obj.options.mergeCells; var keys = Object.keys(obj.options.mergeCells); for (var i = 0; i < keys.length; i++) { data[keys[i]] = [ obj.options.mergeCells[keys[i]][0], obj.options.mergeCells[keys[i]][1] ]; } } } return data; } /** * Remove merge by cellname * @param cellName */ obj.removeMerge = function(cellName, data, keepOptions) { if (obj.options.mergeCells[cellName]) { var cell = jexcel.getIdFromColumnName(cellName, true); obj.records[cell[1]][cell[0]].removeAttribute('colspan'); obj.records[cell[1]][cell[0]].removeAttribute('rowspan'); obj.records[cell[1]][cell[0]].removeAttribute('data-merged'); var info = obj.options.mergeCells[cellName]; var index = 0; for (var j = 0; j < info[1]; j++) { for (var i = 0; i < info[0]; i++) { if (j > 0 || i > 0) { obj.records[cell[1]+j][cell[0]+i] = info[2][index]; obj.records[cell[1]+j][cell[0]+i].style.display = ''; // Recover data if (data && data[index]) { obj.updateCell(cell[0]+i, cell[1]+j, data[index]); } index++; } } } // Update selection obj.updateSelection(obj.records[cell[1]][cell[0]], obj.records[cell[1]+j-1][cell[0]+i-1]); if (! keepOptions) { delete(obj.options.mergeCells[cellName]); } } } /** * Remove all merged cells */ obj.destroyMerged = function(keepOptions) { // Remove any merged cells if (obj.options.mergeCells) { var mergedCells = obj.options.mergeCells; var keys = Object.keys(obj.options.mergeCells); for (var i = 0; i < keys.length; i++) { obj.removeMerge(keys[i], null, keepOptions); } } } /** * Is column merged */ obj.isColMerged = function(x, insertBefore) { var cols = []; // Remove any merged cells if (obj.options.mergeCells) { var keys = Object.keys(obj.options.mergeCells); for (var i = 0; i < keys.length; i++) { var info = jexcel.getIdFromColumnName(keys[i], true); var colspan = obj.options.mergeCells[keys[i]][0]; var x1 = info[0]; var x2 = info[0] + (colspan > 1 ? colspan - 1 : 0); if (insertBefore == null) { if ((x1 <= x && x2 >= x)) { cols.push(keys[i]); } } else { if (insertBefore) { if ((x1 < x && x2 >= x)) { cols.push(keys[i]); } } else { if ((x1 <= x && x2 > x)) { cols.push(keys[i]); } } } } } return cols; } /** * Is rows merged */ obj.isRowMerged = function(y, insertBefore) { var rows = []; // Remove any merged cells if (obj.options.mergeCells) { var keys = Object.keys(obj.options.mergeCells); for (var i = 0; i < keys.length; i++) { var info = jexcel.getIdFromColumnName(keys[i], true); var rowspan = obj.options.mergeCells[keys[i]][1]; var y1 = info[1]; var y2 = info[1] + (rowspan > 1 ? rowspan - 1 : 0); if (insertBefore == null) { if ((y1 <= y && y2 >= y)) { rows.push(keys[i]); } } else { if (insertBefore) { if ((y1 < y && y2 >= y)) { rows.push(keys[i]); } } else { if ((y1 <= y && y2 > y)) { rows.push(keys[i]); } } } } } return rows; } /** * Open the editor * * @param object cell * @return void */ obj.openEditor = function(cell, empty, e) { // Get cell position var y = cell.getAttribute('data-y'); var x = cell.getAttribute('data-x'); // Overflow if (x > 0) { obj.records[y][x-1].style.overflow = 'hidden'; } // Create editor var createEditor = function(type) { // Cell information let info = cell.getBoundingClientRect(); // Create dropdown var editor = document.createElement(type); editor.style.width = (info.width) + 'px'; editor.style.height = (info.height - 2) + 'px'; editor.style.minHeight = (info.height - 2) + 'px'; // Edit cell cell.classList.add('editor'); cell.innerHTML = ''; cell.appendChild(editor); return editor; } // Readonly if (cell.classList.contains('readonly') == true) { // Do nothing } else { // Holder obj.edition = [ obj.records[y][x], obj.records[y][x].innerHTML, x, y ]; // If there is a custom editor for it if (obj.options.columns[x].editor) { // Custom editors obj.options.columns[x].editor.openEditor(cell, el); } else { // Native functions if (obj.options.columns[x].type == 'hidden') { // Do nothing } else if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') { // Get value var value = cell.children[0].checked ? false : true; // Toogle value obj.setValue(cell, value); // Do not keep edition open obj.edition = null; } else if (obj.options.columns[x].type == 'dropdown' || obj.options.columns[x].type == 'autocomplete') { // Get current value var value = obj.options.data[y][x]; // Create dropdown if (typeof(obj.options.columns[x].filter) == 'function') { var source = obj.options.columns[x].filter(el, cell, x, y, obj.options.columns[x].source); } else { var source = obj.options.columns[x].source; } // Create editor var editor = createEditor('div'); var options = { data: source, multiple: obj.options.columns[x].multiple ? true : false, autocomplete: obj.options.columns[x].autocomplete || obj.options.columns[x].type == 'autocomplete' ? true : false, opened:true, value: obj.options.columns[x].multiple ? value.split(';') : value, width:'100%', height:editor.style.minHeight, position: (obj.options.tableOverflow == true || obj.options.fullscreen == true) ? true : false, onclose:function() { obj.closeEditor(cell, true); } }; if (obj.options.columns[x].options && obj.options.columns[x].options.type) { options.type = obj.options.columns[x].options.type; } jApp.dropdown(editor, options); } else if (obj.options.columns[x].type == 'calendar' || obj.options.columns[x].type == 'color') { // Value var value = obj.options.data[y][x]; // Create editor var editor = createEditor('input'); editor.value = value; if (obj.options.tableOverflow == true || obj.options.fullscreen == true) { obj.options.columns[x].options.position = true; } obj.options.columns[x].options.value = obj.options.data[y][x]; obj.options.columns[x].options.onclose = function(el, value) { obj.closeEditor(cell, true); } // Current value if (obj.options.columns[x].type == 'color') { jApp.color(editor, obj.options.columns[x].options); } else { var calendar = jApp.calendar(editor, obj.options.columns[x].options); calendar.setValue(value); } // Focus on editor editor.focus(); } else if (obj.options.columns[x].type == 'image') { // Value var img = cell.children[0]; // Create editor var editor = createEditor('div'); editor.style.position = 'relative'; var div = document.createElement('div'); div.classList.add('jclose'); if (img && img.src) { div.appendChild(img); } editor.appendChild(div); jApp.image(div); const rect = cell.getBoundingClientRect(); const rectContent = div.getBoundingClientRect(); if (window.innerHeight < rect.bottom + rectContent.height) { div.style.top = rect.top - (rectContent.height + 2); } else