saasufy-components 2.15.2 → 3.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/README.md CHANGED
@@ -348,14 +348,12 @@ Used for displaying and editing a single field of a model instance in real time.
348
348
  - `model-id` (required): The id of the model instance/record to bind to.
349
349
  - `model-field` (required): The field of the model instance/record to bind to for reading and updating.
350
350
  - `debounce-delay`: The delay in milliseconds to wait before sending an update to the server. This is useful to batch multiple updates together (as is common when user is typing). Default is 300ms.
351
- - `enable-rebound`: By default, real-time updates performed via `model-input` components are not sent back to the publishing client as it is assumed that state changes are either localized to a single `model-input` component or shared with other components on the same page via the use of `consumers`, `bind-to-collection` or `bind-to-model` attributes. There may be situations where you have a specific value displayed in multiple places on the same page/screen and want them to be updated in real-time to reflect the latest value of the model field. For convenience, you can add the `enable-rebound` attribute to the `model-input` component to force real-time updates to rebound back to the publisher and this will ensure that all components on the page will receive real-time updates (will sync with each other) without having to explicitly bind them together via the front end. Note that enabling rebound comes with additional performance overheads.
352
351
  - `input-id`: Can be used to set an `id` attribute on the inner `input` element.
353
352
  - `list`: If specified, this sets the list attribute of the inner `input` element to provide input suggestions (only works with standard input elements).
354
- - `bind-to-model`: If this attribute exists, the `model-input` component will attempt to host its model data within the nearest compatible `model-viewer` parent component instead of creating its own model internally. This can improve efficiency/reuse of model data and provide a better user experience. For example, if the `model-input` element is re-rendered, it will not have to re-fetch its data as it will be kept inside its parent `model-viewer` component.
355
- - `bind-to-collection`: If this attribute exists, the `model-input` component will attempt to host its model data within the nearest compatible `collection-browser` or `collection-reducer` parent component instead of creating its own model internally. This can improve efficiency/reuse of model data and provide a better user experience. For example, if the `model-input` element is re-rendered, it will not have to re-fetch its data as it will be kept inside its parent `collection-browser` or `collection-reducer` component. Note that the parent component will take over the responsibility for cleaning up the model; this includes scenarios where the user loads a different page of the collection/view where the specified model instance is no longer present.
356
353
  - `type`: The type of the input component; can be `text`, `number`, `select`, `textarea` or `checkbox`.
357
354
  - `placeholder`: Can be used to set the placeholder text on the inner input element.
358
355
  - `consumers`: This allows you to connect this `model-input` to other elements on your page which share the same parent element. It takes a list of selectors with optional attributes to target. For example `.my-input:value` will find all elements with a `my-input` class and update their `value` attributes with the value of the `model-input` component in real-time. You can specify multiple selectors separated by commas such as `.my-input,my-div` - In this case, because attribute names are not specified, values will be injected into the `value` attribute (for input elements) or into the `innerHTML` property (for other kinds of elements). The default attribute/property depends on the element type.
356
+ - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers.
359
357
  - `show-error-message`: If this attribute is present, an error message will be displayed above the input if it fails to update the value.
360
358
  - `options`: A comma-separated list of options to provide - This only works if the input type is `select`.
361
359
  - `height`: Allows you to set the height of the inner `input` element programmatically.
@@ -364,6 +362,7 @@ Used for displaying and editing a single field of a model instance in real time.
364
362
  - `hide-error-logs`: By default, this component will log errors to the console. If set, this attribute will suppress such errors from showing up on the console.
365
363
  - `autocapitalize`: Can be `on` or `off` - It will set the auto-capitalize attribute on the inner input element. This is useful for mobile devices to enable or disable auto-capitalization of the first character which is typed into the input element.
366
364
  - `autocorrect`: Can be `on` or `off` - It will set the auto-correct attribute on the inner input element. This is useful for mobile devices to enable or disable auto-correct.
365
+ - `enable-rebound`: By default, for efficiency reasons, real-time updates performed via `model-input` components are not sent back to the publishing client; it is usually unnecessary because state changes are shared locally between all components which are bound to the same `socket-provider`. You can add the `enable-rebound` attribute to the `model-input` component to force real-time updates to rebound back to the publisher to support more advanced scenarios. Note that enabling rebound comes with additional performance overheads.
367
366
 
368
367
  ### model-text
369
368
 
@@ -385,8 +384,6 @@ Used to displaying a field of a model instance in real time.
385
384
  - `model-type` (required): Specifies the type of model to bind to. This should match a `Model` available in your Saasufy service.
386
385
  - `model-id` (required): The id of the model instance/record to bind to.
387
386
  - `model-field` (required): The field of the model instance/record to bind to for reading.
388
- - `bind-to-model`: If this attribute exists, the `model-text` component will attempt to fetch its model data from the nearest compatible `model-viewer` parent component instead of creating its own model internally. This can improve efficiency/reuse of model data and provide a better user experience. For example, if the `model-text` element is re-rendered, it will not have to re-fetch its data as it will be kept inside its parent `model-viewer` component.
389
- - `bind-to-collection`: If this attribute exists, the `model-text` component will attempt to fetch its model data from the nearest compatible `collection-browser` or `collection-reducer` parent component instead of creating its own model internally. This can improve efficiency/reuse of model data and provide a better user experience. For example, if the `model-text` element is re-rendered, it will not have to re-fetch its data as it will be kept inside its parent `collection-browser` or `collection-reducer` component. Note that the parent component will take over the responsibility for cleaning up the model; this includes scenarios where the user loads a different page of the collection/view where the specified model instance is no longer present.
390
387
  - `hide-error-logs`: By default, this component will log errors to the console. If set, this attribute will suppress such errors from showing up on the console.
391
388
 
392
389
  ### model-viewer
@@ -426,7 +423,6 @@ Used for rendering a single model resource using a template. This is the single-
426
423
  An input component which can pass data to other components (or HTML elements) via custom attributes.
427
424
  A common use case for it is to pass user input to `collection-browser` or `model-viewer` components to then fetch data from Saasufy.
428
425
  This component can be configured to provide its data to multiple components (consumers) via custom attributes.
429
- Note that an `input-provider` only targets elements which are downstream from its parent element in the HTML hierarchy.
430
426
 
431
427
  **Example usage**
432
428
 
@@ -446,6 +442,7 @@ Note that an `input-provider` only targets elements which are downstream from it
446
442
  - `type`: The type of the input component; can be `text`, `number`, `select`, `textarea` or `checkbox`.
447
443
  - `placeholder`: Can be used to set the placeholder text on the inner input element.
448
444
  - `consumers`: This allows you to connect this `input-provider` to other elements on your page which share the same parent element. It takes a list of selectors with optional attributes to target. For example `.my-input:value` will find all elements with a `my-input` class and update their `value` attributes with the value of the `input-provider` component in real-time. You can specify multiple selectors separated by commas such as `.my-input,my-div` - In this case, because attribute names are not specified, values will be injected into the `value` attribute (for input elements) or into the `innerHTML` property (for other kinds of elements). The default attribute/property depends on the element type.
445
+ - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers.
449
446
  - `debounce-delay`: The delay in milliseconds to wait before triggering a change event. This is useful to batch multiple updates together (as is common when user is typing). Default is 800ms.
450
447
  - `options`: A comma-separated list of options to provide - This only works if the input type is `select`.
451
448
  - `height`: Allows you to set the height of the inner `input` element programmatically.
package/input-provider.js CHANGED
@@ -135,20 +135,21 @@ class InputProvider extends HTMLElement {
135
135
 
136
136
  let onInputChange = (event) => {
137
137
  debounce(async () => {
138
+ let providerTemplate = this.getAttribute('provider-template');
138
139
  if (event.target.value === this.lastValue) return;
139
140
  if (inputElement.type === 'checkbox') {
140
141
  let checked = !!inputElement.checked;
141
142
  this.lastValue = checked;
142
- updateConsumerElements(this.parentElement, consumers, checked);
143
+ updateConsumerElements(consumers, checked, providerTemplate);
143
144
  return;
144
145
  }
145
146
  this.lastValue = event.target.value;
146
147
  if (event.target.value === '') {
147
- updateConsumerElements(this.parentElement, consumers, '');
148
+ updateConsumerElements(consumers, '', providerTemplate);
148
149
  } else {
149
150
  let targetValue = inputElement.type === 'number' ?
150
151
  Number(event.target.value) : event.target.value;
151
- updateConsumerElements(this.parentElement, consumers, targetValue);
152
+ updateConsumerElements(consumers, targetValue, providerTemplate);
152
153
  }
153
154
  }, debounceDelay);
154
155
  };
@@ -158,14 +159,15 @@ class InputProvider extends HTMLElement {
158
159
  if (inputElement.type !== 'checkbox') {
159
160
  onInputKeyUp = async (event) => {
160
161
  debounce(async () => {
162
+ let providerTemplate = this.getAttribute('provider-template');
161
163
  if (event.target.value === this.lastValue) return;
162
164
  this.lastValue = event.target.value;
163
165
  if (event.target.value === '') {
164
- updateConsumerElements(this.parentElement, consumers, '');
166
+ updateConsumerElements(consumers, '', providerTemplate);
165
167
  } else {
166
168
  let targetValue = inputElement.type === 'number' ?
167
169
  Number(event.target.value) : event.target.value;
168
- updateConsumerElements(this.parentElement, consumers, targetValue);
170
+ updateConsumerElements(consumers, targetValue, providerTemplate);
169
171
  }
170
172
  }, debounceDelay);
171
173
  };
package/model-input.js CHANGED
@@ -8,6 +8,7 @@ class ModelInput extends SocketConsumer {
8
8
  this.isReady = false;
9
9
  this.inputElement = null;
10
10
  }
11
+
11
12
  connectedCallback() {
12
13
  this.isReady = true;
13
14
  this.destroy = () => {};
@@ -22,10 +23,7 @@ class ModelInput extends SocketConsumer {
22
23
  return [
23
24
  'input-id',
24
25
  'list',
25
- 'model-instance-property',
26
- 'collection-instance-property',
27
- 'bind-to-model',
28
- 'bind-to-collection',
26
+ 'socket-instance-property',
29
27
  'type',
30
28
  'placeholder',
31
29
  'consumers',
@@ -89,10 +87,7 @@ class ModelInput extends SocketConsumer {
89
87
 
90
88
  render() {
91
89
  this.innerHTML = '';
92
- let bindToModel = this.hasAttribute('bind-to-model');
93
- let modelInstanceProperty = this.getAttribute('model-instance-property');
94
- let bindToCollection = this.hasAttribute('bind-to-collection');
95
- let collectionInstanceProperty = this.getAttribute('collection-instance-property');
90
+ let socketInstanceProperty = this.getAttribute('socket-instance-property');
96
91
  let showErrorMessage = this.hasAttribute('show-error-message');
97
92
  let inputId = this.getAttribute('input-id');
98
93
  let autocapitalize = this.getAttribute('autocapitalize');
@@ -107,76 +102,43 @@ class ModelInput extends SocketConsumer {
107
102
  let height = this.getAttribute('height');
108
103
  let hideErrorLogs = this.hasAttribute('hide-error-logs');
109
104
  let enableRebound = this.hasAttribute('enable-rebound');
110
- let currentNode = this.parentNode;
111
- let model;
112
- let isModelLocal = false;
113
- if (bindToCollection) {
114
- collectionInstanceProperty = 'collection';
115
- }
116
- if (collectionInstanceProperty) {
117
- let collection;
105
+
106
+ let socket;
107
+ if (socketInstanceProperty) {
108
+ let currentNode = this.parentNode;
118
109
  while (currentNode) {
119
- collection = currentNode[collectionInstanceProperty];
120
- if (collection && collection.type !== modelType) {
121
- collection = null;
122
- }
123
- if (collection && (!collection.agModels || !collection.agModels[modelId])) {
124
- collection = null;
125
- }
126
- if (collection) break;
110
+ socket = currentNode[socketInstanceProperty];
111
+ if (socket) break;
127
112
  currentNode = currentNode.getRootNode().host || currentNode.parentNode;
128
113
  }
129
- if (collection) {
130
- model = collection.agModels[modelId];
131
- if (!model.agFields[modelField]) {
132
- model.addField(modelField);
133
- }
134
- }
114
+ if (!socket) {
115
+ throw new Error(
116
+ `The ${
117
+ this.nodeName.toLowerCase()
118
+ } element failed to bind to a socket via the ${
119
+ socketInstanceProperty
120
+ } socket-instance-property`
121
+ );
122
+ };
123
+ } else {
124
+ socket = this.getSocket();
135
125
  }
136
- if (!model) {
137
- if (bindToModel) {
138
- modelInstanceProperty = 'model';
139
- }
140
- currentNode = this.parentNode;
141
- if (modelInstanceProperty) {
142
- while (currentNode) {
143
- model = currentNode[modelInstanceProperty];
144
- if (model && modelType && (model.type !== modelType || !(model.fields || []).includes(modelField))) {
145
- model = null;
146
- }
147
- if (model && !model.agFields) {
148
- model = null;
149
- }
150
- if (model) break;
151
- currentNode = currentNode.getRootNode().host || currentNode.parentNode;
152
- }
153
- if (!model) {
154
- throw new Error(
155
- `The ${
156
- this.nodeName.toLowerCase()
157
- } element failed to bind to a model - Ensure that the element is nested inside a parent element which exposes a model instance of the same type with a ${modelField} field`
126
+ this.socket = socket;
127
+ let model = new AGModel({
128
+ socket: this.socket,
129
+ type: modelType,
130
+ id: modelId,
131
+ fields: [ modelField ],
132
+ enableRebound
133
+ });
134
+ if (!hideErrorLogs) {
135
+ (async () => {
136
+ for await (let { error } of model.listener('error')) {
137
+ console.error(
138
+ `Model input encountered an error: ${error.message}`
158
139
  );
159
- };
160
- } else {
161
- this.socket = this.getSocket();
162
- model = new AGModel({
163
- socket: this.socket,
164
- type: modelType,
165
- id: modelId,
166
- fields: [ modelField ],
167
- enableRebound
168
- });
169
- if (!hideErrorLogs) {
170
- (async () => {
171
- for await (let { error } of model.listener('error')) {
172
- console.error(
173
- `Model input encountered an error: ${error.message}`
174
- );
175
- }
176
- })();
177
140
  }
178
- isModelLocal = true;
179
- }
141
+ })();
180
142
  }
181
143
  if (model.isLoaded) {
182
144
  this.setAttribute('is-loaded', '');
@@ -240,22 +202,21 @@ class ModelInput extends SocketConsumer {
240
202
  this.destroy = () => {
241
203
  destroyInputSync();
242
204
  this.isLoadedConsumer && this.isLoadedConsumer.kill();
243
- if (isModelLocal) {
244
- model.destroy();
245
- }
205
+ model.destroy();
246
206
  };
247
207
  }
248
208
 
249
209
  updateInputElement(inputElement, fieldValue) {
250
210
  let consumers = this.getAttribute('consumers');
211
+ let providerTemplate = this.getAttribute('provider-template');
251
212
  if (inputElement.type === 'checkbox') {
252
213
  let checked = !!fieldValue;
253
214
  inputElement.checked = checked;
254
- updateConsumerElements(this.parentElement, consumers, checked);
215
+ updateConsumerElements(consumers, checked, providerTemplate);
255
216
  } else {
256
217
  let defaultValue = this.getAttribute('default-value') || '';
257
218
  inputElement.value = fieldValue == null ? defaultValue : fieldValue;
258
- updateConsumerElements(this.parentElement, consumers, inputElement.value);
219
+ updateConsumerElements(consumers, inputElement.value, providerTemplate);
259
220
  }
260
221
  }
261
222
 
@@ -303,12 +264,14 @@ class ModelInput extends SocketConsumer {
303
264
  let debounce = debouncer();
304
265
 
305
266
  let onInputChange = (event) => {
306
- if (event.target.value === String(model.value[fieldName])) return;
307
267
  debounce(async () => {
268
+ if (event.target.value === String(model.value[fieldName] || '')) return;
269
+
270
+ let providerTemplate = this.getAttribute('provider-template');
308
271
  try {
309
272
  if (inputElement.type === 'checkbox') {
310
273
  let checked = !!inputElement.checked;
311
- updateConsumerElements(this.parentElement, consumers, checked);
274
+ updateConsumerElements(consumers, checked, providerTemplate);
312
275
  await model.update(fieldName, checked);
313
276
  inputElement.classList.remove(errorStyleClass);
314
277
  inputElement.classList.add(successStyleClass);
@@ -316,12 +279,12 @@ class ModelInput extends SocketConsumer {
316
279
  return;
317
280
  }
318
281
  if (event.target.value === '') {
319
- updateConsumerElements(this.parentElement, consumers, '');
282
+ updateConsumerElements(consumers, '', providerTemplate);
320
283
  await model.delete(fieldName);
321
284
  } else {
322
285
  let targetValue = inputElement.type === 'number' ?
323
286
  Number(event.target.value) : event.target.value;
324
- updateConsumerElements(this.parentElement, consumers, targetValue);
287
+ updateConsumerElements(consumers, targetValue, providerTemplate);
325
288
  await model.update(fieldName, targetValue);
326
289
  }
327
290
  inputElement.classList.remove(errorStyleClass);
@@ -340,14 +303,17 @@ class ModelInput extends SocketConsumer {
340
303
  if (inputElement.type !== 'checkbox' && inputElement.type !== 'select') {
341
304
  onInputKeyUp = async (event) => {
342
305
  debounce(async () => {
306
+ if (event.target.value === String(model.value[fieldName] || '')) return;
307
+
308
+ let providerTemplate = this.getAttribute('provider-template');
343
309
  try {
344
310
  if (event.target.value === '') {
345
311
  await model.delete(fieldName);
346
- updateConsumerElements(this.parentElement, consumers, '');
312
+ updateConsumerElements(consumers, '', providerTemplate);
347
313
  } else {
348
314
  let targetValue = inputElement.type === 'number' ?
349
315
  Number(event.target.value) : event.target.value;
350
- updateConsumerElements(this.parentElement, consumers, targetValue);
316
+ updateConsumerElements(consumers, targetValue, providerTemplate);
351
317
  await model.update(fieldName, targetValue);
352
318
  }
353
319
  inputElement.classList.remove(errorStyleClass);
package/model-text.js CHANGED
@@ -20,10 +20,7 @@ class ModelText extends SocketConsumer {
20
20
 
21
21
  static get observedAttributes() {
22
22
  return [
23
- 'model-instance-property',
24
- 'collection-instance-property',
25
- 'bind-to-collection',
26
- 'bind-to-model',
23
+ 'socket-instance-property',
27
24
  'model-type',
28
25
  'model-id',
29
26
  'model-field',
@@ -39,84 +36,49 @@ class ModelText extends SocketConsumer {
39
36
 
40
37
  render() {
41
38
  this.innerHTML = '';
42
- let bindToModel = this.hasAttribute('bind-to-model');
43
- let modelInstanceProperty = this.getAttribute('model-instance-property');
44
- let bindToCollection = this.hasAttribute('bind-to-collection');
45
- let collectionInstanceProperty = this.getAttribute('collection-instance-property');
39
+ let socketInstanceProperty = this.getAttribute('socket-instance-property');
46
40
  let modelType = this.getAttribute('model-type');
47
41
  let modelId = this.getAttribute('model-id');
48
42
  let modelField = this.getAttribute('model-field');
49
43
  let hideErrorLogs = this.hasAttribute('hide-error-logs');
50
- let currentNode = this.parentNode;
51
- let model;
52
- let isModelLocal = false;
53
- if (bindToCollection) {
54
- collectionInstanceProperty = 'collection';
55
- }
56
- if (collectionInstanceProperty) {
57
- let collection;
44
+
45
+ let socket;
46
+ if (socketInstanceProperty) {
47
+ let currentNode = this.parentNode;
58
48
  while (currentNode) {
59
- collection = currentNode[collectionInstanceProperty];
60
- if (collection && collection.type !== modelType) {
61
- collection = null;
62
- }
63
- if (collection && (!collection.agModels || !collection.agModels[modelId])) {
64
- collection = null;
65
- }
66
- if (collection) break;
49
+ socket = currentNode[socketInstanceProperty];
50
+ if (socket) break;
67
51
  currentNode = currentNode.getRootNode().host || currentNode.parentNode;
68
52
  }
69
- if (collection) {
70
- model = collection.agModels[modelId];
71
- if (!model.agFields[modelField]) {
72
- model.addField(modelField);
73
- }
74
- }
53
+ if (!socket) {
54
+ throw new Error(
55
+ `The ${
56
+ this.nodeName.toLowerCase()
57
+ } element failed to bind to a socket via the ${
58
+ socketInstanceProperty
59
+ } socket-instance-property`
60
+ );
61
+ };
62
+ } else {
63
+ socket = this.getSocket();
75
64
  }
76
- if (!model) {
77
- if (bindToModel) {
78
- modelInstanceProperty = 'model';
79
- }
80
- currentNode = this.parentNode;
81
- if (modelInstanceProperty) {
82
- while (currentNode) {
83
- model = currentNode[modelInstanceProperty];
84
- if (model && modelType && (model.type !== modelType || !(model.fields || []).includes(modelField))) {
85
- model = null;
86
- }
87
- if (model && !model.agFields) {
88
- model = null;
89
- }
90
- if (model) break;
91
- currentNode = currentNode.getRootNode().host || currentNode.parentNode;
92
- }
93
- if (!model) {
94
- throw new Error(
95
- `The ${
96
- this.nodeName.toLowerCase()
97
- } element failed to bind to a model - Ensure that the element is nested inside a parent element which exposes a model instance of the same type with a ${modelField} field`
65
+ this.socket = socket;
66
+ let model = new AGModel({
67
+ socket: this.socket,
68
+ type: modelType,
69
+ id: modelId,
70
+ fields: [ modelField ]
71
+ });
72
+ if (!hideErrorLogs) {
73
+ (async () => {
74
+ for await (let { error } of model.listener('error')) {
75
+ console.error(
76
+ `Model text encountered an error: ${error.message}`
98
77
  );
99
- };
100
- } else {
101
- this.socket = this.getSocket();
102
- model = new AGModel({
103
- socket: this.socket,
104
- type: modelType,
105
- id: modelId,
106
- fields: [ modelField ]
107
- });
108
- if (!hideErrorLogs) {
109
- (async () => {
110
- for await (let { error } of model.listener('error')) {
111
- console.error(
112
- `Model text encountered an error: ${error.message}`
113
- );
114
- }
115
- })();
116
78
  }
117
- isModelLocal = true;
118
- }
79
+ })();
119
80
  }
81
+
120
82
  if (model.isLoaded) {
121
83
  this.setAttribute('is-loaded', '');
122
84
  this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
@@ -142,9 +104,7 @@ class ModelText extends SocketConsumer {
142
104
  this.destroy = () => {
143
105
  changeConsumer.kill();
144
106
  this.isLoadedConsumer && this.isLoadedConsumer.kill();
145
- if (isModelLocal) {
146
- model.destroy();
147
- }
107
+ model.destroy();
148
108
  };
149
109
  }
150
110
  }
package/model-viewer.js CHANGED
@@ -16,19 +16,14 @@ class ModelViewer extends SocketConsumer {
16
16
 
17
17
  connectedCallback() {
18
18
  this.isReady = true;
19
- this.socket = this.getSocket();
20
19
  this.activeLoader = null;
21
20
  this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
22
21
  this.render();
23
22
  }
24
23
 
25
24
  disconnectedCallback() {
26
- if (this.model && this.isModelLocal) {
25
+ if (this.model) {
27
26
  this.model.destroy();
28
- } else {
29
- if (this.modelLoadConsumer) this.modelLoadConsumer.kill();
30
- if (this.modelChangeConsumer) this.modelChangeConsumer.kill();
31
- if (this.modelErrorConsumer) this.modelErrorConsumer.kill();
32
27
  }
33
28
  this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
34
29
  }
@@ -93,43 +88,38 @@ class ModelViewer extends SocketConsumer {
93
88
  let modelType = this.getAttribute('model-type');
94
89
  let modelId = this.getAttribute('model-id');
95
90
  let modelFields = this.getAttribute('model-fields') || '';
96
- let modelInstanceProperty = this.getAttribute('model-instance-property');
91
+ let socketInstanceProperty = this.getAttribute('socket-instance-property');
97
92
  let hideErrorLogs = this.hasAttribute('hide-error-logs');
98
93
 
99
- if (this.model && this.isModelLocal) this.model.destroy();
94
+ if (this.model) this.model.destroy();
100
95
 
101
- let currentNode = this.parentNode;
102
- let model;
103
- if (modelInstanceProperty) {
96
+ let socket;
97
+ if (socketInstanceProperty) {
98
+ let currentNode = this.parentNode;
104
99
  while (currentNode) {
105
- model = currentNode[modelInstanceProperty];
106
- if (model && modelType && (model.type !== modelType || !(model.fields || []).includes(modelField))) {
107
- model = null;
108
- }
109
- if (model && !model.agFields) {
110
- model = null;
111
- }
112
- if (model) break;
100
+ socket = currentNode[socketInstanceProperty];
101
+ if (socket) break;
113
102
  currentNode = currentNode.getRootNode().host || currentNode.parentNode;
114
103
  }
115
- if (!model) {
104
+ if (!socket) {
116
105
  throw new Error(
117
106
  `The ${
118
107
  this.nodeName.toLowerCase()
119
- } element failed to obtain a model via the specified model-instance-property - Ensure that the element is nested inside a parent element which exposes a model instance of the same type which has the relevant fields`
108
+ } element failed to bind to a socket via the ${
109
+ socketInstanceProperty
110
+ } socket-instance-property`
120
111
  );
121
112
  };
122
- this.model = model;
123
- this.isModelLocal = false;
124
113
  } else {
125
- this.model = new AGModel({
126
- socket: this.socket,
127
- type: modelType,
128
- id: modelId,
129
- fields: modelFields.split(',').map(field => field.trim()).filter(field => field)
130
- });
131
- this.isModelLocal = true;
114
+ socket = this.getSocket();
132
115
  }
116
+ this.socket = socket;
117
+ this.model = new AGModel({
118
+ socket: this.socket,
119
+ type: modelType,
120
+ id: modelId,
121
+ fields: modelFields.split(',').map(field => field.trim()).filter(field => field)
122
+ });
133
123
 
134
124
  this.shadowRoot.innerHTML = `
135
125
  <slot name="loader"></slot>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "2.15.2",
3
+ "version": "3.0.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -17,8 +17,8 @@
17
17
  "author": "Jonathan Gros-Dubois",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "ag-collection": "^4.1.0",
21
- "ag-model": "^2.1.0",
20
+ "ag-collection": "^5.0.0",
21
+ "ag-model": "^3.0.0",
22
22
  "capitalisk-auth-client": "^1.5.1",
23
23
  "socketcluster-client": "^19.1.0"
24
24
  },
package/utils.js CHANGED
@@ -89,6 +89,10 @@ export function createReactiveCollection(collectionOptions, callback) {
89
89
  }
90
90
  })();
91
91
 
92
+ if (collection.isLoaded) {
93
+ collection.safeValue = collection.value.map(toSafeModelValue);
94
+ }
95
+
92
96
  return collection;
93
97
  }
94
98
 
@@ -120,6 +124,10 @@ export function createReactiveModel(modelOptions, callback) {
120
124
  }
121
125
  })();
122
126
 
127
+ if (model.isLoaded) {
128
+ model.safeValue = toSafeModelValue(model.value);
129
+ }
130
+
123
131
  return model;
124
132
  }
125
133
 
@@ -142,6 +150,10 @@ export function createCollection(collectionOptions) {
142
150
  }
143
151
  })();
144
152
 
153
+ if (collection.isLoaded) {
154
+ collection.safeValue = collection.value.map(toSafeModelValue);
155
+ }
156
+
145
157
  return collection;
146
158
  }
147
159
 
@@ -162,6 +174,10 @@ export function createModel(modelOptions) {
162
174
  }
163
175
  })();
164
176
 
177
+ if (model.isLoaded) {
178
+ model.safeValue = toSafeModelValue(model.value);
179
+ }
180
+
165
181
  return model;
166
182
  }
167
183
 
@@ -232,8 +248,8 @@ export function renderTemplate(templateString, data, socket) {
232
248
  });
233
249
  }
234
250
 
235
- export function updateConsumerElements(parentElement, consumers, value) {
236
- if (parentElement && consumers) {
251
+ export function updateConsumerElements(consumers, value, template) {
252
+ if (consumers) {
237
253
  let consumerParts = consumers.split(',')
238
254
  .filter(part => part)
239
255
  .map(part => {
@@ -242,8 +258,15 @@ export function updateConsumerElements(parentElement, consumers, value) {
242
258
  })
243
259
  .filter(([ selector, attributeName ]) => selector);
244
260
 
261
+ if (template) {
262
+ value = renderTemplate(
263
+ template,
264
+ { value }
265
+ );
266
+ }
267
+
245
268
  for (let [ selector, attributeName ] of consumerParts) {
246
- let matchingElements = parentElement.querySelectorAll(selector);
269
+ let matchingElements = document.querySelectorAll(selector);
247
270
  if (attributeName) {
248
271
  for (let element of matchingElements) {
249
272
  if (typeof value === 'boolean') {