@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/setDataHandler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dotNotationToObject,
|
|
3
3
|
queryData,
|
|
4
|
+
searchData,
|
|
4
5
|
sortData,
|
|
5
6
|
getAttributes,
|
|
6
7
|
checkValue,
|
|
@@ -8,21 +9,25 @@ import {
|
|
|
8
9
|
} from "@cocreate/utils";
|
|
9
10
|
import CRUD from "@cocreate/crud-client";
|
|
10
11
|
import filter from "@cocreate/filter";
|
|
11
|
-
import { elements, forms, keys } from "./mapRegistry.js"
|
|
12
|
+
import { elements, forms, keys } from "./mapRegistry.js";
|
|
13
|
+
import { getDataElements, getDataObject } from "./getDataHandler.js";
|
|
12
14
|
|
|
13
|
-
export function setDataType(element, data, action) {
|
|
15
|
+
export async function setDataType(element, data, action) {
|
|
14
16
|
// TODO: if an array name is updated, the attibute array="" needs to be updated.
|
|
15
17
|
|
|
16
18
|
if (!data) return;
|
|
17
19
|
|
|
20
|
+
const dataElements = new Set(getDataElements(data));
|
|
21
|
+
|
|
18
22
|
let form;
|
|
19
23
|
if (element.tagName === "FORM") {
|
|
20
24
|
form = element;
|
|
21
|
-
} else if (element.parentElement){
|
|
25
|
+
} else if (element.parentElement) {
|
|
22
26
|
form = element.parentElement.closest("form");
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
if (!form) {
|
|
30
|
+
dataElements.add(element);
|
|
26
31
|
if (data.type === "object") {
|
|
27
32
|
if (data.object?.[0]?._id) {
|
|
28
33
|
element.setAttribute("object", data.object[0]._id);
|
|
@@ -32,12 +37,31 @@ export function setDataType(element, data, action) {
|
|
|
32
37
|
}
|
|
33
38
|
} else {
|
|
34
39
|
let formObject = forms.get(form);
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const fieldsets = form.querySelectorAll('fieldset[object="pending"]');
|
|
41
|
+
for (const scope of [...fieldsets, form]) {
|
|
42
|
+
if (scope.getAttribute("object") === "pending") {
|
|
43
|
+
const object = data.object?.[0];
|
|
44
|
+
if (scope.hasAttribute("array")) {
|
|
45
|
+
const arrays = scope.getAttribute("array").split(",");
|
|
46
|
+
const objectArrays = Array.isArray(object?.$array)
|
|
47
|
+
? object.$array
|
|
48
|
+
: [object?.$array];
|
|
49
|
+
if (object?._id && arrays.some((array) => {
|
|
50
|
+
const name = array.trim();
|
|
51
|
+
return name !== "" && objectArrays.includes(name);
|
|
52
|
+
})) {
|
|
53
|
+
scope.setAttribute("object", object._id);
|
|
54
|
+
}
|
|
55
|
+
} else if (object?._id) {
|
|
56
|
+
scope.setAttribute("object", object._id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
37
60
|
|
|
38
|
-
|
|
61
|
+
const typeElements = formObject?.types?.get(data.type);
|
|
39
62
|
|
|
40
|
-
for (let [el, elData] of
|
|
63
|
+
for (let [el, elData] of typeElements?.entries() || []) {
|
|
64
|
+
dataElements.add(el);
|
|
41
65
|
if (data.type === "object") {
|
|
42
66
|
if (!elData.object || elData.object === "pending") {
|
|
43
67
|
elData.object = data.object[0]._id;
|
|
@@ -48,9 +72,10 @@ export function setDataType(element, data, action) {
|
|
|
48
72
|
el.setAttribute(data.type, data[data.type].name);
|
|
49
73
|
}
|
|
50
74
|
}
|
|
51
|
-
setData(Array.from(elements.keys()), data);
|
|
52
75
|
}
|
|
53
76
|
|
|
77
|
+
if (dataElements.size) await setData(Array.from(dataElements), data);
|
|
78
|
+
|
|
54
79
|
if (action) {
|
|
55
80
|
action.element.dispatchEvent(
|
|
56
81
|
new CustomEvent("saved", {
|
|
@@ -107,14 +132,22 @@ export async function setData(element, data) {
|
|
|
107
132
|
type = data.method.split(".")[0];
|
|
108
133
|
} else if (type == "key") type = "object";
|
|
109
134
|
|
|
110
|
-
if (data
|
|
111
|
-
let sortedData = sort$in(data[type], data
|
|
135
|
+
if (data.filter && data.filter.query) {
|
|
136
|
+
let sortedData = sort$in(data[type], data.filter.query);
|
|
112
137
|
if (sortedData && sortedData.length) data[type] = sortedData;
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
for (let el of element) {
|
|
116
|
-
//
|
|
117
|
-
if (el.hasAttribute("rendered"))
|
|
141
|
+
// Keep SSR content for this element without skipping its batch siblings.
|
|
142
|
+
if (el.hasAttribute("rendered")) {
|
|
143
|
+
el.removeAttribute("rendered");
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
data.method?.endsWith(".delete") &&
|
|
148
|
+
!(el.getFilter || el.renderValue)
|
|
149
|
+
)
|
|
150
|
+
continue;
|
|
118
151
|
if (el.hasAttribute("render-json")) {
|
|
119
152
|
console.log("render-json", "");
|
|
120
153
|
}
|
|
@@ -129,6 +162,14 @@ export async function setData(element, data) {
|
|
|
129
162
|
}
|
|
130
163
|
|
|
131
164
|
const { key, isRead, isUpdate, isListen, isCrdt } = getAttributes(el);
|
|
165
|
+
const hasRenderController =
|
|
166
|
+
el.hasAttribute("render-query") || el.hasAttribute("render-json");
|
|
167
|
+
|
|
168
|
+
// array/object without a key establishes CRUD identity only. Do not
|
|
169
|
+
// write the response into the element unless it explicitly controls a
|
|
170
|
+
// render target.
|
|
171
|
+
if (key === undefined && !hasRenderController) continue;
|
|
172
|
+
|
|
132
173
|
let elementType = el.getAttribute("type");
|
|
133
174
|
if (elementType !== "file" && (el.getFilter || el.renderValue))
|
|
134
175
|
await filterData(el, data, type, key);
|
|
@@ -188,6 +229,24 @@ export async function setData(element, data) {
|
|
|
188
229
|
}
|
|
189
230
|
|
|
190
231
|
async function filterData(element, data, type, key) {
|
|
232
|
+
if (key === "$length" && (data.error || !Array.isArray(data[type]))) return;
|
|
233
|
+
if (
|
|
234
|
+
data.method &&
|
|
235
|
+
!data.method.endsWith(".read") &&
|
|
236
|
+
(element.getFilter || element.renderValue)
|
|
237
|
+
) {
|
|
238
|
+
// A write payload is a document delta, not the result of this count's
|
|
239
|
+
// query. Keep the displayed count until its own filtered read completes.
|
|
240
|
+
// Re-read even for partial updates: a document may have left the query.
|
|
241
|
+
if (key === "$length" && element.getFilter && element.setFilter) {
|
|
242
|
+
if (data.error || !data[type]?.length) return;
|
|
243
|
+
element.setFilter(await element.getFilter());
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
await checkFilters(element, data, type);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
191
250
|
let operator = "",
|
|
192
251
|
value;
|
|
193
252
|
|
|
@@ -250,24 +309,18 @@ async function filterData(element, data, type, key) {
|
|
|
250
309
|
element.renderValue &&
|
|
251
310
|
data.method &&
|
|
252
311
|
data.method.endsWith(".read") &&
|
|
253
|
-
data
|
|
254
|
-
(data
|
|
312
|
+
data.filter &&
|
|
313
|
+
(data.filter.overwrite || !isRendered)
|
|
255
314
|
) {
|
|
256
315
|
await element.renderValue(data);
|
|
257
316
|
} else if (
|
|
258
|
-
(data
|
|
317
|
+
(data.filter && data.filter.loadMore) ||
|
|
259
318
|
(data.method && data.method.endsWith(".read") && isRendered)
|
|
260
319
|
) {
|
|
261
320
|
await loadMore(element, data, type);
|
|
262
|
-
} else if (
|
|
263
|
-
element.getFilter &&
|
|
264
|
-
data.method &&
|
|
265
|
-
!data.method.endsWith(".read")
|
|
266
|
-
) {
|
|
267
|
-
await checkFilters(element, data, type);
|
|
268
321
|
} else if (element.renderValue) {
|
|
269
322
|
if (key)
|
|
270
|
-
data = {[key]: data}
|
|
323
|
+
data = { [key]: data }
|
|
271
324
|
await element.renderValue(data);
|
|
272
325
|
} else if (key === "$length") {
|
|
273
326
|
element.setValue(data[type].length);
|
|
@@ -289,68 +342,84 @@ async function filterData(element, data, type, key) {
|
|
|
289
342
|
value = data[type][0];
|
|
290
343
|
} else if (data) element.setValue(data);
|
|
291
344
|
|
|
292
|
-
if (data
|
|
345
|
+
if (data.filter) {
|
|
293
346
|
let filterElement = filter.filters.get(element);
|
|
294
|
-
if (filterElement) filterElement.index = data
|
|
347
|
+
if (filterElement) filterElement.index = data.filter.index;
|
|
295
348
|
}
|
|
296
349
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
350
|
+
const evt = new CustomEvent("fetchedData", {
|
|
351
|
+
bubbles: true,
|
|
352
|
+
detail: data
|
|
353
|
+
});
|
|
300
354
|
element.dispatchEvent(evt);
|
|
355
|
+
|
|
356
|
+
if (!data[type] || !Array.isArray(data[type]) || !data[type].length) return;
|
|
301
357
|
}
|
|
302
358
|
|
|
303
359
|
async function loadMore(element, data, type, sort) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (index >= 0) {
|
|
322
|
-
clonedData[type].splice(i, 1);
|
|
323
|
-
i--; // Adjust the index to account for the removed item
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (clonedData[type].length > 0) {
|
|
327
|
-
await element.renderValue(clonedData);
|
|
360
|
+
const renderedData = await element.getData();
|
|
361
|
+
if (!renderedData || !renderedData[type]) return;
|
|
362
|
+
|
|
363
|
+
const key = type === "object" ? "_id" : "name";
|
|
364
|
+
const renderedKeys = new Set(renderedData[type].map(item => item[key]));
|
|
365
|
+
const records = data[type].filter(item => {
|
|
366
|
+
if (renderedKeys.has(item[key])) return false;
|
|
367
|
+
renderedKeys.add(item[key]);
|
|
368
|
+
return true;
|
|
369
|
+
});
|
|
370
|
+
if (records.length) {
|
|
371
|
+
await element.renderValue({
|
|
372
|
+
...data,
|
|
373
|
+
[type]: records,
|
|
374
|
+
filter: { ...data.filter, loadMore: true, overwrite: false }
|
|
375
|
+
});
|
|
328
376
|
}
|
|
329
377
|
}
|
|
330
378
|
|
|
331
379
|
async function checkFilters(element, data, type) {
|
|
380
|
+
if (type && data[type]) {
|
|
381
|
+
const applyUpdate = (record) => {
|
|
382
|
+
if (!record?.$update) return record;
|
|
383
|
+
const { $update, ...fields } = record;
|
|
384
|
+
return { ...fields, ...$update };
|
|
385
|
+
};
|
|
386
|
+
data = {
|
|
387
|
+
...data,
|
|
388
|
+
[type]: Array.isArray(data[type])
|
|
389
|
+
? data[type].map(applyUpdate)
|
|
390
|
+
: applyUpdate(data[type])
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
332
394
|
let localData;
|
|
333
395
|
if (!element.getData) {
|
|
334
|
-
// TODO:
|
|
396
|
+
// TODO: getDataObject is related to render or form, but filter could exist on an input which does not have getData
|
|
335
397
|
// TODO: generate an object of current element key: value to use with filter. because filter is used object is not defined
|
|
336
|
-
localData =
|
|
337
|
-
if (Array.isArray(localData[type]))
|
|
398
|
+
localData = getDataObject(element);
|
|
399
|
+
if (localData && Array.isArray(localData[type])) {
|
|
400
|
+
localData = localData[type][0];
|
|
401
|
+
}
|
|
338
402
|
} else localData = await element.getData();
|
|
339
403
|
|
|
340
404
|
if (!localData) return;
|
|
341
405
|
|
|
406
|
+
let filter = localData.filter
|
|
342
407
|
let newData;
|
|
343
408
|
if (type) {
|
|
344
409
|
localData = localData[type];
|
|
345
410
|
newData = data[type];
|
|
346
411
|
} else newData = data;
|
|
347
412
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
413
|
+
if (localData == null) {
|
|
414
|
+
if (element.renderValue) localData = [];
|
|
415
|
+
else return;
|
|
351
416
|
}
|
|
352
417
|
|
|
353
|
-
if (
|
|
418
|
+
if (
|
|
419
|
+
newData == null ||
|
|
420
|
+
(Array.isArray(newData) && newData.length === 0)
|
|
421
|
+
)
|
|
422
|
+
return;
|
|
354
423
|
|
|
355
424
|
if (Array.isArray(localData)) {
|
|
356
425
|
if (Array.isArray(newData)) {
|
|
@@ -361,6 +430,9 @@ async function checkFilters(element, data, type) {
|
|
|
361
430
|
checkIndex(element, data, localData, newData, type, filter);
|
|
362
431
|
}
|
|
363
432
|
} else {
|
|
433
|
+
if (data.method?.endsWith(".delete")) return;
|
|
434
|
+
|
|
435
|
+
let matched = false;
|
|
364
436
|
let primaryKey;
|
|
365
437
|
if (type === "object") {
|
|
366
438
|
primaryKey = "_id";
|
|
@@ -370,21 +442,27 @@ async function checkFilters(element, data, type) {
|
|
|
370
442
|
|
|
371
443
|
if (Array.isArray(newData)) {
|
|
372
444
|
for (let i = 0; i < newData.length; i++) {
|
|
445
|
+
if (!newData[i]) continue;
|
|
373
446
|
if (
|
|
374
447
|
typeof localData === "string" &&
|
|
375
448
|
localData === newData[i][primaryKey]
|
|
376
449
|
) {
|
|
377
450
|
localData = newData[i];
|
|
378
|
-
|
|
451
|
+
matched = true;
|
|
452
|
+
} else if (localData[primaryKey] && localData[primaryKey] === newData[i][primaryKey]) {
|
|
379
453
|
localData = dotNotationToObject(newData[i], localData);
|
|
454
|
+
matched = true;
|
|
380
455
|
}
|
|
381
456
|
}
|
|
382
457
|
} else if (typeof localData === "string" && localData === newData[primaryKey]) {
|
|
383
458
|
localData = newData;
|
|
384
|
-
|
|
459
|
+
matched = true;
|
|
460
|
+
} else if (localData[primaryKey] && localData[primaryKey] === newData[primaryKey]) {
|
|
385
461
|
localData = dotNotationToObject(newData, localData);
|
|
462
|
+
matched = true;
|
|
386
463
|
}
|
|
387
464
|
|
|
465
|
+
if (!matched) return;
|
|
388
466
|
if (element.renderValue) element.renderValue(data);
|
|
389
467
|
// render({ element, data, key: type });
|
|
390
468
|
else if (data) element.setValue(localData);
|
|
@@ -392,50 +470,51 @@ async function checkFilters(element, data, type) {
|
|
|
392
470
|
}
|
|
393
471
|
|
|
394
472
|
function checkIndex(element, data, localData, newData, type, filter) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
473
|
+
if (!newData) return;
|
|
474
|
+
const primaryKey = type === "object" ? "_id" : "name";
|
|
475
|
+
const primaryValue = newData[primaryKey];
|
|
476
|
+
let index = localData.findIndex(
|
|
477
|
+
(item) => item?.[primaryKey] === primaryValue
|
|
478
|
+
);
|
|
479
|
+
const isMatch =
|
|
480
|
+
(!filter?.query || queryData(newData, filter.query)) &&
|
|
481
|
+
(!filter?.search || searchData(newData, filter.search));
|
|
482
|
+
|
|
483
|
+
if (!data.filter) data.filter = {};
|
|
484
|
+
|
|
485
|
+
if (data.method.endsWith(".delete") || !isMatch) {
|
|
405
486
|
if (index === -1) return;
|
|
406
|
-
data
|
|
487
|
+
data.filter.remove = true;
|
|
407
488
|
localData.splice(index, 1);
|
|
408
489
|
} else {
|
|
409
490
|
if (data.method.endsWith(".create")) {
|
|
410
491
|
if (index === -1) {
|
|
411
|
-
data
|
|
412
|
-
|
|
413
|
-
else index = localData.length;
|
|
492
|
+
data.filter.create = true;
|
|
493
|
+
index = localData.length;
|
|
414
494
|
localData.push(newData);
|
|
415
495
|
}
|
|
416
496
|
} else {
|
|
417
497
|
if (index === -1) return;
|
|
418
|
-
data
|
|
419
|
-
data
|
|
498
|
+
data.filter.update = true;
|
|
499
|
+
data.filter.currentIndex = index;
|
|
420
500
|
localData[index] = dotNotationToObject(newData, localData[index]);
|
|
421
|
-
if (filter && filter.sort) {
|
|
422
|
-
localData[index].isNewData = true;
|
|
423
|
-
}
|
|
424
501
|
}
|
|
425
502
|
|
|
426
503
|
if (filter && filter.sort) {
|
|
427
504
|
localData = sortData(localData, filter.sort);
|
|
428
|
-
index = localData.findIndex(
|
|
505
|
+
index = localData.findIndex(
|
|
506
|
+
(item) => item?.[primaryKey] === primaryValue
|
|
507
|
+
);
|
|
429
508
|
}
|
|
430
509
|
}
|
|
431
510
|
|
|
432
511
|
if (index >= 0) {
|
|
433
|
-
if (data
|
|
434
|
-
delete data
|
|
435
|
-
data
|
|
512
|
+
if (data.filter.currentIndex === index)
|
|
513
|
+
delete data.filter.currentIndex;
|
|
514
|
+
data.filter.index = index;
|
|
436
515
|
if (element.renderValue) element.renderValue(data);
|
|
437
516
|
else if (data) element.setValue(localData);
|
|
438
517
|
}
|
|
439
518
|
}
|
|
440
519
|
|
|
441
|
-
export default { setDataType, setData }
|
|
520
|
+
export default { setDataType, setData }
|