Events

ReportSheet have data entry events that can be bound while report data changing or submitting.

With below events, users can valid all data changes or cancel the operation while data changing, can also get the feedback result of server then show the result in UI.

Description
app.js
index.html
styles.css
Copy to CodeMine
Open the demo in new window can open the designer

How to open desgnr

ReportSheet supports below data entry events. You can use the following code to bind the data entry events:

  1. ReportSheetDataChanging and ReportSheetDataChanged

ReportSheetDataChanging occurs when the report sheet is changing by update, insert or delete. Allows validating or cancellation of the data operation.

ReportSheetDataChanged occurs when the report sheet data has changed by update, insert or delete. Allows submitting data changes to the server.

const report = spread.addSheetTab(0, "Report", GC.Spread.Sheets.SheetType.reportSheet);
report.renderMode("Design");
const templateSheet = report.getTemplate();
templateSheet.setTemplateCell(0, 0, {
   binding: "Orders[orderId]",
   type: "Group",
   showCollapseButton: true
});
templateSheet.setTemplateCell(0, 1, {
   binding: "Orders[customerId]",
   type: "Group",
});
templateSheet.setDataEntrySetting([ {
   name: "Write Back Rule 1",
   tableName: "Orders",
   fields: [
       { dbColumnName: "orderId", formula: "A1", isPrimary: true },
       { dbColumnName: "customerId", formula: "B1" },
   ],
   includeUnmodified: false,
   skipRecordWithEmptyValue: false
} ]);

report.bind(GC.Spread.Sheets.Events.ReportSheetDataChanging, (event, args) => {
    let { type, row, col, oldValue, newValue } = args;
    if (allowChange(oldValue, newValue)) { // user validate this changing operation here.
        console.log(`Changing row: ${row}, col: ${col} from ${oldValue} to ${newValue}`);
    } else {
        args.cancel = true; // user cancel this operation here.
    }
});
report.bind(GC.Spread.Sheets.Events.ReportSheetDataChanged, (event, args) => {
    let changes = report.getChanges(); // user can get all report changes here.
    if (needSubmit(changes)) {
        report.submit(); // submit changes.
    } else {
        report.refresh(); // restore report.
    }
});
// data entry operations by UI.
report.renderMode("Preview");
report.updateCellValue(0, 1, "test");
report.addRecordAt(1, 0);
report.updateCellValue(2, 0, 111);
report.updateCellValue(2, 1, "test2");
report.deleteRecordAt(3, 0);
  1. ReportSheetRecordsSubmitting and ReportSheetRecordsSubmitted

ReportSheetRecordsSubmitting occurs before the report sheet submitting changes to server. Allows final validation of all data changes or cancellign the operation.

ReportSheetRecordsSubmitted occurs after the report sheet submitted the changes to server. Allow user to provide UI feedback of the submit results from the server.

const report = spread.addSheetTab(0, "Report", GC.Spread.Sheets.SheetType.reportSheet);
report.renderMode("Design");
const templateSheet = report.getTemplate();
templateSheet.setTemplateCell(0, 0, {
   binding: "Orders[orderId]",
   type: "Group",
   showCollapseButton: true
});
templateSheet.setTemplateCell(0, 1, {
   binding: "Orders[customerId]",
   type: "Group",
});
templateSheet.setDataEntrySetting([ {
   name: "Write Back Rule 1",
   tableName: "Orders",
   fields: [
       { dbColumnName: "orderId", formula: "A1", isPrimary: true },
       { dbColumnName: "customerId", formula: "B1" },
   ],
   includeUnmodified: false,
   skipRecordWithEmptyValue: false
} ]);
// For some users want to validate the data before submitting to server. They can use the ReportSheetRecordsSubmitting event.
reportsheet.bind(GC.Spread.Sheets.Events.ReportSheetRecordsSubmitting, (event, args) => {
    let changes = args.sheet.getChanges(); // get reportSheet all changes.
    for (let change of changes) {
        let { records, deleteRecords, rule } = change;
        let updateRecordsData = records.map((r) => r.entity), deleteRecordsData = deleteRecords.map((r) => r.entity);
        let tableName = rule.tableName;
        if (isInvalidData(updateRecordsData, tableName)) { // users validate data.
            //cancel the reportsheet submit operation.
            args.cancel = true;
            break;
        }
    }
});
// For some users want to customize the submit result callback, they can use the ReportSheetRecordsSubmitted event to get the successed and failed result.
reportsheet.bind(GC.Spread.Sheets.Events.ReportSheetRecordsSubmitted, (event, args) => {
    let { updateSuccessRecords, deleteSuccessRecords, updateFailedRecords, deleteFailedRecords } = args;
    for (let record of updateFailedRecords) {
        for (let fieldKey in record.info) {
            if (record.info.hasOwnproperty(fieldKey)) {
                let recordDetail = record.info[fieldKey];
                if (recordDetail.state === "updated") {
                    // user console the failed detail info.
                    console.log(`Updated failed in row: ${recordDetail.row} col: ${recordDetail.col}, oldValue: ${recordDetail.oldValue}, reason is ${record.reason}`);
                }
            }
        }
    }
});
// data entry operations by UI.
report.renderMode("Preview");
report.updateCellValue(0, 1, "test");
report.addRecordAt(1, 0);
report.updateCellValue(2, 0, 111);
report.updateCellValue(2, 1, "test2");
report.deleteRecordAt(3, 0);
report.submit();
Open the demo in new window can open the designer ReportSheet supports below data entry events. You can use the following code to bind the data entry events: ReportSheetDataChanging and ReportSheetDataChanged ReportSheetDataChanging occurs when the report sheet is changing by update, insert or delete. Allows validating or cancellation of the data operation. ReportSheetDataChanged occurs when the report sheet data has changed by update, insert or delete. Allows submitting data changes to the server. ReportSheetRecordsSubmitting and ReportSheetRecordsSubmitted ReportSheetRecordsSubmitting occurs before the report sheet submitting changes to server. Allows final validation of all data changes or cancellign the operation. ReportSheetRecordsSubmitted occurs after the report sheet submitted the changes to server. Allow user to provide UI feedback of the submit results from the server.
window.onload = async () => { const loadingTip = addLoadingTip(); const { spread, designer } = createSpreadAndDesigner(); const res = await fetch('$DEMOROOT$/en/sample/features/report-sheet/data-entry/events/spread.json'); await spread.fromJSON(correctTableUrl(await res.json())); loadingTip.remove(); _bindDataEntrySubmitEvents(spread); if (designer) { designer.refresh(); } } function createSpreadAndDesigner() { const demoHost = document.getElementById('demo-host'); if (window !== top) { return { spread: new GC.Spread.Sheets.Workbook(demoHost, { sheetCount: 1 }), } } else { const designer = new GC.Spread.Sheets.Designer.Designer(demoHost, undefined, undefined, { sheetCount: 1 }); return { designer, spread: designer.getWorkbook(), } } } function addLoadingTip() { const div = document.createElement('div'); div.style.position = 'absolute'; div.style.inset = '0'; div.style.display = 'flex'; div.style.alignItems = 'center'; div.style.justifyContent = 'center'; div.style.background = 'white'; div.style.zIndex = '100'; div.textContent = 'Loading data from server ...'; document.body.appendChild(div); return div; } function correctTableUrl(json) { const baseUrl = '$DEMOROOT$'; const localUrl = 'http://localhost:8070/spreadjs/demos'; const replaceUrl = (obj) => { if (obj && obj.url && obj.url.startsWith(localUrl)) { obj.url = obj.url.replace(localUrl, baseUrl); } } if (json.dataManager && json.dataManager.tables) { json.dataManager.tables.forEach((table) => { if (table.dataSourceOption && table.dataSourceOption.remote) { Object.values(table.dataSourceOption.remote).forEach(replaceUrl); } }); } return json; } let time; function _showWindowTip (message, success) { let tipContainer = document.querySelector('.tip-container'); if (!tipContainer) { const div = document.createElement('div'); div.classList.add("tip-container"); document.body.appendChild(div); tipContainer = div; } let innerContainer = document.createElement('div'); innerContainer.className = `tip-inner-container ${success ? 'success' : 'failed'}`; innerContainer.innerHTML = ` <h1>${success ? "Success" : "Failed"}</h1> <p>${message}</p> `; tipContainer.appendChild(innerContainer); if (time) { clearTimeout(time); } time = setTimeout(() => { tipContainer.innerHTML = ""; clearTimeout(time); time = 0; }, 4000); } function _bindDataEntrySubmitEvents (spread) { let reportSheet = spread.getActiveSheetTab(); if (reportSheet) { reportSheet.bind(GC.Spread.Sheets.Events.ReportSheetDataChanging, (event, args) => { let { row, col, newValue, type } = args; if (type === "update") { if (col <= 4 && typeof newValue !== "string") { args.cancel = true; _showWindowTip("New value should be string at col " + col); } else if (col === 5 && typeof newValue !== "number") { args.cancel = true; _showWindowTip("New value should be number at col " + col); } else { type = type[0].toUpperCase() + type.substring(1); _showWindowTip(`${(type)} at cell Row: ${row} col: ${col}`, true); } } else { type = type[0].toUpperCase() + type.substring(1); _showWindowTip(`${type} at cell Row: ${row} col: ${col}`, true); } }); reportSheet.bind(GC.Spread.Sheets.Events.ReportSheetRecordsSubmitted, (event, args) => { let { updateSuccessRecords, updateFailedRecords, deleteSuccessRecords, deleteFailedRecords } = args; let updateSuccessCells = [], deleteSuccessCells = [], updateFailedCells = [], deleteFailedCells = []; updateSuccessRecords.forEach((r) => { for (let key in r.info) { if (r.info.hasOwnProperty(key) && r.info[key].state === "updated") { updateSuccessCells.push({ row: r.info[key].row, col: r.info[key].col, }); } } }); deleteSuccessRecords.forEach((r) => { for (let key in r.info) { if (r.info.hasOwnProperty(key)) { deleteSuccessCells.push({ row: r.info[key].row, col: r.info[key].col, }); } } }); updateFailedRecords.forEach((r) => { for (let key in r.info) { if (r.info.hasOwnProperty(key) && r.info[key].state === "updated") { updateFailedCells.push({ row: r.info[key].row, col: r.info[key].col, reason: r.reason === undefined ? "null" : r.reason }); } } }); deleteFailedRecords.forEach((r) => { for (let key in r.info) { if (r.info.hasOwnProperty(key)) { deleteFailedCells.push({ row: r.info[key].row, col: r.info[key].col, reason: r.reason === undefined ? "null" : r.reason }); } } }); updateSuccessCells.forEach((cell, i) => { setTimeout(() => { _showWindowTip(`Update successfully at cell Row: ${cell.row} col: ${cell.col}`, true); }, i * 100); }); setTimeout(() => { deleteSuccessCells.forEach((cell, i) => { setTimeout(() => { _showWindowTip(`Delete successfully at cell Row: ${cell.row} col: ${cell.col}`, true); }, i * 100); }); }, updateSuccessCells.length * 100); setTimeout(() => { updateFailedCells.forEach((cell, i) => { setTimeout(() => { _showWindowTip(`Update failed at cell Row: ${cell.row} col: ${cell.col}, reason is ${cell.reason}`); }, i * 100); }); }, (updateSuccessCells.length + deleteSuccessCells.length) * 100); setTimeout(() => { deleteFailedCells.forEach((cell, i) => { setTimeout(() => { _showWindowTip(`Delete failed at cell Row: ${cell.row} col: ${cell.col}, reason is ${cell.reason}`); }, i * 100); }); }, (updateSuccessCells.length + deleteSuccessCells.length + updateFailedCells.length) * 100); }); } }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer/styles/gc.spread.sheets.designer.min.css"> <link rel="stylesheet" type="text/css" href="styles.css"> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-print/dist/gc.spread.sheets.print.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-shapes/dist/gc.spread.sheets.shapes.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-charts/dist/gc.spread.sheets.charts.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-reportsheet-addon/dist/gc.spread.report.reportsheet.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script> const designerDependencyScripts = [ '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-barcode/dist/gc.spread.sheets.barcode.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-pdf/dist/gc.spread.sheets.pdf.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-pivot-addon/dist/gc.spread.pivot.pivottables.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-slicers/dist/gc.spread.sheets.slicers.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-tablesheet/dist/gc.spread.sheets.tablesheet.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-ganttsheet/dist/gc.spread.sheets.ganttsheet.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-formula-panel/dist/gc.spread.sheets.formulapanel.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-io/dist/gc.spread.sheets.io.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer-resources-en/dist/gc.spread.sheets.designer.resource.en.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer/dist/gc.spread.sheets.designer.all.min.js', '$DEMOROOT$/spread/source/js/designer/license.js', ] function appendScriptNode (src) { const script = document.createElement('script'); script.src = src; script.async = false; script.type = 'text/javascript'; document.head.appendChild(script); } if (top === window) { // not in iframe designerDependencyScripts.forEach(appendScriptNode); } </script> <script src="app.js" type="text/javascript"></script> </head> <body> <div class="sample-tutorial"> <div id="demo-host"></div> </div> </body> </html>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } #demo-host { width: 100%; height: 100%; overflow: hidden; float: left; } .tip-container { position: absolute; display: flex; align-items: center; justify-content: start; z-index: 100; min-width: 300px; min-height: 60%; max-height: 100%; right: 0; top: 0; overflow: hidden; flex-direction: column; } .tip-inner-container { position: relative; display: flex; height: 52px; background-color: white; flex-direction: column; min-width: 200px; margin-bottom: 10px; padding: 0px 25px 0 10px; border-radius: 10px; box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 5px 0px, rgba(0, 0, 0, 0.1) 0px 2px 3px 0px; align-items: start; animation-name: tip-animation; animation-duration: 4s; animation-fill-mode: forwards; color: rgb(51, 51, 51); font-family: sans-serif; } .success { border-left: solid 8px rgb(154, 233, 140); } .failed { border-left: solid 8px rgb(227, 144, 143); } .tip-inner-container h1 { margin: 0; padding-top: 5px; font-size: 17.6px; font-weight: 500; } .tip-inner-container p { margin: 0; padding-top: 5px; font-size: 12.8px; } @keyframes tip-animation { 0% { left: 300px; opacity: 0; } 20% { left: 10px; opacity: 1; } 75% { left: 10px; opacity: 1; } 100% { left: 300px; opacity: 0; } }