이벤트

ReportSheet에는 보고서 데이터를 변경하거나 제출하는 동안 바인딩할 수 있는 데이터 입력 이벤트가 있습니다.

아래 이벤트를 사용하여 사용자는 모든 데이터 변경의 유효성을 검사하거나 데이터 변경 중 작업을 취소할 수 있으며, 서버의 피드백 결과를 받은 다음 UI에 결과를 표시할 수도 있습니다.

설명
app.jsx
app-func.jsx
index.html
styles.css
새 창에서 데모를 열어 디자이너 열기

디자이너를 여는 방법

ReportSheet는 아래 데이터 입력 이벤트를 지원합니다. 다음 코드를 사용하여 데이터 입력 이벤트를 바인딩할 수 있습니다.

  1. ReportSheetDataChangingReportSheetDataChanged

ReportSheetDataChanging은 업데이트, 삽입 또는 삭제로 인해 보고서 시트가 변경되는 경우에 발생합니다. 데이터 작업의 유효성을 검사하거나 취소하도록 허용합니다.

ReportSheetDataChanged는 업데이트, 삽입 또는 삭제로 인해 보고서 시트 데이터가 변경된 경우에 발생합니다. 데이터 변경 사항을 서버에 제출하도록 허용합니다.

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. ReportSheetRecordsSubmittingReportSheetRecordsSubmitted

ReportSheetRecordsSubmitting는 보고서 시트가 서버에 변경 사항을 제출하기 전에 발생합니다. 모든 데이터 변경 사항의 최종 유효성을 검사하거나 작업을 취소하도록 허용합니다.

ReportSheetRecordsSubmitted는 보고서 시트가 서버에 변경 사항을 제출한 후에 발생합니다. 사용자가 서버의 제출 결과에 대한 UI 피드백을 제공하도록 허용합니다.

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();
새 창에서 데모를 열어 디자이너 열기 ReportSheet는 아래 데이터 입력 이벤트를 지원합니다. 다음 코드를 사용하여 데이터 입력 이벤트를 바인딩할 수 있습니다. ReportSheetDataChanging 및 ReportSheetDataChanged ReportSheetDataChanging은 업데이트, 삽입 또는 삭제로 인해 보고서 시트가 변경되는 경우에 발생합니다. 데이터 작업의 유효성을 검사하거나 취소하도록 허용합니다. ReportSheetDataChanged는 업데이트, 삽입 또는 삭제로 인해 보고서 시트 데이터가 변경된 경우에 발생합니다. 데이터 변경 사항을 서버에 제출하도록 허용합니다. ReportSheetRecordsSubmitting 및 ReportSheetRecordsSubmitted ReportSheetRecordsSubmitting는 보고서 시트가 서버에 변경 사항을 제출하기 전에 발생합니다. 모든 데이터 변경 사항의 최종 유효성을 검사하거나 작업을 취소하도록 허용합니다. ReportSheetRecordsSubmitted는 보고서 시트가 서버에 변경 사항을 제출한 후에 발생합니다. 사용자가 서버의 제출 결과에 대한 UI 피드백을 제공하도록 허용합니다.
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import './styles.css'; import { AppFunc } from './app-func'; ReactDOM.render(<AppFunc />, document.getElementById('app'));
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { SpreadSheets } from '@mescius/spread-sheets-react'; import GC from '@mescius/spread-sheets'; import '@mescius/spread-sheets-print'; import '@mescius/spread-sheets-shapes'; import '@mescius/spread-sheets-charts'; import '@mescius/spread-sheets-reportsheet-addon'; import '@mescius/spread-sheets-tablesheet'; import '@mescius/spread-sheets-ganttsheet'; import '@mescius/spread-sheets-formula-panel'; import '@mescius/spread-sheets-designer-resources-ko'; import '@mescius/spread-sheets-resources-ko'; GC.Spread.Common.CultureManager.culture("ko-kr"); import { Designer } from '@mescius/spread-sheets-designer-react'; import './styles.css'; import '$DEMOROOT$/spread/source/js/react_vue/license.js'; import { registerlic } from '$DEMOROOT$/spread/source/js/designer/react_vue/license.js'; registerlic(GC); export function AppFunc() { return ( <div class='demo'> {top === window ? <Designer styleInfo={{ height: '100%' }} designerInitialized={initDesigner} /> : <SpreadSheets workbookInitialized={initSpread} />} </div> ); } async function initDesigner(designer) { await initSpread(designer.getWorkbook()); designer.refresh(); } async function initSpread(spread) { spread.suspendPaint(); spread.setSheetCount(1); const loadingTip = addLoadingTip(); const res = await fetch('$DEMOROOT$/ko/sample/features/report-sheet/data-entry/events/spread.json'); await spread.fromJSON(correctTableUrl(await res.json())); loadingTip.remove(); _bindDataEntrySubmitEvents(spread); spread.resumePaint(); } 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$/ko/react/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/ko/react/node_modules/@mescius/spread-sheets-designer/styles/gc.spread.sheets.designer.min.css"> <script src="$DEMOROOT$/ko/react/node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app" style="height: 100%;"></div> </body> </html>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .demo { width: 100%; height: 100%; overflow: hidden; } .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; } }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@mescius/spread-sheets-designer-resources-ko': 'npm:@mescius/spread-sheets-designer-resources-ko/index.js', '@mescius/spread-sheets-designer-react': 'npm:@mescius/spread-sheets-designer-react/index.js', '@mescius/spread-sheets-designer': 'npm:@mescius/spread-sheets-designer/index.js', '@mescius/spread-excelio': 'npm:@mescius/spread-excelio/index.js', '@mescius/spread-sheets-barcode': 'npm:@mescius/spread-sheets-barcode/index.js', '@mescius/spread-sheets-charts': 'npm:@mescius/spread-sheets-charts/index.js', '@mescius/spread-sheets-languagepackages': 'npm:@mescius/spread-sheets-languagepackages/index.js', '@mescius/spread-sheets-print': 'npm:@mescius/spread-sheets-print/index.js', '@mescius/spread-sheets-pdf': 'npm:@mescius/spread-sheets-pdf/index.js', '@mescius/spread-sheets-shapes': 'npm:@mescius/spread-sheets-shapes/index.js', '@mescius/spread-sheets-io': 'npm:@mescius/spread-sheets-io/index.js', '@mescius/spread-sheets-reportsheet-addon': 'npm:@mescius/spread-sheets-reportsheet-addon/index.js', '@mescius/spread-sheets-tablesheet': 'npm:@mescius/spread-sheets-tablesheet/index.js', '@mescius/spread-sheets-ganttsheet': 'npm:@mescius/spread-sheets-ganttsheet/index.js', '@mescius/spread-sheets-formula-panel': 'npm:@mescius/spread-sheets-formula-panel/index.js', '@mescius/spread-sheets-react': 'npm:@mescius/spread-sheets-react/index.js', '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-resources-ko': 'npm:@mescius/spread-sheets-resources-ko/index.js', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js', 'react': 'npm:react/umd/react.production.min.js', 'react-dom': 'npm:react-dom/umd/react-dom.production.min.js', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);