Skip to content

Commit e71e305

Browse files
committed
feature #466 [WIP] [Live] Make Ajax calls happen in serial, with "queued" changes (weaverryan, kbond)
This PR was merged into the 2.x branch. Discussion ---------- [WIP] [Live] Make Ajax calls happen in serial, with "queued" changes | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes | Tickets | None | License | MIT Hi! Consider the following situation: A) Model update is made: Ajax call starts B) An action is triggered BEFORE the Ajax call from (A) finishes. Previously, we would start a 2nd Ajax call for (B) before the Ajax call from (A) finished... meaning two Ajax calls were happening at the same time. There are a few problems with this: (i) Ajax call (B) is missing any potential data changes from Ajax call (A) and (i) complexity of multiple things loading at once, and potentially finishing in a different order. This PR simplifies things, which matches Livewire's behavior anyways. Now, the action from (B) will WAIT until the Ajax call from (A) finishes and THEN start. In fact, while an Ajax call is being made, all changes (potentially multiple model updates or actions) will be "queued" and then all set at once on the next request. TODO: * [X] Update the backend: for POST requests, the "data" was moved under a `data` key in JSON. Previously the entire body was the "data". * [X] Update the backend: for POST/action requests, action "arguments" were moved from query parameters to an `args` key in the JSON. * [x] Frontend: add tests for the `batch` action Ajax calls * [x] Update the backend: a new fake `/batch` action needs to be added that can handle multiple actions at once * [ ] A new `updatedModels` is sent on the ajax requests. If the signature fails, use this to give a better error message about what readonly properties were just modified. (in fact, this is the only purpose of sending this new field to the backend at this time). * [X] ~~Pass a `includeUpdatedModels` value to the Stimulus controller ONLY when in `dev` mode.~~ For consistency, we will always pass the `updatedModels` in our Ajax requests, though this is intended to be "internal" and we won't use it other than to throw better errors. * [X] ~~(Optional) If the backend has an unexpected exception (i.e. 5xx or maybe some 4xx where we mark that this is a problem we should show the developer), then render the entire HTML exception page (in dev mode only) so the user can see the error.~~ Done in #467 Cheers! Commits ------- 4fbcebe cs fix and small tweaks a0c4d3f use `Request::toArray()` 13d7110 mildly reworking to follow the logic of what is happening more clearly (#5) b47ece1 [Live] add batch action controller 8b1e879 Refactoring towards single request & pending updates
2 parents 75272b1 + 4fbcebe commit e71e305

File tree

18 files changed

+730
-320
lines changed

18 files changed

+730
-320
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# CHANGELOG
22

3+
## 2.5.0
4+
5+
- [BEHAVIOR CHANGE] Previously, Ajax calls could happen in parallel (if
6+
you changed a model then triggered an action before the model update Ajax
7+
call finished, the action Ajax call would being in parallel). Now, if
8+
an Ajax call is currently happening, any future requests will wait until
9+
it finishes. Then, all queued changes (potentially multiple model updates
10+
or actions) will be sent all at once on the next request.
11+
312
## 2.4.0
413

514
- [BC BREAK] Previously, the `id` attribute was used with `morphdom` as the

src/LiveComponent/assets/src/UnsyncedInputContainer.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default class UnsyncedInputContainer {
22
#mappedFields: Map<string, HTMLElement>;
3-
#unmappedFields: Array<HTMLElement>= [];
3+
#unmappedFields: Array<HTMLElement> = [];
44

55
constructor() {
66
this.#mappedFields = new Map();
@@ -20,14 +20,6 @@ export default class UnsyncedInputContainer {
2020
return [...this.#unmappedFields, ...this.#mappedFields.values()]
2121
}
2222

23-
clone(): UnsyncedInputContainer {
24-
const container = new UnsyncedInputContainer();
25-
container.#mappedFields = new Map(this.#mappedFields);
26-
container.#unmappedFields = [...this.#unmappedFields];
27-
28-
return container;
29-
}
30-
3123
allMappedFields(): Map<string, HTMLElement> {
3224
return this.#mappedFields;
3325
}

src/LiveComponent/assets/src/ValueStore.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { normalizeModelName } from './string_utils';
44

55
export default class {
66
controller: LiveController;
7+
updatedModels: string[] = [];
78

89
constructor(liveController: LiveController) {
910
this.controller = liveController;
@@ -33,6 +34,7 @@ export default class {
3334
*/
3435
set(name: string, value: any): void {
3536
const normalizedName = normalizeModelName(name);
37+
this.updatedModels.push(normalizedName);
3638

3739
this.controller.dataValue = setDeepData(this.controller.dataValue, normalizedName, value);
3840
}
@@ -49,4 +51,8 @@ export default class {
4951
asJson(): string {
5052
return JSON.stringify(this.controller.dataValue);
5153
}
54+
55+
all(): any {
56+
return this.controller.dataValue;
57+
}
5258
}

0 commit comments

Comments
 (0)