saasufy-components 10.0.1 → 10.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 CHANGED
@@ -669,7 +669,7 @@ Used for displaying and editing a single field of a model instance in realtime.
669
669
  - `consumers`: This allows you to connect this `model-input` to other elements on your page. It 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 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. Note that `model-input` elements of the `file` type are treated as write-only unless the `consumers` attribute is present.
670
670
  - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers.
671
671
  - `show-error-message`: If this attribute is present, an error message will be displayed above the input if it fails to update the value.
672
- - `options`: A comma-separated list of options to provide - This only works if the input type is `select`.
672
+ - `options`: A comma-separated list of options to provide - This only works if the input type is `select`. Each option is in the format `label:type=value`; the `:type` and `=value` parts are optional. If the value is not specified, the label is used as the value. The type can be `string` (default), `number` or `boolean` and determines the type which will be saved to the model field when the option is selected; for example `options="30 min:number=30,1 hour:number=60"` will save the number `30` or `60` instead of the string `'30'` or `'60'`.
673
673
  - `height`: Allows you to set the height of the inner `input` element programmatically.
674
674
  - `default-value`: A default value to show the user if the underlying model field's value is null or undefined. Note that setting a default value will not affect the underlying model's value.
675
675
  - `value`: Used to set the model's value.
@@ -790,7 +790,7 @@ This component can be configured to provide its data to multiple components (con
790
790
  - `consumers`: This allows you to connect this `input-provider` to other elements on your page. It 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 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.
791
791
  - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers.
792
792
  - `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.
793
- - `options`: A comma-separated list of options to provide - This only works if the input type is `select`.
793
+ - `options`: A comma-separated list of options to provide - This only works if the input type is `select`. Each option is in the format `label:type=value`; the `:type` and `=value` parts are optional. If the value is not specified, the label is used as the value. The type can be `string` (default), `number` or `boolean` and determines the type of the value which will be passed to consumers when the option is selected; for example `options="30 min:number=30,1 hour:number=60"` will pass the number `30` or `60` instead of the string `'30'` or `'60'`.
794
794
  - `height`: Allows you to set the height of the inner `input` element programmatically.
795
795
  - `value`: Used to set the input's value.
796
796
  - `computable-value`: If this attribute is present, it will be possible to execute expressions using the `{{expression}}` syntax inside the `value` attribute.
package/input-provider.js CHANGED
@@ -3,7 +3,8 @@ import {
3
3
  renderTemplate,
4
4
  updateConsumerElements,
5
5
  convertStringToFieldParams,
6
- convertStringToFieldTypeValues
6
+ convertStringToFieldTypeValues,
7
+ getTypeCastFunction
7
8
  } from './utils.js';
8
9
 
9
10
  const DEFAULT_DEBOUNCE_DELAY = 800;
@@ -135,15 +136,21 @@ class InputProvider extends HTMLElement {
135
136
  elementType = 'input';
136
137
  }
137
138
  this.inputElement = document.createElement(elementType);
139
+ let optionValues = null;
138
140
  if (type === 'select') {
139
141
  if (options != null) {
140
142
  let fieldTypeValues = convertStringToFieldTypeValues(options, true, true);
143
+ optionValues = new Map();
141
144
  let optionElements = fieldTypeValues
142
145
  .map((option) => {
143
146
  if (option.field === placeholder) {
144
147
  return `<option value="" selected class="select-default-option">${option.field}</option>`;
145
148
  }
146
- return `<option value="${option.value ?? option.field}">${option.field}</option>`;
149
+ let Type = getTypeCastFunction(option.type);
150
+ let optionValue = Type(option.value ?? option.field);
151
+ let elementValue = String(optionValue);
152
+ optionValues.set(elementValue, optionValue);
153
+ return `<option value="${elementValue}">${option.field}</option>`;
147
154
  })
148
155
  .join('');
149
156
  this.inputElement.innerHTML = optionElements;
@@ -189,7 +196,7 @@ class InputProvider extends HTMLElement {
189
196
  this.updateInputClassList(value);
190
197
  this.appendChild(this.inputElement);
191
198
 
192
- let destroyHandlers = this.updateConsumerElementsOnEdit();
199
+ let destroyHandlers = this.updateConsumerElementsOnEdit(optionValues);
193
200
 
194
201
  if (this.hasAttribute('force-init-change')) {
195
202
  this.forceTriggerChange = true;
@@ -208,7 +215,14 @@ class InputProvider extends HTMLElement {
208
215
  };
209
216
  }
210
217
 
211
- updateConsumerElementsOnEdit() {
218
+ getTargetValue(inputValue, inputElement, optionValues) {
219
+ if (optionValues && optionValues.has(inputValue)) {
220
+ return optionValues.get(inputValue);
221
+ }
222
+ return inputElement.type === 'number' ? Number(inputValue) : inputValue;
223
+ }
224
+
225
+ updateConsumerElementsOnEdit(optionValues) {
212
226
  let consumers = this.getAttribute('consumers');
213
227
  if (!consumers) return () => {};
214
228
 
@@ -235,8 +249,7 @@ class InputProvider extends HTMLElement {
235
249
  if (event.target.value === '') {
236
250
  updateConsumerElements.call(this, consumers, '', providerTemplate, elementName);
237
251
  } else {
238
- let targetValue = inputElement.type === 'number' ?
239
- Number(event.target.value) : event.target.value;
252
+ let targetValue = this.getTargetValue(event.target.value, inputElement, optionValues);
240
253
  updateConsumerElements.call(this, consumers, targetValue, providerTemplate, elementName);
241
254
  }
242
255
  }, debounceDelay);
@@ -256,8 +269,7 @@ class InputProvider extends HTMLElement {
256
269
  if (event.target.value === '') {
257
270
  updateConsumerElements.call(this, consumers, '', providerTemplate, elementName);
258
271
  } else {
259
- let targetValue = inputElement.type === 'number' ?
260
- Number(event.target.value) : event.target.value;
272
+ let targetValue = this.getTargetValue(event.target.value, inputElement, optionValues);
261
273
  updateConsumerElements.call(this, consumers, targetValue, providerTemplate, elementName);
262
274
  }
263
275
  }, debounceDelay);
package/model-input.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  updateConsumerElements,
5
5
  convertStringToFieldParams,
6
6
  convertStringToFieldTypeValues,
7
+ getTypeCastFunction,
7
8
  formatError,
8
9
  renderTemplate
9
10
  } from './utils.js';
@@ -207,15 +208,21 @@ class ModelInput extends SocketConsumer {
207
208
  }
208
209
  this.inputElement = document.createElement(elementType);
209
210
  let optionList = null;
211
+ let optionValues = null;
210
212
  if (type === 'select') {
211
213
  if (options != null) {
212
214
  let fieldTypeValues = convertStringToFieldTypeValues(options, true, true);
215
+ optionValues = new Map();
213
216
  let optionElements = fieldTypeValues
214
217
  .map((option) => {
215
218
  if (option.field === placeholder) {
216
219
  return `<option value="" selected class="select-default-option">${option.field}</option>`;
217
220
  }
218
- return `<option value="${option.value ?? option.field}">${option.field}</option>`;
221
+ let Type = getTypeCastFunction(option.type);
222
+ let optionValue = Type(option.value ?? option.field);
223
+ let elementValue = String(optionValue);
224
+ optionValues.set(elementValue, optionValue);
225
+ return `<option value="${elementValue}">${option.field}</option>`;
219
226
  })
220
227
  .join('');
221
228
  this.inputElement.innerHTML = optionElements;
@@ -260,7 +267,7 @@ class ModelInput extends SocketConsumer {
260
267
  this.appendChild(errorMessageContainer);
261
268
  }
262
269
  this.appendChild(this.inputElement);
263
- let destroyInputSync = this.syncInputElementWithModel(this.inputElement, model, modelField, errorMessageContainer, optionList);
270
+ let destroyInputSync = this.syncInputElementWithModel(this.inputElement, model, modelField, errorMessageContainer, optionList, optionValues);
264
271
  this.destroy = () => {
265
272
  destroyInputSync();
266
273
  this.isLoadedConsumer && this.isLoadedConsumer.kill();
@@ -302,13 +309,13 @@ class ModelInput extends SocketConsumer {
302
309
  }
303
310
  }
304
311
 
305
- syncInputElementWithModel(inputElement, model, fieldName, messageContainerElement, optionList) {
312
+ syncInputElementWithModel(inputElement, model, fieldName, messageContainerElement, optionList, optionValues) {
306
313
  if (model.isLoaded) {
307
314
  let fieldValue = model.value[fieldName];
308
315
  this.verifyValue(fieldValue, optionList, inputElement, messageContainerElement);
309
316
  this.updateInputElement(inputElement, fieldValue);
310
317
  }
311
- let stopSaving = this.saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement);
318
+ let stopSaving = this.saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement, optionValues);
312
319
  let changeConsumer = model.listener('change').createConsumer();
313
320
  (async () => {
314
321
  for await (let event of changeConsumer) {
@@ -325,7 +332,7 @@ class ModelInput extends SocketConsumer {
325
332
  };
326
333
  }
327
334
 
328
- saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement) {
335
+ saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement, optionValues) {
329
336
  let errorStyleClass = 'error';
330
337
  let successStyleClass = 'success';
331
338
  let showErrorMessage = (fieldName, errorMessage) => {
@@ -387,8 +394,13 @@ class ModelInput extends SocketConsumer {
387
394
  updateConsumerElements.call(this, consumers, '', providerTemplate, this.getAttribute('name'));
388
395
  await model.delete(fieldName);
389
396
  } else {
390
- let targetValue = inputElement.type === 'number' ?
391
- Number(inputValue) : inputValue;
397
+ let targetValue;
398
+ if (optionValues && optionValues.has(inputValue)) {
399
+ targetValue = optionValues.get(inputValue);
400
+ } else {
401
+ targetValue = inputElement.type === 'number' ?
402
+ Number(inputValue) : inputValue;
403
+ }
392
404
  updateConsumerElements.call(this, consumers, targetValue, providerTemplate, this.getAttribute('name'));
393
405
  await model.update(fieldName, targetValue);
394
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "10.0.1",
3
+ "version": "10.1.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {