angular-archwizard
Version:
An Angular 9+ module containing a wizard component and its supporting components and directives
1,305 lines (1,288 loc) • 68.3 kB
JavaScript
import { Directive, TemplateRef, EventEmitter, ContentChild, Input, Output, HostBinding, Component, forwardRef, ContentChildren, Host, Optional, HostListener, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
/**
* The `awWizardStepSymbol` directive can be used as an alternative to the `navigationSymbol` input of a [[WizardStep]]
* to define the step symbol inside the navigation bar. This way step symbol may contain arbitrary content.
*
* ### Syntax
*
* ```html
* <ng-template awWizardStepSymbol>
* ...
* </ng-template>
* ```
*/
class WizardStepSymbolDirective {
/**
* Constructor
*
* @param templateRef A reference to the content of the `ng-template` that contains this [[WizardStepSymbolDirective]]
*/
constructor(templateRef) {
this.templateRef = templateRef;
}
}
WizardStepSymbolDirective.decorators = [
{ type: Directive, args: [{
selector: 'ng-template[awStepSymbol], ng-template[awWizardStepSymbol]'
},] }
];
WizardStepSymbolDirective.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* The `awWizardStepTitle` directive can be used as an alternative to the `stepTitle` input of a [[WizardStep]]
* to define the content of a step title inside the navigation bar.
* This step title can be freely created and can contain more than only plain text
*
* ### Syntax
*
* ```html
* <ng-template awWizardStepTitle>
* ...
* </ng-template>
* ```
*
* @author Marc Arndt
*/
class WizardStepTitleDirective {
/**
* Constructor
*
* @param templateRef A reference to the content of the `ng-template` that contains this [[WizardStepTitleDirective]]
*/
constructor(templateRef) {
this.templateRef = templateRef;
}
}
WizardStepTitleDirective.decorators = [
{ type: Directive, args: [{
selector: 'ng-template[awStepTitle], ng-template[awWizardStepTitle]'
},] }
];
WizardStepTitleDirective.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* Basic functionality every type of wizard step needs to provide
*
* @author Marc Arndt
*/
/* tslint:disable-next-line directive-class-suffix */
class WizardStep {
constructor() {
/**
* A symbol property, which contains an optional symbol for the step inside the navigation bar.
* Takes effect when `stepSymbolTemplate` is not defined or null.
*/
this.navigationSymbol = { symbol: '' };
/**
* A boolean describing if the wizard step is currently selected
*/
this.selected = false;
/**
* A boolean describing if the wizard step has been completed
*/
this.completed = false;
/**
* A boolean describing if the wizard step is shown as completed when the wizard is presented to the user
*
* Users will typically use `CompletedStepDirective` to set this flag
*/
this.initiallyCompleted = false;
/**
* A boolean describing if the wizard step is being edited after being competed
*
* This flag can only be true when `selected` is true.
*/
this.editing = false;
/**
* A boolean describing, if the wizard step should be selected by default, i.e. after the wizard has been initialized as the initial step
*/
this.defaultSelected = false;
/**
* A boolean describing if the wizard step is an optional step
*/
this.optional = false;
/**
* A function or boolean deciding, if this step can be entered
*/
this.canEnter = true;
/**
* A function or boolean deciding, if this step can be exited
*/
this.canExit = true;
/**
* This [[EventEmitter]] is called when the step is entered.
* The bound method should be used to do initialization work.
*/
this.stepEnter = new EventEmitter();
/**
* This [[EventEmitter]] is called when the step is exited.
* The bound method can be used to do cleanup work.
*/
this.stepExit = new EventEmitter();
}
/**
* Returns true if this wizard step should be visible to the user.
* If the step should be visible to the user false is returned, otherwise true
*/
get hidden() {
return !this.selected;
}
/**
* This method returns true, if this wizard step can be transitioned with a given direction.
* Transitioned in this case means either entered or exited, depending on the given `condition` parameter.
*
* @param condition A condition variable, deciding if the step can be transitioned
* @param direction The direction in which this step should be transitioned
* @returns A [[Promise]] containing `true`, if this step can transitioned in the given direction
* @throws An `Error` is thrown if `condition` is neither a function nor a boolean
*/
static canTransitionStep(condition, direction) {
if (typeof (condition) === typeof (true)) {
return Promise.resolve(condition);
}
else if (condition instanceof Function) {
return Promise.resolve(condition(direction));
}
else {
return Promise.reject(new Error(`Input value '${condition}' is neither a boolean nor a function`));
}
}
/**
* A function called when the step is entered
*
* @param direction The direction in which the step is entered
*/
enter(direction) {
this.stepEnter.emit(direction);
}
/**
* A function called when the step is exited
*
* @param direction The direction in which the step is exited
*/
exit(direction) {
this.stepExit.emit(direction);
}
/**
* This method returns true, if this wizard step can be entered from the given direction.
* Because this method depends on the value `canEnter`, it will throw an error, if `canEnter` is neither a boolean
* nor a function.
*
* @param direction The direction in which this step should be entered
* @returns A [[Promise]] containing `true`, if the step can be entered in the given direction, false otherwise
* @throws An `Error` is thrown if `anEnter` is neither a function nor a boolean
*/
canEnterStep(direction) {
return WizardStep.canTransitionStep(this.canEnter, direction);
}
/**
* This method returns true, if this wizard step can be exited into given direction.
* Because this method depends on the value `canExit`, it will throw an error, if `canExit` is neither a boolean
* nor a function.
*
* @param direction The direction in which this step should be left
* @returns A [[Promise]] containing `true`, if the step can be exited in the given direction, false otherwise
* @throws An `Error` is thrown if `canExit` is neither a function nor a boolean
*/
canExitStep(direction) {
return WizardStep.canTransitionStep(this.canExit, direction);
}
}
WizardStep.decorators = [
{ type: Directive }
];
WizardStep.propDecorators = {
stepTitleTemplate: [{ type: ContentChild, args: [WizardStepTitleDirective,] }],
stepSymbolTemplate: [{ type: ContentChild, args: [WizardStepSymbolDirective,] }],
stepId: [{ type: Input }],
stepTitle: [{ type: Input }],
navigationSymbol: [{ type: Input }],
canEnter: [{ type: Input }],
canExit: [{ type: Input }],
stepEnter: [{ type: Output }],
stepExit: [{ type: Output }],
hidden: [{ type: HostBinding, args: ['hidden',] }]
};
/**
* Basic functionality every wizard completion step needs to provide
*
* @author Marc Arndt
*/
/* tslint:disable-next-line directive-class-suffix */
class WizardCompletionStep extends WizardStep {
constructor() {
super(...arguments);
/**
* @inheritDoc
*/
this.stepExit = new EventEmitter();
/**
* @inheritDoc
*/
this.canExit = false;
}
/**
* @inheritDoc
*/
enter(direction) {
this.completed = true;
this.stepEnter.emit(direction);
}
/**
* @inheritDoc
*/
exit(direction) {
// set this completion step as incomplete (unless it happens to be initiallyCompleted)
this.completed = this.initiallyCompleted;
this.stepExit.emit(direction);
}
}
WizardCompletionStep.decorators = [
{ type: Directive }
];
/**
* The `aw-wizard-completion-step` component can be used to define a completion/success step at the end of your wizard
* After a `aw-wizard-completion-step` has been entered, it has the characteristic that the user is blocked from
* leaving it again to a previous step.
* In addition entering a `aw-wizard-completion-step` automatically sets the `aw-wizard` and all steps inside the `aw-wizard`
* as completed.
*
* ### Syntax
*
* ```html
* <aw-wizard-completion-step [stepTitle]="title of the wizard step"
* [navigationSymbol]="{ symbol: 'navigation symbol', fontFamily: 'navigation symbol font family' }"
* (stepEnter)="event emitter to be called when the wizard step is entered"
* (stepExit)="event emitter to be called when the wizard step is exited">
* ...
* </aw-wizard-completion-step>
* ```
*
* ### Example
*
* ```html
* <aw-wizard-completion-step stepTitle="Step 1" [navigationSymbol]="{ symbol: '1' }">
* ...
* </aw-wizard-completion-step>
* ```
*
* With a navigation symbol from the `font-awesome` font:
*
* ```html
* <aw-wizard-completion-step stepTitle="Step 1" [navigationSymbol]="{ symbol: '', fontFamily: 'FontAwesome' }">
* ...
* </aw-wizard-completion-step>
* ```
*
* @author Marc Arndt
*/
class WizardCompletionStepComponent extends WizardCompletionStep {
}
WizardCompletionStepComponent.decorators = [
{ type: Component, args: [{
selector: 'aw-wizard-completion-step',
template: "<ng-content></ng-content>\n",
providers: [
{ provide: WizardStep, useExisting: forwardRef(() => WizardCompletionStepComponent) },
{ provide: WizardCompletionStep, useExisting: forwardRef(() => WizardCompletionStepComponent) }
]
},] }
];
/**
* The direction in which a step transition was made
*
* @author Marc Arndt
*/
/**
* This enum contains the different possible moving directions in which a wizard can be traversed
*
* @author Marc Arndt
*/
var MovingDirection;
(function (MovingDirection) {
/**
* A forward step transition
*/
MovingDirection[MovingDirection["Forwards"] = 0] = "Forwards";
/**
* A backward step transition
*/
MovingDirection[MovingDirection["Backwards"] = 1] = "Backwards";
/**
* No step transition was done
*/
MovingDirection[MovingDirection["Stay"] = 2] = "Stay";
})(MovingDirection || (MovingDirection = {}));
/**
* Base implementation of [[NavigationMode]]
*
* Note: Built-in [[NavigationMode]] classes should be stateless, allowing the library user to easily create
* an instance of a particular [[NavigationMode]] class and pass it to `<aw-wizard [navigationMode]="...">`.
*
* @author Marc Arndt
*/
class BaseNavigationMode {
/**
* Checks, whether a wizard step, as defined by the given destination index, can be transitioned to.
*
* This method controls navigation by [[goToStep]], [[goToPreviousStep]], and [[goToNextStep]] directives.
* Navigation by navigation bar is governed by [[isNavigable]].
*
* In this implementation, a destination wizard step can be entered if:
* - it exists
* - the current step can be exited in the direction of the destination step
* - the destination step can be entered in the direction from the current step
*
* Subclasses can impose additional restrictions, see [[canTransitionToStep]].
*
* @param wizard The wizard component to operate on
* @param destinationIndex The index of the destination step
* @returns A [[Promise]] containing `true`, if the destination step can be transitioned to and `false` otherwise
*/
canGoToStep(wizard, destinationIndex) {
const hasStep = wizard.hasStep(destinationIndex);
const movingDirection = wizard.getMovingDirection(destinationIndex);
const canExitCurrentStep = (previous) => {
return previous && wizard.currentStep.canExitStep(movingDirection);
};
const canEnterDestinationStep = (previous) => {
return previous && wizard.getStepAtIndex(destinationIndex).canEnterStep(movingDirection);
};
const canTransitionToStep = (previous) => {
return previous && this.canTransitionToStep(wizard, destinationIndex);
};
return Promise.resolve(hasStep)
.then(canTransitionToStep)
// Apply user-defined checks at the end. They can involve user interaction
// which is better to be avoided if navigation mode does not actually allow the transition
// (`canTransitionToStep` returns `false`).
.then(canExitCurrentStep)
.then(canEnterDestinationStep);
}
/**
* Imposes additional restrictions for `canGoToStep` in current navigation mode.
*
* The base implementation allows transition iff the given step is navigable from the navigation bar (see `isNavigable`).
* However, in some navigation modes `canTransitionToStep` can be more relaxed to allow navigation to certain steps
* by previous/next buttons, but not using the navigation bar.
*
* @param wizard The wizard component to operate on
* @param destinationIndex The index of the destination step
* @returns `true`, if the destination step can be transitioned to and `false` otherwise
*/
canTransitionToStep(wizard, destinationIndex) {
return this.isNavigable(wizard, destinationIndex);
}
/**
* Tries to transition to the wizard step, as denoted by the given destination index.
*
* When entering the destination step, the following actions are done:
* - the old current step is set as completed
* - the old current step is set as unselected
* - the old current step is exited
* - the destination step is set as selected
* - the destination step is entered
*
* When the destination step couldn't be entered, the following actions are done:
* - the current step is exited and entered in the direction `MovingDirection.Stay`
*
* @param wizard The wizard component to operate on
* @param destinationIndex The index of the destination wizard step, which should be entered
* @param preFinalize An event emitter, to be called before the step has been transitioned
* @param postFinalize An event emitter, to be called after the step has been transitioned
*/
goToStep(wizard, destinationIndex, preFinalize, postFinalize) {
this.canGoToStep(wizard, destinationIndex).then(navigationAllowed => {
if (navigationAllowed) {
// the current step can be exited in the given direction
const movingDirection = wizard.getMovingDirection(destinationIndex);
/* istanbul ignore if */
if (preFinalize) {
preFinalize.emit();
}
// leave current step
wizard.currentStep.completed = true;
wizard.currentStep.exit(movingDirection);
wizard.currentStep.editing = false;
wizard.currentStep.selected = false;
this.transition(wizard, destinationIndex);
// remember if the next step is already completed before entering it to properly set `editing` flag
const wasCompleted = wizard.completed || wizard.currentStep.completed;
// go to next step
wizard.currentStep.enter(movingDirection);
wizard.currentStep.selected = true;
if (wasCompleted) {
wizard.currentStep.editing = true;
}
/* istanbul ignore if */
if (postFinalize) {
postFinalize.emit();
}
}
else {
// if the current step can't be left, reenter the current step
wizard.currentStep.exit(MovingDirection.Stay);
wizard.currentStep.enter(MovingDirection.Stay);
}
});
}
/**
* Transitions the wizard to the given step index.
*
* Can perform additional actions in particular navigation mode implementations.
*
* @param wizard The wizard component to operate on
* @param destinationIndex The index of the destination wizard step
*/
transition(wizard, destinationIndex) {
wizard.currentStepIndex = destinationIndex;
}
/**
* Resets the state of this wizard.
*
* A reset transitions the wizard automatically to the first step and sets all steps as incomplete.
* In addition the whole wizard is set as incomplete.
*
* @param wizard The wizard component to operate on
*/
reset(wizard) {
this.ensureCanReset(wizard);
// reset the step internal state
wizard.wizardSteps.forEach(step => {
step.completed = step.initiallyCompleted;
step.selected = false;
step.editing = false;
});
// set the first step as the current step
wizard.currentStepIndex = wizard.defaultStepIndex;
wizard.currentStep.selected = true;
wizard.currentStep.enter(MovingDirection.Forwards);
}
/**
* Checks if wizard configuration allows to perform reset.
*
* A check failure is indicated by throwing an `Error` with the message discribing the discovered misconfiguration issue.
*
* Can include additional checks in particular navigation mode implementations.
*
* @param wizard The wizard component to operate on
* @throws An `Error` is thrown, if a micconfiguration issue is discovered.
*/
ensureCanReset(wizard) {
// the wizard doesn't contain a step with the default step index
if (!wizard.hasStep(wizard.defaultStepIndex)) {
throw new Error(`The wizard doesn't contain a step with index ${wizard.defaultStepIndex}`);
}
}
}
/**
* The default navigation mode used by [[WizardComponent]] and [[NavigationModeDirective]].
*
* It is parameterized with two navigation policies passed to constructor:
*
* - [[navigateBackward]] policy controls whether wizard steps before the current step are navigable:
*
* - `"deny"` -- the steps are not navigable
* - `"allow"` -- the steps are navigable
* - If the corresponding constructor argument is omitted or is `null` or `undefined`,
* then the default value is applied which is `"deny"`
*
* - [[navigateForward]] policy controls whether wizard steps after the current step are navigable:
*
* - `"deny"` -- the steps are not navigable
* - `"allow"` -- the steps are navigable
* - `"visited"` -- a step is navigable iff it was already visited before
* - If the corresponding constructor argument is omitted or is `null` or `undefined`,
* then the default value is applied which is `"allow"`
*/
class ConfigurableNavigationMode extends BaseNavigationMode {
/**
* Constructor
*
* @param navigateBackward Controls whether wizard steps before the current step are navigable
* @param navigateForward Controls whether wizard steps before the current step are navigable
*/
constructor(navigateBackward = null, navigateForward = null) {
super();
this.navigateBackward = navigateBackward;
this.navigateForward = navigateForward;
this.navigateBackward = this.navigateBackward || 'allow';
this.navigateForward = this.navigateForward || 'deny';
}
/**
* @inheritDoc
*/
canTransitionToStep(wizard, destinationIndex) {
// if the destination step can be navigated to using the navigation bar,
// it should be accessible with [goToStep] as well
if (this.isNavigable(wizard, destinationIndex)) {
return true;
}
// navigation with [goToStep] is permitted if all previous steps
// to the destination step have been completed or are optional
return wizard.wizardSteps
.filter((step, index) => index < destinationIndex && index !== wizard.currentStepIndex)
.every(step => step.completed || step.optional);
}
/**
* @inheritDoc
*/
transition(wizard, destinationIndex) {
if (this.navigateForward === 'deny') {
// set all steps after the destination step to incomplete
wizard.wizardSteps
.filter((step, index) => wizard.currentStepIndex > destinationIndex && index > destinationIndex)
.forEach(step => step.completed = false);
}
super.transition(wizard, destinationIndex);
}
/**
* @inheritDoc
*/
isNavigable(wizard, destinationIndex) {
// Check if the destination step can be navigated to
const destinationStep = wizard.getStepAtIndex(destinationIndex);
if (destinationStep instanceof WizardCompletionStep) {
// A completion step can only be entered, if all previous steps have been completed, are optional, or selected
const previousStepsCompleted = wizard.wizardSteps
.filter((step, index) => index < destinationIndex)
.every(step => step.completed || step.optional || step.selected);
if (!previousStepsCompleted) {
return false;
}
}
// Apply navigation pocicies
if (destinationIndex < wizard.currentStepIndex) {
// If the destination step is before current, apply the `navigateBackward` policy
switch (this.navigateBackward) {
case 'allow': return true;
case 'deny': return false;
default:
throw new Error(`Invalid value for navigateBackward: ${this.navigateBackward}`);
}
}
else if (destinationIndex > wizard.currentStepIndex) {
// If the destination step is after current, apply the `navigateForward` policy
switch (this.navigateForward) {
case 'allow': return true;
case 'deny': return false;
case 'visited': return destinationStep.completed;
default:
throw new Error(`Invalid value for navigateForward: ${this.navigateForward}`);
}
}
else {
// Re-entering the current step is not allowed
return false;
}
}
/**
* @inheritDoc
*/
ensureCanReset(wizard) {
super.ensureCanReset(wizard);
// the default step is a completion step and the wizard contains more than one step
const defaultWizardStep = wizard.getStepAtIndex(wizard.defaultStepIndex);
const defaultCompletionStep = defaultWizardStep instanceof WizardCompletionStep;
if (defaultCompletionStep && wizard.wizardSteps.length !== 1) {
throw new Error(`The default step index ${wizard.defaultStepIndex} references a completion step`);
}
}
}
/**
* The `aw-wizard` component defines the root component of a wizard.
* Through the setting of input parameters for the `aw-wizard` component it's possible to change the location and size
* of its navigation bar.
*
* ### Syntax
* ```html
* <aw-wizard [navBarLocation]="location of navigation bar" [navBarLayout]="layout of navigation bar">
* ...
* </aw-wizard>
* ```
*
* ### Example
*
* Without completion step:
*
* ```html
* <aw-wizard navBarLocation="top" navBarLayout="small">
* <aw-wizard-step>...</aw-wizard-step>
* <aw-wizard-step>...</aw-wizard-step>
* </aw-wizard>
* ```
*
* With completion step:
*
* ```html
* <aw-wizard navBarLocation="top" navBarLayout="small">
* <aw-wizard-step>...</aw-wizard-step>
* <aw-wizard-step>...</aw-wizard-step>
* <aw-wizard-completion-step>...</aw-wizard-completion-step>
* </aw-wizard>
* ```
*
* @author Marc Arndt
*/
class WizardComponent {
/**
* Constructor
*/
constructor() {
/**
* The location of the navigation bar inside the wizard.
* This location can be either top, bottom, left or right
*/
this.navBarLocation = 'top';
/**
* The layout of the navigation bar inside the wizard.
* The layout can be either small, large-filled, large-empty or large-symbols
*/
this.navBarLayout = 'small';
/**
* The direction in which the steps inside the navigation bar should be shown.
* The direction can be either `left-to-right` or `right-to-left`
*/
this.navBarDirection = 'left-to-right';
this._defaultStepIndex = 0;
/**
* True, if the navigation bar shouldn't be used for navigating
*/
this.disableNavigationBar = false;
/**
* The navigation mode used to navigate inside the wizard
*
* For outside access, use the [[navigation]] getter.
*/
this._navigation = new ConfigurableNavigationMode();
/**
* An array representation of all wizard steps belonging to this model
*
* For outside access, use the [[wizardSteps]] getter.
*/
this._wizardSteps = [];
/**
* The index of the currently visible and selected step inside the wizardSteps QueryList.
* If this wizard contains no steps, currentStepIndex is -1
*
* Note: Do not modify this field directly. Instead, use navigation methods:
* [[goToStep]], [[goToPreviousStep]], [[goToNextStep]].
*/
this.currentStepIndex = -1;
}
/**
* The initially selected step, represented by its index
* Beware: This initial default is only used if no wizard step has been enhanced with the `selected` directive
*/
get defaultStepIndex() {
// This value can be either:
// - the index of a wizard step with a `selected` directive, or
// - the default step index, set in the [[WizardComponent]]
const foundDefaultStep = this.wizardSteps.find(step => step.defaultSelected);
if (foundDefaultStep) {
return this.getIndexOfStep(foundDefaultStep);
}
else {
return this._defaultStepIndex;
}
}
set defaultStepIndex(defaultStepIndex) {
this._defaultStepIndex = defaultStepIndex;
}
/**
* Returns true if this wizard uses a horizontal orientation.
* The wizard uses a horizontal orientation, iff the navigation bar is shown at the top or bottom of this wizard
*
* @returns True if this wizard uses a horizontal orientation
*/
get horizontalOrientation() {
return this.navBarLocation === 'top' || this.navBarLocation === 'bottom';
}
/**
* Returns true if this wizard uses a vertical orientation.
* The wizard uses a vertical orientation, iff the navigation bar is shown at the left or right of this wizard
*
* @returns True if this wizard uses a vertical orientation
*/
get verticalOrientation() {
return this.navBarLocation === 'left' || this.navBarLocation === 'right';
}
/**
* Initialization work
*/
ngAfterContentInit() {
// add a subscriber to the wizard steps QueryList to listen to changes in the DOM
this.wizardStepsQueryList.changes.subscribe(changedWizardSteps => {
this.updateWizardSteps(changedWizardSteps.toArray());
});
// initialize the model
this.updateWizardSteps(this.wizardStepsQueryList.toArray());
// finally reset the whole wizard component
setTimeout(() => this.reset());
}
/**
* The WizardStep object belonging to the currently visible and selected step.
* The currentStep is always the currently selected wizard step.
* The currentStep can be either completed, if it was visited earlier,
* or not completed, if it is visited for the first time or its state is currently out of date.
*
* If this wizard contains no steps, currentStep is null
*/
get currentStep() {
if (this.hasStep(this.currentStepIndex)) {
return this.wizardSteps[this.currentStepIndex];
}
else {
return null;
}
}
/**
* The completeness of the wizard.
* If the wizard has been completed, i.e. all steps are either completed or optional, this value is true, otherwise it is false
*/
get completed() {
return this.wizardSteps.every(step => step.completed || step.optional);
}
/**
* An array representation of all wizard steps belonging to this model
*/
get wizardSteps() {
return this._wizardSteps;
}
/**
* Updates the wizard steps to the new array
*
* @param wizardSteps The updated wizard steps
*/
updateWizardSteps(wizardSteps) {
// the wizard is currently not in the initialization phase
if (this.wizardSteps.length > 0 && this.currentStepIndex > -1) {
this.currentStepIndex = wizardSteps.indexOf(this.wizardSteps[this.currentStepIndex]);
}
this._wizardSteps = wizardSteps;
}
/**
* The navigation mode used to navigate inside the wizard
*/
get navigation() {
return this._navigation;
}
/**
* Updates the navigation mode for this wizard component
*
* @param navigation The updated navigation mode
*/
set navigation(navigation) {
this._navigation = navigation;
}
/**
* Checks if a given index `stepIndex` is inside the range of possible wizard steps inside this wizard
*
* @param stepIndex The to be checked index of a step inside this wizard
* @returns True if the given `stepIndex` is contained inside this wizard, false otherwise
*/
hasStep(stepIndex) {
return this.wizardSteps.length > 0 && 0 <= stepIndex && stepIndex < this.wizardSteps.length;
}
/**
* Checks if this wizard has a previous step, compared to the current step
*
* @returns True if this wizard has a previous step before the current step
*/
hasPreviousStep() {
return this.hasStep(this.currentStepIndex - 1);
}
/**
* Checks if this wizard has a next step, compared to the current step
*
* @returns True if this wizard has a next step after the current step
*/
hasNextStep() {
return this.hasStep(this.currentStepIndex + 1);
}
/**
* Checks if this wizard is currently inside its last step
*
* @returns True if the wizard is currently inside its last step
*/
isLastStep() {
return this.wizardSteps.length > 0 && this.currentStepIndex === this.wizardSteps.length - 1;
}
/**
* Finds the [[WizardStep]] at the given index `stepIndex`.
* If no [[WizardStep]] exists at the given index an Error is thrown
*
* @param stepIndex The given index
* @returns The found [[WizardStep]] at the given index `stepIndex`
* @throws An `Error` is thrown, if the given index `stepIndex` doesn't exist
*/
getStepAtIndex(stepIndex) {
if (!this.hasStep(stepIndex)) {
throw new Error(`Expected a known step, but got stepIndex: ${stepIndex}.`);
}
return this.wizardSteps[stepIndex];
}
/**
* Finds the index of the step with the given `stepId`.
* If no step with the given `stepId` exists, `-1` is returned
*
* @param stepId The given step id
* @returns The found index of a step with the given step id, or `-1` if no step with the given id is included in the wizard
*/
getIndexOfStepWithId(stepId) {
return this.wizardSteps.findIndex(step => step.stepId === stepId);
}
/**
* Finds the index of the given [[WizardStep]] `step`.
* If the given [[WizardStep]] is not contained inside this wizard, `-1` is returned
*
* @param step The given [[WizardStep]]
* @returns The found index of `step` or `-1` if the step is not included in the wizard
*/
getIndexOfStep(step) {
return this.wizardSteps.indexOf(step);
}
/**
* Calculates the correct [[MovingDirection]] value for a given `destinationStep` compared to the `currentStepIndex`.
*
* @param destinationStep The given destination step
* @returns The calculated [[MovingDirection]]
*/
getMovingDirection(destinationStep) {
let movingDirection;
if (destinationStep > this.currentStepIndex) {
movingDirection = MovingDirection.Forwards;
}
else if (destinationStep < this.currentStepIndex) {
movingDirection = MovingDirection.Backwards;
}
else {
movingDirection = MovingDirection.Stay;
}
return movingDirection;
}
/**
* Checks, whether a wizard step, as defined by the given destination index, can be transitioned to.
*
* This method controls navigation by [[goToStep]], [[goToPreviousStep]], and [[goToNextStep]] directives.
* Navigation by navigation bar is governed by [[isNavigable]].
*
* @param destinationIndex The index of the destination step
* @returns A [[Promise]] containing `true`, if the destination step can be transitioned to and false otherwise
*/
canGoToStep(destinationIndex) {
return this.navigation.canGoToStep(this, destinationIndex);
}
/**
* Tries to transition to the wizard step, as denoted by the given destination index.
*
* Note: You do not have to call [[canGoToStep]] before calling [[goToStep]].
* The [[canGoToStep]] method will be called automatically.
*
* @param destinationIndex The index of the destination wizard step, which should be entered
* @param preFinalize An event emitter, to be called before the step has been transitioned
* @param postFinalize An event emitter, to be called after the step has been transitioned
*/
goToStep(destinationIndex, preFinalize, postFinalize) {
return this.navigation.goToStep(this, destinationIndex, preFinalize, postFinalize);
}
/**
* Tries to transition the wizard to the previous step
*
* @param preFinalize An event emitter, to be called before the step has been transitioned
* @param postFinalize An event emitter, to be called after the step has been transitioned
*/
goToPreviousStep(preFinalize, postFinalize) {
return this.navigation.goToStep(this, this.currentStepIndex - 1, preFinalize, postFinalize);
}
/**
* Tries to transition the wizard to the next step
*
* @param preFinalize An event emitter, to be called before the step has been transitioned
* @param postFinalize An event emitter, to be called after the step has been transitioned
*/
goToNextStep(preFinalize, postFinalize) {
return this.navigation.goToStep(this, this.currentStepIndex + 1, preFinalize, postFinalize);
}
/**
* Checks, whether the wizard step, located at the given index, can be navigated to using the navigation bar.
*
* @param destinationIndex The index of the destination step
* @returns True if the step can be navigated to, false otherwise
*/
isNavigable(destinationIndex) {
return this.navigation.isNavigable(this, destinationIndex);
}
/**
* Resets the state of this wizard.
*/
reset() {
this.navigation.reset(this);
}
}
WizardComponent.decorators = [
{ type: Component, args: [{
selector: 'aw-wizard',
template: "<aw-wizard-navigation-bar\n *ngIf=\"navBarLocation == 'top' || navBarLocation == 'left'\"\n [ngClass]=\"{\n 'vertical': navBarLocation == 'left',\n 'horizontal': navBarLocation == 'top',\n 'small': navBarLayout == 'small',\n 'large-filled': navBarLayout == 'large-filled',\n 'large-filled-symbols': navBarLayout == 'large-filled-symbols',\n 'large-empty': navBarLayout == 'large-empty',\n 'large-empty-symbols': navBarLayout == 'large-empty-symbols'\n }\">\n</aw-wizard-navigation-bar>\n\n<div [ngClass]=\"{\n 'wizard-steps': true,\n 'vertical': navBarLocation == 'left' || navBarLocation == 'right',\n 'horizontal': navBarLocation == 'top' || navBarLocation == 'bottom'\n}\">\n <ng-content></ng-content>\n</div>\n\n<aw-wizard-navigation-bar\n *ngIf=\"navBarLocation == 'bottom' || navBarLocation == 'right'\"\n [ngClass]=\"{\n 'vertical': navBarLocation == 'right',\n 'horizontal': navBarLocation == 'bottom',\n 'small': navBarLayout == 'small',\n 'large-filled': navBarLayout == 'large-filled',\n 'large-filled-symbols': navBarLayout == 'large-filled-symbols',\n 'large-empty': navBarLayout == 'large-empty',\n 'large-empty-symbols': navBarLayout == 'large-empty-symbols'\n }\">\n</aw-wizard-navigation-bar>\n"
},] }
];
WizardComponent.ctorParameters = () => [];
WizardComponent.propDecorators = {
wizardStepsQueryList: [{ type: ContentChildren, args: [WizardStep, { descendants: true },] }],
navBarLocation: [{ type: Input }],
navBarLayout: [{ type: Input }],
navBarDirection: [{ type: Input }],
defaultStepIndex: [{ type: Input }],
disableNavigationBar: [{ type: Input }],
horizontalOrientation: [{ type: HostBinding, args: ['class.horizontal',] }],
verticalOrientation: [{ type: HostBinding, args: ['class.vertical',] }]
};
/**
* The `aw-wizard-navigation-bar` component contains the navigation bar inside a [[WizardComponent]].
* To correctly display the navigation bar, it's required to set the right css classes for the navigation bar,
* otherwise it will look like a normal `ul` component.
*
* ### Syntax
*
* ```html
* <aw-wizard-navigation-bar></aw-wizard-navigation-bar>
* ```
*
* @author Marc Arndt
*/
class WizardNavigationBarComponent {
/**
* Constructor
*
* @param wizard The state the wizard currently resides in
*/
constructor(wizard) {
this.wizard = wizard;
}
/**
* Returns all [[WizardStep]]s contained in the wizard
*
* @returns An array containing all [[WizardStep]]s
*/
get wizardSteps() {
switch (this.wizard.navBarDirection) {
case 'right-to-left':
return this.wizard.wizardSteps.slice().reverse();
case 'left-to-right':
default:
return this.wizard.wizardSteps;
}
}
/**
* Returns the number of wizard steps, that need to be displaced in the navigation bar
*
* @returns The number of wizard steps to be displayed
*/
get numberOfWizardSteps() {
return this.wizard.wizardSteps.length;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `current` in the navigation bar
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as `current`
*/
isCurrent(wizardStep) {
return wizardStep.selected;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `editing` in the navigation bar
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as `editing`
*/
isEditing(wizardStep) {
return wizardStep.editing;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `done` in the navigation bar
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as `done`
*/
isDone(wizardStep) {
return wizardStep.completed;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `optional` in the navigation bar
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as `optional`
*/
isOptional(wizardStep) {
return wizardStep.optional;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `completed` in the navigation bar.
*
* The `completed` class is only applied to completion steps.
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as `completed`
*/
isCompleted(wizardStep) {
return wizardStep instanceof WizardCompletionStep && this.wizard.completed;
}
/**
* Checks, whether a [[WizardStep]] can be marked as `navigable` in the navigation bar.
* A wizard step can be navigated to if:
* - the step is currently not selected
* - the navigation bar isn't disabled
* - the navigation mode allows navigation to the step
*
* @param wizardStep The wizard step to be checked
* @returns True if the step can be marked as navigable
*/
isNavigable(wizardStep) {
return !wizardStep.selected && !this.wizard.disableNavigationBar &&
this.wizard.isNavigable(this.wizard.getIndexOfStep(wizardStep));
}
}
WizardNavigationBarComponent.decorators = [
{ type: Component, args: [{
selector: 'aw-wizard-navigation-bar',
template: "<ul class=\"steps-indicator steps-{{numberOfWizardSteps}}\">\n <li [attr.id]=\"step.stepId\" *ngFor=\"let step of wizardSteps\" [ngClass]=\"{\n 'current': isCurrent(step),\n 'editing': isEditing(step),\n 'done': isDone(step),\n 'optional': isOptional(step),\n 'completed': isCompleted(step),\n 'navigable': isNavigable(step)\n }\">\n <a [awGoToStep]=\"step\">\n <div class=\"label\">\n <ng-container *ngIf=\"step.stepTitleTemplate\" [ngTemplateOutlet]=\"step.stepTitleTemplate.templateRef\"\n [ngTemplateOutletContext]=\"{wizardStep: step}\"></ng-container>\n <ng-container *ngIf=\"!step.stepTitleTemplate\">{{step.stepTitle}}</ng-container>\n </div>\n <div class=\"step-indicator\"\n [ngStyle]=\"{ 'font-family': step.stepSymbolTemplate ? '' : step.navigationSymbol.fontFamily }\">\n <ng-container *ngIf=\"step.stepSymbolTemplate\" [ngTemplateOutlet]=\"step.stepSymbolTemplate.templateRef\"\n [ngTemplateOutletContext]=\"{wizardStep: step}\"></ng-container>\n <ng-container *ngIf=\"!step.stepSymbolTemplate\">{{step.navigationSymbol.symbol}}</ng-container>\n </div>\n </a>\n </li>\n</ul>\n"
},] }
];
WizardNavigationBarComponent.ctorParameters = () => [
{ type: WizardComponent }
];
/**
* The `aw-wizard-step` component is used to define a normal step inside a wizard.
*
* ### Syntax
*
* With `stepTitle` and `navigationSymbol` inputs:
*
* ```html
* <aw-wizard-step [stepTitle]="step title" [navigationSymbol]="{ symbol: 'symbol', fontFamily: 'font-family' }"
* [canExit]="deciding function" (stepEnter)="enter function" (stepExit)="exit function">
* ...
* </aw-wizard-step>
* ```
*
* With `awWizardStepTitle` and `awWizardStepSymbol` directives:
*
* ```html
* <aw-wizard-step"
* [canExit]="deciding function" (stepEnter)="enter function" (stepExit)="exit function">
* <ng-template awWizardStepTitle>
* step title
* </ng-template>
* <ng-template awWizardStepSymbol>
* symbol
* </ng-template>
* ...
* </aw-wizard-step>
* ```
*
* ### Example
*
* With `stepTitle` and `navigationSymbol` inputs:
*
* ```html
* <aw-wizard-step stepTitle="Address information" [navigationSymbol]="{ symbol: '', fontFamily: 'FontAwesome' }">
* ...
* </aw-wizard-step>
* ```
*
* With `awWizardStepTitle` and `awWizardStepSymbol` directives:
*
* ```html
* <aw-wizard-step>
* <ng-template awWizardStepTitle>
* Address information
* </ng-template>
* <ng-template awWizardStepSymbol>
* <i class="fa fa-taxi"></i>
* </ng-template>
* </aw-wizard-step>
* ```
*
* @author Marc Arndt
*/
class WizardStepComponent extends WizardStep {
}
WizardStepComponent.decorators = [
{ type: Component, args: [{
selector: 'aw-wizard-step',
template: "<ng-content></ng-content>\n",
providers: [
{ provide: WizardStep, useExisting: forwardRef(() => WizardStepComponent) }
]
},] }
];
/**
* The `awEnableBackLinks` directive can be used to allow the user to leave a [[WizardCompletionStep]] after is has been entered.
*
* ### Syntax
*
* ```html
* <aw-wizard-completion-step awEnableBackLinks (stepExit)="exit function">
* ...
* </aw-wizard-completion-step>
* ```
*
* ### Example
*
* ```html
* <aw-wizard-completion-step stepTitle="Final step" awEnableBackLinks>
* ...
* </aw-wizard-completion-step>
* ```
*
* @author Marc Arndt
*/
class EnableBackLinksDirective {
/**
* Constructor
*
* @param completionStep The wizard completion step, which should be exitable
*/
constructor(completionStep) {
this.completionStep = completionStep;
/**
* This EventEmitter is called when the step is exited.
* The bound method can be used to do cleanup work.
*/
this.stepExit = new EventEmitter();
}
/**
* Initialization work
*/
ngOnInit() {
this.completionStep.canExit = true;
this.completionStep.stepExit = this.stepExit;
}
}
EnableBackLinksDirective.decorators = [
{ type: Directive, args: [{
selector: '[awEnableBackLinks]'
},] }
];
EnableBackLinksDirective.ctorParameters = () => [
{ type: WizardCompletionStep, decorators: [{ type: Host }] }
];
EnableBackLinksDirective.propDecorators = {
stepExit: [{ type: Output }]
};
/**
* Checks whether the given `value` implements the interface [[StepId]].
*
* @param value The value to be checked
* @returns True if the given value implements [[StepId]] and false otherwise
*/
function isStepId(value) {
return value.hasOwnProperty('stepId') && !(value instanceof WizardStep);
}
/**
* Checks whether the given `value` implements the interface [[StepIndex]].
*
* @param value The value to be checked
* @returns True if the given value implements [[StepIndex]] and false otherwise
*/
function isStepIndex(value) {
return value.hasOwnProperty('stepIndex');
}
/**
* Checks whether the given `value` implements the interface [[StepOffset]].
*
* @param value The value to be checked
* @returns True if the given value implements [[StepOffset]] and false otherwise
*/
function isStepOffset(value) {
return value.hasOwnProperty('stepOffset');
}
/**
* The `awGoToStep` directive can be used to navigate to a given step.
* This step can be defined in one of multiple formats
*
* ### Syntax
*
* With absolute step index:
*
* ```html
* <button [awGoToStep]="{ stepIndex: absolute step index }" (finalize)="finalize method">...</button>
* ```
*
* With unique step id:
*
* ```html
* <button [awGoToStep]="{ stepId: 'step id of destination step' }" (finalize)="finalize method">...</button>
* ```
*
* With a wizard step object:
*
* ```html
* <button [awGoToStep]="wizard step object" (finalize)="finalize method">...</button>
* ```
*
* With an offset to the defining step:
*
* ```html
* <button [awGoToStep]="{ stepOffset: offset }" (finalize)="finalize method">...</button>
* ```
*
* @author Marc Arndt
*/
class GoToStepDirective {
/**
* Constructor
*
* @param wizard The wizard component
* @param wizardStep The wizard step, which contains this [[GoToStepDirective]]
*/
constructor(wizard, wizardStep) {
this.wizard = wizard;
this.wizardStep = wizardStep;
/**
* This [[EventEmitter]] is called directly before the current step is exited during a transition through a component with this directive.
*/
this.preFinalize = new EventEmitter();
/**
* This [[EventEmitter]] is called directly after the current step is exited during a transition through a component with this directive.
*/
this.postFinalize = new EventEmitter();
}
/**
* A convenience field for `preFinalize`
*/
get finalize() {
return this.preFinalize;
}
/**
* A convenience name for `preFinalize`
*
* @param emitter The [[EventEmitter]] to be set
*/
set finalize(emitter) {
/* istanbul ignore next */
this.preFinalize = emitter;
}
/**
* Returns the destination step of this directive as an absolute step index inside the wizard
*
* @returns The index of the destination step
* @throws If `targetStep` is of an unknown type an `Error` is thrown
*/
get destinationStep() {
let destinationStep;
if (isStepIndex(this.targetStep)) {
destinationStep = this.targetStep.stepIndex;
}
else if (isStepId(this.targetStep)) {
destinationStep = this.wizard.getIndexOfStepWithId(this.targetStep.stepId);
}
else if (isStepOffset(this.targetStep) && this.wizardStep !== null) {
destinationStep = this.wizard.getIndexOfStep(this.wizardStep) + this.targetStep.stepOffset;
}
else if (this.targetStep instanceof WizardStep) {
destinationStep = this.wizard.getIndexOfStep(this.targetStep);