saasufy-components 2.0.0 → 2.2.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 +9 -0
- package/collection-adder-group.js +42 -0
- package/collection-adder.js +85 -51
- package/conditional-group.js +30 -0
- package/model-viewer.js +51 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,3 +76,12 @@ A general purpose modal component.
|
|
|
76
76
|
### render-group
|
|
77
77
|
|
|
78
78
|
A component which can be used to guarantee that certain children components are always rendered at the same time (once they are loaded) to provide a smooth user experience.
|
|
79
|
+
|
|
80
|
+
### conditional-group
|
|
81
|
+
|
|
82
|
+
A modal component which exposes a `show-content` property which can be set to true or false to show or hide its content.
|
|
83
|
+
It's intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false value of the `show-content` attribute can be computed using a template `{{expression}}` placeholder.
|
|
84
|
+
|
|
85
|
+
### collection-adder-group
|
|
86
|
+
|
|
87
|
+
A component which can be used to group together multiple `collection-adder` components. It can be used to insert multiple records into a collection via a single button click.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class CollectionAdderGroup extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.attachShadow({ mode: 'open' });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
this.shadowRoot.innerHTML = `
|
|
9
|
+
<style>
|
|
10
|
+
.hidden {
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
<slot name="success" class="hidden"></slot>
|
|
15
|
+
<slot name="error-container" class="hidden"></slot>
|
|
16
|
+
<slot name="collection-adder"></slot>
|
|
17
|
+
<slot name="submit-button"></slot>
|
|
18
|
+
`;
|
|
19
|
+
this.shadowRoot.querySelector('slot[name="submit-button"]').addEventListener('click', async (event) => {
|
|
20
|
+
let collectionAdders = this.shadowRoot.querySelector('slot[name="collection-adder"]').assignedNodes();
|
|
21
|
+
try {
|
|
22
|
+
await Promise.all(
|
|
23
|
+
collectionAdders.map(async (adder) => adder.submit())
|
|
24
|
+
);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
let errorContainerSlot = this.shadowRoot.querySelector('slot[name="error-container"]');
|
|
27
|
+
errorContainerSlot.classList.remove('hidden');
|
|
28
|
+
this.shadowRoot.querySelector('slot[name="success"]').classList.add('hidden');
|
|
29
|
+
let errorContainers = errorContainerSlot.assignedNodes();
|
|
30
|
+
for (let container of errorContainers) {
|
|
31
|
+
container.textContent = error.message;
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let successSlot = this.shadowRoot.querySelector('slot[name="success"]');
|
|
36
|
+
successSlot.classList.remove('hidden');
|
|
37
|
+
this.shadowRoot.querySelector('slot[name="error-container"]').classList.add('hidden');
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
window.customElements.define('collection-adder-group', CollectionAdderGroup);
|
package/collection-adder.js
CHANGED
|
@@ -32,8 +32,11 @@ class CollectionAdder extends SocketConsumer {
|
|
|
32
32
|
return [
|
|
33
33
|
'collection-type',
|
|
34
34
|
'collection-fields',
|
|
35
|
+
'field-labels',
|
|
35
36
|
'model-values',
|
|
36
|
-
'submit-button-label'
|
|
37
|
+
'submit-button-label',
|
|
38
|
+
'hide-submit-button',
|
|
39
|
+
'success-message'
|
|
37
40
|
];
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -80,22 +83,96 @@ class CollectionAdder extends SocketConsumer {
|
|
|
80
83
|
};
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
async submit() {
|
|
87
|
+
if (!this.isReady) {
|
|
88
|
+
throw new Error('Collection adder form is not ready to be submitted');
|
|
89
|
+
}
|
|
90
|
+
let successMessage = this.getAttribute('success-message');
|
|
91
|
+
let messageContainer = this.querySelector('.collection-adder-message-container');
|
|
92
|
+
|
|
93
|
+
messageContainer.classList.remove('success');
|
|
94
|
+
messageContainer.classList.remove('error');
|
|
95
|
+
messageContainer.textContent = '';
|
|
96
|
+
|
|
97
|
+
let newModelData = {
|
|
98
|
+
...this.modelFieldValues,
|
|
99
|
+
...Object.fromEntries(
|
|
100
|
+
[...this.querySelectorAll('.collection-adder-input')]
|
|
101
|
+
.filter(input => input.value !== '')
|
|
102
|
+
.map((input) => {
|
|
103
|
+
let fieldType = this.fieldTypes[input.name];
|
|
104
|
+
let Type = this.getTypeCastFunction(fieldType);
|
|
105
|
+
let value;
|
|
106
|
+
if (fieldType === 'checkbox') {
|
|
107
|
+
value = input.checked;
|
|
108
|
+
} else {
|
|
109
|
+
value = input.value;
|
|
110
|
+
}
|
|
111
|
+
return [ input.name, Type(value) ];
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await this.collection.create(newModelData);
|
|
118
|
+
messageContainer.classList.add('success');
|
|
119
|
+
messageContainer.classList.remove('error');
|
|
120
|
+
this.dispatchEvent(
|
|
121
|
+
new CustomEvent('success', {
|
|
122
|
+
detail: newModelData
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
let form = this.querySelector('.collection-adder-form');
|
|
126
|
+
form.reset();
|
|
127
|
+
if (successMessage) {
|
|
128
|
+
messageContainer.textContent = successMessage;
|
|
129
|
+
} else {
|
|
130
|
+
messageContainer.textContent = '';
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
messageContainer.classList.add('error');
|
|
134
|
+
messageContainer.classList.remove('success');
|
|
135
|
+
messageContainer.textContent = error.message;
|
|
136
|
+
this.dispatchEvent(
|
|
137
|
+
new CustomEvent('error', {
|
|
138
|
+
detail: error
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
reset() {
|
|
145
|
+
let form = this.querySelector('.collection-adder-form');
|
|
146
|
+
if (form) {
|
|
147
|
+
form.reset();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
83
151
|
render() {
|
|
84
152
|
let submitButtonLabel = this.getAttribute('submit-button-label');
|
|
85
153
|
if (submitButtonLabel == null) {
|
|
86
154
|
submitButtonLabel = 'Submit';
|
|
87
155
|
}
|
|
156
|
+
let hideSubmitButton = this.hasAttribute('hide-submit-button');
|
|
88
157
|
let collectionType = this.getAttribute('collection-type');
|
|
89
158
|
let {
|
|
90
159
|
fieldNames,
|
|
91
160
|
fieldTypes
|
|
92
161
|
} = this.getFieldDetails(this.getAttribute('collection-fields'));
|
|
93
162
|
|
|
163
|
+
this.fieldTypes = fieldTypes;
|
|
164
|
+
|
|
94
165
|
let {
|
|
95
166
|
fieldNames: modelFieldNames,
|
|
96
167
|
fieldValues: modelFieldValues
|
|
97
168
|
} = this.getFieldDetails(this.getAttribute('model-values'));
|
|
98
169
|
|
|
170
|
+
let {
|
|
171
|
+
fieldValues: modelFieldLabels
|
|
172
|
+
} = this.getFieldDetails(this.getAttribute('field-labels'));
|
|
173
|
+
|
|
174
|
+
this.modelFieldValues = modelFieldValues;
|
|
175
|
+
|
|
99
176
|
if (this.collection) this.collection.destroy();
|
|
100
177
|
|
|
101
178
|
this.collection = new AGCollection({
|
|
@@ -114,11 +191,12 @@ class CollectionAdder extends SocketConsumer {
|
|
|
114
191
|
inputType = inputTypeParts[1];
|
|
115
192
|
inputTypeParams = inputTypeParts[3] ? inputTypeParts[3].split(',') : [];
|
|
116
193
|
}
|
|
194
|
+
let inputLabel = modelFieldLabels[field] == null ? field : modelFieldLabels[field];
|
|
117
195
|
if (inputType === 'checkbox') {
|
|
118
196
|
items.push(
|
|
119
197
|
`<label>
|
|
120
198
|
${
|
|
121
|
-
|
|
199
|
+
inputLabel
|
|
122
200
|
}
|
|
123
201
|
<input class="collection-adder-input" type="${
|
|
124
202
|
inputType
|
|
@@ -133,7 +211,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
133
211
|
<input class="collection-adder-input" type="text" name="${
|
|
134
212
|
field
|
|
135
213
|
}" placeholder="${
|
|
136
|
-
|
|
214
|
+
inputLabel
|
|
137
215
|
}" />
|
|
138
216
|
<select class="collection-adder-select">
|
|
139
217
|
<option value="" selected disabled hidden>Options</option>
|
|
@@ -146,7 +224,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
146
224
|
`<textarea class="collection-adder-input" name="${
|
|
147
225
|
field
|
|
148
226
|
}" placeholder="${
|
|
149
|
-
|
|
227
|
+
inputLabel
|
|
150
228
|
}"></textarea>`
|
|
151
229
|
);
|
|
152
230
|
} else {
|
|
@@ -156,7 +234,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
156
234
|
}" name="${
|
|
157
235
|
field
|
|
158
236
|
}" placeholder="${
|
|
159
|
-
|
|
237
|
+
inputLabel
|
|
160
238
|
}" />`
|
|
161
239
|
);
|
|
162
240
|
}
|
|
@@ -167,7 +245,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
167
245
|
<div class="collection-adder-message-container"></div>
|
|
168
246
|
<div class="collection-adder-form-content">
|
|
169
247
|
${items.join('')}
|
|
170
|
-
|
|
248
|
+
${hideSubmitButton ? '' : `<input type="submit" value="${submitButtonLabel}" />`}
|
|
171
249
|
</div>
|
|
172
250
|
</form>
|
|
173
251
|
`;
|
|
@@ -182,54 +260,10 @@ class CollectionAdder extends SocketConsumer {
|
|
|
182
260
|
});
|
|
183
261
|
}
|
|
184
262
|
|
|
185
|
-
let messageContainer = this.querySelector('.collection-adder-message-container');
|
|
186
|
-
|
|
187
263
|
let form = this.querySelector('.collection-adder-form');
|
|
188
264
|
form.addEventListener('submit', async (event) => {
|
|
189
265
|
event.preventDefault();
|
|
190
|
-
|
|
191
|
-
messageContainer.classList.remove('error');
|
|
192
|
-
messageContainer.textContent = '';
|
|
193
|
-
|
|
194
|
-
let newModelData = {
|
|
195
|
-
...modelFieldValues,
|
|
196
|
-
...Object.fromEntries(
|
|
197
|
-
[...event.target.querySelectorAll('.collection-adder-input')]
|
|
198
|
-
.filter(input => input.value !== '')
|
|
199
|
-
.map((input) => {
|
|
200
|
-
let fieldType = fieldTypes[input.name];
|
|
201
|
-
let Type = this.getTypeCastFunction(fieldType);
|
|
202
|
-
let value;
|
|
203
|
-
if (fieldType === 'checkbox') {
|
|
204
|
-
value = input.checked;
|
|
205
|
-
} else {
|
|
206
|
-
value = input.value;
|
|
207
|
-
}
|
|
208
|
-
return [ input.name, Type(value) ];
|
|
209
|
-
})
|
|
210
|
-
)
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
await this.collection.create(newModelData);
|
|
215
|
-
messageContainer.classList.add('success');
|
|
216
|
-
messageContainer.classList.remove('error');
|
|
217
|
-
this.dispatchEvent(
|
|
218
|
-
new CustomEvent('success', {
|
|
219
|
-
detail: newModelData
|
|
220
|
-
})
|
|
221
|
-
);
|
|
222
|
-
form.reset();
|
|
223
|
-
} catch (error) {
|
|
224
|
-
messageContainer.classList.add('error');
|
|
225
|
-
messageContainer.classList.remove('success');
|
|
226
|
-
messageContainer.textContent = error.message;
|
|
227
|
-
this.dispatchEvent(
|
|
228
|
-
new CustomEvent('error', {
|
|
229
|
-
detail: error
|
|
230
|
-
})
|
|
231
|
-
);
|
|
232
|
-
}
|
|
266
|
+
await this.submit();
|
|
233
267
|
});
|
|
234
268
|
}
|
|
235
269
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class ConditionalGroup extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.attachShadow({ mode: 'open' });
|
|
5
|
+
this.isReady = false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static get observedAttributes() {
|
|
9
|
+
return [
|
|
10
|
+
'show-content',
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
attributeChangedCallback() {
|
|
15
|
+
if (!this.isReady) return;
|
|
16
|
+
this.render();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
connectedCallback() {
|
|
20
|
+
this.isReady = true;
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
let showContent = this.getAttribute('show-content');
|
|
26
|
+
this.shadowRoot.innerHTML = showContent != null && showContent !== 'false' ? '<slot></slot>' : '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
window.customElements.define('conditional-group', ConditionalGroup);
|
package/model-viewer.js
CHANGED
|
@@ -21,7 +21,13 @@ class ModelViewer extends SocketConsumer {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
disconnectedCallback() {
|
|
24
|
-
if (this.model
|
|
24
|
+
if (this.model && this.isModelLocal) {
|
|
25
|
+
this.model.destroy();
|
|
26
|
+
} else {
|
|
27
|
+
if (this.modelLoadConsumer) this.modelLoadConsumer.kill();
|
|
28
|
+
if (this.modelChangeConsumer) this.modelChangeConsumer.kill();
|
|
29
|
+
if (this.modelErrorConsumer) this.modelErrorConsumer.kill();
|
|
30
|
+
}
|
|
25
31
|
this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -85,16 +91,40 @@ class ModelViewer extends SocketConsumer {
|
|
|
85
91
|
let modelType = this.getAttribute('model-type');
|
|
86
92
|
let modelId = this.getAttribute('model-id');
|
|
87
93
|
let modelFields = this.getAttribute('model-fields') || '';
|
|
94
|
+
let modelInstanceProperty = this.getAttribute('model-instance-property');
|
|
88
95
|
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
89
96
|
|
|
90
|
-
if (this.model) this.model.destroy();
|
|
97
|
+
if (this.model && this.isModelLocal) this.model.destroy();
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
let currentNode = this.parentNode;
|
|
100
|
+
let model;
|
|
101
|
+
if (modelInstanceProperty) {
|
|
102
|
+
while (currentNode) {
|
|
103
|
+
model = currentNode[modelInstanceProperty];
|
|
104
|
+
if (model && modelType && (model.type !== modelType || !(model.fields || []).includes(modelField))) {
|
|
105
|
+
model = null;
|
|
106
|
+
}
|
|
107
|
+
if (model) break;
|
|
108
|
+
currentNode = currentNode.getRootNode().host || currentNode.parentNode;
|
|
109
|
+
}
|
|
110
|
+
if (!model) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`The ${
|
|
113
|
+
this.nodeName.toLowerCase()
|
|
114
|
+
} 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 field`
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
this.model = model;
|
|
118
|
+
this.isModelLocal = false;
|
|
119
|
+
} else {
|
|
120
|
+
this.model = new AGModel({
|
|
121
|
+
socket: this.socket,
|
|
122
|
+
type: modelType,
|
|
123
|
+
id: modelId,
|
|
124
|
+
fields: modelFields.split(',').map(field => field.trim()).filter(field => field)
|
|
125
|
+
});
|
|
126
|
+
this.isModelLocal = true;
|
|
127
|
+
}
|
|
98
128
|
|
|
99
129
|
this.shadowRoot.innerHTML = `
|
|
100
130
|
<style>
|
|
@@ -109,7 +139,9 @@ class ModelViewer extends SocketConsumer {
|
|
|
109
139
|
`;
|
|
110
140
|
|
|
111
141
|
(async () => {
|
|
112
|
-
|
|
142
|
+
this.modelLoadConsumer = this.model.listener('load').createConsumer();
|
|
143
|
+
let packet = await this.modelLoadConsumer.next();
|
|
144
|
+
if (packet.done) return;
|
|
113
145
|
if (!this.modelValueExists(this.model.value)) {
|
|
114
146
|
this.renderItem();
|
|
115
147
|
}
|
|
@@ -117,15 +149,23 @@ class ModelViewer extends SocketConsumer {
|
|
|
117
149
|
this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
|
|
118
150
|
})();
|
|
119
151
|
|
|
152
|
+
if (this.model.isLoaded) {
|
|
153
|
+
this.renderItem();
|
|
154
|
+
this.setAttribute('is-loaded', '');
|
|
155
|
+
this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
|
|
156
|
+
}
|
|
157
|
+
|
|
120
158
|
(async () => {
|
|
121
|
-
|
|
159
|
+
this.modelChangeConsumer = this.model.listener('change').createConsumer();
|
|
160
|
+
for await (let event of this.modelChangeConsumer) {
|
|
122
161
|
this.renderItem();
|
|
123
162
|
}
|
|
124
163
|
})();
|
|
125
164
|
|
|
126
165
|
if (!hideErrorLogs) {
|
|
127
166
|
(async () => {
|
|
128
|
-
|
|
167
|
+
this.modelErrorConsumer = this.model.listener('error').createConsumer();
|
|
168
|
+
for await (let { error } of this.modelErrorConsumer) {
|
|
129
169
|
console.error(
|
|
130
170
|
`Model viewer encountered an error: ${error.message}`
|
|
131
171
|
);
|