1/******************************************************************************* 2 MaskedPassword.js Converts password fields into "masked" password fields 3 http://www.sitepoint.com/better-passwords-1-the-masked-password-field/ 4 ------------------------------------------------------------------------------ 5 Adapted from FormTools.addPasswordMasking 6 Info/Docs http://www.brothercake.com/site/resources/scripts/formtools/ 7 ------------------------------------------------------------------------------ 8*******************************************************************************/ 9 10 11 12//masked password constructor 13function MaskedPassword(passfield, symbol) 14{ 15 //if the browser is unsupported, silently fail 16 //[pre-DOM1 browsers generally, and Opera 8 specifically] 17 if(typeof document.getElementById == 'undefined' 18 || typeof document.styleSheets == 'undefined') { return false; } 19 20 //or if the passfield doesn't exist, silently fail 21 if(passfield == null) { return false; } 22 23 //save the masking symbol 24 this.symbol = symbol; 25 26 //identify Internet Explorer for a couple of conditions 27 this.isIE = typeof document.uniqueID != 'undefined'; 28 29 //delete any default value for security (and simplicity!) 30 passfield.value = ''; 31 passfield.defaultValue = ''; 32 33 //create a context wrapper, so that we have sole context for modifying the content 34 //(ie. we can go context.innerHTML = replacement; without catering for 35 // anything else that's there besides the password field itself) 36 //and give it a distinctive and underscored name, to prevent conflict 37 passfield._contextwrapper = this.createContextWrapper(passfield); 38 39 //create a fullmask flag which will be used from events to determine 40 //whether to mask the entire password (true) 41 //or to stop at the character limit (false) 42 //most events set the flag before being called, except for onpropertychange 43 //which uses whatever the setting currently is 44 //this used to be an argument, but onpropertychange fires from our modifications 45 //as well as manual input, so the blur event that's supposed to have a fullmask 46 //triggers in turn an onpropertychange which doesn't, with the end result 47 //that fullmask never works; so by doing it like this, we can set it to 48 //true from the blur event and that will persist through all subsequent 49 //onpropertychange events, until another manual event changes it back to fal
49se 50 this.fullmask = false; 51 52 //for the code that converts a password field into a masked field 53 //I used to have lovely clean elegant code for most browsers, then 54 //ugly horrible hacky code for IE; but since the hacky approach does 55 //actually work for everyone, and we have to have it here whatever, 56 //we may as well just use it for everyone, and get a big saving in code-size 57 //it also means we'll get total behavioral consistency, in terms of 58 //the preservation (or rather, lack thereof) of existing event handlers 59 60 //save a reference to the wrapper because the passfield reference will be lost soon 61 var wrapper = passfield._contextwrapper; 62 63 //create the HTML for the hidden field 64 //using the name from the original password field 65 var hiddenfield = '<input type="hidden" name="' + passfield.name + '">'; 66 67 //copy the HTML from the password field to create the new plain-text field 68 var textfield = this.convertPasswordFieldHTML(passfield); 69 70 //write the hiddenfield and textfield HTML into the wrapper, replacing what's there 71 wrapper.innerHTML = hiddenfield + textfield; 72 73 //grab back the passfield reference back and save it back to passfield 74 //then add the masked-password class 75 passfield = wrapper.lastChild; 76 passfield.className += ' masked'; 77 78 //try to disable autocomplete for this field 79 //to prevent firefox from remembering and subsequently offering 80 //a menu of useless masking strings, things like "â«â«â«â«â«â«â«f" 81 //which of course can't be decoded, they'll just be represented by whatever 82 //is in the realfield value at the time, ie. a completely unrelated value! 83 passfield.setAttribute('autocomplete', 'off'); 84 85 //now grab the hidden field reference, 86 //saving it as a property of the passfield 87 passfield._realfield = wrapper.firstChild; 88 89 //restore its contextwrapper reference 90 passfield._contextwrapper = wrapper; 91 92 //limit the caret position so that you can only edit or select from the end 93 //you can't add, edit or select from the beginning or middle of the field 94 //otherwise we can't track which masked characters represent which letters 95 //(far from ideal I know, but I can't see how else to know 96 //which masking symbols represent which letters if you edit from the middle..?) 97 this.limitCaretPosition(passfield); 98 99 //save a reference to this 100 var self = this; 101 102 //then apply the core events to the visible field 103 this.addListener(passfield, 'change', function(e) 104 { 105 self.fullmask = false; 106 self.doPasswordMasking(self.getTarget(e)); 107 }); 108 this.addListener(passfield, 'input', function(e) 109 { 110 self.fullmask = false; 111 self.doPasswordMasking(self.getTarget(e)); 112 }); 113 //no fullmask setting for onpropertychange (as noted above) 114 this.addListener(passfield, 'propertychange', function(e) 115 { 116 self.doPasswordMasking(self.getTarget(e)); 117 }); 118 119 //for keyup, don't respond to the tab or shift key, otherwise when you [shift/]tab 120 //into the field the keyup will cause the fully-masked password to become partially masked 121 //which is inconsistent with the mouse since it doesn't happen when you click focus 122 //so it's only supposed to happen when you actually edit it; we'll also prevent it 123 //from happening in response to arrows keys as well, for visual completeness! 124 //and from the other modifiers keys, just cos it feels like the right thing to do 125 this.addListener(passfield, 'keyup', function(e) 126 { 127 if (e != undefined && e.keyCode != undefined && !/^(9|1[678]|224|3[789]|40)$/.test(e.keyCode.toString())) 128 { 129 self.fullmask = false; 130 self.doPasswordMasking(self.getTarget(e)); 131 } 132 }); 133 134 //the blur event completely masks the input password 135 //(as opposed to leaving the last n characters plain during input) 136 this.addListener(passfield, 'blur', function(e) 137 { 138 self.fullmask = true; 139 self.doPasswordMasking(self.getTarget(e)); 140 }); 141 142 //so between those events we get completely rock-solid behavior 143 //with enough redundency to ensure that all input paths are covered 144 //and no flickering of text between states :-) 145 146 //force the parent form to reset onload 147 //thereby clearing all values after soft refreh 148 this.forceFormReset(passfield); 149 150 //return true for success 151 return true; 152} 153 154 155//associated utility methods 156MaskedPassword.prototype = 157{ 158 159 //implement password masking for a textbox event 160 doPasswordMasking : function(textbox) 161 { 162 //create the plain password string 163 var plainpassword = ''; 164 165 //if we already have a real field value we need to work out the difference 166 //between that and the value that's in the input field 167 if(textbox._realfield.value != '') 168 { 169 //run through the characters in the input string 170 //and build the plain password out of the corresponding characters 171 //from the real field, and any plain characters in the input 172 for(var i=0; i<textbox.value.length; i++) 173 { 174 if(textbox.value.charAt(i) == this.symbol) 175 { 176 plainpassword += textbox._realfield.value.charAt(i); 177 } 178 else 179 { 180 plainpassword += textbox.value.charAt(i); 181 } 182 } 183 } 184 185 //if there's no real field value then we're doing this for the first time 186 //so whatever's in the input field is the entire plain password 187 else 188 { 189 plainpassword = textbox.value; 190 } 191 192 //get the masked version of the plainpassword, according to fullmask 193 //and passing the textbox reference so we have its symbol and limit properties 194 var maskedstring = this.encodeMaskedPassword(plainpassword, this.fullmask, textbox); 195 196 //then we modify the textfield values 197 //if (AND ONLY IF) one of the values are now different from the original 198 //(this condition is essential to avoid infinite repetition 199 // leading to stack overflow, from onpropertychange in IE 200 // [value changes, fires event, which changes value, which fires event ...]) 201 //we check both instead of just one, so that we can still allow the action 202 //of changing the mask without modifying the password itself 203 if(textbox._realfield.value != plainpassword || textbox.value != maskedstring) 204 { 205 //copy the plain password to the real field 206 textbox._realfield.value = plainpassword; 207 208 //then write the masked value to the original textbox 209 textbox.value = maskedstring; 210 } 211 }, 212 213 214 //convert a plain-text password to a masked password 215 encodeMaskedPassword : function(passwordstring, fullmask, textbox) 216 { 217 //the character limit is nominally 1 218 //this is how many characters to leave plain at the end 219 //but if the fullmask flag is true the limit is zero 220 //and the password will be fully masked 221 var characterlimit = fullmask === true ? 0 : 1; 222 223 //create the masked password string then iterate 224 //through he characters in the plain password 225 for(var maskedstring = '', i=0; i<passwordstring.length; i++) 226 { 227 //if we're below the masking limit, 228 //add a masking symbol to represent this character 229 if(i < passwordstring.length - characterlimit) 230 { 231 maskedstring += this.symbol; 232 } 233 //otherwise just copy across the real character 234 else 235 { 236 maskedstring += passwordstring.charAt(i); 237 } 238 } 239
240 //return the final masked string 241 return maskedstring; 242 }, 243 244 245 //create a context wrapper element around a password field 246 createContextWrapper : function(passfield) 247 { 248 //create the wrapper and add its class 249 //it has to be an inline element because we don't know its context 250 var wrapper = document.createElement('span'); 251 252 //enforce relative positioning 253 wrapper.style.position = 'relative'; 254 255 //insert the wrapper directly before the passfield 256 passfield.parentNode.insertBefore(wrapper, passfield); 257 258 //then move the passfield inside it 259 wrapper.appendChild(passfield); 260 261 //return the wrapper reference 262 return wrapper; 263 }, 264 265 266 //force a form to reset its values, so that soft-refresh does not retain them 267 forceFormReset : function(textbox) 268 { 269 //find the parent form from this textbox reference 270 //(which may not be a textbox, but that's fine, it just a reference name!) 271 while(textbox) 272 { 273 if(/form/i.test(textbox.nodeName)) { break; } 274 textbox = textbox.parentNode; 275 } 276 //if the reference is not a form then the textbox wasn't wrapped in one 277 //so in that case we'll just have to abandon what we're doing here 278 if(!/form/i.test(textbox.nodeName)) { return null; } 279 280 //otherwise bind a load event to call the form's reset method 281 //we have to defer until load time for IE or it won't work 282 //because IE renders the page with empty fields 283 //and then adds their values retrospectively! 284 //(but in other browsers we can use DOMContentLoaded; 285 // and the load listener function takes care of that split) 286 this.addSpecialLoadListener(function() { textbox.reset(); }); 287 288 //return the now-form reference 289 return textbox; 290 }, 291 292 293 //copy the HTML from a password field to a plain text field, 294 //we have to convert the field this way because of Internet Explorer 295 //because it doesn't support setting or changing the type of an input 296 convertPasswordFieldHTML : function(passfield, addedattrs) 297 { 298 //start the HTML for a text field 299 var textfield = '<input'; 300 301 //now run through the password fields' specified attributes 302 //and copy across each one into the textfield HTML 303 //*except* for its name and type, and any formtools underscored attributes 304 //we need to exclude the name because we'll define that separately 305 //depending on the situation, and obviously the type, and formtools attributes 306 //because we control them and their meaning in separate conditions too 307 for(var fieldattributes = passfield.attributes, 308 j=0; j<fieldattributes.length; j++) 309 { 310 //we have to check .specified otherwise we'll get back every single attribute 311 //that the element might possibly have! which is what IE puts in the attributes 312 //collection, with default values for unspecified attributes 313 if(fieldattributes[j].specified && !/^(_|type|name)/.test(fieldattributes[j].name)) 314 { 315 textfield += ' ' + fieldattributes[j].name + '="' + fieldattributes[j].value + '"'; 316 } 317 } 318 319 //now add the type of "text" to the end, plus an autocomplete attribute, and close it 320 //we add autocomplete attribute for added safety, though it probably isnt necessary, 321 //since browsers won't offer to remember it anywway, because the field has no name 322 //this uses HTML4 empty-element syntax, but we don't need to distinguish by spec 323 //because the browser's internal representations will generally be identical anyway 324 textfield += ' type="text" autocomplete="off">'; 325 326 //return the finished textfield HTML 327 return textfield; 328 }, 329 330 331 //this crap is what it takes to force the caret in a textbox to stay at the end 332 //I'd really rather not to do this, but it's the only way to have reliable encoding 333 limitCaretPosition : function(textbox) 334 { 335 //create a null timer reference and start function 336 var timer = null, start = function() 337 { 338 //prevent multiple instances 339 if(timer == null) 340 { 341 //IE uses this range stuff 342 if(this.isIE) 343 { 344 //create an interval that continually force the position 345 //as long as the field has the focus 346 timer = window.setInterval(function() 347 { 348 //we can only force position to the end 349 //because there's no way to know whether there's a selection 350 //or just a single caret point, because the range methods 351 //we could use to determine that don't work on created fields 352 //(they generate "Invalid argument" errors) 353 var range = textbox.createTextRange(), 354 valuelength = textbox.value.length, 355 character = 'character'; 356 range.moveEnd(character, valuelength); 357 range.moveStart(character, valuelength); 358 range.select(); 359 360 //not so fast as to be a major CPU hog 361 //but fast enough to do the job effectively 362 }, 100); 363 } 364 //other browsers have these selection properties 365 else 366 {
367 //create an interval that continually force the position 368 //as long as the field has the focus 369 timer = window.setInterval(function() 370 { 371 //allow selection from or position at the end 372 //otherwise force position to the end 373 var valuelength = textbox.value.length; 374 if(!(textbox.selectionEnd == valuelength && textbox.selectionStart <= valuelength)) 375 { 376 textbox.selectionStart = valuelength; 377 textbox.selectionEnd = valuelength; 378 } 379 380 //ditto 381 }, 100); 382 } 383 } 384 }, 385 386 //and a stop function 387 stop = function() 388 { 389 window.clearInterval(timer); 390 timer = null; 391 }; 392 393 //add events to start and stop the timer 394 this.addListener(textbox, 'focus', function() { start(); }); 395 this.addListener(textbox, 'blur', function() { stop(); }); 396 }, 397 398 399 //add an event listener 400 //this is deliberately not called "addEvent" so that we can 401 //compress the name, which would otherwise also effect "addEventListener" 402 addListener : function(eventnode, eventname, eventhandler) 403 { 404 if(typeof document.addEventListener != 'undefined') 405 { 406 return eventnode.addEventListener(eventname, eventhandler, false); 407 } 408 else if(typeof document.attachEvent != 'undefined') 409 { 410 return eventnode.attachEvent('on' + eventname, eventhandler); 411 } 412 }, 413 414 415 //add a special load listener, split between 416 //window load for IE and DOMContentLoaded for others 417 //this is only used by the force form reset method, which wants that split 418 addSpecialLoadListener : function(eventhandler) 419 { 420 //we specifically need a browser condition here, not a feature test 421 //because we know specifically what should be given to who 422 //and that doesn't match general support for these constructs 423 if (typeof document.addEventListener != 'undefined') 424 { 425 return document.addEventListener('DOMContentLoaded', eventhandler, false); 426 } 427 else 428 { 429 return window.attachEvent('onload', eventhandler); 430 } 431 }, 432 433 434 //get an event target by sniffing for its property name 435 //(assuming here that e is already a cross-model reference 436 //as it is from addListener because attachEvent in IE 437 //automatically provides a corresponding event argument) 438 getTarget : function(e) 439 { 440 //just in case! 441 if(!e) { return null; } 442 443 //otherwise return the target 444 return e.target ? e.target : e.srcElement; 445 } 446 447} 448 449
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.