-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathautoform5.js
297 lines (244 loc) · 13.4 KB
/
autoform5.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
(function($){
// extend the $.support function to test for native form attribute and type support
// written by "lonesomeday" at http://stackoverflow.com/users/417562/lonesomeday
// many thanks!
if ( $.support.input === undefined ) { // make sure the .support function does not already support testing for this (a feeble attempt at future proofing this script?)
(function(d){d.extend(d.support,function(){var c=document.createElement("input");return{inputtypes:function(){var a="search number range color tel url email date month week time datetime datetime-local".split(" "),b={};i=0;for(j=a.length;i<j;i++){c.setAttribute("type",a[i]);b[a[i]]=c.type!=="text"}return b}(),input:function(){var a="autocomplete autofocus list placeholder max min multiple pattern required step".split(" "),b={};i=0;for(j=a.length;i<j;i++)b[a[i]]=!!(a[i]in c);return b}()}}())})(jQuery);
}
// Let's Go!
$.fn.extend({
//pass the options variable to the function
autoForm5: function(options) {
$.fn.autoForm5.defaults = {
// general plugin settings & defaults
validateForm: true, // enable error checking to eliminate placeholder and false values to be submitted
requiredFields: '', // manually allow for adding required fields here (comma separated)
interimDataField: 'instructions', // the attribute name used to store interim text
// the default classes used by the plugin
classes : {
form : 'af5-activeform', // added to the form that is the parent of the currenly focussed field
fieldset : 'af5-activefieldset', // added to the fieldset that is the parent of the currently focussed field
error : 'af5-error', // added to fields that fail validation (on form submit and during live checking)
required : 'af5-required', // added to fields that are required (on ready)
passive : 'af5-passive', // added to fields that are not currently focussed (on ready)
active : 'af5-active', // added to the currently focussed field
interim : 'af5-interim', // added to a field that is displaying the data-instructions text (if any)
filled : 'af5-filled' // added to fields that have a user entered value
},
pattern : {
tel : /^((\+\d{1,3}? ?\d{1,3}? ?[\d\- \(\)]{7,15})|([\-\ \(\)\d]{6,20}))$/,
email : /^(\w|\.|-)+?@(\w|-)+?\.\w{2,4}($|\.\w{2,4})$/,
number : /^\d+$/,
url : /(https?:\/\/)?(www\.)?([a-zA-Z0-9_%]*)\b\.[a-z]{2,4}(\.[a-z]{2})?((\/[a-zA-Z0-9_%]*)+)?(\.[a-z]*)?/,
text : /./
}
};
var options = $.extend(true,$.fn.autoForm5.defaults, options);
return this.each(function() {
// should return a <form> element, not any child field directly!
// map options to var o for shorter variable call
var o = options;
// create namespace to store values securely
var AF5 = {};
AF5.form = (this.nodeName == 'FORM'?$(this):$(this).closest('form')); // if someone does not properly point the script at the FORM element this will find the proper form but creates extra object (wasteful…)
AF5.fieldsets = $('fieldset', AF5.form);
AF5.inputs = $('input:not([type=submit])', AF5.form);
AF5.submit = $('[type=submit]', AF5.form);
AF5.fields = {
number : $('input[type=number]', AF5.form),
tel : $('input[type=tel]', AF5.form),
email : $('input[type=email]', AF5.form),
search : $('input[type=search]', AF5.form),
url : $('input[type=url]', AF5.form),
placeholder : $(':input[placeholder]', AF5.form),
required : $(':input[required=""],:input.required,'+o.requiredFields, AF5.form),
autofocus : $(':input[autofocus=""]', AF5.form),
interim : $(':input[data-'+o.interimDataField+']', AF5.form)
};
AF5.validation = {
regex: {
tel: o.pattern.tel,
email: o.pattern.email,
number: o.pattern.number,
url: o.pattern.url,
text: o.pattern.text
},
validate: function(value, type) {
if (type == null) type = 'text';
var reg = (this.regex[type]?this.regex[type]:this.regex['text']);
// console.log('value: '+value+' type: '+type+' result :'+reg.test(value));
return reg.test(value);
}
};
// =======================================
// = CHECK FOR data-[interim] ATTRIBUTE =
// =======================================
function interim(ele, action) { // ele pass $(this) into it & action = boolean (true = print value)
if (action) {
return $(ele).attr('data-'+o.interimDataField); // print the actual data value
} else {
return ($(ele).attr('data-'+o.interimDataField) ? true : false ); // if attribute present return true
}
}
// ======================================================
// = ADD CLASSES TO FORMS & FIELDSETS & REQUIRED INPUTS =
// ======================================================
function classified() {
// attach classes to forms & fieldsets
AF5.inputs.each(function(index) { // cycle through all inputs
var self = $(this);
self.addClass(o.classes.passive); // add passive class to all fields
self.bind('focus blur', function(e) {
var closestForm = self.closest('form'), // cache the closest form, could use AF5.form for this but this way it will be safer should there be multiple forms on the page
closestFieldset = self.closest('fieldset'); // cache the closest fieldset
if (e.type == "focus") {
// add class indicators
closestFieldset.addClass(o.classes.fieldset);
closestForm.addClass(o.classes.form);
self.addClass(o.classes.active).removeClass(o.classes.passive);
} else if (e.type = "blur") {
// take them away…
closestFieldset.removeClass(o.classes.fieldset);
closestForm.removeClass(o.classes.form);
self.removeClass(o.classes.active).addClass(o.classes.passive);
// when leaving the field check the value and if filled by the user add the filled class
if ( self.val() != self.attr('placeholder') && self.val() != "" && self.val() != interim(self,true) && self.val() != self.attr('data-error') ) {
self.addClass(o.classes.filled); // add the class
if (!$.support.input.email || !$.support.input.tel || !$.support.input.url || !$.support.input.number ) { // if not natively supported
if ((AF5.validation.validate( self[0].value , self[0].getAttribute('type')))) { // validate
self.removeClass(o.classes.error); // on success remove error class
} else if ( self.val() != "" ) {
self.addClass(o.classes.error); // on fail re-add error class
}
}
} else {
self.removeClass(o.classes.filled); // if field is left blank on blur remove filled class
}
}
});
// Add error checking while you type
self.bind('keyup', function(e) {
if (self.is('.'+o.classes.error) && (AF5.validation.validate( self[0].value , self[0].getAttribute('type')))) {
self.removeClass(o.classes.error);
} else if ( self.val() == "" ) {
self.removeClass(o.classes.error).removeClass(o.classes.filled);
}
});
});
// attach required classes
AF5.fields.required.each(function(i) {
var self = $(this);
self.addClass(o.classes.required);
});
// special handlers for the submit button
AF5.submit.bind('focus blur', function(e) {
var self = $(this),
closestForm = self.closest('form'), // cache the closest form, could use AF5.form for this but this way it will be safer should there be multiple forms on the page
closestFieldset = self.closest('fieldset'); // cache the closest fieldset
if ( e.type == "focus" ) {
closestFieldset.addClass(o.classes.fieldset);
closestForm.addClass(o.classes.form);
self.addClass(o.classes.active)
} else {
closestFieldset.removeClass(o.classes.fieldset);
closestForm.removeClass(o.classes.form);
self.removeClass(o.classes.active)
}
});
}
classified(); // run classified()
// =====================
// = ADD ERROR CLASSES =
// =====================
function validate(fields) { // fields = failed elements
$.each(fields, function(i, ele) {
$(ele).addClass(o.classes.error).removeClass(o.classes.filled); // add classes
// if the field has a data-error attribute, set it as the value but ONLY IF the field has no user entered information
if ($(ele).attr('data-error') && ele.value == "" || ele.value == interim(ele,true) || ele.value == $(ele).attr('placeholder')) {$(ele).val($(ele).attr('data-error'));}
$(ele).bind('keyup blur change', function(e) { // add value monitoring for live validation feedback
if ((AF5.validation.validate( ele.value , ele.getAttribute('type')))) {
$(ele).removeClass(o.classes.error).addClass(o.classes.filled); // remove error class
} else {
$(ele).addClass(o.classes.error).removeClass(o.classes.filled); // re-add error class
};
});
});
}
// ========================================
// = CHECK FOR REQUIRED ATTRIBUTE SUPPORT =
// ========================================
if ($.support.input.required) { // if required is natively supported
$.each($(':input.required,'+o.requiredFields), function(i, ele) { // go through all required fields that do not use the "required" boolean attribute
ele.setAttribute('required',true); // add the boolean attribute on alternative required marked fields so that the html5 spec can take a hold of them
});
}
// ========================
// = PLACEHOLDER FALLBACK =
// ========================
if (!$.support.input.placeholder) { // if no native placeholder text is supported run the following
AF5.fields.placeholder.each(function(i) {
var self = $(this); // cache current ele
if (self.val() == "") { // if the field is empty || there is no user input (from page reload or back button use)
self.val(self.attr('placeholder')); // add placeholder text into the field
}
self.bind('focus blur keydown click', function(e) {
if (e.type == "focus" && self.val() == self.attr('placeholder')) { // when focussed & placeholder is shown (stops it from clearing user entered info)
if (interim(self)) { // if the iterim state is switched on
self.val(interim(self,true)).addClass(o.classes.interim); // show interim text
} else {
self.val(''); // clear field
}
} else if (e.type == "blur") { // when blurred
if ( self.val() == "" || self.val() == interim(self,true) || self.val() == self.attr('data-error')) {
self.val(self.attr('placeholder')).removeClass(o.classes.interim);
}
} else if (e.type == "keydown" && self.val() == interim(self,true) || self.val() == self.attr('data-error')) { // on keydown and if the interim text is visible
self.val('').removeClass(o.classes.interim); // clear the field to make room for user input
}
});
});
} else {
// in any case looks for interim data attribute to inject
AF5.fields.interim.each(function(i) {
var self = $(this);
self.addClass(o.classes.passive);
self.bind('focus blur keydown', function(e) {
if (e.type == "focus" && interim(self) && self.val() == "") { // when focussed & placeholder is shown (stops it from clearing user entered info)
self.val(interim(self,true)).addClass(o.classes.interim); // show interim text
} else if (e.type=="blur" && self.val() == interim(self,true) || self.val() == self.attr('data-error')) {
self.val('').removeClass(o.classes.interim);
} else if (e.type == "keydown" && self.val() == interim(self,true) || self.val() == self.attr('data-error')) { // on keydown and if the interim text is visible
self.val('').removeClass(o.classes.interim); // clear the field to make room for user input
}
});
});
};
// =============
// = AUTOFOCUS =
// =============
if (!$.support.input.autofocus) { // if no native support
AF5.fields.autofocus.first().focus(); // find all fields with autofocus attribute and focus the first one (just in case someone has the idea of adding multiple per form)
};
// ==============================
// = FORM SUBMISSION VALIDATION =
// ==============================
AF5.form.submit(function(e) {
if (!$.support.input.required) {
var required = []; // create an empty array for the collection of failed but required elements
$.each(AF5.fields.required, function(i, ele) { // cycle through all required fields
$(ele).removeClass(o.classes.error); // make sure all error classes are taken off
if ( ele.value == "" || ele.value == $(ele).attr('placeholder') || ele.value == interim(ele,true) || (! AF5.validation.validate( ele.value , ele.getAttribute('type') ) ) ) { // if the value of the field is blank, the same as the placeholder or the interim text then fail this field
required.push(ele); // add failed field to the required array
}
});
if ( required.length ) // if the required array has collected any fields
{
e.preventDefault(); // stop the form submission
validate(required); // pass those fields onto the validation function
}
};
});
});
}
});
})(jQuery);