@model DevExtreme.NETCore.Demos.ViewModels.StepperFormViewModel
@(Html.DevExtreme().Stepper()
    .ID("stepper")
    .OnInitialized("onStepperInitialized")
    .OnSelectionChanged("onSelectionChanged")
    .OnSelectionChanging("onSelectionChanging")
    .Items(items =>
    {
       foreach (var step in Model.Steps)
       {
           items.Add()
               .Label(step.Label)
               .Hint(step.Hint)
               .Icon(step.Icon)
               .Optional(step.Optional);
       }
    })
)
<div class="content">
    @(Html.DevExtreme().MultiView()
        .ID("stepContent")
        .Height(400)
        .Loop(false)
        .AnimationEnabled(false)
        .FocusStateEnabled(false)
        .OnInitialized("onMultiViewInitialized")
        .Items(items =>
        {
            items.Add()
                .Template(new JS("getDatesTemplate()"));
            items.Add()
                .Template(new JS("getGuestsTemplate()"));
            items.Add()
                .Template(new JS("getRoomAndMealTemplate()"));
            items.Add()
                .Template(new JS("getAdditionalRequestsTemplate()"));
            items.Add()
                .Template(new JS("getConfirmationTemplate()"));
        })
    )
    <div class="nav-panel">
        <div class="current-step">Step <span class="selected-index">1</span> of <span class="step-count">@Model.Steps.Length</span></div>
        <div class="nav-buttons">
            @(Html.DevExtreme().Button()
                .ID("prevButton")
                .Type(ButtonType.Normal)
                .Text("Back")
                .Visible(false)
                .Width(100)
                .OnInitialized("onPrevButtonInitialized")
                .OnClick("onPrevButtonClick")
            )
            @(Html.DevExtreme().Button()
                .ID("nextButton")
                .Type(ButtonType.Default)
                .Text("Next")
                .Width(100)
                .OnInitialized("onNextButtonInitialized")
                .OnClick("onNextButtonClick")
            )
        </div>
    </div>
</div>
<script>
    let stepper,
        prevButton,
        nextButton
        stepContent;
    const stepsCount = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.Steps.Length));
    let confirmed = false;
    const roomTypes = ['Single', 'Double', 'Suite'];
    const mealPlans = ['Bed & Breakfast', 'Half Board', 'Full Board', 'All-Inclusive'];
    const validationGroups = ['dates', 'guests', 'roomAndMealPlan'];
    function getInitialFormData() {
        const initialFormData = {
            dates: [null, null],
            adultsCount: 0,
            childrenCount: 0,
            petsCount: 0,
            roomType: undefined,
            mealPlan: undefined,
            additionalRequest: '',
        };
        return {
            ...initialFormData,
            dates: [...initialFormData.dates],
        };
    }
    let formData = getInitialFormData();
    function onStepperInitialized(e) {
        stepper = e.component;
    }
    function onMultiViewInitialized(e) {
        stepContent = e.component;
    }
    function onNextButtonInitialized(e) {
        nextButton = e.component;
    }
    function onPrevButtonInitialized(e) {
        prevButton = e.component;
    }
    function setSelectedIndex(index) {
        stepper.option('selectedIndex', index);
    }
    function getValidationResult(index) {
        if (index >= validationGroups.length) {
            return true;
        }
        return DevExpress.validationEngine.validateGroup(validationGroups[index]).isValid;
    }
    function setStepValidationResult(index, isValid) {
        stepper.option(`items[${index}].isValid`, isValid);
    }
    function onPrevButtonClick(e) {
        const selectedIndex = stepper.option('selectedIndex');
        setSelectedIndex(selectedIndex - 1);
    }
    function moveNext(selectedIndex) {
        const isValid = getValidationResult(selectedIndex);
        setStepValidationResult(selectedIndex, isValid);
        if (isValid) {
            setSelectedIndex(selectedIndex + 1);
        }
    }
    function setStepperReadonly(readonly) {
        stepper.option('focusStateEnabled', !readonly);
        if (readonly) {
            stepper.option('elementAttr', { class: 'readonly' });
        } else {
            stepper.resetOption('elementAttr');
        }
    }
    function confirm() {
        confirmed = true;
        prevButton.option('visible', false);
        nextButton.option('text', 'Reset');
        setStepValidationResult(stepsCount - 1, true);
        setStepperReadonly(true);
        $('.current-step').text('');
    }
    function resetStepperState() {
        stepper.beginUpdate();
        stepper.option('selectedIndex', 0);
        setStepperReadonly(false);
        for (let i = 0; i < stepsCount; i += 1) {
            setStepValidationResult(i, undefined);
        }
        stepper.endUpdate();
    }
    function reset() {
        confirmed = false;
        resetStepperState();
        formData = getInitialFormData();
        stepContent.repaint();
        $('.current-step').append(`Step <span class="selected-index">1</span> of <span class="step-count">${stepsCount}</span>`);
    };
    function onNextButtonClick(e) {
        const selectedIndex = stepper.option('selectedIndex');
        if (selectedIndex < stepsCount - 1) {
            moveNext(selectedIndex);
        } else if (confirmed) {
            reset();
        } else {
            confirm();
        }
        if (stepper.option('selectedIndex') === stepsCount - 1) {
            stepContent.option('items[4].template', getConfirmationTemplate());
        }
    }
    function onSelectionChanged(e) {
        const selectedIndex = e.component.option('selectedIndex');
        const isLastStep = selectedIndex === stepsCount - 1;
        prevButton.option('visible', !!selectedIndex);
        nextButton.option('text', isLastStep ? 'Confirm' : 'Next');
        stepContent.option('selectedIndex', selectedIndex);
        $('.selected-index').text(selectedIndex + 1);
    }
    function onSelectionChanging(e) {
        const { component, addedItems, removedItems } = e;
        const { items = [] } = component.option();
        const addedIndex = items.findIndex((item) => item === addedItems[0]);
        const removedIndex = items.findIndex((item) => item === removedItems[0]);
        const isMoveForward = addedIndex > removedIndex;
        if (isMoveForward) {
            const isValid = getValidationResult(removedIndex);
            setStepValidationResult(removedIndex, isValid);
            if (isValid === false) {
                e.cancel = true;
            }
        }
    }
    function getDatesTemplate() {
        return function () {
            return $('<div>').append(
                $('<p>').text(`Select your check-in and check-out dates. If your dates are flexible, include that information
                               in Additional Requests. We will do our best to suggest best pricing options,
                               depending on room availability.`),
                $('<div>').dxForm({
                    formData,
                    validationGroup: validationGroups[0],
                    items: [{
                        dataField: 'dates',
                        editorType: 'dxDateRangeBox',
                        editorOptions: {
                            elementAttr: { id: 'datesPicker' },
                            startDatePlaceholder: 'Check-in',
                            endDatePlaceholder: 'Check-out',
                        },
                        isRequired: true,
                        label: { visible: false },
                    }],
                }),
            );
        }
    }
    function getGuestsTemplate() {
        return function () {
            const getNumberBoxOptions = (options) => ({
                editorType: 'dxNumberBox',
                ...options,
                editorOptions: {
                    showSpinButtons: true,
                    min: 0,
                    max: 5,
                    ...options.editorOptions,
                },
                label: {
                    location: 'top',
                    ...options.label,
                },
            });
            return $('<div>').append(
                $('<p>').text(`Enter the number of adults, children, and pets staying in the room. This information help us
                               suggest suitable room types, number of beds, and included amenities.`),
                $('<div>').dxForm({
                    formData,
                    validationGroup: validationGroups[1],
                    colCount: 3,
                    colCountByScreen: { xs: 3 },
                    items: [
                        getNumberBoxOptions({
                            dataField: 'adultsCount',
                            isRequired: true,
                            label: { text: 'Adults' },
                            editorOptions: {
                                elementAttr: { id: 'adultsCount' },
                            },
                            validationRules: [{
                                type: 'range',
                                min: 1,
                            }],
                        }),
                        getNumberBoxOptions({
                            dataField: 'childrenCount',
                            label: { text: 'Children' },
                        }),
                        getNumberBoxOptions({
                            dataField: 'petsCount',
                            label: { text: 'Pets' },
                        }),
                    ],
                }),
            );
        }
    }
    function getRoomAndMealTemplate() {
        return function () {
            const getSelectBoxOptions = (options) => ({
                editorType: 'dxSelectBox',
                isRequired: true,
                ...options,
                label: {
                    location: 'top',
                    ...options.label,
                },
            });
            return $('<div>').append(
                $('<p>').text(`Review room types that can accommodate your group size and make your selection.
                               You can also choose a meal plan, whether it\'s breakfast only or full board.`),
                $('<div>').dxForm({
                    formData,
                    validationGroup: validationGroups[2],
                    colCount: 2,
                    colCountByScreen: { xs: 2 },
                    items: [
                        getSelectBoxOptions({
                            dataField: 'roomType',
                            editorOptions: {
                                items: roomTypes,
                                elementAttr: { id: 'roomType' },
                            },
                            label: { text: 'Room Type' },
                        }),
                        getSelectBoxOptions({
                            dataField: 'mealPlan',
                            editorOptions: {
                                items: mealPlans,
                                elementAttr: { id: 'mealPlan' },
                            },
                            label: { text: 'Meal Plan' },
                        }),
                    ],
                }),
            );
        }
    }
    function getAdditionalRequestsTemplate() {
        return function () {
            return $('<div>').append(
                $('<div>').text('Please let us know if you have any other requests.'),
                $('<div>').dxForm({
                    formData,
                    items: [
                        {
                            dataField: 'additionalRequest',
                            editorType: 'dxTextArea',
                            editorOptions: {
                                height: 160,
                                elementAttr: { id: 'additionalRequest' },
                            },
                            label: { visible: false },
                        },
                    ],
                }),
            );
        }
    }
    function getConfirmationTemplate() {
        return function () {
            if (confirmed) {
                return '<div class="summary-item-header center">Your booking request was submitted.</div>';
            }
            const summaryContainer = $('<div class="summary-container">');
            const datesData = $(`
                <div class="summary-item">
                    <div class="summary-item-header">Dates</div>
                    <div class="separator"></div>
                    <div><span class="summary-item-label">Check-in Date: </span>${new Date(formData.dates[0]).toLocaleDateString()}</div>
                    <div><span class="summary-item-label">Check-out Date: </span>${new Date(formData.dates[1]).toLocaleDateString()}</div>
                </div>
            `);
            const guestsData = $(`
                <div class="summary-item">
                    <div class="summary-item-header">Guests</div>
                    <div class="separator"></div>
                    <div><span class="summary-item-label">Adults: </span>${formData.adultsCount}</div>
                    <div><span class="summary-item-label">Children: </span>${formData.childrenCount}</div>
                    <div><span class="summary-item-label">Pets: </span>${formData.petsCount}</div>
                </div>
            `);
            const roomAndMealData = $(`
                <div class="summary-item">
                    <div class="summary-item-header">Room and Meals</div>
                    <div class="separator"></div>
                    <div><span class="summary-item-label">Room Type: </span>${formData.roomType}</div>
                    <div><span class="summary-item-label">Check-out Date: </span>${formData.mealPlan}</div>
                </div>
            `);
            summaryContainer.append(datesData, guestsData, roomAndMealData);
            if (formData.additionalRequest) {
                const additionalRequestsData = $(`
                    <div class="summary-item">
                        <div class="summary-item-header">Additional Requests</div>
                        <div class="separator"></div>
                        <div>${formData.additionalRequest}</div>
                    </div>
                `);
                summaryContainer.append(additionalRequestsData);
            }
            return summaryContainer;
        }
    }
</script>
        
        using DevExtreme.NETCore.Demos.Models.SampleData;
using DevExtreme.NETCore.Demos.ViewModels;
using Microsoft.AspNetCore.Mvc;
namespace DevExtreme.NETCore.Demos.Controllers {
    public class StepperController : Controller {
        public ActionResult FormIntegration() {
            return View(new StepperFormViewModel {
                Steps = SampleData.Steps,
            });
        }
    }
}
        
        using DevExtreme.NETCore.Demos.ViewModels;
using System;
namespace DevExtreme.NETCore.Demos.Models.SampleData {
    public partial class SampleData {
        public static StepViewModel[] Steps = new [] {
            new StepViewModel { Label = "Dates", Hint = "Dates", Icon = "daterangepicker" },
            new StepViewModel { Label = "Guests", Hint = "Guests", Icon = "group" },
            new StepViewModel { Label = "Room and Meal Plan", Hint = "Room and Meal Plan", Icon = "servicebell" },
            new StepViewModel { Label = "Additional Requests", Hint = "Additional Requests", Icon = "clipboardtasklist", Optional = true },
            new StepViewModel { Label = "Confirmation", Hint = "Confirmation", Icon = "checkmarkcircle" },
        };
    }
}
        
        using DevExtreme.NETCore.Demos.Models;
using System;
namespace DevExtreme.NETCore.Demos.ViewModels {
    public class StepViewModel {
        public string Label { get; set; }
        public string Hint { get; set; }
        public string Icon { get; set; }
        public bool Optional { get; set; }
    }
    public class StepperFormViewModel {
        public StepViewModel[] Steps { get; set; }
    }
}
        
        .demo-wrapper .simulator .demo-device .demo-container {
    min-height: 580px;
}
.demo-container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    row-gap: 20px;
    height: 580px;
    min-width: 620px;
}
.content {
    padding-inline: 40px;
    flex: 1;
    display: flex;
    flex-direction: column;
    row-gap: 20px;
}
.dx-multiview-item-content:has(> .summary-container) {
    overflow: auto;
}
.summary-container {
    display: flex;
    flex-direction: column;
    row-gap: 20px;
}
.summary-item {
    display: flex;
    flex-direction: column;
    row-gap: 8px;
}
.summary-item-header {
    font-weight: 600;
    font-size: var(--dx-font-size-sm);
}
.center {
    text-align: center;
}
.summary-item-label {
  color: var(--dx-color-icon);
}
.separator {
    width: 100%;
    height: 1px;
    border-bottom: solid 1px var(--dx-color-border);
}
.nav-panel {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.current-step {
    color: var(--dx-color-icon);
}
.nav-buttons {
    display: flex;
    gap: 8px;
}
.readonly {
    pointer-events: none;
}