├── .gitignore ├── README.md ├── bower.json ├── examples ├── login-with-disable │ ├── index.html │ └── scripts │ │ ├── LoginApp.js │ │ └── LoginController.js ├── login-with-mailgun │ ├── index.html │ └── scripts │ │ ├── LoginApp.js │ │ └── LoginController.js ├── login-with-password-managers │ ├── index.html │ └── scripts │ │ ├── LoginApp.js │ │ └── LoginController.js ├── simple-login-form │ ├── index.html │ └── scripts │ │ ├── LoginApp.js │ │ └── LoginController.js └── uber-wizard │ ├── index.html │ ├── scripts │ ├── SampleWizardApp.js │ └── SampleWizardController.js │ └── styles │ └── rcWizard.css ├── lib ├── jquery.bootstrap.wizard.js └── mailgun_validator.js ├── mit-license.txt └── src ├── directives ├── rcSubmit.js └── rcVerifySet.js └── modules ├── rcDisabled.js ├── rcForm.js ├── rcMailgun.js └── rcWizard.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angularjs-utilities 2 | =================== 3 | 4 | Hopefully useful directives, providers, filters, etc for AngularJS 5 | 6 | This is a repository of all the custom AngularJS components we created that we thought could be helpful to anyone using Angular. 7 | 8 | ##Included Modules 9 | + rcForm - This module contains all custom directives, providers, filters, etc. related to forms. 10 | + rcSubmit - This directive mimics the standard ngSubmit directive, but cancels the submit event if the form isn't valid. It also tracks whether a submission has been attempted on a form. 11 | + rcVerifySet - This directive enforces that the scope is updated before a form submission on decorated elements. This was created to work with browser plugins like Password Managers that can manipulate the DOM, but may not fire the appropriate events that Angular listens to. 12 | 13 | + rcMailgun - This module contains the custom directive and provider used to configure and use the mailgun api validation service (http://blog.mailgun.com/post/free-email-validation-api-for-web-forms). 14 | + rcMailgunProvider - This provider allows you to configure the mailgun validation api and get its status. 15 | + rcMailgunValid - This directive is used in conjunction with the mailgun jQuery plug-in to validate e-mail addressses. 16 | 17 | + rcDisabled - This module contains the custom directive and provider used to easily disable UI elements during long running operations. 18 | + rcDisabledProvider - This provider allows you to customize the logic used when the UI is being disabled. 19 | + rcDisabled - This directive so be placed on elements that need to be disabled. 20 | 21 | + rcDisabledBootstrap - This module is just an augmented version of rcDisabled with a custom disable method specifically designed to work with Bootstrap. 22 | 23 | + rcWizard - This module contains the directives used to configure and use a Wizard UI. It is dependent on the unofficial third-party jQuery Bootstrap Wizard Plug-in (http://vadimg.com/twitter-bootstrap-wizard-example/) which in turn is dependent on the official Bootstrap "Tabs" jQuery Plug-in (http://getbootstrap.com/javascript/#tabs). 24 | + rcWizard - This directive configures and manages all the wizard behavior. 25 | + rcStep - This directive is used when advanced features are required at the step level. It is not required for basic functionality 26 | 27 | 28 | ##License 29 | Copyright (c) 2013 RealCrowd 30 | http://www.realcrowd.com 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "name": "angularjs-utilities", 4 | 5 | "version": "0.0.9", 6 | 7 | "homepage": "https://github.com/realcrowd/angularjs-utilities", 8 | 9 | "authors": [ 10 | "realcrowd (dev@realcrowd.com)" 11 | 12 | ], 13 | 14 | "description": "Hopefully useful directives, providers, filters, etc for AngularJS", 15 | 16 | "keywords": [ 17 | 18 | "angular", 19 | 20 | "directives", 21 | 22 | "modules", 23 | 24 | "validation" 25 | 26 | ], 27 | 28 | "license": "MIT", 29 | 30 | "ignore": [ 31 | 32 | "**/.*", 33 | 34 | "node_modules", 35 | "bower_components", 36 | 37 | "test", 38 | 39 | "tests" 40 | 41 | ] 42 | , 43 | "dependencies": 44 | { 45 | "angular": "1.*" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/login-with-disable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 |
17 |
18 |
19 |

Simple Login Form

20 |
22 |
24 | 26 | Required 28 |
29 |
31 | 33 | Required 35 |
36 |
37 | 41 |
42 |
43 |
44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /examples/login-with-disable/scripts/LoginApp.js: -------------------------------------------------------------------------------- 1 | // define module for app 2 | angular.module('LoginApp', ['rcForm', 'rcDisabledBootstrap']); -------------------------------------------------------------------------------- /examples/login-with-disable/scripts/LoginController.js: -------------------------------------------------------------------------------- 1 | var LoginController = ['$scope', '$q', '$timeout', 2 | function ($scope, $q, $timeout) { 3 | 4 | $scope.session = {}; 5 | 6 | $scope.login = function () { 7 | // process $scope.session 8 | 9 | var deferred = $q.defer(); 10 | 11 | // fake a long running task 12 | $timeout(function() { 13 | 14 | deferred.resolve(); 15 | alert('logged in!'); 16 | }, 5000); 17 | 18 | return deferred.promise; 19 | }; 20 | }]; -------------------------------------------------------------------------------- /examples/login-with-mailgun/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 |
15 |
16 |
17 |

Login - with Mailgun Validator

18 |
19 |
20 | 22 | 23 | Required 24 | Invalid Email Address 25 | Validating Email Address 26 | Validation Error: {{ rcMailgun.mailgunStatus }} 27 | 28 |
29 |
30 | 32 | Required 33 |
34 |
35 | 38 |
39 |
40 |
41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /examples/login-with-mailgun/scripts/LoginApp.js: -------------------------------------------------------------------------------- 1 | angular.module('LoginApp', ['rcMailgun']) 2 | 3 | .config(['rcMailgunProvider', function (rcMailgunProvider) { 4 | 5 | var mailgunOptions = { 6 | api_key: '40733976039a7f5c8193eea7618d0313aababe20', 7 | in_progress: null, 8 | success: null, 9 | error: null, 10 | }; 11 | 12 | rcMailgunProvider.configure(mailgunOptions); 13 | }]); -------------------------------------------------------------------------------- /examples/login-with-mailgun/scripts/LoginController.js: -------------------------------------------------------------------------------- 1 | var LoginController = ['$scope', 'rcMailgun', 2 | function ($scope, rcMailgun) { 3 | 4 | $scope.session = {}; 5 | $scope.rcMailgun = rcMailgun; 6 | 7 | $scope.login = function () { 8 | // process $scope.session 9 | alert('logged in!'); 10 | }; 11 | }]; -------------------------------------------------------------------------------- /examples/login-with-password-managers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 |
15 |
16 |
17 |

Simple Login Form

18 |
20 |
22 | 24 | Required 26 |
27 |
29 | 31 | Required 33 |
34 |
35 | 38 |
39 |
40 |
41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /examples/login-with-password-managers/scripts/LoginApp.js: -------------------------------------------------------------------------------- 1 | // define module for app 2 | angular.module('LoginApp', ['rcForm']); -------------------------------------------------------------------------------- /examples/login-with-password-managers/scripts/LoginController.js: -------------------------------------------------------------------------------- 1 | var LoginController = ['$scope', 2 | function ($scope) { 3 | 4 | $scope.session = {}; 5 | 6 | $scope.login = function () { 7 | // process $scope.session 8 | alert('logged in!'); 9 | }; 10 | }]; -------------------------------------------------------------------------------- /examples/simple-login-form/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 |
14 |
15 |
16 |

Simple Login Form

17 |
20 |
22 | 24 | Required 26 |
27 |
29 | 31 | Required 33 |
34 |
35 | 39 |
40 |
41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /examples/simple-login-form/scripts/LoginApp.js: -------------------------------------------------------------------------------- 1 | // define module for app 2 | angular.module('LoginApp', ['rcForm']); -------------------------------------------------------------------------------- /examples/simple-login-form/scripts/LoginController.js: -------------------------------------------------------------------------------- 1 | var LoginController = ['$scope', 2 | function ($scope) { 3 | 4 | $scope.session = {}; 5 | 6 | $scope.login = function () { 7 | // process $scope.session 8 | alert('logged in!'); 9 | }; 10 | }]; -------------------------------------------------------------------------------- /examples/uber-wizard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Simple Wizard

23 |
25 | 45 |
46 |
48 |

Enter first step data

49 |
51 | 52 | 54 |
55 |
57 | 58 | 60 |
61 |
62 |
63 |

Enter second step data

64 |
65 | 66 | 68 |
69 |
70 | 71 | 73 |
74 |
75 | 76 | 78 |
79 |
80 | 81 | 83 |
84 |
85 |
86 |

Finish last step

87 |
88 | 89 |

{{ user.firstName }}

90 |
91 |
92 | 93 |

{{ user.lastName }}

94 |
95 |
96 | 97 |

98 | {{ user.streetAddress }} 99 |
100 | {{ user.city }}, {{ user.state }} {{ user.postalCode }} 101 |

102 |
103 |
104 |
105 |
106 |
107 | Back 109 | Continue 111 | Complete 113 |
114 |
115 |
116 |
117 |
118 |
119 | 120 | 121 | -------------------------------------------------------------------------------- /examples/uber-wizard/scripts/SampleWizardApp.js: -------------------------------------------------------------------------------- 1 | // define module for app 2 | angular.module('SampleWizardApp', ['rcWizard', 'rcForm', 'rcDisabledBootstrap']); -------------------------------------------------------------------------------- /examples/uber-wizard/scripts/SampleWizardController.js: -------------------------------------------------------------------------------- 1 | // define controller for wizard 2 | var SampleWizardController = ['$scope', '$q', '$timeout', 3 | function ($scope, $q, $timeout) { 4 | 5 | $scope.user = {}; 6 | 7 | $scope.saveState = function() { 8 | var deferred = $q.defer(); 9 | 10 | $timeout(function() { 11 | deferred.resolve(); 12 | }, 5000); 13 | 14 | return deferred.promise; 15 | }; 16 | 17 | $scope.completeWizard = function() { 18 | alert('Completed!'); 19 | } 20 | }]; -------------------------------------------------------------------------------- /examples/uber-wizard/styles/rcWizard.css: -------------------------------------------------------------------------------- 1 | .rc-nav-wizard > li { 2 | float: left; 3 | font-size: 18px; 4 | } 5 | 6 | .rc-nav-wizard > li + li { 7 | margin-left: 2px; 8 | } 9 | 10 | .rc-nav-wizard > li > a { 11 | border-radius: 5px; 12 | cursor: default; 13 | color: #999; 14 | } 15 | 16 | .rc-nav-wizard > li > a, 17 | .rc-nav-wizard > li > a:hover, 18 | .rc-nav-wizard > li > a:focus { 19 | background-color: transparent; 20 | } 21 | 22 | .rc-nav-wizard > li > a > .badge { 23 | margin-left: 3px; 24 | font-size: 18px; 25 | padding: 5px 9px; 26 | border-radius: 15px; 27 | } 28 | 29 | /* active = current wizard step */ 30 | .rc-nav-wizard > li.active > a, 31 | .rc-nav-wizard > li.active > a:hover, 32 | .rc-nav-wizard > li.active > a:focus { 33 | color: #428bca; 34 | background-color: transparent; 35 | } 36 | 37 | .rc-nav-wizard > .active > a > .badge { 38 | color: #ffffff; 39 | background-color: #428bca; 40 | } 41 | 42 | /* success = completed wizard step */ 43 | .rc-nav-wizard > li.success > a, 44 | .rc-nav-wizard > li.success > a:hover, 45 | .rc-nav-wizard > li.success > a:focus { 46 | color: #D4AF37; 47 | background-color: transparent; 48 | } 49 | 50 | .rc-nav-wizard > .success > a > .badge { 51 | color: #ffffff; 52 | background-color: #D4AF37; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /lib/jquery.bootstrap.wizard.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery twitter bootstrap wizard plugin 3 | * Examples and documentation at: http://github.com/VinceG/twitter-bootstrap-wizard 4 | * version 1.0 5 | * Requires jQuery v1.3.2 or later 6 | * Supports Bootstrap 2.2.x, 2.3.x, 3.0 7 | * Dual licensed under the MIT and GPL licenses: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * http://www.gnu.org/licenses/gpl.html 10 | * Authors: Vadim Vincent Gabriel (http://vadimg.com), Jason Gill (www.gilluminate.com) 11 | */ 12 | ;(function($) { 13 | var bootstrapWizardCreate = function(element, options) { 14 | var element = $(element); 15 | var obj = this; 16 | 17 | // selector skips any 'li' elements that do not contain a child with a tab data-toggle 18 | var baseItemSelector = 'li:has([data-toggle="tab"])'; 19 | 20 | // Merge options with defaults 21 | var $settings = $.extend({}, $.fn.bootstrapWizard.defaults, options); 22 | var $activeTab = null; 23 | var $navigation = null; 24 | 25 | this.rebindClick = function(selector, fn) 26 | { 27 | selector.unbind('click', fn).bind('click', fn); 28 | } 29 | 30 | this.fixNavigationButtons = function() { 31 | // Get the current active tab 32 | if(!$activeTab.length) { 33 | // Select first one 34 | $navigation.find('a:first').tab('show'); 35 | $activeTab = $navigation.find(baseItemSelector + ':first'); 36 | } 37 | 38 | // See if we're currently in the first/last then disable the previous and last buttons 39 | $($settings.previousSelector, element).toggleClass('disabled', (obj.firstIndex() >= obj.currentIndex())); 40 | $($settings.nextSelector, element).toggleClass('disabled', (obj.currentIndex() >= obj.navigationLength())); 41 | 42 | // We are unbinding and rebinding to ensure single firing and no double-click errors 43 | obj.rebindClick($($settings.nextSelector, element), obj.next); 44 | obj.rebindClick($($settings.previousSelector, element), obj.previous); 45 | obj.rebindClick($($settings.lastSelector, element), obj.last); 46 | obj.rebindClick($($settings.firstSelector, element), obj.first); 47 | 48 | if($settings.onTabShow && typeof $settings.onTabShow === 'function' && $settings.onTabShow($activeTab, $navigation, obj.currentIndex())===false){ 49 | return false; 50 | } 51 | }; 52 | 53 | this.next = function(e) { 54 | 55 | // If we clicked the last then dont activate this 56 | if(element.hasClass('last')) { 57 | return false; 58 | } 59 | 60 | if($settings.onNext && typeof $settings.onNext === 'function' && $settings.onNext($activeTab, $navigation, obj.nextIndex())===false){ 61 | return false; 62 | } 63 | 64 | // Did we click the last button 65 | $index = obj.nextIndex(); 66 | if($index > obj.navigationLength()) { 67 | } else { 68 | $navigation.find(baseItemSelector + ':eq('+$index+') a').tab('show'); 69 | } 70 | }; 71 | 72 | this.previous = function(e) { 73 | 74 | // If we clicked the first then dont activate this 75 | if(element.hasClass('first')) { 76 | return false; 77 | } 78 | 79 | if($settings.onPrevious && typeof $settings.onPrevious === 'function' && $settings.onPrevious($activeTab, $navigation, obj.previousIndex())===false){ 80 | return false; 81 | } 82 | 83 | $index = obj.previousIndex(); 84 | if($index < 0) { 85 | } else { 86 | $navigation.find(baseItemSelector + ':eq('+$index+') a').tab('show'); 87 | } 88 | }; 89 | 90 | this.first = function(e) { 91 | if($settings.onFirst && typeof $settings.onFirst === 'function' && $settings.onFirst($activeTab, $navigation, obj.firstIndex())===false){ 92 | return false; 93 | } 94 | 95 | // If the element is disabled then we won't do anything 96 | if(element.hasClass('disabled')) { 97 | return false; 98 | } 99 | $navigation.find(baseItemSelector + ':eq(0) a').tab('show'); 100 | 101 | }; 102 | this.last = function(e) { 103 | if($settings.onLast && typeof $settings.onLast === 'function' && $settings.onLast($activeTab, $navigation, obj.lastIndex())===false){ 104 | return false; 105 | } 106 | 107 | // If the element is disabled then we won't do anything 108 | if(element.hasClass('disabled')) { 109 | return false; 110 | } 111 | $navigation.find(baseItemSelector + ':eq('+obj.navigationLength()+') a').tab('show'); 112 | }; 113 | this.currentIndex = function() { 114 | return $navigation.find(baseItemSelector).index($activeTab); 115 | }; 116 | this.firstIndex = function() { 117 | return 0; 118 | }; 119 | this.lastIndex = function() { 120 | return obj.navigationLength(); 121 | }; 122 | this.getIndex = function(e) { 123 | return $navigation.find(baseItemSelector).index(e); 124 | }; 125 | this.nextIndex = function() { 126 | return $navigation.find(baseItemSelector).index($activeTab) + 1; 127 | }; 128 | this.previousIndex = function() { 129 | return $navigation.find(baseItemSelector).index($activeTab) - 1; 130 | }; 131 | this.navigationLength = function() { 132 | return $navigation.find(baseItemSelector).length - 1; 133 | }; 134 | this.activeTab = function() { 135 | return $activeTab; 136 | }; 137 | this.nextTab = function() { 138 | return $navigation.find(baseItemSelector + ':eq('+(obj.currentIndex()+1)+')').length ? $navigation.find(baseItemSelector + ':eq('+(obj.currentIndex()+1)+')') : null; 139 | }; 140 | this.previousTab = function() { 141 | if(obj.currentIndex() <= 0) { 142 | return null; 143 | } 144 | return $navigation.find(baseItemSelector + ':eq('+parseInt(obj.currentIndex()-1)+')'); 145 | }; 146 | this.show = function(index) { 147 | return element.find(baseItemSelector + ':eq(' + index + ') a').tab('show'); 148 | }; 149 | this.disable = function(index) { 150 | $navigation.find(baseItemSelector + ':eq('+index+')').addClass('disabled'); 151 | }; 152 | this.enable = function(index) { 153 | $navigation.find(baseItemSelector + ':eq('+index+')').removeClass('disabled'); 154 | }; 155 | this.hide = function(index) { 156 | $navigation.find(baseItemSelector + ':eq('+index+')').hide(); 157 | }; 158 | this.display = function(index) { 159 | $navigation.find(baseItemSelector + ':eq('+index+')').show(); 160 | }; 161 | this.remove = function(args) { 162 | var $index = args[0]; 163 | var $removeTabPane = typeof args[1] != 'undefined' ? args[1] : false; 164 | var $item = $navigation.find(baseItemSelector + ':eq('+$index+')'); 165 | 166 | // Remove the tab pane first if needed 167 | if($removeTabPane) { 168 | var $href = $item.find('a').attr('href'); 169 | $($href).remove(); 170 | } 171 | 172 | // Remove menu item 173 | $item.remove(); 174 | }; 175 | 176 | $navigation = element.find('ul:first', element); 177 | $activeTab = $navigation.find(baseItemSelector + '.active', element); 178 | 179 | if(!$navigation.hasClass($settings.tabClass)) { 180 | $navigation.addClass($settings.tabClass); 181 | } 182 | 183 | // Load onInit 184 | if($settings.onInit && typeof $settings.onInit === 'function'){ 185 | $settings.onInit($activeTab, $navigation, 0); 186 | } 187 | 188 | // Load onShow 189 | if($settings.onShow && typeof $settings.onShow === 'function'){ 190 | $settings.onShow($activeTab, $navigation, obj.nextIndex()); 191 | } 192 | 193 | // Work the next/previous buttons 194 | obj.fixNavigationButtons(); 195 | 196 | $('a[data-toggle="tab"]', $navigation).on('click', function (e) { 197 | // Get the index of the clicked tab 198 | var clickedIndex = $navigation.find(baseItemSelector).index($(e.currentTarget).parent(baseItemSelector)); 199 | if($settings.onTabClick && typeof $settings.onTabClick === 'function' && $settings.onTabClick($activeTab, $navigation, obj.currentIndex(), clickedIndex)===false){ 200 | return false; 201 | } 202 | }); 203 | 204 | // attach to both shown and shown.bs.tab to support Bootstrap versions 2.3.2 and 3.0.0 205 | $('a[data-toggle="tab"]', $navigation).on('shown shown.bs.tab', function (e) { // use shown instead of show to help prevent double firing 206 | $element = $(e.target).parent(); 207 | var nextTab = $navigation.find(baseItemSelector).index($element); 208 | 209 | // If it's disabled then do not change 210 | if($element.hasClass('disabled')) { 211 | return false; 212 | } 213 | 214 | if($settings.onTabChange && typeof $settings.onTabChange === 'function' && $settings.onTabChange($activeTab, $navigation, obj.currentIndex(), nextTab)===false){ 215 | return false; 216 | } 217 | 218 | $activeTab = $element; // activated tab 219 | obj.fixNavigationButtons(); 220 | }); 221 | }; 222 | $.fn.bootstrapWizard = function(options) { 223 | //expose methods 224 | if (typeof options == 'string') { 225 | var args = Array.prototype.slice.call(arguments, 1) 226 | if(args.length === 1) { 227 | args.toString(); 228 | } 229 | return this.data('bootstrapWizard')[options](args); 230 | } 231 | return this.each(function(index){ 232 | var element = $(this); 233 | // Return early if this element already has a plugin instance 234 | if (element.data('bootstrapWizard')) return; 235 | // pass options to plugin constructor 236 | var wizard = new bootstrapWizardCreate(element, options); 237 | // Store plugin object in this element's data 238 | element.data('bootstrapWizard', wizard); 239 | }); 240 | }; 241 | 242 | // expose options 243 | $.fn.bootstrapWizard.defaults = { 244 | tabClass: 'nav nav-pills', 245 | nextSelector: '.wizard li.next', 246 | previousSelector: '.wizard li.previous', 247 | firstSelector: '.wizard li.first', 248 | lastSelector: '.wizard li.last', 249 | onShow: null, 250 | onInit: null, 251 | onNext: null, 252 | onPrevious: null, 253 | onLast: null, 254 | onFirst: null, 255 | onTabChange: null, 256 | onTabClick: null, 257 | onTabShow: null 258 | }; 259 | 260 | })(jQuery); 261 | -------------------------------------------------------------------------------- /lib/mailgun_validator.js: -------------------------------------------------------------------------------- 1 | // 2 | // Mailgun Address Validation Plugin 3 | // 4 | // Attaching to a form: 5 | // 6 | // $('jquery_selector').mailgun_validator({ 7 | // api_key: 'api-key', 8 | // in_progress: in_progress_callback, // called when request is made to validator 9 | // success: success_callback, // called when validator has returned 10 | // error: validation_error, // called when an error reaching the validator has occured 11 | // }); 12 | // 13 | // 14 | // Sample JSON in success callback: 15 | // 16 | // { 17 | // "is_valid": true, 18 | // "parts": { 19 | // "local_part": "john.smith@example.com", 20 | // "domain": "example.com", 21 | // "display_name": "" 22 | // }, 23 | // "address": "john.smith@example.com", 24 | // "did_you_mean": null 25 | // } 26 | // 27 | // More API details: https://api.mailgun.net/v2/address 28 | // 29 | 30 | 31 | $.fn.mailgun_validator = function(options) { 32 | return this.each(function() { 33 | $(this).focusout(function() { 34 | run_validator($(this).val(), options); 35 | }); 36 | }); 37 | }; 38 | 39 | 40 | function run_validator(address_text, options) { 41 | // don't run validator without input 42 | if (!address_text) { 43 | return; 44 | } 45 | 46 | // length check 47 | if (address_text.length > 512) { 48 | error_message = 'Stream exceeds allowable length of 512.'; 49 | if (options && options.error) { 50 | options.error(error_message); 51 | } 52 | else { 53 | console.log(error_message); 54 | } 55 | return; 56 | } 57 | 58 | // validator is in progress 59 | if (options && options.in_progress) { 60 | options.in_progress(); 61 | } 62 | 63 | if (options && options.api_key == undefined) { 64 | console.log('Please pass in api_key to mailgun_validator.') 65 | } 66 | 67 | var success = false; 68 | 69 | // make ajax call to get validation results 70 | $.getJSON('https://api:' + options.api_key + '@api.mailgun.net/v2/address/validate?callback=?', { 71 | address: address_text, 72 | }).done(function(data, text_status, jq_xhr) { 73 | success = true; 74 | if (options && options.success) { 75 | options.success(data); 76 | } 77 | }).error(function(jq_xhr, text_status, error_thrown) { 78 | success = true; 79 | if (options && options.error) { 80 | options.error(jq_xhr); 81 | } 82 | else { 83 | console.log(jq_xhr); 84 | } 85 | }); 86 | 87 | setTimeout(function() { 88 | error_message = 'Interal Server Error.'; 89 | if (!success) { 90 | if (options && options.error) { 91 | options.error(error_message); 92 | } 93 | else { 94 | console.log(error_message); 95 | } 96 | } 97 | }, 30000); 98 | } -------------------------------------------------------------------------------- /mit-license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 RealCrowd 2 | http://www.realcrowd.com 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/directives/rcSubmit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc directive 3 | * @name ng.directive:rcSubmit 4 | * 5 | * @description 6 | * Alternative to ngSubmit that verifies the ngFormController is valid before 7 | * executing the given expression. Otherwise it cancels the event. 8 | * 9 | * @element form 10 | * @param {expression} rcSubmit {@link guide/expression Expression} to eval. 11 | */ 12 | var rcSubmitDirective = { 13 | 'rcSubmit': ['$parse', '$q', '$timeout', function ($parse, $q, $timeout) { 14 | return { 15 | restrict: 'A', 16 | require: ['rcSubmit', '?form'], 17 | controller: ['$scope', function ($scope) { 18 | 19 | var formElement = null; 20 | var formController = null; 21 | var attemptHandlers = []; 22 | var submitCompleteHandlers = []; 23 | 24 | this.attempted = false; 25 | this.submitInProgress = false; 26 | 27 | this.setFormElement = function(element) { 28 | formElement = element; 29 | } 30 | 31 | this.submit = function() { 32 | if (!formElement) return; 33 | 34 | jQuery(formElement).submit(); 35 | } 36 | 37 | this.onAttempt = function(handler) { 38 | attemptHandlers.push(handler); 39 | }; 40 | 41 | this.setAttempted = function() { 42 | this.attempted = true; 43 | 44 | angular.forEach(attemptHandlers, function (handler) { 45 | handler(); 46 | }); 47 | }; 48 | 49 | this.setFormController = function(controller) { 50 | formController = controller; 51 | }; 52 | 53 | this.needsAttention = function (fieldModelController) { 54 | if (!formController) return false; 55 | 56 | if (fieldModelController) { 57 | return fieldModelController.$invalid && 58 | (fieldModelController.$dirty || this.attempted); 59 | } else { 60 | return formController && formController.$invalid && 61 | (formController.$dirty || this.attempted); 62 | } 63 | }; 64 | 65 | this.onSubmitComplete = function (handler) { 66 | 67 | submitCompleteHandlers.push(handler); 68 | }; 69 | 70 | this.setSubmitComplete = function (success, data) { 71 | 72 | angular.forEach(submitCompleteHandlers, function (handler) { 73 | handler({ 'success': success, 'data': data }); 74 | }); 75 | }; 76 | }], 77 | compile: function(cElement, cAttributes, transclude) { 78 | return { 79 | pre: function(scope, formElement, attributes, controllers) { 80 | 81 | var submitController = controllers[0]; 82 | var formController = (controllers.length > 1) ? controllers[1] : null; 83 | 84 | submitController.setFormElement(formElement); 85 | submitController.setFormController(formController); 86 | 87 | scope.rc = scope.rc || {}; 88 | scope.rc[attributes.name] = submitController; 89 | }, 90 | post: function(scope, formElement, attributes, controllers) { 91 | 92 | var submitController = controllers[0]; 93 | var formController = (controllers.length > 1) ? controllers[1] : null; 94 | var fn = $parse(attributes.rcSubmit); 95 | 96 | formElement.bind('submit', function (event) { 97 | submitController.setAttempted(); 98 | if (!scope.$$phase) scope.$apply(); 99 | 100 | if (!formController.$valid) return false; 101 | 102 | var doSubmit = function () { 103 | 104 | submitController.submitInProgress = true; 105 | if (!scope.$$phase) scope.$apply(); 106 | 107 | var returnPromise = $q.when(fn(scope, { $event: event })); 108 | 109 | returnPromise.then(function (result) { 110 | submitController.submitInProgress = false; 111 | if (!scope.$$phase) scope.$apply(); 112 | 113 | // This is a small hack. We want the submitInProgress 114 | // flag to be applied to the scope before we actually 115 | // raise the submitComplete event. We do that by 116 | // using angular's $timeout service which even without 117 | // a timeout value specified will not fire until after 118 | // the scope is digested. 119 | $timeout(function() { 120 | submitController.setSubmitComplete(true, result); 121 | }); 122 | 123 | }, function (error) { 124 | submitController.submitInProgress = false; 125 | if (!scope.$$phase) scope.$apply(); 126 | $timeout(function() { 127 | submitController.setSubmitComplete(false, error); 128 | }); 129 | }); 130 | }; 131 | 132 | if (!scope.$$phase) { 133 | scope.$apply(doSubmit); 134 | } else { 135 | doSubmit(); 136 | if (!scope.$$phase) scope.$apply(); 137 | } 138 | }); 139 | } 140 | }; 141 | } 142 | }; 143 | }] 144 | }; 145 | -------------------------------------------------------------------------------- /src/directives/rcVerifySet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc directive 3 | * @name ng.directive:rcVerifySet 4 | * 5 | * @description 6 | * Used to verify the scope is updated from the view. This need arose out 7 | * of some browser plugins (namely Password Managers), manipulate the DOM 8 | * and do not necessarily fire the events that angular list to by default. 9 | * Using this method the values are pushed to the scope before submission. 10 | * before submit. 11 | * 12 | * @element ANY 13 | */ 14 | var rcVerifySetDirective = { 'rcVerifySet': function () { return { restrict: 'A', require: ['^rcSubmit', 'ngModel'], link: function (scope, element, attributes, controllers) { var submitController = controllers[0]; var modelController = controllers[1]; 15 | 16 | submitController.onAttempt(function() { 17 | modelController.$setViewValue(element.val()); 18 | }); } }; } }; -------------------------------------------------------------------------------- /src/modules/rcDisabled.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name rcDisabled 4 | * 5 | * @description 6 | * Module to encapsulate the rcDisabled directive and rcDisabledProvider. 7 | */ 8 | 9 | /** 10 | * @ngdoc directive 11 | * @name ng.directive:rcDisabled 12 | * 13 | * @description 14 | * calls rcDisabledProvider.disable on the given element. 15 | * 16 | * @element ANY 17 | * @param {expression} rcDisabled {@link guide/expression Expression} to watch 18 | * which determines when to disable. 19 | */ 20 | var rcDisabledDirective = { 21 | 'rcDisabled': ['rcDisabled', function (rcDisabled) { 22 | return { 23 | restrict: 'A', 24 | link: function (scope, element, attributes) { 25 | 26 | scope.$watch(attributes.rcDisabled, function(isDisabled) { 27 | rcDisabled.disable(element, isDisabled); 28 | }); 29 | } 30 | } 31 | }] 32 | }; 33 | 34 | /** 35 | * @ngdoc provider 36 | * @name ng.provider:rcDisabledProvider 37 | * 38 | * @description 39 | * The provider for rcDisabled. Allows configuration of the method used when 40 | * toggling disabled. 41 | * 42 | */ 43 | var rcDisabledProvider = function () { 44 | 45 | var defaultDisableHandler = function(rootElement, isDisabled) { 46 | var jElement = jQuery(rootElement); 47 | 48 | return jElement 49 | .find(':not([rc-disabled])') 50 | .filter(function(index) { 51 | return jQuery(this) 52 | .parents() 53 | .not(jElement) 54 | .filter('[rc-disabled]').length === 0; 55 | }) 56 | .filter('input:not([ng-disabled]), button:not([ng-disabled])') 57 | .prop('disabled', isDisabled); 58 | }; 59 | 60 | var customDisableHandler; 61 | 62 | this.onDisable = function (customHandler) { 63 | customDisableHandler = customHandler; 64 | }; 65 | 66 | this.$get = function () { 67 | return { 68 | disable: function (rootElement, isDisabled) { 69 | return (customDisableHandler) ? 70 | customDisableHandler(rootElement, isDisabled) : 71 | defaultDisableHandler(rootElement, isDisabled); 72 | } 73 | } 74 | }; 75 | }; 76 | 77 | angular.module('rcDisabled', []) 78 | .provider('rcDisabled', rcDisabledProvider) 79 | .directive(rcDisabledDirective); 80 | 81 | angular.module('rcDisabledBootstrap', ['rcDisabled']) 82 | .provider('rcDisabled', rcDisabledProvider) 83 | .directive(rcDisabledDirective) 84 | .config(['rcDisabledProvider', function(rcDisabledProvider) { 85 | rcDisabledProvider.onDisable(function(rootElement, isDisabled) { 86 | var jqElement = jQuery(rootElement); 87 | 88 | jqElement = jqElement 89 | .find(':not([rc-disabled])') 90 | .filter(function(index) { 91 | return jQuery(this).parents().not(jqElement).filter('[rc-disabled]').length === 0; 92 | }) 93 | .filter('input:not([ng-disabled]), button:not([ng-disabled]), .btn, li') 94 | .add(jqElement); 95 | 96 | // if the Bootstrap "Button" jQuery plug-in is loaded, use it on those 97 | // that have it configured 98 | if (jqElement.button) { 99 | jqElement.find('[data-loading-text]').button((isDisabled) ? 'loading' : 'reset'); 100 | } 101 | 102 | jqElement.toggleClass('disabled', isDisabled) 103 | .filter('input, button') 104 | .prop('disabled', isDisabled); 105 | }); 106 | }]); -------------------------------------------------------------------------------- /src/modules/rcForm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name rcForm 4 | * 5 | * @description 6 | * Module to encapsulate all of our custom form-based directives. 7 | */ 8 | var rcFormModule = angular.module('rcForm', []); var rcSubmitDirective = rcSubmitDirective || null; 9 | var rcVerifySetDirective = rcVerifySetDirective || null; 10 | if (rcSubmitDirective) rcFormModule.directive(rcSubmitDirective); if (rcVerifySetDirective) rcFormModule.directive(rcVerifySetDirective); -------------------------------------------------------------------------------- /src/modules/rcMailgun.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc provider 3 | * @name ng.provider:rcMailgunProvider 4 | * 5 | * @description 6 | * configure the mailgun api and directive 7 | * adding to the scope so the attempt flag can be referenced in markup. 8 | * 9 | */ 10 | var rcMailgunProvider = { 'rcMailgun': function () { 11 | 12 | var configuredOptions = {}; 13 | 14 | this.configure = function(options) { 15 | configuredOptions = options; 16 | }; 17 | 18 | this.$get = function () { 19 | return { 20 | getOptions: function() { 21 | return configuredOptions; 22 | }, 23 | mailgunStatus: '' 24 | }; 25 | }; 26 | } 27 | }; 28 | 29 | /** 30 | * @ngdoc directive 31 | * @name ng.directive:rcMailgunValid 32 | * 33 | * @description 34 | * Used to track submit attempts on forms. It mimics the ngFormController by 35 | * adding to the scope so the attempt flag can be referenced in markup. 36 | * 37 | */ 38 | var rcMailgunValidDirective = { 39 | 'rcMailgunValid': ['rcMailgun', function (rcMailgun) { 40 | return { 41 | restrict: 'A', 42 | require: 'ngModel', 43 | link: function (scope, element, attributes, modelController) { 44 | 45 | if (!jQuery().mailgun_validator) { 46 | console.log('jQuery mailgun_validator plugin required') 47 | } 48 | 49 | options = rcMailgun.getOptions(); 50 | 51 | options = options || {}; 52 | 53 | var baseSuccessCallback = options.success; 54 | var baseErrorCallback = options.error; 55 | var baseInProgressCallback = options.in_progress; 56 | 57 | var successWrapper = function(data) { 58 | modelController.$setValidity('rcMailgunInProgress', true); 59 | modelController.$setValidity('rcMailgunFinished', true); 60 | modelController.$setValidity('rcMailgunEmailValid', (data && data.is_valid)); 61 | 62 | // clear mailgun status (used for errors) 63 | rcMailgun.mailgunStatus = ''; 64 | 65 | if (!scope.$$phase) scope.$apply(); 66 | 67 | if (baseSuccessCallback) 68 | baseSuccessCallback(data); 69 | } 70 | 71 | var errorWrapper = function(error_message) { 72 | modelController.$setValidity('rcMailgunInProgress', true); 73 | modelController.$setValidity('rcMailgunFinished', true); 74 | modelController.$setValidity('rcMailgunEmailValid', false); 75 | 76 | // set mailgun status (used for errors) 77 | rcMailgun.mailgunStatus = error_message; 78 | if (!scope.$$phase) scope.$apply(); 79 | 80 | if (baseErrorCallback) 81 | baseErrorCallback(error_message); 82 | } 83 | 84 | var inProgressWrapper = function() { 85 | // clear when checking 86 | modelController.$setValidity('rcMailgunEmailValid', true); 87 | modelController.$setValidity('rcMailgunInProgress', false); 88 | if (!scope.$$phase) scope.$apply(); 89 | 90 | if (baseInProgressCallback) 91 | baseInProgressCallback(error_message); 92 | } 93 | 94 | 95 | // wrap all callbacks so the validator can respond, but user can still use the callbacks if needed 96 | options.success = successWrapper; 97 | options.error = errorWrapper; 98 | options.in_progress = inProgressWrapper; 99 | 100 | jQuery(element).mailgun_validator(options); 101 | 102 | var clearWhenBlankValidator = function (value) { 103 | if (!value) { 104 | modelController.$setValidity('rcMailgunEmailValid', true); 105 | 106 | // clear mailgun status (used for errors) 107 | rcMailgun.mailgunStatus = ''; 108 | } 109 | 110 | return value; 111 | }; 112 | 113 | modelController.$formatters.push(clearWhenBlankValidator); 114 | modelController.$parsers.unshift(clearWhenBlankValidator); 115 | 116 | // if element is initialized with a value, force validation 117 | if (element.val()) { 118 | element.focusout(); 119 | } 120 | } 121 | } 122 | }] 123 | }; 124 | 125 | var rcMailgunModule = angular.module('rcMailgun', []); 126 | 127 | if (rcMailgunProvider) rcMailgunModule.provider(rcMailgunProvider); 128 | if (rcMailgunValidDirective) rcMailgunModule.directive(rcMailgunValidDirective); -------------------------------------------------------------------------------- /src/modules/rcWizard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name rcWizard 4 | * 5 | * @description 6 | * Module to encapsulate the rcWizard directive and rcStep directive. 7 | */ 8 | 9 | /** 10 | * @ngdoc directive 11 | * @name ng.directive:rcWizard 12 | * 13 | * @description 14 | * Configures the specified element as a wizard. Uses the jQuery Bootstrap Wizard Plug-in 15 | * 16 | * @element ANY 17 | * @param {name} Specifies the name of the wizard which can be used to look at state 18 | * information on the scope. 19 | */ 20 | var rcWizardDirective = { 21 | 'rcWizard': function () { 22 | return { 23 | restrict: 'A', 24 | controller: ['$scope', function ($scope) { 25 | 26 | var self; 27 | var wizardElement; 28 | var wizardOptions = {}; 29 | var steps = []; 30 | 31 | this.currentIndex = 0; 32 | this.firstIndex = 0; 33 | this.navigationLength = 0; 34 | 35 | this.addStep = function (step) { 36 | 37 | steps.push(step); 38 | 39 | if (!step.element || !step.submitController) return; 40 | 41 | // if a rcSubmitController is being used, automatically add a _hidden_ 42 | // submit button so that 43 | 44 | // in order to place an submit button that is still functional it 45 | // has to technically be visible, so instead we place it way off 46 | // screen 47 | jQuery(step.element) 48 | .append('') 49 | .attr('action', 'javascript:void(0);'); 50 | 51 | // bind to the submit complete event on the rcSubmitController and 52 | // if the action was successful, trigger a next on the wizard. 53 | step.submitController.onSubmitComplete(function (evt) { 54 | if (evt.success) { 55 | onForward(step); 56 | } 57 | }); 58 | }; 59 | 60 | this.forward = function () { 61 | 62 | if (steps.length) 63 | 64 | var currentStep = (steps.length > self.currentIndex) ? steps[self.currentIndex] : null; 65 | 66 | if (0 < steps.length && !currentStep) return; 67 | 68 | if (0 < steps.length && currentStep.submitController) { 69 | currentStep.submitController.submit(); 70 | } else { 71 | onForward(currentStep); 72 | } 73 | }; 74 | 75 | var onForward = function(currentStep) { 76 | 77 | if (0 < steps.length && 78 | currentStep.formController && 79 | currentStep.formController.$invalid) return; 80 | 81 | wizardElement.bootstrapWizard('next'); 82 | }; 83 | 84 | this.backward = function () { 85 | wizardElement.bootstrapWizard('previous'); 86 | }; 87 | 88 | var onTabChange = function (activeTab, navigation, currentIndex, nextTab) { 89 | 90 | self.currentIndex = nextTab; 91 | self.firstIndex = wizardElement.bootstrapWizard('firstIndex'); 92 | self.navigationLength = wizardElement.bootstrapWizard('navigationLength'); 93 | 94 | if (!$scope.$$phase) $scope.$apply(); 95 | }; 96 | 97 | var onTabClick = function (activeTab, navigation, currentIndex, clickedIndex) { 98 | return false; 99 | }; 100 | 101 | var onTabShow = function (activeTab, navigation, currentIndex) { 102 | 103 | if (currentIndex > 0) { 104 | wizardElement 105 | .find('.nav li:gt(' + (currentIndex - 1) + ')') 106 | .removeClass("success"); 107 | wizardElement.find('.nav li:lt(' + currentIndex + ')') 108 | .addClass("success"); 109 | } else { 110 | wizardElement.find('.nav li').removeClass("success"); 111 | } 112 | 113 | // if a rcStep is being used on the current tab, 114 | // automatically focus on the first input of the current tab. This 115 | // allows for easier keyboard-ony navigation. 116 | if (steps.length > currentIndex && steps[currentIndex].element) { 117 | steps[currentIndex].element.find('input').first().focus(); 118 | } 119 | }; 120 | 121 | var updateWizard = function (options) { 122 | 123 | wizardOptions = options; 124 | 125 | if (wizardElement) { 126 | wizardElement.bootstrapWizard(options); 127 | self.currentIndex = wizardElement.bootstrapWizard('currentIndex'); 128 | self.firstIndex = wizardElement.bootstrapWizard('firstIndex'); 129 | self.navigationLength = wizardElement.bootstrapWizard('navigationLength'); 130 | 131 | if (!$scope.$$phase) $scope.$apply(); 132 | } 133 | }; 134 | 135 | this.setWizardElement = function (element) { 136 | 137 | wizardElement = element; 138 | self = this; 139 | updateWizard({ 140 | 'onTabChange': onTabChange, 141 | 'onTabShow': onTabShow, 142 | 'onTabClick': onTabClick 143 | }); 144 | }; 145 | }], 146 | compile: function (cElement, cAttributes, transclude) { 147 | return { 148 | pre: function (scope, formElement, attributes, wizardController) { 149 | // put a reference to the wizardcontroller on the scope so we can 150 | // use some of the properties in the markup 151 | scope.rc = scope.rc || {}; 152 | scope.rc[attributes.rcWizard] = wizardController; 153 | }, 154 | post: function (scope, element, attributes, wizardController) { 155 | // let the controller know about the element 156 | wizardController.setWizardElement(element); 157 | if (!scope.$$phase) scope.$apply(); 158 | } 159 | }; 160 | } 161 | } 162 | } 163 | }; 164 | 165 | /** 166 | * @ngdoc directive 167 | * @name ng.directive:rcStep 168 | * 169 | * @description 170 | * Configures the specified element as a wizard-step. Tells the parent rcWizard 171 | * controller about the step including any optional controllers. 172 | * 173 | * @element ANY 174 | * @param NONE 175 | */ 176 | var rcWizardStepDirective = { 177 | 'rcStep': function () { 178 | return { 179 | restrict: 'A', 180 | require: ['^rcWizard', '?form', '?rcSubmit'], 181 | link: function (scope, element, attributes, controllers) { 182 | 183 | var wizardController = controllers[0]; 184 | 185 | // find all the optional controllers for the step 186 | var formController = controllers.length > 1 ? controllers[1] : null; 187 | var submitController = controllers.length > 2 ? controllers[2] : null; 188 | 189 | // add the step to the wizard controller 190 | var step = wizardController.addStep({ 191 | 'element': element, 192 | 'attributes': attributes, 193 | 'formController': formController, 194 | 'submitController': submitController }); 195 | } 196 | }; 197 | } 198 | }; 199 | 200 | angular.module('rcWizard', ['ng']) 201 | 202 | .directive(rcWizardDirective) 203 | .directive(rcWizardStepDirective); --------------------------------------------------------------------------------