saasufy-components 3.7.2 → 3.9.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
@@ -18,6 +18,7 @@ Components for Saasufy
18
18
 
19
19
  ### User input
20
20
  - [input-provider](#input-provider)
21
+ - [input-combiner](#input-combiner)
21
22
 
22
23
  ### Authentication
23
24
  - [log-in-form](#log-in-form)
@@ -65,6 +66,7 @@ To use components, you just need to include them into your `.html` file inside y
65
66
  <script src="https://saasufy.com/node_modules/saasufy-components/model-text.js" type="module" defer></script>
66
67
  <script src="https://saasufy.com/node_modules/saasufy-components/model-viewer.js" type="module" defer></script>
67
68
  <script src="https://saasufy.com/node_modules/saasufy-components/input-provider.js" type="module" defer></script>
69
+ <script src="https://saasufy.com/node_modules/saasufy-components/input-combiner.js" type="module" defer></script>
68
70
  <script src="https://saasufy.com/node_modules/saasufy-components/log-in-form.js" type="module" defer></script>
69
71
  <script src="https://saasufy.com/node_modules/saasufy-components/log-out.js" type="module" defer></script>
70
72
  <script src="https://saasufy.com/node_modules/saasufy-components/oauth-link.js" type="module" defer></script>
@@ -144,9 +146,12 @@ So for example, if your `index.html` file is served up from the URL `http://mywe
144
146
 
145
147
  - `default-page`: The path/route of the default page to send the user to in case no routes are matched. Commonly used to point to a `/page-not-found` page. For the home page, it's typically recommended to create a page template with `route-path=""` instead.
146
148
  - `debounce-delay`: The number of milliseconds to wait before changing the route. This can help to avoid multiple renders if the route changes rapidly (e.g. when doing hard redirects). Defaults to 100ms.
149
+ - `target-page`: A convenience attribute which can be used to programmatically change the `location.hash` in the address bar.
147
150
 
148
151
  **Attributes of slotted page templates**
149
152
 
153
+ - `route-path`: The path of the page. Supports custom URL parameters in the format `/org/:orgId/user/:userId`.
154
+ - `partial-route`: An optional attribute which, if specified, will allow the `route-path` to be matched partially from the start. This can be used to ignore the ending of a path which is not relevant to the page in order to avoid unnecessary re-renders.
150
155
  - `redirect`: An optional path/route which this page should redirect to. Note that the content of this page will not be shown so it should always be empty.
151
156
  - `auth-redirect`: An optional path/route which this page should redirect to if the user is authenticated. If the user is not authenticated, then the content of the template will be displayed as normal.
152
157
  - `no-auth-redirect`: An optional path/route which this page should redirect to if the user is not authenticated. If the user is authenticated, then the content of the template will be displayed as normal.
@@ -524,6 +529,55 @@ This component can be configured to provide its data to multiple components (con
524
529
  - `autocapitalize`: Can be `on` or `off` - It will set the auto-capitalize attribute on the inner input element. This is useful for mobile devices to enable or disable auto-capitalization of the first character which is typed into the input element.
525
530
  - `autocorrect`: Can be `on` or `off` - It will set the auto-correct attribute on the inner input element. This is useful for mobile devices to enable or disable auto-correct.
526
531
 
532
+ ### input-combiner
533
+
534
+ A component which can be used to combine data from multiple `input-provider` components and pass the combined data to other components (or HTML elements) via custom attributes.
535
+ A common use case for it is to pass user input to `collection-browser` or `model-viewer` components to then fetch data from Saasufy.
536
+ This component can be configured to provide its data to multiple components (consumers) via custom attributes.
537
+
538
+ **Import**
539
+
540
+ ```html
541
+ <script src="https://saasufy.com/node_modules/saasufy-components/input-combiner.js" type="module" defer></script>
542
+ ```
543
+
544
+ **Example usage**
545
+
546
+ The following example shows how to bind multiple `input-provider` components to an `input-combiner`.
547
+ In this example, the `input-combiner` component forwards the combined, formatted values to the `collection-view-params` attribute of a `company-browser` component.
548
+
549
+ ```html
550
+ <input-provider
551
+ class="desc-input"
552
+ type="text"
553
+ consumers=".search-combiner"
554
+ provider-template="desc:description contains (?i){{value}}"
555
+ value=""
556
+ placeholder="Description filter"
557
+ ></input-provider>
558
+
559
+ <input-provider
560
+ class="city-input"
561
+ type="text"
562
+ consumers=".search-combiner"
563
+ provider-template="city:city contains (?i){{value}}"
564
+ value=""
565
+ placeholder="City filter"
566
+ ></input-provider>
567
+
568
+ <input-combiner
569
+ class="search-combiner"
570
+ consumers=".company-browser:collection-view-params"
571
+ provider-template="query='{{combineFilters(value)}}'"
572
+ ></input-combiner>
573
+ ```
574
+
575
+ **Attributes**
576
+
577
+ - `consumers`: This allows you to connect this `input-combiner` to other elements on your page. It takes a list of selectors with optional attributes to target. 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.
578
+ - `provider-template`: A template string within which the `{{value}}` can be injected before passing to consumers. In the case of the `input-combiner`, the value is an object wherein each key represents the unique label associated with different `input-provider` components.
579
+ - `debounce-delay`: The delay in milliseconds to wait before triggering a change event. This is useful to batch multiple updates together. Default is 0ms.
580
+
527
581
  ### log-in-form
528
582
 
529
583
  A form component which allows end-users to authenticate themselves into your app using the Saasufy blockchain or any Capitalisk-based blockchain.
package/app-router.js CHANGED
@@ -14,6 +14,7 @@ export class AppRouter extends SocketConsumer {
14
14
  this.pageRouteInfos = [];
15
15
  this.debounce = debouncer();
16
16
  this.debounceDelay = DEFAULT_DEBOUNCE_DELAY;
17
+ this.lastPageState = null;
17
18
 
18
19
  this.hashStartRegex = /^#/;
19
20
 
@@ -30,12 +31,15 @@ export class AppRouter extends SocketConsumer {
30
31
  }
31
32
 
32
33
  this.pageRouteInfos = Object.keys(this.pages).map((route) => {
34
+ let pageTemplate = this.pages[route];
35
+ let allowPartialMatch = pageTemplate.hasAttribute('partial-route');
33
36
  return {
34
- regExp: new RegExp(`^${route.replace(/\/:[^\/]+/g, '/([^/]*)').replace(/\//g, '\\/')}$`, 'g'),
37
+ regExp: new RegExp(`^${route.replace(/\/:[^\/]+/g, '/([^/]*)').replace(/\//g, '\\/')}${allowPartialMatch ? '' : '$'}`, 'g'),
35
38
  route,
36
39
  params: [ ...route.matchAll(/:[^\/]*/g) ].map(paramMatch => paramMatch[0].replace(':', ''))
37
40
  };
38
41
  });
42
+
39
43
  if (!this.socket || this.socket.state === this.socket.OPEN) {
40
44
  this.debounce(this.renderCurrentPage, this.debounceDelay);
41
45
  }
@@ -52,6 +56,7 @@ export class AppRouter extends SocketConsumer {
52
56
 
53
57
  static get observedAttributes() {
54
58
  return [
59
+ 'target-page',
55
60
  'default-page',
56
61
  'debounce-delay',
57
62
  'max-redirects'
@@ -65,6 +70,8 @@ export class AppRouter extends SocketConsumer {
65
70
  } else {
66
71
  this.debounceDelay = DEFAULT_DEBOUNCE_DELAY;
67
72
  }
73
+ } else if (name === 'target-page') {
74
+ location.hash = newValue;
68
75
  } else if (this.isReady) {
69
76
  this.renderCurrentPage();
70
77
  }
@@ -225,6 +232,16 @@ export class AppRouter extends SocketConsumer {
225
232
  routeArgs = {};
226
233
  }
227
234
 
235
+
236
+ let pageState = JSON.stringify({
237
+ route,
238
+ routeArgs
239
+ });
240
+
241
+ if (pageState === this.lastPageState) return;
242
+
243
+ this.lastPageState = pageState;
244
+
228
245
  routerViewport.innerHTML = renderTemplate(
229
246
  pageTemplate.innerHTML,
230
247
  routeArgs,
@@ -0,0 +1,54 @@
1
+ import { debouncer, updateConsumerElements } from './utils.js';
2
+
3
+ const DEFAULT_DEBOUNCE_DELAY = 0;
4
+
5
+ class InputCombiner extends HTMLElement {
6
+ constructor() {
7
+ super();
8
+ this.isReady = false;
9
+ this.inputValues = {};
10
+ this.debounce = debouncer();
11
+ }
12
+ connectedCallback() {
13
+ this.isReady = true;
14
+ this.render();
15
+ }
16
+
17
+ static get observedAttributes() {
18
+ return [
19
+ 'consumers',
20
+ 'debounce-delay',
21
+ 'provider-template',
22
+ 'value'
23
+ ];
24
+ }
25
+
26
+ attributeChangedCallback(name, oldValue, newValue) {
27
+ this.render();
28
+ }
29
+
30
+ render() {
31
+ if (!this.hasAttribute('value')) return;
32
+
33
+ let value = this.getAttribute('value') || '';
34
+ let debounceDelay = this.getAttribute('debounce-delay');
35
+ debounceDelay = debounceDelay ? Number(debounceDelay) : DEFAULT_DEBOUNCE_DELAY;
36
+
37
+ let valueParts = value.match(/^(?:([^:]*)[:])?(.*)$/);
38
+ let valueUpdater = valueParts[1] || '_root';
39
+ let rawValue = valueParts[2];
40
+ this.inputValues[valueUpdater] = rawValue;
41
+
42
+ if (!this.isReady) return;
43
+
44
+ let consumers = this.getAttribute('consumers');
45
+ if (!consumers) return;
46
+
47
+ this.debounce(async () => {
48
+ let providerTemplate = this.getAttribute('provider-template') || '{{value._root}}';
49
+ updateConsumerElements(consumers, { ...this.inputValues }, providerTemplate);
50
+ }, debounceDelay);
51
+ }
52
+ }
53
+
54
+ window.customElements.define('input-combiner', InputCombiner);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "3.7.2",
3
+ "version": "3.9.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils.js CHANGED
@@ -193,9 +193,6 @@ let templateFormatters = {
193
193
  fallback: (...args) => {
194
194
  return args.filter(arg => arg)[0];
195
195
  },
196
- joinFields: (list, field, sep) => {
197
- return list.map(item => item[field]).join(sep);
198
- },
199
196
  date: (timestamp) => {
200
197
  let date = new Date(timestamp);
201
198
  let year = date.getFullYear();
@@ -204,6 +201,12 @@ let templateFormatters = {
204
201
  let hour = date.getHours();
205
202
  let minutes = date.getMinutes();
206
203
  return `${month} ${day}, ${year} at ${hour}:${minutes.toString().padStart(2, '0')}`;
204
+ },
205
+ joinFields: (list, field, sep) => {
206
+ return list.map(item => item[field]).join(sep);
207
+ },
208
+ combineFilters: (filterMap, useOr) => {
209
+ return Object.values(filterMap || {}).join(useOr ? ' %OR% ' : ' %AND% ');
207
210
  }
208
211
  };
209
212
 
@@ -293,6 +296,8 @@ export function updateConsumerElements(consumers, value, template) {
293
296
  }
294
297
  } else if (element.nodeName === 'MODEL-INPUT') {
295
298
  element.value = value;
299
+ } else if (element.nodeName === 'INPUT-COMBINER') {
300
+ element.setAttribute('value', value);
296
301
  } else {
297
302
  element.innerHTML = value;
298
303
  }