@cocreate/elements 1.17.33 → 1.19.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/src/index.js CHANGED
@@ -1,206 +1,937 @@
1
- import observer from '@cocreate/observer';
2
- import action from '@cocreate/actions';
3
- import crud from '@cocreate/crud-client';
4
- import ccfilter from '@cocreate/filter';
1
+ /********************************************************************************
2
+ * Copyright (C) 2023 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ ********************************************************************************/
17
+
18
+ // Commercial Licensing Information:
19
+ // For commercial use of this software without the copyleft provisions of the AGPLv3,
20
+ // you must obtain a commercial license from CoCreate LLC.
21
+ // For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
22
+
23
+ import Observer from '@cocreate/observer';
24
+ import Actions from '@cocreate/actions';
25
+ import CRUD from '@cocreate/crud-client';
26
+ import { dotNotationToObject, queryData, sortData, getAttributes, getAttributeNames, checkValue } from '@cocreate/utils';
27
+ import '@cocreate/filter';
28
+ import { render } from '@cocreate/render';
5
29
  import '@cocreate/element-prototype';
6
30
  import './fetchSrc';
31
+ import { reset } from './form'
32
+
33
+ const selector = "[storage], [database], [array], [render-json]";
34
+ const elements = new Map();
35
+ const keys = new Map();
36
+ const forms = new Map(); // form, [elements]
37
+ const debounce = new Map();
38
+
39
+ function init(element) {
40
+ if (element && !(element instanceof HTMLCollection) && !Array.isArray(element))
41
+ element = [element]
42
+ else if (!element) {
43
+ element = document.querySelectorAll(selector)
44
+ initSocket();
45
+ }
7
46
 
47
+ let dataObjects = new Map();
48
+ for (let i = 0; i < element.length; i++) {
49
+ if (elements.has(element[i]) || element.tagName === 'FORM')
50
+ continue
8
51
 
9
- const selector = "[collection][name]:not(cocreate-select, link), input, textarea, select, [contenteditable]";
10
- const initializing = new Map();
11
- const els = new Map();
52
+ let data = initElement(element[i]);
53
+ if (data) {
54
+ let dataKey = initDataKey(element[i], data)
55
+ dataObjects.set(dataKey.string, data)
56
+ }
57
+ }
12
58
 
13
- function init() {
14
- let elements = document.querySelectorAll(selector);
15
- __initSocket();
16
- initElements(elements);
59
+ if (dataObjects && dataObjects.size > 0) {
60
+ for (let key of dataObjects.keys()) {
61
+ let { elements, data } = keys.get(key)
62
+ read(Array.from(elements.keys()), data, { string: key });
63
+ }
64
+ }
17
65
  }
18
66
 
19
- function initElements(elements) {
20
- let documents = new Map();
21
- // let els = []
22
- for (let element of elements) {
23
- let doc = initElement(element);
24
- if (doc) {
25
- let key = JSON.stringify(doc)
26
- let item = documents.get(key);
27
- if (item)
28
- doc.elements = [...item.elements, element]
29
- else
30
- doc.elements = [element]
31
-
32
- // let collection = doc.collection;
33
- // let document_id = doc.document_id;
34
- // let filter = doc.filter;
35
- let initialize = initializing.get(element)
36
- if (!initialize) {
37
- initializing.set(element, key);
38
- documents.set(key, doc);
39
- // els.set(key, doc);
40
- // els.push(element)
41
- } else {
42
- // if (document_id && initialize.document_id != document_id || filter && JSON.stringify(initialize.filter) != JSON.stringify(filter)){
43
- if (initialize != key) {
44
- initializing.set(element, key);
45
- documents.set(key, doc);
46
- // els.set(key, doc);
47
- // els.push(element)
48
- }
67
+ function initElement(el) {
68
+ if (el.hasAttribute('render-json')) {
69
+ // TODO find the json template in the text or attributes
70
+ // if found add the node as an element, if the element has crud attributes also add as the element
71
+ for (let attribute of el.attributes) {
72
+ if (attribute.match(/{{(.*?)}}/)) {
73
+ let value = renderValue(attribute, undefined, attribute.value)
74
+ if (value)
75
+ attribute.value = value
49
76
  }
50
77
  }
78
+ if (el.innerHTML.match(/{{(.*?)}}/)) {
79
+ let value = renderValue(el, undefined, el.innerHTML)
80
+ if (value)
81
+ attribute.value = value
82
+ }
83
+
84
+ // if (el.textContent.match(/{{(.*?)}}/)) {
85
+ // let value = renderValue(el, undefined, el.textContent)
86
+ // if (value)
87
+ // attribute.value = value
88
+ // }
89
+
90
+ // let Data = JSON.parse(match[1]);
91
+ // Data.method = read.object
92
+ // CRUD.send(Data)
93
+
51
94
  }
52
95
 
53
- read(documents);
54
- }
96
+ // if (el.closest('.template')) return;
55
97
 
56
- function initElement(el) {
57
- __initEvents(el);
98
+ let data = getObject(el);
99
+ if (!data || !data.type) return
100
+
101
+ if (!elements.has(el)) {
102
+ elements.set(el, '')
103
+
104
+ if (['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName)
105
+ || (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false')
106
+ || el.contenteditable) {
107
+ el.addEventListener('input', function (e) {
108
+ const { object, key, isRealtime, isCrdt } = getAttributes(el);
109
+ if (isRealtime == "false" || key == "_id") return;
58
110
 
59
- if (el.closest('.template')) return;
111
+ if (isCrdt == "true" && object && object !== 'pending') return
112
+ if (object && e.detail && e.detail.skip == true) return;
113
+ if (data.type !== 'object' && data[data.type] === 'pending') return
60
114
 
61
- // if rendered in server side skip
62
- if (el.hasAttribute('rendered')) {
63
- el.removeAttribute('rendered');
115
+ save(el);
116
+ });
117
+ }
118
+ }
119
+
120
+ if (el.getFilter) {
121
+ data.filter = el.getFilter();
122
+ el.setFilter = (filter) => {
123
+ data.filter = filter
124
+ let dataKey = initDataKey(el, data)
125
+ read(el, data, dataKey)
126
+ }
127
+ }
128
+
129
+ const isRead = el.getAttribute('read');
130
+ if (isRead === 'false')
64
131
  return;
132
+
133
+ return data;
134
+ }
135
+
136
+ function getObject(element) {
137
+ const data = getAttributes(element);
138
+ const crudType = ['storage', 'database', 'array', 'index', 'object']
139
+
140
+ for (let i = 0; i < crudType.length; i++) {
141
+ if (!checkValue(data[crudType[i]]))
142
+ return
143
+
144
+ if (data[crudType[i]] && data[crudType[i]].includes(",")) {
145
+ const array = data[crudType[i]].split(',');
146
+ data[crudType[i]] = []
147
+
148
+ for (let j = 0; j < array.length; j++) {
149
+ array[i].trim()
150
+ if (crudType[i] === 'object') {
151
+ data[crudType[i]].push({ _id: array[j] })
152
+ } else {
153
+ data[crudType[i]].push(array[j])
154
+ }
155
+ }
156
+ }
65
157
  }
66
158
 
67
- const { collection, document_id, name, isRead } = crud.getAttributes(el);
68
- if (!collection || !name) return;
159
+ if (data.object || data.object === '') {
160
+ if (!data.array || data.array && !data.array.length) return
161
+ data.type = 'object'
162
+ } else if (data.index || data.index === '') {
163
+ if (!data.array || data.array && !data.array.length) return
164
+ data.type = 'index'
165
+ } else if (data.array || data.array === '')
166
+ data.type = 'array'
167
+ else if (data.database || data.database === '')
168
+ data.type = 'database'
169
+ else if (data.storage || data.storage === '')
170
+ data.type = 'storage'
171
+ else if (data.data)
172
+ data.type = 'data'
173
+
174
+ delete data.isRealtime
175
+ return data
176
+ }
69
177
 
70
- if (document_id)
71
- if (!document_id.match(/^[0-9a-fA-F]{24}$/)) return;
178
+ function initDataKey(element, data) {
179
+ let dataKey = getDataKey(data)
180
+ if (keys.has(dataKey.string))
181
+ keys.get(dataKey.string).elements.set(element, '')
182
+ else
183
+ keys.set(dataKey.string, { elements: new Map([[element, '']]), data, dataKey });
184
+
185
+ elements.set(element, dataKey.string)
186
+
187
+ if (element.parentElement) {
188
+ let form = element.parentElement.closest('form')
189
+ if (form) {
190
+ if (!form.save)
191
+ form.save = () => save(form)
192
+
193
+ if (!form.getData)
194
+ form.getData = () => getData(form)
195
+
196
+ let formObject = forms.get(form)
197
+ if (formObject) {
198
+ formObject.elements.set(element, data)
199
+ if (formObject.types.has(data.type))
200
+ formObject.types.get(data.type).set(element, data)
201
+ else
202
+ formObject.types.set(data.type, new Map([[element, data]]));
203
+ } else
204
+ forms.set(form, { elements: new Map([[element, data]]), types: new Map([[data.type, new Map([[element, data]])]]) });
205
+ }
206
+ }
207
+
208
+ if (!element.read)
209
+ element.read = () => read(element)
210
+ if (!element.save)
211
+ element.save = () => save(element)
72
212
 
73
- let filter
74
- if (el.hasAttribute('filter_id') || el.hasAttribute('filter-name') || el.hasAttribute('filter-sort-name')) {
75
- filter = ccfilter.init(el, "filter_id");
213
+ return dataKey
214
+ }
215
+
216
+ async function read(element, data, dataKey) {
217
+ if (!dataKey)
218
+ dataKey = elements.get(element)
219
+ if (!data)
220
+ data = { ...keys.get(dataKey).data }
221
+
222
+ let delayTimer = debounce.get(dataKey.string)
223
+ clearTimeout(delayTimer);
224
+ delayTimer = setTimeout(function () {
225
+ debounce.delete(dataKey.string)
226
+ // TODO: should server support string and string array for type object, methods create, read, delete
227
+ if (data.type === 'object') {
228
+ if (typeof data.object === 'string')
229
+ data.object = { _id: data.object }
230
+
231
+ if (!data.object._id.match(/^[0-9a-fA-F]{24}$/))
232
+ return
233
+ }
234
+
235
+ data.method = 'read' + '.' + data.type
236
+ CRUD.send(data).then((data) => {
237
+ setData(element, data);
238
+ })
239
+ }, 500);
240
+ debounce.set(dataKey.string, delayTimer)
241
+ }
242
+
243
+ function setData(element, data, action) {
244
+ if (!element) {
245
+ element = getDataElements(data)
246
+ if (!element.length) return
76
247
  }
77
248
 
78
- if (!crud.checkValue(collection) || !crud.checkValue(name)) return;
249
+ if (!(element instanceof HTMLCollection) && !Array.isArray(element))
250
+ element = [element]
251
+
252
+ let type = data.type
253
+ if (!type && action) {
254
+ type = action.split('.')[0]
255
+ } else if (type == 'key')
256
+ type = 'object'
257
+
258
+ for (let el of element) {
259
+ // if rendered in server side skip
260
+ if (el.hasAttribute('rendered'))
261
+ return el.removeAttribute('rendered');
262
+
263
+ if (!data[type])
264
+ continue;
265
+
266
+ action = el.getAttribute('actions')
267
+ if (action && ['database', 'array', 'object', 'key'].includes(action)) continue;
268
+
269
+ const { key, isRead, isListen, isCrdt } = getAttributes(el);
270
+ if (el.getFilter || el.renderValue)
271
+ filterData(el, data, type, key, action)
272
+ else {
273
+ let value = CRUD.getValueFromObject(data[type][0], key);
274
+ if (key) {
275
+ if (!data[type].length) continue;
276
+ if (isRead == "false" || isCrdt == "true") continue;
277
+ if (isListen == "false" && !data.method.startsWith('read')) continue;
278
+ }
279
+ el.setValue(value);
280
+ }
281
+ }
282
+ }
283
+
284
+ function filterData(element, data, type, key, action) {
285
+ if (key) {
286
+ if (!data.type) return
287
+ if (Array.isArray(data[type])) {
288
+ let Data = []
289
+ for (let doc of data[type]) {
290
+ if (doc[key]) {
291
+ if (Array.isArray(doc[key]))
292
+ Data.push(...doc[key])
293
+ else
294
+ Data.push(doc[key])
295
+ }
296
+ }
297
+ let data = Data
298
+ } else {
299
+ data = { [key]: data[type][key] }
300
+ }
301
+ }
79
302
 
80
- if (isRead == 'false') return;
81
- if (!document_id && !filter) return;
303
+ if (element.getFilter && action && !action.startsWith('read'))
304
+ checkFilters(element, data, type, action)
305
+ else if (element.renderValue)
306
+ element.renderValue(data);
307
+ // render({ element, data, key: type });
308
+ else if (data)
309
+ element.setValue(data)
82
310
 
83
- return { collection, document_id, filter };
311
+ const evt = new CustomEvent('fetchedData', { bubbles: true });
312
+ element.dispatchEvent(evt);
84
313
  }
85
314
 
86
- async function read(documents) {
87
- if (documents && documents.size > 0) {
88
- for (let [key, { collection, document_id, filter, elements }] of documents) {
89
- documents.delete(key);
315
+ function checkFilters(element, data, type, action) {
316
+ let Data = element.getData()
317
+ if (!Data) return
318
+
319
+ let newData
320
+ if (type) {
321
+ Data = Data[type]
322
+ newData = data[type]
323
+ } else
324
+ newData = data
325
+
326
+ let filter = element.getFilter()
327
+ if (filter && filter.query)
328
+ newData = queryData(newData, filter.query)
329
+ if (!newData.length)
330
+ return
90
331
 
91
- let data = { collection }
92
- if (document_id)
93
- data.document = { _id: document_id }
94
- if (filter)
95
- data.filter = filter
332
+ if (Array.isArray(Data)) {
333
+ if (Array.isArray(newData)) {
334
+ for (let i = 0; i < newData.length; i++) {
335
+ checkIndex(element, data, Data, newData[i], type, filter, action)
336
+ }
337
+ } else {
338
+ checkIndex(element, data, Data, newData, type, filter, action)
339
+ }
340
+ } else {
341
+ let primaryKey
342
+ if (type === 'object') {
343
+ primaryKey = '_id';
344
+ } else {
345
+ primaryKey = 'key';
346
+ }
96
347
 
97
- let response = await crud.readDocument(data);
98
- setData(elements, response);
348
+ if (Data[primaryKey] === newData[primaryKey]) {
349
+ Data = dotNotationToObject(newData, Data)
99
350
  }
351
+ if (element.renderValue)
352
+ element.renderValue(data);
353
+ // render({ element, data, key: type });
354
+ else if (data)
355
+ element.setValue(data)
100
356
  }
101
357
  }
102
358
 
103
- function setData(elements, data) {
104
- if (!data.document || !data.document[0]) return;
105
- let key = getKey(data)
106
- if (!elements) {
107
- elements = els.get(key)
359
+ function checkIndex(element, data, Data, newData, type, filter, action) {
360
+ let index
361
+ if (type === 'object') {
362
+ index = Data.findIndex(obj => obj._id === newData._id);
108
363
  } else {
109
- let eles = els.get(key)
110
- if (eles && eles.length)
111
- els.set(key, [...eles, ...elements])
112
- else
113
- els.set(key, elements)
364
+ index = Data.findIndex(obj => obj.name === newData.name);
114
365
  }
115
- if (!elements || elements.length == 0)
116
- return
117
366
 
118
- for (let el of elements) {
119
- const { collection, document_id, name, isRead, isUpdate, isCrdt } = crud.getAttributes(el);
367
+ if (!data.filter)
368
+ data.filter = {}
120
369
 
121
- if (el.hasAttribute('actions') || el.tagName === 'DIV' && !el.classList.contains('domEditor')) continue;
122
- if (!name || isRead == "false" || isUpdate == "false" || isCrdt == "true") continue;
370
+ if (action.startsWith('delete')) {
371
+ if (!index && index !== 0)
372
+ return
373
+ data.filter.remove = true
374
+ } else {
375
+ if (!index && index !== 0) {
376
+ data.filter.create = true
377
+ Data.push(newData)
378
+ } else {
379
+ data.filter.update = true
380
+ data.filter.currentIndex = index
381
+ Data[index] = dotNotationToObject(newData, Data[index])
382
+ }
123
383
 
124
- if (data.document[0]['collection'] == collection && data.document[0]['_id'] == document_id) {
125
- let value = crud.getValueFromObject(data.document[0], name);
126
- el.setValue(value);
127
- initializing.delete(el)
384
+ if (filter && filter.sort) {
385
+ newData.isNewData = true
386
+ Data = sortData(Data, filter.sort)
387
+ index = Data.findIndex(obj => obj.isNewData);
128
388
  }
389
+
390
+ if (index >= 0) {
391
+ if (data.filter.currentIndex === index)
392
+ delete data.filter.currentIndex
393
+ data.filter.index = index
394
+ if (element.renderValue)
395
+ element.renderValue(data);
396
+ // render({ element, data, key: type });
397
+ else if (data)
398
+ element.setValue(data)
399
+ }
400
+
129
401
  }
130
402
  }
131
403
 
132
- function getKey(data) {
133
- let key = {};
134
- let attributes = ["storage", "database", "collection", "index", "document"]
404
+ function getDataKey(data) {
405
+ let dataKey = {};
406
+ let attributes = ["host", "organization_id", "apikey", "method", "type", "storage", "database", "array", "index", "object", "key", "updateKey", "filter", "upsert", "namespace", "room", "broadcast", "broadcastSender", "broadcastBrowser"];
135
407
 
136
408
  for (let attribute of attributes) {
137
- let value = data[attribute]
409
+ if (attribute === 'key' && data.type === 'object')
410
+ continue
411
+ let value = data[attribute];
138
412
  if (value) {
139
- if (attribute == 'document')
140
- key[attribute] = value
413
+ if (Array.isArray(value)) {
414
+ dataKey[attribute] = [...value];
415
+ if (typeof value[0] === 'string')
416
+ dataKey[attribute].sort(); // Sort the values alphabetically
417
+ } else {
418
+ dataKey[attribute] = value;
419
+ }
420
+ }
421
+ }
422
+
423
+ const object = Object.fromEntries(Object.entries(dataKey).sort(([a], [b]) => a.localeCompare(b)));
424
+ const string = JSON.stringify(object);
425
+
426
+ return { string, object };
427
+ }
428
+
429
+ function getDataElements(data) {
430
+ let element = []
431
+ let matchingKeys = getDataKeys(data)
432
+ for (let i = 0; i < matchingKeys.length; i++) {
433
+ let matchingElements = keys.get(matchingKeys[i])
434
+ if (matchingElements && matchingElements.elements && matchingElements.elements.size)
435
+ element.push(...matchingElements.elements.keys())
436
+ }
437
+ return element
438
+ }
439
+
440
+ function getDataKeys(data) {
441
+ const matchingKeyStrings = [];
442
+ const targetKeys = ["type", "storage", "database", "array", "index", "object", 'filter'];
443
+
444
+ for (const [keyString, sortedKey] of keys.entries()) {
445
+ let hasMatch = true;
446
+
447
+ for (const key of targetKeys) {
448
+ if (data.hasOwnProperty(key)) {
449
+ if (!sortedKey.data.hasOwnProperty(key)) {
450
+ hasMatch = false;
451
+ break;
452
+ }
453
+ if (Array.isArray(sortedKey.data[key]) && Array.isArray(data[key])) {
454
+ // if key is object check _id
455
+ const matches = sortedKey.data[key].some(value => {
456
+ if (key === 'object') {
457
+ return data[key].some(obj => obj._id === value._id);
458
+ } else {
459
+ return data[key].includes(value)
460
+ }
461
+ });
462
+ if (!matches) {
463
+ hasMatch = false;
464
+ break;
465
+ }
466
+ } else if (sortedKey.data[key] !== data[key]) {
467
+ hasMatch = false;
468
+ break;
469
+ }
470
+ }
471
+ }
472
+
473
+ if (hasMatch) {
474
+ matchingKeyStrings.push(keyString);
141
475
  }
142
476
  }
143
- key.document = data.document[0]._id
144
- return JSON.stringify(key)
477
+
478
+ return matchingKeyStrings;
479
+ }
480
+
481
+ function getData(form) {
482
+ let dataKeys = new Map()
483
+ let formObject = forms.get(form)
484
+ for (let type of formObject.types.values()) {
485
+ for (let [element, data] of type.entries()) {
486
+ let Data = { ...data }
487
+ let dataKey = elements.get(element)
488
+ let value = element.getValue()
489
+ console.log(type, value, data)
490
+
491
+ if (!Data[Data.type] && Data.key) {
492
+ Data.method = 'create.' + Data.type
493
+ if (Data.type === 'object') {
494
+ if (typeof Data.object === 'string')
495
+ Data.object = { _id: Data.object }
496
+
497
+ if (Data.key)
498
+ Data.object[Data.key] = value
499
+
500
+ } else
501
+ Data[Data.type] = value
502
+
503
+ } else if (Data[Data.type] && Data.key) {
504
+ let isUpdate = element.getAttribute('update')
505
+ let isDelete = element.getAttribute('delete')
506
+ let splice = element.getAttribute('splice') // could work on string and array
507
+ let slice = element.getAttribute('slice') // could work on string and array
508
+
509
+ if (/\.([0-9]*)/g.test(Data.key)) {
510
+
511
+ if (splice || splice === "") {
512
+ Data[Data.type][Data.key] = { $splice: value }
513
+ } else if (slice) {
514
+ Data[Data.type][Data.key] = '$delete'
515
+ } else if (isUpdate) {
516
+ value = Data.key.replace(/\[.*?\]/, '[' + value + ']')
517
+ Data.updateKey[Data.key] = value
518
+ Data[Data.type][Data.key] = { $update: value } // $update is string use the value as the key name
519
+ // Data[Data.type][Data.key] = { $update: {[value]: value} } // $update is an object use the key as the value to use for the new key
520
+ }
521
+
522
+ } else if (Data.type = 'object')
523
+ Data.object[Data.key] = value
524
+ else {
525
+ Data[Data.type] = { [Data[Data.type]]: value }
526
+ }
527
+
528
+ if (Data.isUpdate || Data.isUpdate === '') {
529
+ if (!Data.key) return
530
+ delete Data.isUpdate
531
+ Data.updateKey = { [Data.key]: value }
532
+ Data.method = 'update.' + Data.type
533
+ } else if (Data.isDelete || Data.isDelete === '') {
534
+ delete Data.isDelete
535
+ if (Data.type == 'object' && Data.key) {
536
+ Data.method = 'update.' + Data.type
537
+ // TODO: Data.type can be a string _id or an array for string _id needs to be converted to object
538
+ if (typeof Data[Data.type] === 'string')
539
+ Data[Data.type] = { _id: Data[Data.type], [Data.key]: undefined }
540
+ else if (Array.isArray(Data[Data.type])) {
541
+ console.log('Data.type is an array function incomplete')
542
+ } else if (typeof Data[Data.type] === 'object')
543
+ Data[Data.type][Data.key] = undefined
544
+ } else {
545
+ Data.method = 'delete.' + Data.type
546
+ }
547
+ } else if (Data.type !== 'object' && Data[Data.type] === '') {
548
+ // if (!Data.key)
549
+ // Data.method = 'read.' + Data.type
550
+ // else if (Data.key === 'name')
551
+ // Data.method = 'create.' + Data.type
552
+ } else if (Data.type !== 'object' && Data[Data.type]) {
553
+ // if (Data.key)
554
+ // Data.method = 'update.' + Data.type
555
+ }
556
+
557
+ }
558
+
559
+ //dataKey should be used to group
560
+ let key = dataKey + Data.method
561
+ if (dataKeys.has(key))
562
+ dataKeys.get(key).push(Data)
563
+ else
564
+ dataKeys.set(key, [Data])
565
+
566
+ }
567
+ }
568
+
569
+ // TODO: group by methods so we can make one crud request per method
570
+ let dataArray = []
571
+ return dataArray
145
572
  }
146
573
 
147
574
  async function save(element) {
148
- let value = element.getValue();
149
- crud.save(element, value);
575
+ if (!element) return;
576
+ let data, value
577
+ if (element.tagName === "FORM") {
578
+ data = element.getData()
579
+ } else {
580
+ let isSave = element.getAttribute('save')
581
+ if (isSave === 'false') return
582
+
583
+ let form = element.closest('form');
584
+ if (form)
585
+ return save(form);
586
+
587
+ let dataKey = elements.get(element)
588
+ data = { ...keys.get(dataKey).data }
589
+
590
+ value = element.getValue();
591
+
592
+ if (typeof data[data.type] === 'string')
593
+ data[data.type] = { _id: data[data.type], [data.key]: value }
594
+ else if (Array.isArray(data[data.type])) {
595
+ console.log('data.type is an array function incomplete')
596
+ } else if (typeof data[data.type] === 'object')
597
+ data[data.type][data.key] = value
598
+
599
+
600
+ if (/\.([0-9]*)/g.test(data.key)) {
601
+ let splice = element.getAttribute('splice')
602
+ let slice = element.getAttribute('slice')
603
+ let update = element.getAttribute('update')
604
+
605
+ if (splice || splice === "") {
606
+ data[data.type][data.key] = { $splice: value }
607
+ } else if (slice) {
608
+ data[data.type][data.key] = '$delete'
609
+ } else if (update) {
610
+ value = data.key.replace(/\[.*?\]/, '[' + value + ']')
611
+ data.updateKey[data.key] = value
612
+ data[data.type][data.key] = { $update: value } // $update is string use the value as the key name
613
+ // data[data.type][data.key] = { $update: {[value]: value} } // $update is an object use the key as the value to use for the new key
614
+ }
615
+
616
+ }
617
+
618
+ data = [data]
619
+ }
620
+
621
+ for (let i = 0; i < data.length; i++) {
622
+ if (data[i].method && data[i].method.startsWith('create'))
623
+ element.setAttribute(data[i].type, 'pending');
624
+ else if (data[i].method && data[i].method.startsWith('update') && data[i].type == 'object' && typeof value == 'string' && window.CoCreate.crdt && !'crdt') {
625
+ return window.CoCreate.crdt.replaceText({
626
+ array: data[i].array,
627
+ key: data[i].key,
628
+ object: data[i].object._id,
629
+ value
630
+ });
631
+ }
632
+
633
+ data[i] = await CRUD.send(data[i]);
634
+
635
+ if (data[i] && (data[i].method.startsWith('create') || data[i].type !== 'object' && data[i].method.startsWith('update'))) {
636
+ setTypeValue(element, data[i])
637
+ }
638
+
639
+ }
640
+
150
641
  }
151
642
 
152
- function __initSocket() {
153
- crud.listen('updateDocument', function (data) {
154
- setData(null, data);
155
- });
643
+ function setTypeValue(element, data) {
644
+ // TODO: if an array name is updated, the attibute array="" needs to be updated.
645
+
646
+ if (!data) return;
647
+
648
+ let form
649
+ if (element.tagName === "FORM")
650
+ form = element
651
+ else if (element.parentElement)
652
+ form = element.parentElement.closest('form')
653
+
654
+ if (!form) {
655
+ if (data.type === 'object') {
656
+ element.setAttribute('object', data.object[0]._id)
657
+ } else {
658
+ element.setAttribute(data.type, data[data.type].name)
659
+ }
660
+ } else {
661
+ let formObject = forms.get(form)
662
+ let elements = formObject.types.get(data.type)
663
+
664
+ for (let [el, Data] of elements.entries()) {
665
+ if (data.type === 'object') {
666
+ if (!Data.object || Data.object === 'pending') {
667
+ Data.object = data.object[0]._id
668
+ el.setAttribute('object', data.object[0]._id)
669
+ }
670
+ } else if (!Data[data.type]) {
671
+ Data[data.type] = data[data.type].name
672
+ el.setAttribute(data.type, data[data.type].name)
673
+ }
674
+ }
675
+
676
+ // const pass_ids = new Map();
677
+
678
+ // let pass_id = form.getAttribute('pass_id');
679
+ // if (pass_id) {
680
+ // // Set pass_ids to pass_ids if array is not set
681
+ // if (form.getAttribute('array') == array)
682
+ // pass_ids.set(pass_id, '');
683
+ // }
684
+
685
+ // let objectId = el.getAttribute('object');
686
+ // let key = el.getAttribute('key')
687
+ // // set object and pass_id attributes to the object id if the object id is pending
688
+ // if (key && (objectId == '' || objectId == 'pending')) {
689
+ // el.setAttribute('object', id);
690
+ // // Set the id attribute of the element
691
+ // if (key == '_id')
692
+ // el.setValue(id)
693
+ // let pass_id = el.getAttribute('pass_id');
694
+ // // Set the pass id to the pass_ids.
695
+ // if (pass_id) {
696
+ // pass_ids.set(pass_id, '');
697
+ // }
698
+
699
+ // if (el.hasAttribute('pass-object')) {
700
+ // let passObjectId = el.getAttribute('pass-object');
701
+ // // Set the pass object id if not set.
702
+ // if (passObjectId == '') {
703
+ // el.setAttribute('pass-object', id);
704
+ // let pass_id = el.getAttribute('pass_id');
705
+ // // Set the pass id to the pass_ids.
706
+ // if (pass_id) {
707
+ // pass_ids.set(pass_id, '');
708
+ // }
709
+ // }
710
+ // }
711
+
712
+ // // Set the object attribute of all pass_ids to the object attribute of all pass_ids
713
+ // if (pass_ids.size > 0) {
714
+ // for (let key of pass_ids.keys()) {
715
+ // let passEls = document.querySelectorAll(`[pass_id="${key}"]`)
716
+ // for (let passEl of passEls) {
717
+ // // if (passEl.getAttribute('array') == array){
718
+ // // Set the object id attribute to the passEl s object if it is not set.
719
+ // if (passEl.getAttribute('object') == '') {
720
+ // passEl.setAttribute('object', id);
721
+ // }
722
+ // // }
723
+ // }
724
+ // }
725
+ // }
726
+ // }
727
+ }
156
728
  }
157
729
 
158
- const elementEvents = new Map();
159
- function __initEvents(el) {
160
- if (!elementEvents.has(el)) {
161
- if (el.tagName == 'INPUT' || el.tagName == 'TEXTAREA' || el.tagName == 'SELECT' || el.hasAttribute('contenteditable')) {
162
- el.addEventListener('input', function (e) {
163
- const { document_id, name, isRealtime, isCrdt } = crud.getAttributes(el);
164
- if (isCrdt == "true" && document_id && document_id != 'pending' || isRealtime == "false" || name == "_id") return;
165
- if (document_id && e.detail && e.detail.skip == true) return;
166
- save(el);
730
+ async function remove(element) {
731
+ if (element && !(element instanceof HTMLCollection) && !Array.isArray(element))
732
+ element = [element]
733
+ for (let i = 0; i < element.length; i++) {
734
+ let key = elements.get(element[i])
735
+ if (key) {
736
+ let els = keys.get(key).elements
737
+ els.delete(element[i])
738
+ elements.delete(key)
739
+ debounce.delete(element[i])
740
+ // TODO: delete element from forms map
741
+ }
742
+ }
743
+ }
744
+
745
+ function initSocket() {
746
+ const array = ['create', 'update', 'delete'];
747
+ const attributes = ["storage", "database", "array", "index", "object", 'filter'];
748
+
749
+ for (let i = 0; i < array.length; i++) {
750
+ for (let j = 0; j < attributes.length; j++) {
751
+ const action = array[i] + '.' + attributes[j];
752
+
753
+ CRUD.listen(action, function (data) {
754
+ setData(null, data, action);
167
755
  });
756
+ }
757
+ }
758
+
759
+ CRUD.listen('sync', function (data) {
760
+ setData(null, data, action);
761
+ });
762
+
763
+ }
168
764
 
169
- elementEvents.set(el, true)
765
+ Observer.init({
766
+ name: 'render',
767
+ observe: ['addedNodes'],
768
+ target: '[render-clone]',
769
+ callback: function (mutation) {
770
+ let renderedNode = CoCreate.render.renderedNodes.get(mutation.target)
771
+ if (!renderedNode) return
772
+
773
+ if (!mutation.movedFrom) return
774
+ let draggedEl = mutation.target
775
+ let draggedFrom = mutation.movedFrom.parentElement
776
+ let droppedEl = mutation.target.nextElementSibling || mutation.target.previousElementSibling
777
+ let droppedIn = mutation.parentElement
778
+
779
+ if (!draggedFrom || !droppedIn) return
780
+ dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn)
781
+ }
782
+ })
783
+
784
+ // TODO: has the potential to work on most of the crud elements specifically if the value is an object or an array
785
+ function dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn) {
786
+ let from = dndCrudData(draggedEl, draggedFrom, 'remove')
787
+ let to = dndCrudData(droppedEl, droppedIn, 'add')
788
+
789
+ if (from && to && !draggedFrom.isSameNode(droppedIn)) {
790
+ let element = getDataElements(from.data)
791
+ if (!element.length) return
792
+
793
+ let match = element.find(obj => obj === droppedIn);
794
+ if (!match) {
795
+ if (from.keyPath.includes('.')) {
796
+ let test = { [from.keyPath]: undefined }
797
+
798
+ from.newData = dotNotationToObject(test, from.newData)
799
+ dndCrudSend(from.newData, 'update')
800
+ } else {
801
+ const index = from.keyPath.match(/\[(\d+)\]/)
802
+ let removeData = from.newData[from.newData.type].splice(index, 1)[0];
803
+ if (removeData) {
804
+ removeData.type = from.newData.type
805
+ removeData[removeData.type] = removeData
806
+ dndCrudSend(removeData, 'delete')
807
+ }
808
+
809
+ if (from.newData[from.newData.type].length)
810
+ dndCrudSend(from.newData, 'update')
811
+
812
+ }
813
+ }
814
+ }
815
+
816
+ if (to)
817
+ dndCrudSend(to.newData, 'update')
818
+
819
+ }
820
+
821
+ function dndCrudData(element, parent, operator) {
822
+ if (!elements.has(parent)) return
823
+ let data = parent.getData()
824
+ let newData = getObject(parent)
825
+
826
+ let { Data, sortName, sortDirection, keyPath, clones, index } = dndNewData(element, data)
827
+ newData[newData.type] = []
828
+
829
+ if (sortName) {
830
+ for (let i = 0; i < clones.length; i++) {
831
+ if (i > index) {
832
+ let previousData = data[data.type][index]
833
+ if (operator === 'add')
834
+ Data[sortName] = i + 1
835
+ else if (operator === 'remove')
836
+ Data[sortName] = i - 1
837
+
838
+ newData[newData.type].push({ ...previousData, ...Data })
839
+ }
170
840
  }
841
+ } else {
842
+ newData[newData.type] = [{ ...data[data.type][index], ...Data }]
171
843
  }
844
+
845
+ return { data, newData, keyPath, clones, index }
172
846
  }
173
847
 
174
- observer.init({
848
+ function dndNewData(element, data) {
849
+ let Data = {}
850
+ let query = data.filter.query
851
+ if (query && query.length) {
852
+ for (let i = 0; i < query.length; i++) {
853
+ if (query.operator === "$eq")
854
+ Data[query.key] = query.value
855
+ if (query.operator === "$ne")
856
+ Data[query.key] = query.value
857
+ }
858
+ }
859
+
860
+ let sortName, sortDirection
861
+ let sort = data.filter.sort
862
+ if (sort && sort.length) {
863
+ // for (let i = 0; i < sort.length; i++) {
864
+ for (let i = sort.length - 1; i >= 0; i--) {
865
+ if (typeof data[sort.key] === 'number') {
866
+ sortName = sort.key
867
+ sortDirection = sort.direction
868
+ break
869
+ }
870
+ }
871
+ }
872
+
873
+ let keyPath, clonesMap, clones, index
874
+
875
+ let renderedNode = CoCreate.render.renderedNodes.get(element)
876
+ if (renderedNode) {
877
+ keyPath = renderedNode.keyPath
878
+ clonesMap = renderedNode.template.clones
879
+ clones = Array.from(clonesMap.values());
880
+ index = clones.indexOf(element);
881
+ }
882
+
883
+ return { Data, sortName, sortDirection, keyPath, clones, index }
884
+
885
+ }
886
+
887
+ function dndCrudSend(data, crudType) {
888
+ if (CRUD) {
889
+ let action = data.type;
890
+ action = action.charAt(0).toUpperCase() + action.slice(1);
891
+ CRUD[crudType + action](data)
892
+ } else
893
+ console.log('dnd reordered data set as crud is unavailable')
894
+ }
895
+
896
+ Observer.init({
175
897
  name: 'CoCreateElementsChildList',
176
898
  observe: ['childList'],
177
899
  target: selector,
178
900
  callback: function (mutation) {
179
- initElements(mutation.addedNodes);
901
+ init(mutation.addedNodes);
180
902
  }
181
903
  });
182
904
 
183
- observer.init({
905
+ Observer.init({
906
+ name: 'CoCreateElementsRemovedNodes',
907
+ observe: ['removedNodes'],
908
+ target: selector,
909
+ callback: function (mutation) {
910
+ if (mutation.target.parentElement) return
911
+ remove(mutation.target)
912
+ }
913
+ });
914
+
915
+ Observer.init({
184
916
  name: 'CoCreateElementsAttributes',
185
917
  observe: ['attributes'],
186
- attributeName: crud.getAttributeNames(['collection', 'document_id', 'name']),
918
+ attributeName: getAttributeNames(['storage', 'database', 'array', 'object', 'key']),
187
919
  target: selector,
188
920
  callback: function (mutation) {
189
- let { collection, document_id, name } = crud.getAttributes(mutation.target);
190
- if (collection && document_id && name)
191
- initElements([mutation.target]);
921
+ remove(mutation.target)
922
+ init([mutation.target]);
192
923
  }
193
924
  });
194
925
 
195
- action.init({
196
- name: "saveHtml",
197
- endEvent: "changed-element",
198
- callback: (btn, data) => {
199
- let form = btn.closet('form');
926
+ Actions.init({
927
+ name: "save",
928
+ endEvent: "saved",
929
+ callback: (action) => {
930
+ const form = action.element.closest("form");
200
931
  save(form);
201
- },
932
+ }
202
933
  });
203
934
 
204
935
  init();
205
936
 
206
- export default { initElements, initElement, save };
937
+ export default { init, read, save, getData, reset, elements, keys, forms, debounce, getAttributes };