@quandis/qbo4.ui 4.0.1-CI-20240403-131518 → 4.0.1-CI-20240405-163539
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 +2 -1
- package/readme.md +79 -417
- package/src/qbo/IApiService.d.ts +12 -0
- package/src/qbo/IApiService.js +8 -0
- package/src/qbo/IApiService.ts +18 -0
- package/src/qbo/IValidate.d.ts +16 -0
- package/src/qbo/IValidate.js +4 -0
- package/src/qbo/IValidate.ts +19 -0
- package/src/qbo/Program.d.ts +6 -2
- package/src/qbo/Program.js +6 -2
- package/src/qbo/Program.ts +6 -2
- package/src/qbo/RestApiService.d.ts +9 -0
- package/src/qbo/RestApiService.js +32 -0
- package/src/qbo/RestApiService.ts +45 -0
- package/src/qbo/Validators.d.ts +22 -0
- package/src/qbo/{qbo-validators.js → Validators.js} +8 -10
- package/src/qbo/{qbo-validators.ts → Validators.ts} +10 -22
- package/src/qbo/qbo-api.d.ts +0 -19
- package/src/qbo/qbo-api.js +3 -36
- package/src/qbo/qbo-api.ts +3 -57
- package/src/qbo/qbo-form-element.d.ts +20 -0
- package/src/qbo/qbo-form-element.js +128 -0
- package/src/qbo/qbo-form-element.ts +121 -0
- package/src/qbo/qbo-json.d.ts +3 -4
- package/src/qbo/qbo-json.js +24 -4
- package/src/qbo/qbo-json.ts +27 -4
- package/src/qbo/qbo-validate.d.ts +25 -7
- package/src/qbo/qbo-validate.js +84 -33
- package/src/qbo/qbo-validate.ts +81 -28
- package/wwwroot/js/qbo4.ui.js +47650 -11977
- package/wwwroot/js/qbo4.ui.min.js +84 -51
- package/wwwroot/js/qbo4.ui.min.js.map +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ILoggerService, ILoggerServiceToken, services } from '@quandis/qbo4.logging';
|
|
2
|
+
import { html, css, LitElement, PropertyValues } from 'lit';
|
|
3
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
+
import { substitute } from './qbo-json.js';
|
|
5
|
+
|
|
6
|
+
@customElement('qbo-form-element')
|
|
7
|
+
export class QboFormElement extends LitElement {
|
|
8
|
+
static formAssociated = true;
|
|
9
|
+
|
|
10
|
+
@property({ type: FormData })
|
|
11
|
+
value: FormData = new FormData();
|
|
12
|
+
|
|
13
|
+
@property({ type: String })
|
|
14
|
+
template = '';
|
|
15
|
+
|
|
16
|
+
@property({ type: String })
|
|
17
|
+
name = '';
|
|
18
|
+
|
|
19
|
+
@property({ type: Object })
|
|
20
|
+
data = null;
|
|
21
|
+
|
|
22
|
+
private templateNode: HTMLTemplateElement | null = null;
|
|
23
|
+
|
|
24
|
+
private internals: ElementInternals;
|
|
25
|
+
logger: ILoggerService;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
this.internals = this.attachInternals(); // Attach the form internals
|
|
30
|
+
this.logger = services.container.resolve<ILoggerService>(ILoggerServiceToken);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
super.connectedCallback();
|
|
35
|
+
this.templateNode = document.getElementById(this.template) as HTMLTemplateElement;
|
|
36
|
+
|
|
37
|
+
if (this.templateNode) {
|
|
38
|
+
this.appendChild(this.templateNode.content.cloneNode(true));
|
|
39
|
+
this.innerHTML = substitute(this.innerHTML, this.data);
|
|
40
|
+
}
|
|
41
|
+
this.shadowRoot?.addEventListener('change', this.handleFormChange);
|
|
42
|
+
this.shadowRoot?.addEventListener('qbo-form-update', this.augment);
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
disconnectedCallback() {
|
|
47
|
+
super.disconnectedCallback();
|
|
48
|
+
this.shadowRoot?.removeEventListener('change', this.handleFormChange);
|
|
49
|
+
this.shadowRoot?.removeEventListener('qbo-form-update', this.augment);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
updated(changedProperties: PropertyValues) {
|
|
53
|
+
super.updated(changedProperties);
|
|
54
|
+
this.establishFormData();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render() {
|
|
58
|
+
return html`<slot></slot>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
establishFormData() {
|
|
62
|
+
const elements = this.shadowRoot?.querySelectorAll('input, select, textarea');
|
|
63
|
+
const inputs = Array.from(elements!).filter(e => !e.assignedSlot
|
|
64
|
+
&& (e instanceof HTMLInputElement
|
|
65
|
+
|| e instanceof HTMLSelectElement
|
|
66
|
+
|| e instanceof HTMLTextAreaElement
|
|
67
|
+
|| e instanceof QboFormElement
|
|
68
|
+
) && e.name
|
|
69
|
+
);
|
|
70
|
+
inputs!.forEach(element => {
|
|
71
|
+
const input = element as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | QboFormElement;
|
|
72
|
+
|
|
73
|
+
if (input instanceof HTMLSelectElement) {
|
|
74
|
+
this.value.set(`${this.name}${input.name}`, input.value);
|
|
75
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
76
|
+
this.value.set(`${this.name}${input.name}`, input.value);
|
|
77
|
+
} else if (input instanceof QboFormElement) {
|
|
78
|
+
for (let [key, value] of input.value.entries()) {
|
|
79
|
+
this.value.set(`${this.name}${key}`, value);
|
|
80
|
+
}
|
|
81
|
+
} else if (input instanceof HTMLInputElement) {
|
|
82
|
+
if (input.type === 'checkbox' || input.type === 'radio') {
|
|
83
|
+
if (input.checked) {
|
|
84
|
+
this.value.set(`${this.name}${input.name}`, input.value);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
this.value.set(`${this.name}${input.name}`, input.value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.internals.setFormValue(this.value);
|
|
92
|
+
this.logger.logTrace(`Set ${this.name} values to `, this.value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
handleFormChange = (event) => {
|
|
97
|
+
const target = event.target;
|
|
98
|
+
if (target.matches('input, select, textarea') && target.name && !target.assignedSlot) {
|
|
99
|
+
// if the element is assigned to a slot, it's already visible to the parent. Ignore it.
|
|
100
|
+
this.value.set(`${this.name}${target.name}`, target.value);
|
|
101
|
+
this.logger.logTrace(`${this.name}${target.name} was set to ${target.value}`);
|
|
102
|
+
}
|
|
103
|
+
this.internals.setFormValue(this.value);
|
|
104
|
+
this.dispatchEvent(new CustomEvent('qbo-form-update', { bubbles: true, composed: true, detail: { formData: this.value } }));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
augment = (event) => {
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
console.log(event)
|
|
110
|
+
if (event instanceof CustomEvent && event.detail.formData instanceof FormData) {
|
|
111
|
+
// To replace existing entries instead of appending
|
|
112
|
+
for (let [key, value] of event.detail.formData.entries()) {
|
|
113
|
+
this.value.set(key, value);
|
|
114
|
+
console.log(`Added ${key}=${value} to ${this.name}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.internals.setFormValue(this.value);
|
|
118
|
+
this.dispatchEvent(new CustomEvent('qbo-form-update', { bubbles: true, composed: true, detail: { formData: this.value } }));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
package/src/qbo/qbo-json.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
declare function getArray(json: any, arrayName?: string): Array<{
|
|
1
|
+
export declare function getArray(json: any, arrayName?: string): Array<{
|
|
2
2
|
[key: string]: string;
|
|
3
3
|
}> | null;
|
|
4
|
-
declare function substitute(inputString: string, ...jsonData: any): string;
|
|
5
|
-
declare function replicate(target: HTMLElement | null, template: HTMLTemplateElement | null, sourceData: Array<{
|
|
4
|
+
export declare function substitute(inputString: string, ...jsonData: any): string;
|
|
5
|
+
export declare function replicate(target: HTMLElement | null, template: HTMLTemplateElement | null, sourceData: Array<{
|
|
6
6
|
[key: string]: string;
|
|
7
7
|
}>, emptyContent?: boolean): void;
|
|
8
|
-
export { getArray, substitute, replicate };
|
package/src/qbo/qbo-json.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @param arrayName {array} The name of a key that represents an array to use. If not specified, the first array found will be returned.
|
|
4
4
|
* @returns {Array} An array if found, otherwise null.
|
|
5
5
|
*/
|
|
6
|
-
function getArray(json, arrayName) {
|
|
6
|
+
export function getArray(json, arrayName) {
|
|
7
7
|
if (typeof json === 'string') {
|
|
8
8
|
try {
|
|
9
9
|
json = JSON.parse(json);
|
|
@@ -46,7 +46,7 @@ function interpolate(template, data) {
|
|
|
46
46
|
/* @description Perform string substitution of ${expression} using one or more JSON objects.
|
|
47
47
|
* @example substitute('Hello ${name}!', { name: 'World' }) => 'Hello World!'
|
|
48
48
|
*/
|
|
49
|
-
function
|
|
49
|
+
function substituteOld(inputString, ...jsonData) {
|
|
50
50
|
// Use a regular expression to match ${key} expressions
|
|
51
51
|
const regex = /\${(.*?)}/g;
|
|
52
52
|
let resultString = inputString;
|
|
@@ -65,9 +65,30 @@ function substitute(inputString, ...jsonData) {
|
|
|
65
65
|
resultString = resultString.replace(regex, () => '');
|
|
66
66
|
return resultString;
|
|
67
67
|
}
|
|
68
|
+
export function substitute(inputString, ...jsonData) {
|
|
69
|
+
// Use a regular expression to match ${key} expressions
|
|
70
|
+
const regex = /\${(.*?)}/g;
|
|
71
|
+
let resultString = inputString;
|
|
72
|
+
for (let i = 0; i < jsonData.length; i++) {
|
|
73
|
+
const data = jsonData[i];
|
|
74
|
+
resultString = resultString.replace(regex, (match, expression) => {
|
|
75
|
+
const parts = expression.split(/\.|\[|\]/).filter(part => part !== '');
|
|
76
|
+
let currentValue = data;
|
|
77
|
+
// Iterate over the parts to access the desired value
|
|
78
|
+
for (let part of parts) {
|
|
79
|
+
if (currentValue[part] === undefined)
|
|
80
|
+
return match;
|
|
81
|
+
currentValue = currentValue[part];
|
|
82
|
+
}
|
|
83
|
+
return currentValue;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
resultString = resultString.replace(regex, () => '');
|
|
87
|
+
return resultString;
|
|
88
|
+
}
|
|
68
89
|
/* @description Replicates content in a target element for each item in a JSON array.
|
|
69
90
|
*/
|
|
70
|
-
function replicate(target, template, sourceData, emptyContent = true) {
|
|
91
|
+
export function replicate(target, template, sourceData, emptyContent = true) {
|
|
71
92
|
if (target != null && template != null) {
|
|
72
93
|
while (emptyContent && target.firstChild) {
|
|
73
94
|
target.removeChild(target.firstChild);
|
|
@@ -82,4 +103,3 @@ function replicate(target, template, sourceData, emptyContent = true) {
|
|
|
82
103
|
});
|
|
83
104
|
}
|
|
84
105
|
}
|
|
85
|
-
export { getArray, substitute, replicate };
|
package/src/qbo/qbo-json.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param arrayName {array} The name of a key that represents an array to use. If not specified, the first array found will be returned.
|
|
5
5
|
* @returns {Array} An array if found, otherwise null.
|
|
6
6
|
*/
|
|
7
|
-
function getArray(json: any, arrayName?: string): Array<{ [key: string]: string }> | null {
|
|
7
|
+
export function getArray(json: any, arrayName?: string): Array<{ [key: string]: string }> | null {
|
|
8
8
|
if (typeof json === 'string') {
|
|
9
9
|
try {
|
|
10
10
|
json = JSON.parse(json);
|
|
@@ -49,7 +49,7 @@ function interpolate(template: string, data: { [key: string]: string }) {
|
|
|
49
49
|
/* @description Perform string substitution of ${expression} using one or more JSON objects.
|
|
50
50
|
* @example substitute('Hello ${name}!', { name: 'World' }) => 'Hello World!'
|
|
51
51
|
*/
|
|
52
|
-
function
|
|
52
|
+
function substituteOld(inputString: string, ...jsonData: any): string {
|
|
53
53
|
// Use a regular expression to match ${key} expressions
|
|
54
54
|
const regex = /\${(.*?)}/g;
|
|
55
55
|
let resultString = inputString;
|
|
@@ -70,10 +70,34 @@ function substitute(inputString: string, ...jsonData: any): string {
|
|
|
70
70
|
return resultString;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export function substitute(inputString: string, ...jsonData: any): string {
|
|
74
|
+
// Use a regular expression to match ${key} expressions
|
|
75
|
+
const regex = /\${(.*?)}/g;
|
|
76
|
+
let resultString = inputString;
|
|
77
|
+
for (let i = 0; i < jsonData.length; i++) {
|
|
78
|
+
const data = jsonData[i] as Record<string, any>;
|
|
79
|
+
resultString = resultString.replace(regex, (match: string, expression: string) => {
|
|
80
|
+
const parts = expression.split(/\.|\[|\]/).filter(part => part !== '');
|
|
81
|
+
let currentValue: any = data;
|
|
82
|
+
|
|
83
|
+
// Iterate over the parts to access the desired value
|
|
84
|
+
for (let part of parts) {
|
|
85
|
+
if (currentValue[part] === undefined)
|
|
86
|
+
return match;
|
|
87
|
+
currentValue = currentValue[part];
|
|
88
|
+
}
|
|
89
|
+
return currentValue;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
resultString = resultString.replace(regex, () => '');
|
|
93
|
+
|
|
94
|
+
return resultString;
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
|
|
74
98
|
/* @description Replicates content in a target element for each item in a JSON array.
|
|
75
99
|
*/
|
|
76
|
-
function replicate(target: HTMLElement | null, template: HTMLTemplateElement | null, sourceData: Array<{ [key: string]: string }>, emptyContent: boolean = true) {
|
|
100
|
+
export function replicate(target: HTMLElement | null, template: HTMLTemplateElement | null, sourceData: Array<{ [key: string]: string }>, emptyContent: boolean = true) {
|
|
77
101
|
if (target != null && template != null) {
|
|
78
102
|
while (emptyContent && target.firstChild) {
|
|
79
103
|
target.removeChild(target.firstChild);
|
|
@@ -89,4 +113,3 @@ function replicate(target: HTMLElement | null, template: HTMLTemplateElement | n
|
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
115
|
|
|
92
|
-
export { getArray, substitute, replicate };
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import { IValidate } from './
|
|
2
|
+
import { IValidate } from './IValidate.js';
|
|
3
3
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
* `QboValidate` component is responsible for performing validation on forms and inputs.
|
|
5
|
+
* It emits two custom events to indicate the completion of validation processes:
|
|
6
|
+
* - `qbo-form-validated`: Fired when the entire form has been validated.
|
|
7
|
+
* - `qbo-input-validated`: Fired when a single input within the form has been validated.
|
|
8
|
+
*
|
|
9
|
+
* @fires qbo-form-validated - Event fired when the form validation is complete.
|
|
10
|
+
* @fires qbo-input-validated - Event fired when an individual input validation is complete.
|
|
11
|
+
* @example
|
|
12
|
+
* ```html
|
|
13
|
+
* <form id="myForm">
|
|
14
|
+
* <qbo-validate></qbo-validate>
|
|
15
|
+
* </form>
|
|
16
|
+
* ```
|
|
17
|
+
* ```javascript
|
|
18
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-form-validated', (e) => {
|
|
19
|
+
* console.log('Form validation completed');
|
|
20
|
+
* });
|
|
21
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-input-validated', (e) => {
|
|
22
|
+
* console.log('Input validation completed');
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
9
26
|
export declare class QboValidate extends LitElement {
|
|
10
27
|
renderInHost: boolean;
|
|
11
28
|
autoComplete: string;
|
|
@@ -15,6 +32,7 @@ export declare class QboValidate extends LitElement {
|
|
|
15
32
|
validators: IValidate[];
|
|
16
33
|
connectedCallback(): Promise<void>;
|
|
17
34
|
disconnectedCallback(): Promise<void>;
|
|
18
|
-
validate(event:
|
|
35
|
+
validate: (event: Event) => Promise<void>;
|
|
36
|
+
validateElement: (source: Event | HTMLElement) => Promise<void>;
|
|
19
37
|
render(): import("lit").TemplateResult<1>;
|
|
20
38
|
}
|
package/src/qbo/qbo-validate.js
CHANGED
|
@@ -7,16 +7,33 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { LitElement, html } from 'lit';
|
|
11
11
|
import { customElement, property } from 'lit/decorators.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { ValidateToken } from './IValidate.js';
|
|
13
|
+
import { services } from '@quandis/qbo4.logging';
|
|
14
14
|
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
* `QboValidate` component is responsible for performing validation on forms and inputs.
|
|
16
|
+
* It emits two custom events to indicate the completion of validation processes:
|
|
17
|
+
* - `qbo-form-validated`: Fired when the entire form has been validated.
|
|
18
|
+
* - `qbo-input-validated`: Fired when a single input within the form has been validated.
|
|
19
|
+
*
|
|
20
|
+
* @fires qbo-form-validated - Event fired when the form validation is complete.
|
|
21
|
+
* @fires qbo-input-validated - Event fired when an individual input validation is complete.
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <form id="myForm">
|
|
25
|
+
* <qbo-validate></qbo-validate>
|
|
26
|
+
* </form>
|
|
27
|
+
* ```
|
|
28
|
+
* ```javascript
|
|
29
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-form-validated', (e) => {
|
|
30
|
+
* console.log('Form validation completed');
|
|
31
|
+
* });
|
|
32
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-input-validated', (e) => {
|
|
33
|
+
* console.log('Input validation completed');
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
20
37
|
let QboValidate = class QboValidate extends LitElement {
|
|
21
38
|
constructor() {
|
|
22
39
|
super(...arguments);
|
|
@@ -26,40 +43,74 @@ let QboValidate = class QboValidate extends LitElement {
|
|
|
26
43
|
this.workingClass = ['bg-light', 'text-secondary'];
|
|
27
44
|
this.form = null;
|
|
28
45
|
this.validators = [];
|
|
46
|
+
this.validate = async (event) => {
|
|
47
|
+
console.log('validating form');
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
const targets = Array.from(this.form?.querySelectorAll('*'));
|
|
50
|
+
for (const target of targets) {
|
|
51
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
|
|
52
|
+
console.log('validating element ', target);
|
|
53
|
+
await this.validateElement(target);
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
console.log('ignoring element ', target);
|
|
57
|
+
}
|
|
58
|
+
if (!this.form?.checkValidity()) {
|
|
59
|
+
console.log('form is invalid');
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log('form is valid, js submit');
|
|
65
|
+
// this.form.submit();
|
|
66
|
+
}
|
|
67
|
+
this.form?.classList.add(this.validatedClass);
|
|
68
|
+
this.dispatchEvent(new Event('qbo-form-validated', { bubbles: true }));
|
|
69
|
+
};
|
|
70
|
+
this.validateElement = async (source) => {
|
|
71
|
+
const target = source instanceof Event ? source.target : source;
|
|
72
|
+
console.log('validateElement start ', target);
|
|
73
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
|
|
74
|
+
target.setCustomValidity('');
|
|
75
|
+
// Re-verify build-in validation.
|
|
76
|
+
if (!target.checkValidity())
|
|
77
|
+
return;
|
|
78
|
+
for (const validator of this.validators) {
|
|
79
|
+
if (target.matches(validator.selector)) {
|
|
80
|
+
console.log('element matches selector ', validator.selector, ' for ', target.matches(validator.selector) ? 'true' : 'false');
|
|
81
|
+
if (!target.checkValidity())
|
|
82
|
+
break;
|
|
83
|
+
// Show some loading indication if necessary.
|
|
84
|
+
this.workingClass.forEach(c => target.classList.add(c));
|
|
85
|
+
// Validate the element.
|
|
86
|
+
const valid = await validator.validate(target);
|
|
87
|
+
// Clear loading indication.
|
|
88
|
+
this.workingClass.forEach(c => target.classList.remove(c));
|
|
89
|
+
// Set custom validity based on the validation result.
|
|
90
|
+
target.setCustomValidity(valid ? '' : validator.message);
|
|
91
|
+
console.log('input validity is ', target.checkValidity(), ' message is ', target.validationMessage);
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
console.log('element does not match selector ', validator.selector, ' for ', target.matches(validator.selector) ? 'true' : 'false');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.log('validateElement end ', target);
|
|
98
|
+
target?.dispatchEvent(new Event('qbo-input-validated', { bubbles: true }));
|
|
99
|
+
};
|
|
29
100
|
}
|
|
30
101
|
async connectedCallback() {
|
|
31
102
|
super.connectedCallback();
|
|
32
103
|
this.form = this.closest('form');
|
|
33
104
|
if (this.form == null)
|
|
34
105
|
return;
|
|
35
|
-
this.form?.addEventListener('submit', this.validate
|
|
36
|
-
this.
|
|
37
|
-
this.validators.
|
|
38
|
-
this.form?.querySelectorAll(validator.selector).forEach(element => {
|
|
39
|
-
element.addEventListener('change', async () => {
|
|
40
|
-
if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
|
|
41
|
-
element.setCustomValidity('');
|
|
42
|
-
if (!element.checkValidity())
|
|
43
|
-
return;
|
|
44
|
-
this.workingClass.forEach(c => element.classList.add(c));
|
|
45
|
-
var valid = await validator.validate(element);
|
|
46
|
-
this.workingClass.forEach(c => element.classList.add(c));
|
|
47
|
-
element.setCustomValidity(valid ? '' : validator.message);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
});
|
|
106
|
+
this.form?.addEventListener('submit', this.validate);
|
|
107
|
+
this.form?.addEventListener('change', this.validateElement);
|
|
108
|
+
this.validators = services.container.resolveAll(ValidateToken);
|
|
52
109
|
}
|
|
53
110
|
async disconnectedCallback() {
|
|
54
111
|
super.disconnectedCallback();
|
|
55
|
-
this.form?.
|
|
56
|
-
|
|
57
|
-
validate(event) {
|
|
58
|
-
if (!this.form?.checkValidity()) {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
event.stopPropagation();
|
|
61
|
-
}
|
|
62
|
-
this.form?.classList.add(this.validatedClass);
|
|
112
|
+
this.form?.addEventListener('change', this.validateElement);
|
|
113
|
+
this.form?.addEventListener('submit', this.validate);
|
|
63
114
|
}
|
|
64
115
|
render() {
|
|
65
116
|
return html ``;
|
package/src/qbo/qbo-validate.ts
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LitElement, html } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { IValidate, ValidateToken } from './IValidate.js';
|
|
4
|
+
import { services } from '@quandis/qbo4.logging';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
* `QboValidate` component is responsible for performing validation on forms and inputs.
|
|
8
|
+
* It emits two custom events to indicate the completion of validation processes:
|
|
9
|
+
* - `qbo-form-validated`: Fired when the entire form has been validated.
|
|
10
|
+
* - `qbo-input-validated`: Fired when a single input within the form has been validated.
|
|
11
|
+
*
|
|
12
|
+
* @fires qbo-form-validated - Event fired when the form validation is complete.
|
|
13
|
+
* @fires qbo-input-validated - Event fired when an individual input validation is complete.
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <form id="myForm">
|
|
17
|
+
* <qbo-validate></qbo-validate>
|
|
18
|
+
* </form>
|
|
19
|
+
* ```
|
|
20
|
+
* ```javascript
|
|
21
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-form-validated', (e) => {
|
|
22
|
+
* console.log('Form validation completed');
|
|
23
|
+
* });
|
|
24
|
+
* document.querySelector('qbo-validate').addEventListener('qbo-input-validated', (e) => {
|
|
25
|
+
* console.log('Input validation completed');
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
12
29
|
@customElement('qbo-validate')
|
|
13
30
|
export class QboValidate extends LitElement {
|
|
14
31
|
|
|
@@ -37,37 +54,73 @@ export class QboValidate extends LitElement {
|
|
|
37
54
|
this.form = this.closest('form');
|
|
38
55
|
if (this.form == null)
|
|
39
56
|
return;
|
|
40
|
-
|
|
41
|
-
this.form?.addEventListener('
|
|
42
|
-
this.validators = container.resolveAll<IValidate>(ValidateToken);
|
|
43
|
-
this.validators.forEach(validator => {
|
|
44
|
-
this.form?.querySelectorAll(validator.selector).forEach(element => {
|
|
45
|
-
element.addEventListener('change', async () => {
|
|
46
|
-
if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
|
|
47
|
-
element.setCustomValidity('');
|
|
48
|
-
if (!element.checkValidity())
|
|
49
|
-
return;
|
|
50
|
-
this.workingClass.forEach(c => element.classList.add(c));
|
|
51
|
-
var valid = await validator.validate(element);
|
|
52
|
-
this.workingClass.forEach(c => element.classList.add(c))
|
|
53
|
-
element.setCustomValidity(valid ? '' : validator.message);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
57
|
+
this.form?.addEventListener('submit', this.validate);
|
|
58
|
+
this.form?.addEventListener('change', this.validateElement);
|
|
59
|
+
this.validators = services.container.resolveAll<IValidate>(ValidateToken);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
async disconnectedCallback() {
|
|
61
63
|
super.disconnectedCallback();
|
|
62
|
-
this.form?.
|
|
64
|
+
this.form?.addEventListener('change', this.validateElement);
|
|
65
|
+
this.form?.addEventListener('submit', this.validate);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
validate(event:
|
|
68
|
+
validate = async (event: Event) => {
|
|
69
|
+
console.log('validating form');
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
const targets = Array.from(this.form?.querySelectorAll('*')!);
|
|
72
|
+
for (const target of targets) {
|
|
73
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
|
|
74
|
+
console.log('validating element ', target);
|
|
75
|
+
await this.validateElement(target);
|
|
76
|
+
} else
|
|
77
|
+
console.log('ignoring element ', target);
|
|
78
|
+
}
|
|
66
79
|
if (!this.form?.checkValidity()) {
|
|
80
|
+
console.log('form is invalid');
|
|
67
81
|
event.preventDefault()
|
|
68
82
|
event.stopPropagation()
|
|
69
83
|
}
|
|
84
|
+
else {
|
|
85
|
+
console.log('form is valid, js submit');
|
|
86
|
+
// this.form.submit();
|
|
87
|
+
}
|
|
70
88
|
this.form?.classList.add(this.validatedClass);
|
|
89
|
+
this.dispatchEvent(new Event('qbo-form-validated', { bubbles: true }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
validateElement = async (source: Event | HTMLElement) => {
|
|
93
|
+
const target = source instanceof Event ? source.target : source;
|
|
94
|
+
console.log('validateElement start ', target)
|
|
95
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
|
|
96
|
+
target.setCustomValidity('');
|
|
97
|
+
// Re-verify build-in validation.
|
|
98
|
+
if (!target.checkValidity())
|
|
99
|
+
return;
|
|
100
|
+
for (const validator of this.validators) {
|
|
101
|
+
if (target.matches(validator.selector)) {
|
|
102
|
+
console.log('element matches selector ', validator.selector, ' for ', target.matches(validator.selector) ? 'true' : 'false');
|
|
103
|
+
if (!target.checkValidity()) break;
|
|
104
|
+
|
|
105
|
+
// Show some loading indication if necessary.
|
|
106
|
+
this.workingClass.forEach(c => target.classList.add(c));
|
|
107
|
+
|
|
108
|
+
// Validate the element.
|
|
109
|
+
const valid = await validator.validate(target);
|
|
110
|
+
|
|
111
|
+
// Clear loading indication.
|
|
112
|
+
this.workingClass.forEach(c => target.classList.remove(c));
|
|
113
|
+
|
|
114
|
+
// Set custom validity based on the validation result.
|
|
115
|
+
target.setCustomValidity(valid ? '' : validator.message);
|
|
116
|
+
console.log('input validity is ', target.checkValidity(), ' message is ', target.validationMessage);
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
console.log('element does not match selector ', validator.selector, ' for ', target.matches(validator.selector) ? 'true' : 'false');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log('validateElement end ', target);
|
|
123
|
+
target?.dispatchEvent(new Event('qbo-input-validated', { bubbles: true }));
|
|
71
124
|
}
|
|
72
125
|
|
|
73
126
|
render() {
|