From ff33fafb3860d1fd208252fc05684a6141a3a8d3 Mon Sep 17 00:00:00 2001 From: Arman Boyakhchyan Date: Tue, 17 Jun 2025 08:29:57 +0400 Subject: [PATCH 01/10] Stepper: Add Form Integration How To topic --- .../00 Implement a Wizard.md | 16 ++ .../05 Set Up App Layout.md | 150 ++++++++++++++++ ... Synchronize Steps and Navigation Panel.md | 161 ++++++++++++++++++ .../15 Implement Validation.md | 93 ++++++++++ 4 files changed, 420 insertions(+) create mode 100644 concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md create mode 100644 concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md create mode 100644 concepts/05 UI Components/Stepper/30 Implement a Wizard/10 Synchronize Steps and Navigation Panel.md create mode 100644 concepts/05 UI Components/Stepper/30 Implement a Wizard/15 Implement Validation.md diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md new file mode 100644 index 0000000000..6ca56358d5 --- /dev/null +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md @@ -0,0 +1,16 @@ +Stepper can be used with [Form](/Documentation/Guide/UI_Components/Form/Overview/) and [MultiView](/Documentation/Guide/UI_Components/MultiView/Overview/) components to implement a wizard-like application. + +This wizard includes three key parts: + +- The Stepper component. +- Step content where the MultiView component shows content for each step. Every view with input fields contains a Form. +- A navigation panel that displays the current step ("Step 1 of 5") and buttons for moving between steps (Next/Back). + +Image, TBA + +To view the complete code of this example, refer to the following demo: + +#include common-demobutton-named with { + url: "https://js.devexpress.com/Demos/WidgetsGallery/Demo/Stepper/FormIntegration/", + name: "Stepper - Form Integration" +} \ No newline at end of file diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md new file mode 100644 index 0000000000..ee79267536 --- /dev/null +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md @@ -0,0 +1,150 @@ +To get started, add Stepper and MultiView to your project. Create a navigation panel by adding a `
` element to display the active step and two [Button](/Documentation/Guide/UI_Components/Button/Overview/) components to a container: + +--- + +##### jQuery + + +
+
+
+ +
+ + + const stepper = $('#stepper').dxStepper({ + items: steps, + }).dxStepper('instance'); + + const stepContent = $('#stepContent').dxMultiView({ + // ... + focusStateEnabled: false, + swipeEnabled: false, + items: multiViewItems, + }).dxMultiView('instance'); + + const prevButton = $('#prevButton').dxButton({ + // ... + text: 'Back', + visible: false, + }).dxButton('instance'); + + const nextButton = $('#nextButton').dxButton({ + // ... + text: 'Next', + }).dxButton('instance'); + +##### Angular + + + + + +
+ + + +
+ + + // ... + export class AppComponent { + steps: Item[]; + + constructor(private readonly appService: AppService) { + this.steps = this.appService.getInitialSteps(); + } + } + +##### Vue + +##### React + +--- + +The "Back" button is initially hidden. This tutorial implements logic that shows the button on the second step of the wizard later (see [Synchronize Steps and Navigation Panel](/Documentation/Guide/UI_Components/Stepper/Implement_a_Wizard/#Synchronize_Steps_and_Navigation_Panel)). + +Configure forms for each step with MultiView **items[]**.[template](/Documentation/ApiReference/UI_Components/dxMultiView/Configuration/items/#template): + +--- + +##### jQuery + + + const multiViewItems = [ + { template: getDatesForm() }, + { template: getGuestsForm() }, + { template: getRoomAndMealForm() }, + { template: getAdditionalRequestsForm() }, + { template: getConfirmationTemplate() }, + ]; + + function getDatesForm() { + return () => $('
').append( + $('

').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.'), + $('

').dxForm({ + formData, + items: [{ + dataField: 'dates', + editorType: 'dxDateRangeBox', + editorOptions: { + elementAttr: { id: 'datesPicker' }, + startDatePlaceholder: 'Check-in', + endDatePlaceholder: 'Check-out', + }, + label: { visible: false }, + }], + }), + ); + } + + function getGuestsForm() { + // Configure the form + } + + function getRoomAndMealForm() { + // Configure the form + } + + function getAdditionalRequestsForm() { + // Configure the form + } + + function getConfirmationTemplate() { + // Configure the form + } + +--- diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/10 Synchronize Steps and Navigation Panel.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/10 Synchronize Steps and Navigation Panel.md new file mode 100644 index 0000000000..2378f3a45f --- /dev/null +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/10 Synchronize Steps and Navigation Panel.md @@ -0,0 +1,161 @@ + +--- + +##### jQuery + +To synchronize the **selectedIndex** properties of Stepper and MultiView, create a new function, `setSelectedIndex`. Call it in the [onSelectionChanged](/Documentation/ApiReference/UI_Components/dxStepper/Configuration/#onSelectionChanged) function of Stepper and the [onClick]() functions of both navigation buttons: + + + const stepper = $('#stepper').dxStepper({ + // ... + onSelectionChanged(e) { + const selectedIndex = e.component.option('selectedIndex'); + + setSelectedIndex(selectedIndex); + }, + }).dxStepper('instance'); + + const prevButton = $('#prevButton').dxButton({ + // ... + onClick: () => { + const selectedIndex = stepper.option('selectedIndex'); + + setSelectedIndex(selectedIndex - 1); + }, + visible: false, + }).dxButton('instance'); + + const nextButton = $('#nextButton').dxButton({ + // ... + onClick: () => { + const selectedIndex = stepper.option('selectedIndex'); + + if (selectedIndex < steps.length - 1) { + setSelectedIndex(selectedIndex + 1); + } + }, + }).dxButton('instance'); + + function setSelectedIndex(index) { + stepper.option('selectedIndex', index); + stepContent.option('selectedIndex', index); + setCurrentStepCaption(index); + updateStepNavigationButtons(index); + + if (index === steps.length - 1) { + stepContent.option('items[4].template', getConfirmationTemplate()); + } + } + +`setSelectedIndex` also updates the navigation panel with `updateStepNavigationButtons` and `setCurrentStepCaption`. + +The `updateStepNavigationButtons` function displays the "Back" button on the second step and changes the "Next" button to "Confirm" on the last step. The "Confirm" button submits the form and disables interaction with the Stepper. For more details, refer to the [Configure a Readonly Stepper](/Documentation/Guide/UI_Components/Stepper/Configure_a_Read-Only_Stepper/) help topic. + +Both navigation buttons are also replaced with a "Reset" button when "Confirm" is clicked: + + + let confirmed = false; + + const nextButton = $('#nextButton').dxButton({ + onClick: () => { + if (selectedIndex < steps.length - 1) { + setSelectedIndex(selectedIndex + 1); + } else if (confirmed) { + reset(); + } else { + confirm(); + } + } + }) + + function confirm() { + confirmed = true; + + setStepperReadonly(true); + setSelectedIndex(steps.length - 1); + } + + function updateStepNavigationButtons(index) { + const isLastStep = index === steps.length - 1; + const lastStepNextButtonText = confirmed ? 'Reset' : 'Confirm'; + const nextButtonText = isLastStep ? lastStepNextButtonText : 'Next'; + + prevButton.option('visible', !!index && !confirmed); + nextButton.option('text', nextButtonText); + } + + function setStepperReadonly(readonly) { + stepper.option('focusStateEnabled', !readonly); + + if (readonly) { + stepper.option('elementAttr', { class: 'readonly' }); + } else { + stepper.resetOption('elementAttr'); + } + } + + + .readonly { + pointer-events: none; + } + +##### Angular + +To synchronize the **selectedIndex** properties of Stepper and MultiView, assign it to a a common variable with two-way data binding syntax: + + + + + +
+ + + + +
+ + + // ... + export class AppComponent { + selectedIndex: number; + + constructor(private readonly appService: AppService) { + // ... + this.selectedIndex = 0; + } + } + +--- + + + +--- + +##### jQuery + + + +--- + +`setCurrentStepCaption` updates the active step indicator as users progress through the Stepper: + +--- + +##### jQuery + + + function setCurrentStepCaption(index) { + if (confirmed) { + $('.current-step').empty(); + } else if (!$('.current-step').text()) { + $('.current-step').append(`Step ${index + 1} of ${steps.length}`); + } else { + $('.selected-index').text(index + 1); + } + } + +--- diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/15 Implement Validation.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/15 Implement Validation.md new file mode 100644 index 0000000000..199c0586f2 --- /dev/null +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/15 Implement Validation.md @@ -0,0 +1,93 @@ +To configure validation, assign [validation groups]() and specify [validation rules]() in each MultiView form: + +--- + +##### jQuery + + + const validationGroups = ['dates', 'guests', 'roomAndMealPlan']; + + function getDatesForm() { + return () => $('
').append( + // ... + $('
').dxForm({ + // ... + validationGroup: validationGroups[0], + items: [{ + // ... + isRequired: true, + }], + }), + ); + } + +--- + +This tutorial validates steps only when users move forward. If a step fails validation, the step change is cancelled: + +--- + +##### jQuery + + + const stepper = $('#stepper').dxStepper({ + // ... + onSelectionChanging(args) { + const { component, addedItems, removedItems } = args; + 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 && validateStep(removedIndex) === false) { + args.cancel = true; + } + }, + }).dxStepper('instance'); + + function validateStep(index) { + const isValid = getValidationResult(index); + + stepper.option(`items[${index}].isValid`, isValid); + + return isValid; + } + + function getValidationResult(index) { + if (index >= validationGroups.length) { + return true; + } + + return DevExpress.validationEngine.validateGroup(validationGroups[index]).isValid; + } + +--- + +Implement validation in the `onClick` function of the "Next" button: + +--- + +##### jQuery + + const nextButton = $('#nextButton').dxButton({ + // ... + onClick: () => { + // ... + if (selectedIndex < steps.length - 1) { + if (validateStep(selectedIndex)) { + setSelectedIndex(selectedIndex + 1); + } + } + // ... + } + }) + +--- + +Review the full code of this tutorial in the following demo: + +#include common-demobutton-named with { + url: "https://js.devexpress.com/Demos/WidgetsGallery/Demo/Stepper/FormIntegration/", + name: "Stepper - Form Integration" +} \ No newline at end of file From 4e5dae9c0112865e9da0db9b89399a32b2b89705 Mon Sep 17 00:00:00 2001 From: Arman Boyakhchyan Date: Tue, 17 Jun 2025 15:04:04 +0400 Subject: [PATCH 02/10] Add remaining content --- .../00 Implement a Wizard.md | 2 +- .../05 Set Up App Layout.md | 224 ++++++++++- ... Synchronize Steps and Navigation Panel.md | 355 ++++++++++++++++-- .../15 Implement Validation.md | 341 ++++++++++++++++- 4 files changed, 880 insertions(+), 42 deletions(-) diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md index 6ca56358d5..9da314e880 100644 --- a/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/00 Implement a Wizard.md @@ -4,7 +4,7 @@ This wizard includes three key parts: - The Stepper component. - Step content where the MultiView component shows content for each step. Every view with input fields contains a Form. -- A navigation panel that displays the current step ("Step 1 of 5") and buttons for moving between steps (Next/Back). +- A navigation panel with an active step indicator ("Step 1 of 5") and buttons for moving between steps ("Next" and "Back"). Image, TBA diff --git a/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md b/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md index ee79267536..f5bfd9c568 100644 --- a/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md +++ b/concepts/05 UI Components/Stepper/30 Implement a Wizard/05 Set Up App Layout.md @@ -1,4 +1,4 @@ -To get started, add Stepper and MultiView to your project. Create a navigation panel by adding a `
` element to display the active step and two [Button](/Documentation/Guide/UI_Components/Button/Overview/) components to a container: +To get started, add Stepper and MultiView to your project. Create a navigation panel by adding a `
` element for the active step indicator and two [Button](/Documentation/Guide/UI_Components/Button/Overview/) components to a container: --- @@ -43,9 +43,7 @@ To get started, add Stepper and MultiView to your project. Create a navigation p ##### Angular - + @@ -59,8 +57,8 @@ To get started, add Stepper and MultiView to your project. Create a navigation p