saasufy-components 2.0.0 → 2.1.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 +75 -47
- 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
|
@@ -33,7 +33,9 @@ class CollectionAdder extends SocketConsumer {
|
|
|
33
33
|
'collection-type',
|
|
34
34
|
'collection-fields',
|
|
35
35
|
'model-values',
|
|
36
|
-
'submit-button-label'
|
|
36
|
+
'submit-button-label',
|
|
37
|
+
'hide-submit-button',
|
|
38
|
+
'success-message'
|
|
37
39
|
];
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -80,22 +82,92 @@ class CollectionAdder extends SocketConsumer {
|
|
|
80
82
|
};
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
async submit() {
|
|
86
|
+
if (!this.isReady) {
|
|
87
|
+
throw new Error('Collection adder form is not ready to be submitted');
|
|
88
|
+
}
|
|
89
|
+
let successMessage = this.getAttribute('success-message');
|
|
90
|
+
let messageContainer = this.querySelector('.collection-adder-message-container');
|
|
91
|
+
|
|
92
|
+
messageContainer.classList.remove('success');
|
|
93
|
+
messageContainer.classList.remove('error');
|
|
94
|
+
messageContainer.textContent = '';
|
|
95
|
+
|
|
96
|
+
let newModelData = {
|
|
97
|
+
...this.modelFieldValues,
|
|
98
|
+
...Object.fromEntries(
|
|
99
|
+
[...this.querySelectorAll('.collection-adder-input')]
|
|
100
|
+
.filter(input => input.value !== '')
|
|
101
|
+
.map((input) => {
|
|
102
|
+
let fieldType = this.fieldTypes[input.name];
|
|
103
|
+
let Type = this.getTypeCastFunction(fieldType);
|
|
104
|
+
let value;
|
|
105
|
+
if (fieldType === 'checkbox') {
|
|
106
|
+
value = input.checked;
|
|
107
|
+
} else {
|
|
108
|
+
value = input.value;
|
|
109
|
+
}
|
|
110
|
+
return [ input.name, Type(value) ];
|
|
111
|
+
})
|
|
112
|
+
)
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await this.collection.create(newModelData);
|
|
117
|
+
messageContainer.classList.add('success');
|
|
118
|
+
messageContainer.classList.remove('error');
|
|
119
|
+
this.dispatchEvent(
|
|
120
|
+
new CustomEvent('success', {
|
|
121
|
+
detail: newModelData
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
let form = this.querySelector('.collection-adder-form');
|
|
125
|
+
form.reset();
|
|
126
|
+
if (successMessage) {
|
|
127
|
+
messageContainer.textContent = successMessage;
|
|
128
|
+
} else {
|
|
129
|
+
messageContainer.textContent = '';
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
messageContainer.classList.add('error');
|
|
133
|
+
messageContainer.classList.remove('success');
|
|
134
|
+
messageContainer.textContent = error.message;
|
|
135
|
+
this.dispatchEvent(
|
|
136
|
+
new CustomEvent('error', {
|
|
137
|
+
detail: error
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
reset() {
|
|
144
|
+
let form = this.querySelector('.collection-adder-form');
|
|
145
|
+
if (form) {
|
|
146
|
+
form.reset();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
83
150
|
render() {
|
|
84
151
|
let submitButtonLabel = this.getAttribute('submit-button-label');
|
|
85
152
|
if (submitButtonLabel == null) {
|
|
86
153
|
submitButtonLabel = 'Submit';
|
|
87
154
|
}
|
|
155
|
+
let hideSubmitButton = this.hasAttribute('hide-submit-button');
|
|
88
156
|
let collectionType = this.getAttribute('collection-type');
|
|
89
157
|
let {
|
|
90
158
|
fieldNames,
|
|
91
159
|
fieldTypes
|
|
92
160
|
} = this.getFieldDetails(this.getAttribute('collection-fields'));
|
|
93
161
|
|
|
162
|
+
this.fieldTypes = fieldTypes;
|
|
163
|
+
|
|
94
164
|
let {
|
|
95
165
|
fieldNames: modelFieldNames,
|
|
96
166
|
fieldValues: modelFieldValues
|
|
97
167
|
} = this.getFieldDetails(this.getAttribute('model-values'));
|
|
98
168
|
|
|
169
|
+
this.modelFieldValues = modelFieldValues;
|
|
170
|
+
|
|
99
171
|
if (this.collection) this.collection.destroy();
|
|
100
172
|
|
|
101
173
|
this.collection = new AGCollection({
|
|
@@ -167,7 +239,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
167
239
|
<div class="collection-adder-message-container"></div>
|
|
168
240
|
<div class="collection-adder-form-content">
|
|
169
241
|
${items.join('')}
|
|
170
|
-
|
|
242
|
+
${hideSubmitButton ? '' : `<input type="submit" value="${submitButtonLabel}" />`}
|
|
171
243
|
</div>
|
|
172
244
|
</form>
|
|
173
245
|
`;
|
|
@@ -182,54 +254,10 @@ class CollectionAdder extends SocketConsumer {
|
|
|
182
254
|
});
|
|
183
255
|
}
|
|
184
256
|
|
|
185
|
-
let messageContainer = this.querySelector('.collection-adder-message-container');
|
|
186
|
-
|
|
187
257
|
let form = this.querySelector('.collection-adder-form');
|
|
188
258
|
form.addEventListener('submit', async (event) => {
|
|
189
259
|
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
|
-
}
|
|
260
|
+
await this.submit();
|
|
233
261
|
});
|
|
234
262
|
}
|
|
235
263
|
}
|
|
@@ -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
|
);
|