Skip to content

Commit c6077f8

Browse files
authored
Fix/options manager resetter with tests (DevExpress#29856)
1 parent 0817c88 commit c6077f8

File tree

15 files changed

+617
-3
lines changed

15 files changed

+617
-3
lines changed

apps/react/examples/Examples.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ import SelectBoxTemplatesExample from './selectbox-templates-example';
2727
import SelectBoxFieldTemplateExample from './selectbox-fieldtemplate-example';
2828
import SelectBoxParallelTemplateRenderExample from './selectbox-parallel-template-render-example';
2929
import TagBoxFieldTemplateCheckBoxesExample from './tagbox-fieldtemplate-checkboxes-example';
30+
import FormWithNestedExample from './form-with-nested';
31+
3032

3133
const Examples = () => {
3234
return (
3335
<div>
36+
<FormWithNestedExample />
37+
3438
<AccordionExample />
3539

3640
<BoxExample />
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import React, { useCallback, useMemo, useState } from 'react';
2+
import 'devextreme-react/text-area';
3+
import Form, {
4+
GroupItem, SimpleItem, Label, ButtonItem,
5+
} from 'devextreme-react/form';
6+
7+
const employeeCore = {
8+
FirstName: 'John',
9+
LastName: 'Heart',
10+
Address: '351 S Hill St., Los Angeles, CA',
11+
City: 'Atlanta',
12+
Phones: [],
13+
};
14+
15+
function getEmployee() {
16+
return employeeCore;
17+
}
18+
19+
const employee = getEmployee();
20+
21+
const App = () => {
22+
const [phones, setPhones] = useState(employee.Phones);
23+
const [isHomeAddressVisible, setIsHomeAddressVisible] = useState(true);
24+
const formData = useMemo(() => ({ ...employee, Phones: phones }), [phones]);
25+
26+
const CheckBoxOptions = useMemo(() => ({
27+
text: 'Show Address',
28+
value: isHomeAddressVisible,
29+
onValueChanged: (e) => {
30+
setIsHomeAddressVisible(e.component.option('value'));
31+
},
32+
}), [isHomeAddressVisible]);
33+
34+
const PhoneEditorOptions = useCallback((index: number) => ({
35+
mask: '+1 (X00) 000-0000',
36+
maskRules: { X: /[01-9]/ },
37+
buttons: [{
38+
name: 'trash',
39+
location: 'after',
40+
options: {
41+
stylingMode: 'text',
42+
icon: 'trash',
43+
onClick: () => {
44+
const newPhones = phones.slice(0, index).concat(phones.slice(index + 1));
45+
setPhones(newPhones);
46+
},
47+
},
48+
}],
49+
}), [phones]);
50+
51+
const PhoneButtonOptions = useMemo(() => ({
52+
icon: 'add',
53+
text: 'Add phone',
54+
onClick: () => {
55+
setPhones((prevPhones) => [...prevPhones, '']);
56+
},
57+
}), []);
58+
59+
return (
60+
<React.Fragment>
61+
<div className="long-title"><h3>Personal details</h3></div>
62+
<div className="form-container">
63+
<Form
64+
colCount={2}
65+
id="form"
66+
formData={formData}>
67+
<GroupItem>
68+
<GroupItem>
69+
<GroupItem caption="Personal Data">
70+
<SimpleItem dataField="FirstName" />
71+
<SimpleItem dataField="LastName" />
72+
<SimpleItem editorType="dxCheckBox" editorOptions={CheckBoxOptions} />
73+
</GroupItem>
74+
</GroupItem>
75+
<GroupItem>
76+
<GroupItem
77+
name="HomeAddress"
78+
caption="Home Address"
79+
visible={isHomeAddressVisible}>
80+
<SimpleItem dataField="Address" />
81+
<SimpleItem dataField="City" />
82+
</GroupItem>
83+
</GroupItem>
84+
</GroupItem>
85+
<GroupItem
86+
caption="Phones"
87+
name="phones-container">
88+
<GroupItem name="phones">
89+
{phones.map((_, index) => (
90+
<SimpleItem
91+
key={`phone-${index}`}
92+
dataField={`Phones[${index}]`}
93+
editorOptions={PhoneEditorOptions(index)}>
94+
<Label text={`Phone ${index + 1}`} />
95+
</SimpleItem>
96+
))}
97+
</GroupItem>
98+
<ButtonItem
99+
horizontalAlignment="left"
100+
cssClass="add-phone-button"
101+
buttonOptions={PhoneButtonOptions}
102+
/>
103+
</GroupItem>
104+
</Form>
105+
</div>
106+
</React.Fragment>
107+
);
108+
};
109+
110+
export default App;

e2e/wrappers/builders/angular19/src/utils/componentFinder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ export const COMPONENTS = [
44
name: 'Button',
55
component: import('@external/button/angular19/button.component').then((m) => m.ButtonComponent),
66
},
7+
{
8+
path: 'inputs-list-in-form',
9+
name: 'InputsListInForm',
10+
component: import('@external/inputs-list-in-form/angular19/inputs-list-in-form.component').then((m) => m.InputsListInFormComponent),
11+
},
712
];
813

914
export function findComponentByPath(path: string) {

e2e/wrappers/builders/react19/src/utils/componentFinder.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ const COMPONENTS = [
33
path: 'button',
44
name: 'Button',
55
component: () => import('@examples/button/react19/index.jsx')
6-
}
6+
},
7+
{
8+
path: 'inputs-list-in-form',
9+
name: 'InputsListInForm',
10+
component: () => import('@examples/inputs-list-in-form/react19/index.jsx')
11+
}
712
];
813

914
export function getAllComponents() {

e2e/wrappers/builders/vue3/src/utils/componentFinder.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ const COMPONENTS = [
33
path: 'button',
44
name: 'Button',
55
component: () => import('@examples/button/vue3/index.vue')
6+
},
7+
{
8+
path: 'inputs-list-in-form',
9+
name: 'InputsListInForm',
10+
component: () => import('@examples/inputs-list-in-form/vue3/index.vue')
611
}
712
];
813

9-
export default COMPONENTS;
14+
export default COMPONENTS;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export class Employee {
2+
FirstName: string = '';
3+
LastName: string = '';
4+
Address: string = '';
5+
City: string = '';
6+
Phones: string[] = [];
7+
}
8+
9+
export const employee: Employee = {
10+
FirstName: 'John',
11+
LastName: 'Heart',
12+
Address: '351 S Hill St., Los Angeles, CA',
13+
City: 'Atlanta',
14+
Phones: [],
15+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Injectable } from '@angular/core';
2+
import { Employee, employee } from './employee.model';
3+
4+
@Injectable({ providedIn: 'root' })
5+
export class EmployeeService {
6+
getEmployee(): Employee {
7+
return employee;
8+
}
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { InputsListInFormComponent } from './inputs-list-in-form.component';
2+
3+
export default {
4+
component: InputsListInFormComponent,
5+
path: 'inputs-list-in-form'
6+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<div class="long-title">
2+
<h3>Personal details</h3>
3+
</div>
4+
<div id="form-container">
5+
<dx-form id="form" [colCount]="2" [formData]="employee">
6+
<dxi-item itemType="group">
7+
<dxi-item itemType="group" caption="Personal Data">
8+
<dxi-item dataField="FirstName"></dxi-item>
9+
<dxi-item dataField="LastName"></dxi-item>
10+
<dxi-item editorType="dxCheckBox" [editorOptions]="checkBoxOptions"></dxi-item>
11+
</dxi-item>
12+
<dxi-item itemType="group">
13+
<dxi-item
14+
itemType="group"
15+
caption="Home Address"
16+
name="HomeAddress"
17+
[visible]="isHomeAddressVisible"
18+
>
19+
<dxi-item dataField="Address"></dxi-item>
20+
<dxi-item dataField="City"></dxi-item>
21+
</dxi-item>
22+
</dxi-item>
23+
</dxi-item>
24+
<dxi-item itemType="group" caption="Phones" name="phones-container">
25+
<dxi-item
26+
itemType="group"
27+
name="phones"
28+
*ngFor="let phone of phoneOptions; let i = index"
29+
>
30+
<dxi-item
31+
[dataField]="'Phones[' + i + ']'"
32+
[editorOptions]="phoneOptions[i]"
33+
>
34+
<dxo-label [text]="'Phone ' + (i + 1)"></dxo-label>
35+
</dxi-item>
36+
</dxi-item>
37+
<dxi-item
38+
itemType="button"
39+
horizontalAlignment="left"
40+
cssClass="add-phone-button"
41+
[buttonOptions]="addPhoneButtonOptions"
42+
></dxi-item>
43+
</dxi-item>
44+
</dx-form>
45+
</div>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Component } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { DxFormModule } from 'devextreme-angular';
4+
import { EmployeeService } from './employee.service';
5+
import { DxCheckBoxTypes } from 'devextreme-angular/ui/check-box';
6+
import { DxButtonTypes } from 'devextreme-angular/ui/button';
7+
import { DxTextBoxTypes } from 'devextreme-angular/ui/text-box';
8+
import { Employee } from './employee.model';
9+
10+
@Component({
11+
selector: 'app-root',
12+
standalone: true,
13+
imports: [CommonModule, DxFormModule],
14+
templateUrl: './inputs-list-in-form.component.html',
15+
})
16+
export class InputsListInFormComponent {
17+
employee: Employee;
18+
isHomeAddressVisible = true;
19+
phoneOptions: DxTextBoxTypes.Properties[] = [];
20+
21+
checkBoxOptions: DxCheckBoxTypes.Properties = {
22+
text: 'Show Address',
23+
value: true,
24+
onValueChanged: (e: DxCheckBoxTypes.ValueChangedEvent) => {
25+
this.isHomeAddressVisible = e.component.option('value') as boolean;
26+
},
27+
};
28+
29+
addPhoneButtonOptions: DxButtonTypes.Properties = {
30+
icon: 'add',
31+
text: 'Add phone',
32+
onClick: () => {
33+
this.employee.Phones.push('');
34+
this.phoneOptions = this.getPhonesOptions(this.employee.Phones);
35+
},
36+
};
37+
38+
constructor(service: EmployeeService) {
39+
this.employee = service.getEmployee();
40+
this.phoneOptions = this.getPhonesOptions(this.employee.Phones);
41+
}
42+
43+
getPhonesOptions = (phones: string[]) =>
44+
phones.map((_, index) => this.generateNewPhoneOptions(index));
45+
46+
generateNewPhoneOptions(index: number): DxTextBoxTypes.Properties {
47+
return {
48+
mask: '+1 (X00) 000-0000',
49+
maskRules: { X: /[01-9]/ },
50+
buttons: [
51+
{
52+
name: 'trash',
53+
location: 'after',
54+
options: {
55+
stylingMode: 'text',
56+
icon: 'trash',
57+
onClick: () => {
58+
this.employee.Phones.splice(index, 1);
59+
this.phoneOptions = this.getPhonesOptions(this.employee.Phones);
60+
},
61+
},
62+
},
63+
],
64+
};
65+
}
66+
}

0 commit comments

Comments
 (0)