├── .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 |
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 |
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 |
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 |
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 |
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);
--------------------------------------------------------------------------------