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,51 @@
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 const DEMO_PERMANENT = 'permanent';
14
+ export const DEMO_HIGHLIGHT = 'highlight';
15
+ const CLIENT_PREFIX = 'react';
16
+
17
+ // Turns a file name into a stable cache key,
18
+ // e.g. "Sales Report.xlsx" -> "uid-Sales-Report-xlsx".
19
+ export function makeUid(file) {
20
+ return CLIENT_PREFIX + '-uid-' + file.replace(/[^a-zA-Z0-9]/g, '-');
21
+ }
22
+
23
+ // Reads the currently selected workbook (if any) from the URL query string.
24
+ // Returns null when no `file` param is present, i.e. the start page.
25
+ export function readRoute() {
26
+ const params = new URLSearchParams(window.location.search);
27
+ const file = params.get('file');
28
+ if (!file) return null;
29
+ const demo = params.get('demo') === DEMO_HIGHLIGHT ? DEMO_HIGHLIGHT : DEMO_PERMANENT;
30
+ return {
31
+ file,
32
+ storedFile: params.get('storedFile') || file,
33
+ demo,
34
+ uid: params.get('uid') || makeUid(file),
35
+ fromUpload: params.get('fromUpload') || '',
36
+ };
37
+ }
38
+
39
+ // Pushes the given workbook selection into the URL (without a full page
40
+ // reload) so the browser's back/forward buttons and page refresh keep working.
41
+ export function pushWorkbookRoute(workbook) {
42
+ const params = new URLSearchParams({ file: workbook.file, demo: workbook.demo || DEMO_PERMANENT });
43
+ if (workbook.storedFile && workbook.storedFile !== workbook.file) {
44
+ params.set('storedFile', workbook.storedFile);
45
+ }
46
+ if ((workbook.demo || DEMO_PERMANENT) === DEMO_PERMANENT || workbook.fromUpload) {
47
+ params.set('uid', workbook.uid || makeUid(workbook.file));
48
+ }
49
+ if (workbook.fromUpload) params.set('fromUpload', workbook.fromUpload);
50
+ window.history.pushState(null, '', '/?' + params.toString());
51
+ }
@@ -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,16 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ proxy: {
8
+ '/GridJs2': 'http://127.0.0.1:8080',
9
+ '/gridjsdemo': 'http://127.0.0.1:8080'
10
+ }
11
+ },
12
+ build: {
13
+ outDir: 'dist',
14
+ emptyOutDir: true
15
+ }
16
+ });
@@ -7,7 +7,12 @@ import java.nio.file.Files;
7
7
  import java.nio.file.Path;
8
8
  import java.nio.file.Paths;
9
9
  import java.util.ArrayList;
10
+ import java.util.Arrays;
11
+ import java.util.Comparator;
12
+ import java.util.LinkedHashMap;
10
13
  import java.util.List;
14
+ import java.util.Map;
15
+ import java.util.UUID;
11
16
 
12
17
  import org.springframework.beans.factory.annotation.Value;
13
18
  import org.springframework.http.ResponseEntity;
@@ -28,8 +33,34 @@ public class DataController {
28
33
  @Value("${testconfig.FileName}")
29
34
  private String testFileName;//="chart.xlsx";
30
35
 
31
- @Value("${testconfig.ListDir}")
32
- private String listDir;
36
+ @Value("${testconfig.ListDir}")
37
+ private String listDir;
38
+
39
+ @GetMapping("/api/health")
40
+ public Map<String, String> health() {
41
+ Map<String, String> response = new LinkedHashMap<>();
42
+ response.put("status", "ok");
43
+ response.put("service", "gridjsdemo-java");
44
+ return response;
45
+ }
46
+
47
+ @GetMapping("/api/files")
48
+ public Map<String, Object> filesJson() {
49
+ List<String> files = new ArrayList<>();
50
+ File directory = new File(listDir);
51
+ File[] entries = directory.listFiles(File::isFile);
52
+ if (entries != null) {
53
+ Arrays.sort(entries, Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER));
54
+ for (File entry : entries) {
55
+ files.add(entry.getName());
56
+ }
57
+ }
58
+
59
+ Map<String, Object> response = new LinkedHashMap<>();
60
+ response.put("directory", directory.getAbsolutePath());
61
+ response.put("files", files);
62
+ return response;
63
+ }
33
64
 
34
65
  @GetMapping({"/index"})
35
66
  public ModelAndView getIndexPage()
@@ -88,6 +119,53 @@ public class DataController {
88
119
 
89
120
  @Value("${testconfig.UploadPath}")
90
121
  private String uploadDir;
122
+
123
+ @PostMapping("/api/upload")
124
+ public ResponseEntity<Map<String, Object>> uploadApi(
125
+ @RequestParam("file") MultipartFile file,
126
+ @RequestParam(value = "client", defaultValue = "vanilla") String client) {
127
+ Map<String, Object> response = new LinkedHashMap<>();
128
+ if (file.isEmpty()) {
129
+ response.put("error", "Choose a workbook before uploading.");
130
+ return ResponseEntity.badRequest().body(response);
131
+ }
132
+
133
+ try {
134
+ Path root = Paths.get(uploadDir).toAbsolutePath().normalize();
135
+ Files.createDirectories(root);
136
+
137
+ String originalName = safeFileName(file.getOriginalFilename());
138
+ String namespace = safeNamespace(client);
139
+ String storedName = namespace + "-" + UUID.randomUUID().toString() + "-" + originalName;
140
+ Path target = root.resolve(storedName).normalize();
141
+ if (!target.getParent().equals(root)) {
142
+ response.put("error", "Invalid upload path.");
143
+ return ResponseEntity.badRequest().body(response);
144
+ }
145
+
146
+ file.transferTo(target.toFile());
147
+ String uid = namespace + "-" + GridJsWorkbook.getUidForFile(storedName);
148
+ response.put("file", storedName);
149
+ response.put("displayName", originalName);
150
+ response.put("uid", uid);
151
+ response.put("fromUpload", "1");
152
+ return ResponseEntity.ok(response);
153
+ } catch (Exception exception) {
154
+ response.put("error", "Upload failed: " + exception.getMessage());
155
+ return ResponseEntity.status(500).body(response);
156
+ }
157
+ }
158
+
159
+ private static String safeFileName(String name) {
160
+ String candidate = name == null ? "workbook.xlsx" : Paths.get(name).getFileName().toString();
161
+ String sanitized = candidate.replaceAll("[^a-zA-Z0-9._() -]", "_");
162
+ return sanitized.isEmpty() ? "workbook.xlsx" : sanitized;
163
+ }
164
+
165
+ private static String safeNamespace(String client) {
166
+ String sanitized = client == null ? "vanilla" : client.replaceAll("[^a-zA-Z0-9-]", "-");
167
+ return sanitized.isEmpty() ? "vanilla" : sanitized;
168
+ }
91
169
 
92
170
  @PostMapping("/upload")
93
171
  public ModelAndView uploadFile(@RequestParam("file") MultipartFile file) {
@@ -95,6 +95,18 @@ public class GridJs2Controller extends GridJsControllerBase{
95
95
  e.printStackTrace();
96
96
  }
97
97
  }
98
+
99
+ @GetMapping("/DetailStreamJson")
100
+ public void detailStreamJson(@RequestParam String filename, HttpServletResponse response) {
101
+ Path filePath = Paths.get(listDir, filename);
102
+ response.setContentType("application/json");
103
+ response.setHeader("Content-Encoding", "gzip");
104
+ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())) {
105
+ _gridJsService.detailStreamJson(gzipOutputStream, filePath.toString());
106
+ } catch (Exception exception) {
107
+ exception.printStackTrace();
108
+ }
109
+ }
98
110
 
99
111
 
100
112
  @Value("${testconfig.UploadPath}")
@@ -0,0 +1,29 @@
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
+ <meta name="description" content="Use gridjs-spreadsheet from npm with Vanilla HTML and Java.">
7
+ <title>GridJS Vanilla npm demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="start-page" class="demo-shell">
11
+ <header class="demo-navbar"><a href="https://docs.aspose.com/cells/java/aspose-cells-gridjs/">Aspose.Cells.GridJs Vanilla npm demo</a></header>
12
+ <main class="demo-container">
13
+ <h1 class="visually-hidden">GridJS Vanilla npm demo</h1>
14
+ <fieldset class="demo-selector">
15
+ <legend>Select a demo:</legend>
16
+ <label class="radio-card active"><input type="radio" name="demoType" value="permanent" checked><span>permanent url load demo</span></label>
17
+ <label class="radio-card"><input type="radio" name="demoType" value="highlight"><span>highlight and custom context menu demo</span></label>
18
+ </fieldset>
19
+ <section class="upload-block"><h2>Upload a local workbook</h2><input id="upload-input" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"></section>
20
+ <section class="path-block"><h2>Workbook directory: <span id="directory"></span></h2><h2>Choose a workbook to open</h2></section>
21
+ <p id="status-message" class="status-message" role="status"></p>
22
+ <section id="file-list" class="file-list" aria-label="Workbook files"></section>
23
+ </main>
24
+ <footer class="demo-footer">© 2026 · <a href="https://products.aspose.com/cells/java">Aspose.Cells for Java</a></footer>
25
+ </div>
26
+ <main id="editor-page" class="editor-page" hidden><div id="editor-status" class="editor-status" role="status"></div><div id="grid-host"></div></main>
27
+ <script type="module" src="/main.js"></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,8 @@
1
+ import xSpreadsheet from 'gridjs-spreadsheet';
2
+ import JSZip from 'jszip';
3
+ import 'gridjs-spreadsheet/xspreadsheet.css';
4
+ import '../shared/styles.css';
5
+ import { startGridJsDemo } from '../shared/demo-app.js';
6
+
7
+ window.JSZip = JSZip;
8
+ startGridJsDemo({ clientName: 'vanilla-npm', createSpreadsheet: xSpreadsheet });
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "gridjs-vanilla-npm-java-demo",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite --host 127.0.0.1 --port 5175 --strictPort",
7
+ "build": "vite build"
8
+ },
9
+ "dependencies": {
10
+ "gridjs-spreadsheet": "latest",
11
+ "jszip": "^3.10.1"
12
+ },
13
+ "devDependencies": {
14
+ "vite": "latest"
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ fs: { allow: ['..'] },
6
+ proxy: {
7
+ '/GridJs2': 'http://127.0.0.1:8080',
8
+ '/gridjsdemo': 'http://127.0.0.1:8080',
9
+ },
10
+ },
11
+ });
@@ -0,0 +1,32 @@
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
+ <meta name="description" content="Use gridjs-spreadsheet directly with script tags and a Java backend.">
7
+ <title>GridJS Vanilla script demo</title>
8
+ <link rel="stylesheet" href="https://unpkg.com/gridjs-spreadsheet@latest/xspreadsheet.css">
9
+ <script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js"></script>
10
+ <script src="https://unpkg.com/gridjs-spreadsheet@latest/xspreadsheet.js"></script>
11
+ </head>
12
+ <body>
13
+ <div id="start-page" class="demo-shell">
14
+ <header class="demo-navbar"><a href="https://docs.aspose.com/cells/java/aspose-cells-gridjs/">Aspose.Cells.GridJs direct script demo</a></header>
15
+ <main class="demo-container">
16
+ <h1 class="visually-hidden">GridJS Vanilla direct script demo</h1>
17
+ <fieldset class="demo-selector">
18
+ <legend>Select a demo:</legend>
19
+ <label class="radio-card active"><input type="radio" name="demoType" value="permanent" checked><span>permanent url load demo</span></label>
20
+ <label class="radio-card"><input type="radio" name="demoType" value="highlight"><span>highlight and custom context menu demo</span></label>
21
+ </fieldset>
22
+ <section class="upload-block"><h2>Upload a local workbook</h2><input id="upload-input" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"></section>
23
+ <section class="path-block"><h2>Workbook directory: <span id="directory"></span></h2><h2>Choose a workbook to open</h2></section>
24
+ <p id="status-message" class="status-message" role="status"></p>
25
+ <section id="file-list" class="file-list" aria-label="Workbook files"></section>
26
+ </main>
27
+ <footer class="demo-footer">© 2026 · <a href="https://products.aspose.com/cells/java">Aspose.Cells for Java</a></footer>
28
+ </div>
29
+ <main id="editor-page" class="editor-page" hidden><div id="editor-status" class="editor-status" role="status"></div><div id="grid-host"></div></main>
30
+ <script type="module" src="/main.js"></script>
31
+ </body>
32
+ </html>
@@ -0,0 +1,9 @@
1
+ import { startGridJsDemo } from '../shared/demo-app.js';
2
+ import '../shared/styles.css';
3
+
4
+ if (typeof window.x_spreadsheet !== 'function') {
5
+ document.querySelector('#status-message').textContent = 'GridJS failed to load from the script URL.';
6
+ document.querySelector('#status-message').classList.add('status-error');
7
+ } else {
8
+ startGridJsDemo({ clientName: 'vanilla-script', createSpreadsheet: window.x_spreadsheet });
9
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "gridjs-vanilla-script-java-demo",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite --host 127.0.0.1 --port 5176 --strictPort",
7
+ "build": "vite build"
8
+ },
9
+ "devDependencies": {
10
+ "vite": "latest"
11
+ }
12
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ fs: { allow: ['..'] },
6
+ proxy: {
7
+ '/GridJs2': 'http://127.0.0.1:8080',
8
+ '/gridjsdemo': 'http://127.0.0.1:8080',
9
+ },
10
+ },
11
+ });
@@ -0,0 +1,205 @@
1
+ const DEMO_PERMANENT = 'permanent';
2
+ const DEMO_HIGHLIGHT = 'highlight';
3
+
4
+ async function fetchJson(url, options) {
5
+ const response = await fetch(url, options);
6
+ if (!response.ok) {
7
+ const message = await response.text();
8
+ throw new Error(message || `${response.status} ${response.statusText}`);
9
+ }
10
+ return response.json();
11
+ }
12
+
13
+ function normalizeUid(clientName, file) {
14
+ return `${clientName}-uid-${file.replace(/[^a-zA-Z0-9]/g, '-')}`;
15
+ }
16
+
17
+ function readRoute(clientName) {
18
+ const params = new URLSearchParams(window.location.search);
19
+ const file = params.get('file');
20
+ if (!file) return null;
21
+ const demo = params.get('demo') === DEMO_HIGHLIGHT ? DEMO_HIGHLIGHT : DEMO_PERMANENT;
22
+ return {
23
+ file,
24
+ storedFile: params.get('storedFile') || file,
25
+ demo,
26
+ uid: params.get('uid') || normalizeUid(clientName, file),
27
+ fromUpload: params.get('fromUpload') || '',
28
+ };
29
+ }
30
+
31
+ function pushRoute(workbook) {
32
+ const params = new URLSearchParams({ file: workbook.file, demo: workbook.demo });
33
+ if (workbook.storedFile && workbook.storedFile !== workbook.file) params.set('storedFile', workbook.storedFile);
34
+ if (workbook.demo === DEMO_PERMANENT || workbook.fromUpload) params.set('uid', workbook.uid);
35
+ if (workbook.fromUpload) params.set('fromUpload', workbook.fromUpload);
36
+ window.history.pushState(null, '', `/?${params}`);
37
+ }
38
+
39
+ function workbookEndpoint(workbook) {
40
+ const params = new URLSearchParams({ filename: workbook.storedFile || workbook.file });
41
+ if (workbook.fromUpload) {
42
+ params.set('uid', workbook.uid);
43
+ return `/GridJs2/DetailStreamJsonWithUidFromUpload?${params}`;
44
+ }
45
+ if (workbook.demo === DEMO_PERMANENT) {
46
+ params.set('uid', workbook.uid);
47
+ return `/GridJs2/DetailStreamJsonWithUid?${params}`;
48
+ }
49
+ return `/GridJs2/DetailStreamJson?${params}`;
50
+ }
51
+
52
+ export function startGridJsDemo({ clientName, createSpreadsheet }) {
53
+ const startPage = document.querySelector('#start-page');
54
+ const editorPage = document.querySelector('#editor-page');
55
+ const editorStatus = document.querySelector('#editor-status');
56
+ const status = document.querySelector('#status-message');
57
+ const directory = document.querySelector('#directory');
58
+ const fileList = document.querySelector('#file-list');
59
+ const uploadInput = document.querySelector('#upload-input');
60
+ let current = readRoute(clientName);
61
+ let spreadsheet = null;
62
+
63
+ const selectedDemo = () => document.querySelector('input[name="demoType"]:checked')?.value || DEMO_PERMANENT;
64
+
65
+ function setStatus(message, isError = false) {
66
+ status.textContent = message;
67
+ status.classList.toggle('status-error', isError);
68
+ }
69
+
70
+ function updateDemoSelection(demo) {
71
+ const input = document.querySelector(`input[name="demoType"][value="${demo}"]`);
72
+ if (input) input.checked = true;
73
+ document.querySelectorAll('.radio-card').forEach((label) => {
74
+ label.classList.toggle('active', label.contains(input));
75
+ });
76
+ }
77
+
78
+ async function loadFiles() {
79
+ setStatus('Loading workbook list...');
80
+ try {
81
+ const result = await fetchJson('/gridjsdemo/api/files');
82
+ directory.textContent = result.directory || '';
83
+ fileList.replaceChildren();
84
+ if (!result.files?.length) {
85
+ const empty = document.createElement('p');
86
+ empty.className = 'empty-message';
87
+ empty.textContent = 'No workbooks found in the configured directory.';
88
+ fileList.append(empty);
89
+ } else {
90
+ result.files.forEach((file) => {
91
+ const button = document.createElement('button');
92
+ button.type = 'button';
93
+ button.className = 'file-item';
94
+ button.textContent = file;
95
+ button.addEventListener('click', () => openWorkbook({
96
+ file,
97
+ storedFile: file,
98
+ demo: selectedDemo(),
99
+ uid: normalizeUid(clientName, file),
100
+ fromUpload: '',
101
+ }));
102
+ fileList.append(button);
103
+ });
104
+ }
105
+ setStatus('Ready');
106
+ } catch (error) {
107
+ setStatus(`Failed to load files: ${error.message}`, true);
108
+ }
109
+ }
110
+
111
+ async function mountWorkbook() {
112
+ if (!current) {
113
+ editorPage.hidden = true;
114
+ startPage.hidden = false;
115
+ return;
116
+ }
117
+
118
+ startPage.hidden = true;
119
+ editorPage.hidden = false;
120
+ editorStatus.hidden = false;
121
+ editorStatus.textContent = `Loading ${current.file}...`;
122
+ try {
123
+ const payload = await fetchJson(workbookEndpoint(current));
124
+ if (payload.Error) throw new Error(payload.Error);
125
+ payload.filename = current.file;
126
+ document.querySelector('#grid-host').replaceChildren();
127
+
128
+ const options = {
129
+ updateMode: 'server',
130
+ updateUrl: '/GridJs2/UpdateCell',
131
+ showToolbar: true,
132
+ showContextmenu: true,
133
+ mode: 'edit',
134
+ local: 'en',
135
+ };
136
+ spreadsheet = createSpreadsheet('#grid-host', options).loadData(payload.data, payload.actname);
137
+ spreadsheet.setUniqueId?.(payload.uniqueid);
138
+ spreadsheet.setFileName?.(current.file);
139
+ spreadsheet.setImageInfo?.(
140
+ '/GridJs2/ImageUrl',
141
+ '/GridJs2/AddImage',
142
+ '/GridJs2/AddImageByURL',
143
+ '/GridJs2/CopyImage',
144
+ 5678,
145
+ );
146
+ spreadsheet.setFileDownloadInfo?.('/GridJs2/Download');
147
+ spreadsheet.setOleDownloadInfo?.('/GridJs2/Ole');
148
+ spreadsheet.setLazyLoadingUrl?.('/GridJs2/LazyLoadingStreamJson');
149
+ spreadsheet.setOpenFileUrl?.('/');
150
+ spreadsheet.setActiveSheetByName?.(payload.actname)?.setActiveCell?.(payload.actrow, payload.actcol);
151
+ spreadsheet.change?.((data) => console.log('[GridJS Event] changed', data));
152
+ spreadsheet.on?.('cell-selected', (_, row, column) => console.log('[GridJS Event] cell selected', row, column));
153
+ spreadsheet.on?.('sheet-selected', (_, name) => console.log('[GridJS Event] sheet selected', name));
154
+ spreadsheet.updateCellError?.((message) => console.error('[GridJS update error]', message));
155
+ editorStatus.hidden = true;
156
+ } catch (error) {
157
+ editorStatus.hidden = false;
158
+ editorStatus.textContent = `Workbook load failed: ${error.message}`;
159
+ editorStatus.classList.add('status-error');
160
+ }
161
+ }
162
+
163
+ function openWorkbook(workbook) {
164
+ current = workbook;
165
+ pushRoute(workbook);
166
+ void mountWorkbook();
167
+ }
168
+
169
+ document.querySelectorAll('input[name="demoType"]').forEach((input) => {
170
+ input.addEventListener('change', () => updateDemoSelection(input.value));
171
+ });
172
+
173
+ uploadInput.addEventListener('change', async () => {
174
+ const file = uploadInput.files?.[0];
175
+ if (!file) return;
176
+ const form = new FormData();
177
+ form.append('file', file);
178
+ form.append('client', clientName);
179
+ setStatus(`Uploading ${file.name}...`);
180
+ try {
181
+ const result = await fetchJson('/gridjsdemo/api/upload', { method: 'POST', body: form });
182
+ openWorkbook({
183
+ file: result.displayName || file.name,
184
+ storedFile: result.file,
185
+ demo: selectedDemo(),
186
+ uid: result.uid || normalizeUid(clientName, result.file),
187
+ fromUpload: '1',
188
+ });
189
+ } catch (error) {
190
+ setStatus(`Upload failed: ${error.message}`, true);
191
+ } finally {
192
+ uploadInput.value = '';
193
+ }
194
+ });
195
+
196
+ window.addEventListener('popstate', () => {
197
+ current = readRoute(clientName);
198
+ if (current) updateDemoSelection(current.demo);
199
+ void mountWorkbook();
200
+ });
201
+
202
+ updateDemoSelection(current?.demo || DEMO_PERMANENT);
203
+ void loadFiles();
204
+ void mountWorkbook();
205
+ }
@@ -0,0 +1,36 @@
1
+ :root { font-family: Arial, Helvetica, sans-serif; color: #212529; background: #f8f9fa; }
2
+ * { box-sizing: border-box; }
3
+ html, body { min-height: 100%; }
4
+ body { margin: 0; overflow-y: hidden; background: #f8f9fa; font-size: 14px; }
5
+ button, input { font: inherit; }
6
+ [hidden] { display: none !important; }
7
+ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
8
+ .demo-shell { min-height: 100dvh; display: flex; flex-direction: column; background: #f8f9fa; }
9
+ .demo-navbar { min-height: 48px; display: flex; align-items: center; justify-content: center; background: #fff; border-bottom: 1px solid #dee2e6; padding: 8px 12px; font-size: 19px; }
10
+ .demo-navbar a, .demo-footer a { color: #0645ad; text-decoration: underline; }
11
+ .demo-container { flex: 1; width: 100%; max-width: 960px; margin: 0 auto; padding: 20px 16px; }
12
+ .demo-selector { border: 1px solid #adb5bd; border-radius: 6px; margin: 0 0 18px; padding: 18px 14px 14px; display: flex; align-items: center; }
13
+ .demo-selector legend { width: auto; padding: 0 6px; font-weight: 600; }
14
+ .radio-card { min-height: 40px; display: inline-flex; align-items: center; gap: 8px; padding: 0 14px; margin-left: -1px; border: 1px solid #adb5bd; background: #f0f0f0; color: #343a40; cursor: pointer; transition: background-color 180ms ease, color 180ms ease; }
15
+ .radio-card:first-of-type { margin-left: 0; border-radius: 5px 0 0 5px; }
16
+ .radio-card:last-of-type { border-radius: 0 5px 5px 0; }
17
+ .radio-card.active { background: #0b63ce; border-color: #0b63ce; color: #fff; z-index: 1; }
18
+ .radio-card:focus-within, .file-item:focus-visible, input:focus-visible, a:focus-visible { outline: 3px solid rgba(11, 99, 206, .35); outline-offset: 2px; }
19
+ .upload-block h2, .path-block h2 { margin: 0 0 10px; font-size: 14px; line-height: 1.4; }
20
+ .upload-block input[type=file] { min-height: 44px; margin-bottom: 12px; }
21
+ .path-block { margin-top: 4px; }
22
+ .status-message { min-height: 20px; margin: 8px 0; color: #495057; }
23
+ .status-error { color: #b42318 !important; }
24
+ .file-list { min-height: 140px; max-height: 260px; overflow-y: auto; border: 1px solid #ced4da; border-radius: 6px; background: #f6f7ef; padding: 8px; }
25
+ .file-item { display: block; width: 100%; min-height: 40px; border: 0; border-radius: 4px; padding: 8px; background: transparent; color: #0645ad; text-align: left; text-decoration: underline; cursor: pointer; }
26
+ .file-item:hover { background: #e7eef8; }
27
+ .empty-message { margin: 16px; color: #5f6b76; }
28
+ .demo-footer { min-height: 44px; border-top: 1px solid #dee2e6; display: flex; align-items: center; justify-content: center; padding: 8px 12px; color: #495057; }
29
+ .editor-page { height: 100dvh; width: 100vw; overflow: hidden; background: #fff; position: relative; }
30
+ #grid-host { width: 100%; height: 100%; }
31
+ .editor-status { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding: 24px; color: #5f6b76; background: #fff; }
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%; border-radius: 5px !important; }
36
+ }
@@ -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 Vue Demo</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "gridjs-vue-java-demo",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite --host 127.0.0.1 --port 5174 --strictPort",
7
+ "build": "vite build",
8
+ "preview": "vite preview --host 127.0.0.1 --port 6174 --strictPort"
9
+ },
10
+ "dependencies": {
11
+ "gridjs-spreadsheet": "latest",
12
+ "jszip": "^3.10.1",
13
+ "vue": "latest"
14
+ },
15
+ "devDependencies": {
16
+ "@vitejs/plugin-vue": "latest",
17
+ "vite": "latest"
18
+ }
19
+ }