Advertisement
Guest User

Untitled

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