gridjs-spreadsheet 26.6.2 → 26.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/angular.d.ts +67 -0
  2. package/angular.js +174 -0
  3. package/example/README.md +242 -41
  4. package/example/angular-gridjs/.nvmrc +1 -0
  5. package/example/angular-gridjs/angular.json +66 -0
  6. package/example/angular-gridjs/package.json +31 -0
  7. package/example/angular-gridjs/proxy.conf.json +12 -0
  8. package/example/angular-gridjs/src/app/api.ts +7 -0
  9. package/example/angular-gridjs/src/app/app.component.html +57 -0
  10. package/example/angular-gridjs/src/app/app.component.ts +153 -0
  11. package/example/angular-gridjs/src/app/routing.ts +52 -0
  12. package/example/angular-gridjs/src/favicon.ico +0 -0
  13. package/example/angular-gridjs/src/index.html +12 -0
  14. package/example/angular-gridjs/src/main.ts +7 -0
  15. package/example/angular-gridjs/src/styles.css +37 -0
  16. package/example/angular-gridjs/tsconfig.app.json +9 -0
  17. package/example/angular-gridjs/tsconfig.json +29 -0
  18. package/example/pom.xml +3 -3
  19. package/example/react-gridjs/index.html +12 -0
  20. package/example/react-gridjs/package.json +20 -0
  21. package/example/react-gridjs/src/App.jsx +188 -0
  22. package/example/react-gridjs/src/api.js +7 -0
  23. package/example/react-gridjs/src/main.jsx +9 -0
  24. package/example/react-gridjs/src/routing.js +51 -0
  25. package/example/react-gridjs/src/styles.css +37 -0
  26. package/example/react-gridjs/vite.config.js +16 -0
  27. package/example/src/main/java/com/aspose/gridjs/demo/controller/DataController.java +80 -2
  28. package/example/src/main/java/com/aspose/gridjs/demo/controller/GridJs2Controller.java +12 -0
  29. package/example/vanilla-gridjs/npm/index.html +29 -0
  30. package/example/vanilla-gridjs/npm/main.js +8 -0
  31. package/example/vanilla-gridjs/npm/package.json +16 -0
  32. package/example/vanilla-gridjs/npm/vite.config.js +11 -0
  33. package/example/vanilla-gridjs/script/index.html +32 -0
  34. package/example/vanilla-gridjs/script/main.js +9 -0
  35. package/example/vanilla-gridjs/script/package.json +12 -0
  36. package/example/vanilla-gridjs/script/vite.config.js +11 -0
  37. package/example/vanilla-gridjs/shared/demo-app.js +205 -0
  38. package/example/vanilla-gridjs/shared/styles.css +36 -0
  39. package/example/vue-gridjs/index.html +12 -0
  40. package/example/vue-gridjs/package.json +19 -0
  41. package/example/vue-gridjs/src/App.vue +199 -0
  42. package/example/vue-gridjs/src/api.js +7 -0
  43. package/example/vue-gridjs/src/main.js +9 -0
  44. package/example/vue-gridjs/src/routing.js +51 -0
  45. package/example/vue-gridjs/src/styles.css +37 -0
  46. package/example/vue-gridjs/vite.config.js +21 -0
  47. package/index.d.ts +58 -1
  48. package/index.js +1 -1
  49. package/package.json +143 -50
  50. package/react.d.ts +27 -0
  51. package/react.js +111 -0
  52. package/readme.md +183 -384
  53. package/shared.d.ts +66 -0
  54. package/shared.js +204 -0
  55. package/vue.d.ts +20 -0
  56. package/vue.js +125 -0
  57. package/xspreadsheet.css +7 -0
  58. package/xspreadsheet.js +3 -3
  59. package/example/.mvn/wrapper/maven-wrapper.properties +0 -18
  60. package/example/src/test/java/com/aspose/gridjs/demo/GridjsdemoApplicationTests.java +0 -1441
@@ -0,0 +1,57 @@
1
+ <!-- A workbook is selected -> render the full-page spreadsheet editor. -->
2
+ <main *ngIf="current; else startPage" class="editor-page">
3
+ <gridjs-spreadsheet
4
+ *ngIf="workbookData; else loadingEditor"
5
+ [data]="workbookData"
6
+ height="100vh"
7
+ [showToolbar]="true"
8
+ [showContextmenu]="true"
9
+ (ready)="onReady($event)"
10
+ (change)="logEvent('changed')"
11
+ (error)="onError($event)"
12
+ (cellSelected)="logEvent('cell selected ' + (($event && $event[1]) ?? '') + ',' + (($event && $event[2]) ?? ''))"
13
+ (cellEdited)="logEvent('cell edited ' + (($event && $event[1]) ?? '') + ',' + (($event && $event[2]) ?? ''))"
14
+ (sheetSelected)="logEvent('sheet ' + (($event && $event[1]) ?? ''))"
15
+ ></gridjs-spreadsheet>
16
+ <ng-template #loadingEditor>
17
+ <div class="editor-loading">{{ status }}</div>
18
+ </ng-template>
19
+ </main>
20
+
21
+ <!-- No workbook selected -> render the start page (demo picker + file list + upload). -->
22
+ <ng-template #startPage>
23
+ <div class="demo-shell">
24
+ <header class="demo-navbar">
25
+ <a href="https://docs.aspose.com/cells/java/aspose-cells-gridjs/">Aspose.Cells.GridJs demo for Java</a>
26
+ </header>
27
+ <main class="demo-container">
28
+ <fieldset class="demo-selector">
29
+ <legend>Select a demo:</legend>
30
+ <label [class]="demoType === DEMO_PERMANENT ? 'radio-card active' : 'radio-card'">
31
+ <input type="radio" name="demoType" [checked]="demoType === DEMO_PERMANENT" (change)="demoType = DEMO_PERMANENT" />
32
+ <span>permanent url load demo</span>
33
+ </label>
34
+ <label [class]="demoType === DEMO_HIGHLIGHT ? 'radio-card active' : 'radio-card'">
35
+ <input type="radio" name="demoType" [checked]="demoType === DEMO_HIGHLIGHT" (change)="demoType = DEMO_HIGHLIGHT" />
36
+ <span>highlight and custom context menu demo</span>
37
+ </label>
38
+ </fieldset>
39
+ <section class="upload-block">
40
+ <h4>Upload a local file to start show workbook</h4>
41
+ <input type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" (change)="upload($event)" />
42
+ </section>
43
+ <section class="path-block">
44
+ <h4>The specific file path : {{ directory }}</h4>
45
+ <h4>Navigate one of the below file to start show workbook</h4>
46
+ </section>
47
+ <p class="status-message" role="status">{{ status }}</p>
48
+ <section class="file-list" aria-label="Workbook files">
49
+ <p *ngIf="status === 'Ready' && files.length === 0" class="empty-message">No workbooks found in the configured directory.</p>
50
+ <a *ngFor="let file of files" [href]="'/?file=' + file" class="file-item" (click)="$event.preventDefault(); openFile(file)">
51
+ <em>{{ file }}</em>
52
+ </a>
53
+ </section>
54
+ </main>
55
+ <footer class="demo-footer">© 2026 - <a href="https://products.aspose.com/cells/java">Aspose.Cells for Java</a></footer>
56
+ </div>
57
+ </ng-template>
@@ -0,0 +1,153 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component, HostListener, OnInit } from '@angular/core';
3
+ import { GridJsSpreadsheetComponent } from 'gridjs-spreadsheet/angular';
4
+ import { DEMO_PERMANENT, DEMO_HIGHLIGHT, DemoType, WorkbookRef, makeUid, readRoute, pushWorkbookRoute } from './routing';
5
+ import { fetchJson } from './api';
6
+
7
+ // Root demo component.
8
+ // Renders either:
9
+ // - the start page (demo picker + sample file list + upload box), or
10
+ // - the full-page spreadsheet editor once a workbook has been selected.
11
+ // Which one is shown is driven entirely by the URL (see ./routing.ts).
12
+ @Component({
13
+ selector: 'app-root',
14
+ standalone: true,
15
+ imports: [CommonModule, GridJsSpreadsheetComponent],
16
+ templateUrl: './app.component.html',
17
+ })
18
+ export class AppComponent implements OnInit {
19
+ readonly DEMO_PERMANENT = DEMO_PERMANENT;
20
+ readonly DEMO_HIGHLIGHT = DEMO_HIGHLIGHT;
21
+
22
+ // Sample workbooks listed on the start page.
23
+ files: string[] = [];
24
+ directory = '';
25
+ // The workbook selected via the URL; null means "show the start page".
26
+ current: WorkbookRef | null = readRoute();
27
+ // Which demo mode is active (only relevant while on the start page).
28
+ demoType: DemoType = this.current?.demo || DEMO_PERMANENT;
29
+ workbookData: any = null;
30
+ status = 'Loading workbook list...';
31
+ adapter: any = null;
32
+
33
+ async ngOnInit(): Promise<void> {
34
+ await this.loadFiles();
35
+ if (this.current) await this.loadWorkbook();
36
+ }
37
+
38
+ // Keeps state in sync when the user navigates with the browser's back/forward buttons.
39
+ @HostListener('window:popstate')
40
+ onPopState(): void {
41
+ this.current = readRoute();
42
+ if (this.current?.demo) {
43
+ this.demoType = this.current.demo;
44
+ }
45
+ void this.loadWorkbook();
46
+ }
47
+
48
+ logEvent(message: string): void {
49
+ console.log('[GridJS Event]', message);
50
+ }
51
+
52
+ // Loads the list of sample workbooks available on the server.
53
+ async loadFiles(): Promise<void> {
54
+ try {
55
+ const result = await fetchJson<{ directory: string; files: string[] }>('/gridjsdemo/api/files');
56
+ this.files = result.files || [];
57
+ this.directory = result.directory || '';
58
+ this.status = 'Ready';
59
+ } catch (error: any) {
60
+ this.status = 'Failed to load files: ' + error.message;
61
+ }
62
+ }
63
+
64
+ // Navigates to a workbook: updates the URL and local state together.
65
+ openWorkbook(workbook: WorkbookRef): void {
66
+ const normalized = { ...workbook, demo: workbook.demo || this.demoType };
67
+ pushWorkbookRoute(normalized);
68
+ this.current = normalized;
69
+ this.demoType = normalized.demo;
70
+ void this.loadWorkbook();
71
+ }
72
+
73
+ openFile(file: string): void {
74
+ this.openWorkbook({ file, storedFile: file, demo: this.demoType, uid: makeUid(file), fromUpload: '' });
75
+ }
76
+
77
+ // Fetches workbook data for the GridJsSpreadsheet component. Which endpoint
78
+ // is used depends on the demo mode:
79
+ // - permanent: uid-based endpoint, backend caches parsed data per uid.
80
+ // - highlight: plain endpoint, re-parses the file on every load.
81
+ async loadWorkbook(): Promise<void> {
82
+ if (!this.current?.file) {
83
+ this.workbookData = null;
84
+ return;
85
+ }
86
+ this.workbookData = null;
87
+ this.adapter = null;
88
+ this.status = 'Loading ' + this.current.file + '...';
89
+ try {
90
+ const demo = this.current.demo || DEMO_PERMANENT;
91
+ const params = new URLSearchParams({
92
+ filename: this.current.storedFile || this.current.file,
93
+ fromUpload: this.current.fromUpload || '',
94
+ });
95
+ let endpoint = '/GridJs2/DetailStreamJson';
96
+ if (this.current.fromUpload) {
97
+ params.set('uid', this.current.uid || makeUid(this.current.file));
98
+ endpoint = '/GridJs2/DetailStreamJsonWithUidFromUpload';
99
+ } else if (demo === DEMO_PERMANENT) {
100
+ params.set('uid', this.current.uid || makeUid(this.current.file));
101
+ endpoint = '/GridJs2/DetailStreamJsonWithUid';
102
+ }
103
+ const payload = await fetchJson<any>(endpoint + '?' + params);
104
+ this.workbookData = { ...payload, filename: this.current.file };
105
+ this.status = 'Loaded ' + this.current.file;
106
+ } catch (error: any) {
107
+ this.status = 'Workbook load failed: ' + error.message;
108
+ }
109
+ }
110
+
111
+ async upload(event: Event): Promise<void> {
112
+ const input = event.target as HTMLInputElement;
113
+ const file = input.files?.[0];
114
+ if (!file) return;
115
+ const form = new FormData();
116
+ form.append('file', file);
117
+ form.append('client', 'angular');
118
+ this.status = 'Uploading ' + file.name + '...';
119
+ try {
120
+ const result = await fetchJson<{ file: string; displayName: string; uid: string; fromUpload: string }>('/gridjsdemo/api/upload', { method: 'POST', body: form });
121
+ this.logEvent('uploaded ' + result.file);
122
+ // Newly uploaded files live under the server's uploads dir, flagged with fromUpload=1.
123
+ this.openWorkbook({
124
+ file: result.displayName || file.name,
125
+ storedFile: result.file,
126
+ demo: this.demoType,
127
+ uid: result.uid || makeUid(result.file),
128
+ fromUpload: '1',
129
+ });
130
+ } catch (error: any) {
131
+ this.status = 'Upload failed: ' + error.message;
132
+ } finally {
133
+ input.value = '';
134
+ }
135
+ }
136
+
137
+ // Restores the previously active sheet/cell (from the workbook data) once
138
+ // the grid has mounted, and points the "open file" link back at the start page.
139
+ onReady(event: { instance: any; adapter: any }): void {
140
+ this.adapter = event.adapter;
141
+ const sheetName = event.adapter.payload?.actname;
142
+ const row = event.adapter.payload?.actrow;
143
+ const col = event.adapter.payload?.actcol;
144
+ event.instance?.setActiveSheetByName?.(sheetName)?.setActiveCell?.(row, col);
145
+ event.instance?.setOpenFileUrl?.('/');
146
+ this.logEvent('ready ' + (event.adapter.payload?.filename || this.current?.file));
147
+ }
148
+
149
+ onError(payload: any): void {
150
+ console.error(payload);
151
+ this.logEvent('error ' + (payload?.message || payload?.type || 'unknown'));
152
+ }
153
+ }
@@ -0,0 +1,52 @@
1
+ // Route (URL query string) helpers shared by the demo.
2
+ //
3
+ // The demo encodes which workbook is open directly in the URL so a page
4
+ // refresh or a shared link reopens the same file, e.g.:
5
+ // /?file=Sample.xlsx&demo=permanent&uid=uid-Sample-xlsx
6
+ // /?file=Sample.xlsx&demo=highlight&fromUpload=1
7
+ //
8
+ // demo:
9
+ // 'permanent' - loads the workbook through the uid-based streaming endpoint;
10
+ // the backend caches the parsed result under this uid.
11
+ // 'highlight' - loads the workbook through the plain streaming endpoint,
12
+ // used by the "highlight and custom context menu" demo.
13
+ export type DemoType = 'permanent' | 'highlight';
14
+ export type WorkbookRef = { file: string; storedFile: string; uid: string; fromUpload: string; demo: DemoType };
15
+
16
+ export const DEMO_PERMANENT: DemoType = 'permanent';
17
+ export const DEMO_HIGHLIGHT: DemoType = 'highlight';
18
+ const CLIENT_PREFIX = 'angular';
19
+
20
+ // Turns a file name into a stable cache key,
21
+ // e.g. "Sales Report.xlsx" -> "uid-Sales-Report-xlsx".
22
+ export function makeUid(file: string): string {
23
+ return CLIENT_PREFIX + '-uid-' + file.replace(/[^a-zA-Z0-9]/g, '-');
24
+ }
25
+
26
+ // Reads the currently selected workbook (if any) from the URL query string.
27
+ // Returns null when no `file` param is present, i.e. the start page.
28
+ export function readRoute(): WorkbookRef | null {
29
+ const params = new URLSearchParams(window.location.search);
30
+ const file = params.get('file');
31
+ if (!file) return null;
32
+ const demo: DemoType = params.get('demo') === DEMO_HIGHLIGHT ? DEMO_HIGHLIGHT : DEMO_PERMANENT;
33
+ return {
34
+ file,
35
+ storedFile: params.get('storedFile') || file,
36
+ demo,
37
+ uid: params.get('uid') || makeUid(file),
38
+ fromUpload: params.get('fromUpload') || '',
39
+ };
40
+ }
41
+
42
+ // Pushes the given workbook selection into the URL (without a full page
43
+ // reload) so the browser's back/forward buttons and page refresh keep working.
44
+ export function pushWorkbookRoute(workbook: WorkbookRef): void {
45
+ const params = new URLSearchParams({ file: workbook.file, demo: workbook.demo || DEMO_PERMANENT });
46
+ if (workbook.storedFile !== workbook.file) params.set('storedFile', workbook.storedFile);
47
+ if ((workbook.demo || DEMO_PERMANENT) === DEMO_PERMANENT || workbook.fromUpload) {
48
+ params.set('uid', workbook.uid || makeUid(workbook.file));
49
+ }
50
+ if (workbook.fromUpload) params.set('fromUpload', workbook.fromUpload);
51
+ window.history.pushState(null, '', '/?' + params.toString());
52
+ }
File without changes
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>GridJS Angular Demo</title>
6
+ <base href="/">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ </head>
9
+ <body>
10
+ <app-root></app-root>
11
+ </body>
12
+ </html>
@@ -0,0 +1,7 @@
1
+ // Entry point: just bootstraps the standalone AppComponent (see ./app/app.component.ts).
2
+ // Shared route/API helpers live in ./app/routing.ts and ./app/api.ts so
3
+ // app.component.ts stays focused on component state and view logic.
4
+ import { bootstrapApplication } from '@angular/platform-browser';
5
+ import { AppComponent } from './app/app.component';
6
+
7
+ bootstrapApplication(AppComponent).catch((error) => console.error(error));
@@ -0,0 +1,37 @@
1
+ :root { font-family: Arial, Helvetica, sans-serif; color: #212529; background: #f8f9fa; }
2
+ * { box-sizing: border-box; }
3
+ html, body, #root, #app { min-height: 100%; }
4
+ body { margin: 0; overflow-y: hidden; background: #f8f9fa; font-size: 14px; }
5
+ button, input { font: inherit; }
6
+ .demo-shell { min-height: 100dvh; display: flex; flex-direction: column; background: #f8f9fa; }
7
+ .demo-navbar { min-height: 44px; display: flex; align-items: center; justify-content: center; background: #fff; border-bottom: 1px solid #dee2e6; box-shadow: 0 1px 3px rgba(0,0,0,.08); padding: 8px 12px; font-size: 19px; }
8
+ .demo-navbar a { color: #0000ee; text-decoration: underline; }
9
+ .demo-container { flex: 1; width: 100%; max-width: 960px; margin: 0 auto; padding: 16px 12px; }
10
+ .demo-selector { border: 1px solid #adb5bd; border-radius: 4px; margin: 0 0 16px; padding: 18px 14px 14px; display: flex; align-items: center; }
11
+ .demo-selector legend { width: auto; font-size: 14px; padding: 0 6px; }
12
+ .radio-card { min-height: 32px; display: inline-flex; align-items: center; gap: 6px; padding: 0 14px; margin-left: -1px; border: 1px solid #adb5bd; background: #f0f0f0; color: #444; font-size: 14px; cursor: pointer; position: relative; }
13
+ .radio-card:first-of-type { margin-left: 0; border-radius: 4px 0 0 4px; }
14
+ .radio-card:last-of-type { border-radius: 0 4px 4px 0; }
15
+ .radio-card input { width: 14px; height: 14px; margin: 0; accent-color: #0d6efd; }
16
+ .radio-card.active { background: #0d6efd; border-color: #0d6efd; color: #fff; z-index: 1; }
17
+ .upload-block { margin-top: 8px; }
18
+ .upload-block h4, .path-block h4 { margin: 0 0 10px; font-size: 14px; font-weight: bold; line-height: 1.3; }
19
+ .upload-block input[type=file] { margin-bottom: 12px; font-size: 13px; }
20
+ .path-block { margin-top: 4px; }
21
+ .file-list { min-height: 140px; max-height: 220px; overflow-y: auto; border: 1px solid #ced4da; border-radius: 4px; background: #f6f7ef; padding: 10px 12px; }
22
+ .file-item { display: block; margin-bottom: 10px; color: #0000ee; text-decoration: underline; font-size: 14px; font-style: italic; }
23
+ .file-item:hover { text-decoration: underline; }
24
+ .radio-card:focus-within, .file-item:focus-visible, input:focus-visible, a:focus-visible { outline: 3px solid rgba(13, 110, 253, .35); outline-offset: 2px; }
25
+ .status-message { min-height: 20px; margin: 8px 0; color: #495057; }
26
+ .empty-message { margin: 12px 4px; color: #64748b; }
27
+ .demo-footer { min-height: 40px; border-top: 1px solid #dee2e6; display: flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 12px; font-size: 13px; color: #495057; background: #f8f9fa; }
28
+ .demo-footer a { color: #0000ee; text-decoration: underline; }
29
+ .editor-page { height: 100dvh; width: 100vw; overflow: hidden; background: #fff; }
30
+ .editor-loading { height: 100vh; display: grid; place-items: center; color: #64748b; background: #fff; }
31
+ .gridjs-react-host, .gridjs-vue-host, gridjs-spreadsheet { display: block; width: 100%; height: 100vh; }
32
+ @media (max-width: 700px) {
33
+ body { overflow-y: auto; }
34
+ .demo-selector { flex-direction: column; align-items: stretch; }
35
+ .radio-card { margin-left: 0; width: 100%; }
36
+ .radio-card:first-of-type, .radio-card:last-of-type { border-radius: 4px; }
37
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./out-tsc/app",
5
+ "types": []
6
+ },
7
+ "files": ["src/main.ts"],
8
+ "include": ["src/**/*.d.ts"]
9
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "baseUrl": "./",
5
+ "outDir": "./dist/out-tsc",
6
+ "forceConsistentCasingInFileNames": true,
7
+ "strict": false,
8
+ "noImplicitOverride": false,
9
+ "noPropertyAccessFromIndexSignature": false,
10
+ "noImplicitReturns": false,
11
+ "noFallthroughCasesInSwitch": false,
12
+ "sourceMap": true,
13
+ "declaration": false,
14
+ "downlevelIteration": true,
15
+ "experimentalDecorators": true,
16
+ "moduleResolution": "bundler",
17
+ "importHelpers": true,
18
+ "target": "ES2022",
19
+ "module": "ES2022",
20
+ "useDefineForClassFields": false,
21
+ "lib": ["ES2022", "dom"]
22
+ },
23
+ "angularCompilerOptions": {
24
+ "enableI18nLegacyMessageIdFormat": false,
25
+ "strictInjectionParameters": true,
26
+ "strictInputAccessModifiers": false,
27
+ "strictTemplates": false
28
+ }
29
+ }
package/example/pom.xml CHANGED
@@ -17,7 +17,7 @@
17
17
  </repositories>
18
18
  <groupId>com.aspose.gridjs</groupId>
19
19
  <artifactId>gridjsdemo</artifactId>
20
- <version>26.6</version>
20
+ <version>26.7</version>
21
21
  <name>gridjsdemo</name>
22
22
  <description>GridJs Demo project for Spring Boot</description>
23
23
  <properties>
@@ -51,12 +51,12 @@
51
51
  <dependency>
52
52
  <groupId>com.aspose</groupId>
53
53
  <artifactId>aspose-cells</artifactId>
54
- <version>26.5</version>
54
+ <version>26.7</version>
55
55
  </dependency>
56
56
  <dependency>
57
57
  <groupId>com.aspose</groupId>
58
58
  <artifactId>aspose-cells</artifactId>
59
- <version>26.5</version>
59
+ <version>26.7</version>
60
60
  <classifier>gridjs</classifier>
61
61
  </dependency>
62
62
  <!-- <dependency>
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>GridJS React Demo</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "gridjs-react-java-demo",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite --host 127.0.0.1 --port 5173 --strictPort",
7
+ "build": "vite build",
8
+ "preview": "vite preview --host 127.0.0.1 --port 6173 --strictPort"
9
+ },
10
+ "dependencies": {
11
+ "gridjs-spreadsheet": "latest",
12
+ "jszip": "^3.10.1",
13
+ "react": "latest",
14
+ "react-dom": "latest"
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-react": "latest",
18
+ "vite": "latest"
19
+ }
20
+ }
@@ -0,0 +1,188 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { GridJsSpreadsheet } from 'gridjs-spreadsheet/react';
3
+ import { DEMO_PERMANENT, DEMO_HIGHLIGHT, makeUid, readRoute, pushWorkbookRoute } from './routing';
4
+ import { fetchJson } from './api';
5
+
6
+ // Root demo component.
7
+ // Renders either:
8
+ // - the start page (demo picker + sample file list + upload box), or
9
+ // - the full-page spreadsheet editor once a workbook has been selected.
10
+ // Which one is shown is driven entirely by the URL (see ./routing.js).
11
+ export function App() {
12
+ // Sample workbooks listed on the start page.
13
+ const [files, setFiles] = useState([]);
14
+ const [directory, setDirectory] = useState('');
15
+ // The workbook selected via the URL; null means "show the start page".
16
+ const [current, setCurrent] = useState(() => readRoute());
17
+ // Which demo mode is active (only relevant while on the start page).
18
+ const [demoType, setDemoType] = useState(() => readRoute()?.demo || DEMO_PERMANENT);
19
+ const [status, setStatus] = useState('Loading workbook list...');
20
+ const adapterRef = useRef(null);
21
+
22
+ const logEvent = useCallback((message) => {
23
+ console.log('[GridJS Event]', message);
24
+ }, []);
25
+
26
+ // Loads the list of sample workbooks available on the server.
27
+ const loadFiles = useCallback(async () => {
28
+ try {
29
+ const result = await fetchJson('/gridjsdemo/api/files');
30
+ setFiles(result.files || []);
31
+ setDirectory(result.directory || '');
32
+ setStatus('Ready');
33
+ } catch (error) {
34
+ setStatus('Failed to load files: ' + error.message);
35
+ }
36
+ }, []);
37
+
38
+ useEffect(() => {
39
+ loadFiles();
40
+ // Keep state in sync when the user navigates with the browser's back/forward buttons.
41
+ const onPopState = () => {
42
+ const next = readRoute();
43
+ setCurrent(next);
44
+ if (next?.demo) setDemoType(next.demo);
45
+ };
46
+ window.addEventListener('popstate', onPopState);
47
+ return () => window.removeEventListener('popstate', onPopState);
48
+ }, [loadFiles]);
49
+
50
+ // Navigates to a workbook: updates the URL and local state together.
51
+ const openWorkbook = (workbook) => {
52
+ const normalized = { ...workbook, demo: workbook.demo || demoType };
53
+ pushWorkbookRoute(normalized);
54
+ setCurrent(normalized);
55
+ setDemoType(normalized.demo);
56
+ };
57
+
58
+ const openFile = (file) => openWorkbook({ file, storedFile: file, demo: demoType, uid: makeUid(file), fromUpload: '' });
59
+
60
+ // Fetches workbook data for the GridJsSpreadsheet component. Which endpoint
61
+ // is used depends on the demo mode:
62
+ // - permanent: uid-based endpoint, backend caches parsed data per uid.
63
+ // - highlight: plain endpoint, re-parses the file on every load.
64
+ const loader = useCallback(async () => {
65
+ if (!current?.file) return null;
66
+ const demo = current.demo || DEMO_PERMANENT;
67
+ const params = new URLSearchParams({
68
+ filename: current.storedFile || current.file,
69
+ fromUpload: current.fromUpload || '',
70
+ });
71
+ let endpoint = '/GridJs2/DetailStreamJson';
72
+ if (current.fromUpload) {
73
+ params.set('uid', current.uid || makeUid(current.file));
74
+ endpoint = '/GridJs2/DetailStreamJsonWithUidFromUpload';
75
+ } else if (demo === DEMO_PERMANENT) {
76
+ params.set('uid', current.uid || makeUid(current.file));
77
+ endpoint = '/GridJs2/DetailStreamJsonWithUid';
78
+ }
79
+ const payload = await fetchJson(endpoint + '?' + params);
80
+ return { ...payload, filename: current.file };
81
+ }, [current]);
82
+
83
+ const upload = async (event) => {
84
+ const file = event.target.files?.[0];
85
+ if (!file) return;
86
+ const form = new FormData();
87
+ form.append('file', file);
88
+ form.append('client', 'react');
89
+ setStatus('Uploading ' + file.name + '...');
90
+ try {
91
+ const result = await fetchJson('/gridjsdemo/api/upload', { method: 'POST', body: form });
92
+ logEvent('uploaded ' + result.file);
93
+ // Newly uploaded files live under the server's uploads dir, flagged with fromUpload=1.
94
+ openWorkbook({
95
+ file: result.displayName || file.name,
96
+ storedFile: result.file,
97
+ demo: demoType,
98
+ uid: result.uid || makeUid(result.file),
99
+ fromUpload: '1',
100
+ });
101
+ } catch (error) {
102
+ setStatus('Upload failed: ' + error.message);
103
+ } finally {
104
+ event.target.value = '';
105
+ }
106
+ };
107
+
108
+ // Restores the previously active sheet/cell (from the workbook data) once
109
+ // the grid has mounted, and points the "open file" link back at the start page.
110
+ const handleReady = (instance, adapter) => {
111
+ adapterRef.current = adapter;
112
+ instance.setActiveSheetByName?.(adapter.payload?.actname)?.setActiveCell?.(adapter.payload?.actrow, adapter.payload?.actcol);
113
+ instance.setOpenFileUrl?.('/');
114
+ logEvent('ready ' + (adapter.payload?.filename || current.file));
115
+ };
116
+
117
+ const handleError = (payload) => {
118
+ console.error(payload);
119
+ logEvent('error ' + (payload?.message || payload?.type || 'unknown'));
120
+ };
121
+
122
+ // A workbook is selected -> render the full-page spreadsheet editor.
123
+ if (current) {
124
+ return (
125
+ <main className="editor-page">
126
+ <GridJsSpreadsheet
127
+ key={current.file + '-' + current.uid + '-' + current.demo}
128
+ loader={loader}
129
+ height="100vh"
130
+ showToolbar
131
+ showContextmenu
132
+ onReady={handleReady}
133
+ onChange={() => logEvent('changed')}
134
+ onError={handleError}
135
+ onCellSelected={(_, ri, ci) => logEvent('cell selected ' + ri + ',' + ci)}
136
+ onCellEdited={(text, ri, ci) => logEvent('cell edited ' + ri + ',' + ci)}
137
+ onSheetSelected={(_, name) => logEvent('sheet ' + name)}
138
+ />
139
+ </main>
140
+ );
141
+ }
142
+
143
+ // No workbook selected -> render the start page (demo picker + file list + upload).
144
+ return (
145
+ <div className="demo-shell">
146
+ <header className="demo-navbar">
147
+ <a href="https://docs.aspose.com/cells/java/aspose-cells-gridjs/">Aspose.Cells.GridJs demo for Java</a>
148
+ </header>
149
+
150
+ <main className="demo-container">
151
+ <fieldset className="demo-selector">
152
+ <legend>Select a demo:</legend>
153
+ <label className={demoType === DEMO_PERMANENT ? 'radio-card active' : 'radio-card'}>
154
+ <input type="radio" name="demoType" checked={demoType === DEMO_PERMANENT} onChange={() => setDemoType(DEMO_PERMANENT)} />
155
+ <span>permanent url load demo</span>
156
+ </label>
157
+ <label className={demoType === DEMO_HIGHLIGHT ? 'radio-card active' : 'radio-card'}>
158
+ <input type="radio" name="demoType" checked={demoType === DEMO_HIGHLIGHT} onChange={() => setDemoType(DEMO_HIGHLIGHT)} />
159
+ <span>highlight and custom context menu demo</span>
160
+ </label>
161
+ </fieldset>
162
+
163
+ <section className="upload-block">
164
+ <h4>Upload a local file to start show workbook</h4>
165
+ <input type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" onChange={upload} />
166
+ </section>
167
+
168
+ <section className="path-block">
169
+ <h4>The specific file path : {directory}</h4>
170
+ <h4>Navigate one of the below file to start show workbook</h4>
171
+ </section>
172
+
173
+ <p className="status-message" role="status">{status}</p>
174
+
175
+ <section className="file-list" aria-label="Workbook files">
176
+ {status === 'Ready' && files.length === 0 && <p className="empty-message">No workbooks found in the configured directory.</p>}
177
+ {files.map((file) => (
178
+ <a href={'/?file=' + encodeURIComponent(file)} key={file} className="file-item" onClick={(event) => { event.preventDefault(); openFile(file); }}>
179
+ <em>{file}</em>
180
+ </a>
181
+ ))}
182
+ </section>
183
+ </main>
184
+
185
+ <footer className="demo-footer">© 2026 - <a href="https://products.aspose.com/cells/java">Aspose.Cells for Java</a></footer>
186
+ </div>
187
+ );
188
+ }
@@ -0,0 +1,7 @@
1
+ // Small fetch() wrapper that turns non-2xx responses into thrown Errors,
2
+ // so callers can just `await fetchJson(url)` and catch failures in one place.
3
+ export async function fetchJson(url, options) {
4
+ const response = await fetch(url, options);
5
+ if (!response.ok) throw new Error(response.status + ' ' + response.statusText);
6
+ return response.json();
7
+ }
@@ -0,0 +1,9 @@
1
+ // Entry point: just mounts the <App /> root component (see ./App.jsx).
2
+ // Shared route/API helpers live in ./routing.js and ./api.js so this file
3
+ // and App.jsx stay focused on their own concerns.
4
+ import { createRoot } from 'react-dom/client';
5
+ import 'gridjs-spreadsheet/xspreadsheet.css';
6
+ import './styles.css';
7
+ import { App } from './App';
8
+
9
+ createRoot(document.getElementById('root')).render(<App />);