概述

集算表支持使用AutoSync和Batch模式对数据源进行自动和批量更新。

默认情况下,集算表将只是与本地数据管理器进行交互。如果你想将改变的数据与你的远程数据库同步,只要先启用 ""(自动同步)或 "Batch"(批处理)模式。 本演示使用 "批处理"模式*。 自动同步模式 这种模式主要适用于低频的数据操作场景。一旦你使用行操作中的按钮或API与行进行交互,相应变化的请求将被启动并立即发送到服务器。 要在表的初始化中启用 "自动同步 "模式。 批量模式 这种模式主要适用于数据经常被操作的场景。它将按顺序存储每一行的操作,然后将所有的修改打包成一个集合,一次性发送到服务器,以节省网络资源。 要启用 "批处理 "模式,并在表的初始化中指定一个 "BatchApiUrl"。 然后你可以提交或丢弃所有的修改。 获取变化 你也可以在将修改保存到服务器之前获得修改。 请求和回应 操作 请求类型 请求数据 返回数据 update POST 更新的数据 更新的数据 read GET 无数据 数据项数组 delete DELETE 删除的数据项或数组 没有限制 create POST 插入的数据项 插入的数据项 batch POST 一个对象数组,其中每个对象都包含一个 "类型 "属性。 这个操作类型可以是 "更新"、"插入 "或 "删除"。 "dataItem"属性是当前记录。 'sourceIndex'属性是记录的索引。' 可选的'oldDataItem'属性是原始记录。 例如: [ {"type":"delete","dataItem":{...}, "sourceIndex":5}, {"type":"insert","dataItem":{...}, "sourceIndex":3}, {"type":"update","dataItem":{...}, "oldDataItem":{...}, "oldDataItem":{...}, "sourceIndex":1}] 一个对象数组,每个对象包含一个'success'属性,表示操作的成功或失败,还有一个可选的'data'属性,是当前的记录,只用于'插入'操作。 比如说 [{"succeed":true}, {"succeed":false}, {"succeed": true}]
/*REPLACE_MARKER*/ /*DO NOT DELETE THESE COMMENTS*/ import { Component, NgModule, enableProdMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { SpreadSheetsModule } from '@grapecity-software/spread-sheets-angular'; import GC from '@grapecity-software/spread-sheets'; import "@grapecity-software/spread-sheets-tablesheet"; import '@grapecity-software/spread-sheets-resources-zh'; GC.Spread.Common.CultureManager.culture("zh-cn"); import './styles.css'; var tableName = "Employee"; var baseApiUrl = getBaseApiUrl(); var apiUrl = baseApiUrl + "/" + tableName; var batchApiUrl = baseApiUrl + "/" + tableName + 'Collection'; var tablesheetName = 'MyTableSheet'; @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { methods4html = { objectKeys: Object.keys, number: Number }; spread: GC.Spread.Sheets.Workbook; tablesheet: GC.Spread.Sheets.TableSheet; fields = {}; selections: any[] = [{ row: 0, rowCount: 1, col: 0, colCount: 1 }]; isEdit = false; hostStyle = { width: "calc(100% - 280px)", height: "100%", overflow: "hidden", float: "left" }; constructor() { } initSpread($event: any) { let spread = $event.spread; this.spread = spread; spread.suspendPaint(); spread.clearSheets(); spread.options.autoFitType = GC.Spread.Sheets.AutoFitType.cellWithHeader; //init a data manager var dataManager = spread.dataManager(); var myTable = dataManager.addTable("myTable", { remote: { read: { url: apiUrl }, update: { url: apiUrl, method: 'PUT' }, create: { url: apiUrl }, delete: { url: apiUrl }, batch: { url: batchApiUrl } }, batch: true, schema: { columns: { "Id": { dataType: "number" }, "LastName": { dataType: "string" }, "FirstName": { dataType: "string" }, "HomePhone": { dataType: "string" }, "Notes": { dataType: "string" } } } }); //init a table sheet var sheet = spread.addSheetTab(0, tablesheetName, GC.Spread.Sheets.SheetType.tableSheet); this.tablesheet = sheet; var rowActions = GC.Spread.Sheets.TableSheet.BuiltInRowActions; var options = sheet.rowActionOptions(); options.push( rowActions.removeRow, rowActions.saveRow, rowActions.resetRow, ); sheet.rowActionOptions(options); //bind a view to the table sheet myTable.fetch().then(function() { var view = myTable.addView("myView", [ { value: "Id", width: 50, caption: "ID" }, { value: "FirstName", width: 100, caption: "First Name" }, { value: "LastName", width: 100, caption: "Last Name" }, { value: "HomePhone", width: 120, caption: "Home Phone" }, { value: "Title", width: 150, caption: "Title" } ]); //the View has all default columns of the Table sheet.setDataView(view); }); spread.bind(GC.Spread.Sheets.Events.SelectionChanged, (e: any, args: any) => { this.selections = args.newSelections; }); spread.resumePaint(); } removeRow() { this.traverseSelectionsRowsWithOperation((row: number) => { this.tablesheet.removeRow(row); }); } saveRow() { this.traverseSelectionsRowsWithOperation((row: number) => { this.tablesheet.saveRow(row); }); } resetRow() { this.traverseSelectionsRowsWithOperation((row: number) => { this.tablesheet.resetRow(row); }); } saveAllRows() { this.spread.commandManager().SaveAll.execute(this.spread, { sheetName: tablesheetName }); } submitChanges() { this.tablesheet.submitChanges(); } discardChanges() { this.tablesheet.cancelChanges(); } getChanges() { var changesPanel = document.getElementById("changesPanel"); var changes = formatChanges(this.tablesheet.getChanges()); changesPanel.innerHTML = changes; } traverseSelectionsRowsWithOperation(operation: any) { var selections = this.selections; if (selections) { for (var i = 0; i < selections.length; i++) { var selection = selections[i]; var row = selection.row; var rowCount = selection.rowCount; for (var r = row + rowCount - 1; r >= row; r--) { operation(r); } } } } } function getBaseApiUrl() { return window.location.href.match(/http.+spreadjs\/SpreadJSTutorial\//)[0] + 'server/api'; } function formatChanges(changes: any[]) { var json = JSON.stringify(changes, function(k, v) { if (k === "dataItem" || k === "oldDataItem") { return { Id: v.Id, FirstName: v.FirstName, LastName: v.LastName, HomePhone: v.HomePhone, Title: v.Title }; } return v; }, 2); return json; } @NgModule({ imports: [BrowserModule, SpreadSheetsModule, FormsModule], declarations: [AppComponent], exports: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { } enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!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$/zh/angular/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <!-- Polyfills --> <script src="$DEMOROOT$/zh/angular/node_modules/core-js/client/shim.min.js"></script> <script src="$DEMOROOT$/zh/angular/node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="$DEMOROOT$/zh/angular/node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.import('@angular/compiler'); System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('$DEMOROOT$/zh/lib/angular/license.ts'); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="sample-tutorial"> <gc-spread-sheets [hostStyle]="hostStyle" (workbookInitialized)="initSpread($event)"></gc-spread-sheets> <div id="options-container" class="options-container"> <fieldset> <legend>Active Row Operations</legend> <div class="field-line"> <input id="remove" type="button" value="Remove" (click)="removeRow()"> </div> <div class="field-line"> <input id="save" type="button" value="Save" (click)="saveRow()"> </div> <div class="field-line"> <input id="reset" type="button" value="Reset" (click)="resetRow()"> </div> </fieldset> <fieldset> <legend>Save All Rows</legend> <div class="field-line"> <input id="save-all" type="button" value="Save All" (click)="saveAllRows()"> </div> </fieldset> <fieldset> <legend>Batch Operations</legend> <div class="field-line"> <input type="button" value="Submit" id="submit" (click)="submitChanges()"> </div> <div class="field-line"> <input type="button" value="Discard" id="discard" (click)="discardChanges()"> </div> </fieldset> <fieldset> <legend>Get Changes</legend> <div class="field-line"> <input type="button" value="Get Changes" id="getChanges" (click)="getChanges()"> </div> <div class="field-line"> <textarea id="changesPanel"></textarea> </div> </fieldset> </div> </div>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } fieldset { padding: 6px; margin: 0; margin-top: 10px; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: calc(100% - 280px); height: 100%; overflow: hidden; float: left; } .options-container { float: right; width: 280px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; } fieldset span, fieldset input, fieldset select { display: inline-block; text-align: left; } fieldset input[type=text] { width: calc(100% - 58px); } fieldset input[type=button] { width: 100%; text-align: center; } fieldset select { width: calc(100% - 50px); } .field-line { margin-top: 4px; } .field-inline { display: inline-block; vertical-align: middle; } fieldset label.field-inline { width: 100px; } fieldset input.field-inline { width: calc(100% - 100px - 12px); } .required { color: red; font-weight: bold; } #fields { display: none; } #fields.show { display: block; } #changesPanel { width: 100%; height: 300px; }
(function(global) { System.config({ transpiler: 'ts', typescriptOptions: { tsconfig: true }, meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'core-js': 'npm:core-js/client/shim.min.js', 'zone': 'npm:zone.js/fesm2015/zone.min.js', 'rxjs': 'npm:rxjs/dist/bundles/rxjs.umd.min.js', '@angular/core': 'npm:@angular/core/fesm2022', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/compiler': 'npm:@angular/compiler/fesm2022/compiler.mjs', '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2022/platform-browser.mjs', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2022/platform-browser-dynamic.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', 'jszip': 'npm:jszip/dist/jszip.min.js', 'typescript': 'npm:typescript/lib/typescript.js', 'ts': './plugin.js', 'tslib':'npm:tslib/tslib.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', '@grapecity-software/spread-sheets': 'npm:@grapecity-software/spread-sheets/index.js', '@grapecity-software/spread-sheets-resources-zh': 'npm:@grapecity-software/spread-sheets-resources-zh/index.js', '@grapecity-software/spread-sheets-tablesheet': 'npm:@grapecity-software/spread-sheets-tablesheet/index.js', '@grapecity-software/spread-sheets-angular': 'npm:@grapecity-software/spread-sheets-angular/fesm2020/grapecity-software-spread-sheets-angular.mjs', '@grapecity-software/jsob-test-dependency-package/react-components': 'npm:@grapecity-software/jsob-test-dependency-package/react-components/index.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'ts' }, rxjs: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, "node_modules/@angular": { defaultExtension: 'mjs' }, "@grapecity-software/spread-sheets-angular": { defaultExtension: 'mjs' }, '@angular/core': { defaultExtension: 'mjs', main: 'core.mjs' } } }); })(this);