saasufy-components 9.0.4 → 9.1.1
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 +4 -1
- package/collection-adder-form.js +43 -0
- package/collection-adder.js +136 -74
- package/package.json +1 -1
- package/styles.css +5 -0
package/README.md
CHANGED
|
@@ -404,7 +404,7 @@ In addition to the slots shown above, the `collection-viewer` also supports the
|
|
|
404
404
|
**Attributes**
|
|
405
405
|
|
|
406
406
|
- `collection-type` (required): Specifies the type of collection to display. This should match a `Model` available in your Saasufy service.
|
|
407
|
-
- `collection-fields` (required): A comma-separated list of fields from the `Model` that you want to display or use. This lets you pick specific pieces of data from your collection to work with.
|
|
407
|
+
- `collection-fields` (required): A comma-separated list of fields from the `Model` that you want to display or use. This lets you pick specific pieces of data from your collection to work with. Note that updates made to these fields can cause the `collection-viewer` to re-render itself so you should consider whether or not a field needs to be specified at this level in the component hierarchy or should be delegated to a child component (otherwise it could cause nested `model-input` components to lose focus).
|
|
408
408
|
- `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
|
|
409
409
|
- `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
|
|
410
410
|
- `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
|
|
@@ -437,6 +437,7 @@ This component is a simplified version of the `collection-adder-form` component
|
|
|
437
437
|
collection-fields="name,brand,qty"
|
|
438
438
|
model-values="categoryName=electronics,isNew:boolean=true"
|
|
439
439
|
submit-button-label="Create"
|
|
440
|
+
show-field-errors
|
|
440
441
|
trim-spaces
|
|
441
442
|
></collection-adder>
|
|
442
443
|
```
|
|
@@ -453,6 +454,7 @@ This component is a simplified version of the `collection-adder-form` component
|
|
|
453
454
|
- `success-message`: A message to show the user if the resource has been successfully added to the collection after submitting the form.
|
|
454
455
|
- `autocapitalize`: Can be set to `off` to disable auto-capitalization of the first input character on mobile devices.
|
|
455
456
|
- `autocorrect`: Can be set to `off` to disable auto-correction of input on mobile devices.
|
|
457
|
+
- `show-field-errors`: If this attribute exists on the element, then granular error messages will be shown above each corresponding input element instead of a single form-level error message.
|
|
456
458
|
- `trim-spaces`: If this attribute exists on the element, then leading and trailing spaces will be trimmed from each input element's value before submitting the form.
|
|
457
459
|
- `consumers`: This allows you to connect this `collection-adder` to other elements on your page. It should be used in conjunction with the `provider-template` attribute. The `consumers` attribute takes a list of selectors with optional attributes to target in the format `element-selector:attribute-name`. For example `.my-input:value` will find all elements with a `my-input` class and update their `value` attributes with the output of the `collection-adder` component on submit. 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.
|
|
458
460
|
- `success-consumers`: Same as the `consumers` attribute but the target elements are only updated if insertion was a success.
|
|
@@ -492,6 +494,7 @@ This component is similar to the `collection-adder` component except that it req
|
|
|
492
494
|
- `model-values`: An optional list of key-value pairs in the format `field1=value1,field2=value2` to add to the newly created resource alongside the values collected from the user via the form. For non-string values, the type should be provided in the format `fieldName:type=value`; supported types are `string`, `number` and `boolean`. Unlike `collection-fields` which are rendered as input elements in the form, `model-values` are not shown to the user.
|
|
493
495
|
- `computable-model-values`: If this attribute exists on the element, then the `model-values` attribute will be re-computed before the form is submitted; this may be useful if the `model-values` attribute contains expressions which need to be updated before submitting.
|
|
494
496
|
- `success-message`: A message to show the user if the resource has been successfully added to the collection after submitting the form.
|
|
497
|
+
- `show-field-errors`: If this attribute exists on the element, then granular error messages will be shown above each corresponding input element instead of a single form-level error message.
|
|
495
498
|
- `trim-spaces`: If this attribute exists on the element, then leading and trailing spaces will be trimmed from each input element's value before submitting the form.
|
|
496
499
|
- `auto-reset-hidden-inputs`: If this attribute exists, then `hidden` input elements which are inside the `collection-adder-form` will be cleared along with other input fields. By default, `hidden` input elements are not cleared when the form is reset.
|
|
497
500
|
- `disable-submit-on-enter-key`: If this attribute exists, then the form will not submit when the enter key is pressed.
|
package/collection-adder-form.js
CHANGED
|
@@ -58,6 +58,14 @@ class CollectionAdderForm extends SocketConsumer {
|
|
|
58
58
|
messageContainer.textContent = '';
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Clear any existing field errors
|
|
62
|
+
let existingFieldErrors = this.querySelectorAll('.collection-adder-field-error-container');
|
|
63
|
+
existingFieldErrors.forEach(el => el.remove());
|
|
64
|
+
|
|
65
|
+
// Clear error classes from all inputs
|
|
66
|
+
let allInputs = this.querySelectorAll('input, textarea, select');
|
|
67
|
+
allInputs.forEach(input => input.classList.remove('error'));
|
|
68
|
+
|
|
61
69
|
let trimSpaces = this.hasAttribute('trim-spaces');
|
|
62
70
|
|
|
63
71
|
let inputElements = this.getAllInputElements();
|
|
@@ -158,8 +166,43 @@ class CollectionAdderForm extends SocketConsumer {
|
|
|
158
166
|
if (messageContainer) {
|
|
159
167
|
messageContainer.classList.add('error');
|
|
160
168
|
messageContainer.classList.remove('success');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.hasAttribute('show-field-errors') && error.fieldErrors) {
|
|
172
|
+
let inputElements = this.getAllInputElements();
|
|
173
|
+
|
|
174
|
+
for (let fieldName in error.fieldErrors) {
|
|
175
|
+
let fieldError = error.fieldErrors[fieldName];
|
|
176
|
+
|
|
177
|
+
// Find the input element by name
|
|
178
|
+
let input = inputElements.find(el => el.name === fieldName);
|
|
179
|
+
|
|
180
|
+
if (input) {
|
|
181
|
+
let inputType = input.type || (input.nodeName === 'TEXTAREA' ? 'textarea' : 'select');
|
|
182
|
+
|
|
183
|
+
// For radio buttons and checkboxes, just add error class to input(s)
|
|
184
|
+
if (inputType === 'radio') {
|
|
185
|
+
// Find all radio inputs with the same name
|
|
186
|
+
let radioInputs = inputElements.filter(el => el.name === fieldName && el.type === 'radio');
|
|
187
|
+
radioInputs.forEach(radio => radio.classList.add('error'));
|
|
188
|
+
} else if (inputType === 'checkbox') {
|
|
189
|
+
input.classList.add('error');
|
|
190
|
+
} else {
|
|
191
|
+
// For other input types, create and insert error message div before the input
|
|
192
|
+
let errorDiv = document.createElement('div');
|
|
193
|
+
errorDiv.classList.add('collection-adder-field-error-container');
|
|
194
|
+
errorDiv.classList.add('error');
|
|
195
|
+
let simpleFieldError = fieldError.replace(/^Invalid [^:]+:/i, '');
|
|
196
|
+
errorDiv.textContent = simpleFieldError;
|
|
197
|
+
|
|
198
|
+
input.parentNode.insertBefore(errorDiv, input);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else if (messageContainer) {
|
|
161
203
|
messageContainer.textContent = formatError(error);
|
|
162
204
|
}
|
|
205
|
+
|
|
163
206
|
this.dispatchEvent(
|
|
164
207
|
new CustomEvent('error', {
|
|
165
208
|
detail: error
|
package/collection-adder.js
CHANGED
|
@@ -59,6 +59,14 @@ class CollectionAdder extends SocketConsumer {
|
|
|
59
59
|
messageContainer.classList.remove('error');
|
|
60
60
|
messageContainer.textContent = '';
|
|
61
61
|
|
|
62
|
+
// Clear any existing field errors
|
|
63
|
+
let existingFieldErrors = this.querySelectorAll('.collection-adder-field-error-container');
|
|
64
|
+
existingFieldErrors.forEach(el => el.textContent = '');
|
|
65
|
+
|
|
66
|
+
// Clear error classes from all inputs
|
|
67
|
+
let allInputs = this.querySelectorAll('.collection-adder-input, .collection-adder-radio');
|
|
68
|
+
allInputs.forEach(input => input.classList.remove('error'));
|
|
69
|
+
|
|
62
70
|
let radioInputs = [ ...this.querySelectorAll('.collection-adder-radio') ];
|
|
63
71
|
let radioData = {};
|
|
64
72
|
for (let radio of radioInputs) {
|
|
@@ -150,7 +158,36 @@ class CollectionAdder extends SocketConsumer {
|
|
|
150
158
|
} catch (error) {
|
|
151
159
|
messageContainer.classList.add('error');
|
|
152
160
|
messageContainer.classList.remove('success');
|
|
153
|
-
|
|
161
|
+
|
|
162
|
+
if (this.hasAttribute('show-field-errors') && error.fieldErrors) {
|
|
163
|
+
for (let fieldName in error.fieldErrors) {
|
|
164
|
+
let fieldError = error.fieldErrors[fieldName];
|
|
165
|
+
let fieldContainer = this.querySelector(`.collection-adder-field-container[data-field="${fieldName}"]`);
|
|
166
|
+
|
|
167
|
+
if (fieldContainer) {
|
|
168
|
+
let fieldType = this.fieldTypes[fieldName];
|
|
169
|
+
let errorDiv = fieldContainer.querySelector('.collection-adder-field-error-container');
|
|
170
|
+
|
|
171
|
+
// For radio buttons and checkboxes, just add error class to input(s)
|
|
172
|
+
if (fieldType === 'radio') {
|
|
173
|
+
let radioInputs = fieldContainer.querySelectorAll('.collection-adder-radio');
|
|
174
|
+
radioInputs.forEach(radio => radio.classList.add('error'));
|
|
175
|
+
} else if (fieldType === 'checkbox') {
|
|
176
|
+
let input = fieldContainer.querySelector('.collection-adder-input');
|
|
177
|
+
input.classList.add('error');
|
|
178
|
+
} else {
|
|
179
|
+
// For other input types, populate the error message div
|
|
180
|
+
if (errorDiv) {
|
|
181
|
+
let simpleFieldError = fieldError.replace(/^Invalid [^:]+:/i, '');
|
|
182
|
+
errorDiv.textContent = simpleFieldError;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
messageContainer.textContent = formatError(error);
|
|
189
|
+
}
|
|
190
|
+
|
|
154
191
|
this.dispatchEvent(
|
|
155
192
|
new CustomEvent('error', {
|
|
156
193
|
detail: error
|
|
@@ -241,101 +278,126 @@ class CollectionAdder extends SocketConsumer {
|
|
|
241
278
|
let optionLabel = optionLabels[field] == null ? 'Options' : optionLabels[field];
|
|
242
279
|
if (inputType === 'checkbox') {
|
|
243
280
|
items.push(
|
|
244
|
-
`<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
281
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
282
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
283
|
+
<label>
|
|
284
|
+
${
|
|
285
|
+
inputLabel
|
|
286
|
+
}
|
|
287
|
+
<input class="collection-adder-input" type="${
|
|
288
|
+
inputType
|
|
289
|
+
}" name="${
|
|
290
|
+
field
|
|
291
|
+
}" />
|
|
292
|
+
</label>
|
|
293
|
+
</div>`
|
|
254
294
|
);
|
|
255
295
|
} else if (inputType === 'select') {
|
|
256
296
|
items.push(
|
|
257
|
-
`<
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
297
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
298
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
299
|
+
<label>
|
|
300
|
+
${
|
|
301
|
+
inputLabel
|
|
302
|
+
}
|
|
303
|
+
<select name="${field}" class="collection-adder-input collection-adder-select">
|
|
304
|
+
<option value="" selected disabled hidden>${optionLabel}</option>
|
|
305
|
+
${inputTypeParams.map(param => `<option value="${param}">${param}</option>`).join('')}
|
|
306
|
+
</select>
|
|
307
|
+
</label>
|
|
308
|
+
</div>`
|
|
266
309
|
);
|
|
267
310
|
} else if (inputType === 'text-select') {
|
|
268
311
|
items.push(
|
|
269
|
-
`<div class="collection-adder-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
313
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
314
|
+
<div class="collection-adder-select-text-row">
|
|
315
|
+
<input class="collection-adder-input" type="text" name="${
|
|
316
|
+
field
|
|
317
|
+
}" placeholder="${
|
|
318
|
+
inputLabel
|
|
319
|
+
}"${extraAttributesString} />
|
|
320
|
+
<select class="collection-adder-select">
|
|
321
|
+
<option value="" selected disabled hidden>${optionLabel}</option>
|
|
322
|
+
${inputTypeParams.map(param => `<option value="${param}">${param}</option>`).join('')}
|
|
323
|
+
</select>
|
|
324
|
+
</div>
|
|
279
325
|
</div>`
|
|
280
326
|
);
|
|
281
327
|
} else if (inputType === 'radio') {
|
|
282
328
|
items.push(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
329
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
330
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
331
|
+
<div>
|
|
332
|
+
${
|
|
333
|
+
inputLabel
|
|
334
|
+
}
|
|
335
|
+
${
|
|
336
|
+
inputTypeParams.map(
|
|
337
|
+
param => (
|
|
338
|
+
`<label>
|
|
339
|
+
<input class="collection-adder-radio" type="${
|
|
340
|
+
inputType
|
|
341
|
+
}" name="${
|
|
342
|
+
field
|
|
343
|
+
}" value=${
|
|
344
|
+
param
|
|
345
|
+
} />
|
|
346
|
+
${param}
|
|
347
|
+
</label>`
|
|
348
|
+
)
|
|
349
|
+
).join('')
|
|
350
|
+
}
|
|
351
|
+
</div>
|
|
352
|
+
</div>`
|
|
303
353
|
);
|
|
304
354
|
} else if (inputType === 'textarea') {
|
|
305
355
|
items.push(
|
|
306
|
-
`<
|
|
307
|
-
field
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
356
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
357
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
358
|
+
<textarea class="collection-adder-input" name="${
|
|
359
|
+
field
|
|
360
|
+
}" placeholder="${
|
|
361
|
+
inputLabel
|
|
362
|
+
}"${extraAttributesString}></textarea>
|
|
363
|
+
</div>`
|
|
311
364
|
);
|
|
312
365
|
} else if (inputType === 'file') {
|
|
313
366
|
items.push(
|
|
314
|
-
`<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
367
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
368
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
369
|
+
<input class="collection-adder-input" type="${
|
|
370
|
+
inputType
|
|
371
|
+
}" name="${
|
|
372
|
+
field
|
|
373
|
+
}"${extraAttributesString} />
|
|
374
|
+
</div>`
|
|
319
375
|
);
|
|
320
376
|
} else if (inputType === 'number') {
|
|
321
377
|
items.push(
|
|
322
|
-
`<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
378
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
379
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
380
|
+
<input class="collection-adder-input" type="${
|
|
381
|
+
inputType
|
|
382
|
+
}" name="${
|
|
383
|
+
field
|
|
384
|
+
}" placeholder="${
|
|
385
|
+
inputLabel
|
|
386
|
+
}" step="any"${extraAttributesString} />
|
|
387
|
+
</div>`
|
|
329
388
|
);
|
|
330
389
|
} else {
|
|
331
390
|
items.push(
|
|
332
|
-
`<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
391
|
+
`<div class="collection-adder-field-container" data-field="${field}">
|
|
392
|
+
<div class="collection-adder-field-error-container error"></div>
|
|
393
|
+
<input class="collection-adder-input" type="${
|
|
394
|
+
inputType
|
|
395
|
+
}" name="${
|
|
396
|
+
field
|
|
397
|
+
}" placeholder="${
|
|
398
|
+
inputLabel
|
|
399
|
+
}"${extraAttributesString} />
|
|
400
|
+
</div>`
|
|
339
401
|
);
|
|
340
402
|
}
|
|
341
403
|
}
|
package/package.json
CHANGED
package/styles.css
CHANGED
|
@@ -159,6 +159,11 @@ collection-adder .collection-adder-form-content > label {
|
|
|
159
159
|
align-self: flex-start;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
collection-adder .collection-adder-field-container {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
}
|
|
166
|
+
|
|
162
167
|
.label-container {
|
|
163
168
|
display: flex;
|
|
164
169
|
justify-content: flex-start;
|