├── karma.conf.js
├── .npmignore
├── .gitignore
├── .travis.yml
├── tsconfig.json
├── src
├── wizard.component.spec.ts
├── wizard-step.component.spec.ts
├── wizard-step.component.ts
└── wizard.component.ts
├── index.ts
├── config
├── helpers.js
├── karma.conf.js
├── spec-bundle.js
└── webpack.test.js
├── package.json
├── tslint.json
└── README.MD
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./config/karma.conf.js');
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 |
5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM
6 | # TypeScript
7 | # *.js
8 | # *.map
9 | # *.d.ts
10 |
11 | # JetBrains
12 | .idea
13 | .project
14 | .settings
15 | .idea/*
16 | *.iml
17 |
18 | # VS Code
19 | .vscode/*
20 |
21 | # Windows
22 | Thumbs.db
23 | Desktop.ini
24 |
25 | # Mac
26 | .DS_Store
27 | **/.DS_Store
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | *.ngfactory.ts
3 | *.ngsummary.json
4 |
5 | # Coverage
6 | coverage/*
7 |
8 | # Node
9 | node_modules/*
10 | npm-debug.log
11 |
12 | # TypeScript
13 | *.js
14 | *.map
15 | *.d.ts
16 |
17 | # JetBrains
18 | .idea
19 | .project
20 | .settings
21 | .idea/*
22 | *.iml
23 |
24 | # VS Code
25 | .vscode/*
26 |
27 | # Windows
28 | Thumbs.db
29 | Desktop.ini
30 |
31 | # Mac
32 | .DS_Store
33 | **/.DS_Store
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '7.3.0'
4 | addons:
5 | code_climate:
6 | repo_token: 1d74e14d8b4235f5b311de8a5b9e567b178e10dc2226130034184fba0f93a759
7 | before_install:
8 | - export CHROME_BIN=chromium-browser
9 | - export DISPLAY=:99.0
10 | - sh -e /etc/init.d/xvfb start
11 | install:
12 | - npm install
13 | - npm install codeclimate-test-reporter
14 | script:
15 | - karma start
16 | after_script:
17 | - "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
18 | - codeclimate-test-reporter < coverage/lcov.info
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noImplicitAny": true,
4 | "module": "commonjs",
5 | "target": "ES5",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "sourceMap": true,
9 | "declaration": true,
10 | "outDir": "./dist",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ]
14 | },
15 | "files": [
16 | "index.ts"
17 | ],
18 | "exclude": [
19 | "node_modules",
20 | "dist",
21 | "**/*.spec.ts"
22 | ],
23 | "angularCompilerOptions": {
24 | "strictMetadataEmit": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/wizard.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { WizardComponent } from './wizard.component';
4 | import { FormWizardModule } from '../index';
5 |
6 | describe('Wizard Component', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | imports: [FormWizardModule]
10 | });
11 | });
12 |
13 | beforeEach(() => {
14 | TestBed.compileComponents();
15 | });
16 |
17 | describe('', () => {
18 | it('', () => {
19 | let fixture = TestBed.createComponent(WizardComponent);
20 | });
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { WizardComponent } from './src/wizard.component';
4 | import { WizardStepComponent } from './src/wizard-step.component';
5 |
6 | export * from './src/wizard.component';
7 | export * from './src/wizard-step.component';
8 |
9 | @NgModule({
10 | imports: [
11 | CommonModule
12 | ],
13 | declarations: [
14 | WizardComponent,
15 | WizardStepComponent
16 | ],
17 | exports: [
18 | WizardComponent,
19 | WizardStepComponent
20 | ]
21 | })
22 | export class FormWizardModule {
23 | static forRoot(): ModuleWithProviders {
24 | return {
25 | ngModule: FormWizardModule
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/wizard-step.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { WizardStepComponent } from './wizard-step.component';
4 | import { FormWizardModule } from '../index';
5 |
6 | describe('Wizard Step Component', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | imports: [FormWizardModule]
10 | });
11 | });
12 |
13 | beforeEach(() => {
14 | TestBed.compileComponents();
15 | });
16 |
17 | describe('when create wizard step', () => {
18 | it('should have a title', () => {
19 | let fixture = TestBed.createComponent(WizardStepComponent);
20 | fixture.componentInstance.title = 'Step1';
21 |
22 | fixture.detectChanges();
23 |
24 | expect(fixture.componentInstance.title).toBe('Step1');
25 | });
26 | });
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/config/helpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * taken from angular2-webpack-starter
3 | */
4 | var path = require('path');
5 |
6 | // Helper functions
7 | var ROOT = path.resolve(__dirname, '..');
8 |
9 | function hasProcessFlag(flag) {
10 | return process.argv.join('').indexOf(flag) > -1;
11 | }
12 |
13 | function isWebpackDevServer() {
14 | return process.argv[1] && !! (/webpack-dev-server$/.exec(process.argv[1]));
15 | }
16 |
17 | function root(args) {
18 | args = Array.prototype.slice.call(arguments, 0);
19 | return path.join.apply(path, [ROOT].concat(args));
20 | }
21 |
22 | function checkNodeImport(context, request, cb) {
23 | if (!path.isAbsolute(request) && request.charAt(0) !== '.') {
24 | cb(null, 'commonjs ' + request); return;
25 | }
26 | cb();
27 | }
28 |
29 | exports.hasProcessFlag = hasProcessFlag;
30 | exports.isWebpackDevServer = isWebpackDevServer;
31 | exports.root = root;
32 | exports.checkNodeImport = checkNodeImport;
33 |
--------------------------------------------------------------------------------
/src/wizard-step.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'wizard-step',
5 | template:
6 | `
7 |
8 |
9 |
10 | `
11 | })
12 | export class WizardStepComponent {
13 | @Input() title: string;
14 | @Input() hidden: boolean = false;
15 | @Input() isValid: boolean = true;
16 | @Input() showNext: boolean = true;
17 | @Input() showPrev: boolean = true;
18 |
19 | @Output() onNext: EventEmitter = new EventEmitter();
20 | @Output() onPrev: EventEmitter = new EventEmitter();
21 | @Output() onComplete: EventEmitter = new EventEmitter();
22 |
23 | private _isActive: boolean = false;
24 | isDisabled: boolean = true;
25 |
26 | constructor() { }
27 |
28 | @Input('isActive')
29 | set isActive(isActive: boolean) {
30 | this._isActive = isActive;
31 | this.isDisabled = false;
32 | }
33 |
34 | get isActive(): boolean {
35 | return this._isActive;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-wizard",
3 | "version": "0.3.0",
4 | "scripts": {
5 | "build": "ngc -p tsconfig.json",
6 | "lint": "tslint src/**/*.ts",
7 | "test": "karma start karma.conf.js",
8 | "prepublish": "tsc",
9 | "tsc": "tsc"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/maiyaporn/angular2-wizard"
14 | },
15 | "author": {
16 | "name": "Maiyaporn Phanich",
17 | "email": "p.maiyaporn@gmail.com"
18 | },
19 | "keywords": [
20 | "angular2-wizard",
21 | "form-wizard",
22 | "wizard",
23 | "wizard-step",
24 | "angular2",
25 | "angular2-component"
26 | ],
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/maiyaporn/angular2-wizard/issues"
30 | },
31 | "main": "./dist/index.js",
32 | "dependencies": {
33 | "bootstrap": "^4.0.0-alpha.6"
34 | },
35 | "devDependencies": {
36 | "@angular/common": "^2.3.1",
37 | "@angular/compiler": "^2.3.1",
38 | "@angular/compiler-cli": "^2.3.1",
39 | "@angular/core": "^2.3.1",
40 | "@angular/platform-browser": "^2.3.1",
41 | "@angular/platform-browser-dynamic": "^2.3.1",
42 | "@angular/platform-server": "^2.3.1",
43 | "@types/es6-shim": "^0.31.32",
44 | "@types/jasmine": "^2.5.42",
45 | "@types/selenium-webdriver": "^2.53.39",
46 | "awesome-typescript-loader": "^3.0.4-rc.2",
47 | "codelyzer": "^0.0.28",
48 | "istanbul-instrumenter-loader": "^2.0.0",
49 | "jasmine-core": "^2.5.2",
50 | "karma": "^1.4.1",
51 | "karma-chrome-launcher": "^2.0.0",
52 | "karma-coverage": "^1.1.1",
53 | "karma-jasmine": "^1.1.0",
54 | "karma-mocha-reporter": "^2.2.2",
55 | "karma-remap-coverage": "^0.1.4",
56 | "karma-sourcemap-loader": "^0.3.7",
57 | "karma-webpack": "^2.0.2",
58 | "rxjs": "^5.0.1",
59 | "source-map-loader": "^0.1.6",
60 | "ts-helpers": "^1.1.2",
61 | "tslint": "^3.15.1",
62 | "typescript": "^2.1.6",
63 | "webpack": "^2.2.1",
64 | "zone.js": "0.7.2"
65 | },
66 | "engines": {
67 | "node": ">=0.8.0"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | var testWebpackConfig = require('./webpack.test.js');
3 |
4 | var configuration = {
5 | basePath: '',
6 |
7 | frameworks: ['jasmine'],
8 |
9 | // list of files to exclude
10 | exclude: [],
11 |
12 | /*
13 | * list of files / patterns to load in the browser
14 | *
15 | * we are building the test environment in ./spec-bundle.js
16 | */
17 | files: [ { pattern: './config/spec-bundle.js', watched: false } ],
18 |
19 | preprocessors: { './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] },
20 |
21 |
22 | // Webpack Config at ./webpack.test.js
23 | webpack: testWebpackConfig,
24 |
25 | coverageReporter: {
26 | type: 'in-memory'
27 | },
28 |
29 | remapCoverageReporter: {
30 | 'text-summary': null,
31 | json: './coverage/coverage.json',
32 | html: './coverage/html',
33 | lcovonly: './coverage/lcov.info'
34 | },
35 |
36 | // Webpack please don't spam the console when running in karma!
37 | webpackMiddleware: { stats: 'errors-only'},
38 |
39 | reporters: [ 'mocha', 'coverage', 'remap-coverage' ],
40 |
41 | mochaReporter: {
42 | ignoreSkipped: true
43 | },
44 |
45 | // web server port
46 | port: 9876,
47 |
48 | colors: true,
49 |
50 | /*
51 | * level of logging
52 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
53 | */
54 | logLevel: config.LOG_INFO,
55 |
56 | autoWatch: false,
57 |
58 | browsers: [
59 | 'Chrome'
60 | ],
61 |
62 | customLaunchers: {
63 | ChromeTravisCi: {
64 | base: 'Chrome',
65 | flags: ['--no-sandbox']
66 | }
67 | },
68 |
69 | singleRun: true
70 | };
71 |
72 | if (process.env.TRAVIS){
73 | configuration.browsers = [
74 | 'ChromeTravisCi'
75 | ];
76 | }
77 |
78 | config.set(configuration);
79 | };
80 |
--------------------------------------------------------------------------------
/config/spec-bundle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * When testing with webpack and ES6, we have to do some extra
3 | * things to get testing to work right. Because we are gonna write tests
4 | * in ES6 too, we have to compile those as well. That's handled in
5 | * karma.conf.js with the karma-webpack plugin. This is the entry
6 | * file for webpack test. Just like webpack will create a bundle.js
7 | * file for our client, when we run test, it will compile and bundle them
8 | * all here! Crazy huh. So we need to do some setup
9 | */
10 | Error.stackTraceLimit = Infinity;
11 |
12 | require('core-js/es6');
13 | require('core-js/es7/reflect');
14 |
15 | // Typescript emit helpers polyfill
16 | require('ts-helpers');
17 |
18 | require('zone.js/dist/zone');
19 | require('zone.js/dist/long-stack-trace-zone');
20 | require('zone.js/dist/async-test');
21 | require('zone.js/dist/fake-async-test');
22 | require('zone.js/dist/sync-test');
23 | require('zone.js/dist/proxy'); // since zone.js 0.6.15
24 | require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14
25 |
26 | // RxJS
27 | require('rxjs/Rx');
28 |
29 | var testing = require('@angular/core/testing');
30 | var browser = require('@angular/platform-browser-dynamic/testing');
31 |
32 | testing.TestBed.initTestEnvironment(
33 | browser.BrowserDynamicTestingModule,
34 | browser.platformBrowserDynamicTesting()
35 | );
36 |
37 | /*
38 | * Ok, this is kinda crazy. We can use the context method on
39 | * require that webpack created in order to tell webpack
40 | * what files we actually want to require or import.
41 | * Below, context will be a function/object with file names as keys.
42 | * Using that regex we are saying look in ../src then find
43 | * any file that ends with spec.ts and get its path. By passing in true
44 | * we say do this recursively
45 | */
46 | var testContext = require.context('../src', true, /\.spec\.ts/);
47 |
48 | /*
49 | * get all the files, for each file, call the context function
50 | * that will require the file and load it up here. Context will
51 | * loop and require those spec files here
52 | */
53 | function requireAll(requireContext) {
54 | return requireContext.keys().map(requireContext);
55 | }
56 |
57 | // requires and returns all modules that match
58 | var modules = requireAll(testContext);
59 |
--------------------------------------------------------------------------------
/config/webpack.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adapted from angular2-webpack-starter
3 | */
4 |
5 | const helpers = require('./helpers'),
6 | webpack = require('webpack'),
7 | LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
8 |
9 | /**
10 | * Webpack Plugins
11 | */
12 |
13 | module.exports = {
14 |
15 | /**
16 | * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack
17 | *
18 | * Do not change, leave as is or it wont work.
19 | * See: https://github.com/webpack/karma-webpack#source-maps
20 | */
21 | devtool: 'source-map',
22 |
23 | resolve: {
24 | extensions: ['.ts', '.js'],
25 | modules: [helpers.root('src'), 'node_modules']
26 | },
27 |
28 | module: {
29 | rules: [{
30 | enforce: 'pre',
31 | test: /\.js$/,
32 | loader: 'source-map-loader',
33 | exclude: [
34 | // these packages have problems with their sourcemaps
35 | helpers.root('node_modules/rxjs'),
36 | helpers.root('node_modules/@angular')
37 | ]
38 | },
39 | {
40 | test: /\.ts$/,
41 | loader: 'awesome-typescript-loader',
42 | query: {
43 | // use inline sourcemaps for "karma-remap-coverage" reporter
44 | sourceMap: true,
45 | inlineSourceMap: false,
46 | module: "commonjs",
47 | removeComments: true
48 | },
49 | exclude: [/\.e2e\.ts$/]
50 | }, {
51 | enforce: 'post',
52 | test: /\.ts$/,
53 | loader: 'istanbul-instrumenter-loader',
54 | include: helpers.root('src'),
55 | exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/]
56 | }],
57 | },
58 |
59 | plugins: [
60 | // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js
61 | new webpack.ContextReplacementPlugin(
62 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
63 | helpers.root('./src')
64 | ),
65 |
66 | new LoaderOptionsPlugin({
67 | debug: true,
68 | options: {
69 |
70 | /**
71 | * Static analysis linter for TypeScript advanced options configuration
72 | * Description: An extensible linter for the TypeScript language.
73 | *
74 | * See: https://github.com/wbuchwalter/tslint-loader
75 | */
76 | tslint: {
77 | emitErrors: false,
78 | failOnHint: false,
79 | resourcePath: 'src'
80 | },
81 |
82 | }
83 | })
84 | ]
85 | };
86 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "label-undefined": true,
20 | "max-line-length": [
21 | true,
22 | 140
23 | ],
24 | "member-access": false,
25 | "member-ordering": [
26 | true,
27 | "static-before-instance",
28 | "variables-before-functions"
29 | ],
30 | "no-arg": true,
31 | "no-bitwise": true,
32 | "no-console": [
33 | true,
34 | "debug",
35 | "info",
36 | "time",
37 | "timeEnd",
38 | "trace"
39 | ],
40 | "no-construct": true,
41 | "no-debugger": true,
42 | "no-duplicate-key": true,
43 | "no-duplicate-variable": true,
44 | "no-empty": false,
45 | "no-eval": true,
46 | "no-inferrable-types": true,
47 | "no-shadowed-variable": true,
48 | "no-string-literal": false,
49 | "no-switch-case-fall-through": true,
50 | "no-trailing-whitespace": true,
51 | "no-unused-expression": true,
52 | "no-unused-variable": true,
53 | "no-unreachable": true,
54 | "no-use-before-declare": true,
55 | "no-var-keyword": true,
56 | "object-literal-sort-keys": false,
57 | "one-line": [
58 | true,
59 | "check-open-brace",
60 | "check-catch",
61 | "check-else",
62 | "check-whitespace"
63 | ],
64 | "quotemark": [
65 | true,
66 | "single"
67 | ],
68 | "radix": true,
69 | "semicolon": [
70 | "always"
71 | ],
72 | "triple-equals": [
73 | true,
74 | "allow-null-check"
75 | ],
76 | "typedef-whitespace": [
77 | true,
78 | {
79 | "call-signature": "nospace",
80 | "index-signature": "nospace",
81 | "parameter": "nospace",
82 | "property-declaration": "nospace",
83 | "variable-declaration": "nospace"
84 | }
85 | ],
86 | "variable-name": false,
87 | "whitespace": [
88 | true,
89 | "check-branch",
90 | "check-decl",
91 | "check-operator",
92 | "check-separator",
93 | "check-type"
94 | ],
95 |
96 | "directive-selector-name": [true, "camelCase"],
97 | "component-selector-name": [true, "kebab-case"],
98 | "directive-selector-type": [true, "attribute"],
99 | "component-selector-type": [true, "element"],
100 | "use-input-property-decorator": true,
101 | "use-output-property-decorator": true,
102 | "use-host-property-decorator": true,
103 | "no-input-rename": true,
104 | "no-output-rename": true,
105 | "use-life-cycle-interface": true,
106 | "use-pipe-transform-interface": true,
107 | "component-class-suffix": true,
108 | "directive-class-suffix": true
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # angular2-wizard
2 | [](https://badge.fury.io/js/angular2-wizard)
3 | [](https://travis-ci.org/maiyaporn/angular2-wizard)
4 | [](https://codeclimate.com/github/maiyaporn/angular2-wizard)
5 | [](https://codeclimate.com/github/maiyaporn/angular2-wizard/coverage)
6 |
7 | This is an Angular2 Form Wizard component. Just like any form wizard. You can define steps and control how each step works. You can enable/disable navigation button based on validity of the current step. Currently, the component only support basic functionality. More features will come later.
8 |
9 | You can checkout the demo below and see how to use it in the next section.
10 |
11 | ## Demo
12 | https://maiyaporn.github.io/angular2-wizard-demo/
13 |
14 | ## Dependencies
15 | - Angular2 (tested with 2.3.1)
16 | - Bootstrap 4
17 |
18 | ## Installation
19 |
20 | After installing the above dependencies, install angular2-wizard via:
21 |
22 | ```bash
23 | $ npm install angular2-wizard --save
24 | ```
25 |
26 | ## How to use the component
27 |
28 | Once you have installed the library, you can import it in `AppModule` of your application:
29 |
30 | ```typescript
31 | import { BrowserModule } from '@angular/platform-browser';
32 | import { NgModule } from '@angular/core';
33 | import { AppComponent } from './app.component';
34 |
35 | // Import your library
36 | import { FormWizardModule } from 'angular2-wizard';
37 |
38 | @NgModule({
39 | declarations: [
40 | AppComponent
41 | ],
42 | imports: [
43 | BrowserModule,
44 | // Specify the library as an import
45 | FormWizardModule
46 | ],
47 | providers: [],
48 | bootstrap: [AppComponent]
49 | })
50 | export class AppModule { }
51 | ```
52 |
53 | Once your library is imported, you can use form-wizard and wizard-step components in your Angular application:
54 |
55 | ```xml
56 |
57 |
58 | Step1
59 |
67 |
68 |
69 | Step2
70 |
71 |
72 | Step3
73 |
74 |
75 |
76 |
77 |
Step4
78 |
79 |
80 |
Thank you! You have completed all the steps.
81 |
82 |
83 |
84 |
85 | ```
86 |
87 | ## Document
88 | https://github.com/maiyaporn/angular2-wizard/wiki
89 |
90 | ## Development
91 |
92 | To generate all `*.js`, `*.js.map` and `*.d.ts` files:
93 |
94 | ```bash
95 | $ npm run tsc
96 | ```
97 |
98 | To lint all `*.ts` files:
99 |
100 | ```bash
101 | $ npm run lint
102 | ```
103 |
104 | ## Improvement
105 | - [x] Click title to navigate
106 | - [x] Hide/Show navigation button
107 | - [ ] Disable visited steps after navigate to previous
108 | - [ ] Dynamically add/remove step
109 |
110 |
111 | ## License
112 |
113 | MIT © [Maiyaporn Phanich](mailto:p.maiyaporn@gmail.com)
114 |
--------------------------------------------------------------------------------
/src/wizard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Output, EventEmitter, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
2 | import { WizardStepComponent } from './wizard-step.component';
3 |
4 | @Component({
5 | selector: 'form-wizard',
6 | template:
7 | `
8 |
15 |
16 |
17 |
18 |
23 |
`
24 | ,
25 | styles: [
26 | '.card { height: 100%; }',
27 | '.card-header { background-color: #fff; padding: 0; font-size: 1.25rem; }',
28 | '.card-block { overflow-y: auto; }',
29 | '.card-footer { background-color: #fff; border-top: 0 none; }',
30 | '.nav-item { padding: 1rem 0rem; border-bottom: 0.5rem solid #ccc; }',
31 | '.active { font-weight: bold; color: black; border-bottom-color: #1976D2 !important; }',
32 | '.enabled { cursor: pointer; border-bottom-color: rgb(88, 162, 234); }',
33 | '.disabled { color: #ccc; }',
34 | '.completed { cursor: default; }'
35 | ]
36 | })
37 | export class WizardComponent implements AfterContentInit {
38 | @ContentChildren(WizardStepComponent)
39 | wizardSteps: QueryList;
40 |
41 | private _steps: Array = [];
42 | private _isCompleted: boolean = false;
43 |
44 | @Output()
45 | onStepChanged: EventEmitter = new EventEmitter();
46 |
47 | constructor() { }
48 |
49 | ngAfterContentInit() {
50 | this.wizardSteps.forEach(step => this._steps.push(step));
51 | this.steps[0].isActive = true;
52 | }
53 |
54 | get steps(): Array {
55 | return this._steps.filter(step => !step.hidden);
56 | }
57 |
58 | get isCompleted(): boolean {
59 | return this._isCompleted;
60 | }
61 |
62 | get activeStep(): WizardStepComponent {
63 | return this.steps.find(step => step.isActive);
64 | }
65 |
66 | set activeStep(step: WizardStepComponent) {
67 | if (step !== this.activeStep && !step.isDisabled) {
68 | this.activeStep.isActive = false;
69 | step.isActive = true;
70 | this.onStepChanged.emit(step);
71 | }
72 | }
73 |
74 | public get activeStepIndex(): number {
75 | return this.steps.indexOf(this.activeStep);
76 | }
77 |
78 | get hasNextStep(): boolean {
79 | return this.activeStepIndex < this.steps.length - 1;
80 | }
81 |
82 | get hasPrevStep(): boolean {
83 | return this.activeStepIndex > 0;
84 | }
85 |
86 | public goToStep(step: WizardStepComponent): void {
87 | if (!this.isCompleted) {
88 | this.activeStep = step;
89 | }
90 | }
91 |
92 | public next(): void {
93 | if (this.hasNextStep) {
94 | let nextStep: WizardStepComponent = this.steps[this.activeStepIndex + 1];
95 | this.activeStep.onNext.emit();
96 | nextStep.isDisabled = false;
97 | this.activeStep = nextStep;
98 | }
99 | }
100 |
101 | public previous(): void {
102 | if (this.hasPrevStep) {
103 | let prevStep: WizardStepComponent = this.steps[this.activeStepIndex - 1];
104 | this.activeStep.onPrev.emit();
105 | prevStep.isDisabled = false;
106 | this.activeStep = prevStep;
107 | }
108 | }
109 |
110 | public complete(): void {
111 | this.activeStep.onComplete.emit();
112 | this._isCompleted = true;
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------