@cocreate/elements 1.46.0 → 1.48.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/CONTRIBUTING.md +1 -1
- package/README.md +467 -44
- package/package.json +9 -8
- package/src/crudHandler.js +150 -46
- package/src/dndHandler.js +11 -8
- package/src/formHandler.js +225 -170
- package/src/getDataHandler.js +111 -53
- package/src/index.js +4 -3
- package/src/initHandler.js +96 -46
- package/src/mapRegistry.js +10 -1
- package/src/setDataHandler.js +163 -84
- package/src/socketHandler.js +328 -0
- package/src/srcHandler.js +68 -32
- package/src/subscribeHandler.js +64 -64
- package/test/deferred-src.test.cjs +11 -0
- package/test/form-enter.test.cjs +48 -0
- package/test/get-data.test.cjs +100 -0
- package/test/init-read.test.cjs +145 -0
- package/test/load-more.test.cjs +19 -0
- package/test/save-array.test.cjs +31 -0
- package/test/save-completion.test.cjs +36 -0
- package/test/set-data-type.test.cjs +119 -0
- package/test/ssr-response.test.cjs +108 -0
- package/webpack.config.js +1 -0
package/src/crudHandler.js
CHANGED
|
@@ -8,8 +8,11 @@ import CRUD from "@cocreate/crud-client";
|
|
|
8
8
|
|
|
9
9
|
import { elements, keys, debounce } from "./mapRegistry.js";
|
|
10
10
|
import { getDataObject } from "./getDataHandler.js";
|
|
11
|
-
import { setData } from "./setDataHandler.js";
|
|
12
|
-
import {
|
|
11
|
+
import { setData, setDataType } from "./setDataHandler.js";
|
|
12
|
+
import {
|
|
13
|
+
deriveSubscriptionKeys,
|
|
14
|
+
getSubscription
|
|
15
|
+
} from "./subscribeHandler.js";
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Reads data for one or more elements based on their dataKey scope.
|
|
@@ -26,8 +29,12 @@ export async function read(element, data, dataKey) {
|
|
|
26
29
|
data = { ...existingData.dataKey.object };
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
if (!dataKey || !data
|
|
30
|
-
|
|
32
|
+
if (!dataKey || !data?.type) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!data.filter && !data[data.type]) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
39
|
if (!(element instanceof HTMLCollection) && !Array.isArray(element))
|
|
33
40
|
element = [element];
|
|
@@ -42,12 +49,10 @@ export async function read(element, data, dataKey) {
|
|
|
42
49
|
if (
|
|
43
50
|
isRead !== "false" &&
|
|
44
51
|
!el.hasRead &&
|
|
45
|
-
(!data
|
|
52
|
+
(!data.filter || (data.filter && data.filter.isFilter !== false))
|
|
46
53
|
) {
|
|
47
54
|
el.hasRead = true;
|
|
48
55
|
delay.elements.set(el, true);
|
|
49
|
-
} else {
|
|
50
|
-
// console.log("read skipped", el);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -55,26 +60,39 @@ export async function read(element, data, dataKey) {
|
|
|
55
60
|
|
|
56
61
|
clearTimeout(delay.timer);
|
|
57
62
|
delay.timer = setTimeout(function () {
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
// Detach this batch before sending so reads queued while the request is
|
|
64
|
+
// in flight cannot mutate (or later delete) the same element map.
|
|
65
|
+
const els = Array.from(delay.elements?.keys() || []);
|
|
66
|
+
if (debounce.get(dataKey.string) === delay)
|
|
67
|
+
debounce.delete(dataKey.string);
|
|
68
|
+
clearTimeout(delay.timer);
|
|
69
|
+
delete delay.elements;
|
|
70
|
+
delete delay.timer;
|
|
71
|
+
|
|
72
|
+
if (!els.length) return;
|
|
73
|
+
|
|
74
|
+
if (!data.filter && data.type === "object") {
|
|
75
|
+
if (!data.object) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
60
78
|
else if (typeof data.object === "string")
|
|
61
79
|
data.object = { _id: data.object };
|
|
62
80
|
else if (Array.isArray(data.object)) {
|
|
63
81
|
if (data.object.length)
|
|
64
82
|
data.object = { _id: data.object[0]._id };
|
|
65
|
-
} else if (!data.object._id.match(/^[0-9a-fA-F]{24}$/))
|
|
83
|
+
} else if (!data.object._id.match(/^[0-9a-fA-F]{24}$/)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
66
86
|
}
|
|
67
87
|
|
|
68
88
|
data.method = data.type + ".read";
|
|
89
|
+
const subscription = getSubscription(data);
|
|
90
|
+
if (subscription) data.subscription = subscription.string;
|
|
69
91
|
|
|
70
|
-
CRUD.send(data).then((responseData) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
delete delay.elements;
|
|
75
|
-
delete delay.timer;
|
|
76
|
-
|
|
77
|
-
setData(els, responseData);
|
|
92
|
+
CRUD.send(data).then(async (responseData) => {
|
|
93
|
+
if (responseData) {
|
|
94
|
+
await setData(els, responseData);
|
|
95
|
+
}
|
|
78
96
|
});
|
|
79
97
|
}, 500);
|
|
80
98
|
}
|
|
@@ -111,8 +129,17 @@ export async function save(element, action) {
|
|
|
111
129
|
|
|
112
130
|
value = await element.getValue();
|
|
113
131
|
let key = element.getAttribute("key");
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
if (data.type === "object" && !data.object) {
|
|
133
|
+
if (key == "{}") {
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
data[data.type] = value.map((v) => ({ ...v }));
|
|
136
|
+
} else
|
|
137
|
+
data[data.type] = value;
|
|
138
|
+
} else {
|
|
139
|
+
data[data.type] = { [key]: value };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (typeof data[data.type] === "string")
|
|
116
143
|
if (key == "{}")
|
|
117
144
|
data[data.type] = { _id: data[data.type], ...value };
|
|
118
145
|
else data[data.type] = { _id: data[data.type], [key]: value };
|
|
@@ -183,19 +210,26 @@ export async function save(element, action) {
|
|
|
183
210
|
}
|
|
184
211
|
|
|
185
212
|
// Derive and attach subscriptionKeys array (contains specific document key and list key)
|
|
213
|
+
const subscription = getSubscription(data[i]);
|
|
214
|
+
if (subscription) data[i].subscription = subscription.string;
|
|
186
215
|
data[i].subscriptionKeys = deriveSubscriptionKeys(data[i]);
|
|
187
216
|
|
|
188
217
|
data[i] = await CRUD.send(data[i]);
|
|
218
|
+
const errors = data[i]?.error;
|
|
219
|
+
if (!data[i] || (Array.isArray(errors) ? errors.length > 0 : !!errors)) {
|
|
220
|
+
const message = Array.isArray(errors)
|
|
221
|
+
? errors.map((error) => error.message || String(error)).join("; ")
|
|
222
|
+
: errors?.message || String(errors || "Save failed");
|
|
223
|
+
throw new Error(message);
|
|
224
|
+
}
|
|
189
225
|
|
|
190
226
|
responseDataList.push(data[i]);
|
|
191
227
|
|
|
192
228
|
if (
|
|
193
229
|
data[i] &&
|
|
194
|
-
(data[i].method.endsWith(".create") ||
|
|
195
|
-
(data[i].type !== "object" &&
|
|
196
|
-
data[i].method.endsWith(".update")))
|
|
230
|
+
(data[i].method.endsWith(".create") || data[i].method.endsWith(".update"))
|
|
197
231
|
) {
|
|
198
|
-
setDataType(element, data[i], action);
|
|
232
|
+
await setDataType(element, data[i], action);
|
|
199
233
|
} else if (data[i]) {
|
|
200
234
|
if (action) {
|
|
201
235
|
action.element.dispatchEvent(
|
|
@@ -220,8 +254,49 @@ Actions.init([
|
|
|
220
254
|
{
|
|
221
255
|
name: "save",
|
|
222
256
|
endEvent: "saved",
|
|
223
|
-
callback: (action) => {
|
|
224
|
-
if (action.
|
|
257
|
+
callback: async (action) => {
|
|
258
|
+
if (action.element.hasAttribute("save-query")) {
|
|
259
|
+
const targets = queryElements({
|
|
260
|
+
element: action.element,
|
|
261
|
+
prefix: "save"
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (!targets.length) {
|
|
265
|
+
action.abort({
|
|
266
|
+
code: "save-target-not-found",
|
|
267
|
+
message: "The save target could not be found.",
|
|
268
|
+
selector: action.element.attributes.getNamedItem("save-query")?.value
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const result = [];
|
|
275
|
+
for (const target of targets) {
|
|
276
|
+
result.push(await save(target));
|
|
277
|
+
}
|
|
278
|
+
action.end({ result });
|
|
279
|
+
} catch (error) {
|
|
280
|
+
action.abort({
|
|
281
|
+
code: "save-failed",
|
|
282
|
+
message: "Unable to save the form.",
|
|
283
|
+
cause: error
|
|
284
|
+
});
|
|
285
|
+
console.error("Save action failed:", error);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await save(action.form || action.element, action);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
action.abort({
|
|
294
|
+
code: "save-failed",
|
|
295
|
+
message: "Unable to save the form.",
|
|
296
|
+
cause: error
|
|
297
|
+
});
|
|
298
|
+
console.error("Save action failed:", error);
|
|
299
|
+
}
|
|
225
300
|
}
|
|
226
301
|
},
|
|
227
302
|
{
|
|
@@ -238,34 +313,63 @@ Actions.init([
|
|
|
238
313
|
elements = [action.element];
|
|
239
314
|
}
|
|
240
315
|
|
|
316
|
+
const groups = new Map();
|
|
241
317
|
for (let i = 0; i < elements.length; i++) {
|
|
242
318
|
const data = getDataObject(elements[i]);
|
|
243
|
-
if (!data)
|
|
319
|
+
if (!data) continue;
|
|
244
320
|
data.method = data.type + ".delete";
|
|
245
321
|
|
|
246
|
-
if (
|
|
247
|
-
let selected = elements[i].querySelectorAll(".selected");
|
|
248
|
-
data[data.type] = [];
|
|
249
|
-
for (let j = 0; j < selected.length; j++) {
|
|
250
|
-
let attribute = selected[j].getAttribute(data.type);
|
|
251
|
-
if (attribute) {
|
|
252
|
-
attribute = attribute.split(",");
|
|
253
|
-
for (let k = 0; k < attribute.length; k++) {
|
|
254
|
-
if (data.type === "object")
|
|
255
|
-
data[data.type].push({ _id: attribute[k] });
|
|
256
|
-
else {
|
|
257
|
-
data[data.type].push(attribute[k]);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
} else if (
|
|
322
|
+
if (
|
|
263
323
|
data.type === "object" &&
|
|
264
324
|
typeof data[data.type] === "string"
|
|
265
325
|
)
|
|
266
326
|
data[data.type] = { _id: data[data.type] };
|
|
267
327
|
|
|
268
|
-
|
|
328
|
+
if (data.type !== "object") {
|
|
329
|
+
groups.set(Symbol(), data);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const groupKey = JSON.stringify({
|
|
334
|
+
storage: data.storage || "",
|
|
335
|
+
database: data.database || "",
|
|
336
|
+
array: data.array
|
|
337
|
+
});
|
|
338
|
+
const objects = Array.isArray(data.object)
|
|
339
|
+
? data.object
|
|
340
|
+
: [data.object];
|
|
341
|
+
let group = groups.get(groupKey);
|
|
342
|
+
|
|
343
|
+
if (!group) {
|
|
344
|
+
group = {
|
|
345
|
+
...data,
|
|
346
|
+
object: [],
|
|
347
|
+
objectIds: new Set()
|
|
348
|
+
};
|
|
349
|
+
groups.set(groupKey, group);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const object of objects) {
|
|
353
|
+
if (!object || !object._id || group.objectIds.has(object._id))
|
|
354
|
+
continue;
|
|
355
|
+
group.objectIds.add(object._id);
|
|
356
|
+
group.object.push({ _id: object._id });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
for (const data of groups.values()) {
|
|
361
|
+
if (data.objectIds) {
|
|
362
|
+
delete data.objectIds;
|
|
363
|
+
if (!data.object.length) continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Subscribe the requester at collection scope. A grouped object
|
|
367
|
+
// array is a delete payload, not a subscribable document key.
|
|
368
|
+
const subscriptionData = { ...data };
|
|
369
|
+
delete subscriptionData.object;
|
|
370
|
+
const subscription = getSubscription(subscriptionData);
|
|
371
|
+
if (subscription)
|
|
372
|
+
data.subscription = subscription.string;
|
|
269
373
|
data.subscriptionKeys = deriveSubscriptionKeys(data);
|
|
270
374
|
|
|
271
375
|
let response = await CRUD.send(data);
|
|
@@ -279,4 +383,4 @@ Actions.init([
|
|
|
279
383
|
}
|
|
280
384
|
]);
|
|
281
385
|
|
|
282
|
-
export default { read, save };
|
|
386
|
+
export default { read, save };
|
package/src/dndHandler.js
CHANGED
|
@@ -2,7 +2,8 @@ import { dotNotationToObject } from "@cocreate/utils";
|
|
|
2
2
|
import CRUD from "@cocreate/crud-client";
|
|
3
3
|
import Observer from "@cocreate/observer";
|
|
4
4
|
import { render, renderValue, renderedNodes } from "@cocreate/render";
|
|
5
|
-
import { elements, keys, debounce } from "./mapRegistry.js"
|
|
5
|
+
import { elements, keys, debounce } from "./mapRegistry.js";
|
|
6
|
+
import { getDataElements, getDataObject } from "./getDataHandler.js";
|
|
6
7
|
|
|
7
8
|
// TODO: has the potential to work on most of the crud elements specifically if the value is an object or an array
|
|
8
9
|
async function dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn) {
|
|
@@ -69,12 +70,12 @@ async function dndCrudData(element, parent, operator) {
|
|
|
69
70
|
|
|
70
71
|
function dndNewData(element, data) {
|
|
71
72
|
let dragData = {};
|
|
72
|
-
if (data
|
|
73
|
-
dndNewDataUpdate(dragData, data
|
|
73
|
+
if (data.filter.query) {
|
|
74
|
+
dndNewDataUpdate(dragData, data.filter.query);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
let sortName, sortDirection;
|
|
77
|
-
let sort = data
|
|
78
|
+
let sort = data.filter.sort;
|
|
78
79
|
if (sort && sort.length) {
|
|
79
80
|
for (let i = sort.length - 1; i >= 0; i--) {
|
|
80
81
|
if (typeof data[sort.key] === "number") {
|
|
@@ -89,7 +90,9 @@ function dndNewData(element, data) {
|
|
|
89
90
|
|
|
90
91
|
let renderedNode = renderedNodes.get(element);
|
|
91
92
|
if (renderedNode) {
|
|
92
|
-
|
|
93
|
+
const renderKeys = renderedNode.keys || {};
|
|
94
|
+
const activeKey = Object.keys(renderKeys).at(-1);
|
|
95
|
+
keyPath = activeKey ? renderKeys[activeKey].path : undefined;
|
|
93
96
|
clonesMap = renderedNode.template.clones;
|
|
94
97
|
clones = Array.from(clonesMap.values());
|
|
95
98
|
index = clones.indexOf(element);
|
|
@@ -141,9 +144,9 @@ Observer.init({
|
|
|
141
144
|
let renderedNode = renderedNodes.get(mutation.target);
|
|
142
145
|
if (!renderedNode) return;
|
|
143
146
|
|
|
144
|
-
if (!mutation.
|
|
145
|
-
let draggedEl = mutation.
|
|
146
|
-
let draggedFrom = mutation.
|
|
147
|
+
if (!mutation.move || mutation.move.isDescendant) return;
|
|
148
|
+
let draggedEl = mutation.move.movedElement;
|
|
149
|
+
let draggedFrom = mutation.move.from.parentElement;
|
|
147
150
|
let droppedEl =
|
|
148
151
|
mutation.target.nextElementSibling ||
|
|
149
152
|
mutation.target.previousElementSibling;
|