saasufy-components 8.7.0 → 9.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
@@ -446,6 +446,7 @@ This component is a simplified version of the `collection-adder-form` component
446
446
  - `collection-type` (required): Specifies the type of collection to add the resource to when the form is submitted. This should match a `Model` available in your Saasufy service.
447
447
  - `collection-fields`: A comma-separated list of fields from the `Model` to display as input elements inside the form for the user to fill in. Each field name in this list can optionally be followed by an input element type after a `:` character. For example `collection-fields="qty:number, size:select(small,medium,large)` will create one input element with `type="number"` and one with `type=select` with options `small`, `medium` or `large`. To make a select field optional, you can prefix the list of options with a comma; e.g. `size:select(,small,medium,large)`. Supported input types include: `text`, `select`, `number`, `checkbox`, `radio`, `textarea`, `file` and `text-select`.
448
448
  - `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.
449
+ - `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.
449
450
  - `field-labels`: Allows you to specify custom input labels by mapping field names to more user-friendly labels in the format `fieldName1='Field Name 1',fieldName2='Field Name 2'`.
450
451
  - `submit-button-label`: Text to display on the submit button. If not specified, defaults to `Submit`.
451
452
  - `hide-submit-button`: Adding this attribute will hide the submit button from the form.
@@ -489,6 +490,7 @@ This component is similar to the `collection-adder` component except that it req
489
490
 
490
491
  - `collection-type` (required): Specifies the type of collection to add the resource to when the form is submitted. This should match a `Model` available in your Saasufy service.
491
492
  - `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
+ - `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.
492
494
  - `success-message`: A message to show the user if the resource has been successfully added to the collection after submitting the form.
493
495
  - `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.
494
496
  - `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.
package/app-router.js CHANGED
@@ -291,7 +291,8 @@ export class AppRouter extends SocketConsumer {
291
291
  }
292
292
 
293
293
  this.setCurrentState(routeArgs);
294
- routerViewport.innerHTML = renderTemplate(
294
+ routerViewport.innerHTML = renderTemplate.call(
295
+ this,
295
296
  pageTemplate.innerHTML,
296
297
  this.getStateContext(),
297
298
  this.socket
@@ -3,7 +3,8 @@ import {
3
3
  updateConsumerElements,
4
4
  convertStringToFieldParams,
5
5
  getTypeCastFunction,
6
- formatError
6
+ formatError,
7
+ renderTemplate
7
8
  } from './utils.js';
8
9
  import AGCollection from '/node_modules/ag-collection/ag-collection.js';
9
10
 
@@ -61,6 +62,18 @@ class CollectionAdderForm extends SocketConsumer {
61
62
  let inputElements = this.getAllInputElements();
62
63
  let outputValue;
63
64
 
65
+ let modelValues = this.getAttribute('model-values');
66
+
67
+ if (modelValues && this.hasAttribute('computable-model-values')) {
68
+ modelValues = renderTemplate.call(this, modelValues, null, null, true);
69
+ }
70
+
71
+ let {
72
+ fieldValues: modelFieldValues
73
+ } = convertStringToFieldParams(modelValues);
74
+
75
+ this.modelFieldValues = modelFieldValues;
76
+
64
77
  try {
65
78
  let newModelData = {
66
79
  ...this.modelFieldValues,
@@ -168,8 +181,8 @@ class CollectionAdderForm extends SocketConsumer {
168
181
  }
169
182
  let providerTemplate = this.getAttribute('provider-template');
170
183
  let selfName = this.getAttribute('name');
171
- updateConsumerElements(consumers, outputValue, providerTemplate, selfName);
172
- updateConsumerElements(oppositeOutcomeConsumers, '', null, selfName);
184
+ updateConsumerElements.call(this, consumers, outputValue, providerTemplate, selfName);
185
+ updateConsumerElements.call(this, oppositeOutcomeConsumers, '', null, selfName);
173
186
  }
174
187
 
175
188
  getAllInputElements() {
@@ -210,12 +223,6 @@ class CollectionAdderForm extends SocketConsumer {
210
223
  render() {
211
224
  let collectionType = this.getAttribute('collection-type');
212
225
 
213
- let {
214
- fieldValues: modelFieldValues
215
- } = convertStringToFieldParams(this.getAttribute('model-values'));
216
-
217
- this.modelFieldValues = modelFieldValues;
218
-
219
226
  if (this.collection) this.collection.destroy();
220
227
 
221
228
  this.collection = new AGCollection({
@@ -3,7 +3,8 @@ import {
3
3
  updateConsumerElements,
4
4
  convertStringToFieldParams,
5
5
  getTypeCastFunction,
6
- formatError
6
+ formatError,
7
+ renderTemplate
7
8
  } from './utils.js';
8
9
  import AGCollection from '/node_modules/ag-collection/ag-collection.js';
9
10
 
@@ -69,6 +70,17 @@ class CollectionAdder extends SocketConsumer {
69
70
  let trimSpaces = this.hasAttribute('trim-spaces');
70
71
  let outputValue;
71
72
 
73
+ let modelValues = this.getAttribute('model-values');
74
+ if (modelValues && this.hasAttribute('computable-model-values')) {
75
+ modelValues = renderTemplate.call(this, modelValues, null, null, true);
76
+ }
77
+
78
+ let {
79
+ fieldValues: modelFieldValues
80
+ } = convertStringToFieldParams(modelValues);
81
+
82
+ this.modelFieldValues = modelFieldValues;
83
+
72
84
  try {
73
85
  let newModelData = {
74
86
  ...this.modelFieldValues,
@@ -160,8 +172,8 @@ class CollectionAdder extends SocketConsumer {
160
172
  }
161
173
  let providerTemplate = this.getAttribute('provider-template');
162
174
  let selfName = this.getAttribute('name');
163
- updateConsumerElements(consumers, outputValue, providerTemplate, selfName);
164
- updateConsumerElements(oppositeOutcomeConsumers, '', null, selfName);
175
+ updateConsumerElements.call(this, consumers, outputValue, providerTemplate, selfName);
176
+ updateConsumerElements.call(this, oppositeOutcomeConsumers, '', null, selfName);
165
177
  }
166
178
 
167
179
  reset() {
@@ -185,11 +197,6 @@ class CollectionAdder extends SocketConsumer {
185
197
 
186
198
  this.fieldTypes = fieldTypes;
187
199
 
188
- let {
189
- fieldNames: modelFieldNames,
190
- fieldValues: modelFieldValues
191
- } = convertStringToFieldParams(this.getAttribute('model-values'));
192
-
193
200
  let {
194
201
  fieldValues: modelFieldLabels
195
202
  } = convertStringToFieldParams(this.getAttribute('field-labels'));
@@ -198,6 +205,11 @@ class CollectionAdder extends SocketConsumer {
198
205
  fieldValues: optionLabels
199
206
  } = convertStringToFieldParams(this.getAttribute('option-labels'));
200
207
 
208
+ let modelValues = this.getAttribute('model-values');
209
+ let {
210
+ fieldNames: modelFieldNames
211
+ } = convertStringToFieldParams(modelValues);
212
+
201
213
  let autocapitalize = this.getAttribute('autocapitalize');
202
214
  let autocorrect = this.getAttribute('autocorrect');
203
215
 
@@ -207,8 +219,6 @@ class CollectionAdder extends SocketConsumer {
207
219
  autocorrect ? ` autocorrect="${autocorrect}"` : ''
208
220
  }`;
209
221
 
210
- this.modelFieldValues = modelFieldValues;
211
-
212
222
  if (this.collection) this.collection.destroy();
213
223
 
214
224
  this.collection = new AGCollection({
@@ -148,7 +148,8 @@ class CollectionReducer extends SocketConsumer {
148
148
  if (errorTemplate) {
149
149
  let type = this.getAttribute('type-alias') || this.getAttribute('collection-type');
150
150
  this.setCurrentState({ [`$${type}`]: { error } });
151
- let errorItemString = renderTemplate(
151
+ let errorItemString = renderTemplate.call(
152
+ this,
152
153
  errorTemplate.innerHTML,
153
154
  this.getStateContext(),
154
155
  this.socket
@@ -189,7 +190,8 @@ class CollectionReducer extends SocketConsumer {
189
190
  } else if (itemTemplate) {
190
191
  let type = this.getAttribute('type-alias') || this.collection.type;
191
192
  this.setCurrentState({ [type]: this.collection.value, [`$${type}`]: this.collection.meta });
192
- let itemString = renderTemplate(
193
+ let itemString = renderTemplate.call(
194
+ this,
193
195
  itemTemplate.innerHTML,
194
196
  this.getStateContext(),
195
197
  this.socket
@@ -252,7 +252,8 @@ class CollectionViewer extends SocketConsumer {
252
252
  if (errorTemplate) {
253
253
  let type = this.getAttribute('type-alias') || this.getAttribute('collection-type');
254
254
  this.setCurrentState({ [`$${type}`]: { error } });
255
- let errorItemString = renderTemplate(
255
+ let errorItemString = renderTemplate.call(
256
+ this,
256
257
  errorTemplate.innerHTML,
257
258
  this.getStateContext(),
258
259
  this.socket
@@ -313,7 +314,8 @@ class CollectionViewer extends SocketConsumer {
313
314
 
314
315
  if (firstItemTemplate) {
315
316
  let state = { [`$${type}`]: this.collection.meta };
316
- let itemString = renderTemplate(
317
+ let itemString = renderTemplate.call(
318
+ this,
317
319
  firstItemTemplate.innerHTML,
318
320
  {...stateContext, ...state},
319
321
  this.socket
@@ -322,7 +324,8 @@ class CollectionViewer extends SocketConsumer {
322
324
  }
323
325
  if (noItemTemplate && !this.collection.value.length) {
324
326
  let state = { [`$${type}`]: this.collection.meta };
325
- let itemString = renderTemplate(
327
+ let itemString = renderTemplate.call(
328
+ this,
326
329
  noItemTemplate.innerHTML,
327
330
  {...stateContext, ...state},
328
331
  this.socket
@@ -334,7 +337,8 @@ class CollectionViewer extends SocketConsumer {
334
337
  [type]: modelItem,
335
338
  [`$${type}`]: this.collection.meta
336
339
  };
337
- let itemString = renderTemplate(
340
+ let itemString = renderTemplate.call(
341
+ this,
338
342
  itemTemplate.innerHTML,
339
343
  {...stateContext, ...state},
340
344
  this.socket
@@ -344,7 +348,8 @@ class CollectionViewer extends SocketConsumer {
344
348
  }
345
349
  if (lastItemTemplate) {
346
350
  let state = { [`$${type}`]: this.collection.meta };
347
- let itemString = renderTemplate(
351
+ let itemString = renderTemplate.call(
352
+ this,
348
353
  lastItemTemplate.innerHTML,
349
354
  {...stateContext, ...state},
350
355
  this.socket
package/input-combiner.js CHANGED
@@ -48,7 +48,7 @@ class InputCombiner extends HTMLElement {
48
48
 
49
49
  this.debounce(async () => {
50
50
  let providerTemplate = this.getAttribute('provider-template') || '{{value._root}}';
51
- updateConsumerElements(consumers, { ...this.inputValues }, providerTemplate);
51
+ updateConsumerElements.call(this, consumers, { ...this.inputValues }, providerTemplate);
52
52
  }, debounceDelay);
53
53
  }
54
54
  }
package/input-provider.js CHANGED
@@ -78,7 +78,7 @@ class InputProvider extends HTMLElement {
78
78
  let skipInitChange = this.skipInitChange && !forceInitChange;
79
79
  delete this.skipInitChange;
80
80
  if (newValue && this.hasAttribute('computable-value')) {
81
- newValue = renderTemplate(newValue, null, null, true);
81
+ newValue = renderTemplate.call(this, newValue, null, null, true);
82
82
  }
83
83
  this.updateInputClassList(newValue);
84
84
  let oldValue;
@@ -228,16 +228,16 @@ class InputProvider extends HTMLElement {
228
228
  let checked = !!inputElement.checked;
229
229
  this.lastValue = checked;
230
230
 
231
- updateConsumerElements(consumers, checked, providerTemplate, elementName);
231
+ updateConsumerElements.call(this, consumers, checked, providerTemplate, elementName);
232
232
  return;
233
233
  }
234
234
  this.lastValue = event.target.value;
235
235
  if (event.target.value === '') {
236
- updateConsumerElements(consumers, '', providerTemplate, elementName);
236
+ updateConsumerElements.call(this, consumers, '', providerTemplate, elementName);
237
237
  } else {
238
238
  let targetValue = inputElement.type === 'number' ?
239
239
  Number(event.target.value) : event.target.value;
240
- updateConsumerElements(consumers, targetValue, providerTemplate, elementName);
240
+ updateConsumerElements.call(this, consumers, targetValue, providerTemplate, elementName);
241
241
  }
242
242
  }, debounceDelay);
243
243
  };
@@ -254,11 +254,11 @@ class InputProvider extends HTMLElement {
254
254
  if (event.target.value === this.lastValue) return;
255
255
  this.lastValue = event.target.value;
256
256
  if (event.target.value === '') {
257
- updateConsumerElements(consumers, '', providerTemplate, elementName);
257
+ updateConsumerElements.call(this, consumers, '', providerTemplate, elementName);
258
258
  } else {
259
259
  let targetValue = inputElement.type === 'number' ?
260
260
  Number(event.target.value) : event.target.value;
261
- updateConsumerElements(consumers, targetValue, providerTemplate, elementName);
261
+ updateConsumerElements.call(this, consumers, targetValue, providerTemplate, elementName);
262
262
  }
263
263
  }, debounceDelay);
264
264
  };
@@ -44,7 +44,7 @@ class InputTransformer extends HTMLElement {
44
44
 
45
45
  this.debounce(async () => {
46
46
  let providerTemplate = this.getAttribute('provider-template') || '{{value}}';
47
- updateConsumerElements(consumers, value, providerTemplate, null, outputType);
47
+ updateConsumerElements.call(this, consumers, value, providerTemplate, null, outputType);
48
48
  }, debounceDelay);
49
49
  }
50
50
  }
package/model-input.js CHANGED
@@ -4,7 +4,8 @@ import {
4
4
  updateConsumerElements,
5
5
  convertStringToFieldParams,
6
6
  convertStringToFieldTypeValues,
7
- formatError
7
+ formatError,
8
+ renderTemplate
8
9
  } from './utils.js';
9
10
  import AGModel from '/node_modules/ag-model/ag-model.js';
10
11
 
@@ -72,7 +73,7 @@ class ModelInput extends SocketConsumer {
72
73
  set value(newValue) {
73
74
  if (this.inputElement) {
74
75
  if (newValue && this.hasAttribute('computable-value')) {
75
- newValue = renderTemplate(newValue, null, null, true);
76
+ newValue = renderTemplate.call(this, newValue, null, null, true);
76
77
  }
77
78
  let oldValue;
78
79
  if (this.inputElement.type === 'checkbox') {
@@ -273,14 +274,14 @@ class ModelInput extends SocketConsumer {
273
274
  if (inputElement.type === 'checkbox') {
274
275
  let checked = !!fieldValue;
275
276
  inputElement.checked = checked;
276
- updateConsumerElements(consumers, checked, providerTemplate, this.getAttribute('name'));
277
+ updateConsumerElements.call(this, consumers, checked, providerTemplate, this.getAttribute('name'));
277
278
  } else {
278
279
  let defaultValue = this.getAttribute('default-value') || '';
279
280
  let elementValue = fieldValue == null ? defaultValue : fieldValue;
280
281
  if (inputElement.type !== 'file') {
281
282
  inputElement.value = elementValue;
282
283
  }
283
- updateConsumerElements(consumers, elementValue, providerTemplate, this.getAttribute('name'));
284
+ updateConsumerElements.call(this, consumers, elementValue, providerTemplate, this.getAttribute('name'));
284
285
  }
285
286
  }
286
287
 
@@ -354,7 +355,7 @@ class ModelInput extends SocketConsumer {
354
355
  try {
355
356
  if (inputElement.type === 'checkbox') {
356
357
  let checked = !!inputElement.checked;
357
- updateConsumerElements(consumers, checked, providerTemplate, this.getAttribute('name'));
358
+ updateConsumerElements.call(this, consumers, checked, providerTemplate, this.getAttribute('name'));
358
359
  await model.update(fieldName, checked);
359
360
  inputElement.classList.remove(errorStyleClass);
360
361
  inputElement.classList.add(successStyleClass);
@@ -383,12 +384,12 @@ class ModelInput extends SocketConsumer {
383
384
  inputValue = event.target.value;
384
385
  }
385
386
  if (inputValue === '') {
386
- updateConsumerElements(consumers, '', providerTemplate, this.getAttribute('name'));
387
+ updateConsumerElements.call(this, consumers, '', providerTemplate, this.getAttribute('name'));
387
388
  await model.delete(fieldName);
388
389
  } else {
389
390
  let targetValue = inputElement.type === 'number' ?
390
391
  Number(inputValue) : inputValue;
391
- updateConsumerElements(consumers, targetValue, providerTemplate, this.getAttribute('name'));
392
+ updateConsumerElements.call(this, consumers, targetValue, providerTemplate, this.getAttribute('name'));
392
393
  await model.update(fieldName, targetValue);
393
394
  }
394
395
  inputElement.classList.remove(errorStyleClass);
@@ -415,11 +416,11 @@ class ModelInput extends SocketConsumer {
415
416
  try {
416
417
  if (event.target.value === '') {
417
418
  await model.delete(fieldName);
418
- updateConsumerElements(consumers, '', providerTemplate, this.getAttribute('name'));
419
+ updateConsumerElements.call(this, consumers, '', providerTemplate, this.getAttribute('name'));
419
420
  } else {
420
421
  let targetValue = inputType === 'number' ?
421
422
  Number(event.target.value) : event.target.value;
422
- updateConsumerElements(consumers, targetValue, providerTemplate, this.getAttribute('name'));
423
+ updateConsumerElements.call(this, consumers, targetValue, providerTemplate, this.getAttribute('name'));
423
424
  await model.update(fieldName, targetValue);
424
425
  }
425
426
  inputElement.classList.remove(errorStyleClass);
package/model-viewer.js CHANGED
@@ -64,7 +64,8 @@ class ModelViewer extends SocketConsumer {
64
64
  if (errorTemplate) {
65
65
  let type = this.getAttribute('type-alias') || this.getAttribute('model-type');
66
66
  this.setCurrentState({ [`$${type}`]: { error } });
67
- let errorItemString = renderTemplate(
67
+ let errorItemString = renderTemplate.call(
68
+ this,
68
69
  errorTemplate.innerHTML,
69
70
  this.getStateContext(),
70
71
  this.socket
@@ -102,7 +103,8 @@ class ModelViewer extends SocketConsumer {
102
103
  } else if (itemTemplate) {
103
104
  let type = this.getAttribute('type-alias') || this.model.type;
104
105
  this.setCurrentState({ [type]: modelValue });
105
- let itemString = renderTemplate(
106
+ let itemString = renderTemplate.call(
107
+ this,
106
108
  itemTemplate.innerHTML,
107
109
  this.getStateContext(),
108
110
  this.socket
package/oauth-link.js CHANGED
@@ -50,7 +50,8 @@ class OAuthLink extends HTMLElement {
50
50
 
51
51
  let clientId = this.getAttribute('client-id');
52
52
 
53
- let itemString = renderTemplate(
53
+ let itemString = renderTemplate.call(
54
+ this,
54
55
  itemTemplate.innerHTML,
55
56
  {
56
57
  oauth: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "8.7.0",
3
+ "version": "9.0.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils.js CHANGED
@@ -235,9 +235,9 @@ function execExpression(expression, options) {
235
235
  ...keys,
236
236
  `return (function () {
237
237
  return (${expression});
238
- })();`
238
+ }).call(this);`
239
239
  ];
240
- return (new Function(...args))(...keys.map(key => options[key]));
240
+ return (new Function(...args)).call(this, ...keys.map(key => options[key]));
241
241
  }
242
242
 
243
243
  function getRenderOptions(data, socket) {
@@ -317,7 +317,8 @@ export function renderTemplate(templateString, data, socket, autoExecFunction) {
317
317
  if (expression.startsWith('{{{') && expression.endsWith('}}}')) {
318
318
  expString = expression.slice(3, -3);
319
319
  try {
320
- let result = execExpression(
320
+ let result = execExpression.call(
321
+ this,
321
322
  toExpression(expString),
322
323
  options
323
324
  );
@@ -334,7 +335,8 @@ export function renderTemplate(templateString, data, socket, autoExecFunction) {
334
335
  }
335
336
  expString = expression.slice(2, -2);
336
337
  try {
337
- let result = execExpression(
338
+ let result = execExpression.call(
339
+ this,
338
340
  toExpression(expString),
339
341
  options
340
342
  );
@@ -362,7 +364,7 @@ export function updateConsumerElements(consumers, value, template, sourceElement
362
364
  .filter(([ selector, attributeName ]) => selector);
363
365
 
364
366
  if (template) {
365
- value = renderTemplate(template, { value }, null, true);
367
+ value = renderTemplate.call(this, template, { value }, null, true);
366
368
  }
367
369
 
368
370
  if (outputType === 'boolean') {