├── 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 | [![npm version](https://badge.fury.io/js/angular2-wizard.svg)](https://badge.fury.io/js/angular2-wizard) 3 | [![Build Status](https://travis-ci.org/maiyaporn/angular2-wizard.svg?branch=master)](https://travis-ci.org/maiyaporn/angular2-wizard) 4 | [![Code Climate](https://codeclimate.com/github/maiyaporn/angular2-wizard/badges/gpa.svg)](https://codeclimate.com/github/maiyaporn/angular2-wizard) 5 | [![Test Coverage](https://codeclimate.com/github/maiyaporn/angular2-wizard/badges/coverage.svg)](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 |
60 |
61 | 62 | 64 | We'll never share your email with anyone else. 65 |
66 |
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 |
9 | 14 |
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 | --------------------------------------------------------------------------------