@arrai-innovations/reactive-helpers 9.0.3 → 10.0.1
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/.idea/inspectionProfiles/Project_Default.xml +1 -0
- package/README.md +405 -65
- package/config/index.js +2 -0
- package/config/listCrud.js +37 -0
- package/config/objectCrud.js +45 -0
- package/docs.md +186 -24
- package/index.js +1 -0
- package/package.json +2 -2
- package/tests/unit/config/index.spec.js +18 -0
- package/tests/unit/config/listCrud.spec.js +107 -0
- package/tests/unit/config/objectCrud.spec.js +155 -0
- package/tests/unit/use/cancellableIntent.spec.js +1 -1
- package/tests/unit/use/listInstance.spec.js +5 -4
- package/tests/unit/use/listSort.spec.js +1 -1
- package/tests/unit/use/listSubscription.spec.js +3 -5
- package/tests/unit/utils/flattenPaths.spec.js +8 -0
- package/tests/unit/utils/keyDiff.spec.js +44 -1
- package/use/list.js +49 -15
- package/use/listCalculated.js +3 -0
- package/use/listFilter.js +40 -0
- package/use/listInstance.js +79 -27
- package/use/listRelated.js +41 -9
- package/use/listSort.js +71 -41
- package/use/listSubscription.js +57 -15
- package/use/object.js +26 -5
- package/use/objectCalculated.js +26 -39
- package/use/objectInstance.js +19 -35
- package/use/objectRelated.js +24 -34
- package/use/objectSubscription.js +25 -28
- package/utils/flattenPaths.js +11 -3
- package/utils/keyDiff.js +11 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCancellableIntent } from "../../../use/
|
|
1
|
+
import { useCancellableIntent } from "../../../use/cancellableIntent.js";
|
|
2
2
|
import { CancellableResolvable } from "../crudPromise.js";
|
|
3
3
|
import flushPromises from "flush-promises";
|
|
4
4
|
import { nextTick, reactive, ref } from "vue";
|
|
@@ -9,12 +9,13 @@ afterAll(() => {
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
const fields = ["id", "__str__", "name"];
|
|
12
|
-
describe
|
|
12
|
+
describe("use/listInstance.spec.js", function () {
|
|
13
13
|
let useListInstance, ListError, useListInstances, globalList;
|
|
14
14
|
beforeEach(async () => {
|
|
15
|
+
const listCrud = await import("../../../config/listCrud.js");
|
|
15
16
|
const imported = await import("../../../use/listInstance");
|
|
16
17
|
globalList = vi.fn();
|
|
17
|
-
|
|
18
|
+
listCrud.setListCrud({
|
|
18
19
|
list: globalList,
|
|
19
20
|
args: { stream: "test_stream" },
|
|
20
21
|
});
|
|
@@ -143,8 +144,8 @@ describe.skip("use/listInstance.spec.js", function () {
|
|
|
143
144
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
144
145
|
globalList.mockImplementation(() => new Promise(() => {}));
|
|
145
146
|
|
|
146
|
-
listInstance.list();
|
|
147
|
-
|
|
147
|
+
const firstPromise = listInstance.list();
|
|
148
|
+
expect(listInstance.list()).toBe(firstPromise);
|
|
148
149
|
|
|
149
150
|
expect(globalList).toHaveBeenCalledWith({
|
|
150
151
|
crudArgs: { stream: "test_stream" },
|
|
@@ -68,7 +68,7 @@ describe("use/useListSort", () => {
|
|
|
68
68
|
expect(listSort.state.order).toEqual([]);
|
|
69
69
|
expect(listSort.state.objectsInOrder).toEqual([]);
|
|
70
70
|
expect(listSort.state.sortCriteria).toEqual({});
|
|
71
|
-
expect(listSort.state.
|
|
71
|
+
expect(listSort.state.sortCriteriaEffectScopes).toEqual({});
|
|
72
72
|
expect(listSort.state.orderByDesc).toEqual([true, false]);
|
|
73
73
|
});
|
|
74
74
|
describe("addSortCriteria and removeSortCriteria", () => {
|
|
@@ -21,6 +21,7 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
21
21
|
crudSubscribeResolvable = [];
|
|
22
22
|
crudListResolvable.push(new CancellableResolvable());
|
|
23
23
|
crudSubscribeResolvable.push(new CancellableResolvable());
|
|
24
|
+
const listCrudModule = await import("../../../config/listCrud.js");
|
|
24
25
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
25
26
|
crudList = vi
|
|
26
27
|
.fn()
|
|
@@ -30,8 +31,9 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
30
31
|
crudListResolvable.push(newResolvable);
|
|
31
32
|
return newResolvable.promise;
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
+
listCrudModule.setListCrud({
|
|
34
35
|
list: crudList,
|
|
36
|
+
subscribe: crudSubscribe,
|
|
35
37
|
args: { stream: "test_stream" },
|
|
36
38
|
});
|
|
37
39
|
const listSubscriptionModule = await import("../../../use/listSubscription");
|
|
@@ -49,10 +51,6 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
49
51
|
passedSubscriptionEventCallback = subscriptionEventCallback;
|
|
50
52
|
return newResolvable.promise;
|
|
51
53
|
});
|
|
52
|
-
listSubscriptionModule.setListSubscriptionCrud({
|
|
53
|
-
subscribe: crudSubscribe,
|
|
54
|
-
args: { stream: "test_stream" },
|
|
55
|
-
});
|
|
56
54
|
|
|
57
55
|
useListInstance = listInstanceModule.useListInstance;
|
|
58
56
|
useListInstances = listInstanceModule.useListInstances;
|
|
@@ -15,6 +15,10 @@ describe("utils/flattenPaths", () => {
|
|
|
15
15
|
it("with a single level and a nested object and array", () => {
|
|
16
16
|
expect(flattenPaths({ a: 1, b: { c: [2] } })).toEqual(["a", "b.c[0]"]);
|
|
17
17
|
});
|
|
18
|
+
it("should limit to a specific depth", () => {
|
|
19
|
+
expect(flattenPaths({ a: 1, b: { c: [2] } }, { limit: 1 })).toEqual(["a"]);
|
|
20
|
+
expect(flattenPaths({ a: 1, b: { c: 1, d: [2], e: { f: 3 } } }, { limit: 2 })).toEqual(["a", "b.c"]);
|
|
21
|
+
});
|
|
18
22
|
});
|
|
19
23
|
describe("should work on arrays as the base", () => {
|
|
20
24
|
it("with a single level", () => {
|
|
@@ -29,6 +33,10 @@ describe("utils/flattenPaths", () => {
|
|
|
29
33
|
it("with a single level and a nested object and array", () => {
|
|
30
34
|
expect(flattenPaths([1, { c: [2] }])).toEqual(["[0]", "[1].c[0]"]);
|
|
31
35
|
});
|
|
36
|
+
it("should limit to a specific depth", () => {
|
|
37
|
+
expect(flattenPaths([1, { c: [2] }], { limit: 1 })).toEqual(["[0]"]);
|
|
38
|
+
expect(flattenPaths([1, [1, [2, [3], 4], 5]], { limit: 2 })).toEqual(["[0]", "[1][0]", "[1][2]"]);
|
|
39
|
+
});
|
|
32
40
|
});
|
|
33
41
|
it("should work on a real world example", () => {
|
|
34
42
|
const toBeFlattened = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keyDiff } from "../../../utils/keyDiff.js";
|
|
1
|
+
import { keyDiff, keyDiffDeep } from "../../../utils/keyDiff.js";
|
|
2
2
|
|
|
3
3
|
describe("keyDiff", function () {
|
|
4
4
|
describe("should return the difference between two arrays of object keys", function () {
|
|
@@ -64,4 +64,47 @@ describe("keyDiff", function () {
|
|
|
64
64
|
expect(removedKeys).toEqual(new Set(["c"]));
|
|
65
65
|
expect(sameKeys).toEqual(new Set(["a"]));
|
|
66
66
|
});
|
|
67
|
+
describe("keyDiffDeep", function () {
|
|
68
|
+
it("should return the difference between two objects", function () {
|
|
69
|
+
const newObj = {
|
|
70
|
+
a: 1,
|
|
71
|
+
b: {
|
|
72
|
+
c: 2,
|
|
73
|
+
},
|
|
74
|
+
c: 3,
|
|
75
|
+
};
|
|
76
|
+
const oldObj = {
|
|
77
|
+
a: 1,
|
|
78
|
+
b: 2,
|
|
79
|
+
c: 3,
|
|
80
|
+
};
|
|
81
|
+
// if using to patch, be sure to remove keys before adding them
|
|
82
|
+
expect(keyDiffDeep(newObj, oldObj)).toEqual({
|
|
83
|
+
addedKeys: new Set(["b.c"]),
|
|
84
|
+
removedKeys: new Set(["b"]),
|
|
85
|
+
sameKeys: new Set(["a", "c"]),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it("should be able to limit the depth", function () {
|
|
89
|
+
const newObj = {
|
|
90
|
+
a: 1,
|
|
91
|
+
b: {
|
|
92
|
+
c: { d: 1, e: 2, f: 3 },
|
|
93
|
+
},
|
|
94
|
+
g: 3,
|
|
95
|
+
};
|
|
96
|
+
const oldObj = {
|
|
97
|
+
a: 1,
|
|
98
|
+
b: {
|
|
99
|
+
c: {},
|
|
100
|
+
},
|
|
101
|
+
g: 3,
|
|
102
|
+
};
|
|
103
|
+
expect(keyDiffDeep(newObj, oldObj, { limit: 1 })).toEqual({
|
|
104
|
+
addedKeys: new Set(),
|
|
105
|
+
removedKeys: new Set(),
|
|
106
|
+
sameKeys: new Set(["a", "g"]),
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
67
110
|
});
|
package/use/list.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { useListCalculated } from "./listCalculated.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { useListCalculated, listCalculatedFunctions } from "./listCalculated.js";
|
|
2
|
+
import { useListFilter, listFilterFunctions } from "./listFilter.js";
|
|
3
|
+
import { useListInstance, listInstanceFunctions } from "./listInstance.js";
|
|
4
|
+
import { useListRelated, listRelatedFunctions } from "./listRelated.js";
|
|
5
|
+
import { useListSort, listSortFunctions } from "./listSort.js";
|
|
6
|
+
import { useListSubscription, listSubscriptionFunctions } from "./listSubscription.js";
|
|
5
7
|
import { usePagedListInstance } from "./paginatedListInstance.js";
|
|
6
8
|
import { effectScope, reactive, shallowReactive, shallowReadonly, toRef } from "vue";
|
|
7
9
|
|
|
@@ -14,7 +16,7 @@ export const useLists = (listArgs) => {
|
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
// the big brother of useObject, managing a chain of useList* instances.
|
|
17
|
-
export const useList = ({ props, functions, paged = false, keepOldPages = false }) => {
|
|
19
|
+
export const useList = ({ props, functions = {}, paged = false, keepOldPages = false, useTextSearch = false }) => {
|
|
18
20
|
const managed = shallowReactive({
|
|
19
21
|
listInstance: null,
|
|
20
22
|
listSubscription: null,
|
|
@@ -42,6 +44,7 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
42
44
|
listInstance: managed.listInstance,
|
|
43
45
|
});
|
|
44
46
|
managed.listSubscription.state.intendToList = toRef(props, "intendToList");
|
|
47
|
+
managed.listSubscription.state.intendToSubscribe = toRef(props, "intendToSubscribe");
|
|
45
48
|
|
|
46
49
|
managed.listRelated = useListRelated({
|
|
47
50
|
parentState: managed.listSubscription.state,
|
|
@@ -52,6 +55,23 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
52
55
|
parentState: managed.listRelated.state,
|
|
53
56
|
calculatedObjectsRules: toRef(props, "calculatedObjectsRules"),
|
|
54
57
|
});
|
|
58
|
+
|
|
59
|
+
managed.listFilter = useListFilter({
|
|
60
|
+
parentState: managed.listCalculated.state,
|
|
61
|
+
useTextSearch,
|
|
62
|
+
textSearchRules: toRef(props, "textSearchRules"),
|
|
63
|
+
textSearchValue: toRef(props, "textSearchValue"),
|
|
64
|
+
allowedValues: toRef(props, "allowedValues"),
|
|
65
|
+
excludedValues: toRef(props, "excludedValues"),
|
|
66
|
+
allowedFilter: toRef(props, "allowedFilter"),
|
|
67
|
+
excludedFilter: toRef(props, "excludedFilter"),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
managed.listSort = useListSort({
|
|
71
|
+
parentState: managed.listFilter.state,
|
|
72
|
+
orderByRules: toRef(props, "orderByRules"),
|
|
73
|
+
sortThrottleWait: functions.sortThrottleWait,
|
|
74
|
+
});
|
|
55
75
|
});
|
|
56
76
|
|
|
57
77
|
const clearError = (error) => {
|
|
@@ -59,18 +79,32 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
59
79
|
managed.listInstance.clearError(error);
|
|
60
80
|
};
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
const returnObject = reactive({
|
|
63
83
|
// we manage the keys on both of these, so hands off the root.
|
|
64
84
|
managed: shallowReadonly(managed),
|
|
65
|
-
state: managed.
|
|
66
|
-
list: managed.listInstance.list,
|
|
67
|
-
addListObject: managed.listInstance.addListObject,
|
|
68
|
-
updateListObject: managed.listInstance.updateListObject,
|
|
69
|
-
deleteListObject: managed.listInstance.deleteListObject,
|
|
70
|
-
clearList: managed.listInstance.clearList,
|
|
71
|
-
clearError,
|
|
72
|
-
getFakeId: managed.listInstance.getFakeId,
|
|
73
|
-
defaultPageCallback: managed.listInstance.defaultPageCallback,
|
|
85
|
+
state: managed.listSort.state,
|
|
74
86
|
effectScope: es,
|
|
75
87
|
});
|
|
88
|
+
const handledDuplicateFunctions = {
|
|
89
|
+
clearError,
|
|
90
|
+
};
|
|
91
|
+
for (const [source, fnNames] of [
|
|
92
|
+
[managed.listInstance, listInstanceFunctions],
|
|
93
|
+
[managed.listSubscription, listSubscriptionFunctions],
|
|
94
|
+
[managed.listRelated, listRelatedFunctions],
|
|
95
|
+
[managed.listCalculated, listCalculatedFunctions],
|
|
96
|
+
[managed.listFilter, listFilterFunctions],
|
|
97
|
+
[managed.listSort, listSortFunctions],
|
|
98
|
+
]) {
|
|
99
|
+
for (const fnName of fnNames) {
|
|
100
|
+
if (handledDuplicateFunctions[fnName]) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
returnObject[fnName] = source[fnName];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [fnName, fn] of Object.entries(handledDuplicateFunctions)) {
|
|
107
|
+
returnObject[fnName] = fn;
|
|
108
|
+
}
|
|
109
|
+
return returnObject;
|
|
76
110
|
};
|
package/use/listCalculated.js
CHANGED
|
@@ -13,6 +13,8 @@ export const listCalculatedStateKeys = [
|
|
|
13
13
|
"calculatedObjectsWatchRunning",
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
+
export const listCalculatedFunctions = [];
|
|
17
|
+
|
|
16
18
|
export function useListCalculateds(instances, args) {
|
|
17
19
|
for (const [key, value] of Object.entries(args)) {
|
|
18
20
|
useListCalculated({
|
|
@@ -130,6 +132,7 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
130
132
|
],
|
|
131
133
|
});
|
|
132
134
|
|
|
135
|
+
state.calculatedRunning = toRef(watchesRunning.state, "running");
|
|
133
136
|
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
134
137
|
|
|
135
138
|
onScopeDispose(() => {
|
package/use/listFilter.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
import { keyDiff } from "../utils/keyDiff.js";
|
|
2
|
+
import { listCalculatedStateKeys } from "./listCalculated.js";
|
|
3
|
+
import { listInstanceStateKeys } from "./listInstance.js";
|
|
4
|
+
import { listRelatedStateKeys } from "./listRelated.js";
|
|
5
|
+
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
2
6
|
import { useSearch } from "./search.js";
|
|
3
7
|
import get from "lodash-es/get.js";
|
|
4
8
|
import identity from "lodash-es/identity.js";
|
|
5
9
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
6
10
|
import { computed, effectScope, onScopeDispose, reactive, toRef, watch, watchEffect } from "vue";
|
|
7
11
|
|
|
12
|
+
export const listFilterStateKeys = [
|
|
13
|
+
"objectIndexes",
|
|
14
|
+
// override but not ours
|
|
15
|
+
// "objects",
|
|
16
|
+
"textSearchRules",
|
|
17
|
+
"textSearchValue",
|
|
18
|
+
"allowedValues",
|
|
19
|
+
"excludedValues",
|
|
20
|
+
"allowedFilter",
|
|
21
|
+
"excludedFilter",
|
|
22
|
+
"searched",
|
|
23
|
+
"searching",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const listFilterFunctions = [];
|
|
27
|
+
|
|
8
28
|
export function useListFilters(listFilterArgs, parentInstances) {
|
|
9
29
|
const filters = {};
|
|
10
30
|
for (const [key, value] of Object.entries(listFilterArgs)) {
|
|
@@ -32,6 +52,8 @@ export function useListFilter({
|
|
|
32
52
|
excludedValues,
|
|
33
53
|
allowedFilter,
|
|
34
54
|
excludedFilter,
|
|
55
|
+
searched: undefined,
|
|
56
|
+
searching: undefined,
|
|
35
57
|
});
|
|
36
58
|
|
|
37
59
|
const es = effectScope();
|
|
@@ -39,6 +61,21 @@ export function useListFilter({
|
|
|
39
61
|
let textSearchIndex;
|
|
40
62
|
|
|
41
63
|
es.run(() => {
|
|
64
|
+
for (const key of listInstanceStateKeys) {
|
|
65
|
+
if (key === "objects") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
state[key] = toRef(parentState, key);
|
|
69
|
+
}
|
|
70
|
+
for (const key of listSubscriptionStateKeys) {
|
|
71
|
+
state[key] = toRef(parentState, key);
|
|
72
|
+
}
|
|
73
|
+
for (const key of listRelatedStateKeys) {
|
|
74
|
+
state[key] = toRef(parentState, key);
|
|
75
|
+
}
|
|
76
|
+
for (const key of listCalculatedStateKeys) {
|
|
77
|
+
state[key] = toRef(parentState, key);
|
|
78
|
+
}
|
|
42
79
|
if (useTextSearch) {
|
|
43
80
|
textSearchIndex = useSearch();
|
|
44
81
|
textSearchIndex.state.search = toRef(state, "textSearchValue");
|
|
@@ -46,9 +83,11 @@ export function useListFilter({
|
|
|
46
83
|
state.searching = toRef(textSearchIndex.state, "searching");
|
|
47
84
|
}
|
|
48
85
|
|
|
86
|
+
// todo: computed is not the solution here for deep reactions
|
|
49
87
|
state.objectsInOrder = computed(() => parentState.order.map((id) => state.objects[id]).filter(identity));
|
|
50
88
|
state.order = computed(() => state.objectsInOrder.map((object) => `${object.id}`));
|
|
51
89
|
|
|
90
|
+
// todo: this huge watchEffect is fairly gross, but also doesn't watch deep similarly to computed
|
|
52
91
|
watchEffect(() => {
|
|
53
92
|
const allowedValuesEmpty = !state.allowedValues || isEmpty(state.allowedValues);
|
|
54
93
|
const excludedValuesEmpty = !state.excludedValues || isEmpty(state.excludedValues);
|
|
@@ -104,6 +143,7 @@ export function useListFilter({
|
|
|
104
143
|
if (useTextSearch) {
|
|
105
144
|
const stopIndexWatch = {};
|
|
106
145
|
|
|
146
|
+
// todo: this huge watchEffect is fairly gross, but also doesn't watch deep similarly to computed
|
|
107
147
|
watchEffect(() => {
|
|
108
148
|
const { removedKeys, addedKeys } = keyDiff(
|
|
109
149
|
Object.keys(parentState.objects),
|
package/use/listInstance.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getListCrud } from "../config/listCrud.js";
|
|
2
|
+
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
2
3
|
import { getFakeId } from "../utils/getFakeId.js";
|
|
3
4
|
import inspect from "browser-util-inspect";
|
|
4
|
-
import
|
|
5
|
-
import isFunction from "lodash-es/isFunction.js";
|
|
6
|
-
import { computed, effectScope, reactive, toRef, watchEffect } from "vue";
|
|
5
|
+
import { computed, effectScope, reactive, toRef } from "vue";
|
|
7
6
|
|
|
8
7
|
export class ListError extends Error {
|
|
9
8
|
constructor(message, code) {
|
|
@@ -13,11 +12,6 @@ export class ListError extends Error {
|
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const defaultCrud = {
|
|
17
|
-
args: {},
|
|
18
|
-
list: undefined,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
15
|
export const listInstanceStateKeys = [
|
|
22
16
|
"crud",
|
|
23
17
|
"retrieveArgs",
|
|
@@ -34,11 +28,39 @@ export const listInstanceStateKeys = [
|
|
|
34
28
|
"perPage",
|
|
35
29
|
];
|
|
36
30
|
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
export const listInstanceFunctions = [
|
|
32
|
+
"list",
|
|
33
|
+
"addListObject",
|
|
34
|
+
"updateListObject",
|
|
35
|
+
"deleteListObject",
|
|
36
|
+
"clearList",
|
|
37
|
+
"clearError",
|
|
38
|
+
"getFakeId",
|
|
39
|
+
"defaultPageCallback",
|
|
40
|
+
"pageCallback",
|
|
41
|
+
];
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The configuration options used to create a list instance.
|
|
45
|
+
* @typedef {object} ListInstanceOptions
|
|
46
|
+
* @property {object} props - props passed to the component
|
|
47
|
+
* @property {object} props.retrieveArgs - the arguments passed to the server
|
|
48
|
+
* @property {object} props.listArgs - the arguments passed to the server
|
|
49
|
+
* @property {object} props.crudArgs - implementation specific arguments
|
|
50
|
+
* @property {object} functions - optional. default implementation are used as set by `setListCrud`.
|
|
51
|
+
* @property {Function} functions.list - provide the implementation for the list function
|
|
52
|
+
* @property {Function} functions.subscribe - provide the implementation for the subscribe function
|
|
53
|
+
* @property {boolean} keepOldPages - if true, pages will not be cleared when defaultPageCallback is called. default is false.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/* eslint-disable jsdoc/check-types */
|
|
57
|
+
// types valid for jsdoc-to-markdown, which uses the strict jsdoc.app. Object shorthand syntax doesn't work.
|
|
58
|
+
/**
|
|
59
|
+
* A Vue composition function that creates multiple list instances, and returns them as an object.
|
|
60
|
+
* @param {Object.<string, ListInstanceOptions>} listInstanceArgs - each desired list instance options, keyed by an instance name.
|
|
61
|
+
* @returns {Object.<string, ListInstance>} - each list instance, keyed by the instance name.
|
|
62
|
+
*/
|
|
63
|
+
/* eslint-enable jsdoc/check-types */
|
|
42
64
|
export function useListInstances(listInstanceArgs) {
|
|
43
65
|
const instances = {};
|
|
44
66
|
for (const [key, value] of Object.entries(listInstanceArgs)) {
|
|
@@ -47,6 +69,48 @@ export function useListInstances(listInstanceArgs) {
|
|
|
47
69
|
return instances;
|
|
48
70
|
}
|
|
49
71
|
|
|
72
|
+
/**
|
|
73
|
+
* A reactive object that manages a list of objects, as returned by `useListInstance`.
|
|
74
|
+
* @typedef {object} ListInstanceState
|
|
75
|
+
* @property {object} crud - the crud functions
|
|
76
|
+
* @property {object} crud.args - the arguments passed to the crud functions
|
|
77
|
+
* @property {Function} crud.list - the list function
|
|
78
|
+
* @property {Function} crud.subscribe - the subscribe function
|
|
79
|
+
* @property {object} retrieveArgs - the arguments passed to the server
|
|
80
|
+
* @property {object} listArgs - the arguments passed to the server
|
|
81
|
+
* @property {Map} objects - the objects in the list
|
|
82
|
+
* @property {boolean} loading - true if the list is loading
|
|
83
|
+
* @property {boolean} errored - true if the list has errored
|
|
84
|
+
* @property {object} error - the error object
|
|
85
|
+
* @property {Array} objectsInOrder - the objects in the list in order
|
|
86
|
+
* @property {Array} order - the order of the objects in the list
|
|
87
|
+
* @property {number} totalRecords - the total number of records
|
|
88
|
+
* @property {number} totalPages - the total number of pages
|
|
89
|
+
* @property {number} perPage - the number of records per page
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {object} ListInstance
|
|
94
|
+
* @property {Function} list - subscribe to updates from the implementation
|
|
95
|
+
* @property {Function} addListObject - add an object to the list
|
|
96
|
+
* @property {Function} updateListObject - update an object in the list
|
|
97
|
+
* @property {Function} deleteListObject - delete an object from the list
|
|
98
|
+
* @property {Function} clearList - clear the list
|
|
99
|
+
* @property {Function} clearError - clear the error
|
|
100
|
+
* @property {Function} getFakeId - get a fake id
|
|
101
|
+
* @property {Function} defaultPageCallback - the default page callback
|
|
102
|
+
* @property {Function} pageCallback - the page callback
|
|
103
|
+
* @property {ListInstanceState} state - the list instance state
|
|
104
|
+
* @property {object} effectScope - a Vue effect scope
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `useListInstance` is a Vue composition function that manages a list of objects.
|
|
109
|
+
* It has the ability to retrieve the list from an implementation, or subscribe to updates from an implementation.
|
|
110
|
+
* It tracks the objects in the list, and their added order.
|
|
111
|
+
* @param {ListInstanceOptions} options - the options used to create the list instance
|
|
112
|
+
* @returns {ListInstance} - the list instance
|
|
113
|
+
*/
|
|
50
114
|
export function useListInstance({ props, functions = {}, keepOldPages = false }) {
|
|
51
115
|
if (!props) {
|
|
52
116
|
throw new ListError(`useListInstance requires props`);
|
|
@@ -98,20 +162,8 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
|
|
|
98
162
|
error: null,
|
|
99
163
|
});
|
|
100
164
|
const es = effectScope();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Object.assign(state.crud, cloneDeep(defaultCrud));
|
|
104
|
-
if (props.crudArgs) {
|
|
105
|
-
addOrUpdateReactiveObject(state.crud.args, props.crudArgs);
|
|
106
|
-
}
|
|
107
|
-
for (const [key, value] of Object.entries(functions)) {
|
|
108
|
-
if (isFunction(value) && key in state.crud) {
|
|
109
|
-
state.crud[key] = value;
|
|
110
|
-
} else {
|
|
111
|
-
throw ListError(`Invalid function "${key}" for useListInstance: invalid key or not a function.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
});
|
|
165
|
+
|
|
166
|
+
getListCrud(state.crud, { props, functions });
|
|
115
167
|
|
|
116
168
|
const defaultPageCallback = (newObjects) => {
|
|
117
169
|
// with keepOldPages, you are responsible for clearing the list as needed
|
package/use/listRelated.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { keyDiff
|
|
1
|
+
import { keyDiff } from "../utils/keyDiff.js";
|
|
2
|
+
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
2
3
|
import { listInstanceStateKeys } from "./listInstance.js";
|
|
3
4
|
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
4
5
|
import { useWatchesRunning } from "./watchesRunning.js";
|
|
5
6
|
import get from "lodash-es/get.js";
|
|
7
|
+
import identity from "lodash-es/identity.js";
|
|
6
8
|
import isArray from "lodash-es/isArray.js";
|
|
7
9
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
10
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
8
11
|
import isUndefined from "lodash-es/isUndefined.js";
|
|
9
12
|
import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
|
|
10
13
|
|
|
@@ -15,6 +18,8 @@ export const listRelatedStateKeys = [
|
|
|
15
18
|
"relatedObjectsParentStateObjectsWatchRunning",
|
|
16
19
|
];
|
|
17
20
|
|
|
21
|
+
export const listRelatedFunctions = [];
|
|
22
|
+
|
|
18
23
|
export function useListRelateds(instances, args) {
|
|
19
24
|
for (const [key, value] of Object.entries(args)) {
|
|
20
25
|
useListRelated({
|
|
@@ -52,7 +57,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
52
57
|
const relatedObjectsRulesIsEmpty = !state.relatedObjectsRules || isEmpty(state.relatedObjectsRules);
|
|
53
58
|
for (const objectKey of Object.keys(state.relatedObjects)) {
|
|
54
59
|
const relatedObjectsObject = state.relatedObjects[objectKey];
|
|
55
|
-
const
|
|
60
|
+
const originalObjectRef = toRef(parentState.objects, objectKey);
|
|
56
61
|
let removedRuleKeys, addedRuleKeys;
|
|
57
62
|
if (!relatedObjectsRulesIsEmpty) {
|
|
58
63
|
({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
|
|
@@ -74,22 +79,48 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
74
79
|
}
|
|
75
80
|
relatedObjectsEffectScopes[objectKey].run(() => {
|
|
76
81
|
for (const addedRuleKey of addedRuleKeys) {
|
|
77
|
-
relatedObjectsObject[addedRuleKey] =
|
|
82
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
83
|
+
const relatedObjectsObjectWatchFn = () => {
|
|
78
84
|
// deal with computed objects being passed.
|
|
79
85
|
const ruleObjects = unref(state.relatedObjectsRules?.[addedRuleKey]?.objects);
|
|
80
86
|
const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
|
|
87
|
+
const ruleOrder = unref(state.relatedObjectsRules?.[addedRuleKey]?.order);
|
|
81
88
|
if (!ruleObjects || !rulePkKey) {
|
|
82
|
-
|
|
89
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
90
|
+
return;
|
|
83
91
|
}
|
|
84
|
-
|
|
92
|
+
let value = get(unref(originalObjectRef), rulePkKey);
|
|
85
93
|
if (isUndefined(value)) {
|
|
86
|
-
|
|
94
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
95
|
+
return;
|
|
87
96
|
}
|
|
88
97
|
if (isArray(value)) {
|
|
89
|
-
|
|
98
|
+
// the related list could be sorted differently than the original list.
|
|
99
|
+
if (ruleOrder?.length) {
|
|
100
|
+
value = value.filter(identity);
|
|
101
|
+
const indexById = Object.fromEntries(ruleOrder.map((e, i) => [e, i]));
|
|
102
|
+
value.sort((a, b) => {
|
|
103
|
+
const aIndex = indexById[a];
|
|
104
|
+
const bIndex = indexById[b];
|
|
105
|
+
return aIndex - bIndex;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
value = value.map((e) => ruleObjects[e]).filter(identity);
|
|
109
|
+
} else {
|
|
110
|
+
value = ruleObjects[value];
|
|
111
|
+
}
|
|
112
|
+
if (!isEqual(value, relatedObjectsObject[addedRuleKey])) {
|
|
113
|
+
relatedObjectsObject[addedRuleKey] = value;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
watch(
|
|
117
|
+
[toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef],
|
|
118
|
+
relatedObjectsObjectWatchFn,
|
|
119
|
+
{
|
|
120
|
+
deep: true,
|
|
121
|
+
immediate: true,
|
|
90
122
|
}
|
|
91
|
-
|
|
92
|
-
});
|
|
123
|
+
);
|
|
93
124
|
}
|
|
94
125
|
});
|
|
95
126
|
}
|
|
@@ -130,6 +161,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
130
161
|
],
|
|
131
162
|
});
|
|
132
163
|
|
|
164
|
+
state.relatedRunning = toRef(watchesRunning.state, "running");
|
|
133
165
|
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
134
166
|
|
|
135
167
|
onScopeDispose(() => {
|