1816 lines
69 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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: "&bull;&bull;&bull;",
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 += "<br/>";
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 += "<br/>" + _locale.msg.generateMsg.replace("{}", "<div class=\"" + formatClass("btn-gen-help") + "\"></div>");
}
_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.<event_name> and called if present
* If jQuery is present, jQuery event (pass:<event_name>) 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);