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);