@quandis/qbo4.ui 4.0.1-CI-20240426-215901 → 4.0.1-CI-20240429-172951

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Quandis, Inc.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "4.0.1-CI-20240426-215901",
6
+ "version": "4.0.1-CI-20240429-172951",
7
7
  "workspaces": [
8
8
  "code"
9
9
  ],
package/readme.md CHANGED
@@ -34,6 +34,38 @@ const address = getApiService('geoApi').fetch('', { value: '1600 Pennsylvania Av
34
34
  > - The `json` object can be a string, or an object. If it is an object, it will be stringified.
35
35
  > - Additional headers can be specified in the `qbo-api` component.
36
36
 
37
+ ## Default API Endpoint
38
+
39
+ The `qbo4-ui` package will automatically register a default API endpoint using the window.location.
40
+ So, if your endpoints are on the same page as your web page, you can use the default API endpoint.
41
+
42
+ ```typescript
43
+ // This will point to the same website you are on.
44
+ const defaultApi = getApiService('default');
45
+ ```
46
+
47
+ ## Reusing the same API with different paths
48
+
49
+ You may register an API, and then reuse it with different paths:
50
+
51
+ ```html
52
+ <qbo-api name="qms" method="POST" apiEndpoint="https://services.quandis.io/api/military/">
53
+ <header name="Accept">application/json</header>
54
+ </qbo-api>
55
+ ```
56
+
57
+ Then, in typescript:
58
+
59
+ ```typescript
60
+ import { getApiService } from '@quandis/qbo4.ui';
61
+ const qmsSearch = getApiService('api://qms/scra/instant/{clientID}');
62
+ const qmsHealth = getApiService('api://qms/scra/healthcheck');
63
+ ```
64
+
65
+ In this example, the `qms` is cloned in `getApiService`, and the `relativePath` is substituted into the `apiEndpoint`.
66
+
67
+ ## Custom API Services
68
+
37
69
  You can write your own `IApiService` class, and register it to qbo's DI container:
38
70
 
39
71
  ```typescript
@@ -52,6 +84,80 @@ export class MyApi implements IApiService {
52
84
  services.container.registerInstance<IApiService>('myApi', new MyApi());
53
85
  ```
54
86
 
87
+ # QboFormElement
88
+
89
+ The `QboFormElement` web component is used to wrap form elements,
90
+ and present them to a <code>form</code> element upon submission.
91
+
92
+ For example, a credit card components might look like this:
93
+
94
+ ```typescript
95
+ export class QboPayment extends QboFormElement {
96
+ render() {
97
+ return html`<slot>
98
+ <input type="text" name="Number" placeholder="Card Number" required />
99
+ <input type="text" name="Expiry" placeholder="MM/YY" required />
100
+ <input type="text" name="CVC" placeholder="CVC" required />
101
+ <input type="text" name="Name" placeholder="Name on Card" required />
102
+ </slot>`;
103
+ }
104
+ }
105
+ ```
106
+
107
+ > This is a simple example without any credit card validation logic.
108
+
109
+ This can be used within a form multiple times:
110
+
111
+ ```html
112
+ <form id='fact'>
113
+ <qbo-payment name="primary"></qbo-payment>
114
+ <qbo-payment name="backup"></qbo-payment>
115
+ </form>
116
+ ```
117
+
118
+ Upon submission, the form will contain the following fields:
119
+
120
+ ```json
121
+ {
122
+ "primaryNumber": "...",
123
+ "primaryExpiry": "...",
124
+ "primaryCVC": "...",
125
+ "primaryName": "..."
126
+ "backupNumber": "...",
127
+ "backupExpiry": "...",
128
+ "backupCVC": "...",
129
+ "backupName": "..."
130
+ }
131
+ ```
132
+
133
+ > The `name` attribute is used to prefix the field names for any fields in the `ShadowDOM`.
134
+
135
+ If you render elements as children of a `QboFormElement`, they will be directly visible to the form:
136
+
137
+ ```html
138
+ <form id='fact'>
139
+ <qbo-payment name="primary">
140
+ Number: <input type="text" name="myNumber" placeholder="Card Number" required value="1234567890">
141
+ </qbo-payment>
142
+ <qbo-payment name="backup"></qbo-payment>
143
+ </form>
144
+ ```
145
+
146
+ Upon submission, the form will contain the following fields:
147
+
148
+ ```json
149
+ {
150
+ "myNumber": "...",
151
+ "backupNumber": "...",
152
+ "backupExpiry": "...",
153
+ "backupCVC": "...",
154
+ "backupName": "..."
155
+ }
156
+ ```
157
+
158
+ > The `myNumber` field is slotted into the `ShadowDOM` from the normal markup, and is visible to the form.
159
+ Thus, `myNumber` is not prefixed with `primary`.
160
+
55
161
  # Form Validation
56
162
 
57
163
  Modern browers support a very rich set of [form valiation functionality](https://www.w3.org/TR/2009/WD-html5-20090825/forms.html#attr-input-pattern); use it!
@@ -3,10 +3,18 @@ import { InjectionToken } from "tsyringe";
3
3
  * Defines a contract for API calls.
4
4
  */
5
5
  export interface IApiService {
6
+ /**
7
+ * The relative path to the API endpoint.
8
+ */
9
+ relativePath: string;
10
+ /**
11
+ * Fetch data from the API.
12
+ * @param relativePath The relative path to the API endpoint.
13
+ * @param payload The payload to send to the API.
14
+ */
6
15
  fetch(relativePath: string | null, payload: Record<string, string> | null): Promise<any>;
7
16
  }
8
17
  /**
9
18
  * Define a token for the IApiService interface
10
19
  */
11
20
  export declare const IApiServiceToken: InjectionToken<IApiService>;
12
- export declare function getApiService(name: string): IApiService;
@@ -1,8 +1,4 @@
1
- import { services } from "@quandis/qbo4.logging";
2
1
  /**
3
2
  * Define a token for the IApiService interface
4
3
  */
5
4
  export const IApiServiceToken = 'ApiServiceToken';
6
- export function getApiService(name) {
7
- return services.container.resolve(name);
8
- }
@@ -5,6 +5,16 @@ import { InjectionToken } from "tsyringe";
5
5
  * Defines a contract for API calls.
6
6
  */
7
7
  export interface IApiService {
8
+ /**
9
+ * The relative path to the API endpoint.
10
+ */
11
+ relativePath: string;
12
+
13
+ /**
14
+ * Fetch data from the API.
15
+ * @param relativePath The relative path to the API endpoint.
16
+ * @param payload The payload to send to the API.
17
+ */
8
18
  fetch(relativePath: string | null, payload: Record<string, string> | null): Promise<any>;
9
19
  }
10
20
 
@@ -13,6 +23,3 @@ export interface IApiService {
13
23
  */
14
24
  export const IApiServiceToken: InjectionToken<IApiService> = 'ApiServiceToken';
15
25
 
16
- export function getApiService(name: string) {
17
- return services.container.resolve<IApiService>(name);
18
- }
@@ -1,4 +1,5 @@
1
1
  import 'reflect-metadata';
2
+ import { IApiService } from './IApiService.js';
2
3
  export { services } from '@quandis/qbo4.configuration';
3
4
  export * from './IApiService.js';
4
5
  export * from './IValidate.js';
@@ -27,3 +28,5 @@ export declare function elementSelectorAll(element: Element, selector: string):
27
28
  export declare function elementResetValue(element: Element): void;
28
29
  export declare function elementClearValue(element: Element): void;
29
30
  export declare function matches(source: any, match: any, wildCard?: string, ignoreCase?: boolean): boolean;
31
+ export declare function elementData(element: HTMLElement): Record<string, string>;
32
+ export declare function getApiService(url: string): IApiService;
@@ -1,4 +1,6 @@
1
1
  import 'reflect-metadata';
2
+ import { services } from '@quandis/qbo4.configuration';
3
+ import { RestApiService } from './RestApiService.js';
2
4
  export { services } from '@quandis/qbo4.configuration';
3
5
  export * from './IApiService.js';
4
6
  export * from './IValidate.js';
@@ -96,3 +98,27 @@ export function matches(source, match, wildCard = "*", ignoreCase = true) {
96
98
  let regex = new RegExp("^" + match + "$", ignoreCase ? "i" : "");
97
99
  return regex.test(source);
98
100
  }
101
+ export function elementData(element) {
102
+ const data = element.dataset;
103
+ const record = {};
104
+ for (const key in data) {
105
+ if (data.hasOwnProperty(key) && data[key] !== undefined) {
106
+ record[key] = data[key];
107
+ }
108
+ }
109
+ return record;
110
+ }
111
+ export function getApiService(url) {
112
+ const parts = url.match(/api:\/\/([^/]+)/);
113
+ const name = parts ? parts[1] : url;
114
+ const relativePath = parts ? url.substring(`api://${name}`.length) : '';
115
+ const service = services.container.isRegistered(name)
116
+ ? services.container.resolve(name)
117
+ : new RestApiService(url);
118
+ if (relativePath) {
119
+ const clone = structuredClone(service);
120
+ clone.relativePath = relativePath;
121
+ return clone;
122
+ }
123
+ return service;
124
+ }
@@ -1,4 +1,7 @@
1
1
  import 'reflect-metadata';
2
+ import { IApiService } from './IApiService.js';
3
+ import { services } from '@quandis/qbo4.configuration';
4
+ import { RestApiService } from './RestApiService.js';
2
5
 
3
6
  export { services } from '@quandis/qbo4.configuration';
4
7
  export * from './IApiService.js';
@@ -101,3 +104,30 @@ export function matches(source, match, wildCard = "*", ignoreCase = true) {
101
104
  return regex.test(source);
102
105
  }
103
106
 
107
+ export function elementData(element: HTMLElement) {
108
+ const data = element.dataset;
109
+ const record: Record<string, string> = {};
110
+ for (const key in data) {
111
+ if (data.hasOwnProperty(key) && data[key] !== undefined) {
112
+ record[key] = data[key] as string;
113
+ }
114
+ }
115
+ return record;
116
+ }
117
+
118
+
119
+ export function getApiService(url: string) {
120
+ const parts = url.match(/api:\/\/([^/]+)/);
121
+ const name = parts ? parts[1] : url;
122
+ const relativePath = parts ? url.substring(`api://${name}`.length) : '';
123
+ const service: IApiService = services.container.isRegistered(name)
124
+ ? services.container.resolve<IApiService>(name)
125
+ : new RestApiService(url);
126
+
127
+ if (relativePath) {
128
+ const clone = structuredClone(service);
129
+ clone.relativePath = relativePath;
130
+ return clone;
131
+ }
132
+ return service;
133
+ }
@@ -3,6 +3,7 @@ export declare class RestApiService implements IApiService {
3
3
  private headers;
4
4
  private apiEndpoint;
5
5
  private method;
6
+ relativePath: string;
6
7
  constructor(apiEndpoint: string, headers?: HeadersInit, method?: string | null);
7
8
  fetch(relativePath: string | null, payload?: Record<string, string> | null): Promise<any>;
8
9
  }
@@ -2,12 +2,13 @@ import { services } from "@quandis/qbo4.logging";
2
2
  import { substitute } from "./qbo-json.js";
3
3
  export class RestApiService {
4
4
  constructor(apiEndpoint, headers = { 'Content-Type': 'application/json' }, method = null) {
5
+ this.relativePath = '';
5
6
  this.apiEndpoint = apiEndpoint;
6
7
  this.headers = headers;
7
8
  this.method = method;
8
9
  }
9
10
  async fetch(relativePath, payload = null) {
10
- const endpoint = substitute(new URL(relativePath ?? '', this.apiEndpoint).href, payload);
11
+ const endpoint = substitute(new URL(relativePath ?? this.relativePath, this.apiEndpoint).href, payload);
11
12
  const method = this.method ?? (payload !== null ? 'POST' : 'GET');
12
13
  const headers = new Headers(this.headers || {});
13
14
  if (payload !== null && !headers.has('Content-Type')) {
@@ -7,6 +7,7 @@ export class RestApiService implements IApiService {
7
7
  private headers: HeadersInit;
8
8
  private apiEndpoint: string;
9
9
  private method: string | null;
10
+ public relativePath: string = '';
10
11
 
11
12
  constructor(apiEndpoint: string, headers: HeadersInit = { 'Content-Type': 'application/json' }, method: string | null = null) {
12
13
  this.apiEndpoint = apiEndpoint;
@@ -15,7 +16,7 @@ export class RestApiService implements IApiService {
15
16
  }
16
17
 
17
18
  async fetch(relativePath: string | null, payload: Record<string, string> | null = null): Promise<any> {
18
- const endpoint = substitute(new URL(relativePath ?? '', this.apiEndpoint).href, payload);
19
+ const endpoint = substitute(new URL(relativePath ?? this.relativePath, this.apiEndpoint).href, payload);
19
20
  const method = this.method ?? (payload !== null ? 'POST' : 'GET');
20
21
  const headers = new Headers(this.headers || {});
21
22
  if (payload !== null && !headers.has('Content-Type')) {
@@ -1,5 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
- export declare class ApiServiceComponent extends LitElement {
2
+ export declare class QboApiElement extends LitElement {
3
3
  name: string | null;
4
4
  apiEndpoint: string | null;
5
5
  method: string;
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  import { LitElement, html } from 'lit';
11
11
  import { customElement, property } from 'lit/decorators.js';
12
12
  import { registerRestApi } from './RestApiService.js';
13
- let ApiServiceComponent = class ApiServiceComponent extends LitElement {
13
+ let QboApiElement = class QboApiElement extends LitElement {
14
14
  constructor() {
15
15
  super(...arguments);
16
16
  this.name = null;
@@ -51,16 +51,16 @@ let ApiServiceComponent = class ApiServiceComponent extends LitElement {
51
51
  __decorate([
52
52
  property({ type: String }),
53
53
  __metadata("design:type", Object)
54
- ], ApiServiceComponent.prototype, "name", void 0);
54
+ ], QboApiElement.prototype, "name", void 0);
55
55
  __decorate([
56
56
  property({ type: String }),
57
57
  __metadata("design:type", Object)
58
- ], ApiServiceComponent.prototype, "apiEndpoint", void 0);
58
+ ], QboApiElement.prototype, "apiEndpoint", void 0);
59
59
  __decorate([
60
60
  property({ type: String }),
61
61
  __metadata("design:type", String)
62
- ], ApiServiceComponent.prototype, "method", void 0);
63
- ApiServiceComponent = __decorate([
62
+ ], QboApiElement.prototype, "method", void 0);
63
+ QboApiElement = __decorate([
64
64
  customElement('qbo-api')
65
- ], ApiServiceComponent);
66
- export { ApiServiceComponent };
65
+ ], QboApiElement);
66
+ export { QboApiElement };
@@ -5,7 +5,7 @@ import { registerRestApi } from './RestApiService.js';
5
5
 
6
6
 
7
7
  @customElement('qbo-api')
8
- export class ApiServiceComponent extends LitElement {
8
+ export class QboApiElement extends LitElement {
9
9
  @property({ type: String })
10
10
  name: string | null = null;
11
11