Skip to content

Allow adding new rows or columns on paste #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function (self) {
['allowSorting', true],
['allowGroupingRows', true],
['allowGroupingColumns', true],
['allowGridExpandOnPaste', false],
['animationDurationShowContextMenu', 50],
['animationDurationHideContextMenu', 50],
['autoGenerateSchema', false],
Expand Down
1 change: 1 addition & 0 deletions lib/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* @param {string} [args.blanksText=(Blanks)] - The text that appears on the context menu for filtering blank values (i.e. `undefined`, `null`, `''`).
* @param {string} [args.ellipsisText=...] - The text used as ellipsis text on truncated values.
* @param {boolean} [args.allowSorting=true] - Allow user to sort rows by clicking on column headers.
* @param {boolean} [args.allowGridExpandOnPaste=false] - Allow adding new rows and columns if pasted data dimensions are bigger than existing grid dimensions.
* @param {boolean} [args.allowGroupingColumns=true] - Allow user to group columns by clicking on column headers.
* @param {boolean} [args.allowGroupingRows=true] - Allow user to group rows by clicking on rows headers.
* @param {boolean} [args.showFilter=true] - When true, filter will be an option in the context menu.
Expand Down
50 changes: 35 additions & 15 deletions lib/events/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict';

import isPrintableKeyEvent from 'is-printable-key-event';
import { isSupportedHtml, parseData } from './util';
import { isSupportedHtml, parseData, deduceColTypeFromData } from './util';

export default function (self) {
var wheeling;
Expand Down Expand Up @@ -2007,7 +2007,6 @@ export default function (self) {
alwaysFilling = false,
direction = 'both',
}) {
var schema = self.getSchema();
const rowsLength = Math.max(rows.length, minRowsLength);
const fillCellCallback = self.fillCellCallback;
const filledCells = [];
Expand All @@ -2029,12 +2028,22 @@ export default function (self) {
: rowPosReal;
// Rows may have been moved by user, so get the actual row index
// (instead of the row index at which the row is rendered):
var realRowIndex = self.orders.rows[startRowIndex + rowPosition];
var cells = rows[rowDataPos];
if (self.originalData[startRowIndex + rowPosition] === undefined) {
if (self.attributes.allowGridExpandOnPaste) {
// This needs to be optimized because .addRow() calls .refresh()
self.addRow({});
} else {
console.warn('Paste data exceeded grid bounds. Skipping.');
break;
}
}
const realRowIndex = self.orders.rows[startRowIndex + rowPosition];

const cells = rows[rowDataPos];
const cellsLength = Math.max(cells.length, minColumnsLength);

var existingRowData = self.viewData[realRowIndex];
var newRowData = Object.assign({}, existingRowData);
const existingRowData = self.viewData[realRowIndex];
const newRowData = Object.assign({}, existingRowData);
const fillArgs = fillCellCallback
? {
rows: rows,
Expand All @@ -2060,7 +2069,7 @@ export default function (self) {
: undefined;

for (
var colPosReal = 0, cellDataPos = 0;
let colPosReal = 0, cellDataPos = 0;
colPosReal < cellsLength;
colPosReal++, cellDataPos++
) {
Expand All @@ -2073,15 +2082,26 @@ export default function (self) {
? cellsLength - colPosReal - 1
: colPosReal;
const columnIndex = startColumnIndex + colPosition;
var column = schema[self.orders.columns[columnIndex]];

if (!column) {
console.warn('Paste data exceeded grid bounds. Skipping.');
continue;
if (!self.getSchema()[columnIndex]) {
if (self.attributes.allowGridExpandOnPaste) {
const lastColSchema = self.getSchema()[
self.orders.columns[self.getSchema().length - 1]
];
const newColSchema = {
name: `col${self.getSchema().length + 1}:${Date.now()}`,
type: deduceColTypeFromData(rows, colPosReal),
title: ' ',
width: lastColSchema.width,
};
self.addColumn(newColSchema);
} else {
console.warn('Paste data exceeded grid bounds. Skipping.');
continue;
}
}

var columnName = column.name;
var cellData = cells[cellDataPos];
const column = self.getSchema()[self.orders.columns[columnIndex]];
const columnName = column.name;
let cellData = cells[cellDataPos];
if (cellData && cellData.value) {
cellData = cellData.value.map((item) => item.value).join('');
}
Expand Down
30 changes: 30 additions & 0 deletions lib/events/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,35 @@ const createHTMLString = function (selectedData, isNeat) {
return htmlString;
};

/**
* deduce the type of values in the column 'colNum' of data represented by 'rows'
* if the column is not empty and all values in the column are numbers, then the type is 'number',
* otherwise the type is 'string'.
* Purpose: check the type of pasted data when a new column is created dynamically
* @param {Array} rows - array of rows, e.g. [ [{"value": [{"value": "a"}]},{"value": [{"value": "b"}]}], [{"value": [{"value": "1"}]},{"value": [{"value": "2"}]}] ]
* @param {number} colNum - Column index to check
* @returns {("string" | "number")} deduced type
*/
const deduceColTypeFromData = function (rows, colNum) {
const len = rows.length;
// the first row can represent column headings.
// if one row is pasted, check it, otherwise start from the second row.
const startRow = len === 1 ? 0 : 1;
let isColEmpty = true;
for (let i = startRow; i < len; i += 1) {
const cellData = rows[i][colNum];
if (!cellData || cellData.value.length === 0) {
continue;
}
isColEmpty = false;
const val = cellData.value[0].value;
if (!Number.isFinite(Number(val))) {
return 'string';
}
}
return isColEmpty ? 'string' : 'number';
};

export {
createTextString,
createHTMLString,
Expand All @@ -132,4 +161,5 @@ export {
parseHtmlText,
parseText,
sanitizeElementData,
deduceColTypeFromData,
};
Loading