Skip to content

Commit 7878e25

Browse files
committed
feat(Stepper): implement custom validation support
1 parent 471327c commit 7878e25

File tree

2 files changed

+207
-12
lines changed

2 files changed

+207
-12
lines changed

docs/content/forms/stepper.md

Lines changed: 192 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ A simple multi-step form built with the Bootstrap Stepper. Each step displays fo
6262
<label for="horizontalStepperUsername" class="form-label">Username</label>
6363
<div class="input-group has-validation">
6464
<span class="input-group-text" id="inputGroupPrepend">@</span>
65-
<input type="text" class="form-control" id="horizontalStepperUsername" aria-describedby="inputGroupPrepend">
65+
<input type="text" class="form-control" id="horizontalStepperUsername" aria-describedby="inputGroupPrepend" required>
6666
<div class="invalid-feedback">
6767
Please choose a username.
6868
</div>
@@ -353,14 +353,197 @@ The Stepper Component includes native HTML5 validation for each step. Before all
353353

354354
If a form is invalid, the stepper blocks navigation and displays validation messages.
355355

356-
To disable native browser validation UI, add the `novalidate` attribute to your `<form>` elements.
356+
### Browser default validation
357357

358-
```html
359-
<form novalidate>
360-
<input required>
361-
<button type="submit">Submit</button>
362-
</form>
363-
```
358+
This example shows a stepper with native browser validation enabled:
359+
360+
{{< example >}}
361+
<div class="stepper" data-coreui-toggle="stepper" id="validationStepper">
362+
<ol class="stepper-steps">
363+
<li class="stepper-step">
364+
<button type="button" class="stepper-step-button active" data-coreui-toggle="step" data-coreui-target="#validation-step-1">
365+
<span class="stepper-step-indicator">1</span>
366+
<span class="stepper-step-label">Account</span>
367+
</button>
368+
</li>
369+
<li class="stepper-step">
370+
<button type="button" class="stepper-step-button" data-coreui-toggle="step" data-coreui-target="#validation-step-2">
371+
<span class="stepper-step-indicator">2</span>
372+
<span class="stepper-step-label">Profile</span>
373+
</button>
374+
</li>
375+
</ol>
376+
<div class="stepper-content">
377+
<div class="stepper-pane active show" id="validation-step-1">
378+
<form class="row g-3 mb-4">
379+
<div class="col-md-6">
380+
<label for="validationEmail" class="form-label">Email</label>
381+
<input type="email" class="form-control" id="validationEmail" required>
382+
<div class="invalid-feedback">
383+
Please provide a valid email.
384+
</div>
385+
</div>
386+
<div class="col-md-6">
387+
<label for="validationPassword" class="form-label">Password</label>
388+
<input type="password" class="form-control" id="validationPassword" required minlength="8">
389+
<div class="invalid-feedback">
390+
Password must be at least 8 characters long.
391+
</div>
392+
</div>
393+
</form>
394+
<button class="btn btn-primary" data-coreui-stepper-action="next">Next</button>
395+
</div>
396+
<div class="stepper-pane" id="validation-step-2">
397+
<form class="row g-3 mb-4">
398+
<div class="col-md-6">
399+
<label for="validationName" class="form-label">First name</label>
400+
<input type="text" class="form-control" id="validationName" required>
401+
<div class="invalid-feedback">
402+
Please provide your first name.
403+
</div>
404+
</div>
405+
<div class="col-md-6">
406+
<label for="validationLastName" class="form-label">Last name</label>
407+
<input type="text" class="form-control" id="validationLastName" required>
408+
<div class="invalid-feedback">
409+
Please provide your last name.
410+
</div>
411+
</div>
412+
</form>
413+
<button class="btn btn-secondary" data-coreui-stepper-action="prev">Previous</button>
414+
<button class="btn btn-success" data-coreui-stepper-action="finish">Finish</button>
415+
</div>
416+
</div>
417+
</div>
418+
{{< /example >}}
419+
420+
### Custom styles
421+
422+
To disable native browser styles validation and turn on custom styles, add the `novalidate` attribute to your forms:
423+
424+
{{< example >}}
425+
<div class="stepper" data-coreui-toggle="stepper" id="novalidateStepper">
426+
<ol class="stepper-steps">
427+
<li class="stepper-step">
428+
<button type="button" class="stepper-step-button active" data-coreui-toggle="step" data-coreui-target="#novalidate-step-1">
429+
<span class="stepper-step-indicator">1</span>
430+
<span class="stepper-step-label">Account</span>
431+
</button>
432+
</li>
433+
<li class="stepper-step">
434+
<button type="button" class="stepper-step-button" data-coreui-toggle="step" data-coreui-target="#novalidate-step-2">
435+
<span class="stepper-step-indicator">2</span>
436+
<span class="stepper-step-label">Profile</span>
437+
</button>
438+
</li>
439+
</ol>
440+
<div class="stepper-content">
441+
<div class="stepper-pane active show" id="novalidate-step-1">
442+
<form class="row g-3 mb-4" novalidate>
443+
<div class="col-md-6">
444+
<label for="customEmail" class="form-label">Email</label>
445+
<input type="email" class="form-control" id="customEmail" required>
446+
<div class="invalid-feedback">
447+
Please provide a valid email.
448+
</div>
449+
</div>
450+
<div class="col-md-6">
451+
<label for="customPassword" class="form-label">Password</label>
452+
<input type="password" class="form-control" id="customPassword" required minlength="8">
453+
<div class="invalid-feedback">
454+
Password must be at least 8 characters long.
455+
</div>
456+
</div>
457+
</form>
458+
<button class="btn btn-primary" data-coreui-stepper-action="next">Next</button>
459+
</div>
460+
<div class="stepper-pane" id="novalidate-step-2">
461+
<form class="row g-3 mb-4" novalidate>
462+
<div class="col-md-6">
463+
<label for="customName" class="form-label">First name</label>
464+
<input type="text" class="form-control" id="customName" required>
465+
<div class="invalid-feedback">
466+
Please provide your first name.
467+
</div>
468+
</div>
469+
<div class="col-md-6">
470+
<label for="customLastName" class="form-label">Last name</label>
471+
<input type="text" class="form-control" id="customLastName" required>
472+
<div class="invalid-feedback">
473+
Please provide your last name.
474+
</div>
475+
</div>
476+
</form>
477+
<button class="btn btn-secondary" data-coreui-stepper-action="prev">Previous</button>
478+
<button class="btn btn-success" data-coreui-stepper-action="finish">Finish</button>
479+
</div>
480+
</div>
481+
</div>
482+
{{< /example >}}
483+
484+
### Skip validation
485+
486+
To completely skip form validation and allow free navigation between steps, add `data-coreui-skip-validation="true"` to the stepper:
487+
488+
{{< example >}}
489+
<div class="stepper" data-coreui-toggle="stepper" data-coreui-skip-validation="true" id="skipValidationStepper">
490+
<ol class="stepper-steps">
491+
<li class="stepper-step">
492+
<button type="button" class="stepper-step-button active" data-coreui-toggle="step" data-coreui-target="#skip-step-1">
493+
<span class="stepper-step-indicator">1</span>
494+
<span class="stepper-step-label">Account</span>
495+
</button>
496+
</li>
497+
<li class="stepper-step">
498+
<button type="button" class="stepper-step-button" data-coreui-toggle="step" data-coreui-target="#skip-step-2">
499+
<span class="stepper-step-indicator">2</span>
500+
<span class="stepper-step-label">Profile</span>
501+
</button>
502+
</li>
503+
</ol>
504+
<div class="stepper-content">
505+
<div class="stepper-pane active show" id="skip-step-1">
506+
<form class="row g-3 mb-4">
507+
<div class="col-md-6">
508+
<label for="skipEmail" class="form-label">Email</label>
509+
<input type="email" class="form-control" id="skipEmail" required>
510+
<div class="invalid-feedback">
511+
Please provide a valid email.
512+
</div>
513+
</div>
514+
<div class="col-md-6">
515+
<label for="skipPassword" class="form-label">Password</label>
516+
<input type="password" class="form-control" id="skipPassword" required minlength="8">
517+
<div class="invalid-feedback">
518+
Password must be at least 8 characters long.
519+
</div>
520+
</div>
521+
</form>
522+
<button class="btn btn-primary" data-coreui-stepper-action="next">Next</button>
523+
</div>
524+
<div class="stepper-pane" id="skip-step-2">
525+
<form class="row g-3 mb-4">
526+
<div class="col-md-6">
527+
<label for="skipName" class="form-label">First name</label>
528+
<input type="text" class="form-control" id="skipName" required>
529+
<div class="invalid-feedback">
530+
Please provide your first name.
531+
</div>
532+
</div>
533+
<div class="col-md-6">
534+
<label for="skipLastName" class="form-label">Last name</label>
535+
<input type="text" class="form-control" id="skipLastName" required>
536+
<div class="invalid-feedback">
537+
Please provide your last name.
538+
</div>
539+
</div>
540+
</form>
541+
<button class="btn btn-secondary" data-coreui-stepper-action="prev">Previous</button>
542+
<button class="btn btn-success" data-coreui-stepper-action="finish">Finish</button>
543+
</div>
544+
</div>
545+
</div>
546+
{{< /example >}}
364547

365548
Validation is fully automatic, no extra JavaScript is needed.
366549

@@ -422,6 +605,7 @@ const stepperList = stepperElementList.map(stepperEl => {
422605
| Option | Type | Default | Description |
423606
| --- | --- | --- | --- |
424607
| `linear` | boolean | `true` | Forces steps to be completed in order (sequential navigation). Set `false` for free navigation. |
608+
| `skipValidation` | boolean | `false` | When set to `true`, disables form validation completely, allowing users to navigate between steps regardless of form state. |
425609
{{< /bs-table >}}
426610

427611
### Methods

js/src/stepper.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ const HOME_KEY = 'Home'
5555
const END_KEY = 'End'
5656

5757
const Default = {
58-
linear: true
58+
linear: true,
59+
skipValidation: false
5960
}
6061

6162
const DefaultType = {
62-
linear: 'boolean'
63+
linear: 'boolean',
64+
skipValidation: 'boolean'
6365
}
6466

6567
/**
@@ -288,14 +290,18 @@ class Stepper extends BaseComponent {
288290
}
289291

290292
_isCurrentStepValid(element) {
293+
if (this._config.skipValidation) {
294+
return true
295+
}
296+
291297
const pane = this._getTargetPane(element)
292298
const target = pane ?? element.parentNode.querySelector(SELECTOR_STEPPER_STEP_CONTENT)
293299

294300
if (!target) {
295301
return true
296302
}
297303

298-
const form = target.querySelector('form:not([novalidate])')
304+
const form = target.querySelector('form')
299305
if (!form) {
300306
return true
301307
}
@@ -308,7 +314,12 @@ class Stepper extends BaseComponent {
308314
})
309315

310316
if (!isValid) {
311-
form.reportValidity()
317+
if (form.noValidate) {
318+
form.classList.add('was-validated')
319+
} else {
320+
form.reportValidity()
321+
}
322+
312323
return false
313324
}
314325

0 commit comments

Comments
 (0)