Implementing a custom wizard component with Steps

PrimeNG has a component called Steps, which indicates the steps in a workflow. The usage is simple:

<p-steps [model]="items" [(activeIndex)]="activeIndex"></p-steps>

The model is a collection of objects of type MenuItem which we met in the Chapter 7, Endless Menu Variations. The property activeIndex points to an index of the active item (step) in the collection of items. The default value is 0 what means that the first item is selected by default. We can also make the items clickable by setting [readonly]="false".

Refer to the PrimeNG showcase to see Steps in action:
https://www.primefaces.org/primeng/#/steps

On basis of <p-steps>, we will implement a wizard like behavior with two custom components named <pe-steps> and <pe-step>. The prefix pe should hint at "PrimeNG extensions". The component <pe-steps> acts as container for multiple steps. The basic structure:

<pe-steps [(activeIndex)]="activeIndex" (change)="onChange($event)">
<pe-step label="First Step">
// content of the first step
</pe-step>
<pe-step label="Second Step">
// content of the second step
</pe-step>
<pe-step label="Third Step">
// content of the third step
</pe-step>
</pe-steps>

We can grasp this structure as wizard. The navigation between wizard's steps happens by clicking on Breadcrumb items (clickable steps), navigation buttons, or setting step's index (activeIndex) programmatically. The next screenshot shows how the wizard and the navigation could look like:

Before starting the implementation, let's specify the API. The <pe-step> component has the following:

Attributes:

Name Type Default Description
styleClass string null Style class of single Step component
label string null Label of this Step shown earlier

 

Styling:

Name Element
pe-step-container Container element of a single Step component

 

The <pe-steps> component has:

Attributes:

Name Type Default Description
activeIndex number 0 Index of the active step (two way binding)
styleClass string null Style class of wizard's container element
stepClass string null Style class of each Step component

 

Events:

Name Parameters Description
change label: Label of currently shown step Callback invoked when switching steps

 

Equipped with this knowledge, we can implement StepComponent and StepsComponent. The first one has ng-content in the template to be able to put custom content. The component class has two specified inputs. Furthermore, there is a property active, which indicates whether the step is currently shown:

@Component({
selector: 'pe-step',
styles: ['.pe-step-container {padding: 45px 25px 45px 25px;
margin-bottom: 20px;}'],
template: `
<div *ngIf="active" [ngClass]="'ui-widget-content ui-corner-all
pe-step-container'" [class]="styleClass">
<ng-content></ng-content>
</div>
`
})
export class StepComponent {
@Input() styleClass: string;
@Input() label: string;
active: boolean = false;
}

The second component is more complicated. It iterates over child components of the type StepComponent and creates items in the life cycle method ngAfterContentInit(). The property active of the child component is set to true if it matches activeIndex. Otherwise, it is set to false. This allows to display exactly one step in the workflow. The complete listing would go beyond the size of this book. We will only show an excerpt:

@Component({
selector: 'pe-steps',
template: `
<p-steps [model]="items" [(activeIndex)]="activeIndex"
[class]="styleClass" [readonly]="false"></p-steps>
<ng-content></ng-content>
<button pButton type="text" *ngIf="activeIndex > 0"
(click)="previous()" icon="fa-hand-o-left" label="Previous">
</button>
<button pButton type="text" *ngIf="activeIndex
< items.length - 1"
(click)="next()" icon="fa-hand-o-right"
iconPos="right" label="Next">
</button>
`
})
export class StepsComponent implements AfterContentInit, OnChanges {
@Input() activeIndex: number = 0;
@Input() styleClass: string;
@Input() stepClass: string;
@Output() activeIndexChange: EventEmitter<any> = new EventEmitter();
@Output() change = new EventEmitter();
items: MenuItem[] = [];
@ContentChildren(StepComponent) steps: QueryList<StepComponent>;

ngAfterContentInit() {
this.steps.toArray().forEach((step: StepComponent,
index: number) =>
{
...
if (index === this.activeIndex) { step.active = true; }

this.items[index] = {
label: step.label,
command: (event: any) => {
// hide all steps
this.steps.toArray().forEach((s: StepComponent) =>
s.active = false);

// show the step the user has clicked on.
step.active = true;
this.activeIndex = index;

// emit currently selected index (two-way binding)
this.activeIndexChange.emit(index);
// emit currently selected label
this.change.next(step.label);
}
};
});
}

ngOnChanges(changes: SimpleChanges) {
if (!this.steps) { return; }

for (let prop in changes) {
if (prop === 'activeIndex') {
let curIndex = changes[prop].currentValue;
this.steps.toArray().forEach((step: StepComponent,
index: number) => {
// show / hide the step
let selected = index === curIndex;
step.active = selected;
if (selected) {
// emit currently selected label
this.change.next(step.label);
}
});
}
}
}

private next() {
this.activeIndex++;
// emit currently selected index (two-way binding)
this.activeIndexChange.emit(this.activeIndex);
// show / hide steps and emit selected label
this.ngOnChanges({
activeIndex: {
currentValue: this.activeIndex,
previousValue: this.activeIndex - 1,
firstChange: false,
isFirstChange: () => false
}
});
}

...
}
The fully implemented and documented components are available on GitHub at
https://github.com/ova2/angular-development-with-primeng/tree/master/chapter9/primeng-extensions-wizard.

To make the implemented wizard distributable, we need to create WizardModule:

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {StepComponent} from './step.component';
import {StepsComponent} from './steps.component';
import {ButtonModule} from 'primeng/components/button/button';
import {StepsModule} from 'primeng/components/steps/steps';

@NgModule({
imports: [CommonModule, ButtonModule, StepsModule],
exports: [StepComponent, StepsComponent],
declarations: [StepComponent, StepsComponent]
})
export class WizardModule { }

The WizardModule class can be imported in any PrimeNG application as usually with imports inside @NgModule. A concrete usage example to the shown picture looks like as follows:

<pe-steps [(activeIndex)]="activeIndex" (change)="onChange($event)">
<pe-step label="First Step">
<label for="firstname">First Name:</label>
<input id="firstname" name="firstname" type="text"
pInputText [(ngModel)]="firstName"/>
<button pButton label="Go" (click)="next()"></button>
</pe-step>
<pe-step label="Second Step">
<label for="lastname">Last Name:</label>
<input id="lastname" name="lastname" type="text"
pInputText [(ngModel)]="lastName"/>
<button pButton label="Go" (click)="next()"></button>
</pe-step>
<pe-step label="Third Step">
<label for="address">Address:</label>
<input id="address" name="address" type="text"
pInputText [(ngModel)]="address"/>
<button pButton label="Ok" (click)="ok()"></button>
</pe-step>
</pe-steps>

<p-growl [value]="msgs"></p-growl>

The corresponding component implements the next() and ok() methods, and the event callback onChange(). To go forwards, you can simple write next() {this.activeIndex++;}. Consult the GitHub project for more details.

The wizard component can be published to the npm repository with npm run update. There is no running demo app and no npm start command in the project on GitHub.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.69.199