@cocreate/elements 1.46.0 → 2.0.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/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +753 -700
- package/CONTRIBUTING.md +6 -6
- package/README.md +469 -46
- package/demo/index.html +1 -1
- package/docs/index.html +7 -7
- package/package.json +12 -11
- package/src/crudHandler.js +153 -47
- package/src/dataHandler.js +7 -0
- package/src/dataObject.js +54 -0
- package/src/formHandler.js +226 -170
- package/src/formSelectors.js +1 -0
- package/src/getDataHandler.js +97 -94
- package/src/hrefHandler.js +104 -0
- package/src/iframeHandler.js +90 -0
- package/src/index.js +446 -12
- package/src/initHandler.js +96 -46
- package/src/mapRegistry.js +10 -1
- package/src/setDataHandler.js +171 -87
- package/src/socketHandler.js +328 -0
- package/src/socketSelectors.js +3 -0
- package/src/srcHandler.js +69 -32
- package/src/subscribeHandler.js +64 -64
- package/src/valueQuery.js +3 -4
- package/src/valueQuerySelector.js +1 -0
- 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/href-handler.test.cjs +132 -0
- package/test/init-read.test.cjs +145 -0
- package/test/lazy-loading.test.cjs +168 -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/dndHandler.js +0 -155
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/elements",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Fetch content into any html element by using html5 attributes to query data stored in CoCreate headless CMS. If data is changed it will instantly reflect in html tags as changes are broadcasted in realtime",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elements",
|
|
@@ -22,30 +22,31 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/
|
|
25
|
+
"url": "git+https://github.com/CoCreateJS/elements.git"
|
|
26
26
|
},
|
|
27
27
|
"author": "CoCreate LLC",
|
|
28
28
|
"license": "AGPL-3.0",
|
|
29
29
|
"bugs": {
|
|
30
|
-
"url": "https://github.com/
|
|
30
|
+
"url": "https://github.com/CoCreateJS/elements/issues"
|
|
31
31
|
},
|
|
32
|
-
"homepage": "https://
|
|
32
|
+
"homepage": "https://CoCreateJS.com/docs/elements",
|
|
33
33
|
"funding": {
|
|
34
34
|
"type": "GitHub Sponsors ❤",
|
|
35
|
-
"url": "https://github.com/sponsors/
|
|
35
|
+
"url": "https://github.com/sponsors/CoCreateJS"
|
|
36
36
|
},
|
|
37
37
|
"main": "./src/index.js",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@cocreate/actions": "^1.24.0",
|
|
40
|
-
"@cocreate/crud-client": "^1.
|
|
40
|
+
"@cocreate/crud-client": "^1.37.0",
|
|
41
41
|
"@cocreate/element-prototype": "^1.35.0",
|
|
42
|
-
"@cocreate/filter": "^1.
|
|
43
|
-
"@cocreate/observer": "^1.
|
|
44
|
-
"@cocreate/render": "^1.
|
|
45
|
-
"@cocreate/
|
|
42
|
+
"@cocreate/filter": "^1.37.0",
|
|
43
|
+
"@cocreate/observer": "^1.23.0",
|
|
44
|
+
"@cocreate/render": "^1.51.0",
|
|
45
|
+
"@cocreate/socket-client": "^1.43.0",
|
|
46
|
+
"@cocreate/utils": "^1.54.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@cocreate/webpack": "^1.
|
|
49
|
+
"@cocreate/webpack": "^1.9.0"
|
|
49
50
|
},
|
|
50
51
|
"allowScripts": {
|
|
51
52
|
"@cocreate/actions@1.21.4": true,
|
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 };
|
|
@@ -178,24 +205,33 @@ export async function save(element, action) {
|
|
|
178
205
|
array: data[i].array,
|
|
179
206
|
key: data[i].key,
|
|
180
207
|
object: data[i].object._id,
|
|
181
|
-
value
|
|
208
|
+
value,
|
|
209
|
+
realtime: true,
|
|
210
|
+
commit: true
|
|
182
211
|
});
|
|
183
212
|
}
|
|
184
213
|
|
|
185
214
|
// Derive and attach subscriptionKeys array (contains specific document key and list key)
|
|
215
|
+
const subscription = getSubscription(data[i]);
|
|
216
|
+
if (subscription) data[i].subscription = subscription.string;
|
|
186
217
|
data[i].subscriptionKeys = deriveSubscriptionKeys(data[i]);
|
|
187
218
|
|
|
188
219
|
data[i] = await CRUD.send(data[i]);
|
|
220
|
+
const errors = data[i]?.error;
|
|
221
|
+
if (!data[i] || (Array.isArray(errors) ? errors.length > 0 : !!errors)) {
|
|
222
|
+
const message = Array.isArray(errors)
|
|
223
|
+
? errors.map((error) => error.message || String(error)).join("; ")
|
|
224
|
+
: errors?.message || String(errors || "Save failed");
|
|
225
|
+
throw new Error(message);
|
|
226
|
+
}
|
|
189
227
|
|
|
190
228
|
responseDataList.push(data[i]);
|
|
191
229
|
|
|
192
230
|
if (
|
|
193
231
|
data[i] &&
|
|
194
|
-
(data[i].method.endsWith(".create") ||
|
|
195
|
-
(data[i].type !== "object" &&
|
|
196
|
-
data[i].method.endsWith(".update")))
|
|
232
|
+
(data[i].method.endsWith(".create") || data[i].method.endsWith(".update"))
|
|
197
233
|
) {
|
|
198
|
-
setDataType(element, data[i], action);
|
|
234
|
+
await setDataType(element, data[i], action);
|
|
199
235
|
} else if (data[i]) {
|
|
200
236
|
if (action) {
|
|
201
237
|
action.element.dispatchEvent(
|
|
@@ -220,8 +256,49 @@ Actions.init([
|
|
|
220
256
|
{
|
|
221
257
|
name: "save",
|
|
222
258
|
endEvent: "saved",
|
|
223
|
-
callback: (action) => {
|
|
224
|
-
if (action.
|
|
259
|
+
callback: async (action) => {
|
|
260
|
+
if (action.element.hasAttribute("save-query")) {
|
|
261
|
+
const targets = queryElements({
|
|
262
|
+
element: action.element,
|
|
263
|
+
prefix: "save"
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!targets.length) {
|
|
267
|
+
action.abort({
|
|
268
|
+
code: "save-target-not-found",
|
|
269
|
+
message: "The save target could not be found.",
|
|
270
|
+
selector: action.element.attributes.getNamedItem("save-query")?.value
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const result = [];
|
|
277
|
+
for (const target of targets) {
|
|
278
|
+
result.push(await save(target));
|
|
279
|
+
}
|
|
280
|
+
action.end({ result });
|
|
281
|
+
} catch (error) {
|
|
282
|
+
action.abort({
|
|
283
|
+
code: "save-failed",
|
|
284
|
+
message: "Unable to save the form.",
|
|
285
|
+
cause: error
|
|
286
|
+
});
|
|
287
|
+
console.error("Save action failed:", error);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
await save(action.form || action.element, action);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
action.abort({
|
|
296
|
+
code: "save-failed",
|
|
297
|
+
message: "Unable to save the form.",
|
|
298
|
+
cause: error
|
|
299
|
+
});
|
|
300
|
+
console.error("Save action failed:", error);
|
|
301
|
+
}
|
|
225
302
|
}
|
|
226
303
|
},
|
|
227
304
|
{
|
|
@@ -238,34 +315,63 @@ Actions.init([
|
|
|
238
315
|
elements = [action.element];
|
|
239
316
|
}
|
|
240
317
|
|
|
318
|
+
const groups = new Map();
|
|
241
319
|
for (let i = 0; i < elements.length; i++) {
|
|
242
320
|
const data = getDataObject(elements[i]);
|
|
243
|
-
if (!data)
|
|
321
|
+
if (!data) continue;
|
|
244
322
|
data.method = data.type + ".delete";
|
|
245
323
|
|
|
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 (
|
|
324
|
+
if (
|
|
263
325
|
data.type === "object" &&
|
|
264
326
|
typeof data[data.type] === "string"
|
|
265
327
|
)
|
|
266
328
|
data[data.type] = { _id: data[data.type] };
|
|
267
329
|
|
|
268
|
-
|
|
330
|
+
if (data.type !== "object") {
|
|
331
|
+
groups.set(Symbol(), data);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const groupKey = JSON.stringify({
|
|
336
|
+
storage: data.storage || "",
|
|
337
|
+
database: data.database || "",
|
|
338
|
+
array: data.array
|
|
339
|
+
});
|
|
340
|
+
const objects = Array.isArray(data.object)
|
|
341
|
+
? data.object
|
|
342
|
+
: [data.object];
|
|
343
|
+
let group = groups.get(groupKey);
|
|
344
|
+
|
|
345
|
+
if (!group) {
|
|
346
|
+
group = {
|
|
347
|
+
...data,
|
|
348
|
+
object: [],
|
|
349
|
+
objectIds: new Set()
|
|
350
|
+
};
|
|
351
|
+
groups.set(groupKey, group);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (const object of objects) {
|
|
355
|
+
if (!object || !object._id || group.objectIds.has(object._id))
|
|
356
|
+
continue;
|
|
357
|
+
group.objectIds.add(object._id);
|
|
358
|
+
group.object.push({ _id: object._id });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const data of groups.values()) {
|
|
363
|
+
if (data.objectIds) {
|
|
364
|
+
delete data.objectIds;
|
|
365
|
+
if (!data.object.length) continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Subscribe the requester at collection scope. A grouped object
|
|
369
|
+
// array is a delete payload, not a subscribable document key.
|
|
370
|
+
const subscriptionData = { ...data };
|
|
371
|
+
delete subscriptionData.object;
|
|
372
|
+
const subscription = getSubscription(subscriptionData);
|
|
373
|
+
if (subscription)
|
|
374
|
+
data.subscription = subscription.string;
|
|
269
375
|
data.subscriptionKeys = deriveSubscriptionKeys(data);
|
|
270
376
|
|
|
271
377
|
let response = await CRUD.send(data);
|
|
@@ -279,4 +385,4 @@ Actions.init([
|
|
|
279
385
|
}
|
|
280
386
|
]);
|
|
281
387
|
|
|
282
|
-
export default { read, save };
|
|
388
|
+
export default { read, save };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// CRUD runtime. Loaded by data selectors, CRUD actions, or an Elements API call.
|
|
2
|
+
import "@cocreate/element-prototype";
|
|
3
|
+
|
|
4
|
+
export { init } from "./initHandler.js";
|
|
5
|
+
export { read, save } from "./crudHandler.js";
|
|
6
|
+
export { getData } from "./getDataHandler.js";
|
|
7
|
+
export { setDataType } from "./setDataHandler.js";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getAttributes, checkValue } from "@cocreate/utils";
|
|
2
|
+
|
|
3
|
+
export const environment = localStorage.getItem("environment");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parses and normalizes HTML attributes from an element into a canonical CRUD data object.
|
|
7
|
+
* Infers execution type (object, index, array, database, storage, data) dynamically.
|
|
8
|
+
*
|
|
9
|
+
* @param {Element} element
|
|
10
|
+
* @returns {Object|undefined} Normalized data payload object
|
|
11
|
+
*/
|
|
12
|
+
export function getDataObject(element) {
|
|
13
|
+
const data = getAttributes(element);
|
|
14
|
+
const crudType = ["storage", "database", "array", "index", "object"];
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < crudType.length; i++) {
|
|
17
|
+
if (!checkValue(data[crudType[i]])) return;
|
|
18
|
+
|
|
19
|
+
if (data[crudType[i]] && data[crudType[i]].includes(",")) {
|
|
20
|
+
const array = data[crudType[i]].split(",");
|
|
21
|
+
data[crudType[i]] = [];
|
|
22
|
+
|
|
23
|
+
for (let j = 0; j < array.length; j++) {
|
|
24
|
+
const item = array[j].trim();
|
|
25
|
+
if (crudType[i] === "object") {
|
|
26
|
+
data[crudType[i]].push({ _id: item });
|
|
27
|
+
} else {
|
|
28
|
+
data[crudType[i]].push(item);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (data.object || data.object === "" || data.key || data.key === "") {
|
|
35
|
+
if (!data.array || (data.array && !data.array.length)) return;
|
|
36
|
+
data.type = "object";
|
|
37
|
+
} else if (data.index || data.index === "") {
|
|
38
|
+
if (!data.array || (data.array && !data.array.length)) return;
|
|
39
|
+
data.type = "index";
|
|
40
|
+
} else if (data.array || data.array === "") data.type = "array";
|
|
41
|
+
else if (data.database || data.database === "") data.type = "database";
|
|
42
|
+
else if (data.storage || data.storage === "") data.type = "storage";
|
|
43
|
+
else if (data.data) data.type = "data";
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
environment &&
|
|
47
|
+
!data.database &&
|
|
48
|
+
["array", "index", "object"].includes(data.type)
|
|
49
|
+
)
|
|
50
|
+
data.database = environment;
|
|
51
|
+
|
|
52
|
+
delete data.isRealtime;
|
|
53
|
+
return data;
|
|
54
|
+
}
|