/** * @license Pass*Field | (c) 2013 Antelle | https://github.com/antelle/passfield/blob/master/MIT-LICENSE.txt */ // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /** * Entry point * Initializes PassField * If jQuery is present, sets jQuery plugin ($.passField) */ (function($, document, window, undefined) { "use strict"; // ========================== definitions ========================== var PassField = window.PassField = window.PassField || {}; /** * Password char types * @readonly * @enum {string} */ PassField.CharTypes = { DIGIT: "digits", LETTER: "letters", LETTER_UP: "letters_up", SYMBOL: "symbols", UNKNOWN: "unknown" }; /** * Password checking mode * @readonly * @enum {number} */ PassField.CheckModes = { /** more user friendly: if a password is better than the pattern (e.g. longer), its strength is increased and it could match even not containing all char types */ MODERATE: 0, /** more strict: it a password is longer than expected length, this makes no difference; all rules must be satisfied */ STRICT: 1 }; // ========================== defaults ========================== PassField.Config = { defaults: { pattern: "abcdef12", // pattern for password (for strength calculation) acceptRate: 0.8, // threshold (of strength conformity with pattern) under which the password is considered weak allowEmpty: true, // allow empty password (will show validation errors if not) isMasked: true, // is the password masked by default showToggle: true, // show toggle password masking button showGenerate: true, // show generation button showWarn: true, // show short password validation warning showTip: true, // show password validation tooltips tipPopoverStyle: {}, // if tooltip is shown and Twitter Bootstrap is present, this style will be used. null = use own tips, {} = bootstrap tip options strengthCheckTimeout: 500, // timeout before automatic strength checking if no key is pressed (in ms; 0 = disable this feature) validationCallback: null, // function which will be called when password strength is validated blackList: [], // well-known bad passwords (very weak), e.g. qwerty or 12345 locale: "", // selected locale (null to auto-detect) localeMsg: {}, // overriden locale messages warnMsgClassName: "help-inline form-control-static", // class name added to the waring control (empty or null to disable the feature) errorWrapClassName: "error", // class name added to wrapping control when validation fails (empty or null to disable the feature) allowAnyChars: true, // suppress validation errors if password contains characters not from list (chars param) checkMode: PassField.CheckModes.MODERATE, // password checking mode (how the strength is calculated) chars: { // symbol sequences for generation and checking digits: "1234567890", letters: "abcdefghijklmnopqrstuvwxyzßабвгедёжзийклмнопрстуфхцчшщъыьэюяґєåäâáàãéèêëíìîїóòôõöüúùûýñçøåæþðαβγδεζηθικλμνξοπρσςτυφχψω", letters_up: "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГЕДЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯҐЄÅÄÂÁÀÃÉÈÊËÍÌÎЇÓÒÔÕÖÜÚÙÛÝÑÇØÅÆÞÐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ", symbols: "@#$%^&*()-_=+[]{};:<>/?!" }, events: { generated: null, switched: null }, nonMatchField: null, // login field (to compare password with - should not be equal) length: { // strict length checking rules min: null, max: null }, maskBtn : { textMasked : "abc", textUnmasked: "•••", className: false, classMasked: false, classUnmasked: false } }, locales: PassField.Config ? PassField.Config.locales : {}, // locales are defined in locales.js blackList: [ "password", "123456", "12345678", "abc123", "qwerty", "monkey", "letmein", "dragon", "111111", "baseball", "iloveyou", "trustno1", "1234567", "sunshine", "master", "123123", "welcome", "shadow", "ashley", "football", "jesus", "michael", "ninja", "mustang", "password1", "p@ssw0rd", "miss", "root", "secret" ], generationChars: { digits: "1234567890", letters: "abcdefghijklmnopqrstuvwxyz", letters_up: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, dataAttr: "PassField.Field" }; // ========================== initialization ========================== /** * Encapsulates PassField logic * @param {HTMLInputElement|string} el - input for which the field is initizlized (or ID of the input) * @param {object} [opts] - options to override defaults */ PassField.Field = function(el, opts) { var _conf = PassField.Config; var _dom = {}; var _opts = utils.extend({}, _conf.defaults, opts); var _isMasked = true; var _locale; var _id; var _validateTimer; var _blurTimer; var _blurMaskTimer; var _warningShown = false; var _validationResult = null; var _features; var _is_input_group = false; var _isInputHover = false; var _isInputFocused = false; var _passHidesGenBtn = false; var _passHidesMaskBtn = false; var _bootstrapPopoverShownText = false; var _tipHtml = null; var ELEMENTS_PREFIX = "a_pf-"; var JQUERY_EVENT_PREFIX = "pass:"; var BUTTONS_PADDING_RIGHT = 5; var KEY_DELETE = 46; var KEY_BACKSPACE = 8; var BOOTSTRAP_INPUT_GROUP_CLASS = "input-group"; // exports this.toggleMasking = function(isMasked) { toggleMasking(isMasked); }; this.setPass = setPass; this.validatePass = validatePassword; this.getPassValidationMessage = getPassValidationMessage; this.getPassStrength = getPassStrength; init.call(this); /** * Initizlizes the password field */ function init() { fixErrorsAndFillOptions(); if (!setMainEl()) return; setLocale(); defineId(); detectFeatures(); createNodes(); bindEvents(); toggleMasking(_opts.isMasked, false); doAutofocus(); assignDataObject(PassField.Config.dataAttr, this); } // ========================== logic ========================== /** * Corrects user errors in options * Fills blackList */ function fixErrorsAndFillOptions() { _opts.blackList = (_opts.blackList || []).concat(PassField.Config.blackList); } /** * Sets mainEl to the actual element (it can be string) */ function setMainEl() { if (typeof el == "string") { //noinspection JSValidateTypes el = document.getElementById(el); } _dom.mainInput = el; if(hasClass(el.parentNode, BOOTSTRAP_INPUT_GROUP_CLASS, true)) { _is_input_group = true; } return !!_dom.mainInput; } /** * Fills _locale from setting defined in _opts * Locale will be merged from default locale and user-defined messages */ function setLocale() { var neutralLocale = "en"; var loc = _opts.locale; if (!loc && navigator.language) loc = navigator.language.replace(/\-.*/g, ""); if (loc) _locale = _conf.locales[loc]; if (_locale) _locale = utils.extend({}, _conf.locales[neutralLocale], _locale); if (!_locale) _locale = utils.extend({}, _conf.locales[neutralLocale]); if (_opts.localeMsg) utils.extend(_locale.msg, _opts.localeMsg); } /** * Sets password value in field * @param {String} val - value to set */ function setPass(val) { _dom.mainInput.value = val; if (_dom.clearInput) { _dom.clearInput.value = val; } handleInputKeyup(); } /** * Defines _id * This will be id of the main input or random id (if main input doesn't have id) */ function defineId() { _id = _dom.mainInput.id; if (!_id) { _id = ("i" + Math.round(Math.random() * 100000)); _dom.mainInput.id = _id; } } /** * Inserts DOM nodes to the tree * Fills _dom with created objects */ function createNodes() { var mainInputRect = getRect(_dom.mainInput); mainInputRect.top += cssFloat(_dom.mainInput, "marginTop"); setupWrapper(); setInputAttrs(); createClearInput(); createWarnLabel(); createMaskBtn(); createGenBtn(); createTip(mainInputRect); createFakePlaceholder(mainInputRect); createPassLengthChecker(); setTimeout(resizeControls, 0); } /** * Assigns necessary properties to wrapper element */ function setupWrapper() { _dom.wrapper = _dom.mainInput.parentNode; addClass(_dom.wrapper, "wrap"); if (!_features.hasInlineBlock) { addClass(_dom.wrapper, "wrap-no-ib"); } if (css(_dom.wrapper, "position") == "static") { _dom.wrapper.style.position = "relative"; } } /** * Assigns attributes (from options) to the input field */ function setInputAttrs() { if (_opts.length && _opts.length.max) _dom.mainInput.setAttribute("maxLength", _opts.length.max.toString()); } /** * Creates cleartext password field */ function createClearInput() { if (!_features.changeType) { // IE < 9 don't support input type changing _dom.clearInput = newEl("input", { type: "text", id: "txt-clear", className: "txt-clear", value: _dom.mainInput.value }, { display: "none" }); var cls = _dom.mainInput.className; if (cls) { addClass(_dom.clearInput, cls, true); } var inputStyle = _dom.mainInput.style.cssText; if (inputStyle) { _dom.clearInput.style.cssText = inputStyle; } utils.each(["maxLength", "size", "placeholder"], function(attr) { var value = _dom.mainInput.getAttribute(attr); if (value) { _dom.clearInput.setAttribute(attr, value); } }); insertAfter(_dom.mainInput, _dom.clearInput); } addClass(_dom.mainInput, "txt-pass"); } /** * Creates password warning label */ function createWarnLabel() { if (_opts.showWarn) { _dom.warnMsg = newEl("p", { id: "warn", className: "warn" }, { margin: "0 0 0 3px" }); addClass(_dom.warnMsg, "empty"); if (_opts.warnMsgClassName) addClass(_dom.warnMsg, _opts.warnMsgClassName, true); var insertAfterNode = _dom.clearInput || _dom.mainInput; if (hasClass(el.parentNode, BOOTSTRAP_INPUT_GROUP_CLASS, true)) { insertAfterNode = insertAfterNode.parentNode; } insertAfter(insertAfterNode, _dom.warnMsg); } } /** * Creates toggle password masking button */ function createMaskBtn() { if (_opts.showToggle) { var zIndex = css(_dom.mainInput, 'z-index'); _dom.maskBtn = newEl("div", { id: "btn-mask", className: "btn-mask", title: _locale.msg.showPass }, { position: "absolute", margin: "0", padding: "0", 'z-index': zIndex ? zIndex + 1 : null }); addClass(_dom.maskBtn, "btn"); if (_opts.maskBtn.className) { addClass(_dom.maskBtn, _opts.maskBtn.className, true); } if (_opts.maskBtn.classMasked) { addClass(_dom.maskBtn, _opts.maskBtn.classMasked, true); } setHtml(_dom.maskBtn, _opts.maskBtn.textMasked); insertBefore(_dom.mainInput, _dom.maskBtn); } } /** * Creates password generation button */ function createGenBtn() { if (_opts.showGenerate) { var zIndex = css(_dom.mainInput, "z-index"); _dom.genBtn = newEl("div", { id: "btn-gen", className: "btn-gen", title: _locale.msg.genPass }, { position: "absolute", margin: "0", padding: "0", "z-index": zIndex ? zIndex + 1 : null }); addClass(_dom.genBtn, "btn"); insertBefore(_dom.mainInput, _dom.genBtn); _dom.genBtnInner = newEl("div", { id: "btn-gen-i", className: "btn-gen-i", title: _locale.msg.genPass }); _dom.genBtn.appendChild(_dom.genBtnInner); } } /** * Creates password tooltip * If Twitter Bootstrap is present, this will be popover; fallback to own popover else * @param {Object} mainInputRect - password input element rect */ function createTip(mainInputRect) { if (_opts.showTip) { if (_opts.tipPopoverStyle && $ && typeof $.fn.popover === "function") { // using Twitter Bootstrap $(_dom.mainInput).popover(utils.extend({ // popover defaults (overridable) title: '', placement: _opts.tipPopoverStyle.placement || function(pop, el) { //noinspection JSValidateTypes var top = $(el).position().top - $(window).scrollTop(); var spaceBelow = $(window).height() - top; return spaceBelow > 300 || spaceBelow > top ? "bottom" : "top"; }, animation: false }, _opts.tipPopoverStyle, { // popovers properties (non-overridable) trigger: "manual", html: true, content: function() { return _tipHtml; } })); } else { // not using Twitter Bootstrap _dom.tip = newEl("div", { id: "tip", className: "tip" }, { position: "absolute", margin: "0", padding: "0", width: mainInputRect.width + "px" }); insertBefore(_dom.mainInput, _dom.tip); var arrWrap = newEl("div", { id: "tip-arr-wrap", className: "tip-arr-wrap" }); _dom.tip.appendChild(arrWrap); arrWrap.appendChild(newEl("div", { id: "tip-arr", className: "tip-arr" })); arrWrap.appendChild(newEl("div", { id: "tip-arr-in", className: "tip-arr-in" })); _dom.tipBody = newEl("div", { id: "tip-body", className: "tip-body" }); _dom.tip.appendChild(_dom.tipBody); } } } /** * Creates fake placeholders (if there's no support in browser) * @param {Object} mainInputRect - password input element rect */ function createFakePlaceholder(mainInputRect) { if (!_features.placeholders) { var placeholderText = _dom.mainInput.getAttribute("placeholder") || _dom.mainInput.getAttribute("data-placeholder"); if (placeholderText) { _dom.placeholder = newEl("div", { id: "placeholder", className: "placeholder" }, { position: "absolute", margin: "0", padding: "0", height: mainInputRect.height + "px", lineHeight: mainInputRect.height + "px" }); setHtml(_dom.placeholder, placeholderText); insertBefore(_dom.mainInput, _dom.placeholder); } } else if (!_dom.mainInput.getAttribute("placeholder") && _dom.mainInput.getAttribute("data-placeholder")) { _dom.mainInput.setAttribute("placeholder", _dom.mainInput.getAttribute("data-placeholder")); } } /** * Creates invisible div for measuring password test rect */ function createPassLengthChecker() { if (_features.passSymbol) { _dom.passLengthChecker = newEl("div", { id: "len" }, { position: "absolute", height: css(_dom.mainInput, "height"), top: "-10000px", left: "-10000px", display: "block", color: "transparent", border: "none" }); insertBefore(_dom.mainInput, _dom.passLengthChecker); setTimeout(function() { utils.each(["marginLeft", "fontFamily", "fontSize", "fontWeight", "fontStyle", "fontVariant"], function(attr) { var value = css(_dom.mainInput, attr); if (value) { _dom.passLengthChecker.style[attr] = value; } }); }, 50); } } /** * Resizes controls (for window.onresize event) */ function resizeControls() { toggleButtons(); toggleTip(); var rect = getRect(getActiveInput()); var left = getRightBtnPadding(); if (_dom.maskBtn && _dom.maskBtn.style.display != "none") { left += cssFloat(_dom.maskBtn, "width"); setRect(_dom.maskBtn, { top: rect.top, left: rect.left + rect.width - left, height: rect.height }); } if (_dom.genBtn && _dom.genBtn.style.display != "none") { left += cssFloat(_dom.genBtn, "width"); setRect(_dom.genBtn, { top: rect.top, left: rect.left + rect.width - left, height: rect.height }); _dom.genBtnInner.style.marginTop = Math.max(0, Math.round((rect.height - 19) / 2)) + "px"; } if (_dom.placeholder && _dom.placeholder.style.display != "none") { setRect(_dom.placeholder, { top: rect.top, left: rect.left + 7, height: rect.height }); } if (_dom.tip && _dom.tip.style.display != "none") { setRect(_dom.tip, { left: rect.left, top: rect.top + rect.height, width: rect.width }); } } /** * Gets padding after right button * @returns {number} - padding in px. */ function getRightBtnPadding() { var paddingRight = cssFloat(getActiveInput(), "paddingRight"); return Math.max(BUTTONS_PADDING_RIGHT, paddingRight); } /** * Shows or hides buttons depending on the controls state */ function toggleButtons() { if (_dom.genBtn) { _dom.genBtn.style.display = _isInputHover || _isInputFocused && !_passHidesGenBtn ? "block" : "none"; } if (_dom.maskBtn) { _dom.maskBtn.style.display = _isInputHover || _isInputFocused && !_passHidesMaskBtn ? "block" : "none"; } } /** * Toggles tip visibility */ function toggleTip() { if (!_opts.showTip) { return; } if (_dom.tip) { _dom.tip.style.display = (_warningShown && _isInputFocused) ? "block" : "none"; } else { if (_warningShown && _isInputFocused) { if (!_bootstrapPopoverShownText || (_tipHtml != _bootstrapPopoverShownText)) { var data = $(_dom.mainInput).data("popover") || $(_dom.mainInput).data("bs.popover"); var opts = data.options; var animationBackup = opts.animation; if (_bootstrapPopoverShownText) opts.animation = false; // set popover width (Bootstrap popovers doesn't support width setting, so we'll apply a hack) var width = getActiveInput().offsetWidth - 2; var el = data.$tip; if (el) { el.width(width); } else if (data.options.template) { data.options.template = data.options.template.replace("class=\"popover\"", "class=\"popover\" style=\"width: " + width + "px\""); } if (_dom.clearInput) { data.$element = $(getActiveInput()); } $(_dom.mainInput).popover("show"); _bootstrapPopoverShownText = _tipHtml; opts.animation = animationBackup; } } else { if (_bootstrapPopoverShownText) { _bootstrapPopoverShownText = null; $(_dom.mainInput).popover("hide"); } } } } /** * Detects browser features support * Fills in _features variable */ function detectFeatures() { var supportsPlaceholder = true; var changeType = true; var test = document.createElement("input"); if (!("placeholder" in test)) { supportsPlaceholder = false; } test.setAttribute("style", "position:absolute;left:-10000px;top:-10000px;"); document.body.appendChild(test); try { test.setAttribute("type", "password"); } catch (err) { changeType = false; } document.body.removeChild(test); var box = document.createElement("div"); box.setAttribute("style", "display:inline-block"); box.style.paddingLeft = box.style.width = "1px"; document.body.appendChild(box); var isBoxModel = box.offsetWidth == 2; var hasInlineBlock = css(box, "display") === "inline-block"; document.body.removeChild(box); var passSymbol = navigator.userAgent.indexOf("AppleWebKit") >= 0 || navigator.userAgent.indexOf("Opera") >= 0 || navigator.userAgent.indexOf("Firefox") >= 0 && navigator.platform.indexOf("Mac") >= 0 ? /*BULLET*/"\u2022" : /*BLACK CIRCLE*/"\u25cf"; _features = { placeholders: supportsPlaceholder, changeType: changeType, boxModel: isBoxModel, hasInlineBlock: hasInlineBlock, passSymbol: passSymbol }; } /** * Gets currently active input * @return {HTMLInputElement} - currently active input. */ function getActiveInput() { return _isMasked ? _dom.mainInput : _dom.clearInput || _dom.mainInput; } /** * Binds handler events to DOM nodes */ function bindEvents() { utils.each(_dom.clearInput ? [_dom.mainInput, _dom.clearInput] : [_dom.mainInput], function (el) { utils.attachEvent(el, "onkeyup", handleInputKeyup); utils.attachEvent(el, "onfocus", handleInputFocus); utils.attachEvent(el, "onblur", handleInputBlur); utils.attachEvent(el, "onmouseover", handleMouseEvent); utils.attachEvent(el, "onmouseout", handleMouseEvent); if (_dom.placeholder) { utils.attachEvent(el, "onkeydown", handleInputKeydown); } }); utils.attachEvent(window, "onresize", resizeControls); if (_dom.maskBtn) { utils.attachEvent(_dom.maskBtn, "onclick", function() { toggleMasking(); }); utils.attachEvent(_dom.maskBtn, "onmouseover", handleMouseEvent); utils.attachEvent(_dom.maskBtn, "onmouseout", handleMouseEvent); } if (_dom.genBtn) { utils.attachEvent(_dom.genBtn, "onclick", function() { generatePassword(); }); utils.attachEvent(_dom.genBtn, "onmouseover", handleMouseEvent); utils.attachEvent(_dom.genBtn, "onmouseout", handleMouseEvent); } if (_dom.placeholder) { utils.attachEvent(_dom.placeholder, "onclick", handlePlaceholderClicked); } if (_opts.nonMatchField) { var el = getEl(_opts.nonMatchField); if (el) { utils.attachEvent(el, "onkeyup", handleLoginChanged); } } } /** * Handles fake placeholder click event */ function handlePlaceholderClicked() { getActiveInput().focus(); } /** * Handles MouseOver and MouseOut events * sets _inputHover state * @param {object} e - event */ function handleMouseEvent(e) { var isInside = e.type === "mouseover"; var el = e.relatedTarget ? e.relatedTarget : isInside ? e.fromElement : e.toElement; if (el && el.id && (el.id.indexOf(ELEMENTS_PREFIX + "btn") === 0 || el === _dom.mainInput || el === _dom.clearInput)) return; _isInputHover = isInside; resizeControls(); } /** * Input received keyup * @param {Event} [e] - event */ function handleInputKeyup(e) { var keyCode = e ? e.which || e.keyCode : null; var isDelete = keyCode === KEY_BACKSPACE || keyCode === KEY_DELETE; var val; if (_dom.clearInput) { if (_isMasked) { val = _dom.clearInput.value = _dom.mainInput.value; } else { val = _dom.mainInput.value = _dom.clearInput.value; } } else { val = _dom.mainInput.value; } if (_opts.strengthCheckTimeout > 0 && !_warningShown && !isDelete) { if (_validateTimer) { clearTimeout(_validateTimer); } _validateTimer = setTimeout(validatePassword, _opts.strengthCheckTimeout); } else { validatePassword(); } if (_dom.placeholder && !val) { _dom.placeholder.style.display = "block"; } hideButtonsByPassLength(); } /** * Hides buttons if the password is too long */ function hideButtonsByPassLength() { if (!_dom.passLengthChecker) return; var pass = getActiveInput().value; if (_isMasked) pass = pass.replace(/./g, _features.passSymbol); setHtml(_dom.passLengthChecker, pass); var passWidth = _dom.passLengthChecker.offsetWidth; passWidth += cssFloat(_dom.mainInput, "paddingLeft"); var maskBtnWidth = 0, genBtnWidth = 0; var fieldBounds = getBounds(getActiveInput()); var fieldWidth = fieldBounds.width; var changed = false; var btnPadding = getRightBtnPadding(); if (_dom.maskBtn) { maskBtnWidth = cssFloat(_dom.maskBtn, "width"); var maskBtnLeft = fieldWidth - maskBtnWidth - btnPadding; var passHidesMaskBtn = passWidth > maskBtnLeft; if (_passHidesMaskBtn != passHidesMaskBtn) { changed = true; _passHidesMaskBtn = passHidesMaskBtn; } } if (_dom.genBtn) { genBtnWidth = cssFloat(_dom.genBtn, "width"); var genBtnLeft = fieldWidth - maskBtnWidth - genBtnWidth - btnPadding; var passHidesGenBtn = passWidth > genBtnLeft; if (_passHidesGenBtn != passHidesGenBtn) { changed = true; _passHidesGenBtn = passHidesGenBtn; } } if (changed) { resizeControls(); } } /** * Input received KeyDown */ function handleInputKeydown() { // Here we support old browsers and remove placeholder if (_dom.placeholder) { _dom.placeholder.style.display = "none"; } } /** * Input is focused */ function handleInputFocus() { if (_blurTimer) { clearTimeout(_blurTimer); _blurTimer = null; } if (_blurMaskTimer) { clearTimeout(_blurMaskTimer); _blurMaskTimer = null; } _isInputFocused = true; resizeControls(); } /** * Input is blurred */ function handleInputBlur() { _blurTimer = setTimeout(function() { _blurTimer = null; _isInputFocused = false; resizeControls(); // if the password has not been masked my default, toggle mode after inactivity if (_opts.isMasked && !_blurMaskTimer) { _blurMaskTimer = setTimeout(function() { _blurMaskTimer = null; toggleMasking(true, false); }, 1500); } }, 100); } /** * Login changed in login field */ function handleLoginChanged() { if (_warningShown) { validatePassword(); } } /** * Toggles masking state * @param {Boolean} [isMasked] - should we display the password masked (undefined or null = change masking) * @param {Boolean} [needFocus] - should we focus the field after changing */ function toggleMasking(isMasked, needFocus) { if (needFocus === undefined) needFocus = true; var eventHappened = isMasked != _isMasked; if (isMasked === undefined) isMasked = !_isMasked; else isMasked = !!isMasked; if (_features.changeType) { var el = getActiveInput(); var sel = getSelection(el); el.setAttribute("type", isMasked ? "password" : "text"); if (needFocus) { setSelection(el, sel); el.focus(); } } else { var currentDisplayMode = css(getActiveInput(), "display") || "block"; var currentInput = isMasked ? _dom.clearInput : _dom.mainInput; var nextInput = isMasked ? _dom.mainInput : _dom.clearInput; if (_isMasked != isMasked) { // LastPass could insert style attributes here: we'll copy them to clear input (if any) utils.each(["paddingRight", "width", "backgroundImage", "backgroundPosition", "backgroundRepeat", "backgroundAttachment", "border"], function (prop) { var cur = currentInput.style[prop]; if (cur) { nextInput.style[prop] = cur; } }); } var selection = getSelection(currentInput); nextInput.style.display = currentDisplayMode; currentInput.style.display = "none"; nextInput.value = currentInput.value; if (needFocus) { setSelection(nextInput, selection); nextInput.focus(); } // jQuery.validation can insert error label right after our input, so we'll handle it here if (_dom.mainInput.nextSibling != _dom.clearInput) { insertAfter(_dom.mainInput, _dom.clearInput); } } if (_dom.maskBtn) { setHtml(_dom.maskBtn, isMasked ? _opts.maskBtn.textMasked : _opts.maskBtn.textUnmasked); if (isMasked) { if (_opts.maskBtn.classUnmasked) removeClass(_dom.maskBtn, _opts.maskBtn.classUnmasked, true); if (_opts.maskBtn.classMasked) addClass(_dom.maskBtn, _opts.maskBtn.classMasked, true); } else { if (_opts.maskBtn.classMasked) removeClass(_dom.maskBtn, _opts.maskBtn.classMasked, true); if (_opts.maskBtn.classUnmasked) addClass(_dom.maskBtn, _opts.maskBtn.classUnmasked, true); } _dom.maskBtn.title = isMasked ? _locale.msg.showPass : _locale.msg.hidePass; } _isMasked = isMasked; hideButtonsByPassLength(); resizeControls(); if (eventHappened) { triggerEvent("switched", _isMasked); } } /** * Gets selected text indices in input * @param {HTMLInputElement} el - element to get the selection * @returns {{ start: Number, end: Number }} - selection. */ function getSelection(el) { if (typeof el.selectionStart === "number" && typeof el.selectionEnd === "number") { return { start: el.selectionStart, end: el.selectionEnd }; } return null; } /** * Sets selection in input * @param {HTMLInputElement} el - element to set the selection * @param {{ start: Number, end: Number }} selection - selection. */ function setSelection(el, selection) { if (!selection) return; if (typeof el.selectionStart === "number" && typeof el.selectionEnd === "number") { el.selectionStart = selection.start; el.selectionEnd = selection.end; } } /** * If the element has autofocus attribute, we'll check it and focus if necessary */ function doAutofocus() { if (typeof _dom.mainInput.hasAttribute === "function" && _dom.mainInput.hasAttribute("autofocus") || _dom.mainInput.getAttribute("autofocus")) { _dom.mainInput.focus(); handleInputFocus(); } } /** * Generates random password and inserts it to the field */ function generatePassword() { var pass = createRandomPassword(); _dom.mainInput.value = pass; if (_dom.clearInput) { _dom.clearInput.value = pass; } triggerEvent("generated", pass); toggleMasking(false); if (_validateTimer) { clearTimeout(_validateTimer); _validateTimer = null; } validatePassword(); if (_dom.placeholder) { _dom.placeholder.style.display = "none"; } } /** * Validates the password and shows an alert if need * @return {Boolean} - is the password valid. */ function validatePassword() { if (_validateTimer) { clearTimeout(_validateTimer); _validateTimer = null; } var pass = getActiveInput().value; var checkResult = calculateStrength(pass); if (pass.length === 0) { checkResult = { strength: _opts.allowEmpty ? 0 : null, messages: [_locale.msg.passRequired] }; } else { // check: contains bad chars if (!_opts.allowAnyChars && checkResult.charTypes[PassField.CharTypes.UNKNOWN]) { checkResult = { strength: null, messages: [_locale.msg.badChars.replace("{}", checkResult.charTypes[PassField.CharTypes.UNKNOWN])] }; } delete checkResult.charTypes; // check: blacklist var isInBlackList = false; utils.each(_opts.blackList, function(el) { if (el == pass) { isInBlackList = true; return false; } return true; }); if (isInBlackList) { checkResult = { strength: 0, messages: [_locale.msg.inBlackList] }; } // check equal to login if (pass && pass === getNonMatchingFieldValue()) { checkResult = { strength: 0, messages: [_locale.msg.equalTo] }; } } // check: external (if present) if (typeof _opts.validationCallback === "function") { var externalResult = _opts.validationCallback(_dom.mainInput, checkResult); var returnedMessages; var returnedStrength; if (externalResult && externalResult.messages && utils.isArray(externalResult.messages)) { returnedMessages = externalResult.messages; } if (externalResult && Object.prototype.hasOwnProperty.call(externalResult, "strength") && ((typeof externalResult.strength === "number") || (externalResult.strength === null))) { returnedStrength = externalResult.strength; } if (returnedMessages && returnedMessages.length) { // both messages and strength are replaced with custom messages checkResult.messages = returnedMessages; checkResult.strength = returnedStrength; } else { // no message is provided; strength can only be increased if (returnedStrength && returnedStrength > checkResult.strength) { checkResult.strength = returnedStrength; } // else: // We have been told: "the password is invalid/weak". // But why? There's no explanation given (in messages) and we can't guess the reason. // We'll not show such unknown errors to the user and disregard returned strength. } } if (pass.length === 0 && _opts.allowEmpty) { // empty but ok hidePasswordWarning(); _validationResult = { strength: 0 }; return true; } else if (checkResult.strength === null || checkResult.strength < _opts.acceptRate) { showPasswordWarning(checkResult.strength, checkResult.messages); return false; } else { // ok hidePasswordWarning(); _validationResult = { strength: checkResult.strength }; return true; } } /** * Calculates password strength according to pattern * @param {string} pass - password * @return {object} - { strength: 0..1 (null for non-valid pass), messages: ["please add numbers"] }. */ function calculateStrength(pass) { var charTypesPattern = splitToCharTypes(_opts.pattern, PassField.CharTypes.SYMBOL); var charTypesPass = splitToCharTypes(pass, _opts.allowAnyChars ? PassField.CharTypes.SYMBOL : PassField.CharTypes.UNKNOWN); var messages = []; var charTypesPatternCount = 0; utils.each(charTypesPattern, function(charType) { charTypesPatternCount++; if (!charTypesPass[charType]) { var msg = _locale.msg[charType]; if (charType == PassField.CharTypes.SYMBOL) { // we should give example of symbols; for other types this is not required var symbolsCount = 4; var charsExample = _opts.chars[charType]; if (charsExample.length > symbolsCount) charsExample = charsExample.substring(0, symbolsCount); msg = msg + " (" + charsExample + ")"; } messages.push(msg); } }); var strength = 1 - messages.length / charTypesPatternCount; if (messages.length) { messages = [joinMessagesForCharTypes(messages)]; } if (_opts.checkMode == PassField.CheckModes.MODERATE) { var extraCharTypesCount = 0; utils.each(charTypesPass, function(charType) { if (!charTypesPattern[charType]) { // cool: the user entered char of type which was not in pattern; +strength! extraCharTypesCount++; } }); strength += extraCharTypesCount / charTypesPatternCount; } var minPassLength = _opts.pattern.length; var lengthRatio = pass.length / minPassLength - 1; if (_opts.length && _opts.length.min && pass.length < _opts.length.min) { lengthRatio = -10; if (_opts.length.min > minPassLength) minPassLength = _opts.length.min; } if (lengthRatio < 0) { strength += lengthRatio; messages.push(_locale.msg.passTooShort.replace("{}", minPassLength.toString())); } else { if (_opts.checkMode == PassField.CheckModes.MODERATE) { strength += lengthRatio / charTypesPatternCount; } } if (pass.length > 2) { var firstChar = pass.charAt(0); var allEqual = true; for (var i = 0; i < pass.length; i++) { if (pass.charAt(i) != firstChar) { allEqual = false; break; } } if (allEqual) { strength = 0; messages = [_locale.msg.repeat]; } } if (strength < 0) { strength = 0; } // MODERATE checking mode could produce positive results for extra long passwords if (strength > 1) { strength = 1; } return { strength: strength, messages: messages, charTypes: charTypesPass }; } /** * Joins messages about absesnse of different char types in one message like: * "there are no XXX, YYY and ZZZ in your password" * @param messages {string[]} - messages to join. * @return {string} - single joined message */ function joinMessagesForCharTypes(messages) { var replacement = messages[0]; for (var i = 1; i < messages.length; i++) { if (i == messages.length - 1) replacement += " " + _locale.msg.and + " "; else replacement += ", "; replacement += messages[i]; } return _locale.msg.noCharType.replace("{}", replacement); } /** * Shows password warning * @param {Number} strength - password strength (null if the password is not valid) * @param {String[]} messages - validation messages */ function showPasswordWarning(strength, messages) { var shortErrorText = ""; var errorText = ""; if (strength === null) { shortErrorText = _locale.msg.invalidPassWarn; errorText = messages[0].charAt(0).toUpperCase() + messages[0].substring(1); } else { shortErrorText = _locale.msg.weakWarn; errorText = ""; if (messages) { for (var i = 0; i < messages.length; i++) { var firstLetter = messages[i].charAt(0); if (i === 0) { errorText += _locale.msg.weakTitle + ": "; if (_locale.lower) firstLetter = firstLetter.toLowerCase(); } else { errorText += "
"; firstLetter = firstLetter.toUpperCase(); } errorText += firstLetter + messages[i].substring(1); if (errorText && (errorText.charAt(errorText.length - 1) != ".")) errorText += "."; } } } if (errorText && (errorText.charAt(errorText.length - 1) != ".")) errorText += "."; _validationResult = { strength: strength, message: errorText }; if (_dom.warnMsg) { setHtml(_dom.warnMsg, shortErrorText); _dom.warnMsg.title = errorText; if (_opts.errorWrapClassName) { addClass(_dom.wrapper, _opts.errorWrapClassName, true); } if (shortErrorText) removeClass(_dom.warnMsg, "empty"); else addClass(_dom.warnMsg, "empty"); } if (_opts.showTip) { var html = errorText; if (_dom.genBtn) { html += "
" + _locale.msg.generateMsg.replace("{}", "
"); } _tipHtml = html; if (_dom.tipBody) { setHtml(_dom.tipBody, html); } } _warningShown = true; resizeControls(); } /** * Hides password warning */ function hidePasswordWarning() { if (_dom.warnMsg) { setHtml(_dom.warnMsg, ""); _dom.warnMsg.title = ""; if (_opts.errorWrapClassName) { removeClass(_dom.wrapper, _opts.errorWrapClassName, true); } addClass(_dom.warnMsg, "empty"); } _tipHtml = null; _warningShown = false; resizeControls(); } /** * Returns the value of field with which the password is compared * @return {String} - field value or null if we don't need this */ function getNonMatchingFieldValue() { if (!_opts.nonMatchField) return null; var field = getEl(_opts.nonMatchField); if (!field) return null; return field.value; } /** * Gets message for last password validation * @return {String} - last validation message. */ function getPassValidationMessage() { return _validationResult ? _validationResult.message : null; } /** * Gets strength measured during last password validation * @return {Number|Object} - last measured strength: -1 = not measured; null = pass not valid, Number = strength [0..1]. */ function getPassStrength() { return _validationResult ? _validationResult.strength : -1; } /** * Creates random sequence by pattern * @return {string} - generated password. */ function createRandomPassword() { var result = ""; var charTypes = splitToCharTypes(_opts.pattern, PassField.CharTypes.SYMBOL); // We're creating an array of charTypes and shuffling it. // In such way, generated password will contain exactly the same number of character types as was defined in pattern var charTypesSeq = []; utils.each(charTypes, function(charType, chars) { for (var j = 0; j < chars.length; j++) { charTypesSeq.push(charType); } }); charTypesSeq.sort(function() { return 0.7 - Math.random(); }); utils.each(charTypesSeq, function(charType) { var sequence = _conf.generationChars[charType]; if (sequence) { if (_opts.chars[charType] && _opts.chars[charType].indexOf(sequence) < 0) sequence = _opts.chars[charType]; // overriden without default letters - ok, generate from given chars } else { sequence = _opts.chars[charType]; } result += utils.selectRandom(sequence); }); return result; } /** * Determines character types in string * @param {string} str - input string * @param {string} defaultCharType - default character type (if not found in chars) * @return {object} - PassField.CharType => chars. */ function splitToCharTypes(str, defaultCharType) { var result = {}; for (var i = 0; i < str.length; i++) { var ch = str.charAt(i); var type = defaultCharType; utils.each(_opts.chars, function(charType, seq) { if (seq.indexOf(ch) >= 0) { type = charType; return false; } return true; }); result[type] = (result[type] || "") + ch; } return result; } // ========================== DOM-related functions ========================== /** * Formats class for this element by adding common prefix * Need to ensure there are no conflicts * @param {string} id - class name or id * @return {string} - formatted class name or id. */ function formatId(id) { return ELEMENTS_PREFIX + id + "-" + _id; } /** * Formats class for this element by adding common prefix * Need to ensure there are no conflicts * @param {string} cls - class name or id * @return {string} - formatted class name or id. */ function formatClass(cls) { return ELEMENTS_PREFIX + cls; } /** * Invokes utils.newEl but adds common prefixes to class and id * @param {string} tagName - tag name of the inserted element * @param {object} attr - attributes of the inserted element * @param {object} [css] - CSS properties to apply * @return {object} - created DOM element. */ function newEl(tagName, attr, css) { if (attr.id) attr.id = formatId(attr.id); if (attr.className) attr.className = formatClass(attr.className); return utils.newEl(tagName, attr, css); } /** * Gets bound rect safely * @param {Object} el - DOM element * @return {Object} - rect: { top: Number, left: Number }. */ function getBoundingRect(el) { try { return el.getBoundingClientRect(); } catch (err) { return { top: 0, left: 0 }; } } /** * Gets element offset * @param {Object} el - DOM element * @return {Object} - offset: { top: Number, left: Number }. */ function offset(el) { var doc = el.ownerDocument; if (!doc) { return { top: 0, left: 0 }; } var box = getBoundingRect(el); return { top: box.top + (window.pageYOffset || 0) - (doc.documentElement.clientTop || 0), left: box.left + (window.pageXOffset || 0) - (doc.documentElement.clientLeft || 0) }; } /** * Gets offset parent for element * @param {Object} el - DOM element * @return {Function|HTMLElement|Element|Element} - offset parent. */ function offsetParent(el) { var op; try { op = el.offsetParent; } catch (e) { } if (!op) op = document.documentElement; while (op && (op.nodeName.toLowerCase() != "html") && css(op, "position") === "static") { op = op.offsetParent; } return op || document.documentElement; } /** * Gets element position * @param {Object} el - DOM element * @return {Object} - offset: { top: Number, left: Number }. */ function position(el) { var offs, parentOffset = { top: 0, left: 0 }; if (css(el, "position") === "fixed") { offs = getBoundingRect(el); } else { var op = offsetParent(el); offs = offset(el); if (op.nodeName.toLowerCase() != "html") { parentOffset = offset(op); } parentOffset.top += cssFloat(op, "borderTopWidth"); parentOffset.left += cssFloat(op, "borderLeftWidth"); } return { top: offs.top - parentOffset.top - cssFloat(el, "marginTop"), left: offs.left - parentOffset.left - cssFloat(el, "marginLeft") }; } /** * Get outer width and height for DOM element * @param {Object} el - DOM element * @return {Object} - bounds: { width: Number, height: Number }. */ function getBounds(el) { return { width: el.offsetWidth, height: el.offsetHeight }; } /** * Get bounds and position for DOM element * @param {Object} el - DOM element * @return {Object} - bounds and offset combined. */ function getRect(el) { return utils.extend(offset(el), getBounds(el)); } /** * Sets bounds and offset * @param {Object} el - DOM element * @param {Object} rect - coords to set */ function setRect(el, rect) { if (rect.height && !isNaN(rect.height)) { el.style.height = rect.height + "px"; el.style.lineHeight = rect.height + "px"; } if (rect.width && !isNaN(rect.width)) { el.style.width = rect.width + "px"; } if (rect.top || rect.left) { if (css(el, "display") == "none") { el.style.top = rect.top + "px"; el.style.left = rect.left + "px"; return; } var curLeft, curTop, curOffset; curOffset = offset(el); curTop = css(el, "top") || 0; curLeft = css(el, "left") || 0; if ((curTop + curLeft + "").indexOf("auto") > -1) { var pos = position(el); curTop = pos.top; curLeft = pos.left; } else { curTop = parseFloat(curTop) || 0; curLeft = parseFloat(curLeft) || 0; } if (rect.top) { el.style.top = ((rect.top - curOffset.top) + curTop) + "px"; } if (rect.left) { el.style.left = ((rect.left - curOffset.left) + curLeft) + "px"; } } } /** * Gets CSS property (including inherited) * @param {Object|HTMLElement} el - DOM element * @param {String} prop - CSS property name * @return {String} - property value. */ function css(el, prop) { var st = typeof window.getComputedStyle === "function" ? window.getComputedStyle(el, null) : el.currentStyle; return st ? st[prop] : null; } /** * Gets CSS property and parses the value as float * @param {Object} el - DOM element * @param {String} prop - CSS property name * @return {Number} - parsed property value. */ function cssFloat(el, prop) { var v = css(el, prop); if (!v) return 0; var parsed = parseFloat(v); return isNaN(parsed) ? 0 : parsed; } /** * Inserts DOM node after another * @param {Object} target - target node * @param {Node} el - new node */ function insertAfter(target, el) { if (target.parentNode) target.parentNode.insertBefore(el, target.nextSibling); } /** * Inserts DOM node before another * @param {Object} target - target node * @param {Node} el - new node */ function insertBefore(target, el) { if (target.parentNode) target.parentNode.insertBefore(el, target); } /** * Sets innerHTML in element * @param {Object} el - DOM element * @param {String} html - HTML to set */ function setHtml(el, html) { try { el.innerHTML = html; } catch (err) { // browser doesn't support innerHTML or it's readonly var newNode = document.createElement("c"); newNode.innerHTML = html; while (el.firstChild) { el.removeChild(el.firstChild); } el.appendChild(newNode); } } /** * Gets DOM element my ID or jQuery selector or DOM element * @param el {object|string|jQuery} - element * @returns {object} - DOM element */ function getEl(el) { if (typeof el === "string") { return document.getElementById(el); } if (el.jquery) { return el[0]; } return el; } /** * Adds class to element * @param {object} el - element * @param {string} cls - class name * @param {Boolean} [raw=false] - is given class name raw (without prefix) */ function addClass(el, cls, raw) { if (hasClass(el, cls, raw)) return; el.className = el.className + (el.className ? " " : "") + (raw === true ? cls : formatClass(cls)); } /** * Removes class from element * @param {object} el - element * @param {string} cls - class name * @param {Boolean} [raw=false] - is given class name raw (without prefix) */ function removeClass(el, cls, raw) { if (!hasClass(el, cls, raw)) return; el.className = (" " + el.className + " ").replace((raw === true ? cls : formatClass(cls)) + " ", "").replace(/^\s+|\s+$/g, ""); } /** * Checks whether element has class * @param {object} el - element * @param {string} cls - class name * @param {Boolean} [raw=false] - is given class name raw (without prefix) * @return {Boolean} - whether the element has the class. */ function hasClass(el, cls, raw) { cls = " " + (raw === true ? cls : formatClass(cls)) + " "; return (" " + el.className + " ").replace(/[\n\t]/g, " ").indexOf(cls) > -1; } /** * Triggers event * Event is searched in opts.events. and called if present * If jQuery is present, jQuery event (pass:) is also triggered * @param {string} name - event name * @param arg - event argument */ function triggerEvent(name, arg) { if ($) { try { $(_dom.mainInput).trigger(JQUERY_EVENT_PREFIX + name, arg); } catch (err) {} } if (_opts.events && typeof _opts.events[name] === "function") { try { _opts.events[name].call(_dom.mainInput, arg); } catch (err) {} } } /** * Assigns data attribute for jQuery */ function assignDataObject(prop, obj) { if ($) { $(_dom.mainInput).data(prop, obj); } } }; // ========================== utility ========================== /** * Internal utility functions */ var utils = {}; /** * Almost the same as jQuery extend * @return {object} first arg, extended with others. */ utils.extend = function() { var arg = arguments; for (var i = 1; i < arg.length; i++) { utils.each(arg[i], function (key, value) { if (utils.isArray(arg[0][key]) || utils.isArray(value)) { arg[0][key] = arg[0][key] ? arg[0][key].concat(value || []) : value; } else if (utils.isElement(value)) { arg[0][key] = value; } else if (typeof arg[0][key] === "object" && typeof value === "object" && value !== null) { arg[0][key] = utils.extend({}, arg[0][key], value); } else if (typeof value === "object" && value !== null) { arg[0][key] = utils.extend({}, value); } else { arg[0][key] = value; } }); } return arg[0]; }; /** * Creates DOM element * @param {string} tagName - tag name of the inserted element * @param {object} [attr] - attributes of the inserted element * @param {object} [css] - CSS properties to apply * @return {object} - created DOM element. */ utils.newEl = function(tagName, attr, css) { var el = document.createElement(tagName); if (attr) { utils.each(attr, function(key, value) { if (value) el[key] = value; }); } if (css) { utils.each(css, function(key, value) { if (value) el.style[key] = value; }); } return el; }; /** * Attaches event to element * @param {object} el - DOM element * @param {string} event - event name * @param {function} handler - handler to attach */ utils.attachEvent = function(el, event, handler) { var oldHandler = el[event]; el[event] = function(e) { if (!e) e = window.event; handler(e); if (typeof oldHandler === "function") { oldHandler(e); } }; }; /** * Iterates the collection or object attrs * @param {object|object[]|string[]} obj - object or collection to iterate * @param {function} fn - function to invoke on each element */ utils.each = function(obj, fn) { if (utils.isArray(obj)) { for (var i = 0; i < obj.length; i++) { if (fn(obj[i]) === false) return; } } else { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (fn(key, obj[key]) === false) return; } } } }; /** * Check whether the object is array * @param {object} obj - object to check * @return {Boolean} - array or not. */ utils.isArray = function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; /** * Check whether the object is DOM element or jQuery object * @param {object} obj - object to check * @return {Boolean} - element or not. */ utils.isElement = function(obj) { if (!obj) return false; try { return obj instanceof HTMLElement || $ && obj instanceof jQuery; } catch (err) { return typeof obj === "object" && obj.nodeType || obj.jquery; } }; /** * Selects random element from collection * @param {object} arr - collection * @return {object} - random element. */ utils.selectRandom = function(arr) { var pos = Math.floor(Math.random() * arr.length); return utils.isArray(arr) ? arr[pos] : arr.charAt(pos); }; /** * Checks whether array contains item * @param {Array} arr - array * @param {object} item - item to check (strictly) * @return {Boolean} - contains or not. */ utils.contains = function(arr, item) { if (!arr) return false; var result = false; utils.each(arr, function (el) { if (el === item) { result = true; return false; } return true; }); return result; }; // ========================== jQuery plugin ========================== if ($) { /** * passField jQuery plugin. Usage: $(selector).passField(options); * @param {object} [opts] - options * @return {object} - jQuery object. */ $.fn.passField = function(opts) { return this.each(function() { new PassField.Field(this, opts); }); }; /** * Toggles masking state * @param {Boolean} [isMasked] - should we display the password masked (undefined or null = change masking) * @return {object} - jQuery object. */ $.fn.togglePassMasking = function(isMasked) { return this.each(function() { var pf = $(this).data(PassField.Config.dataAttr); if (pf) { pf.toggleMasking(isMasked); } }); }; /** * Sets password value in field * @param {String} val - value to set * @return {object} - jQuery object. */ $.fn.setPass = function(val) { return this.each(function() { var pf = $(this).data(PassField.Config.dataAttr); if (pf) { pf.setPass(val); } }); }; /** * Validates the password * @return {Boolean} - is the password valid. */ $.fn.validatePass = function() { var isValid = true; this.each(function() { var pf = $(this).data(PassField.Config.dataAttr); if (pf && !pf.validatePass()) { isValid = false; } }); return isValid; }; /** * Gets message for last password validation * @return {String} - last validation message. */ $.fn.getPassValidationMessage = function() { var el = this.first(); if (el) { var pf = el.data(PassField.Config.dataAttr); if (pf) { return pf.getPassValidationMessage(); } } return null; }; /** * Gets message for last password validation * @return {String} - last validation message. */ $.fn.getPassStrength = function () { var el = this.first(); if (el) { var pf = el.data(PassField.Config.dataAttr); if (pf) { return pf.getPassStrength(); } } return null; }; } // ========================== jQuery.Validation plugin ========================== if ($ && $.validator) { jQuery.validator.addMethod("passfield", function(val, el) { return $(el).validatePass(); // this will set validation message }, function(val, el) { return $(el).getPassValidationMessage(); }); } })(window.jQuery, document, window);