@cocreate/elements 1.43.2 → 1.44.2
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/workflows/automated.yml +22 -20
- package/package.json +9 -10
- package/src/crudHandler.js +282 -0
- package/src/dndHandler.js +155 -0
- package/src/getDataHandler.js +417 -0
- package/src/index.js +11 -1455
- package/src/initHandler.js +295 -0
- package/src/mapRegistry.js +6 -0
- package/src/setDataHandler.js +441 -0
- package/src/subscribeHandler.js +114 -0
- /package/src/{form.js → formHandler.js} +0 -0
- /package/src/{fetchSrc.js → srcHandler.js} +0 -0
- /package/src/{value.js → valueQuery.js} +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import Observer from "@cocreate/observer";
|
|
2
|
+
import CRUD from "@cocreate/crud-client";
|
|
3
|
+
import filter from "@cocreate/filter";
|
|
4
|
+
|
|
5
|
+
import { getAttributes } from "@cocreate/utils";
|
|
6
|
+
import { initDataKey, getData, getDataObject } from "./getDataHandler.js";
|
|
7
|
+
import { read, save } from "./crudHandler.js";
|
|
8
|
+
import { setData } from "./setDataHandler.js";
|
|
9
|
+
import { elements, forms, keys, debounce } from "./mapRegistry.js";
|
|
10
|
+
import { unsubscribe} from "./subscribeHandler.js";
|
|
11
|
+
|
|
12
|
+
const selector = "[storage], [database], [array], [render-json]";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initializes elements with specific CRUD attributes. If no parameter is provided, it queries and initializes all elements
|
|
16
|
+
* that have certain CRUD-related attributes. It can also initialize a single element or an array of elements.
|
|
17
|
+
*
|
|
18
|
+
* @param {(Element|Element[])} [element] - Optional. A single element or an array of elements to initialize.
|
|
19
|
+
*/
|
|
20
|
+
export async function init(element) {
|
|
21
|
+
if (
|
|
22
|
+
element &&
|
|
23
|
+
!(element instanceof HTMLCollection) &&
|
|
24
|
+
!(element instanceof NodeList) &&
|
|
25
|
+
!Array.isArray(element)
|
|
26
|
+
) {
|
|
27
|
+
element = [element];
|
|
28
|
+
} else if (!element) {
|
|
29
|
+
element = document.querySelectorAll(selector);
|
|
30
|
+
initSocket();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let promises = [];
|
|
34
|
+
for (let i = 0; i < element.length; i++) {
|
|
35
|
+
if (
|
|
36
|
+
elements.has(element[i]) ||
|
|
37
|
+
element[i].tagName === "FORM" ||
|
|
38
|
+
element[i].getAttribute("crud") === "false"
|
|
39
|
+
)
|
|
40
|
+
continue;
|
|
41
|
+
|
|
42
|
+
promises.push(
|
|
43
|
+
(async () => {
|
|
44
|
+
let data = await initElement(element[i]);
|
|
45
|
+
if (data) {
|
|
46
|
+
let dataKey = initDataKey(element[i], data);
|
|
47
|
+
return { string: dataKey.string, data };
|
|
48
|
+
}
|
|
49
|
+
})()
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let results = await Promise.all(promises);
|
|
54
|
+
let dataObjects = new Map();
|
|
55
|
+
|
|
56
|
+
for (let res of results) {
|
|
57
|
+
if (res) dataObjects.set(res.string, res.data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (dataObjects && dataObjects.size > 0) {
|
|
61
|
+
for (let key of dataObjects.keys()) {
|
|
62
|
+
let keyEntry = keys.get(key);
|
|
63
|
+
if (keyEntry) {
|
|
64
|
+
let { data, elements: mappedElements } = keyEntry;
|
|
65
|
+
read(Array.from(mappedElements.keys()), data, { string: key });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Prepares a single element for CRUD synchronization, attaching event listeners and filters.
|
|
73
|
+
*
|
|
74
|
+
* @param {Element} el
|
|
75
|
+
* @returns {Promise<Object|undefined>}
|
|
76
|
+
*/
|
|
77
|
+
export async function initElement(el) {
|
|
78
|
+
let data = getDataObject(el);
|
|
79
|
+
if (!data || !data.type) return;
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
data.object &&
|
|
83
|
+
data.object !== "pending" &&
|
|
84
|
+
!/^[0-9a-fA-F]{24}$/.test(data.object)
|
|
85
|
+
)
|
|
86
|
+
return;
|
|
87
|
+
|
|
88
|
+
if (!elements.has(el)) {
|
|
89
|
+
elements.set(el, "");
|
|
90
|
+
|
|
91
|
+
if (!el.elementsInputEvent) {
|
|
92
|
+
el.elementsInputEvent = true;
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName) ||
|
|
96
|
+
(el.hasAttribute("contenteditable") &&
|
|
97
|
+
el.getAttribute("contenteditable") !== "false") ||
|
|
98
|
+
el.contenteditable || el.value !== undefined || el.hasAttribute("value")
|
|
99
|
+
) {
|
|
100
|
+
if (el.tagName == "IFRAME") {
|
|
101
|
+
el = el.contentDocument.documentElement;
|
|
102
|
+
el.setAttribute("contenteditable", "true");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
el.addEventListener("input", function (e) {
|
|
106
|
+
if (el.pendingObject) return;
|
|
107
|
+
|
|
108
|
+
const { object, key, isRealtime, isCrdt } =
|
|
109
|
+
getAttributes(el);
|
|
110
|
+
if (!isRealtime || isRealtime === "false" || key === "_id")
|
|
111
|
+
return;
|
|
112
|
+
if (isCrdt == "true" && object && object !== "pending")
|
|
113
|
+
return;
|
|
114
|
+
if (e.detail && e.detail.skip == true) return;
|
|
115
|
+
if (data.type !== "object" && data[data.type] === "pending")
|
|
116
|
+
return;
|
|
117
|
+
if (!el.value && !el.hasAttribute("value") && el.contenteditable && !el.hasAttribute("contenteditable")) return;
|
|
118
|
+
save(el);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await filter.init(el);
|
|
125
|
+
|
|
126
|
+
if (el.getFilter) {
|
|
127
|
+
data.$filter = await el.getFilter();
|
|
128
|
+
|
|
129
|
+
el.setFilter = (filter) => {
|
|
130
|
+
data.$filter = filter;
|
|
131
|
+
el.hasRead = false;
|
|
132
|
+
read(el, data);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return data;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Removes elements from tracking maps and dispatches an unsubscription socket event
|
|
141
|
+
* when no remaining elements watch a specific dataKey scope.
|
|
142
|
+
*
|
|
143
|
+
* @param {Element|Element[]} element - Element(s) being unmounted or cleaned up
|
|
144
|
+
*/
|
|
145
|
+
export async function remove(element) {
|
|
146
|
+
if (
|
|
147
|
+
element &&
|
|
148
|
+
!(element instanceof HTMLCollection) &&
|
|
149
|
+
!Array.isArray(element)
|
|
150
|
+
)
|
|
151
|
+
element = [element];
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < element.length; i++) {
|
|
154
|
+
const el = element[i];
|
|
155
|
+
delete el.hasRead;
|
|
156
|
+
|
|
157
|
+
if (el.tagName === "FORM") {
|
|
158
|
+
let form = forms.get(el);
|
|
159
|
+
if (!form) return;
|
|
160
|
+
|
|
161
|
+
form = form.elements;
|
|
162
|
+
for (let formEl of form.keys()) {
|
|
163
|
+
let key = elements.get(formEl);
|
|
164
|
+
if (key) {
|
|
165
|
+
let keyEntry = keys.get(key);
|
|
166
|
+
if (keyEntry) {
|
|
167
|
+
keyEntry.elements.delete(formEl);
|
|
168
|
+
if (!keyEntry.elements.size) {
|
|
169
|
+
// Last element watching this key was removed -> trigger socket unsubscribe
|
|
170
|
+
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
171
|
+
keys.delete(key);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
elements.delete(formEl);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let key = elements.get(el);
|
|
179
|
+
if (key) {
|
|
180
|
+
let keyEntry = keys.get(key);
|
|
181
|
+
if (keyEntry) {
|
|
182
|
+
keyEntry.elements.delete(el);
|
|
183
|
+
if (!keyEntry.elements.size) {
|
|
184
|
+
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
185
|
+
keys.delete(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
elements.delete(el);
|
|
190
|
+
forms.delete(el);
|
|
191
|
+
} else {
|
|
192
|
+
let key = elements.get(el);
|
|
193
|
+
if (key) {
|
|
194
|
+
if (el.getFilter) filter.filters.delete(el);
|
|
195
|
+
|
|
196
|
+
let keyEntry = keys.get(key);
|
|
197
|
+
if (keyEntry) {
|
|
198
|
+
keyEntry.elements.delete(el);
|
|
199
|
+
elements.delete(el);
|
|
200
|
+
|
|
201
|
+
if (!keyEntry.elements.size) {
|
|
202
|
+
// Last element watching this key was removed -> trigger socket unsubscribe
|
|
203
|
+
unsubscribe(keyEntry.dataKey, keyEntry.data);
|
|
204
|
+
keys.delete(key);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
elements.delete(el);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let form = el.closest("form");
|
|
211
|
+
form = forms.get(form);
|
|
212
|
+
if (form) {
|
|
213
|
+
form.elements.delete(el);
|
|
214
|
+
if (!form.elements.size) forms.delete(form);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
elements.delete(el);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Initializes global CRUD socket response listeners.
|
|
225
|
+
*/
|
|
226
|
+
export function initSocket() {
|
|
227
|
+
const type = ["storage", "database", "array", "index", "object", "filter"];
|
|
228
|
+
const method = ["create", "update", "delete"];
|
|
229
|
+
|
|
230
|
+
for (let i = 0; i < type.length; i++) {
|
|
231
|
+
for (let j = 0; j < method.length; j++) {
|
|
232
|
+
const action = type[i] + "." + method[j];
|
|
233
|
+
|
|
234
|
+
CRUD.listen(action, function (data) {
|
|
235
|
+
if (
|
|
236
|
+
data.resolved &&
|
|
237
|
+
data.status === "received" &&
|
|
238
|
+
CRUD.socket.clientId === data.clientId
|
|
239
|
+
)
|
|
240
|
+
return;
|
|
241
|
+
if (data.rendered) return;
|
|
242
|
+
|
|
243
|
+
data.rendered = true;
|
|
244
|
+
|
|
245
|
+
setData(null, data);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Observer.init({
|
|
252
|
+
name: "CoCreateElementsChildList",
|
|
253
|
+
types: ["addedNodes"],
|
|
254
|
+
selector: selector,
|
|
255
|
+
callback: function (mutation) {
|
|
256
|
+
init([mutation.target]);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
Observer.init({
|
|
261
|
+
name: "CoCreateElementsRemovedNodes",
|
|
262
|
+
types: ["removedNodes"],
|
|
263
|
+
selector: selector,
|
|
264
|
+
callback: function (mutation) {
|
|
265
|
+
if (mutation.target.hasAttribute("render-clone")) return;
|
|
266
|
+
remove(mutation.target);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
Observer.init({
|
|
271
|
+
name: "CoCreateElementsAttributes",
|
|
272
|
+
types: ["attributes"],
|
|
273
|
+
attributeFilter: [
|
|
274
|
+
"organization_id",
|
|
275
|
+
"host",
|
|
276
|
+
"storage",
|
|
277
|
+
"database",
|
|
278
|
+
"array",
|
|
279
|
+
"index",
|
|
280
|
+
"object",
|
|
281
|
+
"key"
|
|
282
|
+
],
|
|
283
|
+
callback: function (mutation) {
|
|
284
|
+
let currentValue = mutation.target.getAttribute(mutation.attributeName);
|
|
285
|
+
if (currentValue !== mutation.oldValue) {
|
|
286
|
+
if (mutation.target.tagName === "FORM") return;
|
|
287
|
+
remove(mutation.target);
|
|
288
|
+
init([mutation.target]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
init();
|
|
294
|
+
|
|
295
|
+
export default { init, initElement, initSocket, remove };
|