diff --git a/.travis.yml b/.travis.yml
index dbcbd7a9f..6d85e41e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,7 @@
language: node_js
+node_js:
+ - '6'
+
cache:
directories:
- node_modules
@@ -6,9 +9,6 @@ cache:
notifications:
email: false
-node_js:
- - '6'
-
env:
global:
- ENCRYPTION_LABEL: "a8c5e363dc84"
@@ -24,9 +24,10 @@ install: true
script:
- sh -x ./node_modules/patternfly-eng-release/scripts/_build.sh -x
-before_script:
- - npm prune
after_success:
+ - if [[ -s package.json ]]; then cp package.json dist; fi
+ - if [[ -s npm-shrinkwrap.json ]]; then cp package.json dist; fi
+ - npm prune
- npm run semantic-release
- npm run publish-travis
diff --git a/index.ts b/index.ts
index 9437a50a7..d45012bd1 100644
--- a/index.ts
+++ b/index.ts
@@ -61,3 +61,15 @@ export { ToolbarConfig } from './src/app/toolbar/toolbar-config';
export { ToolbarComponent } from './src/app/toolbar/toolbar.component';
export { ToolbarModule } from './src/app/toolbar/toolbar.module';
export { ToolbarView } from './src/app/toolbar/toolbar-view';
+
+// Wizard
+export { WizardBase } from './src/app/wizard/wizard-base';
+export { WizardComponent } from './src/app/wizard/wizard.component';
+export { WizardConfig } from './src/app/wizard/wizard-config';
+export { WizardEvent } from './src/app/wizard/wizard-event';
+export { WizardModule } from './src/app/wizard/wizard.module';
+export { WizardReviewComponent } from './src/app/wizard/wizard-review.component';
+export { WizardStep } from './src/app/wizard/wizard-step';
+export { WizardStepComponent } from './src/app/wizard/wizard-step.component';
+export { WizardStepConfig } from './src/app/wizard/wizard-step-config';
+export { WizardSubstepComponent } from './src/app/wizard/wizard-substep.component';
diff --git a/package.json b/package.json
index 6e1b808c8..46877ee44 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"start:demo": "webpack-dev-server --config config/webpack.demo.js --progress --host 0.0.0.0 --port 8001 --profile --watch --content-base dist-demo",
"test": "karma start",
"transpile": "gulp transpile",
- "semantic-release": "semantic-release pre && cp package.json dist && npm publish dist/ && semantic-release post",
+ "semantic-release": "semantic-release pre && npm publish dist/ && semantic-release post",
"semantic-release-build": "npm run build"
},
"license": "Apache-2.0",
@@ -67,10 +67,10 @@
"@angular/router": "^4.0.1",
"core-js": "2.4.1",
"moment": "2.17.1",
- "ngx-bootstrap": "1.7.1",
- "patternfly": "^3.24.0",
+ "ngx-bootstrap": "1.8.0",
+ "patternfly": "^3.26.1",
"rxjs": "5.0.1",
- "zone.js": "0.7.7"
+ "zone.js": "0.8.4"
},
"devDependencies": {
"@types/jasmine": "2.5.43",
@@ -133,12 +133,13 @@
"mocha": "3.2.0",
"ng-router-loader": "2.1.0",
"npm-run-all": "4.0.2",
+ "nsp": "2.7.0",
"null-loader": "0.1.1",
"opt-cli": "1.5.1",
"optimize-js-plugin": "0.0.4",
"parse5": "2.2.3",
"patternfly-eng-publish": "0.0.4",
- "patternfly-eng-release": "~3.25.1",
+ "patternfly-eng-release": "^3.26.3",
"phantomjs-prebuilt": "2.1.14",
"postcss": "6.0.6",
"postcss-loader": "1.3.3",
diff --git a/patternfly-ng.module.ts b/patternfly-ng.module.ts
index 4ce5d811e..5b31c3dcc 100644
--- a/patternfly-ng.module.ts
+++ b/patternfly-ng.module.ts
@@ -11,13 +11,12 @@ import { SampleModule } from './src/app/sample/sample.module';
import { SearchHighlightModule } from './src/app/search-highlight/search-highlight.module';
import { SortModule } from './src/app/sort/sort.module';
import { ToolbarModule } from './src/app/toolbar/toolbar.module';
+import { WizardModule } from './src/app/wizard/wizard.module';
@NgModule({
imports: [
FormsModule
],
- declarations: [
- ],
exports: [
ActionModule,
EmptyStateModule,
@@ -28,7 +27,8 @@ import { ToolbarModule } from './src/app/toolbar/toolbar.module';
SampleModule,
SearchHighlightModule,
SortModule,
- ToolbarModule
+ ToolbarModule,
+ WizardModule
]
})
export class PatternFlyNgModule {
diff --git a/src/app/filter/filter-fields.component.less b/src/app/filter/filter-fields.component.less
index 19c2c8bac..e8cbe6f4f 100644
--- a/src/app/filter/filter-fields.component.less
+++ b/src/app/filter/filter-fields.component.less
@@ -7,7 +7,6 @@
padding-left: 0;
width: 275px;
}
- .btn-default { font-size: 12px; }
.typeahead-input-container {
position: relative;
padding-right: 0;
@@ -26,7 +25,6 @@
background-color: @color-pf-white;
background-image: none;
color: @color-pf-black-500;
- font-size: 12px;
font-style: italic;
font-weight: 400;
}
diff --git a/src/app/wizard/examples/wizard-basic-example.component.html b/src/app/wizard/examples/wizard-basic-example.component.html
new file mode 100644
index 000000000..20522ae20
--- /dev/null
+++ b/src/app/wizard/examples/wizard-basic-example.component.html
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Deployment in progress
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
Deployment was successful
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
View Deployment
+
+
+
+
+
+
+
+
+
+ Name:
+ {{data.name}}
+
+
+ Description:
+ {{data.description}}
+
+
+
+
+
+
+
+ Lorem:
+ {{data.lorem}}
+
+
+ Ipsum:
+ {{data.ipsum}}
+
+
+
+
+
+
+
+ Aliquam:
+ {{data.aliquam}}
+
+
+ Fermentum:
+ {{data.fermentum}}
+
+
+
+
+
+
+
+ Consectetur:
+ {{data.consectetur}}
+
+
+ Adipiscing:
+ {{data.adipiscing}}
+
+
+
diff --git a/src/app/wizard/examples/wizard-basic-example.component.ts b/src/app/wizard/examples/wizard-basic-example.component.ts
new file mode 100644
index 000000000..0ded8dabb
--- /dev/null
+++ b/src/app/wizard/examples/wizard-basic-example.component.ts
@@ -0,0 +1,168 @@
+import {
+ Component,
+ Host,
+ OnInit,
+ ViewChild,
+ ViewEncapsulation,
+} from '@angular/core';
+
+import { WizardConfig } from '../wizard-config';
+import { WizardComponent } from '../wizard.component';
+import { WizardExampleComponent } from './wizard-example.component';
+import { WizardEvent } from '../wizard-event';
+import { WizardStepConfig } from '../wizard-step-config';
+
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'wizard-basic-example',
+ templateUrl: './wizard-basic-example.component.html'
+})
+export class WizardBasicExampleComponent implements OnInit {
+ @ViewChild('wizard') wizard: WizardComponent;
+
+ data: any = {};
+ deployComplete: boolean = true;
+
+ // Wizard Step 1
+ step1Config: WizardStepConfig;
+ step1aConfig: WizardStepConfig;
+ step1bConfig: WizardStepConfig;
+
+ // Wizard Step 2
+ step2Config: WizardStepConfig;
+ step2aConfig: WizardStepConfig;
+ step2bConfig: WizardStepConfig;
+
+ // Wizard Step 3
+ step3Config: WizardStepConfig;
+ step3aConfig: WizardStepConfig;
+ step3bConfig: WizardStepConfig;
+
+ // Wizard
+ wizardConfig: WizardConfig;
+ wizardExample: WizardExampleComponent;
+
+ constructor(@Host() wizardExample: WizardExampleComponent) {
+ this.wizardExample = wizardExample;
+ }
+
+ ngOnInit(): void {
+ // Step 1
+ this.step1Config = {
+ id: 'step1',
+ priority: 0,
+ title: 'First Step'
+ } as WizardStepConfig;
+ this.step1aConfig = {
+ id: 'step1a',
+ expandReviewDetails: true,
+ nextEnabled: false,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step1bConfig = {
+ id: 'step1b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 2
+ this.step2Config = {
+ id: 'step2',
+ priority: 0,
+ title: 'Second Step'
+ } as WizardStepConfig;
+ this.step2aConfig = {
+ id: 'step2a',
+ expandReviewDetails: true,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step2bConfig = {
+ id: 'step2b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 3
+ this.step3Config = {
+ id: 'step3',
+ priority: 2,
+ title: 'Review'
+ } as WizardStepConfig;
+ this.step3aConfig = {
+ id: 'step3a',
+ priority: 0,
+ title: 'Summary'
+ } as WizardStepConfig;
+ this.step3bConfig = {
+ id: 'step3b',
+ priority: 1,
+ title: 'Deploy'
+ } as WizardStepConfig;
+
+ // Wizard
+ this.wizardConfig = {
+ title: 'Wizard Title',
+ sidebarStyleClass: 'example-wizard-sidebar',
+ stepStyleClass: 'example-wizard-step'
+ } as WizardConfig;
+
+ this.setNavAway(false);
+ }
+
+ // Methods
+
+ nextClicked($event: WizardEvent): void {
+ if ($event.step.config.id === 'step3b') {
+ this.wizardExample.closeModal($event);
+ }
+ }
+
+ startDeploy(): void {
+ this.deployComplete = false;
+ this.wizardConfig.done = true;
+
+ // Simulate a delay
+ setTimeout(() => {
+ this.deployComplete = true;
+ }, 2500);
+ }
+
+ stepChanged($event: WizardEvent) {
+ if ($event.step.config.id === 'step1a') {
+ this.setNavAway(false);
+ } else if ($event.step.config.id === 'step3a') {
+ this.wizardConfig.nextTitle = 'Deploy';
+ } else if ($event.step.config.id === 'step3b') {
+ this.wizardConfig.nextTitle = 'Close';
+ } else {
+ this.wizardConfig.nextTitle = 'Next >';
+ }
+ }
+
+ updateName(): void {
+ this.step1aConfig.nextEnabled = (this.data.name !== undefined && this.data.name.length > 0);
+ this.setNavAway(this.step1aConfig.nextEnabled);
+ }
+
+ // Private
+
+ private setNavAway(allow: boolean) {
+ this.step1aConfig.allowNavAway = allow;
+
+ this.step1Config.allowClickNav = allow;
+ this.step1aConfig.allowClickNav = allow;
+ this.step1bConfig.allowClickNav = allow;
+
+ this.step2Config.allowClickNav = allow;
+ this.step2aConfig.allowClickNav = allow;
+ this.step2bConfig.allowClickNav = allow;
+
+ this.step3Config.allowClickNav = allow;
+ this.step3aConfig.allowClickNav = allow;
+ this.step3bConfig.allowClickNav = allow;
+ }
+}
diff --git a/src/app/wizard/examples/wizard-embed-example.component.html b/src/app/wizard/examples/wizard-embed-example.component.html
new file mode 100644
index 000000000..20522ae20
--- /dev/null
+++ b/src/app/wizard/examples/wizard-embed-example.component.html
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Deployment in progress
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
Deployment was successful
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
View Deployment
+
+
+
+
+
+
+
+
+
+ Name:
+ {{data.name}}
+
+
+ Description:
+ {{data.description}}
+
+
+
+
+
+
+
+ Lorem:
+ {{data.lorem}}
+
+
+ Ipsum:
+ {{data.ipsum}}
+
+
+
+
+
+
+
+ Aliquam:
+ {{data.aliquam}}
+
+
+ Fermentum:
+ {{data.fermentum}}
+
+
+
+
+
+
+
+ Consectetur:
+ {{data.consectetur}}
+
+
+ Adipiscing:
+ {{data.adipiscing}}
+
+
+
diff --git a/src/app/wizard/examples/wizard-embed-example.component.ts b/src/app/wizard/examples/wizard-embed-example.component.ts
new file mode 100644
index 000000000..8c5f7dcf4
--- /dev/null
+++ b/src/app/wizard/examples/wizard-embed-example.component.ts
@@ -0,0 +1,172 @@
+import {
+ Component,
+ Host,
+ OnInit,
+ ViewChild,
+ ViewEncapsulation,
+} from '@angular/core';
+
+import { WizardConfig } from '../wizard-config';
+import { WizardComponent } from '../wizard.component';
+import { WizardExampleComponent } from './wizard-example.component';
+import { WizardEvent } from '../wizard-event';
+import { WizardStepConfig } from '../wizard-step-config';
+
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'wizard-embed-example',
+ templateUrl: './wizard-embed-example.component.html'
+})
+export class WizardEmbedExampleComponent implements OnInit {
+ @ViewChild('wizard') wizard: WizardComponent;
+
+ data: any = {};
+ deployComplete: boolean = true;
+
+ // Wizard Step 1
+ step1Config: WizardStepConfig;
+ step1aConfig: WizardStepConfig;
+ step1bConfig: WizardStepConfig;
+
+ // Wizard Step 2
+ step2Config: WizardStepConfig;
+ step2aConfig: WizardStepConfig;
+ step2bConfig: WizardStepConfig;
+
+ // Wizard Step 3
+ step3Config: WizardStepConfig;
+ step3aConfig: WizardStepConfig;
+ step3bConfig: WizardStepConfig;
+
+ // Wizard
+ wizardConfig: WizardConfig;
+ wizardExample: WizardExampleComponent;
+
+ constructor(@Host() wizardExample: WizardExampleComponent) {
+ this.wizardExample = wizardExample;
+ }
+
+ ngOnInit(): void {
+ // Step 1
+ this.step1Config = {
+ id: 'step1',
+ priority: 0,
+ title: 'First Step'
+ } as WizardStepConfig;
+ this.step1aConfig = {
+ id: 'step1a',
+ expandReviewDetails: true,
+ nextEnabled: false,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step1bConfig = {
+ id: 'step1b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 2
+ this.step2Config = {
+ id: 'step2',
+ priority: 0,
+ title: 'Second Step'
+ } as WizardStepConfig;
+ this.step2aConfig = {
+ id: 'step2a',
+ expandReviewDetails: true,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step2bConfig = {
+ id: 'step2b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 3
+ this.step3Config = {
+ id: 'step3',
+ priority: 2,
+ title: 'Review'
+ } as WizardStepConfig;
+ this.step3aConfig = {
+ id: 'step3a',
+ priority: 0,
+ title: 'Summary'
+ } as WizardStepConfig;
+ this.step3bConfig = {
+ id: 'step3b',
+ priority: 1,
+ title: 'Deploy'
+ } as WizardStepConfig;
+
+ // Wizard
+ this.wizardConfig = {
+ embedInPage: true,
+ loadingTitle: 'Wizard loading',
+ loadingSecondaryInfo: 'ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu.',
+ title: 'Wizard Title',
+ sidebarStyleClass: 'example-wizard-sidebar',
+ stepStyleClass: 'example-wizard-step'
+ } as WizardConfig;
+
+ this.setNavAway(false);
+ }
+
+ // Methods
+
+ nextClicked($event: WizardEvent): void {
+ if ($event.step.config.id === 'step3b') {
+ this.wizardExample.closeModal($event);
+ }
+ }
+
+ startDeploy(): void {
+ this.deployComplete = false;
+ this.wizardConfig.done = true;
+
+ // Simulate a delay
+ setTimeout(() => {
+ this.deployComplete = true;
+ }, 2500);
+ }
+
+ stepChanged($event: WizardEvent) {
+ if ($event.step.config.id === 'step1a') {
+ this.setNavAway(false);
+ } else if ($event.step.config.id === 'step3a') {
+ this.wizardConfig.nextTitle = 'Deploy';
+ } else if ($event.step.config.id === 'step3b') {
+ // Note: The next button is not disabled by default when wizard is done
+ this.step3Config.nextEnabled = false;
+ } else {
+ this.wizardConfig.nextTitle = 'Next >';
+ }
+ }
+
+ updateName(): void {
+ this.step1aConfig.nextEnabled = (this.data.name !== undefined && this.data.name.length > 0);
+ this.setNavAway(this.step1aConfig.nextEnabled);
+ }
+
+ // Private
+
+ private setNavAway(allow: boolean) {
+ this.step1aConfig.allowNavAway = allow;
+
+ this.step1Config.allowClickNav = allow;
+ this.step1aConfig.allowClickNav = allow;
+ this.step1bConfig.allowClickNav = allow;
+
+ this.step2Config.allowClickNav = allow;
+ this.step2aConfig.allowClickNav = allow;
+ this.step2bConfig.allowClickNav = allow;
+
+ this.step3Config.allowClickNav = allow;
+ this.step3aConfig.allowClickNav = allow;
+ this.step3bConfig.allowClickNav = allow;
+ }
+}
diff --git a/src/app/wizard/examples/wizard-example.component.css b/src/app/wizard/examples/wizard-example.component.css
new file mode 100644
index 000000000..50e41b69f
--- /dev/null
+++ b/src/app/wizard/examples/wizard-example.component.css
@@ -0,0 +1,2 @@
+.example-wizard-sidebar,.example-wizard-step{height:500px;max-height:500px;overflow-y:auto}
+/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC93aXphcmQvZXhhbXBsZXMvd2l6YXJkLWV4YW1wbGUuY29tcG9uZW50Lmxlc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxhQUFBO0VBQ0EsaUJBQUE7RUFDQSxnQkFBQTs7QUFFRjtFQUNFLGFBQUE7RUFDQSxpQkFBQTtFQUNBLGdCQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLmV4YW1wbGUtd2l6YXJkLXNpZGViYXIge1xuICBoZWlnaHQ6IDUwMHB4O1xuICBtYXgtaGVpZ2h0OiA1MDBweDtcbiAgb3ZlcmZsb3cteTogYXV0bztcbn1cbi5leGFtcGxlLXdpemFyZC1zdGVwIHtcbiAgaGVpZ2h0OiA1MDBweDtcbiAgbWF4LWhlaWdodDogNTAwcHg7XG4gIG92ZXJmbG93LXk6IGF1dG87XG59XG4iXSwiZmlsZSI6ImFwcC93aXphcmQvZXhhbXBsZXMvd2l6YXJkLWV4YW1wbGUuY29tcG9uZW50LmNzcyJ9 */
diff --git a/src/app/wizard/examples/wizard-example.component.html b/src/app/wizard/examples/wizard-example.component.html
new file mode 100644
index 000000000..0d51f4a44
--- /dev/null
+++ b/src/app/wizard/examples/wizard-example.component.html
@@ -0,0 +1,109 @@
+
+
+
+
Wizard Component Example
+
+
+
+
+
+
+
+
+
+ Launch Wizard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/wizard/examples/wizard-example.component.less b/src/app/wizard/examples/wizard-example.component.less
new file mode 100644
index 000000000..497a3ddca
--- /dev/null
+++ b/src/app/wizard/examples/wizard-example.component.less
@@ -0,0 +1,15 @@
+@import (reference) "../../../assets/stylesheets/patternfly-ng";
+
+.example-wizard-sidebar {
+ height: 500px;
+ max-height: 500px;
+ overflow-y: auto;
+}
+.example-wizard-step {
+ height: 500px;
+ max-height: 500px;
+ overflow-y: auto;
+}
+.example-wizard-embed {
+ border: 1px solid @color-pf-black-300;
+}
diff --git a/src/app/wizard/examples/wizard-example.component.ts b/src/app/wizard/examples/wizard-example.component.ts
new file mode 100644
index 000000000..82a2013d9
--- /dev/null
+++ b/src/app/wizard/examples/wizard-example.component.ts
@@ -0,0 +1,46 @@
+import {
+ Component,
+ OnInit,
+ TemplateRef,
+ ViewChild,
+ ViewEncapsulation,
+} from '@angular/core';
+
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { BsModalRef } from 'ngx-bootstrap/modal/modal-options.class';
+import { TabDirective } from 'ngx-bootstrap/tabs';
+
+import { WizardEvent } from '../wizard-event';
+
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'wizard-example',
+ styleUrls: ['./wizard-example.component.less'],
+ templateUrl: './wizard-example.component.html'
+})
+export class WizardExampleComponent implements OnInit {
+ @ViewChild('wizardTemplate') wizardTemplate: TemplateRef;
+
+ activeTab: string = '';
+ modalRef: BsModalRef;
+
+ constructor(private modalService: BsModalService) {
+ }
+
+ ngOnInit(): void {
+ }
+
+ // Methods
+
+ closeModal($event: WizardEvent): void {
+ this.modalRef.hide();
+ }
+
+ openModal(template: TemplateRef): void {
+ this.modalRef = this.modalService.show(template, {class: 'modal-lg'});
+ }
+
+ tabSelected($event: TabDirective): void {
+ this.activeTab = $event.heading;
+ }
+}
diff --git a/src/app/wizard/examples/wizard-example.module.ts b/src/app/wizard/examples/wizard-example.module.ts
new file mode 100644
index 000000000..d7b948fc2
--- /dev/null
+++ b/src/app/wizard/examples/wizard-example.module.ts
@@ -0,0 +1,34 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { ModalModule } from 'ngx-bootstrap/modal';
+import { TabsModule, TabsetConfig } from 'ngx-bootstrap/tabs';
+
+import { DemoComponentsModule } from '../../../demo/components/demo-components.module';
+import { WizardBasicExampleComponent } from './wizard-basic-example.component';
+import { WizardEmbedExampleComponent } from './wizard-embed-example.component';
+import { WizardNavExampleComponent } from './wizard-nav-example.component';
+import { WizardExampleComponent } from './wizard-example.component';
+import { WizardModule } from '../wizard.module';
+
+@NgModule({
+ declarations: [
+ WizardBasicExampleComponent,
+ WizardEmbedExampleComponent,
+ WizardNavExampleComponent,
+ WizardExampleComponent
+ ],
+ imports: [
+ CommonModule,
+ DemoComponentsModule,
+ FormsModule,
+ ModalModule.forRoot(),
+ TabsModule.forRoot(),
+ WizardModule
+ ],
+ providers: [ TabsetConfig ]
+})
+export class WizardExampleModule {
+ constructor() {}
+}
diff --git a/src/app/wizard/examples/wizard-nav-example.component.html b/src/app/wizard/examples/wizard-nav-example.component.html
new file mode 100644
index 000000000..461b03b5c
--- /dev/null
+++ b/src/app/wizard/examples/wizard-nav-example.component.html
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
Fetching data...
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Deployment in progress
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
+
+
+
Deployment was successful
+
Lorem ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu eget nunc amet.
+
View Deployment
+
+
+
+
+
+
+
+
+
+ Name:
+ {{data.name}}
+
+
+ Description:
+ {{data.description}}
+
+
+
+
+
+
+
+ Lorem:
+ {{data.lorem}}
+
+
+ Ipsum:
+ {{data.ipsum}}
+
+
+
+
+
+
+
+ Aliquam:
+ {{data.aliquam}}
+
+
+ Fermentum:
+ {{data.fermentum}}
+
+
+
+
+
+
+
+ Consectetur:
+ {{data.consectetur}}
+
+
+ Adipiscing:
+ {{data.adipiscing}}
+
+
+
diff --git a/src/app/wizard/examples/wizard-nav-example.component.ts b/src/app/wizard/examples/wizard-nav-example.component.ts
new file mode 100644
index 000000000..2f2f7bc84
--- /dev/null
+++ b/src/app/wizard/examples/wizard-nav-example.component.ts
@@ -0,0 +1,186 @@
+import {
+ Component,
+ Host,
+ OnInit,
+ ViewChild,
+ ViewEncapsulation,
+} from '@angular/core';
+
+import { WizardConfig } from '../wizard-config';
+import { WizardComponent } from '../wizard.component';
+import { WizardExampleComponent } from './wizard-example.component';
+import { WizardEvent } from '../wizard-event';
+import { WizardStepConfig } from '../wizard-step-config';
+
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'wizard-nav-example',
+ templateUrl: './wizard-nav-example.component.html'
+})
+export class WizardNavExampleComponent implements OnInit {
+ @ViewChild('wizard') wizard: WizardComponent;
+
+ data: any = {};
+ deployComplete: boolean = true;
+ fetchComplete: boolean = true;
+
+ // Wizard Step 1
+ step1Config: WizardStepConfig;
+ step1aConfig: WizardStepConfig;
+ step1bConfig: WizardStepConfig;
+
+ // Wizard Step 2
+ step2Config: WizardStepConfig;
+ step2aConfig: WizardStepConfig;
+ step2bConfig: WizardStepConfig;
+
+ // Wizard Step 3
+ step3Config: WizardStepConfig;
+ step3aConfig: WizardStepConfig;
+ step3bConfig: WizardStepConfig;
+
+ // Wizard
+ wizardConfig: WizardConfig;
+ wizardExample: WizardExampleComponent;
+
+ constructor(@Host() wizardExample: WizardExampleComponent) {
+ this.wizardExample = wizardExample;
+ }
+
+ ngOnInit(): void {
+ // Step 1
+ this.step1Config = {
+ id: 'step1',
+ priority: 0,
+ title: 'First Step'
+ } as WizardStepConfig;
+ this.step1aConfig = {
+ id: 'step1a',
+ expandReviewDetails: true,
+ nextEnabled: false,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step1bConfig = {
+ id: 'step1b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 2
+ this.step2Config = {
+ id: 'step2',
+ priority: 0,
+ title: 'Second Step'
+ } as WizardStepConfig;
+ this.step2aConfig = {
+ id: 'step2a',
+ expandReviewDetails: true,
+ priority: 0,
+ title: 'Details'
+ } as WizardStepConfig;
+ this.step2bConfig = {
+ id: 'step2b',
+ expandReviewDetails: true,
+ priority: 1,
+ title: 'Settings'
+ } as WizardStepConfig;
+
+ // Step 3
+ this.step3Config = {
+ id: 'step3',
+ priority: 2,
+ title: 'Review'
+ } as WizardStepConfig;
+ this.step3aConfig = {
+ id: 'step3a',
+ priority: 0,
+ title: 'Summary'
+ } as WizardStepConfig;
+ this.step3bConfig = {
+ id: 'step3b',
+ priority: 1,
+ title: 'Deploy'
+ } as WizardStepConfig;
+
+ // Wizard
+ this.wizardConfig = {
+ loadingTitle: 'Wizard loading',
+ loadingSecondaryInfo: 'ipsum dolor sit amet, porta at suspendisse ac, ut wisi vivamus, lorem sociosqu.',
+ title: 'Wizard Title',
+ ready: false,
+ sidebarStyleClass: 'example-wizard-sidebar',
+ stepStyleClass: 'example-wizard-step'
+ } as WizardConfig;
+
+ // Simulate a delay
+ setTimeout(() => {
+ this.wizardConfig.ready = true;
+ }, 2500);
+
+ this.setNavAway(false);
+ }
+
+ // Methods
+
+ nextClicked($event: WizardEvent): void {
+ if ($event.step.config.id === 'step1a') {
+ this.fetchComplete = false;
+ this.setNavAway(false);
+
+ // Simulate a delay
+ setTimeout(() => {
+ this.fetchComplete = true;
+ this.setNavAway(true);
+ this.wizard.goToNextStep();
+ }, 2500);
+ } else if ($event.step.config.id === 'step3b') {
+ this.wizardExample.closeModal($event);
+ }
+ }
+
+ startDeploy(): void {
+ this.deployComplete = false;
+ this.wizardConfig.done = true;
+
+ // Simulate a delay
+ setTimeout(() => {
+ this.deployComplete = true;
+ }, 2500);
+ }
+
+ stepChanged($event: WizardEvent) {
+ if ($event.step.config.id === 'step1a') {
+ this.setNavAway(false);
+ } else if ($event.step.config.id === 'step3a') {
+ this.wizardConfig.nextTitle = 'Deploy';
+ } else if ($event.step.config.id === 'step3b') {
+ this.wizardConfig.nextTitle = 'Close';
+ } else {
+ this.wizardConfig.nextTitle = 'Next >';
+ }
+ }
+
+ updateName(): void {
+ this.step1aConfig.nextEnabled = (this.data.name !== undefined && this.data.name.length > 0);
+ }
+
+ // Private
+
+ private setNavAway(allow: boolean) {
+ this.step1aConfig.allowNavAway = allow;
+
+ this.step1Config.allowClickNav = allow;
+ this.step1aConfig.allowClickNav = allow;
+ this.step1bConfig.allowClickNav = allow;
+
+ this.step2Config.allowClickNav = allow;
+ this.step2aConfig.allowClickNav = allow;
+ this.step2bConfig.allowClickNav = allow;
+
+ this.step3Config.allowClickNav = allow;
+ this.step3aConfig.allowClickNav = allow;
+ this.step3bConfig.allowClickNav = allow;
+ }
+}
diff --git a/src/app/wizard/wizard-base.ts b/src/app/wizard/wizard-base.ts
new file mode 100644
index 000000000..63a520d75
--- /dev/null
+++ b/src/app/wizard/wizard-base.ts
@@ -0,0 +1,150 @@
+import { WizardStep } from './wizard-step';
+
+import { find } from 'lodash';
+
+/**
+ * A base class with common functionality for wizard and wizard-step
+ */
+export class WizardBase {
+ private _selectedStep: WizardStep;
+ private _steps: WizardStep[] = [];
+
+ /**
+ * The default constructor
+ */
+ constructor() {}
+
+ // Accessors
+
+ /**
+ * Returns the selected wizard step or substep
+ *
+ * @returns {WizardStep} The wizard step or substep
+ */
+ get selectedStep(): WizardStep {
+ return this._selectedStep;
+ }
+
+ /**
+ * Returns the selected wizard step or substep number
+ *
+ * @returns {number} The step index
+ */
+ get selectedStepIndex(): number {
+ // Retrieve selected step number
+ return this.stepIndex(this.selectedStep) + 1;
+ }
+
+ /**
+ * Set the selected wizard step or substep for this component
+ *
+ * @param {WizardStep} step The wizard step or substep
+ */
+ set selectedStep(step: WizardStep) {
+ this._selectedStep = step;
+ }
+
+ /**
+ * Returns the wizard steps or substeps for this component
+ *
+ * @returns {WizardStep[]} The wizard steps or substeps
+ */
+ get steps(): WizardStep[] {
+ return this._steps;
+ }
+
+ /**
+ * Set the wizard steps or substeps for this component
+ *
+ * @param {WizardStep[]} steps The wizard steps or substeps
+ */
+ set steps(steps: WizardStep[]) {
+ this._steps = steps;
+ }
+
+ // Methods
+
+ /**
+ * Add a wizard step or substep to this component
+ *
+ * @param {WizardStep} step The wizard step or substep to add
+ */
+ addStep(step: WizardStep): void {
+ // Insert the step into step array
+ let insertBefore = find(this.steps, (nextStep) => {
+ return nextStep.config.priority > step.config.priority;
+ });
+ if (insertBefore) {
+ this.steps.splice(this.steps.indexOf(insertBefore), 0, step);
+ } else {
+ this.steps.push(step);
+ }
+ }
+
+ /**
+ * Returns only enabled wizard steps
+ *
+ * @returns {WizardStep[]} The wizard stepd or substepd
+ */
+ protected getEnabledSteps(): WizardStep[] {
+ return this.steps.filter((step: WizardStep) => {
+ return (step.config.disabled !== true);
+ });
+ }
+
+ /**
+ * Returns the step index for the given wizard step or substep
+ *
+ * @param {WizardStep} step The wizard step or substep
+ * @returns {number} The step number
+ */
+ getStepIndex(step: WizardStep): number {
+ return this.stepIndex(step) + 1;
+ }
+
+ /**
+ * Returns the wizard step or substep for the given title
+ *
+ * @param {string} title The title to find
+ * @returns {WizardStep} The wizard step or substep
+ */
+ protected stepByTitle(title: string): WizardStep {
+ let foundStep;
+ this.getEnabledSteps().forEach((step: WizardStep) => {
+ if (step.config.title === title) {
+ foundStep = step;
+ }
+ });
+ return foundStep;
+ }
+
+ /**
+ * Returns the index for the given wizard step or substep
+ *
+ * @param {WizardStep} step The wizard step or substep
+ * @returns {number} The wizard step or substep index
+ */
+ protected stepIndex(step: WizardStep): number {
+ let idx = 0;
+ let res = -1;
+ this.getEnabledSteps().forEach((currStep) => {
+ if (currStep === step) {
+ res = idx;
+ }
+ idx++;
+ });
+ return res;
+ }
+
+ /**
+ * Unselect all wizard steps and substeps
+ */
+ protected unselectAll(): void {
+ // Traverse steps array and set each "selected" property to false
+ this.getEnabledSteps().forEach((step: WizardStep) => {
+ step.selected = false;
+ });
+ // Set selectedStep variable to null
+ this.selectedStep = null;
+ }
+}
diff --git a/src/app/wizard/wizard-config.ts b/src/app/wizard/wizard-config.ts
new file mode 100644
index 000000000..66ff0fb2b
--- /dev/null
+++ b/src/app/wizard/wizard-config.ts
@@ -0,0 +1,92 @@
+/**
+ * A config containing properties for wizard
+ */
+export class WizardConfig {
+ /**
+ * The text to display on the cancel button
+ */
+ cancelTitle?: string;
+
+ /**
+ * The height the wizard content should be set to. This is used only if the stepStyleClass is not given.
+ * The default is 300px.
+ */
+ contentHeight?: string;
+
+ /**
+ * The current step can be changed externally - this is the title of the step to switch the wizard to
+ */
+ currentStep?: string;
+
+ /**
+ * Flag indicating that the wizard is done
+ */
+ done?: boolean;
+
+ /**
+ * Flag indicating that the wizard is embedded in a page (not a modal). This moves the navigation buttons to the left
+ * hand side of the footer and removes the close button.
+ */
+ embedInPage?: boolean;
+
+ /**
+ * Flag indicating to hide the step indicators in the header of the wizard
+ */
+ hideIndicators?: boolean;
+
+ /**
+ * Flag indicating to hide page navigation sidebar on the wizard pages
+ */
+ hideSidebar?: boolean;
+
+ /**
+ * Flag indicating to hide the title bar. Default is false
+ */
+ hideHeader?: boolean;
+
+ /**
+ * Flag indicating to hide the back button, useful in 2 step wizards. Default is false
+ */
+ hidePreviousButton?: boolean;
+
+ /**
+ * The text displayed when the wizard is loading
+ */
+ loadingTitle?: string;
+
+ /**
+ * Secondary descriptive information to display when the wizard is loading
+ */
+ loadingSecondaryInfo?: string;
+
+ /**
+ * The text to display on the next button
+ */
+ nextTitle?: string;
+
+ /**
+ * The text to display on the back button
+ */
+ previousTitle?: string;
+
+ /**
+ * Flag indicating that the wizard is ready
+ */
+ ready?: boolean;
+
+ /**
+ * CSS class to be give to the sidebar panel. Only used if the stepClass is also provided
+ */
+ sidebarStyleClass?: string;
+
+ /**
+ * CSS class to be given to the steps page container. Used for the sidebar panel as well unless
+ * sidebarStyleClass is provided
+ */
+ stepStyleClass?: string;
+
+ /**
+ * The wizard title displayed in the header
+ */
+ title: string;
+}
diff --git a/src/app/wizard/wizard-event.ts b/src/app/wizard/wizard-event.ts
new file mode 100644
index 000000000..65eecf71e
--- /dev/null
+++ b/src/app/wizard/wizard-event.ts
@@ -0,0 +1,16 @@
+import { WizardStep } from './wizard-step';
+
+/**
+ * An object containing properties for wizard events
+ */
+export class WizardEvent {
+ /**
+ * The order of the wizard step of substep
+ */
+ index?: number;
+
+ /**
+ * The current wizard step or substep
+ */
+ step: WizardStep;
+}
diff --git a/src/app/wizard/wizard-review.component.html b/src/app/wizard/wizard-review.component.html
new file mode 100644
index 000000000..41db25123
--- /dev/null
+++ b/src/app/wizard/wizard-review.component.html
@@ -0,0 +1,34 @@
+
diff --git a/src/app/wizard/wizard-review.component.ts b/src/app/wizard/wizard-review.component.ts
new file mode 100644
index 000000000..a3697206e
--- /dev/null
+++ b/src/app/wizard/wizard-review.component.ts
@@ -0,0 +1,73 @@
+import {
+ Component,
+ Host,
+ OnInit,
+ ViewEncapsulation
+} from '@angular/core';
+
+import { WizardComponent } from './wizard.component';
+import { WizardStepComponent } from './wizard-step.component';
+import { WizardSubstepComponent } from './wizard-substep.component';
+import { WizardStep } from './wizard-step';
+
+/**
+ * Wizard review component
+ *
+ * Note: This component is expected to be direct descendant of wizard-step or wizard-substep.
+ */
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'pfng-wizard-review',
+ templateUrl: './wizard-review.component.html'
+})
+export class WizardReviewComponent implements OnInit {
+ private wizard: WizardComponent;
+
+ /**
+ * The default constructor
+ */
+ constructor(@Host() wizard: WizardComponent) {
+ this.wizard = wizard;
+ }
+
+ // Initialization
+
+ /**
+ * Setup component configuration upon initialization
+ */
+ ngOnInit(): void {
+ }
+
+ // Methods
+
+ /**
+ * Returns only wizard steps with review templates
+ *
+ * @returns {WizardStep[]} The wizard stepd or substepd
+ */
+ getReviewSteps(): WizardStep[] {
+ return this.wizard.getReviewSteps();
+ }
+
+ // Private
+
+ // Returns the step number for the given wizard step and substep
+ private getSubstepNumber(step: WizardStepComponent, substep: WizardSubstepComponent) {
+ return step.getDisplayNumber(substep);
+ }
+
+ // Returns only wizard steps with review templates
+ private getReviewSubsteps(step: WizardStepComponent): WizardStep[] {
+ return step.getReviewSteps();
+ }
+
+ // Toggles the review step control
+ private toggleReview(step: WizardStep): void {
+ step.config.expandReview = !step.config.expandReview;
+ };
+
+ // Toggles the review details control
+ private toggleReviewDetails(step: WizardStep): void {
+ step.config.expandReviewDetails = !step.config.expandReviewDetails;
+ };
+}
diff --git a/src/app/wizard/wizard-step-config.ts b/src/app/wizard/wizard-step-config.ts
new file mode 100644
index 000000000..8a512866b
--- /dev/null
+++ b/src/app/wizard/wizard-step-config.ts
@@ -0,0 +1,79 @@
+/**
+ * A config containing properties for wizard steps and substeps
+ */
+export class WizardStepConfig {
+ /**
+ * Indicates that the user can click on numeric step indicators to navigate directly to a step
+ */
+ allowClickNav?: boolean;
+
+ /**
+ * Indicates that the user may navigate away from this wizard step or substep
+ */
+ allowNavAway?: boolean;
+
+ /**
+ * Indicates that this step has been completed
+ */
+ completed?: boolean;
+
+ /**
+ * The wizard step or substep description
+ */
+ description?: string;
+
+ /**
+ * Indicates that the wizard is disabled
+ */
+ disabled?: boolean;
+
+ /**
+ * Indicates whether review information should be expanded by default when the review step is reached
+ */
+ expandReview?: boolean;
+
+ /**
+ * Indicators whether review details information should be expanded by default when the review step is reached
+ */
+ expandReviewDetails?: boolean;
+
+ /**
+ * The wizard step or substep identifier
+ */
+ id?: string;
+
+ /**
+ * Indicates the priority of this wizard step or substep relative to other wizard steps. Steps are expected to be
+ * numbered sequentially in the order they should be viewed.
+ */
+ priority?: number;
+
+ /**
+ * Indicates that the next button is enabled when the wizard step or substep is displayed
+ */
+ nextEnabled?: boolean;
+
+ /**
+ * The text to display as a tooltip for the next button when the wizard step is displayed.
+ *
+ * Not applicable for the wizard-substep component.
+ */
+ nextTooltip?: string;
+
+ /**
+ * Indicates that the previous button is enabled when the wizard step or substep is displayed
+ */
+ previousEnabled?: boolean;
+
+ /**
+ * The text to display as a tooltip for the previous button when the wizard step is displayed
+ *
+ * Not applicable for the wizard-substep component.
+ */
+ previousTooltip?: string;
+
+ /**
+ * The title for the wizard step or substep to be displayed in the header and review page
+ */
+ title: string;
+}
diff --git a/src/app/wizard/wizard-step.component.css b/src/app/wizard/wizard-step.component.css
new file mode 100644
index 000000000..4f53717c7
--- /dev/null
+++ b/src/app/wizard/wizard-step.component.css
@@ -0,0 +1,2 @@
+.pfng-wizard-single-step{margin-left:0}
+/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC93aXphcmQvd2l6YXJkLXN0ZXAuY29tcG9uZW50Lmxlc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxjQUFBIiwic291cmNlc0NvbnRlbnQiOlsiQGltcG9ydCAocmVmZXJlbmNlKSBcIi4uLy4uL2Fzc2V0cy9zdHlsZXNoZWV0cy9wYXR0ZXJuZmx5LW5nXCI7XG5cbi5wZm5nLXdpemFyZC1zaW5nbGUtc3RlcCB7XG4gIG1hcmdpbi1sZWZ0OiAwO1xufVxuIl0sImZpbGUiOiJhcHAvd2l6YXJkL3dpemFyZC1zdGVwLmNvbXBvbmVudC5jc3MifQ== */
diff --git a/src/app/wizard/wizard-step.component.html b/src/app/wizard/wizard-step.component.html
new file mode 100644
index 000000000..8b52bd895
--- /dev/null
+++ b/src/app/wizard/wizard-step.component.html
@@ -0,0 +1,24 @@
+
diff --git a/src/app/wizard/wizard-step.component.less b/src/app/wizard/wizard-step.component.less
new file mode 100644
index 000000000..668213815
--- /dev/null
+++ b/src/app/wizard/wizard-step.component.less
@@ -0,0 +1,5 @@
+@import (reference) "../../assets/stylesheets/patternfly-ng";
+
+.pfng-wizard-single-step {
+ margin-left: 0;
+}
diff --git a/src/app/wizard/wizard-step.component.ts b/src/app/wizard/wizard-step.component.ts
new file mode 100644
index 000000000..5dbc5079b
--- /dev/null
+++ b/src/app/wizard/wizard-step.component.ts
@@ -0,0 +1,325 @@
+import {
+ Component,
+ EventEmitter,
+ Host,
+ Input,
+ OnInit,
+ Output,
+ TemplateRef,
+ ViewEncapsulation
+} from '@angular/core';
+
+import { WizardBase } from './wizard-base';
+import { WizardComponent } from './wizard.component';
+import { WizardEvent } from './wizard-event';
+import { WizardStep } from './wizard-step';
+import { WizardStepConfig } from './wizard-step-config';
+
+import { cloneDeep, defaults, isEqual } from 'lodash';
+
+/**
+ * Wizard step component. Each step can stand alone or have substeps.
+ *
+ * Note: This component is expected to be a child of wizard.
+ */
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'pfng-wizard-step',
+ styleUrls: ['./wizard-step.component.less'],
+ templateUrl: './wizard-step.component.html'
+})
+export class WizardStepComponent extends WizardBase implements OnInit, WizardStep {
+ /**
+ * The wizard step config containing component properties
+ */
+ @Input() config: WizardStepConfig;
+
+ /**
+ * The wizard step template used for the review details screen
+ */
+ @Input() reviewTemplate: TemplateRef;
+
+ /**
+ * The event emitted when this wizard step is shown
+ */
+ @Output('onShow') onShow = new EventEmitter();
+
+ private defaultConfig = {
+ allowClickNav: true,
+ allowNavAway: true,
+ completed: false,
+ data: {},
+ disabled: false,
+ expandReview: true,
+ expandReviewDetails: false,
+ priority: 999,
+ nextEnabled: true,
+ title: ''
+ } as WizardStepConfig;
+ private init: boolean = true;
+ private pageIndex: number = 0;
+ private prevConfig: WizardStepConfig;
+ private _selected: boolean = false;
+ private wizard: WizardComponent;
+
+ /**
+ * The default constructor
+ */
+ constructor(@Host() wizard: WizardComponent) {
+ super();
+ this.wizard = wizard;
+ }
+
+ // Initialization
+
+ /**
+ * Setup component configuration upon initialization
+ */
+ ngOnInit(): void {
+ this.setupConfig();
+ if (this.wizard !== undefined && this.selectedStep === undefined) {
+ this.wizard.addStep(this);
+ }
+ }
+
+ /**
+ * Check if the component config has changed
+ */
+ ngDoCheck(): void {
+ // Do a deep compare on config
+ if (!isEqual(this.config, this.prevConfig)) {
+ this.setupConfig();
+ }
+ if (this.wizard !== undefined) {
+ this.pageIndex = this.wizard.getStepIndex(this);
+ }
+ }
+
+ /**
+ * Set up default config
+ */
+ protected setupConfig(): void {
+ if (this.config !== undefined) {
+ defaults(this.config, this.defaultConfig);
+ } else {
+ this.config = cloneDeep(this.defaultConfig);
+ }
+ this.prevConfig = cloneDeep(this.config);
+ }
+
+ // Accessors
+
+ /**
+ * Indicates that this wizard step has substeps
+ *
+ * @returns {boolean} true if this wizard step has substeps
+ */
+ get hasSubsteps() {
+ return this.steps.length > 0;
+ }
+
+ /**
+ * Indicates that the next button is enabled
+ *
+ * @returns {boolean} true if the next button is enabled
+ */
+ get nextEnabled() {
+ let enabled = this.config.nextEnabled;
+ if (this.hasSubsteps) {
+ this.getEnabledSteps().forEach((step: WizardStep) => {
+ enabled = enabled && step.config.nextEnabled;
+ });
+ }
+ return enabled;
+ }
+
+ /**
+ * Indicates that the previous button is enabled
+ *
+ * @returns {boolean} true if the previous button is enabled
+ */
+ get previousEnabled() {
+ let enabled = this.config.previousEnabled;
+ if (this.hasSubsteps) {
+ this.getEnabledSteps().forEach((step: WizardStep) => {
+ enabled = enabled && step.config.previousEnabled;
+ });
+ }
+ return enabled;
+ }
+
+ /**
+ * Indicates that this wizard step is selected
+ *
+ * @returns {boolean} True if this wizard step is selected
+ */
+ get selected() {
+ return this._selected;
+ }
+
+ /**
+ * Sets a flag indicating that this wizard step is selected
+ *
+ * @param {boolean} selected True if this wizard step is selected
+ */
+ set selected(selected: boolean) {
+ this._selected = selected;
+ }
+
+ // Methods
+
+ /**
+ * Returns the step number to be displayed for the given wizard step or substep
+ *
+ * @param {WizardStep} step The wizard step or substep
+ * @returns {string} The step number to be displayed
+ */
+ getDisplayNumber(step: WizardStep): string {
+ return this.pageIndex + String.fromCharCode(65 + this.stepIndex(step)) + '.';
+ }
+
+ /**
+ * Returns only wizard substeps with review templates
+ *
+ * @returns {WizardStep[]} The wizard stepd or substepd
+ */
+ getReviewSteps(): WizardStep[] {
+ let reviewSteps = this.getEnabledSteps().filter((step: WizardStep) => {
+ return (step.reviewTemplate !== undefined);
+ });
+ return reviewSteps;
+ }
+
+ /**
+ * Navigate to the first wizard substep
+ */
+ goToFirstStep(): void {
+ this.goTo(this.getEnabledSteps()[0]);
+ }
+
+ /**
+ * Navigate to the last wizard substep
+ */
+ goToLastStep(): void {
+ let enabledSteps = this.getEnabledSteps();
+ this.goTo(enabledSteps[enabledSteps.length - 1]);
+ }
+
+ /**
+ * Navigate to the next wizard step or substep
+ */
+ goToNextStep(): void {
+ this.next(false);
+ }
+
+ /**
+ * Navigate to the previous wizard step or substep
+ */
+ goToPreviousStep(): void {
+ this.previous(false);
+ }
+
+ /**
+ * Called when the next button has been selected.
+ *
+ * @param {boolean} emitEvent True to emit the wizard's onNext event
+ */
+ next(emitEvent: boolean): boolean {
+ let enabledSteps: WizardStep[] = this.getEnabledSteps();
+
+ // Save the step you were on when next() was invoked
+ let index = this.stepIndex(this.selectedStep);
+
+ let wizEvent = {
+ index: index,
+ step: this.selectedStep
+ } as WizardEvent;
+
+ if (emitEvent !== false) {
+ this.wizard.onNext.emit(wizEvent);
+ }
+
+ // Set completed property, which may be used to add/remove a style class from progress bar
+ this.selectedStep.config.completed = true;
+
+ // Ensure this is not the last step.
+ if (index === enabledSteps.length - 1) {
+ return false;
+ }
+ this.goTo(enabledSteps[index + 1]);
+ return true;
+ }
+
+ /**
+ * Called when the previous button has been selected.
+ *
+ * @param {boolean} emitEvent True to emit the wizard's onPrevious event
+ */
+ previous(emitEvent: boolean): boolean {
+ let index = this.stepIndex(this.selectedStep);
+ let wizEvent = {
+ index: index,
+ step: this.selectedStep
+ } as WizardEvent;
+
+ if (emitEvent !== false) {
+ this.wizard.onPrevious.emit(wizEvent);
+ }
+
+ // Ensure this is not the first step
+ if (index === 0) {
+ return false;
+ }
+ this.goTo(this.getEnabledSteps()[index - 1]);
+ return true;
+ }
+
+ /**
+ * Emits an event when a wizard step or substep is shown
+ */
+ show(index: number) {
+ this.onShow.emit({
+ index: index,
+ step: this
+ } as WizardEvent);
+ }
+
+ // Private
+
+ // Navigate to the given wizard substep
+ private goTo(step: WizardStep): void {
+ if (step === undefined || this.wizard === undefined || this.wizard.config.done
+ || (!this.init && this.selectedStep !== undefined && !this.selectedStep.config.allowNavAway)) {
+ return;
+ }
+ if (this.init || this.isPreviousStepsComplete(step)
+ || (this.getStepIndex(step) < this.selectedStepIndex && this.selectedStep.config.previousEnabled)) {
+ this.unselectAll();
+ this.selectedStep = step;
+ step.selected = true;
+ step.show(this.stepIndex(step));
+ this.wizard.stepChanged(step, this.stepIndex(step));
+ this.wizard.updateStepIndex(this.stepIndex(this.selectedStep));
+ this.init = false;
+ }
+ }
+
+ // Indicates all previous substeps are complete for this wizard step
+ private isPreviousStepsComplete(nextStep: WizardStep): boolean {
+ let nextIdx = this.stepIndex(nextStep);
+ let complete = true;
+ this.getEnabledSteps().forEach((step: WizardStep, stepIndex) => {
+ if (stepIndex < nextIdx) {
+ complete = complete && step.config.nextEnabled;
+ }
+ });
+ return complete;
+ }
+
+ // Handle step navigation
+ private stepClick(step: WizardStep): void {
+ if (step.config.allowClickNav) {
+ this.goTo(step);
+ }
+ }
+}
diff --git a/src/app/wizard/wizard-step.ts b/src/app/wizard/wizard-step.ts
new file mode 100644
index 000000000..a0c770c06
--- /dev/null
+++ b/src/app/wizard/wizard-step.ts
@@ -0,0 +1,91 @@
+import {
+ TemplateRef
+} from '@angular/core';
+
+import { WizardComponent } from './wizard.component';
+import { WizardStepConfig } from './wizard-step-config';
+
+/**
+ * Wizard step
+ */
+export interface WizardStep {
+ /**
+ * The wizard step config containing component properties
+ */
+ config: WizardStepConfig;
+
+ /**
+ * Returns the step number to be displayed for the given wizard step or substep.
+ */
+ getDisplayNumber?: Function;
+
+ /**
+ * Returns only wizard steps with review templates.
+ */
+ getReviewSteps?: Function;
+
+ /**
+ * Navigate to the first wizard substep.
+ */
+ goToFirstStep?: Function;
+
+ /**
+ * Navigate to the last wizard substep.
+ */
+ goToLastStep?: Function;
+
+ /**
+ * Navigate to the next wizard step or substep
+ */
+ goToNextStep?: Function;
+
+ /**
+ * Navigate to the previous wizard step or substep
+ */
+ goToPreviousStep?: Function;
+
+ /**
+ * Indicates that this wizard step has substeps.
+ */
+ hasSubsteps?: boolean;
+
+ /**
+ * Called when the next button has been selected.
+ */
+ next?: Function;
+
+ /**
+ * Indicates that the next button is enabled when the wizard step is displayed.
+ */
+ nextEnabled?: boolean;
+
+ /**
+ * Called when the previous button has been selected.
+ */
+ previous?: Function;
+
+ /**
+ * Indicates that the previous button is enabled when the wizard step is displayed.
+ */
+ previousEnabled?: boolean;
+
+ /**
+ * The wizard step template used for the review details screen
+ */
+ reviewTemplate: TemplateRef;
+
+ /**
+ * Indicates that this wizard step or substep is selected
+ */
+ selected: boolean;
+
+ /**
+ * Returns the selected wizard step or substep index.
+ */
+ selectedStepIndex?: number;
+
+ /**
+ * Emits an event when a wizard step or substep is shown
+ */
+ show: Function;
+}
diff --git a/src/app/wizard/wizard-substep.component.html b/src/app/wizard/wizard-substep.component.html
new file mode 100644
index 000000000..b3ad55c97
--- /dev/null
+++ b/src/app/wizard/wizard-substep.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/wizard/wizard-substep.component.ts b/src/app/wizard/wizard-substep.component.ts
new file mode 100644
index 000000000..c0e514fd1
--- /dev/null
+++ b/src/app/wizard/wizard-substep.component.ts
@@ -0,0 +1,141 @@
+import {
+ Component,
+ EventEmitter,
+ Host,
+ Input,
+ OnInit,
+ Output,
+ TemplateRef,
+ ViewEncapsulation
+} from '@angular/core';
+
+import { WizardEvent } from './wizard-event';
+import { WizardStep } from './wizard-step';
+import { WizardStepConfig } from './wizard-step-config'
+import { WizardStepComponent } from './wizard-step.component';
+
+import { cloneDeep, defaults, isEqual } from 'lodash';
+
+/**
+ * Wizard substep component.
+ *
+ * Note: This component is expected to be a child of wizard-step.
+ */
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'pfng-wizard-substep',
+ templateUrl: './wizard-substep.component.html'
+})
+export class WizardSubstepComponent implements OnInit, WizardStep {
+ /**
+ * The wizard step config containing component properties
+ */
+ @Input() config: WizardStepConfig;
+
+ /**
+ * The wizard substep template used for the review details screen
+ */
+ @Input() reviewTemplate: TemplateRef;
+
+ /**
+ * The event emitted when this wizard substep is shown
+ */
+ @Output('onShow') onShow = new EventEmitter();
+
+ private defaultConfig = {
+ allowClickNav: true,
+ allowNavAway: true,
+ completed: false,
+ data: {},
+ disabled: false,
+ expandReview: true,
+ expandReviewDetails: false,
+ priority: 999,
+ nextEnabled: true,
+ okToNavAway: true,
+ previousEnabled: true,
+ title: ''
+ } as WizardStepConfig;
+ private prevConfig: WizardStepConfig;
+ private _selected: boolean = false;
+ private step: WizardStepComponent;
+
+ /**
+ * The default constructor
+ */
+ constructor(@Host() step: WizardStepComponent) {
+ this.step = step;
+ }
+
+ // Initialization
+
+ /**
+ * Setup component configuration upon initialization
+ */
+ ngOnInit(): void {
+ this.setupConfig();
+
+ if (this.step !== undefined) {
+ this.step.config.allowClickNav = this.config.allowClickNav;
+ this.step.config.nextEnabled = this.config.nextEnabled;
+ this.step.config.allowNavAway = this.config.allowNavAway;
+ this.step.config.previousEnabled = this.config.previousEnabled;
+
+ this.step.addStep(this);
+ }
+ }
+
+ /**
+ * Check if the component config has changed
+ */
+ ngDoCheck(): void {
+ // Do a deep compare on config
+ if (!isEqual(this.config, this.prevConfig)) {
+ this.setupConfig();
+ }
+ }
+
+ /**
+ * Set up default config
+ */
+ protected setupConfig(): void {
+ if (this.config !== undefined) {
+ defaults(this.config, this.defaultConfig);
+ } else {
+ this.config = cloneDeep(this.defaultConfig);
+ }
+ this.prevConfig = cloneDeep(this.config);
+ }
+
+ // Accessors
+
+ /**
+ * Indicates that this wizard substep is selected
+ *
+ * @returns {boolean} True if this wizard substep is selected
+ */
+ get selected(): boolean {
+ return this._selected;
+ }
+
+ /**
+ * Sets a flag indicating that this wizard substep is selected
+ *
+ * @param {boolean} selected True if this wizard substep is selected
+ */
+ set selected(selected: boolean) {
+ this._selected = selected;
+ }
+
+ // Methods
+
+ /**
+ * Emits an event when this wizard substep is shown
+ */
+ show(index: number) {
+ this.onShow.emit({
+ index: index,
+ step: this
+ } as WizardEvent);
+ }
+}
diff --git a/src/app/wizard/wizard.component.css b/src/app/wizard/wizard.component.css
new file mode 100644
index 000000000..3e67c1efd
--- /dev/null
+++ b/src/app/wizard/wizard.component.css
@@ -0,0 +1,2 @@
+.pfng-wizard-cancel-inline{margin-left:25px}.pfng-wizard-footer-inline{text-align:left}.pfng-wizard-main{margin-left:0}.pfng-wizard-position-override{position:relative}.wizard-pf-footer .btn-cancel.wizard-pf-cancel-no-back{margin-right:0}.wizard-pf-steps-indicator li a.disabled{cursor:default}.wizard-pf-steps-indicator li a.disabled:hover .wizard-pf-step-number{background-color:#fff;border-color:#bbb;color:#bbb}
+/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC93aXphcmQvd2l6YXJkLmNvbXBvbmVudC5sZXNzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBO0VBQ0UsaUJBQUE7O0FBRUY7RUFDRSxnQkFBQTs7QUFFRjtFQUNFLGNBQUE7O0FBRUY7RUFDRSxrQkFBQTs7QUFJRSxpQkFERixZQUNHO0VBQ0MsZUFBQTs7QUFJTiwwQkFBMkIsR0FBRyxFQUFDO0VBQzdCLGVBQUE7O0FBQ0EsMEJBRnlCLEdBQUcsRUFBQyxTQUU1QixNQUNDO0VBQ0Usc0JBQUE7RUFDQSxrQkFBQTtFQUNBLFdBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJAaW1wb3J0IChyZWZlcmVuY2UpIFwiLi4vLi4vYXNzZXRzL3N0eWxlc2hlZXRzL3BhdHRlcm5mbHktbmdcIjtcblxuLnBmbmctd2l6YXJkLWNhbmNlbC1pbmxpbmUge1xuICBtYXJnaW4tbGVmdDogMjVweDtcbn1cbi5wZm5nLXdpemFyZC1mb290ZXItaW5saW5lIHtcbiAgdGV4dC1hbGlnbjogbGVmdDtcbn1cbi5wZm5nLXdpemFyZC1tYWluIHtcbiAgbWFyZ2luLWxlZnQ6IDA7XG59XG4ucGZuZy13aXphcmQtcG9zaXRpb24tb3ZlcnJpZGUge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG4ud2l6YXJkLXBmLWZvb3RlciB7XG4gIC5idG4tY2FuY2VsIHtcbiAgICAmLndpemFyZC1wZi1jYW5jZWwtbm8tYmFjayB7XG4gICAgICBtYXJnaW4tcmlnaHQ6IDA7XG4gICAgfVxuICB9XG59XG4ud2l6YXJkLXBmLXN0ZXBzLWluZGljYXRvciBsaSBhLmRpc2FibGVkIHtcbiAgY3Vyc29yOiBkZWZhdWx0O1xuICAmOmhvdmVyIHtcbiAgICAud2l6YXJkLXBmLXN0ZXAtbnVtYmVyIHtcbiAgICAgIGJhY2tncm91bmQtY29sb3I6IEBjb2xvci1wZi13aGl0ZTtcbiAgICAgIGJvcmRlci1jb2xvcjogQGNvbG9yLXBmLWJsYWNrLTQwMDtcbiAgICAgIGNvbG9yOiBAY29sb3ItcGYtYmxhY2stNDAwO1xuICAgIH1cbiAgfVxufVxuIl0sImZpbGUiOiJhcHAvd2l6YXJkL3dpemFyZC5jb21wb25lbnQuY3NzIn0= */
diff --git a/src/app/wizard/wizard.component.html b/src/app/wizard/wizard.component.html
new file mode 100644
index 000000000..5d1b9c670
--- /dev/null
+++ b/src/app/wizard/wizard.component.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
{{config?.loadingTitle}}
+
{{config?.loadingSecondaryInfo}}
+
+
+
+
+
+
+
diff --git a/src/app/wizard/wizard.component.less b/src/app/wizard/wizard.component.less
new file mode 100644
index 000000000..fdce54b86
--- /dev/null
+++ b/src/app/wizard/wizard.component.less
@@ -0,0 +1,31 @@
+@import (reference) "../../assets/stylesheets/patternfly-ng";
+
+.pfng-wizard-cancel-inline {
+ margin-left: 25px;
+}
+.pfng-wizard-footer-inline {
+ text-align: left;
+}
+.pfng-wizard-main {
+ margin-left: 0;
+}
+.pfng-wizard-position-override {
+ position: relative;
+}
+.wizard-pf-footer {
+ .btn-cancel {
+ &.wizard-pf-cancel-no-back {
+ margin-right: 0;
+ }
+ }
+}
+.wizard-pf-steps-indicator li a.disabled {
+ cursor: default;
+ &:hover {
+ .wizard-pf-step-number {
+ background-color: @color-pf-white;
+ border-color: @color-pf-black-400;
+ color: @color-pf-black-400;
+ }
+ }
+}
diff --git a/src/app/wizard/wizard.component.spec.ts b/src/app/wizard/wizard.component.spec.ts
new file mode 100644
index 000000000..2f2314fff
--- /dev/null
+++ b/src/app/wizard/wizard.component.spec.ts
@@ -0,0 +1,37 @@
+import {
+ async,
+ ComponentFixture,
+ TestBed
+} from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { FormsModule } from '@angular/forms';
+import { By } from '@angular/platform-browser';
+
+import { WizardComponent } from './wizard.component';
+
+describe('Wizard component - ', () => {
+ let comp: WizardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ });
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ BrowserAnimationsModule,
+ FormsModule
+ ],
+ declarations: [
+ WizardComponent
+ ]
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(WizardComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+ }));
+
+});
diff --git a/src/app/wizard/wizard.component.ts b/src/app/wizard/wizard.component.ts
new file mode 100644
index 000000000..5f3ab64c3
--- /dev/null
+++ b/src/app/wizard/wizard.component.ts
@@ -0,0 +1,394 @@
+import {
+ Component,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output,
+ ViewEncapsulation
+} from '@angular/core';
+
+import { WizardBase } from './wizard-base';
+import { WizardConfig } from './wizard-config';
+import { WizardEvent } from './wizard-event';
+import { WizardStep } from './wizard-step';
+
+import { cloneDeep, defaults, isEqual } from 'lodash';
+
+/**
+ * Wizard component
+ */
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ selector: 'pfng-wizard',
+ styleUrls: ['./wizard.component.less'],
+ templateUrl: './wizard.component.html'
+})
+export class WizardComponent extends WizardBase implements OnInit {
+ /**
+ * The wizard config containing component properties
+ */
+ @Input() config: WizardConfig;
+
+ /**
+ * The event emitted when the cancel button has been selected
+ */
+ @Output('onCancel') onCancel = new EventEmitter();
+
+ /**
+ * The event emitted when all wizard steps and substeps have finished
+ */
+ @Output('onFinish') onFinish = new EventEmitter();
+
+ /**
+ * The event emitted when the next button has been selected
+ */
+ @Output('onNext') onNext = new EventEmitter();
+
+ /**
+ * The event emitted when the back button has been selected
+ */
+ @Output('onPrevious') onPrevious = new EventEmitter();
+
+ /**
+ * The event emitted when a step has changed
+ */
+ @Output('onStepChange') onStepChange = new EventEmitter();
+
+ private contentStyle: any;
+ private defaultConfig = {
+ cancelTitle: 'Cancel',
+ done: false,
+ contentHeight: '300px',
+ embedInPage: false,
+ hideIndicators: false,
+ hideSidebar: false,
+ hideHeader: false,
+ hidePreviousButton: false,
+ nextTitle: 'Next >',
+ previousTitle: '< Back',
+ ready: true
+ } as WizardConfig;
+ private init: boolean = true;
+ private _firstStep: boolean = false;
+ private prevConfig: WizardConfig;
+
+ /**
+ * The default constructor
+ */
+ constructor() {
+ super();
+ }
+
+ // Initialization
+
+ /**
+ * Setup component configuration upon initialization
+ */
+ ngOnInit(): void {
+ this.setupConfig();
+ if (this.init && this.config.ready) {
+ setTimeout(() => {
+ this.initFirstStep();
+ }, 10);
+ }
+ }
+
+ /**
+ * Check if the component config has changed
+ */
+ ngDoCheck(): void {
+ // Do a deep compare on config
+ if (!isEqual(this.config, this.prevConfig)) {
+ this.setupConfig();
+ }
+ }
+
+ /**
+ * Set up default config
+ */
+ protected setupConfig(): void {
+ if (this.config !== undefined) {
+ defaults(this.config, this.defaultConfig);
+ } else {
+ this.config = cloneDeep(this.defaultConfig);
+ }
+
+ // If a step class is given use it for all steps
+ if (this.config.stepStyleClass !== undefined) {
+ // If a sidebarStyleClass is given, us it for sidebar panel, if not, apply the stepsClass to the sidebar panel
+ if (this.config.sidebarStyleClass === undefined) {
+ this.config.sidebarStyleClass = this.config.stepStyleClass;
+ }
+ } else {
+ this.contentStyle = {
+ 'height': this.config.contentHeight,
+ 'max-height': this.config.contentHeight,
+ 'overflow-y': 'auto'
+ };
+ }
+
+ // Ready state changed
+ if (this.prevConfig !== undefined && !isEqual(this.config.ready, this.prevConfig.ready)) {
+ this.initFirstStep();
+ }
+ this.prevConfig = cloneDeep(this.config);
+ }
+
+ // Accessors
+
+ /**
+ * Indicates that the selected step is also the first wizard step or substep
+ *
+ * @returns {boolean} True if the selected step is the first wizard step or substep
+ */
+ get firstStep(): boolean {
+ return this._firstStep;
+ }
+
+ /**
+ * Set a flag indicating that the selected step is also the first wizard step or substep
+ *
+ * @param {boolean} firstStep True if the selected step is the first wizard step or substep
+ */
+ set firstStep(firstStep: boolean) {
+ this._firstStep = firstStep;
+ }
+
+ // Methods
+
+ /**
+ * Add a wizard step or substep to this component
+ *
+ * @param {WizardStep} step The wizard step or substep
+ */
+ addStep(step: WizardStep): void {
+ super.addStep(step)
+
+ let enabledSteps: WizardStep[] = this.getEnabledSteps();
+ if (this.config.ready && (enabledSteps.length > 0) && (step === enabledSteps[0])) {
+ this.goTo(enabledSteps[0], true, false);
+ }
+ };
+
+ /**
+ * Returns only wizard steps with review templates
+ *
+ * @returns {WizardStep[]} The wizard stepd or substepd
+ */
+ getReviewSteps(): WizardStep[] {
+ let reviewSteps = this.getEnabledSteps().filter((step: WizardStep) => {
+ return (step.reviewTemplate !== undefined || step.getReviewSteps().length > 0);
+ });
+ return reviewSteps;
+ }
+
+ /**
+ * Navigate to the next wizard step or substep
+ */
+ goToNextStep(): void {
+ this.next(false);
+ }
+
+ /**
+ * Navigate to the previous wizard step or substep
+ */
+ goToPreviousStep(): void {
+ this.previous(false);
+ }
+
+ /**
+ * Navigate to the given wizard step index
+ *
+ * @param {number} stepIndex The step number to navigate to
+ * @param {boolean} resetStepNav True if the first substep (if exists) should be selected
+ */
+ goToStep(stepIndex: number, resetStepNav: boolean): void {
+ let enabledSteps: WizardStep[] = this.getEnabledSteps();
+ if (stepIndex < enabledSteps.length) {
+ this.goTo(enabledSteps[stepIndex], resetStepNav, false);
+ }
+ }
+
+ /**
+ * Called when the next button has been selected.
+ *
+ * @param {boolean} emitEvent True to emit the onNext event
+ */
+ next(emitEvent: boolean): void {
+ let enabledSteps: WizardStep[] = this.getEnabledSteps();
+
+ // Save the step you were on when next() was invoked
+ let index = this.stepIndex(this.selectedStep);
+
+ let wizEvent = {
+ index: index,
+ step: this.selectedStep
+ } as WizardEvent;
+
+ if (this.selectedStep.hasSubsteps) {
+ // Handle navigation in substep
+ if (this.selectedStep.next(emitEvent)) {
+ return;
+ }
+ } else {
+ if (emitEvent !== false) {
+ this.onNext.emit(wizEvent);
+ }
+ }
+
+ // Set completed property, which may be used to add/remove a style class from progress bar
+ this.selectedStep.config.completed = true;
+
+ // Ensure this is not the last step
+ if (index === enabledSteps.length - 1) {
+ this.finish();
+ } else {
+ this.goTo(enabledSteps[index + 1], true, false);
+ }
+ }
+
+ /**
+ * Called when the previous button has been selected.
+ *
+ * @param {boolean} emitEvent True to emit the onNext event
+ */
+ previous(emitEvent: boolean): void {
+ let index = this.stepIndex(this.selectedStep);
+
+ let wizEvent = {
+ index: index,
+ step: this.selectedStep
+ } as WizardEvent;
+
+ if (this.selectedStep.hasSubsteps) {
+ // Handle navigation in substep
+ if (this.selectedStep.previous(emitEvent)) {
+ return;
+ }
+ } else {
+ if (emitEvent !== false) {
+ this.onPrevious.emit(wizEvent);
+ }
+ }
+
+ // Ensure this is not the first step
+ if (index === 0) {
+ throw new Error("Can't go back. Already at first step");
+ } else {
+ this.goTo(this.getEnabledSteps()[index - 1], false, true);
+ }
+ }
+
+ /**
+ * Emits an event when the wizard step or substep has changed
+ *
+ * @param {WizardStep} step The wizard step or substep
+ * @param {number} index The order of the wizard step of substep within its parent
+ */
+ stepChanged(step: WizardStep, index: number): void {
+ this.onStepChange.emit({
+ index: index,
+ step: step
+ } as WizardEvent);
+ }
+
+ /**
+ * Set a flag indicating that the selected step is also the first wizard step or substep
+ *
+ * @param {number} stepIndex The step index
+ */
+ updateStepIndex(stepIndex: number): void {
+ this.firstStep = this.stepIndex(this.selectedStep) === 0 && stepIndex === 0;
+ };
+
+ // Private
+
+ // Indicates that the user can click on numeric step indicators to navigate directly to a step
+ private allowStepIndicatorClick(step: WizardStep): boolean {
+ if (step === undefined || this.selectedStep === undefined) {
+ return false;
+ }
+ return !this.config.done
+ && step.config.allowClickNav
+ && this.selectedStep.config.allowNavAway
+ && (this.selectedStep.config.nextEnabled || (step.config.priority < this.selectedStep.config.priority))
+ && (this.selectedStep.config.previousEnabled || (step.config.priority > this.selectedStep.config.priority));
+ }
+
+ // Emits an event inidcating that the cancel button has been selected
+ private cancel(): void {
+ this.onCancel.emit({
+ index: this.stepIndex(this.selectedStep),
+ step: this.selectedStep
+ } as WizardEvent);
+ this.reset();
+ }
+
+ // Emits an event inidcating that all wizard steps and substeps have finished
+ private finish(): void {
+ this.onFinish.emit({
+ index: this.stepIndex(this.selectedStep),
+ step: this.selectedStep
+ } as WizardEvent);
+ this.reset();
+ }
+
+ // Navigate to the given substep
+ private goTo(step: WizardStep, goToFirstSubstep: boolean, goToLastSubstep: boolean): void {
+ if (step === undefined || this.config.done
+ || (!this.init && this.selectedStep !== undefined && !this.selectedStep.config.allowNavAway)) {
+ return;
+ }
+ if (this.init || (this.getStepIndex(step) < this.selectedStepIndex && this.selectedStep.previousEnabled)
+ || this.selectedStep.nextEnabled) {
+ this.unselectAll();
+ if (step.hasSubsteps && goToFirstSubstep) {
+ step.goToFirstStep();
+ } else if (step.hasSubsteps && goToLastSubstep) {
+ step.goToLastStep();
+ } else {
+ step.show(this.stepIndex(step));
+ this.stepChanged(step, this.stepIndex(step));
+ }
+ this.selectedStep = step;
+ step.selected = true;
+ }
+
+ if (!this.selectedStep.hasSubsteps) {
+ this.firstStep = this.stepIndex(this.selectedStep) === 0;
+ } else {
+ this.firstStep = this.stepIndex(this.selectedStep) === 0 && this.selectedStep.selectedStepIndex === 1;
+ }
+ }
+
+ // Initializes the first step based on the ready state and whether a current step has been provided
+ private initFirstStep(): void {
+ // Set currentStep equal to selected step title
+ if (this.config !== undefined && this.config.currentStep !== undefined
+ && !isEqual(this.config.currentStep, this.prevConfig.currentStep)
+ && (this.selectedStep !== undefined && this.selectedStep.config.title !== this.config.currentStep)) {
+ this.goTo(this.stepByTitle(this.config.currentStep), true, false);
+ } else {
+ let enabledSteps: WizardStep[] = this.getEnabledSteps();
+ this.goTo(enabledSteps[0], true, false);
+ }
+ this.init = false;
+ }
+
+ // Reset wizard state
+ private reset(): void {
+ // Traverse steps array and set each "completed" property to false
+ this.getEnabledSteps().forEach((step: WizardStep) => {
+ step.config.completed = false;
+ });
+ // Go to first step
+ this.goToStep(0, true);
+ }
+
+ // Handle step navigation
+ private stepClick(step: WizardStep): void {
+ if (step.config.allowClickNav) {
+ this.goTo(step, true, false);
+ }
+ }
+}
diff --git a/src/app/wizard/wizard.module.ts b/src/app/wizard/wizard.module.ts
new file mode 100644
index 000000000..16775d8bd
--- /dev/null
+++ b/src/app/wizard/wizard.module.ts
@@ -0,0 +1,36 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { TooltipConfig, TooltipModule } from 'ngx-bootstrap/tooltip';
+
+import { WizardBase } from './wizard-base';
+import { WizardComponent } from './wizard.component';
+import { WizardConfig } from './wizard-config';
+import { WizardEvent } from './wizard-event';
+import { WizardReviewComponent } from './wizard-review.component';
+import { WizardStep } from './wizard-step';
+import { WizardStepComponent } from './wizard-step.component';
+import { WizardStepConfig } from './wizard-step-config';
+import { WizardSubstepComponent } from './wizard-substep.component';
+
+export {
+ WizardBase,
+ WizardConfig,
+ WizardEvent,
+ WizardStep,
+ WizardStepConfig
+}
+
+/**
+ * A module containing objects associated with the wizard component
+ */
+@NgModule({
+ imports: [
+ CommonModule,
+ TooltipModule.forRoot()
+ ],
+ declarations: [ WizardComponent, WizardReviewComponent, WizardStepComponent, WizardSubstepComponent ],
+ exports: [ WizardComponent, WizardReviewComponent, WizardStepComponent, WizardSubstepComponent ],
+ providers: [ TooltipConfig ]
+})
+export class WizardModule { }
diff --git a/src/demo/app-routing.module.ts b/src/demo/app-routing.module.ts
index ee713932a..d8e1b4aff 100644
--- a/src/demo/app-routing.module.ts
+++ b/src/demo/app-routing.module.ts
@@ -13,6 +13,7 @@ import { SortExampleComponent } from '../app/sort/examples/sort-example.componen
import { ToastNotificationExampleComponent } from '../app/notification/examples/toast-notification-example.component';
import { ToolbarExampleComponent } from '../app/toolbar/examples/toolbar-example.component';
import { WelcomeComponent } from './components/welcome.component';
+import { WizardExampleComponent } from '../app/wizard/examples/wizard-example.component';
const routes: Routes = [{
path: '',
@@ -51,6 +52,9 @@ const routes: Routes = [{
}, {
path: 'toolbar',
component: ToolbarExampleComponent
+ }, {
+ path: 'wizard',
+ component: WizardExampleComponent
}];
@NgModule({
diff --git a/src/demo/app.component.html b/src/demo/app.component.html
index df07bcfae..84aeb0612 100644
--- a/src/demo/app.component.html
+++ b/src/demo/app.component.html
@@ -40,6 +40,9 @@ Components
Toolbar
+
+ Wizard
+
Directives
diff --git a/src/demo/app.module.ts b/src/demo/app.module.ts
index 99afff249..c9fb7d74c 100644
--- a/src/demo/app.module.ts
+++ b/src/demo/app.module.ts
@@ -22,6 +22,7 @@ import { SearchHighlightExampleModule } from '../app/search-highlight/examples/s
import { SortExampleModule } from '../app/sort/examples/sort-example.module';
import { ToolbarExampleModule } from '../app/toolbar/examples/toolbar-example.module';
import { WelcomeComponent } from './components/welcome.component';
+import { WizardExampleModule } from '../app/wizard/examples/wizard-example.module';
@NgModule({
imports: [
@@ -38,7 +39,8 @@ import { WelcomeComponent } from './components/welcome.component';
SampleExampleModule,
SearchHighlightExampleModule,
SortExampleModule,
- ToolbarExampleModule
+ ToolbarExampleModule,
+ WizardExampleModule
],
declarations: [
AppComponent,