├── .gitattributes ├── LICENSE ├── README.md ├── app ├── address │ ├── address.component.js │ ├── address.controller.js │ └── address.html ├── app.module.js ├── form │ ├── form.component.js │ ├── form.controller.js │ ├── form.html │ └── formData.value.js ├── personal │ ├── personal.component.js │ ├── personal.controller.js │ └── personal.html ├── result │ ├── result.component.js │ ├── result.controller.js │ └── result.html └── work │ ├── work.component.js │ ├── work.controller.js │ └── work.html ├── content ├── css │ ├── riliwan-rabo.css │ └── style.css └── images │ └── favicon.ico ├── index.html ├── screen-address.png ├── screen-personal.png ├── screen-result.png └── screen-work.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-language=JavaScript -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cathy Wun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Series 2: Tutorials on building a Multi-Step Wizard using UI-Router 1.0 with Angular 1.5+ component-based architecture 2 | 3 | #### [Demo site](http://multi-step-wizard.azurewebsites.net/series-2/#/form/personal) 4 | 5 | *** Personal *** 6 | ![Picture](https://github.com/cwun/ng-multi-step-wizard-ui-router1/blob/master/screen-personal.png) 7 | 8 | *** Work *** 9 | ![Picture](https://github.com/cwun/ng-multi-step-wizard-ui-router1/blob/master/screen-work.png) 10 | 11 | *** Address *** 12 | ![Picture](https://github.com/cwun/ng-multi-step-wizard-ui-router1/blob/master/screen-address.png) 13 | 14 | *** Result *** 15 | ![Picture](https://github.com/cwun/ng-multi-step-wizard-ui-router1/blob/master/screen-result.png) 16 | -------------------------------------------------------------------------------- /app/address/address.component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .component('addressComponent', { 7 | templateUrl: 'app/address/address.html', 8 | controller: 'AddressController', 9 | controllerAs: 'vm', 10 | require: { 11 | // access to the functionality of the parent component called 'formComponent' 12 | parent: '^formComponent' 13 | }, 14 | bindings: { 15 | // send a changeset of 'formData' upwards to the parent component called 'formComponent' 16 | formData: '<' 17 | } 18 | }) 19 | })(); -------------------------------------------------------------------------------- /app/address/address.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .controller('AddressController', AddressController); 7 | 8 | AddressController.$inject = []; 9 | 10 | function AddressController() { 11 | var vm = this; 12 | vm.title = 'Where do you live?'; 13 | vm.formData = {}; 14 | 15 | vm.$onInit = activate; 16 | 17 | //////////////// 18 | 19 | function activate() { 20 | // get data from the parent component 21 | vm.formData = vm.parent.getData(); 22 | console.log('Address feature loaded!'); 23 | } 24 | } 25 | })(); -------------------------------------------------------------------------------- /app/address/address.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{vm.title}}

4 |
5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 | Previous 36 | Next 37 |
38 |
39 |
-------------------------------------------------------------------------------- /app/app.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | // Creating our angular app and inject ui-router 5 | // ============================================================================= 6 | var app = angular.module('wizardApp', ['ui.router']) 7 | 8 | // Configuring our states 9 | // ============================================================================= 10 | app.config(['$stateProvider', '$urlRouterProvider', 11 | 12 | function($stateProvider, $urlRouterProvider) { 13 | 14 | // For any unmatched url, redirect to /wizard/personal 15 | $urlRouterProvider.otherwise('/form/personal'); 16 | 17 | $stateProvider 18 | // PARENT STATE: form state 19 | .state('form', { 20 | url: '/form', 21 | component: 'formComponent' 22 | }) 23 | 24 | // NESTED STATES: child states of 'form' state 25 | // URL will become '/form/personal' 26 | .state('form.personal', { 27 | url: '/personal', 28 | component: 'personalComponent' 29 | }) 30 | 31 | // URL will become /form/work 32 | .state('form.work', { 33 | url: '/work', 34 | component: 'workComponent' 35 | }) 36 | 37 | // URL will become /form/address 38 | .state('form.address', { 39 | url: '/address', 40 | component: 'addressComponent' 41 | }) 42 | 43 | // URL will become /form/result 44 | .state('form.result', { 45 | url: '/result', 46 | component: 'resultComponent' 47 | }) 48 | } 49 | ]); 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /app/form/form.component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .component('formComponent', { 7 | templateUrl: 'app/form/form.html', 8 | controller: 'FormController', 9 | controllerAs: 'vm' 10 | }) 11 | })(); -------------------------------------------------------------------------------- /app/form/form.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .controller('FormController', FormController); 7 | 8 | FormController.$inject = ['FormDataModel']; 9 | 10 | function FormController(FormDataModel) { 11 | var vm = this; 12 | vm.title = 'Multi-Step Wizard'; 13 | // we will store all of our form data in this object 14 | vm.formData = new FormDataModel(); 15 | 16 | vm.$onInit = activate; 17 | vm.getData = getData; 18 | 19 | //////////////// 20 | 21 | function activate() { 22 | console.log(vm.title + ' loaded!'); 23 | } 24 | 25 | function getData() { 26 | return vm.formData; 27 | } 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /app/form/form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 45 |
46 |
47 | 48 | 49 | 50 |
51 | 52 |
53 |
54 | 55 |
56 | 57 | 58 |
{{ vm.formData | json }}
59 |
60 | 61 | -------------------------------------------------------------------------------- /app/form/formData.value.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .value('FormDataModel', FormDataModel); 7 | 8 | function FormDataModel() { 9 | this.firstName = ''; 10 | this.lastName = ''; 11 | this.email = ''; 12 | this.work = 'Code'; 13 | this.street = ''; 14 | this.city = ''; 15 | this.state = ''; 16 | this.zip = ''; 17 | } 18 | })(); -------------------------------------------------------------------------------- /app/personal/personal.component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .component('personalComponent', { 7 | templateUrl: 'app/personal/personal.html', 8 | controller: 'PersonalController', 9 | controllerAs: 'vm', 10 | require: { 11 | // access to the functionality of the parent component called 'formComponent' 12 | parent: '^formComponent' 13 | }, 14 | bindings: { 15 | // send a changeset of 'formData' upwards to the parent component called 'formComponent' 16 | formData: '<' 17 | } 18 | }) 19 | })(); -------------------------------------------------------------------------------- /app/personal/personal.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .controller('PersonalController', PersonalController); 7 | 8 | PersonalController.$inject = []; 9 | 10 | function PersonalController() { 11 | var vm = this; 12 | vm.title = 'Please tell us about yourself.'; 13 | vm.formData = {}; 14 | 15 | vm.$onInit = activate; 16 | 17 | //////////////// 18 | 19 | function activate() { 20 | // get data from the parent component 21 | vm.formData = vm.parent.getData(); 22 | console.log('Personal feature loaded!'); 23 | } 24 | 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /app/personal/personal.html: -------------------------------------------------------------------------------- 1 |
2 |

{{vm.title}}

3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 |
24 | 25 |
26 | Next 27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /app/result/result.component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .component('resultComponent', { 7 | templateUrl: 'app/result/result.html', 8 | controller: 'ResultController', 9 | controllerAs: 'vm', 10 | require: { 11 | // access to the functionality of the parent component called 'formComponent' 12 | parent: '^formComponent' 13 | } 14 | }) 15 | })(); -------------------------------------------------------------------------------- /app/result/result.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .controller('ResultController', ResultController); 7 | 8 | ResultController.$inject = []; 9 | 10 | function ResultController() { 11 | var vm = this; 12 | vm.title = 'Thanks for staying tuned!'; 13 | vm.formData = {}; 14 | 15 | vm.$onInit = activate; 16 | 17 | //////////////// 18 | 19 | function activate() { 20 | // get data from the parent component 21 | vm.formData = vm.parent.getData(); 22 | console.log('Result feature loaded!'); 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /app/result/result.html: -------------------------------------------------------------------------------- 1 |
2 |

{{vm.title}}

3 |

4 | Made with by Cathy Wun. Check out the tutorials here. 5 |

6 |

7 | Here is a copy of the information you have entered: 8 |

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | {{vm.formData.firstName + ' ' + vm.formData.lastName}} 20 |
21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 | {{vm.formData.email}} 30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 | {{vm.formData.work}} 40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | {{vm.formData.street}} 50 |
51 | {{vm.formData.city + ' ' + vm.formData.state + ' ' + vm.formData.zip}} 52 |
53 |
54 |
55 |
56 |
-------------------------------------------------------------------------------- /app/work/work.component.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .component('workComponent', { 7 | templateUrl: 'app/work/work.html', 8 | controller: 'WorkController', 9 | controllerAs: 'vm', 10 | require: { 11 | // access to the functionality of the parent component called 'formComponent' 12 | parent: '^formComponent' 13 | }, 14 | bindings: { 15 | // send a changeset of 'formData' upwards to the parent component called 'formComponent' 16 | formData: '<' 17 | } 18 | }) 19 | })(); -------------------------------------------------------------------------------- /app/work/work.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('wizardApp') 6 | .controller('WorkController', WorkController); 7 | 8 | WorkController.$inject = []; 9 | 10 | function WorkController() { 11 | var vm = this; 12 | vm.title = 'What do you do?'; 13 | vm.formData = {}; 14 | 15 | vm.$onInit = activate; 16 | 17 | //////////////// 18 | 19 | function activate() { 20 | // get data from the parent component 21 | vm.formData = vm.parent.getData(); 22 | console.log('Work feature loaded!'); 23 | } 24 | } 25 | })(); -------------------------------------------------------------------------------- /app/work/work.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{vm.title}}

4 |
5 |
6 |
7 |
8 |
9 | 13 |
14 |
15 | 19 |
20 |
21 | 25 |
26 |
27 |
28 |
29 | 30 |
31 | Previous 32 | Next 33 |
34 |
35 |
-------------------------------------------------------------------------------- /content/css/riliwan-rabo.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Roboto+Condensed:400,700); 2 | /* written by riliwan balogun http://www.facebook.com/riliwan.rabo*/ 3 | .board{ 4 | width: 75%; 5 | margin: 60px auto; 6 | height: 500px; 7 | background: #fff; 8 | /*box-shadow: 10px 10px #ccc,-10px 20px #ddd;*/ 9 | } 10 | .board .nav-tabs { 11 | position: relative; 12 | /* border-bottom: 0; */ 13 | /* width: 80%; */ 14 | margin: 40px auto; 15 | margin-bottom: 0; 16 | box-sizing: border-box; 17 | 18 | } 19 | 20 | .board > div.board-inner{ 21 | background: #fafafa url(http://subtlepatterns.com/patterns/geometry2.png); 22 | background-size: 30%; 23 | } 24 | 25 | p.narrow{ 26 | width: 60%; 27 | margin: 10px auto; 28 | } 29 | 30 | .liner{ 31 | height: 2px; 32 | background: #ddd; 33 | position: absolute; 34 | width: 80%; 35 | margin: 0 auto; 36 | left: 0; 37 | right: 0; 38 | top: 50%; 39 | z-index: 1; 40 | } 41 | 42 | .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { 43 | color: #555555; 44 | cursor: default; 45 | /* background-color: #ffffff; */ 46 | border: 0; 47 | border-bottom-color: transparent; 48 | } 49 | 50 | span.round-tabs{ 51 | width: 70px; 52 | height: 70px; 53 | line-height: 70px; 54 | display: inline-block; 55 | border-radius: 100px; 56 | background: white; 57 | z-index: 2; 58 | position: absolute; 59 | left: 0; 60 | text-align: center; 61 | font-size: 25px; 62 | } 63 | 64 | span.round-tabs.one{ 65 | color: rgb(34, 194, 34);border: 2px solid rgb(34, 194, 34); 66 | } 67 | 68 | li.active span.round-tabs.one{ 69 | background: #fff !important; 70 | border: 2px solid #ddd; 71 | color: rgb(34, 194, 34); 72 | } 73 | 74 | span.round-tabs.two{ 75 | color: #febe29;border: 2px solid #febe29; 76 | } 77 | 78 | li.active span.round-tabs.two{ 79 | background: #fff !important; 80 | border: 2px solid #ddd; 81 | color: #febe29; 82 | } 83 | 84 | span.round-tabs.three{ 85 | color: #3e5e9a;border: 2px solid #3e5e9a; 86 | } 87 | 88 | li.active span.round-tabs.three{ 89 | background: #fff !important; 90 | border: 2px solid #ddd; 91 | color: #3e5e9a; 92 | } 93 | 94 | span.round-tabs.four{ 95 | color: #f1685e;border: 2px solid #f1685e; 96 | } 97 | 98 | li.active span.round-tabs.four{ 99 | background: #fff !important; 100 | border: 2px solid #ddd; 101 | color: #f1685e; 102 | } 103 | 104 | span.round-tabs.five{ 105 | color: #999;border: 2px solid #999; 106 | } 107 | 108 | li.active span.round-tabs.five{ 109 | background: #fff !important; 110 | border: 2px solid #ddd; 111 | color: #999; 112 | } 113 | 114 | .nav-tabs > li.active > a span.round-tabs{ 115 | background: #fafafa; 116 | } 117 | .nav-tabs > li { 118 | /*width: 20%;*/ 119 | width: 25%; 120 | } 121 | /*li.active:before { 122 | content: " "; 123 | position: absolute; 124 | left: 45%; 125 | opacity:0; 126 | margin: 0 auto; 127 | bottom: -2px; 128 | border: 10px solid transparent; 129 | border-bottom-color: #fff; 130 | z-index: 1; 131 | transition:0.2s ease-in-out; 132 | }*/ 133 | .nav-tabs > li:after { 134 | content: " "; 135 | position: absolute; 136 | left: 45%; 137 | opacity:0; 138 | margin: 0 auto; 139 | bottom: 0px; 140 | border: 5px solid transparent; 141 | border-bottom-color: #ddd; 142 | transition:0.1s ease-in-out; 143 | 144 | } 145 | .nav-tabs > li.active:after { 146 | content: " "; 147 | position: absolute; 148 | left: 45%; 149 | opacity:1; 150 | margin: 0 auto; 151 | bottom: 0px; 152 | border: 10px solid transparent; 153 | border-bottom-color: #ddd; 154 | 155 | } 156 | .nav-tabs > li a{ 157 | width: 70px; 158 | height: 70px; 159 | margin: 20px auto; 160 | border-radius: 100%; 161 | padding: 0; 162 | } 163 | 164 | .nav-tabs > li a:hover{ 165 | background: transparent; 166 | } 167 | 168 | .tab-content .tab-pane{ 169 | position: relative; 170 | padding-top: 50px; 171 | } 172 | .tab-content .head{ 173 | font-family: 'Roboto Condensed', sans-serif; 174 | font-size: 25px; 175 | text-transform: uppercase; 176 | padding-bottom: 10px; 177 | } 178 | .btn-outline-rounded{ 179 | padding: 10px 40px; 180 | margin: 20px 0; 181 | border: 2px solid transparent; 182 | border-radius: 25px; 183 | } 184 | 185 | .btn.green{ 186 | background-color:#5cb85c; 187 | /*border: 2px solid #5cb85c;*/ 188 | color: #ffffff; 189 | } 190 | 191 | 192 | 193 | @media( max-width : 585px ){ 194 | 195 | .board { 196 | width: 90%; 197 | height:auto !important; 198 | } 199 | span.round-tabs { 200 | font-size:16px; 201 | width: 50px; 202 | height: 50px; 203 | line-height: 50px; 204 | } 205 | .tab-content .head{ 206 | font-size:20px; 207 | } 208 | .nav-tabs > li a { 209 | width: 50px; 210 | height: 50px; 211 | line-height:50px; 212 | } 213 | 214 | .nav-tabs > li.active:after { 215 | content: " "; 216 | position: absolute; 217 | left: 35%; 218 | } 219 | 220 | .btn-outline-rounded { 221 | padding:12px 20px; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /content/css/style.css: -------------------------------------------------------------------------------- 1 | .btn-default { 2 | border-color: #ccc; 3 | } 4 | 5 | .tab-content .choice { 6 | text-align: center; 7 | cursor: pointer; 8 | margin-top: 38px; 9 | } 10 | 11 | .tab-content .choice i { 12 | font-size: 32px; 13 | line-height: 55px; 14 | } 15 | 16 | .btn-radio { 17 | width: 100%; 18 | } 19 | .img-radio { 20 | opacity: 0.8; 21 | margin-bottom: 5px; 22 | } 23 | 24 | .space-20 { 25 | margin-top: 20px; 26 | } 27 | 28 | /* active buttons */ 29 | #status-buttons a.active span.round-tabs.one { 30 | background: rgb(34, 194, 34); 31 | color: #fff 32 | } 33 | 34 | #status-buttons a.active span.round-tabs.two { 35 | background: #febe29; 36 | color: #fff 37 | } 38 | 39 | #status-buttons a.active span.round-tabs.three { 40 | background: #3e5e9a; 41 | color: #fff 42 | } 43 | 44 | #status-buttons a.active span.round-tabs.four { 45 | background: #f1685e; 46 | color: #fff 47 | } 48 | 49 | 50 | .iradio_buttons { 51 | display: inline-block; 52 | vertical-align: middle; 53 | margin: 0; 54 | padding: 0; 55 | width: 22px; 56 | height: 22px; 57 | background: #febe29 no-repeat; 58 | border: none; 59 | cursor: pointer; 60 | } 61 | .iradio_buttons { 62 | background-position: -120px 0; 63 | } 64 | .iradio_buttons.hover { 65 | background-position: -144px 0; 66 | } 67 | .iradio_buttons.checked { 68 | background-position: -168px 0; 69 | } -------------------------------------------------------------------------------- /content/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwun/ng-multi-step-wizard-ui-router1/bdb090c5582936b1a2778bd8a3db87eafc038419/content/images/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Multi-Step Wizard using AngularJS 1.5+ And UI-Router 1.0 by Cathy Wun 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /screen-address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwun/ng-multi-step-wizard-ui-router1/bdb090c5582936b1a2778bd8a3db87eafc038419/screen-address.png -------------------------------------------------------------------------------- /screen-personal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwun/ng-multi-step-wizard-ui-router1/bdb090c5582936b1a2778bd8a3db87eafc038419/screen-personal.png -------------------------------------------------------------------------------- /screen-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwun/ng-multi-step-wizard-ui-router1/bdb090c5582936b1a2778bd8a3db87eafc038419/screen-result.png -------------------------------------------------------------------------------- /screen-work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwun/ng-multi-step-wizard-ui-router1/bdb090c5582936b1a2778bd8a3db87eafc038419/screen-work.png --------------------------------------------------------------------------------