4 * @author Kendall Conrad
5 * @url http://www.angelwatt.com/coding/masked_input.php
8 * @license This work is licensed under a Creative Commons
9 * Attribution-Share Alike 3.0 United States License
10 * http://creativecommons.org/licenses/by-sa/3.0/us/
12 * @param scope The object to attach MaskedInput to.
18 * MaskedInput takes many possible arguments described below.
19 * Note: req = required, opt = optional
20 * @param {object} args {
21 * -elm [req] text input node to apply the mask on
22 * -format [req] string format for the mask
23 * -allowed [opt, '0123456789'] string with chars allowed to be typed
24 * -sep [opt, '\/:-'] string of char(s) used as separators in mask
25 * -typeon [opt, '_YMDhms'] string of chars in mask that can be typed on
26 * -onfilled [opt, null] function to run when the format is filled in
27 * -onbadkey [opt, null] function to run when user types a unallowed key
28 * -badkeywait [opt, 0] used with onbadkey. Indicates how long (in ms)
29 * to lock text input for onbadkey function to run
30 * -preserve [opt, true] whether to preserve existing text in
33 * @returns MaskedInput
35 scope.MaskedInput = function(args) {
36 // Ensure passing in valid argument
37 if (!args || !args.elm || !args.format) {
40 // Ensure use of 'new'
41 if (!(this instanceof scope.MaskedInput)) {
42 return new scope.MaskedInput(args);
44 // Initialize variables
48 allowed = args.allowed || '0123456789',
49 sep = args.separator || '\/:-',
50 open = args.typeon || '_YMDhms',
51 onbadkey = args.onbadkey || function() {},
52 onfilled = args.onfilled || function() {},
53 badwait = args.badkeywait || 0,
54 preserve = args.hasOwnProperty('preserve') ? !!args.preserve : true,
60 * Add events to objects.
62 evtAdd = (function() {
63 if (window.addEventListener) {
64 return function(obj, type, fx, capture) {
65 obj.addEventListener(type, fx,
66 (capture === undefined) ? false : capture);
69 if (window.attachEvent) {
70 return function(obj, type, fx) {
71 obj.attachEvent('on' + type, fx);
74 return function(obj, type, fx) {
75 obj['on' + type] = fx;
79 * Checks whether the format has been completely filled out.
80 * @return boolean if all typeon chars have been filled.
82 isFilled = function() {
83 // Check if any typeon characters are left
84 // Work from end of string as it's usually last filled
85 for (var a = el.value.length - 1; a >= 0; a--) {
86 // Check against each typeon character
87 for (var c = 0, d = open.length; c < d; c++) {
88 // If one matches we don't need to check anymore
89 if (el.value[a] === open[c]) {
97 * Gets the current position of the text cursor in a text field.
98 * @param node a input or textarea HTML node.
99 * @return int text cursor position index, or -1 if there was a problem.
101 getTextCursor = function(node) {
104 if (node.selectionStart >= 0) {
105 return node.selectionStart;
107 if (document.selection) {// IE
108 var rng = document.selection.createRange();
109 return -rng.moveStart('character', -node.value.length);
118 * Sets the text cursor in a text field to a specific position.
119 * @param node a input or textarea HTML node.
120 * @param pos int of the position to be placed.
121 * @return boolean true is successful, false otherwise.
123 setTextCursor = function(node, pos) {
125 if (node.selectionStart) {
127 node.setSelectionRange(pos, pos);
129 else if (node.createTextRange) { // IE
130 var rng = node.createTextRange();
131 rng.move('character', pos);
141 * Gets the keyboard input in usable way.
142 * @param code integer character code
143 * @return string representing character code
145 getKey = function(code) {
146 code = code || window.event;
148 keyCode = code.which,
150 if (keyCode === undefined || keyCode === null) {
151 keyCode = code.keyCode;
154 if (keyCode === undefined || keyCode === null) {
157 // deal with special keys
162 case 46: // handle del and . both being 46
163 ch = (evt === 'keydown') ? 'del' : '.';
176 case 40: // arrow keys
177 ch = (!code.shiftKey &&
178 (code.charCode !== 39 && code.charCode !== undefined)) ?
179 'etc' : String.fromCharCode(keyCode);
181 // default to thinking it's a character or digit
183 ch = String.fromCharCode(keyCode);
189 * Stop the event propogation chain.
190 * @param evt Event to stop
191 * @param ret boolean, used for IE to prevent default event
193 stopEvent = function(evt, ret) {
194 // Stop default behavior the standard way
195 if (evt.preventDefault) {
196 evt.preventDefault();
199 evt.returnValue = ret || false;
202 * Updates the text field with the given key.
203 * @param key string keyboard input.
205 update = function(key) {
206 var p = getTextCursor(el),
212 // Allowed characters
213 case (allowed.indexOf(key) !== -1):
215 // if text cursor at end
216 if (p > format.length) {
219 // Handle cases where user places cursor before separator
220 while (sep.indexOf(c.charAt(p - 1)) !== -1 && p <= format.length) {
223 val = c.substr(0, p - 1) + key + c.substr(p);
224 // Move csor up a spot if next char is a separator char
225 if (allowed.indexOf(c.charAt(p)) === -1
226 && open.indexOf(c.charAt(p)) === -1) {
230 case (key === 'bksp'): // backspace
236 // If previous char is a separator, move a little more
237 while (allowed.indexOf(c.charAt(p)) === -1
238 && open.indexOf(c.charAt(p)) === -1
242 val = c.substr(0, p) + format.substr(p, 1) + c.substr(p + 1);
244 case (key === 'del'): // forward delete
249 // If next char is a separator and not the end of the text field
250 while (sep.indexOf(c.charAt(p)) !== -1
251 && c.charAt(p) !== '') {
254 val = c.substr(0, p) + format.substr(p, 1) + c.substr(p + 1);
255 p = p + 1; // Move position forward
257 case (key === 'etc'):
258 // Catch other allowed chars
261 return false; // Ignore the rest
263 el.value = ''; // blank it first (Firefox issue)
264 el.value = val; // put updated value back in
265 setTextCursor(el, p); // Set the text cursor
269 * Returns whether or not a given input is valid for the mask.
270 * @param k string of character to check.
271 * @return bool true if it's a valid character.
273 goodOnes = function(k) {
274 // if not in allowed list, or invisible key action
275 if (allowed.indexOf(k) === -1 && k !== 'bksp' && k !== 'del' && k !== 'etc') {
276 // Need to ensure cursor position not lost
277 var p = getTextCursor(el);
280 // Hold lock long enough for onbadkey function to run
281 setTimeout(function() {
283 setTextCursor(el, p);
290 * Handles the key down events.
293 keyHandlerDown = function(e) {
303 // Stop copy and paste
304 if ((e.metaKey || e.ctrlKey) && (key === 'X' || key === 'V')) {
308 // Allow for OS commands
309 if (e.metaKey || e.ctrlKey) {
312 if (el.value === '') {
314 setTextCursor(el, 0);
316 // Only do update for bksp del
317 if (key === 'bksp' || key === 'del') {
325 * Handles the key press events.
328 keyHandlerPress = function(e) {
338 // Check if modifier key is being pressed; command
339 if (key === 'etc' || e.metaKey || e.ctrlKey || e.altKey) {
342 if (key !== 'bksp' && key !== 'del' && key !== 'shift') {
343 if (!goodOnes(key)) {
363 * Initialize the object.
366 // Check if an input or textarea tag was passed in
367 if (!el.tagName || (el.tagName.toUpperCase() !== 'INPUT'
368 && el.tagName.toUpperCase() !== 'TEXTAREA')) {
371 // Only place formatted text in field when not preserving
372 // text or it's empty.
373 if (!preserve || el.value === '') {
377 evtAdd(el, 'keydown', function(e) {
380 evtAdd(el, 'keypress', function(e) {
383 // Let us set the initial text state when focused
384 evtAdd(el, 'focus', function() {
385 startText = el.value;
387 // Handle onChange event manually
388 evtAdd(el, 'blur', function() {
389 if (el.value !== startText && el.onchange) {
397 * Resets the text field so just the format is present.
399 self.resetField = function() {
404 * Set the allowed characters that can be used in the mask.
405 * @param a string of characters that can be used.
407 self.setAllowed = function(a) {
413 * The format to be used in the mask.
414 * @param f string of the format.
416 self.setFormat = function(f) {
422 * Set the characters to be used as separators.
423 * @param s string representing the separator characters.
425 self.setSeparator = function(s) {
431 * Set the characters that the user will be typing over.
432 * @param t string representing the characters that will be typed over.
434 self.setTypeon = function(t) {
440 * Sets whether the mask is active.
442 self.setEnabled = function(enable) {
447 * Local change for Freeside: sets the content of the field,
448 * respecting formatting rules
450 self.setValue = function(value) {
452 setTextCursor(el, 0);
453 var i = 0; // index in value
454 while (i < value.length && !isFilled()) {