1 /**
  2  * @author Gillis Haasnoot <gillis.haasnoot@gmail.com>
  3  * @package Banana.Controls
  4  * @summary Masket textbox. just like a textbox with possibility to force a mask
  5  */
  6 
  7 goog.provide('Banana.Controls.DataControls.MaskedTextBox');
  8 
  9 goog.require('Banana.Controls.DataControls.TextBox');
 10 
 11 /** @namespace Banana.Controls.MaskedTextBox */
 12 namespace('Banana.Controls').MaskedTextBox = Banana.Controls.TextBox.extend(
 13 /** @lends Banana.Controls.MaskedTextBox.prototype */
 14 {	
 15 	/**
 16 	 * Creates a masked textbox.
 17 	 * use set mask to supply the mask
 18 	 * @constructs
 19 	 * @extends Banana.Controls.TextBox
 20 	 */
 21 	init : function()
 22 	{
 23 		this._super();
 24 		this.mask = "99/99/9999";
 25 	},
 26 
 27 	/**
 28 	 * @override
 29 	 */
 30 	updateDisplay : function()
 31 	{
 32 		this._super();
 33 		jQuery('#'+this.getClientId()).mask(this.mask);
 34 	}
 35 });
 36 
 37 /*
 38  * sets mask on control
 39  *
 40  * @param string for example 99/99/9999/
 41  */
 42 Banana.Controls.MaskedTextBox.prototype.setMask = function(mask)
 43 {
 44 	this.mask = mask;
 45 	return this;
 46 };
 47 
 48 
 49 
 50 (function($) {
 51 	var pasteEventName = ($.browser.msie ? 'paste' : 'input') + ".mask";
 52 	var iPhone = (window.orientation != undefined);
 53 
 54 	$.mask = {
 55 		//Predefined character definitions
 56 		definitions: {
 57 			'9': "[0-9]",
 58 			'a': "[A-Za-z]",
 59 			'*': "[A-Za-z0-9]"
 60 		}
 61 	};
 62 
 63 	$.fn.extend({
 64 		//Helper Function for Caret positioning
 65 		caret: function(begin, end) {
 66 			if (this.length == 0) return;
 67 			if (typeof begin == 'number') {
 68 				end = (typeof end == 'number') ? end : begin;
 69 				return this.each(function() {
 70 					if (this.setSelectionRange) {
 71 						this.focus();
 72 						this.setSelectionRange(begin, end);
 73 					} else if (this.createTextRange) {
 74 						var range = this.createTextRange();
 75 						range.collapse(true);
 76 						range.moveEnd('character', end);
 77 						range.moveStart('character', begin);
 78 						range.select();
 79 					}
 80 				});
 81 			} else {
 82 				if (this[0].setSelectionRange) {
 83 					begin = this[0].selectionStart;
 84 					end = this[0].selectionEnd;
 85 				} else if (document.selection && document.selection.createRange) {
 86 					var range = document.selection.createRange();
 87 					begin = 0 - range.duplicate().moveStart('character', -100000);
 88 					end = begin + range.text.length;
 89 				}
 90 				return { begin: begin, end: end };
 91 			}
 92 		},
 93 		unmask: function() { return this.trigger("unmask"); },
 94 		mask: function(mask, settings) {
 95 			if (!mask && this.length > 0) {
 96 				var input = $(this[0]);
 97 				var tests = input.data("tests");
 98 				return $.map(input.data("buffer"), function(c, i) {
 99 					return tests[i] ? c : null;
100 				}).join('');
101 			}
102 			settings = $.extend({
103 				placeholder: "_",
104 				completed: null
105 			}, settings);
106 
107 			var defs = $.mask.definitions;
108 			var tests = [];
109 			var partialPosition = mask.length;
110 			var firstNonMaskPos = null;
111 			var len = mask.length;
112 
113 			$.each(mask.split(""), function(i, c) {
114 				if (c == '?') {
115 					len--;
116 					partialPosition = i;
117 				} else if (defs[c]) {
118 					tests.push(new RegExp(defs[c]));
119 					if(firstNonMaskPos==null)
120 						firstNonMaskPos =  tests.length - 1;
121 				} else {
122 					tests.push(null);
123 				}
124 			});
125 
126 			return this.each(function() {
127 				var input = $(this);
128 				var buffer = $.map(mask.split(""), function(c, i) { if (c != '?') return defs[c] ? settings.placeholder : c });
129 				var ignore = false;  			//Variable for ignoring control keys
130 				var focusText = input.val();
131 
132 				input.data("buffer", buffer).data("tests", tests);
133 
134 				function seekNext(pos) {
135 					while (++pos <= len && !tests[pos])
136 					{
137 						return pos;
138 					}
139 				};
140 
141 				function shiftL(pos) {
142 					while (!tests[pos] && --pos >= 0)
143 					{
144 						for (var i = pos; i < len; i++) {
145 							if (tests[i]) {
146 								buffer[i] = settings.placeholder;
147 								var j = seekNext(i);
148 								if (j < len && tests[i].test(buffer[j])) {
149 									buffer[i] = buffer[j];
150 								} else
151 									break;
152 							}
153 						}
154 						writeBuffer();
155 						input.caret(Math.max(firstNonMaskPos, pos));
156 					}
157 				};
158 
159 				function shiftR(pos) {
160 					for (var i = pos, c = settings.placeholder; i < len; i++) {
161 						if (tests[i]) {
162 							var j = seekNext(i);
163 							var t = buffer[i];
164 							buffer[i] = c;
165 							if (j < len && tests[j].test(t))
166 								c = t;
167 							else
168 								break;
169 						}
170 					}
171 				};
172 
173 				function keydownEvent(e) {
174 					var pos = $(this).caret();
175 					var k = e.keyCode;
176 					ignore = (k < 16 || (k > 16 && k < 32) || (k > 32 && k < 41));
177 
178 					//delete selection before proceeding
179 					if ((pos.begin - pos.end) != 0 && (!ignore || k == 8 || k == 46))
180 						clearBuffer(pos.begin, pos.end);
181 
182 					//backspace, delete, and escape get special treatment
183 					if (k == 8 || k == 46 || (iPhone && k == 127)) {//backspace/delete
184 						shiftL(pos.begin + (k == 46 ? 0 : -1));
185 						return false;
186 					} else if (k == 27) {//escape
187 						input.val(focusText);
188 						input.caret(0, checkVal());
189 						return false;
190 					}
191 				};
192 
193 				function keypressEvent(e) {
194 					if (ignore) {
195 						ignore = false;
196 						//Fixes Mac FF bug on backspace
197 						return (e.keyCode == 8) ? false : null;
198 					}
199 					e = e || window.event;
200 					var k = e.charCode || e.keyCode || e.which;
201 					var pos = $(this).caret();
202 
203 					if (e.ctrlKey || e.altKey || e.metaKey) {//Ignore
204 						return true;
205 					} else if ((k >= 32 && k <= 125) || k > 186) {//typeable characters
206 						var p = seekNext(pos.begin - 1);
207 						if (p < len) {
208 							var c = String.fromCharCode(k);
209 							if (tests[p].test(c)) {
210 								shiftR(p);
211 								buffer[p] = c;
212 								writeBuffer();
213 								var next = seekNext(p);
214 								$(this).caret(next);
215 								if (settings.completed && next == len)
216 									settings.completed.call(input);
217 							}
218 						}
219 					}
220 					return false;
221 				};
222 
223 				function clearBuffer(start, end) {
224 					for (var i = start; i < end && i < len; i++) {
225 						if (tests[i])
226 							buffer[i] = settings.placeholder;
227 					}
228 				};
229 
230 				function writeBuffer() { return input.val(buffer.join('')).val(); };
231 
232 				function checkVal(allow) {
233 					//try to place characters where they belong
234 					var test = input.val();
235 					var lastMatch = -1;
236 					for (var i = 0, pos = 0; i < len; i++) {
237 						if (tests[i]) {
238 							buffer[i] = settings.placeholder;
239 							while (pos++ < test.length) {
240 								var c = test.charAt(pos - 1);
241 								if (tests[i].test(c)) {
242 									buffer[i] = c;
243 									lastMatch = i;
244 									break;
245 								}
246 							}
247 							if (pos > test.length)
248 								break;
249 						} else if (buffer[i] == test[pos] && i!=partialPosition) {
250 							pos++;
251 							lastMatch = i;
252 						}
253 					}
254 					if (!allow && lastMatch + 1 < partialPosition) {
255 						input.val("");
256 						clearBuffer(0, len);
257 					} else if (allow || lastMatch + 1 >= partialPosition) {
258 						writeBuffer();
259 						if (!allow) input.val(input.val().substring(0, lastMatch + 1));
260 					}
261 					return (partialPosition ? i : firstNonMaskPos);
262 				};
263 
264 				if (!input.attr("readonly"))
265 					input
266 					.one("unmask", function() {
267 						input
268 							.unbind(".mask")
269 							.removeData("buffer")
270 							.removeData("tests");
271 					})
272 					.bind("focus.mask", function() {
273 						focusText = input.val();
274 						var pos = checkVal();
275 						writeBuffer();
276 						setTimeout(function() {
277 							if (pos == mask.length)
278 								input.caret(0, pos);
279 							else
280 								input.caret(pos);
281 						}, 0);
282 					})
283 					.bind("blur.mask", function() {
284 						checkVal();
285 						if (input.val() != focusText)
286 							input.change();
287 					})
288 					.bind("keydown.mask", keydownEvent)
289 					.bind("keypress.mask", keypressEvent)
290 					.bind(pasteEventName, function() {
291 						setTimeout(function() { input.caret(checkVal(true)); }, 0);
292 					});
293 
294 				checkVal(); //Perform initial check for existing values
295 			});
296 		}
297 	});
298 })(jQuery);