Persistence
This section covers practices for saving data grid data, including the most popular techniques.
What's new with Version 13
- Original input: persistence payloads carry the method input exactly as given. Transformations such as casting, formulas and events are repeated by every peer that replays the operation, and internal working fields never travel
- Identity: rows and columns carry a
guid, anddeleteRowanddeleteColumnbroadcast the guids aligned with the positions,deleteRow(rows, guids), so all connected users agree on what was removed- Sub-steps are not persisted: the handler receives the public operation only, never the internal steps it runs, such as the nested header updates inside
insertColumn- View state: zoom, selection, group state, search and pagination are personal and never reach the handler
Documentation
Methods
| Method | Description |
|---|---|
getRowId |
Get the row ID from a given row number.getRowId(rowIndex: Number) : String | Number |
setRowId |
Set the row ID for a given row number.setRowId(rowIndex: Number, rowId: Number | String) : void |
getRowById |
Get the data from a row, or the row object by ID.getRowById(rowId: Number, element?: Boolean) : object | array |
Settings
On the spreadsheet configuration level
| Property | Description |
|---|---|
persistence: function |
A function to handle all changes in the spreadsheet.persistence(worksheet: Object, method: String, args: Object) => void |
autoId: boolean |
Automatically generate GUID identifiers for rows |
What the handler receives
The handler is called once per public operation, with the method name and its input exactly as given, so a peer or a server can replay it by calling the same method with the same arguments. Transformations such as casting, formula evaluation and events are repeated on the replaying side, and internal working fields never travel. The internal steps an operation runs, for example the nested header updates inside insertColumn, are not reported separately.
Rows and columns carry an identity guid, generated automatically. deleteRow and deleteColumn report the positions with the guids aligned as the second argument, deleteRow(rows, guids), so every connected user agrees on what was removed even after concurrent structural changes. Personal view state, such as zoom, selection, group state, search and pagination, never reaches the handler.
On the worksheet configuration level
| Property | Description |
|---|---|
rows.id: string | number |
Define the unique identifier for the row |
rows.guid: string |
Identity of the row, generated automatically. Do not overwrite it |
Examples
General persistence implementation
Use the persistence property to send all spreadsheet changes to your server.
<html>
<script src="https://jspreadsheet.com/v13/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v13/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<div id="spreadsheet"></div>
<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
const data = [
{
"title": "Unlocking Android",
"pageCount": 416,
"publishedDate": "2009-04-01" ,
"authors": "W. Frank Ableson, Charlie Collins, Robi Sen",
"categories": "Open Source;Mobile"
},
{
"title": "Android in Action, Second Edition",
"pageCount": 592,
"publishedDate": "2011-01-14",
"authors": "W. Frank Ableson, Robi Sen",
"categories": "Java"
},
{
"title": "Flex 3 in Action",
"pageCount": 576,
"publishedDate": "2009-02-02",
"authors": "Tariq Ahmed with Jon Hirschi, Faisal Abid",
"categories": "Internet"
},
];
/**
* Aggregate all the changes in the same row
*/
const aggregateRows = function(worksheet, data) {
let rows = [];
for (let i = 0; i < data.length; i++) {
// Coords
let x = data[i].x;
let y = data[i].y;
// Create row
if (! rows[y]) {
rows[y] = {
id: worksheet.rows[y].id,
row: y,
data: {},
}
}
// Key
let key = worksheet.options.columns[x].name;
// Data
rows[y].data[key] = data[i].value;
}
// Filter rows
return rows.filter(function(row) {
return row != null;
});
}
// Create the spreadsheet
jspreadsheet(document.getElementById('spreadsheet'), {
worksheets: [
{
autoId: true,
data: data,
columns: [
{
type: 'text',
title: 'Title',
name: 'title',
width: '150px',
},
{
type: 'text',
width: '55px',
title: 'Pages',
name: 'pageCount'
},
{
type: 'calendar',
width: '90px',
title: 'Published',
name: 'publishedDate'
},
{
type: 'text',
title: 'Author',
name: 'authors',
width: '150px',
},
{
type: 'dropdown',
title: 'Categories',
name: 'categories',
source: [
'Internet',
'Web Development',
'Java',
'Mobile',
'Open Source'
],
width: '200px',
render: 'tag',
multiple: true
},
],
rows: [
{ id: '4ab2b86b-6b48-4234-82c9-ea614374fb0b' },
{ id: 'ce9fbb3a-2330-425d-9a81-e75b711bec8d' },
{ id: 'd57533c3-d181-4083-b999-b8bc58bbf236' }
]
}
],
persistence: function(worksheet, method, payload) {
let ignore = ['setBorder', 'resetBorders' ];
// Ignore the methods to update the borders
if (ignore.indexOf(method) >= 0) {
return false;
} else {
// Aggregate rows for the following methods
if (method === 'setValue' || method === 'setFormula') {
payload = aggregateRows(worksheet, payload.data)
}
// Create attributes to be sent
let formData = new FormData();
formData.append('data', JSON.stringify(payload));
// Fetch options
const fetchOptions = {
method: 'POST',
headers: {
'X-Requested-With': 'http',
'Authorization': 'Bearer your-token',
},
body: formData,
};
// Perform the fetch
fetch('/save', fetchOptions).then(response => response.json()).then(function (result) {
// Save your data remotely
}).catch(function (result) {
// Something went wrong
});
}
}
});
</script>
</html>
import React, { useRef } from "react";
import { Spreadsheet, Worksheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
/**
* Aggregate all the changes in the same row
*/
const aggregateRows = function(worksheet, data) {
let rows = [];
for (let i = 0; i < data.length; i++) {
// Coords
let x = data[i].x;
let y = data[i].y;
// Create row
if (! rows[y]) {
rows[y] = {
id: worksheet.rows[y].id,
row: y,
data: {},
}
}
// Key
let key = worksheet.options.columns[x].name;
// Data
rows[y].data[key] = data[i].value;
}
// Filter rows
return rows.filter(function(row) {
return row != null;
});
}
export default function App() {
const spreadsheet = useRef();
// Data
const data = [
{
"title": "Unlocking Android",
"pageCount": 416,
"publishedDate": "2009-04-01" ,
"authors": "W. Frank Ableson, Charlie Collins, Robi Sen",
"categories": "Open Source;Mobile"
},
{
"title": "Android in Action, Second Edition",
"pageCount": 592,
"publishedDate": "2011-01-14",
"authors": "W. Frank Ableson, Robi Sen",
"categories": "Java"
},
{
"title": "Flex 3 in Action",
"pageCount": 576,
"publishedDate": "2009-02-02",
"authors": "Tariq Ahmed with Jon Hirschi, Faisal Abid",
"categories": "Internet"
},
];
// Rows with IDs
const rows = [
{ id: '4ab2b86b-6b48-4234-82c9-ea614374fb0b' },
{ id: 'ce9fbb3a-2330-425d-9a81-e75b711bec8d' },
{ id: 'd57533c3-d181-4083-b999-b8bc58bbf236' }
];
// Columns
const columns = [
{
type: 'text',
title: 'Title',
name: 'title',
width: '150px',
},
{
type: 'text',
width: '55px',
title: 'Pages',
name: 'pageCount'
},
{
type: 'calendar',
width: '90px',
title: 'Published',
name: 'publishedDate'
},
{
type: 'text',
title: 'Author',
name: 'authors',
width: '150px',
},
{
type: 'dropdown',
title: 'Categories',
name: 'categories',
source: [
'Internet',
'Web Development',
'Java',
'Mobile',
'Open Source'
],
width: '200px',
render: 'tag',
multiple: true
},
];
// Persistence function
const persistence = function(worksheet, method, payload) {
let ignore = ['setBorder', 'resetBorders' ];
// Ignore the methods to update the borders
if (ignore.indexOf(method) >= 0) {
return false;
} else {
// Aggregate rows for the following methods
if (method === 'setValue' || method === 'setFormula') {
payload = aggregateRows(worksheet, payload.data)
}
// Create attributes to be sent
let formData = new FormData();
formData.append('data', JSON.stringify(payload));
// Fetch options
const fetchOptions = {
method: 'POST',
headers: {
'X-Requested-With': 'http',
'Authorization': 'Bearer your-token',
},
body: formData,
};
// Perform the fetch
fetch('/save', fetchOptions).then(response => response.json()).then(function (result) {
// Save your data remotely
}).catch(function (result) {
// Something went wrong
});
}
}
// Render react component
return (
<Spreadsheet ref={spreadsheet} persistence={persistence}>
<Worksheet autoId={true} data={data} rows={rows} columns={columns} />
</Spreadsheet>
);
}
<template>
<Spreadsheet ref="spreadsheet" :persistence="persistence">
<Worksheet :autoId="true" :data="data" :rows="rows" :columns="columns" />
</Spreadsheet>
</template>
<script>
import { Spreadsheet, Worksheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
/**
* Aggregate all the changes in the same row
*/
const aggregateRows = function(worksheet, data) {
let rows = [];
for (let i = 0; i < data.length; i++) {
// Coords
let x = data[i].x;
let y = data[i].y;
// Create row
if (! rows[y]) {
rows[y] = {
id: worksheet.rows[y].id,
row: y,
data: {},
}
}
// Key
let key = worksheet.options.columns[x].name;
// Data
rows[y].data[key] = data[i].value;
}
// Filter rows
return rows.filter(function(row) {
return row != null;
});
}
export default {
components: {
Spreadsheet,
Worksheet,
},
methods: {
// Persistence function
persistence(worksheet, method, payload) {
let ignore = ['setBorder', 'resetBorders' ];
// Ignore the methods to update the borders
if (ignore.indexOf(method) >= 0) {
return false;
} else {
// Aggregate rows for the following methods
if (method === 'setValue' || method === 'setFormula') {
payload = aggregateRows(worksheet, payload.data)
}
// Create attributes to be sent
let formData = new FormData();
formData.append('data', JSON.stringify(payload));
// Fetch options
const fetchOptions = {
method: 'POST',
headers: {
'X-Requested-With': 'http',
'Authorization': 'Bearer your-token',
},
body: formData,
};
// Perform the fetch
fetch('/save', fetchOptions).then(response => response.json()).then(function (result) {
// Save your data remotely
}).catch(function (result) {
// Something went wrong
});
}
}
},
data() {
// Data
const data = [
{
"title": "Unlocking Android",
"pageCount": 416,
"publishedDate": "2009-04-01" ,
"authors": "W. Frank Ableson, Charlie Collins, Robi Sen",
"categories": "Open Source;Mobile"
},
{
"title": "Android in Action, Second Edition",
"pageCount": 592,
"publishedDate": "2011-01-14",
"authors": "W. Frank Ableson, Robi Sen",
"categories": "Java"
},
{
"title": "Flex 3 in Action",
"pageCount": 576,
"publishedDate": "2009-02-02",
"authors": "Tariq Ahmed with Jon Hirschi, Faisal Abid",
"categories": "Internet"
},
];
// Rows with IDs
const rows = [
{ id: '4ab2b86b-6b48-4234-82c9-ea614374fb0b' },
{ id: 'ce9fbb3a-2330-425d-9a81-e75b711bec8d' },
{ id: 'd57533c3-d181-4083-b999-b8bc58bbf236' }
];
// Columns
const columns = [
{
type: 'text',
title: 'Title',
name: 'title',
width: '150px',
},
{
type: 'text',
width: '55px',
title: 'Pages',
name: 'pageCount'
},
{
type: 'calendar',
width: '90px',
title: 'Published',
name: 'publishedDate'
},
{
type: 'text',
title: 'Author',
name: 'authors',
width: '150px',
},
{
type: 'dropdown',
title: 'Categories',
name: 'categories',
source: [
'Internet',
'Web Development',
'Java',
'Mobile',
'Open Source'
],
width: '200px',
render: 'tag',
multiple: true
},
];
return {
data,
rows,
columns,
};
}
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import "jspreadsheet/dist/jspreadsheet.css"
import "jsuites/dist/jsuites.css"
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
/**
* Aggregate all the changes in the same row
*/
const aggregateRows = function(worksheet, data) {
let rows = [];
for (let i = 0; i < data.length; i++) {
// Coords
let x = data[i].x;
let y = data[i].y;
// Create row
if (! rows[y]) {
rows[y] = {
id: worksheet.rows[y].id,
row: y,
data: {},
}
}
// Key
let key = worksheet.options.columns[x].name;
// Data
rows[y].data[key] = data[i].value;
}
// Filter rows
return rows.filter(function(row) {
return row != null;
});
}
@Component({
standalone: true,
selector: "app-root",
template: `<div #spreadsheet></div>`,
})
export class AppComponent {
@ViewChild("spreadsheet") spreadsheet: ElementRef;
// Worksheets
worksheets: jspreadsheet.worksheetInstance[];
// Create a new data grid
ngAfterViewInit() {
// Create spreadsheet
this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
worksheets: [
{
autoId: true,
data: [
{
"title": "Unlocking Android",
"pageCount": 416,
"publishedDate": "2009-04-01" ,
"authors": "W. Frank Ableson, Charlie Collins, Robi Sen",
"categories": "Open Source;Mobile"
},
{
"title": "Android in Action, Second Edition",
"pageCount": 592,
"publishedDate": "2011-01-14",
"authors": "W. Frank Ableson, Robi Sen",
"categories": "Java"
},
{
"title": "Flex 3 in Action",
"pageCount": 576,
"publishedDate": "2009-02-02",
"authors": "Tariq Ahmed with Jon Hirschi, Faisal Abid",
"categories": "Internet"
},
],
columns: [
{
type: 'text',
title: 'Title',
name: 'title',
width: '150px',
},
{
type: 'text',
width: '55px',
title: 'Pages',
name: 'pageCount'
},
{
type: 'calendar',
width: '90px',
title: 'Published',
name: 'publishedDate'
},
{
type: 'text',
title: 'Author',
name: 'authors',
width: '150px',
},
{
type: 'dropdown',
title: 'Categories',
name: 'categories',
source: [
'Internet',
'Web Development',
'Java',
'Mobile',
'Open Source'
],
width: '200px',
render: 'tag',
multiple: true
},
],
rows: [
{ id: '4ab2b86b-6b48-4234-82c9-ea614374fb0b' },
{ id: 'ce9fbb3a-2330-425d-9a81-e75b711bec8d' },
{ id: 'd57533c3-d181-4083-b999-b8bc58bbf236' }
]
}
],
persistence: function(worksheet, method, payload) {
let ignore = ['setBorder', 'resetBorders' ];
// Ignore the methods to update the borders
if (ignore.indexOf(method) >= 0) {
return false;
} else {
// Aggregate rows for the following methods
if (method === 'setValue' || method === 'setFormula') {
payload = aggregateRows(worksheet, payload.data)
}
// Create attributes to be sent
let formData = new FormData();
formData.append('data', JSON.stringify(payload));
// Fetch options
const fetchOptions = {
method: 'POST',
headers: {
'X-Requested-With': 'http',
'Authorization': 'Bearer your-token',
},
body: formData,
};
// Perform the fetch
fetch('/save', fetchOptions).then(response => response.json()).then(function (result) {
// Save your data remotely
}).catch(function (result) {
// Something went wrong
});
}
}
});
}
}
Custom Row IDs
The following example demonstrates custom ID generation with action buttons embedded in the last column.
<html>
<script src="https://jspreadsheet.com/v13/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v13/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<div id="spreadsheet"></div>
<script>
const action = function() {
let methods = {};
methods.createCell = function(cell, value, x, y, instance, options) {
let input = document.createElement('i');
input.className = 'material-icons';
input.style.cursor = 'pointer';
input.style.fontSize = '22px';
input.innerHTML = "search";
input.onclick = function() {
let id = instance.getRowId(y);
// Do some action
alert(id);
}
cell.appendChild(input);
// Readonly
cell.classList.add('readonly');
}
return methods;
}();
// New random GUIDs
const guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0;
let v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
jspreadsheet(document.getElementById('spreadsheet'), {
worksheets: [
{
data: [
['Google', '5', ''],
['Bing', '4', ''],
['Yahoo', '1', ''],
['Duckduckgo', '5', ''],
],
columns: [
{ type: 'text', width:'400px' },
{ type: 'rating', width:'100px' },
{ type: action, width:'100px' },
],
rows: [
{ id: 'cae4673d-5c11-4ece-beb0-4d89fad5510c' },
{ id: '0bb4800f-c997-4f3c-b1aa-303bc49096df' },
{ id: '52694185-722d-47cf-b35e-ffa6323ddcef' },
{ id: '370915fe-0ce0-464a-915a-cdcf1c157b20' },
]
}
],
onbeforeinsertrow: function(worksheet, rows) {
// Generate new random GUIDs for the new rows
return rows.map(function(v) {
return { ...v, id: guid() };
});
}
});
</script>
</html>
import React, { useRef } from "react";
import { Spreadsheet, Worksheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
const action = function() {
const methods = {};
methods.createCell = function(cell, value, x, y, instance, options) {
let input = document.createElement('i');
input.className = 'material-icons';
input.style.cursor = 'pointer';
input.style.fontSize = '22px';
input.innerHTML = "search";
input.onclick = function() {
let id = instance.getRowId(y);
// Do some action
alert(id);
}
cell.appendChild(input);
// Readonly
cell.classList.add('readonly');
}
return methods;
}();
// New random GUIDs
const guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0;
let v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
export default function App() {
const spreadsheet = useRef();
// Data
const data = [
['Google', '5', ''],
['Bing', '4', ''],
['Yahoo', '1', ''],
['Duckduckgo', '5', ''],
];
// Rows with IDs
const rows = [
{ id: 'cae4673d-5c11-4ece-beb0-4d89fad5510c' },
{ id: '0bb4800f-c997-4f3c-b1aa-303bc49096df' },
{ id: '52694185-722d-47cf-b35e-ffa6323ddcef' },
{ id: '370915fe-0ce0-464a-915a-cdcf1c157b20' },
];
// Event handler for before insert row
const onbeforeinsertrow = function(worksheet, rows) {
// Generate new random GUIDs for the new rows
return rows.map(function(v) {
return { ...v, id: guid() };
});
}
// Columns
const columns = [
{ type: 'text', width:'400px' },
{ type: 'rating', width:'100px' },
{ type: action, width:'100px' },
];
// Render react component
return (
<Spreadsheet ref={spreadsheet} onbeforeinsertrow={onbeforeinsertrow}>
<Worksheet data={data} rows={rows} columns={columns} />
</Spreadsheet>
);
}
<template>
<Spreadsheet ref="spreadsheet" :onbeforeinsertrow="onbeforeinsertrow">
<Worksheet :data="data" :rows="rows" :columns="columns" />
</Spreadsheet>
</template>
<script>
import { Spreadsheet, Worksheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
const action = function() {
const methods = {};
methods.createCell = function(cell, value, x, y, instance, options) {
let input = document.createElement('i');
input.className = 'material-icons';
input.style.cursor = 'pointer';
input.style.fontSize = '22px';
input.innerHTML = "search";
input.onclick = function() {
let id = instance.getRowId(y);
// Do some action
alert(id);
}
cell.appendChild(input);
// Readonly
cell.classList.add('readonly');
}
return methods;
}();
// New random GUIDs
const guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0;
let v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
export default {
components: {
Spreadsheet,
Worksheet,
},
methods: {
// Event handler for before insert row
onbeforeinsertrow(worksheet, rows) {
// Generate new random GUIDs for the new rows
return rows.map(function(v) {
return { ...v, id: guid() };
});
}
},
data() {
// Data
const data = [
['Google', '5', ''],
['Bing', '4', ''],
['Yahoo', '1', ''],
['Duckduckgo', '5', ''],
];
// Rows with IDs
const rows = [
{ id: 'cae4673d-5c11-4ece-beb0-4d89fad5510c' },
{ id: '0bb4800f-c997-4f3c-b1aa-303bc49096df' },
{ id: '52694185-722d-47cf-b35e-ffa6323ddcef' },
{ id: '370915fe-0ce0-464a-915a-cdcf1c157b20' },
];
// Columns
const columns = [
{ type: 'text', width:'400px' },
{ type: 'rating', width:'100px' },
{ type: action, width:'100px' },
];
return {
data,
rows,
columns,
};
}
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import "jspreadsheet/dist/jspreadsheet.css"
import "jsuites/dist/jsuites.css"
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NzdkNzc0ZjhlN2Q1YjRhZWI4ZWM1N2U4ODdiYzE1Y2UzMjZkZWNiMTdmZjU5NzI1MTM0N2NkY2Y5MDhhZWZlYTc2ZGRiY2JlMTg0NWZkYjc5MTRiNzE4MjI4ZDA0ZjI4YjRmMGMwNWQ1ZmFlNzhjMDI3YjBhODBhZDYzZWMyNTgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlPRE15TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
const action = function() {
let methods = {};
methods.createCell = (cell, value, x, y, instance, options) => {
let input = document.createElement('i');
input.className = 'material-icons';
input.style.cursor = 'pointer';
input.style.fontSize = '22px';
input.innerHTML = "search";
input.onclick = function() {
let id = instance.getRowId(y);
// Do some action
alert(id);
}
cell.appendChild(input);
// Readonly
cell.classList.add('readonly');
}
return methods;
}();
// New random GUIDs
const guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0;
let v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
@Component({
standalone: true,
selector: "app-root",
template: `<div #spreadsheet></div>`,
})
export class AppComponent {
@ViewChild("spreadsheet") spreadsheet: ElementRef;
// Worksheets
worksheets: jspreadsheet.worksheetInstance[];
// Create a new data grid
ngAfterViewInit() {
// Create spreadsheet
this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
worksheets: [{
data: [
['Google', '5', ''],
['Bing', '4', ''],
['Yahoo', '1', ''],
['Duckduckgo', '5', ''],
],
columns: [
{ type: 'text', width:'400px' },
{ type: 'rating', width:'100px' },
{ type: action, width:'100px' },
],
rows: [
{ id: 'cae4673d-5c11-4ece-beb0-4d89fad5510c' },
{ id: '0bb4800f-c997-4f3c-b1aa-303bc49096df' },
{ id: '52694185-722d-47cf-b35e-ffa6323ddcef' },
{ id: '370915fe-0ce0-464a-915a-cdcf1c157b20' },
],
}],
onbeforeinsertrow: function(worksheet, rows) {
// Generate new random GUIDs for the new rows
return rows.map(function(v) {
return { ...v, id: guid() };
});
}
});
}
}