Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API change] ConfigurableNavigationMode proposal #211

Merged
merged 14 commits into from
May 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 58 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,26 +116,6 @@ Normally the steps in the navigation bar are layed out from left to right or fro
In some cases, like with languages that are written from right to left, it may be required to change this direction to layout the steps from right to left.
To layout the steps from right to left you can pass `right-to-left` to the `navBarDirection` input of the wizard component.

#### \[navigationMode\]
`angular-archwizard` supports three different navigation modes:
- **strict** navigation mode:
The first navigation mode is strict navigation.
This mode describes the status quo, i.e. the current navigation behavior of the wizard.
Currently you can only navigate through the wizard steps in a linear fashion,
where you can only enter the next step if all previous steps have been completed and the exit condition of your current step have been fulfilled.
In this mode it is not possible to jump between different steps, i.e. move to step 3 from step 1, then go to step 2 to finally go to step 4.
The only exception to this rule are optional steps, which a user can skip.
Therefore you are required to do the steps in the order `1 -> 2 -> 3 -> 4`.
- **semi-strict** navigation mode:
The second navigation mode is semi-strict navigation.
This mode lets the user navigate between the steps in any order he likes.
This means that in this navigation mode a user could complete the steps in the order `1 -> 3 -> 2 -> 4`, if the exit conditions have been fulfilled.
This mode has only one restriction, where the user can enter the completion step after he has completed all previous steps.
Again optional steps are skipable in this mode.
- **free** navigation mode:
The third navigation mode is free navigation.
This mode let's the user navigate freely between the different steps, including the completion step, in any order he desires.

#### \[defaultStepIndex\]
Per default the wizard always starts with the first wizard step, after initialisation. The same applies for a reset, where the wizard normally resets to the first step.
Sometimes this needs to be changed. If another default wizard step needs to be used, you can set it, by using the `[defaultStepIndex]` input of the wizard component.
Expand All @@ -158,7 +138,6 @@ Possible `<aw-wizard>` parameters:
| [navBarLocation] | `top` \| `bottom` \| `left` \| `right` | top |
| [navBarLayout] | `small` \| `large-filled` \| `large-empty` \| `large-filled-symbols` \| `large-empty-symbols` | small |
| [navBarDirection] | `left-to-right` \| `right-to-left` | left-to-right |
| [navigationMode] | `strict` \| `semi-strict` \| `free` | strict |
| [defaultStepIndex] | `number` | 0 |
| [disableNavigationBar] | `boolean` | false |

Expand Down Expand Up @@ -272,6 +251,64 @@ Possible `<aw-wizard-completion-step>` parameters:

## Directives

### \[awNavigationMode\]
By default `angular-archwizard` operates in a "strict" navigation mode.
It requires users to navigate through the wizard steps in a linear fashion, where they can only enter the next step if all previous steps have been completed and the exit condition of the current step have been fulfilled.
The only exception to this rule are optional steps, which a user can skip.
Using the navigation bar, the user can navigate back to steps they already visited.

You can alter this behavior by applying to the `<aw-wizard>` element an additional `[awNavigationMode]` directive, which can be used in two ways.
The easiest option is to tweak the default navigation mode with `[navigateBackward]` and/or `[navigateForward]` inputs which control the navigation bar. Valid options for these inputs are `'allow'` and `'deny`'. Take notice that the `'allow'` option still respects step exit conditions. Also, the completion step still only becomes enterable after all previous steps are completed. Example usage:

```html
<aw-wizard [awNavigationMode] navigateBackward="allow" navigateForward="allow">...</aw-wizard>
```

If changes you need are more radical, you can define your own navigation mode. In order to do this, create a class implementing the `NavigationMode` interface and pass an instance of this class into the `[awNavigationMode]` directive. This takes priority over `[navigateBackward]` and `[navigateForward]` inputs. Example usage:

custom-navigation-mode.ts:
```typescript
import { NavigationMode } from 'angular-archwizard'

class CustomNavigationMode implements NavigationMode {

// ...
}
```

my.component.ts:
```typescript
@Component({
// ...
})
class MyComponent {

navigationMode = new CustomNavigationMode();
}
```

my.component.html:
```html
<aw-wizard [awNavigationMode]="navigationMode">...</aw-wizard>
```

Instead of implementing the `NavigationMode` interface from scratch, you can extend one of the classes provided by `angular-archwizard`:

- `BaseNavigationMode`. This class contains an abstract method called `isNavigable`, which you will have to override to define wizard's behavior towards navigation using the navigation bar.

- `ConfigurableNavigationMode`. This class defines the default navigation mode used by `angular-archwizard`. In some cases, it might be more convenient to base your custom implementation on it.

This way of customizing the wizard is advanced, so be prepared to refer to documentation comments and source code for help.

#### Parameter overview
Possible `awNavigationMode` parameters:

| Parameter name | Possible Values | Default Value |
| ----------------------------- | ---------------------------------------------------------------------------------------------------- | ------------- |
| [awNavigationMode] | `NavigationMode` | `null` |
| [navigateBackward] | `'allow'|'deny'` | `'deny'` |
| [navigateForward] | `'allow'|'deny'` | `'allow'` |

### \[awEnableBackLinks\]
In some cases it may be required that the user is allowed to leave an entered `aw-wizard-completion-step`.
In such a case you can enable this by adding the directive `[awEnableBackLinks]` to the `aw-wizard-completion-step`.
Expand Down
10 changes: 3 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ export {SelectedStepDirective} from './lib/directives/selected-step.directive';
export {WizardCompletionStepDirective} from './lib/directives/wizard-completion-step.directive';
export {WizardStepDirective} from './lib/directives/wizard-step.directive';
export {WizardStepTitleDirective} from './lib/directives/wizard-step-title.directive';
export {NavigationModeDirective} from './lib/directives/navigation-mode.directive';

// export the navigation classes
export {FreeNavigationMode} from './lib/navigation/free-navigation-mode';
export {NavigationMode} from './lib/navigation/navigation-mode.interface';
export {SemiStrictNavigationMode} from './lib/navigation/semi-strict-navigation-mode';
export {StrictNavigationMode} from './lib/navigation/strict-navigation-mode';
export {ConfigurableNavigationMode} from './lib/navigation/configurable-navigation-mode';
export {BaseNavigationMode} from './lib/navigation/base-navigation-mode.interface';
export {NavigationModeInput} from './lib/navigation/navigation-mode-input.interface';
export {NavigationModeFactory} from './lib/navigation/navigation-mode-factory.interface';
export {BaseNavigationModeFactory} from './lib/navigation/base-navigation-mode-factory.provider';

// export the utility functions
export {MovingDirection} from './lib/util/moving-direction.enum';
Expand All @@ -36,4 +32,4 @@ export {WizardCompletionStep} from './lib/util/wizard-completion-step.interface'
export {WizardStep} from './lib/util/wizard-step.interface';

// export the module
export {ArchwizardModule, ArchwizardModuleConfig} from './lib/archwizard.module';
export {ArchwizardModule} from './lib/archwizard.module';
29 changes: 7 additions & 22 deletions src/lib/archwizard.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,9 @@ import {WizardCompletionStepDirective} from './directives/wizard-completion-step
import {WizardStepSymbolDirective} from './directives/wizard-step-symbol.directive';
import {WizardStepTitleDirective} from './directives/wizard-step-title.directive';
import {WizardStepDirective} from './directives/wizard-step.directive';
import {NAVIGATION_MODE_FACTORY, NavigationModeFactory} from './navigation/navigation-mode-factory.interface';
import {BaseNavigationModeFactory} from './navigation/base-navigation-mode-factory.provider';
import {NavigationModeDirective} from './directives/navigation-mode.directive';


/**
* Configuration object for the `angular-archwizard` module.
*
* Allows to customize global settings.
*/
export interface ArchwizardModuleConfig {

/**
* Custom factory of [[NavigationMode]] instances.
*
* You may need a custom factory in order to support custom navigation modes.
* By default, [[BaseNavigationModeFactory]] is used.
*/
navigationModeFactory?: NavigationModeFactory;
}

earshinov marked this conversation as resolved.
Show resolved Hide resolved
/**
* The module defining all the content inside `angular-archwizard`
*
Expand All @@ -56,7 +39,8 @@ export interface ArchwizardModuleConfig {
WizardStepDirective,
WizardCompletionStepDirective,
SelectedStepDirective,
ResetWizardDirective
ResetWizardDirective,
NavigationModeDirective,
],
imports: [
CommonModule
Expand All @@ -76,16 +60,17 @@ export interface ArchwizardModuleConfig {
WizardStepDirective,
WizardCompletionStepDirective,
SelectedStepDirective,
ResetWizardDirective
ResetWizardDirective,
NavigationModeDirective,
]
})
export class ArchwizardModule {
/* istanbul ignore next */
public static forRoot(config?: ArchwizardModuleConfig): ModuleWithProviders {
public static forRoot(): ModuleWithProviders {
return {
ngModule: ArchwizardModule,
providers: [
{ provide: NAVIGATION_MODE_FACTORY, useValue: config && config.navigationModeFactory || new BaseNavigationModeFactory() },
// Nothing here yet
]
};
}
Expand Down
17 changes: 9 additions & 8 deletions src/lib/components/wizard.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import {AfterViewInit, ChangeDetectorRef, Component, ViewChild} from '@angular/c
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {ArchwizardModule} from '../archwizard.module';
import {FreeNavigationMode} from '../navigation/free-navigation-mode';
import {StrictNavigationMode} from '../navigation/strict-navigation-mode';
import {WizardComponent} from './wizard.component';
import {WizardStep} from '../util/wizard-step.interface';

@Component({
selector: 'aw-test-wizard',
template: `
<aw-wizard [navigationMode]="navigationMode" [disableNavigationBar]="disableNavigationBar" [defaultStepIndex]="defaultStepIndex">
<aw-wizard
[disableNavigationBar]="disableNavigationBar" [defaultStepIndex]="defaultStepIndex"
[awNavigationMode] [navigateForward]="navigateForward" [navigateBackward]="navigateBackward">
<aw-wizard-step stepTitle='Steptitle 1' *ngIf="showStep1">
Step 1
</aw-wizard-step>
Expand All @@ -24,7 +24,8 @@ import {WizardStep} from '../util/wizard-step.interface';
`
})
class WizardTestComponent implements AfterViewInit {
public navigationMode = 'strict';
public navigateForward = 'deny';
public navigateBackward = 'deny';

public disableNavigationBar = false;

Expand Down Expand Up @@ -69,7 +70,6 @@ describe('WizardComponent', () => {
it('should create', () => {
expect(wizardTest).toBeTruthy();
expect(wizard).toBeTruthy();
expect(wizard.navigationMode).toBeTruthy();
});

it('should contain navigation bar at the correct position in default navBarLocation mode', () => {
Expand Down Expand Up @@ -180,12 +180,13 @@ describe('WizardComponent', () => {
});

it('should change the navigation mode correctly during runtime', () => {
expect(wizard.navigation instanceof StrictNavigationMode).toBe(true);
const oldNavigation = wizard.navigation;

wizardTest.navigationMode = 'free';
wizardTest.navigateForward = 'allow';
wizardTest.navigateBackward = 'allow';
wizardTestFixture.detectChanges();

expect(wizard.navigation instanceof FreeNavigationMode).toBe(true);
expect(wizard.navigation).not.toBe(oldNavigation);
});

it('should change disableNavigationBar correctly during runtime', () => {
Expand Down
63 changes: 8 additions & 55 deletions src/lib/components/wizard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,12 @@ import {
HostBinding,
Input,
QueryList,
SimpleChanges,
Inject,
Optional,
EventEmitter,
OnChanges,
} from '@angular/core';
import {NavigationMode} from '../navigation/navigation-mode.interface';
import {NavigationModeInput} from '../navigation/navigation-mode-input.interface';
import {NavigationModeFactory, NAVIGATION_MODE_FACTORY} from '../navigation/navigation-mode-factory.interface';
import {BaseNavigationModeFactory} from '../navigation/base-navigation-mode-factory.provider';
import {WizardStep} from '../util/wizard-step.interface';
import {MovingDirection} from '../util/moving-direction.enum';
import {ConfigurableNavigationMode} from '../navigation/configurable-navigation-mode';

/**
* The `aw-wizard` component defines the root component of a wizard.
Expand Down Expand Up @@ -57,7 +51,7 @@ import {MovingDirection} from '../util/moving-direction.enum';
selector: 'aw-wizard',
templateUrl: 'wizard.component.html',
})
export class WizardComponent implements AfterContentInit, OnChanges {
export class WizardComponent implements AfterContentInit {
/**
* A QueryList containing all [[WizardStep]]s inside this wizard
*/
Expand Down Expand Up @@ -85,23 +79,6 @@ export class WizardComponent implements AfterContentInit, OnChanges {
@Input()
public navBarDirection = 'left-to-right';

/**
* The navigation mode used for transitioning between different steps.
*
* The input value can be either a navigation mode name or a function.
*
* A set of supported mode names is determined by the configured navigation mode factory.
* The default navigation mode factory recognizes `strict`, `semi-strict` and `free`.
*
* If the value is a function, the function will be called during the initialization of the wizard
* component and must return an instance of [[NavigationMode]] to be used in the component.
*
* If the input is not configured or set to a falsy value, a default mode will be chosen by the navigation mode factory.
* For the default navigation mode factory, the default mode is `strict`.
*/
@Input()
public navigationMode: NavigationModeInput;

/**
* 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
Expand Down Expand Up @@ -136,7 +113,7 @@ export class WizardComponent implements AfterContentInit, OnChanges {
*
* For outside access, use the [[navigation]] getter.
*/
private _navigation: NavigationMode;
private _navigation: NavigationMode = new ConfigurableNavigationMode();

/**
* An array representation of all wizard steps belonging to this model
Expand All @@ -156,16 +133,8 @@ export class WizardComponent implements AfterContentInit, OnChanges {

/**
* Constructor
*
* @param model The model for this wizard component
* @param navigationModeFactory Navigation mode factory for this wizard component
*/
constructor(
// Using @Optional() in order not to break applications which import ArchwizardModule without calling forRoot().
@Optional() @Inject(NAVIGATION_MODE_FACTORY) private navigationModeFactory: NavigationModeFactory) {
if (!this.navigationModeFactory) {
this.navigationModeFactory = new BaseNavigationModeFactory();
}
*/
constructor() {
}

/**
Expand All @@ -190,21 +159,6 @@ export class WizardComponent implements AfterContentInit, OnChanges {
return this.navBarLocation === 'left' || this.navBarLocation === 'right';
}

/**
* Updates the model after certain input values have changed
*
* @param changes The detected changes
*/
public ngOnChanges(changes: SimpleChanges) {
for (const propName of Object.keys(changes)) {
const change = changes[propName];

if (!change.firstChange && propName === 'navigationMode') {
this.updateNavigationMode(change.currentValue);
}
}
}

/**
* Initialization work
*/
Expand All @@ -216,7 +170,6 @@ export class WizardComponent implements AfterContentInit, OnChanges {

// initialize the model
this.updateWizardSteps(this.wizardStepsQueryList.toArray());
this.updateNavigationMode(this.navigationMode);

// finally reset the whole wizard componennt
this.reset();
Expand Down Expand Up @@ -277,10 +230,10 @@ export class WizardComponent implements AfterContentInit, OnChanges {
/**
* Updates the navigation mode for this wizard component
*
* @param navigationMode The updated navigation mode
* @param navigation The updated navigation mode
*/
public updateNavigationMode(navigationMode: NavigationModeInput) {
this._navigation = this.navigationModeFactory.create(this, navigationMode);
public set navigation(navigation: NavigationMode) {
this._navigation = navigation;
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/lib/directives/go-to-step.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import {Component, ViewChild} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {ArchwizardModule} from '../archwizard.module';
import {NavigationMode} from '../navigation/navigation-mode.interface';
import {GoToStepDirective} from './go-to-step.directive';
import { WizardComponent } from '../components/wizard.component';
import {WizardComponent} from '../components/wizard.component';

@Component({
selector: 'aw-test-wizard',
Expand Down