Skip to content
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

Allow adding new rows or columns on paste #553

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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],
['allowGridSizeChangeOnPaste', false],
ndrsn marked this conversation as resolved.
Show resolved Hide resolved
['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.allowGridSizeChangeOnPaste=false] - Allow adding new rows and columns if pasted data dimentions are bigger than existing grid dimentions.
ndrsn marked this conversation as resolved.
Show resolved Hide resolved
* @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
48 changes: 34 additions & 14 deletions lib/events/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2007,7 +2007,7 @@ export default function (self) {
alwaysFilling = false,
direction = 'both',
}) {
var schema = self.getSchema();
// var schema = self.getSchema();
ndrsn marked this conversation as resolved.
Show resolved Hide resolved
const rowsLength = Math.max(rows.length, minRowsLength);
const fillCellCallback = self.fillCellCallback;
const filledCells = [];
Expand All @@ -2029,12 +2029,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.allowGridSizeChangeOnPaste) {
// 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 +2070,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 +2083,25 @@ export default function (self) {
? cellsLength - colPosReal - 1
: colPosReal;
const columnIndex = startColumnIndex + colPosition;
var column = schema[columnIndex];

if (!column) {
console.warn('Paste data exceeded grid bounds. Skipping.');
continue;
if (!self.getSchema()[columnIndex]) {
if (self.attributes.allowGridSizeChangeOnPaste) {
const lastColSchema = self.getSchema()[
self.orders.columns[self.getSchema().length - 1]
];
const newColSchema = {
...lastColSchema,
name: `col${self.schema.length + 1}:${Date.now()}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not be using self.getSchema() here? Or rather:

const schema = self.getSchema();

if (!schema[columnIndex]) {
  if (self.attributes.allowGridSizeChangeOnPaste) {
    const lastColSchema = schema[self.orders.columns[schema.length - 1]];
  }
  const newColSchema = {
    ...lastColSchema,
    name: `col${schema.length + 1}${Date.now()}`,
    title: ' ',
  };
  self.addColumn(newColSchema)
}

Also, two (three?) other points:

  1. can we safely assume that the last column's schema is similar to the newly pasted column? What is you're pasting text and the previous existing column schema defines it as type: number ?
  2. What is the purpose of the name including the current date? Do we even need to name the new column?
  3. Why the empty title?

title: ' ',
};
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
220 changes: 206 additions & 14 deletions test/editing.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export default function () {
done(
assertIf(
grid.input.childNodes[0].innerHTML === 'A' &&
grid.input.childNodes.length === 3 &&
grid.input.tagName !== 'SELECT',
grid.input.childNodes.length === 3 &&
grid.input.tagName !== 'SELECT',
'Expected an input to have appeared',
),
);
Expand Down Expand Up @@ -438,6 +438,192 @@ export default function () {
);
}, 10);
});

it('paste a 2x3 table into 2x2 grid should add a new column, if allowGridSizeChangeOnPaste === true', function (done) {
var grid = g({
test: this.test,
data: [
{ a: 'a', b: 'b' },
{ a: 'c', b: 'd' },
],
autoGenerateSchema: true,
allowGridSizeChangeOnPaste: true,
});

grid.focus();
grid.setActiveCell(0, 0);
grid.selectArea({ top: 0, left: 0, bottom: 0, right: 0 });

grid.paste({
clipboardData: {
items: [
{
type: 'text/plain',
getAsString: function (callback) {
callback('1\t2\t3\n4\t5\t6');
},
},
],
},
});

setTimeout(function () {
const colName = Object.keys(grid.viewData[0])[2];
const cellData1 = grid.viewData[0][colName];
const cellData2 = grid.viewData[1][colName];

try {
doAssert(
grid.viewData.length === 2 &&
Object.keys(grid.viewData[0]).length === 3,
'New column was not added to the grid',
);
doAssert(
cellData1 === '3' && cellData2 === '6',
'Correct data was not added to the new column',
);
done();
} catch (error) {
done(error);
}
}, 10);
});

it('paste a 3x2 table into 2x2 grid should add a new row, if allowGridSizeChangeOnPaste === true', function (done) {
var grid = g({
test: this.test,
data: [
{ a: 'a', b: 'b' },
{ a: 'c', b: 'd' },
],
autoGenerateSchema: true,
allowGridSizeChangeOnPaste: true,
});

grid.focus();
grid.setActiveCell(0, 0);
grid.selectArea({ top: 0, left: 0, bottom: 0, right: 0 });

grid.paste({
clipboardData: {
items: [
{
type: 'text/plain',
getAsString: function (callback) {
callback('1\t2\n3\t4\n5\t6');
},
},
],
},
});

setTimeout(function () {
const lastRow = grid.viewData[2];
const cellData1 = lastRow['a'];
const cellData2 = lastRow['b'];

try {
doAssert(
grid.viewData.length === 3 &&
Object.keys(grid.viewData[0]).length === 2,
'New row was not added to the grid',
);
doAssert(
cellData1 === '5' && cellData2 === '6',
'Correct data was not added to the new row',
);
done();
} catch (error) {
done(error);
}
}, 10);
});

it('paste a 2x2 table into 1x1 grid should add a new row and a new column, if allowGridSizeChangeOnPaste === true', function (done) {
var grid = g({
test: this.test,
data: [{ a: 'a' }],
autoGenerateSchema: true,
allowGridSizeChangeOnPaste: true,
});

grid.focus();
grid.setActiveCell(0, 0);
grid.selectArea({ top: 0, left: 0, bottom: 0, right: 0 });

grid.paste({
clipboardData: {
items: [
{
type: 'text/plain',
getAsString: function (callback) {
callback('1\t2\n3\t4');
},
},
],
},
});

setTimeout(function () {
const [firstRow, lastRow] = grid.viewData;
const colName = Object.keys(firstRow)[1];
const cellData1 = firstRow[colName];
const cellData2 = lastRow['a'];
const cellData3 = lastRow[colName];

try {
doAssert(
grid.viewData.length === 2 &&
Object.keys(grid.viewData[0]).length === 2,
'New row or new column was not added to the grid',
);
doAssert(
cellData1 === '2' && cellData2 === '3' && cellData3 === '4',
'Correct data was not added to the new row',
);
done();
} catch (error) {
done(error);
}
}, 10);
});

it('paste a 1x1 table into 1x1 grid should not add any new rows or columns, if allowGridSizeChangeOnPaste === true', function (done) {
var grid = g({
test: this.test,
data: [{ a: 'a' }],
autoGenerateSchema: true,
allowGridSizeChangeOnPaste: true,
});

grid.focus();
grid.setActiveCell(0, 0);
grid.selectArea({ top: 0, left: 0, bottom: 0, right: 0 });

grid.paste({
clipboardData: {
items: [
{
type: 'text/plain',
getAsString: function (callback) {
callback('1');
},
},
],
},
});

setTimeout(function () {
done(
doAssert(
grid.viewData.length === 1 &&
Object.keys(grid.viewData[0]).length === 1,
'New row or new column was not added to the grid',
),
);
}, 10);
});

it('paste a Excel table single row / single cell value from the clipboard into a cell', function (done) {
var grid = g({
test: this.test,
Expand Down Expand Up @@ -558,14 +744,20 @@ export default function () {
setTimeout(function () {
try {
doAssert(grid.viewData.length == 3, 'Should have 3 rows exactly');
doAssert(Object.keys(grid.viewData[0]).length == 3, 'Should have 3 columns exactly');
doAssert(
Object.keys(grid.viewData[0]).length == 3,
'Should have 3 columns exactly',
);

for (let i = 0; i < grid.viewData.length; i++) {
for (const columnKey in grid.viewData[i]) {
const currentValue = grid.viewData[i][columnKey];
doAssert(
currentValue === 'New value',
'Value for "' + columnKey + '" should be "New value", but got ' + currentValue
'Value for "' +
columnKey +
'" should be "New value", but got ' +
currentValue,
);
}
}
Expand Down Expand Up @@ -617,7 +809,10 @@ export default function () {
setTimeout(function () {
try {
doAssert(grid.viewData.length == 2, 'Should have 2 rows exactly');
doAssert(Object.keys(grid.viewData[0]).length == 3, 'Should have 3 columns exactly');
doAssert(
Object.keys(grid.viewData[0]).length == 3,
'Should have 3 columns exactly',
);

const expectedResult = [
{
Expand All @@ -638,7 +833,7 @@ export default function () {
const currentValue = grid.viewData[i][columnKey];
doAssert(
currentValue === expectedValue,
`Value for "${columnKey}" should be "${expectedValue}", but got "${currentValue}"`
`Value for "${columnKey}" should be "${expectedValue}", but got "${currentValue}"`,
);
}
}
Expand Down Expand Up @@ -808,14 +1003,11 @@ export default function () {
it('Moving handle on desktop fills the overlay region with selection data', function (done) {
const grid = g({
test: this.test,
data: [
{ field1: 'value1' },
{ field1: 'value2' },
{ field1: 'value3' },
],
data: [{ field1: 'value1' }, { field1: 'value2' }, { field1: 'value3' }],
fillCellCallback: function (args) {
return args.newCellData + ' ' +
(args.fillingRowPosition + 1).toString();
return (
args.newCellData + ' ' + (args.fillingRowPosition + 1).toString()
);
},
selectionHandleBehavior: 'single',
});
Expand All @@ -839,7 +1031,7 @@ export default function () {
const currentValue = grid.viewData[i][columnKey];
doAssert(
currentValue === expectedValue,
`Value for "${columnKey}" should be "${expectedValue}", but got "${currentValue}"`
`Value for "${columnKey}" should be "${expectedValue}", but got "${currentValue}"`,
);
}
}
Expand Down