Skip to content

Commit

Permalink
Refactor the hierarchy of NavigationMode classes. (#166)
Browse files Browse the repository at this point in the history
* Refactor the hierarchy of NavigationMode classes.
    * Shared code extracted to the base `abstract class NavigationMode`.
    * This higlights differences between provided navigation modes.
* Document arguments for goToNextStep and goToPreviousStep
* Allow checkReset to indicate check failure by returning false
    #166 (comment)
* Split `abstract class NavigationMode` into `interface NavigationMode` and `abstract class BaseNavigationMode`.
* Provide an ability to specify WizardComponent's navigationMode input as a function returning a navigation mode name or a created NavigationMode.
    Notes:
	 - `WizardState.updateNavigationMode` method signature is left compatible with the old version.
	 - A new set of tests is added in `navigation-mode-selection.spec.ts`.
	 - The new ability should be documented in the 'navigationMode' section of the README.
* Extract NavigationModeFactory interface and BaseNavigationModeFactory class.
    ArchwizardModule.forRoot() now takes an optional configuration object with an optional `navigationModeFactory` field.
**BREAKING API CHANGES**:
   * WizardState.updateNavigationMode() no longer takes a navigation mode name.  WizardComponent.updateNavigationMode() should be used instead.
* Add comments for ArchwizardModuleConfig and ArchwizardModuleConfig.navigationModeFactory
* Add missing doc-comment for a WizardComponent's constructor parameter
* Add missing documentation for WizardComponent.updateNavigationMode
* Rename `navigationMode` parameter to `navigationModeInput` for consistency and to match the doc comments
* Improve doc comments for WizardComponent.navigationInput field and NavigationInput type
* Removed the default value for WizardComponent.navigationMode field.  It is not required.
* Enable "member-access" check in tslint.json.  Add missing access modifiers.
* Move implementation-specific comments from NavigationMode interface to BaseNavigationMode class
  • Loading branch information
earshinov authored and madoar committed Mar 24, 2019
1 parent 5b54481 commit f9bffa7
Show file tree
Hide file tree
Showing 32 changed files with 576 additions and 459 deletions.
7 changes: 5 additions & 2 deletions src/index.ts
Expand Up @@ -21,8 +21,11 @@ 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 {BaseNavigationMode} from './lib/navigation/base-navigation-mode.interface';
export {WizardState} from './lib/navigation/wizard-state.model';
export {navigationModeFactory} from './lib/navigation/navigation-mode.provider';
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 @@ -34,4 +37,4 @@ export {WizardCompletionStep} from './lib/util/wizard-completion-step.interface'
export {WizardStep} from './lib/util/wizard-step.interface';

// export the module
export {ArchwizardModule} from './lib/archwizard.module';
export {ArchwizardModule, ArchwizardModuleConfig} from './lib/archwizard.module';
28 changes: 26 additions & 2 deletions src/lib/archwizard.module.ts
Expand Up @@ -15,6 +15,25 @@ 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';


/**
* 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;
}

/**
* The module defining all the content inside `angular-archwizard`
Expand Down Expand Up @@ -62,7 +81,12 @@ import {WizardStepDirective} from './directives/wizard-step.directive';
})
export class ArchwizardModule {
/* istanbul ignore next */
static forRoot(): ModuleWithProviders {
return {ngModule: ArchwizardModule, providers: []};
public static forRoot(config?: ArchwizardModuleConfig): ModuleWithProviders {
return {
ngModule: ArchwizardModule,
providers: [
{ provide: NAVIGATION_MODE_FACTORY, useValue: config && config.navigationModeFactory || new BaseNavigationModeFactory() },
]
};
}
}
5 changes: 2 additions & 3 deletions src/lib/components/wizard-completion-step.component.spec.ts
Expand Up @@ -5,7 +5,6 @@ import {ArchwizardModule} from '../archwizard.module';
import {NavigationMode} from '../navigation/navigation-mode.interface';
import {WizardState} from '../navigation/wizard-state.model';
import {MovingDirection} from '../util/moving-direction.enum';
import {WizardCompletionStepComponent} from './wizard-completion-step.component';

@Component({
selector: 'aw-test-wizard',
Expand All @@ -29,11 +28,11 @@ class WizardTestComponent {

public eventLog: Array<string> = [];

enterInto(direction: MovingDirection, destination: number): void {
public enterInto(direction: MovingDirection, destination: number): void {
this.eventLog.push(`enter ${MovingDirection[direction]} ${destination}`);
}

exitFrom(direction: MovingDirection, source: number): void {
public exitFrom(direction: MovingDirection, source: number): void {
this.eventLog.push(`exit ${MovingDirection[direction]} ${source}`);
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/lib/components/wizard-step.component.spec.ts
Expand Up @@ -5,7 +5,6 @@ import {ArchwizardModule} from '../archwizard.module';
import {NavigationMode} from '../navigation/navigation-mode.interface';
import {WizardState} from '../navigation/wizard-state.model';
import {MovingDirection} from '../util/moving-direction.enum';
import {WizardStepComponent} from './wizard-step.component';

@Component({
selector: 'aw-test-wizard',
Expand All @@ -29,11 +28,11 @@ class WizardTestComponent {

public eventLog: Array<string> = [];

enterInto(direction: MovingDirection, destination: number): void {
public enterInto(direction: MovingDirection, destination: number): void {
this.eventLog.push(`enter ${MovingDirection[direction]} ${destination}`);
}

exitFrom(direction: MovingDirection, source: number): void {
public exitFrom(direction: MovingDirection, source: number): void {
this.eventLog.push(`exit ${MovingDirection[direction]} ${source}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/wizard.component.spec.ts
Expand Up @@ -40,7 +40,7 @@ class WizardTestComponent implements AfterViewInit {
constructor(private _changeDetectionRef: ChangeDetectorRef) {
}

ngAfterViewInit(): void {
public ngAfterViewInit(): void {
// Force another change detection in order to fix an occuring ExpressionChangedAfterItHasBeenCheckedError
this._changeDetectionRef.detectChanges();
}
Expand Down
49 changes: 41 additions & 8 deletions src/lib/components/wizard.component.ts
Expand Up @@ -7,9 +7,14 @@ import {
OnChanges,
QueryList,
SimpleChanges,
ViewEncapsulation
ViewEncapsulation,
Inject,
Optional
} 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 {WizardState} from '../navigation/wizard-state.model';
import {WizardStep} from '../util/wizard-step.interface';

Expand Down Expand Up @@ -85,10 +90,20 @@ export class WizardComponent implements OnChanges, AfterContentInit {

/**
* The navigation mode used for transitioning between different steps.
* The navigation mode can be either `strict`, `semi-strict` or `free`
*
* 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 = 'strict';
public navigationMode: NavigationModeInput;

/**
* The initially selected step, represented by its index
Expand All @@ -106,8 +121,15 @@ export class WizardComponent implements OnChanges, AfterContentInit {
* Constructor
*
* @param model The model for this wizard component
* @param navigationModeFactory Navigation mode factory for this wizard component
*/
constructor(public model: WizardState) {
constructor(
public model: WizardState,
// 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();
}
}

/**
Expand Down Expand Up @@ -144,7 +166,7 @@ export class WizardComponent implements OnChanges, AfterContentInit {
*
* @param changes The detected changes
*/
ngOnChanges(changes: SimpleChanges) {
public ngOnChanges(changes: SimpleChanges) {
for (const propName of Object.keys(changes)) {
const change = changes[propName];

Expand All @@ -157,7 +179,7 @@ export class WizardComponent implements OnChanges, AfterContentInit {
this.model.disableNavigationBar = change.currentValue;
break;
case 'navigationMode':
this.model.updateNavigationMode(change.currentValue);
this.updateNavigationMode(change.currentValue);
break;
/* istanbul ignore next */
default:
Expand All @@ -169,7 +191,7 @@ export class WizardComponent implements OnChanges, AfterContentInit {
/**
* Initialization work
*/
ngAfterContentInit(): void {
public ngAfterContentInit(): void {
// add a subscriber to the wizard steps QueryList to listen to changes in the DOM
this.wizardSteps.changes.subscribe(changedWizardSteps => {
this.model.updateWizardSteps(changedWizardSteps.toArray());
Expand All @@ -179,9 +201,20 @@ export class WizardComponent implements OnChanges, AfterContentInit {
this.model.disableNavigationBar = this.disableNavigationBar;
this.model.defaultStepIndex = this.defaultStepIndex;
this.model.updateWizardSteps(this.wizardSteps.toArray());
this.model.updateNavigationMode(this.navigationMode);
this.updateNavigationMode(this.navigationMode);

// finally reset the whole wizard state
this.navigation.reset();
}

/**
* Updates the navigation mode for this wizard component.
*
* Initially the wizard component uses the navigation mode specified in the [[navigationMode]] input
* or the default navigation mode if the [[navigationMode]] input is not defined.
* Use this method to select a different navigation mode after the wizard component is initialized.
*/
public updateNavigationMode(navigationModeInput: NavigationModeInput) {
this.model.updateNavigationMode(this.navigationModeFactory.create(this, navigationModeInput));
}
}
4 changes: 2 additions & 2 deletions src/lib/directives/enable-back-links.directive.spec.ts
Expand Up @@ -31,11 +31,11 @@ class WizardTestComponent {

public completionStepExit: (direction: MovingDirection, source: number) => void = this.exitFrom;

enterInto(direction: MovingDirection, destination: number): void {
public enterInto(direction: MovingDirection, destination: number): void {
this.eventLog.push(`enter ${MovingDirection[direction]} ${destination}`);
}

exitFrom(direction: MovingDirection, source: number): void {
public exitFrom(direction: MovingDirection, source: number): void {
this.eventLog.push(`exit ${MovingDirection[direction]} ${source}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/directives/enable-back-links.directive.ts
Expand Up @@ -45,7 +45,7 @@ export class EnableBackLinksDirective implements OnInit {
/**
* Initialization work
*/
ngOnInit(): void {
public ngOnInit(): void {
this.completionStep.canExit = true;
this.completionStep.stepExit = this.stepExit;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/directives/go-to-step.directive.spec.ts
Expand Up @@ -47,7 +47,7 @@ class WizardTestComponent {

public eventLog: Array<string> = [];

finalizeStep(stepIndex: number): void {
public finalizeStep(stepIndex: number): void {
this.eventLog.push(`finalize ${stepIndex}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/directives/next-step.directive.spec.ts
Expand Up @@ -31,7 +31,7 @@ import {NextStepDirective} from './next-step.directive';
class WizardTestComponent {
public eventLog: Array<string> = [];

finalizeStep(stepIndex: number): void {
public finalizeStep(stepIndex: number): void {
this.eventLog.push(`finalize ${stepIndex}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/directives/optional-step.directive.ts
Expand Up @@ -38,7 +38,7 @@ export class OptionalStepDirective implements OnInit {
/**
* Initialization work
*/
ngOnInit(): void {
public ngOnInit(): void {
this.wizardStep.optional = true;
}
}
2 changes: 1 addition & 1 deletion src/lib/directives/previous-step.directive.spec.ts
Expand Up @@ -31,7 +31,7 @@ import {PreviousStepDirective} from './previous-step.directive';
class WizardTestComponent {
public eventLog: Array<string> = [];

finalizeStep(stepIndex: number): void {
public finalizeStep(stepIndex: number): void {
this.eventLog.push(`finalize ${stepIndex}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/directives/selected-step.directive.ts
Expand Up @@ -29,7 +29,7 @@ export class SelectedStepDirective implements OnInit {
/**
* Initialization work
*/
ngOnInit(): void {
public ngOnInit(): void {
this.wizardStep.defaultSelected = true;
}
}
4 changes: 2 additions & 2 deletions src/lib/directives/wizard-completion-step.directive.spec.ts
Expand Up @@ -29,11 +29,11 @@ class WizardTestComponent {

public eventLog: Array<string> = [];

enterInto(direction: MovingDirection, destination: number): void {
public enterInto(direction: MovingDirection, destination: number): void {
this.eventLog.push(`enter ${MovingDirection[direction]} ${destination}`);
}

exitFrom(direction: MovingDirection, source: number): void {
public exitFrom(direction: MovingDirection, source: number): void {
this.eventLog.push(`exit ${MovingDirection[direction]} ${source}`);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/directives/wizard-step.directive.spec.ts
Expand Up @@ -29,11 +29,11 @@ class WizardTestComponent {

public eventLog: Array<string> = [];

enterInto(direction: MovingDirection, destination: number): void {
public enterInto(direction: MovingDirection, destination: number): void {
this.eventLog.push(`enter ${MovingDirection[direction]} ${destination}`);
}

exitFrom(direction: MovingDirection, source: number): void {
public exitFrom(direction: MovingDirection, source: number): void {
this.eventLog.push(`exit ${MovingDirection[direction]} ${source}`);
}
}
Expand Down
72 changes: 72 additions & 0 deletions src/lib/navigation/base-navigation-mode-factory.provider.ts
@@ -0,0 +1,72 @@
import {FreeNavigationMode} from './free-navigation-mode';
import {NavigationMode} from './navigation-mode.interface';
import {SemiStrictNavigationMode} from './semi-strict-navigation-mode';
import {StrictNavigationMode} from './strict-navigation-mode';
import {WizardComponent} from '../components/wizard.component';
import {NavigationModeInput} from './navigation-mode-input.interface';
import {NavigationModeFactory} from './navigation-mode-factory.interface';

/**
* A factory used to create [[NavigationMode]] instances
*/
export class BaseNavigationModeFactory implements NavigationModeFactory {

/**
* @inheritDoc
*/
public create(wizard: WizardComponent, navigationModeInput: NavigationModeInput): NavigationMode {
let navigationModeName: string;
if (typeof navigationModeInput === 'function') {
// input is a function
return navigationModeInput(wizard);
} else {
// input is a name
navigationModeName = navigationModeInput;
}
// create NavigationMode by name
return this.createByName(wizard, navigationModeName);
}

/**
* Create a [[NavigationMode]] for the given wizard instance by a navigation mode name
*
* @param wizard The wizard componenent where the created [[NavigationMode]] will be used
* @param navigationModeInput The name of a built-in navigation mode or a custom navigation mode
* @returns The created [[NavigationMode]]
*/
protected createByName(wizard: WizardComponent, navigationModeInput: string): NavigationMode {
switch (navigationModeInput) {
case 'free':
return new FreeNavigationMode(wizard.model);
case 'semi-strict':
return new SemiStrictNavigationMode(wizard.model);
case 'strict':
return new StrictNavigationMode(wizard.model);
default:
return !navigationModeInput ? this.createDefault(wizard) : this.createUnknown(wizard, navigationModeInput);
}
}

/**
* Create a [[NavigationMode]] for the given wizard instance which does not have a configured navigation mode
*
* @param wizard The wizard componenent where the created [[NavigationMode]] will be used
* @returns The created [[NavigationMode]]
*/
protected createDefault(wizard: WizardComponent): NavigationMode {
return new StrictNavigationMode(wizard.model);
}

/**
* Create a [[NavigationMode]] for the given wizard instance by a not recognized navigation mode name
*
* The base implementation always throws an Error.
*
* @param wizard The wizard componenent where the created [[NavigationMode]] will be used
* @param navigationModeInput The name of a custom navigation mode
* @returns The created [[NavigationMode]]
*/
protected createUnknown(wizard: WizardComponent, navigationModeInput: string): NavigationMode {
throw new Error(`Unknown navigation mode name: ${navigationModeInput}`);
}
}

0 comments on commit f9bffa7

Please sign in to comment.