Skip to content

Commit d3aaa32

Browse files
committed
feat: radio
1 parent 4c1302d commit d3aaa32

File tree

6 files changed

+175
-1
lines changed

6 files changed

+175
-1
lines changed
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
11+
switch (condition.operator) {
12+
case '==':
13+
case '=':
14+
case '===':
15+
return selected === condition.value;
16+
case '!=':
17+
case '!==':
18+
return selected !== condition.value;
19+
case '==empty':
20+
return Number(selected) === 0;
21+
case '!=empty':
22+
return Number(selected) > 0;
23+
default:
24+
return false;
25+
}
26+
}
27+
}
28+
29+
export default RadioConditionValidator;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
console.log(this.unstructuredConditions);
13+
this.conditions = conditionsBuilder.build(this.unstructuredConditions);
14+
this.setValueChangeListener();
15+
}
16+
17+
private updateDisabled(disabled: boolean): void {
18+
if (this.isDisabled !== disabled) {
19+
this.isDisabled = disabled;
20+
21+
this.parent?.getChoices().forEach((checkbox, index) => {
22+
if (index === 0) {
23+
this.parent?.getField().classList.toggle('u-display--none', disabled)
24+
}
25+
26+
checkbox.disabled = disabled;
27+
});
28+
29+
this.dispatchUpdateEvent();
30+
}
31+
}
32+
33+
public validate(): void {
34+
let isValid: boolean = false;
35+
for (const condition of this.getConditions()) {
36+
if (condition.validate()) {
37+
isValid = true;
38+
break;
39+
}
40+
}
41+
42+
this.updateDisabled(!isValid);
43+
}
44+
45+
public dispatchUpdateEvent(): void {
46+
const choice = this.parent?.getChoices()[0];
47+
48+
if (choice) {
49+
choice.dispatchEvent(new Event('change'));
50+
}
51+
}
52+
53+
public getIsDisabled(): boolean {
54+
return this.isDisabled;
55+
}
56+
57+
public getConditions(): ConditionInterface[] {
58+
return this.conditions;
59+
}
60+
61+
public addValueChangeListener(field: FieldInterface): void {
62+
this.fieldsObject[field.getName()] = field;
63+
}
64+
65+
private setValueChangeListener(): void {
66+
this.parent?.getChoices().forEach((radio) => {
67+
radio.addEventListener('change', () => {
68+
for (const fieldName in this.fieldsObject) {
69+
this.fieldsObject[fieldName].getConditionsHandler().validate();
70+
}
71+
});
72+
});
73+
}
74+
}
75+
76+
export default RadioConditionsHandler;

source/js/fields/fieldBuilder.ts

Lines changed: 22 additions & 1 deletion
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';
@@ -28,6 +31,8 @@ class FieldBuilder implements FieldBuilderInterface {
2831
return this.buildText(field);
2932
case 'select':
3033
return this.buildSelect(field);
34+
case 'radio':
35+
return this.buildRadio(field);
3136
}
3237

3338
return this.buildNullField(field, type);
@@ -42,9 +47,25 @@ class FieldBuilder implements FieldBuilderInterface {
4247
new NullFieldConditionsHandler(field, this.getFieldCondition(field))
4348
);
4449
}
50+
51+
private buildRadio(field: HTMLElement): FieldInterface {
52+
const choices = field.querySelectorAll('input[type="radio"]') as NodeListOf<HTMLInputElement>;
53+
54+
if (choices.length === 0) {
55+
console.error('Radio field is missing input elements');
56+
return this.buildNullField(field, 'radio');
57+
}
58+
59+
return new Radio(
60+
field,
61+
choices,
62+
this.getFieldName(field),
63+
new RadioConditionValidator(),
64+
new RadioConditionsHandler(this.getFieldCondition(field))
65+
);
66+
}
4567

4668
private buildSelect(field: HTMLElement): FieldInterface {
47-
console.log(field)
4869
const select = field.querySelector('select') as HTMLSelectElement;
4970
const options = select?.querySelectorAll('option') as NodeListOf<HTMLOptionElement>;
5071

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
}

source/php/Module/FormatSteps.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ private function mapRadio(array $field): array
282282
{
283283
$mapped = $this->mapBasic($field, 'radio');
284284
$mapped['choices'] = [];
285+
$mapped['attributeList']['role'] = 'radiogroup';
285286
foreach ($field['choices'] as $key => $value) {
286287
$mapped['choices'][$key] = [
287288
'type' => $mapped['type'],

0 commit comments

Comments
 (0)