Skip to content

Commit 36d02fd

Browse files
authored
chore: merge branch 'master' into next branch & update dependencies (#2359)
* chore: merge branch 'master' into next branch & update dependencies
1 parent f55cbe2 commit 36d02fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1830
-929
lines changed

demos/aurelia/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"cypress": "catalog:",
5858
"cypress-real-events": "catalog:",
5959
"dompurify": "catalog:",
60-
"npm-run-all2": "catalog:",
6160
"sass": "catalog:",
6261
"tslib": "catalog:",
6362
"typescript": "catalog:",

demos/react/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
"@slickgrid-universal/excel-export": "workspace:*",
3030
"@slickgrid-universal/graphql": "workspace:*",
3131
"@slickgrid-universal/odata": "workspace:*",
32-
"@slickgrid-universal/pdf-export": "workspace:*",
3332
"@slickgrid-universal/pagination-component": "workspace:*",
33+
"@slickgrid-universal/pdf-export": "workspace:*",
3434
"@slickgrid-universal/react-row-detail-plugin": "workspace:*",
35+
"@slickgrid-universal/row-detail-view-plugin": "workspace:*",
3536
"@slickgrid-universal/rxjs-observable": "workspace:*",
3637
"@slickgrid-universal/text-export": "workspace:*",
3738
"bootstrap": "catalog:",

docs/TOC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878

7979
## Backend Services
8080

81-
* [Custom Backend Service](backend-services/Custom-Backend-Service.md)
81+
* [Custom Backend Service](backend-services/custom-backend-service.md)
8282
* [OData](backend-services/OData.md)
8383
* [GraphQL](backend-services/GraphQL.md)
8484
* [JSON Result Structure](backend-services/graphql/GraphQL-JSON-Result.md)

docs/backend-services/Custom-Backend-Service.md

Lines changed: 0 additions & 50 deletions
This file was deleted.

docs/backend-services/GraphQL.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ backendServiceApi: {
3030
preProcess?: () => void;
3131

3232
// On Processing, we get the query back from the service, and we need to provide a Promise. For example: this.http.get(myGraphqlUrl)
33-
process: (query: string) => Promise<any>;
33+
// Note: SlickGrid automatically manages request cancellation via AbortSignal (see "Request Cancellation with AbortSignal" section below)
34+
process: (query: string, options?: { signal?: AbortSignal }) => Promise<any>;
3435

3536
// After executing the query, what action to perform? For example, stop the spinner
3637
postProcess: (response: any) => void;
@@ -153,12 +154,12 @@ export class Example {
153154
}
154155

155156
// Web API call
156-
getAllCustomers(graphqlQuery) {
157+
getAllCustomers(graphqlQuery: string, options?: { signal?: AbortSignal }) {
157158
// regular Http Client call
158159
return this.http.createRequest(`/api/customers?${graphqlQuery}`).then(response => response.json());
159160

160-
// or with Fetch Client
161-
// return this.http.fetch(`/api/customers?${graphqlQuery}`).then(response => response.json());
161+
// or with Fetch Client - supporting AbortSignal for request cancellation
162+
// return this.http.fetch(`/api/customers?${graphqlQuery}`, { signal: options?.signal }).then(response => response.json());
162163
}
163164
}
164165
```
@@ -249,6 +250,38 @@ changeQueryArguments() {
249250
}
250251
```
251252

253+
### Request Cancellation with AbortSignal
254+
When users trigger rapid filter or sort changes (e.g., typing quickly in a filter), SlickGrid will automatically cancel any pending HTTP requests using the `AbortSignal` API. This prevents stale results from overwriting newer data and improves user experience.
255+
256+
The `process` method receives an optional `options` parameter. If you want to support automatic request cancellation, pass the `signal` from this parameter to your fetch/HTTP call. When a new request is triggered, any previous `AbortSignal` will be aborted automatically.
257+
258+
#### How It Works
259+
1. User types in a filter → sends "Jo" query (request #1)
260+
2. User types "e" quickly before response arrives → "Joe" query (request #2)
261+
3. SlickGrid automatically aborts request #1's signal
262+
4. Request #2's response is processed, request #1's response is ignored
263+
264+
#### Implementation Example with Fetch API
265+
```ts
266+
getAllCustomers(graphqlQuery: string, options?: { signal?: AbortSignal }) {
267+
// Use the signal from options to enable automatic cancellation
268+
return fetch(`/api/graphql`, {
269+
method: 'POST',
270+
body: graphqlQuery,
271+
signal: options?.signal // Pass the signal to abort the request
272+
}).then(response => response.json());
273+
}
274+
```
275+
276+
#### Implementation with Axios
277+
```ts
278+
getAllCustomers(graphqlQuery: string, options?: { signal?: AbortSignal }) {
279+
return axios.post(`/api/graphql`, graphqlQuery, {
280+
signal: options?.signal // Axios supports AbortSignal
281+
});
282+
}
283+
```
284+
252285
### GraphQL without Pagination
253286
By default, the Pagination is enabled and will produce a GraphQL query which includes page related information but you could also use the GraphQL Service without Pagination if you wish by disabling the flag `enablePagination: false` in the Grid Options. However please note that the GraphQL Query will be totally different since it won't include any page related information.
254287

docs/backend-services/OData.md

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ backendServiceApi: {
4242
preProcess?: () => void;
4343

4444
// On Processing, we get the query back from the service, and we need to provide a Promise. For example: this.http.get(myGraphqlUrl)
45-
process: (query: string) => Promise<any>;
45+
// Note: The optional second parameter includes an AbortSignal to cancel pending requests when a new filter/sort is triggered
46+
process: (query: string, options?: { signal?: AbortSignal }) => Promise<any>;
4647

4748
// After executing the query, what action to perform? For example, stop the spinner
4849
postProcess: (response: any) => void;
@@ -99,7 +100,7 @@ export class Example {
99100
top: defaultPageSize
100101
} as OdataOption,
101102
preProcess: () => this.displaySpinner(true),
102-
process: (query) => this.getCustomerApiCall(query),
103+
process: (query, options) => this.getCustomerApiCall(query, options),
103104
postProcess: (response) => {
104105
this.displaySpinner(false);
105106
this.getCustomerCallback(response);
@@ -108,13 +109,13 @@ export class Example {
108109
};
109110
}
110111

111-
// Web API call
112-
getCustomerApiCall(odataQuery) {
112+
// Web API call - note the second parameter contains the AbortSignal for request cancellation
113+
getCustomerApiCall(odataQuery: string, options?: { signal?: AbortSignal }) {
113114
// regular Http Client call
114115
return this.http.createRequest(`/api/customers?${odataQuery}`).then(response => response.json());
115116

116-
// or with Fetch Client
117-
// return this.http.fetch(`/api/customers?${odataQuery}`).then(response => response.json());
117+
// or with Fetch Client - supporting AbortSignal for request cancellation
118+
// return this.http.fetch(`/api/customers?${odataQuery}`, { signal: options?.signal }).then(response => response.json());
118119
}
119120

120121
getCustomerCallback(response) {
@@ -135,6 +136,36 @@ export class Example {
135136
}
136137
```
137138

139+
### Request Cancellation with AbortSignal
140+
When users trigger rapid filter or sort changes (e.g., typing quickly in a filter), SlickGrid will automatically cancel any pending HTTP requests using the `AbortSignal` API. This prevents stale results from overwriting newer data and improves user experience.
141+
142+
The `process` method receives an optional `options` parameter. If you want to support automatic request cancellation, pass the `signal` from this parameter to your fetch/HTTP call. When a new request is triggered, any previous `AbortSignal` will be aborted automatically.
143+
144+
#### How It Works
145+
1. User types in a filter → sends "Jo" query (request #1)
146+
2. User types "e" quickly before response arrives → "Joe" query (request #2)
147+
3. SlickGrid automatically aborts request #1's signal
148+
4. Request #2's response is processed, request #1's response is ignored
149+
150+
#### Implementation Example with Fetch API
151+
```ts
152+
getCustomerApiCall(odataQuery: string, options?: { signal?: AbortSignal }) {
153+
// Use the signal from options to enable automatic cancellation
154+
return fetch(`/api/customers?${odataQuery}`, {
155+
signal: options?.signal // Pass the signal to abort the request
156+
}).then(response => response.json());
157+
}
158+
```
159+
160+
#### Implementation with Axios
161+
```ts
162+
getCustomerApiCall(odataQuery: string, options?: { signal?: AbortSignal }) {
163+
return axios.get(`/api/customers?${odataQuery}`, {
164+
signal: options?.signal // Axios supports AbortSignal
165+
});
166+
}
167+
```
168+
138169
### Passing Extra Arguments to the Query
139170
You might need to pass extra arguments to your OData query, for example passing a `userId`, you can do that simply by modifying the query you sent to your `process` callback method. For example
140171
```ts
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
### Intro
2+
The lib currently supports OData and GraphQL with built-in Services, if you want to use and create a different and Custom Backend Service, then follow the steps below.
3+
4+
### Instructions
5+
To create your own Custom Backend Service, I suggest you take the code of the [GraphqlService](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/graphql/src/services/graphql.service.ts) and then rewrite the internal of each methods. The thing to remember is that you have to implement the `BackendService` as defined in the GraphqlService (`export class GraphqlService implements BackendService`).
6+
7+
You typically want to implement your service following these TypeScript interfaces
8+
- [backendService.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendService.interface.ts)
9+
- [backendServiceApi.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendServiceApi.interface.ts)
10+
- [backendServiceOption.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendServiceOption.interface.ts)
11+
12+
At the end of it, you'll have a Custom Backend Service that will be instantiated just like the GraphQL or OData that I've created, it should look similar to this (also note, try to avoid passing anything in the `constructor` of your Service to keep it usable by everyone)
13+
```ts
14+
class MyComponent {
15+
gridInit() {
16+
this.gridOptions = {
17+
backendServiceApi: {
18+
service: new YourCustomBackendService(),
19+
options: {
20+
// custom service options that extends "backendServiceOption" interface
21+
},
22+
preProcess: () => !this.isDataLoaded ? this.displaySpinner(true) : '',
23+
process: (query, options) => this.getCountries(query, options),
24+
postProcess: (result) => {
25+
this.displaySpinner(false);
26+
this.isDataLoaded = true;
27+
}
28+
} as YourCustomBackendServiceApi
29+
};
30+
}
31+
32+
// Note: The second parameter contains the AbortSignal for an optional request cancellation
33+
getCountries(query: string, options?: { signal?: AbortSignal }) {
34+
// You can pass the signal to fetch or other HTTP libraries
35+
return fetch(`/api/countries?${query}`, {
36+
signal: options?.signal // Pass the signal to enable automatic request cancellation
37+
});
38+
}
39+
}
40+
```
41+
42+
If you need to reference your Service for other purposes then you better instantiated in a separate variable and then just pass it to the `service` property of the `backendServiceApi`.
43+
```ts
44+
class MyComponent {
45+
myCustomService = new YourCustomBackendService();
46+
47+
gridInit {
48+
this.gridOptions = {
49+
backendServiceApi: {
50+
service: this.myCustomService,
51+
// ...
52+
} as YourCustomBackendServiceApi
53+
};
54+
}
55+
}
56+
```
57+
58+
If your Service is for a well known DB or API framework, then it might be possible to add your Service to the lib itself, however it should be added to the new monorepo lib [Slickgrid-Universal](https://github.com/ghiscoding/slickgrid-universal) in the list of [slickgrid-universal/packages](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages). Since that is a monorepo lib, users will have the ability to use and download only the package they need.
59+
60+
## Request Cancellation with AbortSignal
61+
62+
SlickGrid automatically supports request cancellation for Promise-based backend services using the standard `AbortSignal` API. This feature prevents stale results from being processed when users rapidly trigger new filter or sort operations.
63+
64+
### Why Cancellation is Important
65+
66+
Without cancellation, if a user is typing a filter (e.g., "Jo", then "Joe", then "John"), each letter triggers a backend request. If the requests complete out of order, the result from the "Jo" query might arrive AFTER the "John" query result, overwriting the correct data with outdated results.
67+
68+
### How It Works
69+
70+
1. **Automatic**: SlickGrid manages the AbortController internally - you don't need to create one
71+
2. **Transparent**: The `AbortSignal` is automatically passed to your `process` method
72+
3. **Smart**: Only the most recent request result is processed; cancelled requests are silently ignored
73+
74+
### Implementation
75+
76+
All you need to do is accept the optional second parameter in your `process` method and, if you want to support automatic request cancellation, pass the `signal` to your fetch/HTTP call:
77+
78+
```ts
79+
process: (query: string, options?: { signal?: AbortSignal }) => Promise<any>
80+
```
81+
82+
#### Example with Fetch API
83+
```ts
84+
process: (query: string, options?: { signal?: AbortSignal }) => {
85+
return fetch('/api/data?q=' + query, {
86+
signal: options?.signal // This automatically aborts when a new request comes in
87+
}).then(r => r.json());
88+
}
89+
```
90+
91+
#### Example with Axios
92+
```ts
93+
process: (query: string, options?: { signal?: AbortSignal }) => {
94+
return axios.get('/api/data?q=' + query, {
95+
signal: options?.signal
96+
});
97+
}
98+
```
99+
100+
#### Example with Error Handling
101+
102+
If you want to handle cancellation errors explicitly:
103+
104+
```ts
105+
process: (query: string, options?: { signal?: AbortSignal }) => {
106+
return fetch('/api/data?q=' + query, {
107+
signal: options?.signal
108+
}).then(r => {
109+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
110+
return r.json();
111+
}).catch(error => {
112+
// Warning: Aborted requests throw DOMException with name 'AbortError'
113+
// SlickGrid handles these automatically, so you can ignore them
114+
if (error.name === 'AbortError') {
115+
console.log('Request cancelled due to newer filter/sort');
116+
} else {
117+
throw error; // Re-throw real errors
118+
}
119+
});
120+
}
121+
```
122+
123+
### Important Notes
124+
125+
- The `AbortSignal` parameter is **optional** - existing implementations without it will continue to work
126+
- **AbortError handling**: When a request is cancelled, it throws an error with `name: 'AbortError'`. SlickGrid handles these automatically
127+
- **Real errors**: Non-AbortError exceptions are still passed to your error callbacks
128+
- **Backwards compatible**: Older implementations that don't use the signal parameter will work as before

docs/migrations/migration-to-10.x.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,31 +88,31 @@ Below is a list of Enums that you need to replace with their associated string l
8888
| `DelimiterType` | `DelimiterType.comma` | `','` |
8989
| | `DelimiterType.colon` | `':'` |
9090
| | `DelimiterType.space` | `' '` |
91-
| | ... | ... |
91+
| ... | ... | ... |
9292
| `EventNamingStyle` | `EventNamingStyle.camelCase` | `'camelCase'` |
9393
| | `EventNamingStyle.kebabCase` | `'kebabCase'` |
9494
| | `EventNamingStyle.lowerCase` | `'lowerCase'` |
95-
| | ... | ... |
95+
| ... | ... | ... |
9696
| `FieldType` | `FieldType.boolean` | `'boolean'` |
9797
| | `FieldType.number` | `'number'` |
9898
| | `FieldType.dateIso` | `'dateIso'` |
99-
| | ... | ... |
99+
| ... | ... | ... |
100100
| `FileType` | `FileType.csv` | `'csv'` |
101101
| | `FileType.xlsx` | `'xlsx'` |
102-
| | ... | ... |
102+
| ... | ... | ... |
103103
| `GridStateType` | `GridStateType.columns` | `'columns'` |
104104
| | `GridStateType.filters` | `'filters'` |
105105
| | `GridStateType.sorters` | `'sorters'` |
106-
| | ... | ... |
106+
| ... | ... | ... |
107107
| `OperatorType` | `OperatorType.greaterThan` | `'>'` or `'GT'` | See [Operator](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/enums/operator.type.ts) list for all available operators |
108108
| | `OperatorType.lessThanOrEqual` | `'<='` or `'LE'` |
109109
| | `OperatorType.contains` | `'Contains'` or `'CONTAINS'` | Operators are written as PascalCase |
110110
| | `OperatorType.equal` | `'='` or `'EQ'` |
111111
| | `OperatorType.rangeExclusive` | `'RangeExclusive'` |
112-
| | ... | ... |
112+
| ... | ... | ... |
113113
| `SortDirection` | `SortDirection.ASC` | `'ASC'` or `'asc'` |
114114
| | `SortDirection.DESC` | `'DESC'` or `'desc'` |
115-
| | ... | ... |
115+
| ... | ... | ... |
116116

117117
**Hint** You can use VSCode search & replace, but make sure it's set to Regular Expression pattern
118118

0 commit comments

Comments
 (0)