@arrai-innovations/reactive-helpers 10.2.0 → 10.4.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.
- package/.circleci/config.yml +1 -3
- package/docs.md +5 -4
- package/package.json +1 -1
- package/tests/unit/use/listCalculated.spec.js +61 -1
- package/tests/unit/use/listRelated.spec.js +116 -2
- package/tests/unit/use/listSort.spec.js +111 -16
- package/use/listRelated.js +23 -4
- package/use/listSort.js +116 -65
- package/use/listSubscription.js +2 -2
- package/utils/watches.js +1 -1
package/.circleci/config.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
version: 2.1
|
|
2
2
|
orbs:
|
|
3
3
|
eslint: arrai/eslint@7.4.0
|
|
4
|
-
prettier: arrai/prettier@5.
|
|
4
|
+
prettier: arrai/prettier@5.4.0
|
|
5
5
|
npm: arrai/npm@2.4.1
|
|
6
6
|
github: arrai/github@1.1.0
|
|
7
7
|
workflows:
|
|
@@ -75,14 +75,12 @@ workflows:
|
|
|
75
75
|
- prettier/code_style:
|
|
76
76
|
name: prettier
|
|
77
77
|
context: arrai-global
|
|
78
|
-
version: 2.6.2
|
|
79
78
|
filters:
|
|
80
79
|
branches:
|
|
81
80
|
only:
|
|
82
81
|
- main
|
|
83
82
|
- prettier/code_style:
|
|
84
83
|
name: prettier-no-badge
|
|
85
|
-
version: 2.6.2
|
|
86
84
|
create_badges: false
|
|
87
85
|
filters:
|
|
88
86
|
branches:
|
package/docs.md
CHANGED
|
@@ -494,11 +494,11 @@ It tracks the objects in the list, and their added order.
|
|
|
494
494
|
causing the list to be re-fetched as needed and listening for updates to the list.
|
|
495
495
|
|
|
496
496
|
**Kind**: global function
|
|
497
|
-
**Returns**: ListSubscription
|
|
497
|
+
**Returns**: [`ListSubscription`] - - the list subscription
|
|
498
498
|
|
|
499
|
-
| Param | Type |
|
|
500
|
-
| ------- | --------------------------- |
|
|
501
|
-
| options | [`ListSubscriptionOptions`] |
|
|
499
|
+
| Param | Type | Description |
|
|
500
|
+
| ------- | --------------------------- | --------------------------------------------------- |
|
|
501
|
+
| options | [`ListSubscriptionOptions`] | the configuration options for the list subscription |
|
|
502
502
|
|
|
503
503
|
## useObjectInstances(instanceArgs)
|
|
504
504
|
|
|
@@ -832,6 +832,7 @@ A CSS object or a space-separated list of CSS classes.
|
|
|
832
832
|
[`cssclasseswithrefs`]: #cssclasseswithrefs
|
|
833
833
|
[`listinstance`]: #listinstance
|
|
834
834
|
[`listinstanceoptions`]: #listinstanceoptions
|
|
835
|
+
[`listsubscription`]: #listsubscription
|
|
835
836
|
[`listsubscriptionoptions`]: #listsubscriptionoptions
|
|
836
837
|
[`objectinstanceinstance`]: #objectinstanceinstance
|
|
837
838
|
[`listinstancestate`]: #listinstancestate
|
package/package.json
CHANGED
|
@@ -2,12 +2,16 @@ import { nextTick } from "vue";
|
|
|
2
2
|
import { deepUnref } from "vue-deepunref";
|
|
3
3
|
|
|
4
4
|
describe("use/listCalculated", () => {
|
|
5
|
-
let useListInstance, useListCalculated;
|
|
5
|
+
let useListInstance, useListCalculated, useListRelated, AwaitNot;
|
|
6
6
|
beforeEach(async () => {
|
|
7
7
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
8
8
|
useListInstance = listInstanceModule.useListInstance;
|
|
9
9
|
const listCalculatedModule = await import("../../../use/listCalculated");
|
|
10
10
|
useListCalculated = listCalculatedModule.useListCalculated;
|
|
11
|
+
const listRelatedModule = await import("../../../use/listRelated");
|
|
12
|
+
useListRelated = listRelatedModule.useListRelated;
|
|
13
|
+
const watchesModule = await import("../../../utils/watches.js");
|
|
14
|
+
AwaitNot = watchesModule.AwaitNot;
|
|
11
15
|
});
|
|
12
16
|
it("should return a list of calculated items", async () => {
|
|
13
17
|
const mainListInstance = useListInstance({ props: {} });
|
|
@@ -71,4 +75,60 @@ describe("use/listCalculated", () => {
|
|
|
71
75
|
},
|
|
72
76
|
});
|
|
73
77
|
});
|
|
78
|
+
it("should allow calculated objects to return results based on related objects", async () => {
|
|
79
|
+
const mainListInstance = useListInstance({ props: {} });
|
|
80
|
+
const relatedListInstance = useListInstance({ props: {} });
|
|
81
|
+
mainListInstance.addListObject({
|
|
82
|
+
id: "1",
|
|
83
|
+
name: "main",
|
|
84
|
+
related_items: ["2", "3"],
|
|
85
|
+
related_id: "4",
|
|
86
|
+
calculated_items: ["2", "3"],
|
|
87
|
+
calculated_id: "4",
|
|
88
|
+
});
|
|
89
|
+
relatedListInstance.addListObject({
|
|
90
|
+
id: "2",
|
|
91
|
+
name: "related1",
|
|
92
|
+
});
|
|
93
|
+
relatedListInstance.addListObject({
|
|
94
|
+
id: "3",
|
|
95
|
+
name: "related2",
|
|
96
|
+
});
|
|
97
|
+
relatedListInstance.addListObject({
|
|
98
|
+
id: "4",
|
|
99
|
+
name: "related3",
|
|
100
|
+
});
|
|
101
|
+
const listRelated = useListRelated({
|
|
102
|
+
parentState: mainListInstance.state,
|
|
103
|
+
relatedObjectsRules: {
|
|
104
|
+
relatedItems: {
|
|
105
|
+
objects: relatedListInstance.state.objects,
|
|
106
|
+
pkKey: "related_items",
|
|
107
|
+
},
|
|
108
|
+
relatedItem: {
|
|
109
|
+
objects: relatedListInstance.state.objects,
|
|
110
|
+
pkKey: "related_id",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const listCalculated = useListCalculated({
|
|
115
|
+
parentState: listRelated.state,
|
|
116
|
+
calculatedObjectsRules: {
|
|
117
|
+
calculatedItems: (obj, relatedObj) => relatedObj.relatedItems?.map((x) => x.name + "-modified"),
|
|
118
|
+
calculatedItem: (obj, relatedObj) => relatedObj.relatedItem?.name + "-modified",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const anr = new AwaitNot({
|
|
122
|
+
obj: listCalculated.state,
|
|
123
|
+
prop: "running",
|
|
124
|
+
});
|
|
125
|
+
anr.start();
|
|
126
|
+
await anr.promise;
|
|
127
|
+
expect(deepUnref(listCalculated.state.calculatedObjects)).toEqual({
|
|
128
|
+
1: {
|
|
129
|
+
calculatedItems: ["related1-modified", "related2-modified"],
|
|
130
|
+
calculatedItem: "related3-modified",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
74
134
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// import { useListInstance, useListRelated } from "../../../use/listRelated.js";
|
|
2
1
|
import { nextTick } from "vue";
|
|
3
2
|
import { deepUnref } from "vue-deepunref";
|
|
4
3
|
|
|
5
4
|
describe("use/listRelated", () => {
|
|
6
|
-
let useListInstance, useListRelated;
|
|
5
|
+
let useListInstance, useListRelated, AwaitNot;
|
|
7
6
|
beforeEach(async () => {
|
|
8
7
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
9
8
|
useListInstance = listInstanceModule.useListInstance;
|
|
10
9
|
const listRelatedModule = await import("../../../use/listRelated");
|
|
11
10
|
useListRelated = listRelatedModule.useListRelated;
|
|
11
|
+
const watchesModule = await import("../../../utils/watches.js");
|
|
12
|
+
AwaitNot = watchesModule.AwaitNot;
|
|
12
13
|
});
|
|
13
14
|
it("should return a list of related items", async () => {
|
|
14
15
|
const mainListInstance = useListInstance({ props: {} });
|
|
@@ -78,4 +79,117 @@ describe("use/listRelated", () => {
|
|
|
78
79
|
},
|
|
79
80
|
});
|
|
80
81
|
});
|
|
82
|
+
it('should allow related objects pkKey to be prefixed with "relatedItem." to reference previously related objects', async () => {
|
|
83
|
+
//
|
|
84
|
+
const mainListInstance = useListInstance({ props: {} });
|
|
85
|
+
const intermediateListInstance = useListInstance({ props: {} });
|
|
86
|
+
const relatedListInstance = useListInstance({ props: {} });
|
|
87
|
+
mainListInstance.addListObject({
|
|
88
|
+
id: "1",
|
|
89
|
+
name: "main",
|
|
90
|
+
intermediate_ids: ["2", "3"],
|
|
91
|
+
intermediate_id: "2",
|
|
92
|
+
});
|
|
93
|
+
intermediateListInstance.addListObject({
|
|
94
|
+
id: "2",
|
|
95
|
+
name: "intermediate1",
|
|
96
|
+
related_ids: ["4", "5"],
|
|
97
|
+
related_id: "4",
|
|
98
|
+
});
|
|
99
|
+
intermediateListInstance.addListObject({
|
|
100
|
+
id: "3",
|
|
101
|
+
name: "intermediate2",
|
|
102
|
+
related_ids: ["6", "7"],
|
|
103
|
+
related_id: "6",
|
|
104
|
+
});
|
|
105
|
+
relatedListInstance.addListObject({
|
|
106
|
+
id: "4",
|
|
107
|
+
name: "related1",
|
|
108
|
+
});
|
|
109
|
+
relatedListInstance.addListObject({
|
|
110
|
+
id: "5",
|
|
111
|
+
name: "related2",
|
|
112
|
+
});
|
|
113
|
+
relatedListInstance.addListObject({
|
|
114
|
+
id: "6",
|
|
115
|
+
name: "related3",
|
|
116
|
+
});
|
|
117
|
+
relatedListInstance.addListObject({
|
|
118
|
+
id: "7",
|
|
119
|
+
name: "related4",
|
|
120
|
+
});
|
|
121
|
+
const listRelated = useListRelated({
|
|
122
|
+
parentState: mainListInstance.state,
|
|
123
|
+
relatedObjectsRules: {
|
|
124
|
+
intermediateItems: {
|
|
125
|
+
objects: intermediateListInstance.state.objects,
|
|
126
|
+
pkKey: "intermediate_ids",
|
|
127
|
+
},
|
|
128
|
+
intermediateItem: {
|
|
129
|
+
objects: intermediateListInstance.state.objects,
|
|
130
|
+
pkKey: "intermediate_id",
|
|
131
|
+
},
|
|
132
|
+
relatedItems: {
|
|
133
|
+
objects: relatedListInstance.state.objects,
|
|
134
|
+
pkKey: "relatedItem.intermediateItems.related_ids",
|
|
135
|
+
},
|
|
136
|
+
relatedItem: {
|
|
137
|
+
objects: relatedListInstance.state.objects,
|
|
138
|
+
pkKey: "relatedItem.intermediateItem.related_id",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const anr = new AwaitNot({
|
|
143
|
+
obj: listRelated.state,
|
|
144
|
+
prop: "running",
|
|
145
|
+
});
|
|
146
|
+
anr.start();
|
|
147
|
+
await anr.promise;
|
|
148
|
+
expect(deepUnref(listRelated.state.relatedObjects)).toEqual({
|
|
149
|
+
1: {
|
|
150
|
+
intermediateItems: [
|
|
151
|
+
{
|
|
152
|
+
id: "2",
|
|
153
|
+
name: "intermediate1",
|
|
154
|
+
related_ids: ["4", "5"],
|
|
155
|
+
related_id: "4",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "3",
|
|
159
|
+
name: "intermediate2",
|
|
160
|
+
related_ids: ["6", "7"],
|
|
161
|
+
related_id: "6",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
intermediateItem: {
|
|
165
|
+
id: "2",
|
|
166
|
+
name: "intermediate1",
|
|
167
|
+
related_ids: ["4", "5"],
|
|
168
|
+
related_id: "4",
|
|
169
|
+
},
|
|
170
|
+
relatedItems: [
|
|
171
|
+
{
|
|
172
|
+
id: "4",
|
|
173
|
+
name: "related1",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "5",
|
|
177
|
+
name: "related2",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "6",
|
|
181
|
+
name: "related3",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "7",
|
|
185
|
+
name: "related4",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
relatedItem: {
|
|
189
|
+
id: "4",
|
|
190
|
+
name: "related1",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
});
|
|
81
195
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { doAwaitTimeout } from "../../../utils/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { reactive } from "vue";
|
|
3
3
|
import { deepUnref } from "vue-deepunref";
|
|
4
4
|
|
|
5
5
|
describe("use/useListSort", () => {
|
|
@@ -8,9 +8,12 @@ describe("use/useListSort", () => {
|
|
|
8
8
|
sortThrottleWait,
|
|
9
9
|
useListInstance,
|
|
10
10
|
useListInstances,
|
|
11
|
+
useListRelated,
|
|
12
|
+
useListCalculated,
|
|
11
13
|
useListSort,
|
|
12
14
|
useListSorts,
|
|
13
|
-
setListSortDefaultOptions
|
|
15
|
+
setListSortDefaultOptions,
|
|
16
|
+
AwaitNot;
|
|
14
17
|
const contactsResolved = [
|
|
15
18
|
{
|
|
16
19
|
id: 15,
|
|
@@ -41,6 +44,8 @@ describe("use/useListSort", () => {
|
|
|
41
44
|
const imported = await import("../../../use");
|
|
42
45
|
useListInstance = imported.useListInstance;
|
|
43
46
|
useListInstances = imported.useListInstances;
|
|
47
|
+
useListRelated = imported.useListRelated;
|
|
48
|
+
useListCalculated = imported.useListCalculated;
|
|
44
49
|
orderByRules = [
|
|
45
50
|
{ key: "organization", desc: true, localeCompare: false },
|
|
46
51
|
{ key: "lexical_name", desc: false, localeCompare: true },
|
|
@@ -58,10 +63,22 @@ describe("use/useListSort", () => {
|
|
|
58
63
|
setListSortDefaultOptions({
|
|
59
64
|
sortThrottleWait: 0,
|
|
60
65
|
});
|
|
66
|
+
const importedUtils = await import("../../../utils");
|
|
67
|
+
AwaitNot = importedUtils.AwaitNot;
|
|
61
68
|
});
|
|
62
69
|
|
|
63
70
|
afterEach(() => vi.resetAllMocks());
|
|
64
71
|
|
|
72
|
+
const waitForListSort = async (listSort) => {
|
|
73
|
+
const anr = new AwaitNot({
|
|
74
|
+
obj: listSort.state,
|
|
75
|
+
prop: "running",
|
|
76
|
+
timeout: 2000,
|
|
77
|
+
});
|
|
78
|
+
anr.start();
|
|
79
|
+
await anr.promise;
|
|
80
|
+
};
|
|
81
|
+
|
|
65
82
|
it("generates initial values from inputs", () => {
|
|
66
83
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
|
|
67
84
|
expect(listSort.state.orderByRules).toEqual(orderByRules);
|
|
@@ -89,13 +106,14 @@ describe("use/useListSort", () => {
|
|
|
89
106
|
listInstance.addListObject(contact);
|
|
90
107
|
}
|
|
91
108
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules });
|
|
92
|
-
|
|
109
|
+
// sorts immediately
|
|
93
110
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
111
|
+
await waitForListSort(listSort);
|
|
94
112
|
listInstance.addListObject(addObject);
|
|
95
|
-
await
|
|
113
|
+
await waitForListSort(listSort);
|
|
96
114
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
97
115
|
listInstance.deleteListObject(12);
|
|
98
|
-
await
|
|
116
|
+
await waitForListSort(listSort);
|
|
99
117
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
100
118
|
});
|
|
101
119
|
});
|
|
@@ -115,17 +133,17 @@ describe("use/useListSort", () => {
|
|
|
115
133
|
listSort.state.orderByRules.pop();
|
|
116
134
|
listSort.state.orderByRules.push({ key: "lexical_name", desc: false, localeCompare: true });
|
|
117
135
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
118
|
-
await
|
|
136
|
+
await waitForListSort(listSort);
|
|
119
137
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
120
138
|
listSort.state.orderByRules.pop();
|
|
121
139
|
listSort.state.orderByRules.push({ key: "organization", desc: true, localeCompare: true });
|
|
122
|
-
await
|
|
140
|
+
await waitForListSort(listSort);
|
|
123
141
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
124
142
|
listSort.state.orderByRules.pop();
|
|
125
|
-
await
|
|
143
|
+
await waitForListSort(listSort);
|
|
126
144
|
expect(listSort.state.order).toEqual(testOrder4);
|
|
127
145
|
listSort.state.orderByRules.push({ key: "organization", desc: false, localeCompare: false });
|
|
128
|
-
await
|
|
146
|
+
await waitForListSort(listSort);
|
|
129
147
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
130
148
|
});
|
|
131
149
|
});
|
|
@@ -194,6 +212,72 @@ describe("use/useListSort", () => {
|
|
|
194
212
|
expect(deepUnref(listSorts.A.state)).toEqual(deepUnref(listSortA.state));
|
|
195
213
|
expect(deepUnref(listSorts.B.state)).toEqual(deepUnref(listSortB.state));
|
|
196
214
|
});
|
|
215
|
+
it("orderByRules can refer to relatedItem or calculatedItem", async () => {
|
|
216
|
+
const listInstance = useListInstance({
|
|
217
|
+
props: reactive({
|
|
218
|
+
crudArgs: { stream: "test_stream" },
|
|
219
|
+
listArgs: { user: 1 },
|
|
220
|
+
retrieveArgs: {
|
|
221
|
+
fields: ["id", "__str__", "name", "relatedItem", "calculatedItem"],
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
const relatedListInstance = useListInstance({
|
|
226
|
+
props: reactive({
|
|
227
|
+
crudArgs: { stream: "test_related_stream" },
|
|
228
|
+
listArgs: { user: 1 },
|
|
229
|
+
retrieveArgs: {
|
|
230
|
+
fields: ["id", "__str__", "name"],
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
const listRelated = useListRelated({
|
|
235
|
+
parentState: listInstance.state,
|
|
236
|
+
relatedObjectsRules: reactive({
|
|
237
|
+
relatedItemName: {
|
|
238
|
+
objects: relatedListInstance.state.objects,
|
|
239
|
+
pkKey: "relatedItem",
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
242
|
+
});
|
|
243
|
+
const listCalculated = useListCalculated({
|
|
244
|
+
parentState: listRelated.state,
|
|
245
|
+
calculatedObjectsRules: reactive({
|
|
246
|
+
calculatedItemName: (obj, relatedObj) => {
|
|
247
|
+
return obj.sameValue + ":" + relatedObj.relatedItemName?.oppositeOrder;
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
const orderByRules = reactive([
|
|
252
|
+
{ key: "calculatedItem.calculatedItemName", desc: false, localeCompare: false },
|
|
253
|
+
]);
|
|
254
|
+
const listSort = useListSort({
|
|
255
|
+
parentState: listCalculated.state,
|
|
256
|
+
orderByRules,
|
|
257
|
+
});
|
|
258
|
+
for (let i = 1; i <= 4; i++) {
|
|
259
|
+
listInstance.addListObject({
|
|
260
|
+
id: i,
|
|
261
|
+
name: `item${i}`,
|
|
262
|
+
relatedItem: i,
|
|
263
|
+
sameValue: "sameValue",
|
|
264
|
+
});
|
|
265
|
+
relatedListInstance.addListObject({
|
|
266
|
+
id: i,
|
|
267
|
+
name: `relatedItem${i}`,
|
|
268
|
+
oppositeOrder: 5 - i,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
await waitForListSort(listSort);
|
|
272
|
+
expect(listSort.state.order).toEqual(["4", "3", "2", "1"]);
|
|
273
|
+
orderByRules[0].key = "relatedItemName.name";
|
|
274
|
+
await waitForListSort(listSort);
|
|
275
|
+
expect(listSort.state.order).toEqual(["1", "2", "3", "4"]);
|
|
276
|
+
orderByRules[0].key = "relatedItemName.sameValue";
|
|
277
|
+
orderByRules[1] = { key: "calculatedItem.calculatedItemName", desc: false, localeCompare: false };
|
|
278
|
+
await waitForListSort(listSort);
|
|
279
|
+
expect(listSort.state.order).toEqual(["4", "3", "2", "1"]);
|
|
280
|
+
});
|
|
197
281
|
describe("useListSort/sortThrottleWait", () => {
|
|
198
282
|
it("respects throttle time prior to triggering", async () => {
|
|
199
283
|
setListSortDefaultOptions({
|
|
@@ -212,20 +296,31 @@ describe("use/useListSort", () => {
|
|
|
212
296
|
}
|
|
213
297
|
|
|
214
298
|
const testOrder1 = [];
|
|
215
|
-
const testOrder2 = ["
|
|
216
|
-
const testOrder3 = ["35", "15", "9"];
|
|
299
|
+
const testOrder2 = ["12", "15", "9"];
|
|
300
|
+
const testOrder3 = ["35", "12", "15", "9"];
|
|
301
|
+
const testOrder4 = ["35", "15", "9"];
|
|
217
302
|
|
|
218
303
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
|
|
219
|
-
await nextTick();
|
|
220
304
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
305
|
+
|
|
306
|
+
// wait for the original throttle to finish
|
|
307
|
+
await doAwaitTimeout(250);
|
|
308
|
+
|
|
309
|
+
expect(listSort.state.order).toEqual(testOrder2);
|
|
221
310
|
listInstance.addListObject(addObject);
|
|
222
|
-
await nextTick();
|
|
223
311
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
312
|
+
// trigger the leading edge of the throttle
|
|
313
|
+
await doAwaitTimeout(10);
|
|
314
|
+
expect(listSort.state.order).toEqual(testOrder3);
|
|
315
|
+
// trigger again before the 200ms throttle
|
|
224
316
|
listInstance.deleteListObject(12);
|
|
225
|
-
await nextTick();
|
|
226
|
-
expect(listSort.state.order).toEqual(testOrder2);
|
|
227
|
-
await doAwaitTimeout(200);
|
|
228
317
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
318
|
+
// this should trigger before the 200ms throttle
|
|
319
|
+
await doAwaitTimeout(100);
|
|
320
|
+
expect(listSort.state.order).toEqual(testOrder3);
|
|
321
|
+
// this should trigger after the 200ms throttle
|
|
322
|
+
await doAwaitTimeout(200);
|
|
323
|
+
expect(listSort.state.order).toEqual(testOrder4);
|
|
229
324
|
});
|
|
230
325
|
});
|
|
231
326
|
});
|
package/use/listRelated.js
CHANGED
|
@@ -58,6 +58,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
58
58
|
for (const objectKey of Object.keys(state.relatedObjects)) {
|
|
59
59
|
const relatedObjectsObject = state.relatedObjects[objectKey];
|
|
60
60
|
const originalObjectRef = toRef(parentState.objects, objectKey);
|
|
61
|
+
const relatedObjectRef = toRef(state.relatedObjects, objectKey);
|
|
61
62
|
let removedRuleKeys, addedRuleKeys;
|
|
62
63
|
if (!relatedObjectsRulesIsEmpty) {
|
|
63
64
|
({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
|
|
@@ -86,12 +87,30 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
86
87
|
const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
|
|
87
88
|
const ruleOrder = unref(state.relatedObjectsRules?.[addedRuleKey]?.order);
|
|
88
89
|
if (!ruleObjects || !rulePkKey) {
|
|
89
|
-
relatedObjectsObject[addedRuleKey]
|
|
90
|
+
if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
|
|
91
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
92
|
+
}
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
|
-
let value
|
|
95
|
+
let value;
|
|
96
|
+
if (rulePkKey.startsWith("relatedItem.")) {
|
|
97
|
+
value = get(unref(relatedObjectRef), rulePkKey.slice(12));
|
|
98
|
+
if (isUndefined(value)) {
|
|
99
|
+
// is the first level an array?
|
|
100
|
+
const firstLevelKey = rulePkKey.slice(12).split(".")[0];
|
|
101
|
+
const firstLevelItem = get(unref(relatedObjectRef), firstLevelKey);
|
|
102
|
+
if (isArray(firstLevelItem)) {
|
|
103
|
+
const restOfKey = rulePkKey.slice(12 + firstLevelKey.length + 1);
|
|
104
|
+
value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
value = get(unref(originalObjectRef), rulePkKey);
|
|
109
|
+
}
|
|
93
110
|
if (isUndefined(value)) {
|
|
94
|
-
relatedObjectsObject[addedRuleKey]
|
|
111
|
+
if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
|
|
112
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
113
|
+
}
|
|
95
114
|
return;
|
|
96
115
|
}
|
|
97
116
|
if (isArray(value)) {
|
|
@@ -114,7 +133,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
114
133
|
}
|
|
115
134
|
};
|
|
116
135
|
watch(
|
|
117
|
-
[toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef],
|
|
136
|
+
[toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef, relatedObjectRef],
|
|
118
137
|
relatedObjectsObjectWatchFn,
|
|
119
138
|
{
|
|
120
139
|
deep: true,
|
package/use/listSort.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { assignReactiveObject, keyDiff } from "../utils/index.js";
|
|
1
|
+
import { assignReactiveObject, keyDiff, loadingCombine } from "../utils/index.js";
|
|
2
2
|
import { listCalculatedStateKeys } from "./listCalculated.js";
|
|
3
3
|
import { listFilterStateKeys } from "./listFilter.js";
|
|
4
4
|
import { listInstanceStateKeys } from "./listInstance.js";
|
|
5
5
|
import { listRelatedStateKeys } from "./listRelated.js";
|
|
6
6
|
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
7
|
+
import { useWatchesRunning } from "./watchesRunning.js";
|
|
7
8
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
8
9
|
import get from "lodash-es/get.js";
|
|
9
10
|
import identity from "lodash-es/identity.js";
|
|
10
11
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
12
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
11
13
|
import isNull from "lodash-es/isNull.js";
|
|
12
14
|
import isUndefined from "lodash-es/isUndefined.js";
|
|
13
15
|
import throttle from "lodash-es/throttle.js";
|
|
14
16
|
import zip from "lodash-es/zip.js";
|
|
15
|
-
import { effectScope, reactive, toRef, unref, watch } from "vue";
|
|
17
|
+
import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
|
|
16
18
|
|
|
17
19
|
export const listSortStateKeys = [
|
|
18
20
|
"orderByRules",
|
|
@@ -21,6 +23,10 @@ export const listSortStateKeys = [
|
|
|
21
23
|
"sortCriteria",
|
|
22
24
|
"sortCriteriaEffectScopes",
|
|
23
25
|
"orderByDesc",
|
|
26
|
+
"sortCriteriaWatchRunning",
|
|
27
|
+
"sortWatchRunning",
|
|
28
|
+
"outstandingEffects",
|
|
29
|
+
// "running",
|
|
24
30
|
];
|
|
25
31
|
|
|
26
32
|
export const listSortFunctions = [];
|
|
@@ -56,6 +62,9 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
56
62
|
sortCriteria: {},
|
|
57
63
|
sortCriteriaEffectScopes: {},
|
|
58
64
|
orderByDesc: [],
|
|
65
|
+
sortCriteriaWatchRunning: false,
|
|
66
|
+
sortWatchRunning: false,
|
|
67
|
+
outstandingEffects: false,
|
|
59
68
|
});
|
|
60
69
|
const es = effectScope();
|
|
61
70
|
|
|
@@ -68,7 +77,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
68
77
|
delete state.sortCriteria[removedKey];
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
function addSortCriteria(object, key) {
|
|
80
|
+
function addSortCriteria(object, relatedObject, calculatedObject, key) {
|
|
72
81
|
const oldScope = state.sortCriteriaEffectScopes[key];
|
|
73
82
|
if (oldScope) {
|
|
74
83
|
oldScope.stop();
|
|
@@ -79,20 +88,34 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
79
88
|
state.sortCriteria[key] = [];
|
|
80
89
|
}
|
|
81
90
|
watch(
|
|
82
|
-
[object, toRef(state, "orderByRules")],
|
|
91
|
+
[object, relatedObject, calculatedObject, toRef(state, "orderByRules")],
|
|
83
92
|
() => {
|
|
84
93
|
const obj = unref(object);
|
|
94
|
+
const relatedObj = unref(relatedObject);
|
|
95
|
+
const calculatedObj = unref(calculatedObject);
|
|
85
96
|
const newSearchCriteria = [];
|
|
86
97
|
for (const orderByObj of state.orderByRules.filter(identity)) {
|
|
87
98
|
let newItem;
|
|
88
99
|
if (orderByObj.keyFn) {
|
|
89
100
|
newItem = orderByObj.keyFn(obj, state);
|
|
90
101
|
} else {
|
|
91
|
-
|
|
102
|
+
if (orderByObj.key.startsWith("relatedItem.")) {
|
|
103
|
+
newItem = get(relatedObj, orderByObj.key.slice(12));
|
|
104
|
+
} else if (orderByObj.key.startsWith("calculatedItem.")) {
|
|
105
|
+
newItem = get(calculatedObj, orderByObj.key.slice(15));
|
|
106
|
+
} else {
|
|
107
|
+
newItem = get(obj, orderByObj.key);
|
|
108
|
+
}
|
|
92
109
|
}
|
|
93
110
|
newSearchCriteria.push(newItem);
|
|
94
111
|
}
|
|
112
|
+
if (isEqual(newSearchCriteria, state.sortCriteria[key])) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
95
115
|
assignReactiveObject(state.sortCriteria[key], newSearchCriteria);
|
|
116
|
+
if (!state.outstandingEffects) {
|
|
117
|
+
state.outstandingEffects = true;
|
|
118
|
+
}
|
|
96
119
|
},
|
|
97
120
|
{
|
|
98
121
|
deep: true,
|
|
@@ -104,82 +127,98 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
104
127
|
}
|
|
105
128
|
|
|
106
129
|
function sortCriteriaWatch() {
|
|
107
|
-
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
try {
|
|
131
|
+
if (!state.orderByRules || !state.orderByRules.filter(identity).length) {
|
|
132
|
+
if (!isEmpty(state.sortCriteria)) {
|
|
133
|
+
for (const removedKey of Object.keys(state.sortCriteria)) {
|
|
134
|
+
removeSortCriteria(removedKey);
|
|
135
|
+
}
|
|
111
136
|
}
|
|
137
|
+
assignReactiveObject(state.order, cloneDeep(parentState.order));
|
|
138
|
+
assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
|
|
139
|
+
return;
|
|
112
140
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
removeSortCriteria(removedKey);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
es.run(() => {
|
|
123
|
-
for (const addedKey of addedKeys) {
|
|
124
|
-
const object = toRef(() => parentState.objects[addedKey]);
|
|
125
|
-
addSortCriteria(object, addedKey);
|
|
141
|
+
const { removedKeys, addedKeys } = keyDiff(
|
|
142
|
+
Object.keys(parentState.objects),
|
|
143
|
+
Object.keys(state.sortCriteria)
|
|
144
|
+
);
|
|
145
|
+
for (const removedKey of removedKeys) {
|
|
146
|
+
removeSortCriteria(removedKey);
|
|
126
147
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
|
|
149
|
+
es.run(() => {
|
|
150
|
+
for (const addedKey of addedKeys) {
|
|
151
|
+
const object = toRef(() => parentState.objects[addedKey]);
|
|
152
|
+
const relatedObj = toRef(() => parentState.relatedObjects?.[addedKey]);
|
|
153
|
+
const calculatedObj = toRef(() => parentState.calculatedObjects?.[addedKey]);
|
|
154
|
+
addSortCriteria(object, relatedObj, calculatedObj, addedKey);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
assignReactiveObject(
|
|
158
|
+
state.orderByDesc,
|
|
159
|
+
state.orderByRules.filter(identity).map((e) => e.desc || false)
|
|
160
|
+
);
|
|
161
|
+
} finally {
|
|
162
|
+
state.sortCriteriaWatchRunning = false;
|
|
163
|
+
}
|
|
132
164
|
}
|
|
133
165
|
|
|
134
166
|
function sortWatch() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
try {
|
|
168
|
+
if (!state.orderByRules || !state.orderByRules.length) {
|
|
169
|
+
assignReactiveObject(state.order, cloneDeep(parentState.order));
|
|
170
|
+
assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
140
173
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (orderByObj.desc) {
|
|
150
|
-
[x, y] = [y, x];
|
|
151
|
-
}
|
|
152
|
-
const isUndefinedX = isUndefined(x) || isNull(x);
|
|
153
|
-
const isUndefinedY = isUndefined(y) || isNull(y);
|
|
154
|
-
if (isUndefinedX && isUndefinedY) {
|
|
155
|
-
continue;
|
|
156
|
-
} else if (isUndefinedX) {
|
|
157
|
-
return -1;
|
|
158
|
-
} else if (isUndefinedY) {
|
|
159
|
-
return 1;
|
|
160
|
-
}
|
|
161
|
-
if (orderByObj.localeCompare) {
|
|
162
|
-
const strComp = collator.compare(x, y);
|
|
163
|
-
if (strComp) {
|
|
164
|
-
return strComp;
|
|
174
|
+
let idList = Object.keys(parentState.objects);
|
|
175
|
+
idList.sort((xKey, yKey) => {
|
|
176
|
+
const xCriteria = state.sortCriteria[xKey];
|
|
177
|
+
const yCriteria = state.sortCriteria[yKey];
|
|
178
|
+
for (let [x, y, orderByObj] of zip(xCriteria, yCriteria, state.orderByRules)) {
|
|
179
|
+
if (!orderByObj) {
|
|
180
|
+
continue;
|
|
165
181
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return -1;
|
|
182
|
+
if (orderByObj.desc) {
|
|
183
|
+
[x, y] = [y, x];
|
|
169
184
|
}
|
|
170
|
-
|
|
185
|
+
const isUndefinedX = isUndefined(x) || isNull(x);
|
|
186
|
+
const isUndefinedY = isUndefined(y) || isNull(y);
|
|
187
|
+
if (isUndefinedX && isUndefinedY) {
|
|
188
|
+
continue;
|
|
189
|
+
} else if (isUndefinedX) {
|
|
190
|
+
return -1;
|
|
191
|
+
} else if (isUndefinedY) {
|
|
171
192
|
return 1;
|
|
172
193
|
}
|
|
194
|
+
if (orderByObj.localeCompare) {
|
|
195
|
+
const strComp = collator.compare(x, y);
|
|
196
|
+
if (strComp) {
|
|
197
|
+
return strComp;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (x < y) {
|
|
201
|
+
return -1;
|
|
202
|
+
}
|
|
203
|
+
if (x > y) {
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
173
207
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
208
|
+
return 0;
|
|
209
|
+
});
|
|
210
|
+
assignReactiveObject(state.order, idList);
|
|
211
|
+
assignReactiveObject(state.objectsInOrder, idList.map((e) => parentState.objects[e]).filter(identity));
|
|
212
|
+
} finally {
|
|
213
|
+
state.sortWatchRunning = false;
|
|
214
|
+
state.outstandingEffects = false;
|
|
215
|
+
}
|
|
179
216
|
}
|
|
180
217
|
|
|
181
218
|
const throttledSortWatch = throttle(sortWatch, sortThrottleWait);
|
|
182
219
|
|
|
220
|
+
let watchesRunning = null;
|
|
221
|
+
|
|
183
222
|
es.run(() => {
|
|
184
223
|
for (const key of listInstanceStateKeys) {
|
|
185
224
|
if (["order", "objectsInOrder"].includes(key)) {
|
|
@@ -209,11 +248,23 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
209
248
|
deep: true,
|
|
210
249
|
immediate: true,
|
|
211
250
|
});
|
|
251
|
+
watchesRunning = useWatchesRunning({
|
|
252
|
+
triggerRefs: [
|
|
253
|
+
computed(() => (state.orderByRules && !isEmpty(state.orderByRules) ? parentState.loading : false)),
|
|
254
|
+
],
|
|
255
|
+
watchSentinelRefs: [toRef(state, "sortCriteriaWatchRunning"), toRef(state, "sortWatchRunning")],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
state.sortRunning = computed(() => loadingCombine(watchesRunning.state.running, state.outstandingEffects));
|
|
259
|
+
state.running = computed(() =>
|
|
260
|
+
loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentState.running)
|
|
261
|
+
);
|
|
212
262
|
});
|
|
213
263
|
|
|
214
264
|
return {
|
|
215
265
|
state,
|
|
216
266
|
parentState,
|
|
217
267
|
effectScope: es,
|
|
268
|
+
watchesRunning,
|
|
218
269
|
};
|
|
219
270
|
}
|
package/use/listSubscription.js
CHANGED
|
@@ -77,8 +77,8 @@ export function useListSubscriptions(args, listInstances = {}) {
|
|
|
77
77
|
/**
|
|
78
78
|
* `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`,
|
|
79
79
|
* causing the list to be re-fetched as needed and listening for updates to the list.
|
|
80
|
-
* @param {ListSubscriptionOptions} options
|
|
81
|
-
* @returns ListSubscription
|
|
80
|
+
* @param {ListSubscriptionOptions} options - the configuration options for the list subscription
|
|
81
|
+
* @returns {ListSubscription} - the list subscription
|
|
82
82
|
*/
|
|
83
83
|
export function useListSubscription({ listInstance, props, functions, keepOldPages = false }) {
|
|
84
84
|
if (!listInstance && !props) {
|
package/utils/watches.js
CHANGED
|
@@ -100,7 +100,7 @@ export class AwaitNot {
|
|
|
100
100
|
this.falseISW = new ImmediateStopWatch();
|
|
101
101
|
// prebuild the exception for a more useful stack.
|
|
102
102
|
if (this.timeout) {
|
|
103
|
-
this.timeoutError = new
|
|
103
|
+
this.timeoutError = new AwaitNotError("Timeout", "timeout");
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|