Skip to content

Commit 261ab5a

Browse files
author
Sebastian Thulin
committed
Merge branch 'main' of github.com:helsingborg-stad/modularity-frontend-form
2 parents 694123f + d80de7c commit 261ab5a

15 files changed

+273
-51
lines changed

source/js/fields/field/checkbox/checkboxConditionValidator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ class CheckboxConditionValidator implements ConditionValidatorInterface {
1212
case '==':
1313
case '=':
1414
case '===':
15+
case '==contains':
1516
return selected.includes(condition.value);
1617
case '!=':
1718
case '!==':
19+
case '!=contains':
1820
return !selected.includes(condition.value);
1921
case '==empty':
2022
return selected.length === 0;
@@ -25,6 +27,7 @@ class CheckboxConditionValidator implements ConditionValidatorInterface {
2527
case '<':
2628
return selected.some(selectedValue => Number(selectedValue) < Number(condition.value));
2729
default:
30+
console.error('Invalid operator:', condition.operator);
2831
return false;
2932
}
3033
}

source/js/fields/field/nullField/nullFieldConditionValidator.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ class NullFieldConditionValidator implements ConditionValidatorInterface {
44
}
55

66
public validate(condition: Condition): boolean {
7-
87
return false;
98
}
109
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class Radio implements RadioInterface {
2+
constructor(
3+
private field: HTMLElement,
4+
private choices: NodeListOf<HTMLInputElement>,
5+
private name: string,
6+
private radioValidator: ConditionValidatorInterface,
7+
private conditionsHandler: ConditionsHandlerInterface
8+
) {
9+
}
10+
11+
public init(conditionBuilder: ConditionBuilderInterface): void {
12+
this.conditionsHandler.init(this, conditionBuilder);
13+
this.radioValidator.init(this);
14+
}
15+
16+
public getName(): string {
17+
return this.name;
18+
}
19+
20+
public getConditionsHandler(): ConditionsHandlerInterface {
21+
return this.conditionsHandler;
22+
}
23+
24+
public getConditionValidator(): ConditionValidatorInterface {
25+
return this.radioValidator;
26+
}
27+
28+
public getChoices(): NodeListOf<HTMLInputElement> {
29+
return this.choices;
30+
}
31+
32+
public getSelectedChoice(): string {
33+
const selectedChoice = [...this.getChoices()].find(choice => choice.checked);
34+
return selectedChoice ? selectedChoice.value : '';
35+
}
36+
37+
public getField(): HTMLElement {
38+
return this.field;
39+
}
40+
}
41+
42+
export default Radio;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class RadioConditionValidator implements ConditionValidatorInterface {
2+
private parent: RadioInterface|null = null;
3+
4+
public init(parent: RadioInterface): void {
5+
this.parent = parent;
6+
}
7+
8+
public validate(condition: Condition): boolean {
9+
const selected = this.parent?.getSelectedChoice() ?? '';
10+
console.log(condition, 'selected', selected);
11+
12+
switch (condition.operator) {
13+
case '==':
14+
case '=':
15+
case '===':
16+
case '==contains':
17+
return selected === condition.value;
18+
case '!=':
19+
case '!==':
20+
case '!=contains':
21+
return selected !== condition.value;
22+
case '==empty':
23+
return Number(selected) === 0;
24+
case '!=empty':
25+
return Number(selected) > 0;
26+
default:
27+
console.error('Invalid operator:', condition.operator);
28+
return false;
29+
}
30+
}
31+
}
32+
33+
export default RadioConditionValidator;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
class RadioConditionsHandler implements ConditionsHandlerInterface {
2+
private fieldsObject: FieldsObject = {};
3+
private parent: RadioInterface | null = null;
4+
private conditions: ConditionInterface[] = [];
5+
private isDisabled: boolean = false;
6+
7+
constructor(private unstructuredConditions: any) {
8+
}
9+
10+
public init(parent: RadioInterface, conditionsBuilder: ConditionBuilderInterface): void {
11+
this.parent = parent;
12+
this.conditions = conditionsBuilder.build(this.unstructuredConditions);
13+
this.setValueChangeListener();
14+
}
15+
16+
private updateDisabled(disabled: boolean): void {
17+
if (this.isDisabled !== disabled) {
18+
this.isDisabled = disabled;
19+
20+
this.parent?.getChoices().forEach((checkbox, index) => {
21+
if (index === 0) {
22+
this.parent?.getField().classList.toggle('u-display--none', disabled)
23+
}
24+
25+
checkbox.disabled = disabled;
26+
});
27+
28+
this.dispatchUpdateEvent();
29+
}
30+
}
31+
32+
public validate(): void {
33+
let isValid: boolean = false;
34+
for (const condition of this.getConditions()) {
35+
if (condition.validate()) {
36+
isValid = true;
37+
break;
38+
}
39+
}
40+
41+
this.updateDisabled(!isValid);
42+
}
43+
44+
public dispatchUpdateEvent(): void {
45+
const choice = this.parent?.getChoices()[0];
46+
47+
if (choice) {
48+
choice.dispatchEvent(new Event('change'));
49+
}
50+
}
51+
52+
public getIsDisabled(): boolean {
53+
return this.isDisabled;
54+
}
55+
56+
public getConditions(): ConditionInterface[] {
57+
return this.conditions;
58+
}
59+
60+
public addValueChangeListener(field: FieldInterface): void {
61+
this.fieldsObject[field.getName()] = field;
62+
}
63+
64+
private setValueChangeListener(): void {
65+
this.parent?.getChoices().forEach((radio) => {
66+
radio.addEventListener('change', () => {
67+
for (const fieldName in this.fieldsObject) {
68+
this.fieldsObject[fieldName].getConditionsHandler().validate();
69+
}
70+
});
71+
});
72+
}
73+
}
74+
75+
export default RadioConditionsHandler;

source/js/fields/field/select/selectConditionHandler.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ class SelectConditionHandler implements ConditionsHandlerInterface {
5757

5858
private setValueChangeListener(): void {
5959
this.parent?.getSelect().addEventListener('change', () => {
60-
console.log('change')
6160
for (const fieldName in this.fieldsObject) {
6261
this.fieldsObject[fieldName].getConditionsHandler().validate();
6362
}

source/js/fields/field/select/selectConditionValidator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ class SelectConditionValidator implements ConditionValidatorInterface {
1212
case '==':
1313
case '=':
1414
case '===':
15+
case '==contains':
1516
return selected.includes(condition.value);
1617
case '!=':
1718
case '!==':
19+
case '!=contains':
1820
return !selected.includes(condition.value);
1921
case '==empty':
2022
return selected.length === 0;
2123
case '!=empty':
2224
return selected.length > 0;
2325
default:
26+
console.error('Invalid operator:', condition.operator);
2427
return false;
2528
}
2629
}

source/js/fields/field/text/textConditionValidator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ class TextConditionValidator implements ConditionValidatorInterface {
2020
return value.length === 0;
2121
case '!=empty':
2222
return value.length > 0;
23+
case '==contains':
24+
return value.includes(condition.value);
25+
case '!=contains':
26+
return !value.includes(condition.value);
2327
case '>':
2428
return Number(value) > Number(condition.value);
2529
case '<':
2630
return Number(value) < Number(condition.value);
2731
default:
32+
console.error('Invalid operator:', condition.operator);
2833
return false;
2934
}
3035

source/js/fields/fieldBuilder.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import TextConditionValidator from "./field/text/textConditionValidator";
1010
import Select from "./field/select/select";
1111
import SelectConditionHandler from "./field/select/selectConditionHandler";
1212
import SelectConditionValidator from "./field/select/selectConditionValidator";
13+
import Radio from "./field/radio/radio";
14+
import RadioConditionValidator from "./field/radio/radioConditionValidator";
15+
import RadioConditionsHandler from "./field/radio/radioConditionsHandler";
1316

1417
class FieldBuilder implements FieldBuilderInterface {
1518
private name: string = 'data-js-field-name';
@@ -25,9 +28,16 @@ class FieldBuilder implements FieldBuilderInterface {
2528
case 'checkbox':
2629
return this.buildCheckbox(field);
2730
case 'text':
31+
case 'email':
32+
case 'url':
33+
case 'date':
34+
case 'time':
2835
return this.buildText(field);
2936
case 'select':
3037
return this.buildSelect(field);
38+
case 'radio':
39+
case 'trueFalse':
40+
return this.buildRadio(field);
3141
}
3242

3343
return this.buildNullField(field, type);
@@ -42,9 +52,25 @@ class FieldBuilder implements FieldBuilderInterface {
4252
new NullFieldConditionsHandler(field, this.getFieldCondition(field))
4353
);
4454
}
55+
56+
private buildRadio(field: HTMLElement): FieldInterface {
57+
const choices = field.querySelectorAll('input[type="radio"]') as NodeListOf<HTMLInputElement>;
58+
59+
if (choices.length === 0) {
60+
console.error('Radio field is missing input elements');
61+
return this.buildNullField(field, 'radio');
62+
}
63+
64+
return new Radio(
65+
field,
66+
choices,
67+
this.getFieldName(field),
68+
new RadioConditionValidator(),
69+
new RadioConditionsHandler(this.getFieldCondition(field))
70+
);
71+
}
4572

4673
private buildSelect(field: HTMLElement): FieldInterface {
47-
console.log(field)
4874
const select = field.querySelector('select') as HTMLSelectElement;
4975
const options = select?.querySelectorAll('option') as NodeListOf<HTMLOptionElement>;
5076

@@ -64,10 +90,10 @@ class FieldBuilder implements FieldBuilderInterface {
6490
}
6591

6692
private buildText(field: HTMLElement): FieldInterface {
67-
const input = field.querySelector('input[type="text"]') as HTMLInputElement;
68-
93+
const input = field.querySelector('input:is([type="text"], [type="email"], [type="url"], [type="date"], [type="time"])') as HTMLInputElement;
94+
console.log(field);
6995
if (!input) {
70-
console.error('Text field is an input element');
96+
console.error('Text field is not an input element with type "text", "email" or "url", "date" or "time"');
7197
return this.buildNullField(field, 'text');
7298
}
7399

source/js/fields/fieldsInterface.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ interface CheckboxInterface extends FieldInterface {
1515
getSelectedChoices(): string[];
1616
}
1717

18+
interface RadioInterface extends FieldInterface {
19+
getChoices(): NodeListOf<HTMLInputElement>;
20+
getSelectedChoice(): string;
21+
}
22+
1823
interface TextInterface extends FieldInterface {
1924
getInput(): HTMLInputElement;
2025
}

0 commit comments

Comments
 (0)