@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.
@@ -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
- if (form.getAttribute("object") === "pending")
36
- form.setAttribute("object", data.object[0]._id);
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
- let elements = formObject.types.get(data.type);
61
+ const typeElements = formObject?.types?.get(data.type);
39
62
 
40
- for (let [el, elData] of elements.entries()) {
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.$filter && data.$filter.query) {
111
- let sortedData = sort$in(data[type], data.$filter.query);
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
- // if rendered in server side skip
117
- if (el.hasAttribute("rendered")) return el.removeAttribute("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.$filter &&
254
- (data.$filter.overwrite || !isRendered)
312
+ data.filter &&
313
+ (data.filter.overwrite || !isRendered)
255
314
  ) {
256
315
  await element.renderValue(data);
257
316
  } else if (
258
- (data.$filter && data.$filter.loadmore) ||
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.$filter) {
345
+ if (data.filter) {
293
346
  let filterElement = filter.filters.get(element);
294
- if (filterElement) filterElement.index = data.$filter.index;
347
+ if (filterElement) filterElement.index = data.filter.index;
295
348
  }
296
349
 
297
- if (!data[type] || !Array.isArray(data[type]) || !data[type].length) return;
298
-
299
- const evt = new CustomEvent("fetchedData", { bubbles: true });
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
- let clonedData = { ...data };
305
- let renderedData = await element.getData();
306
- if (!renderedData || !renderedData[type]) {
307
- return;
308
- }
309
- for (let i = 0; i < clonedData[type].length; i++) {
310
- let index;
311
- if (type === "object") {
312
- index = renderedData[type].findIndex(
313
- (obj) => obj._id === clonedData[type][i]._id
314
- );
315
- } else {
316
- index = renderedData[type].findIndex(
317
- (obj) => obj.name === clonedData[type][i].name
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: fix: getObject as this is related to render or form, but filter could exist on an input which does not have getObject
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 = getObject(element);
337
- if (Array.isArray(localData[type])) localData = localData[type][0];
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
- let filter = await element.getFilter();
349
- if (filter && filter.query) {
350
- newData = newData.filter(item => queryData(item, filter.query));
413
+ if (localData == null) {
414
+ if (element.renderValue) localData = [];
415
+ else return;
351
416
  }
352
417
 
353
- if (!newData || !newData.length) return;
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
- } else if (localData[primaryKey] === newData[i][primaryKey]) {
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
- } else if (localData[primaryKey] === newData[primaryKey]) {
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
- let index;
396
- if (type === "object") {
397
- index = localData.findIndex((obj) => obj._id === newData._id);
398
- } else {
399
- index = localData.findIndex((obj) => obj.name === newData.name);
400
- }
401
-
402
- if (!data.$filter) data.$filter = {};
403
-
404
- if (data.method.endsWith(".delete")) {
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.$filter.remove = true;
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.$filter.create = true;
412
- if (filter && filter.sort) newData.isNewData = true;
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.$filter.update = true;
419
- data.$filter.currentIndex = index;
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((obj) => obj.isNewData);
505
+ index = localData.findIndex(
506
+ (item) => item?.[primaryKey] === primaryValue
507
+ );
429
508
  }
430
509
  }
431
510
 
432
511
  if (index >= 0) {
433
- if (data.$filter.currentIndex === index)
434
- delete data.$filter.currentIndex;
435
- data.$filter.index = index;
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 }