PageSourceSearch

https://fastclaims.com/scripts/libs/MaskedPassword.js

js fastclaims.com collected 2026-10-05 02:26:11 UTC 17,109 bytes, 449 lines download raw bytes

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.