Skip to content

Commit 67a5ec3

Browse files
Stepper: add form integration demo (DevExpress#29622)
1 parent 20b5f89 commit 67a5ec3

File tree

82 files changed

+3021
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3021
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div> Please let us know if you have any other requests. </div>
2+
<dx-form [(formData)]="formData">
3+
<dxi-item
4+
dataField="additionalRequest"
5+
editorType="dxTextArea"
6+
[editorOptions]="textAreaOptions"
7+
[label]="labelOptions"
8+
>
9+
</dxi-item>
10+
</dx-form>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, Input } from '@angular/core';
2+
import { DxFormTypes } from 'devextreme-angular/ui/form';
3+
import { DxTextAreaTypes } from 'devextreme-angular/ui/text-area';
4+
import type { BookingFormData } from '../app.types';
5+
6+
let modulePrefix = '';
7+
// @ts-ignore
8+
if (window && window.config?.packageConfigPaths) {
9+
modulePrefix = '/app';
10+
}
11+
12+
@Component({
13+
selector: 'additional-form',
14+
templateUrl: `.${modulePrefix}/additional-form/additional-form.component.html`,
15+
})
16+
export class AdditionalFormComponent {
17+
@Input() formData: BookingFormData;
18+
19+
textAreaOptions: DxTextAreaTypes.Properties = {
20+
height: 160,
21+
elementAttr: { id: 'additionalRequest' },
22+
}
23+
24+
labelOptions: DxFormTypes.SimpleItem["label"] = {
25+
visible: false,
26+
};
27+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
::ng-deep .demo-container {
2+
display: flex;
3+
flex-direction: column;
4+
justify-content: center;
5+
row-gap: 20px;
6+
height: 480px;
7+
min-width: 620px;
8+
padding: 40px 20px;
9+
}
10+
11+
::ng-deep .content {
12+
padding-inline: 40px;
13+
flex: 1;
14+
display: flex;
15+
flex-direction: column;
16+
row-gap: 20px;
17+
}
18+
19+
::ng-deep .nav-panel {
20+
display: flex;
21+
align-items: center;
22+
justify-content: space-between;
23+
}
24+
25+
::ng-deep .current-step {
26+
color: var(--dx-color-icon);
27+
}
28+
29+
::ng-deep .nav-buttons > *:not(:last-child) {
30+
margin-right: 8px;
31+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<dx-stepper
2+
[(selectedIndex)]="selectedIndex"
3+
(onSelectionChanging)="onSelectionChanging($event)"
4+
>
5+
<dxi-item
6+
*ngFor="let step of steps"
7+
[label]="step.label"
8+
[icon]="step.icon"
9+
[isValid]="step.isValid"
10+
[hint]="step.hint"
11+
></dxi-item>
12+
</dx-stepper>
13+
<div class="content">
14+
<dx-multi-view
15+
[(selectedIndex)]="selectedIndex"
16+
[animationEnabled]="false"
17+
[swipeEnabled]="false"
18+
[height]="300"
19+
>
20+
<dxi-item>
21+
<div *dxTemplate>
22+
<dates-form
23+
[formData]="formData"
24+
[validationGroup]="validationGroups[0]"
25+
></dates-form>
26+
</div>
27+
</dxi-item>
28+
<dxi-item>
29+
<div *dxTemplate>
30+
<guests-form
31+
[formData]="formData"
32+
[validationGroup]="validationGroups[1]"
33+
></guests-form>
34+
</div>
35+
</dxi-item>
36+
<dxi-item>
37+
<div *dxTemplate>
38+
<room-meal-plan-form
39+
[formData]="formData"
40+
[validationGroup]="validationGroups[2]"
41+
></room-meal-plan-form>
42+
</div>
43+
</dxi-item>
44+
<dxi-item>
45+
<div *dxTemplate>
46+
<additional-form [formData]="formData"></additional-form>
47+
</div>
48+
</dxi-item>
49+
<dxi-item>
50+
<div *dxTemplate>
51+
<confirmation
52+
[formData]="formData"
53+
[isConfirmed]="isConfirmed"
54+
></confirmation>
55+
</div>
56+
</dxi-item>
57+
</dx-multi-view>
58+
<div class="nav-panel">
59+
<div class="current-step">
60+
<span *ngIf="!isConfirmed">
61+
Step <span class="selected-index">{{ selectedIndex + 1 }}</span> of
62+
<span class="step-count">{{ steps.length }}</span>
63+
</span>
64+
</div>
65+
<div class="nav-buttons">
66+
<dx-button
67+
id="prevButton"
68+
[visible]="selectedIndex !== 0 && !isConfirmed"
69+
text="Back"
70+
type="normal"
71+
(onClick)="onPrevButtonClick($event)"
72+
[width]="100"
73+
>
74+
</dx-button>
75+
<dx-button
76+
id="nextButton"
77+
[text]="getNextButtonText()"
78+
type="default"
79+
(onClick)="onNextButtonClick($event)"
80+
[width]="100"
81+
>
82+
</dx-button>
83+
</div>
84+
</div>
85+
</div>
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { NgModule, Component, enableProdMode } from '@angular/core';
2+
import { BrowserModule } from '@angular/platform-browser';
3+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4+
import {
5+
DxStepperModule,
6+
DxButtonModule,
7+
DxMultiViewModule,
8+
DxFormModule,
9+
DxDateRangeBoxModule,
10+
DxNumberBoxModule,
11+
DxSelectBoxModule,
12+
DxTextAreaModule,
13+
} from 'devextreme-angular';
14+
import type { Item, SelectionChangingEvent } from 'devextreme/ui/stepper';
15+
import validationEngine from "devextreme/ui/validation_engine";
16+
import { AppService } from './app.service';
17+
import { BookingFormData } from './app.types';
18+
import { DatesFormComponent } from "./dates-form/dates-form.component";
19+
import { GuestsFormComponent } from "./guests-form/guests-form.component";
20+
import { RoomMealPlanFormComponent } from "./room-meal-plan-form/room-meal-plan-form.component";
21+
import { AdditionalFormComponent } from "./additional-form/additional-form.component";
22+
import { ConfirmationComponent } from "./confirmation/confirmation.component";
23+
24+
if (!/localhost/.test(document.location.host)) {
25+
enableProdMode();
26+
}
27+
28+
let modulePrefix = '';
29+
// @ts-ignore
30+
if (window && window.config?.packageConfigPaths) {
31+
modulePrefix = '/app';
32+
}
33+
34+
@Component({
35+
selector: 'demo-app',
36+
templateUrl: `.${modulePrefix}/app.component.html`,
37+
styleUrls: [`.${modulePrefix}/app.component.css`],
38+
})
39+
export class AppComponent {
40+
steps: Item[];
41+
42+
formData: BookingFormData;
43+
44+
selectedIndex: number;
45+
46+
isConfirmed: boolean;
47+
48+
validationGroups = ['dates', 'guests', 'roomAndMealPlan'];
49+
50+
constructor(private readonly appService: AppService) {
51+
this.steps = this.appService.getInitialSteps();
52+
this.formData = this.appService.getInitialFormData();
53+
this.selectedIndex = 0;
54+
this.isConfirmed = false;
55+
56+
this.getNextButtonText = this.getNextButtonText.bind(this);
57+
}
58+
59+
getValidationResult(index: number){
60+
if (index >= this.validationGroups.length) {
61+
return true;
62+
}
63+
64+
return validationEngine.validateGroup(this.validationGroups[index]).isValid;
65+
}
66+
67+
setStepValidationResult(index: number, isValid: boolean | undefined){
68+
this.steps[index].isValid = isValid;
69+
}
70+
71+
onSelectionChanging(e: SelectionChangingEvent) {
72+
if (this.isConfirmed) {
73+
e.cancel = true;
74+
75+
return;
76+
}
77+
78+
const { component, addedItems, removedItems } = e;
79+
const { items = [] } = component.option();
80+
81+
const addedIndex = items.findIndex((item: Item) => item === addedItems[0]);
82+
const removedIndex = items.findIndex((item: Item) => item === removedItems[0]);
83+
const isMoveForward = removedIndex > -1 && addedIndex > removedIndex;
84+
85+
if (isMoveForward) {
86+
const isValid = this.getValidationResult(removedIndex);
87+
88+
this.setStepValidationResult(removedIndex, isValid);
89+
90+
if (isValid === false) {
91+
e.cancel = true;
92+
}
93+
}
94+
}
95+
96+
getNextButtonText() {
97+
if (this.selectedIndex < this.steps.length - 1) {
98+
return 'Next';
99+
}
100+
101+
return this.isConfirmed ? 'Reset' : 'Confirm';
102+
}
103+
104+
onPrevButtonClick() {
105+
this.selectedIndex -= 1;
106+
}
107+
108+
moveNext() {
109+
const isValid = this.getValidationResult(this.selectedIndex);
110+
111+
this.setStepValidationResult(this.selectedIndex, isValid);
112+
113+
if (isValid) {
114+
this.selectedIndex += 1;
115+
}
116+
}
117+
118+
reset(){
119+
this.isConfirmed = false;
120+
this.selectedIndex = 0;
121+
this.steps = this.appService.getInitialSteps();
122+
this.formData = this.appService.getInitialFormData();
123+
validationEngine.resetGroup(this.validationGroups[0]);
124+
validationEngine.resetGroup(this.validationGroups[1]);
125+
}
126+
127+
confirm(){
128+
this.isConfirmed = true;
129+
this.setStepValidationResult(this.selectedIndex, true);
130+
}
131+
132+
onNextButtonClick() {
133+
if (this.selectedIndex < this.steps.length - 1) {
134+
this.moveNext();
135+
} else if (this.isConfirmed) {
136+
this.reset();
137+
} else {
138+
this.confirm();
139+
}
140+
}
141+
}
142+
143+
@NgModule({
144+
imports: [
145+
BrowserModule,
146+
DxStepperModule,
147+
DxButtonModule,
148+
DxMultiViewModule,
149+
DxFormModule,
150+
DxDateRangeBoxModule,
151+
DxNumberBoxModule,
152+
DxSelectBoxModule,
153+
DxTextAreaModule,
154+
],
155+
declarations: [
156+
AppComponent,
157+
DatesFormComponent,
158+
GuestsFormComponent,
159+
RoomMealPlanFormComponent,
160+
AdditionalFormComponent,
161+
ConfirmationComponent,
162+
],
163+
bootstrap: [AppComponent],
164+
providers: [AppService],
165+
})
166+
export class AppModule { }
167+
168+
platformBrowserDynamic().bootstrapModule(AppModule);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Injectable } from '@angular/core';
2+
import { Item } from 'devextreme/ui/stepper';
3+
import {BookingFormData} from "./app.types";
4+
import {initialFormData, initialSteps} from "../../Vue/data";
5+
6+
@Injectable({
7+
providedIn: 'root',
8+
})
9+
export class AppService {
10+
initialSteps: Item[];
11+
12+
initialFormData: BookingFormData;
13+
14+
roomTypes: string[];
15+
16+
mealPlans: string[];
17+
18+
constructor() {
19+
this.initialSteps = [
20+
{
21+
label: 'Dates', hint: 'Dates', icon: 'daterangepicker',
22+
},
23+
{
24+
label: 'Guests', hint: 'Guests', icon: 'group',
25+
},
26+
{
27+
label: 'Room and Meal Plan', hint: 'Room and Meal Plan', icon: 'servicebell',
28+
},
29+
{
30+
label: 'Additional Requests', hint: 'Additional Requests', icon: 'clipboardtasklist', optional: true,
31+
},
32+
{
33+
label: 'Confirmation', hint: 'Confirmation', icon: 'checkmarkcircle',
34+
},
35+
];
36+
37+
this.initialFormData = {
38+
dates: [null, null],
39+
adultsCount: 0,
40+
childrenCount: 0,
41+
petsCount: 0,
42+
roomType: undefined,
43+
mealPlan: undefined,
44+
additionalRequest: '',
45+
};
46+
}
47+
48+
getInitialSteps(): Item[] {
49+
return this.initialSteps.map((item) => ({ ...item }));
50+
}
51+
52+
getInitialFormData(): BookingFormData {
53+
return {
54+
...this.initialFormData,
55+
dates: [...this.initialFormData.dates],
56+
};
57+
}
58+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface BookingFormData {
2+
dates: Array<Date | null>;
3+
adultsCount: number;
4+
childrenCount: number;
5+
petsCount: number;
6+
roomType: string | undefined;
7+
mealPlan: string | undefined;
8+
additionalRequest: string;
9+
}

0 commit comments

Comments
 (0)