parent
8a6063ff07
commit
cea7d4961e
@ -0,0 +1,459 @@ |
||||
/* |
||||
* The MIT License |
||||
* |
||||
* Copyright (c) 2012 James Allardice |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
// Defines the global Placeholders object along with various utility methods
|
||||
(function (global) { |
||||
|
||||
"use strict"; |
||||
|
||||
// Cross-browser DOM event binding
|
||||
function addEventListener(elem, event, fn) { |
||||
if (elem.addEventListener) { |
||||
return elem.addEventListener(event, fn, false); |
||||
} |
||||
if (elem.attachEvent) { |
||||
return elem.attachEvent("on" + event, fn); |
||||
} |
||||
} |
||||
|
||||
// Check whether an item is in an array (we don't use Array.prototype.indexOf so we don't clobber any existing polyfills - this is a really simple alternative)
|
||||
function inArray(arr, item) { |
||||
var i, len; |
||||
for (i = 0, len = arr.length; i < len; i++) { |
||||
if (arr[i] === item) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// Move the caret to the index position specified. Assumes that the element has focus
|
||||
function moveCaret(elem, index) { |
||||
var range; |
||||
if (elem.createTextRange) { |
||||
range = elem.createTextRange(); |
||||
range.move("character", index); |
||||
range.select(); |
||||
} else if (elem.selectionStart) { |
||||
elem.focus(); |
||||
elem.setSelectionRange(index, index); |
||||
} |
||||
} |
||||
|
||||
// Attempt to change the type property of an input element
|
||||
function changeType(elem, type) { |
||||
try { |
||||
elem.type = type; |
||||
return true; |
||||
} catch (e) { |
||||
// You can't change input type in IE8 and below
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Expose public methods
|
||||
global.Placeholders = { |
||||
Utils: { |
||||
addEventListener: addEventListener, |
||||
inArray: inArray, |
||||
moveCaret: moveCaret, |
||||
changeType: changeType |
||||
} |
||||
}; |
||||
|
||||
}(this)); |
||||
|
||||
(function (global) { |
||||
|
||||
"use strict"; |
||||
|
||||
var validTypes = [ |
||||
"text", |
||||
"search", |
||||
"url", |
||||
"tel", |
||||
"email", |
||||
"password", |
||||
"number", |
||||
"textarea" |
||||
], |
||||
|
||||
// The list of keycodes that are not allowed when the polyfill is configured to hide-on-input
|
||||
badKeys = [ |
||||
|
||||
// The following keys all cause the caret to jump to the end of the input value
|
||||
27, // Escape
|
||||
33, // Page up
|
||||
34, // Page down
|
||||
35, // End
|
||||
36, // Home
|
||||
|
||||
// Arrow keys allow you to move the caret manually, which should be prevented when the placeholder is visible
|
||||
37, // Left
|
||||
38, // Up
|
||||
39, // Right
|
||||
40, // Down
|
||||
|
||||
// The following keys allow you to modify the placeholder text by removing characters, which should be prevented when the placeholder is visible
|
||||
8, // Backspace
|
||||
46 // Delete
|
||||
], |
||||
|
||||
// Styling variables
|
||||
placeholderStyleColor = "#ccc", |
||||
placeholderClassName = "placeholdersjs", |
||||
classNameRegExp = new RegExp("(?:^|\\s)" + placeholderClassName + "(?!\\S)"), |
||||
|
||||
// These will hold references to all elements that can be affected. NodeList objects are live, so we only need to get those references once
|
||||
inputs, textareas, |
||||
|
||||
// The various data-* attributes used by the polyfill
|
||||
ATTR_CURRENT_VAL = "data-placeholder-value", |
||||
ATTR_ACTIVE = "data-placeholder-active", |
||||
ATTR_INPUT_TYPE = "data-placeholder-type", |
||||
ATTR_FORM_HANDLED = "data-placeholder-submit", |
||||
ATTR_EVENTS_BOUND = "data-placeholder-bound", |
||||
ATTR_OPTION_FOCUS = "data-placeholder-focus", |
||||
ATTR_OPTION_LIVE = "data-placeholder-live", |
||||
ATTR_MAXLENGTH = "data-placeholder-maxlength", |
||||
|
||||
// Various other variables used throughout the rest of the script
|
||||
test = document.createElement("input"), |
||||
head = document.getElementsByTagName("head")[0], |
||||
root = document.documentElement, |
||||
Placeholders = global.Placeholders, |
||||
Utils = Placeholders.Utils, |
||||
hideOnInput, liveUpdates, keydownVal, styleElem, styleRules, placeholder, timer, form, elem, len, i; |
||||
|
||||
// No-op (used in place of public methods when native support is detected)
|
||||
function noop() {} |
||||
|
||||
// Avoid IE9 activeElement of death when an iframe is used.
|
||||
// More info:
|
||||
// http://bugs.jquery.com/ticket/13393
|
||||
// https://github.com/jquery/jquery/commit/85fc5878b3c6af73f42d61eedf73013e7faae408
|
||||
function safeActiveElement() { |
||||
try { |
||||
return document.activeElement; |
||||
} catch (err) {} |
||||
} |
||||
|
||||
// Hide the placeholder value on a single element. Returns true if the placeholder was hidden and false if it was not (because it wasn't visible in the first place)
|
||||
function hidePlaceholder(elem, keydownValue) { |
||||
var type, |
||||
maxLength, |
||||
valueChanged = (!!keydownValue && elem.value !== keydownValue), |
||||
isPlaceholderValue = (elem.value === elem.getAttribute(ATTR_CURRENT_VAL)); |
||||
|
||||
if ((valueChanged || isPlaceholderValue) && elem.getAttribute(ATTR_ACTIVE) === "true") { |
||||
elem.removeAttribute(ATTR_ACTIVE); |
||||
elem.value = elem.value.replace(elem.getAttribute(ATTR_CURRENT_VAL), ""); |
||||
elem.className = elem.className.replace(classNameRegExp, ""); |
||||
|
||||
// Restore the maxlength value
|
||||
maxLength = elem.getAttribute(ATTR_MAXLENGTH); |
||||
if (parseInt(maxLength, 10) >= 0) { // Old FF returns -1 if attribute not set (see GH-56)
|
||||
elem.setAttribute("maxLength", maxLength); |
||||
elem.removeAttribute(ATTR_MAXLENGTH); |
||||
} |
||||
|
||||
// If the polyfill has changed the type of the element we need to change it back
|
||||
type = elem.getAttribute(ATTR_INPUT_TYPE); |
||||
if (type) { |
||||
elem.type = type; |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// Show the placeholder value on a single element. Returns true if the placeholder was shown and false if it was not (because it was already visible)
|
||||
function showPlaceholder(elem) { |
||||
var type, |
||||
maxLength, |
||||
val = elem.getAttribute(ATTR_CURRENT_VAL); |
||||
if (elem.value === "" && val) { |
||||
elem.setAttribute(ATTR_ACTIVE, "true"); |
||||
elem.value = val; |
||||
elem.className += " " + placeholderClassName; |
||||
|
||||
// Store and remove the maxlength value
|
||||
maxLength = elem.getAttribute(ATTR_MAXLENGTH); |
||||
if (!maxLength) { |
||||
elem.setAttribute(ATTR_MAXLENGTH, elem.maxLength); |
||||
elem.removeAttribute("maxLength"); |
||||
} |
||||
|
||||
// If the type of element needs to change, change it (e.g. password inputs)
|
||||
type = elem.getAttribute(ATTR_INPUT_TYPE); |
||||
if (type) { |
||||
elem.type = "text"; |
||||
} else if (elem.type === "password") { |
||||
if (Utils.changeType(elem, "text")) { |
||||
elem.setAttribute(ATTR_INPUT_TYPE, "password"); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
function handleElem(node, callback) { |
||||
|
||||
var handleInputsLength, handleTextareasLength, handleInputs, handleTextareas, elem, len, i; |
||||
|
||||
// Check if the passed in node is an input/textarea (in which case it can't have any affected descendants)
|
||||
if (node && node.getAttribute(ATTR_CURRENT_VAL)) { |
||||
callback(node); |
||||
} else { |
||||
|
||||
// If an element was passed in, get all affected descendants. Otherwise, get all affected elements in document
|
||||
handleInputs = node ? node.getElementsByTagName("input") : inputs; |
||||
handleTextareas = node ? node.getElementsByTagName("textarea") : textareas; |
||||
|
||||
handleInputsLength = handleInputs ? handleInputs.length : 0; |
||||
handleTextareasLength = handleTextareas ? handleTextareas.length : 0; |
||||
|
||||
// Run the callback for each element
|
||||
for (i = 0, len = handleInputsLength + handleTextareasLength; i < len; i++) { |
||||
elem = i < handleInputsLength ? handleInputs[i] : handleTextareas[i - handleInputsLength]; |
||||
callback(elem); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Return all affected elements to their normal state (remove placeholder value if present)
|
||||
function disablePlaceholders(node) { |
||||
handleElem(node, hidePlaceholder); |
||||
} |
||||
|
||||
// Show the placeholder value on all appropriate elements
|
||||
function enablePlaceholders(node) { |
||||
handleElem(node, showPlaceholder); |
||||
} |
||||
|
||||
// Returns a function that is used as a focus event handler
|
||||
function makeFocusHandler(elem) { |
||||
return function () { |
||||
|
||||
// Only hide the placeholder value if the (default) hide-on-focus behaviour is enabled
|
||||
if (hideOnInput && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") { |
||||
|
||||
// Move the caret to the start of the input (this mimics the behaviour of all browsers that do not hide the placeholder on focus)
|
||||
Utils.moveCaret(elem, 0); |
||||
|
||||
} else { |
||||
|
||||
// Remove the placeholder
|
||||
hidePlaceholder(elem); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
// Returns a function that is used as a blur event handler
|
||||
function makeBlurHandler(elem) { |
||||
return function () { |
||||
showPlaceholder(elem); |
||||
}; |
||||
} |
||||
|
||||
// Functions that are used as a event handlers when the hide-on-input behaviour has been activated - very basic implementation of the "input" event
|
||||
function makeKeydownHandler(elem) { |
||||
return function (e) { |
||||
keydownVal = elem.value; |
||||
|
||||
//Prevent the use of the arrow keys (try to keep the cursor before the placeholder)
|
||||
if (elem.getAttribute(ATTR_ACTIVE) === "true") { |
||||
if (keydownVal === elem.getAttribute(ATTR_CURRENT_VAL) && Utils.inArray(badKeys, e.keyCode)) { |
||||
if (e.preventDefault) { |
||||
e.preventDefault(); |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
function makeKeyupHandler(elem) { |
||||
return function () { |
||||
hidePlaceholder(elem, keydownVal); |
||||
|
||||
// If the element is now empty we need to show the placeholder
|
||||
if (elem.value === "") { |
||||
elem.blur(); |
||||
Utils.moveCaret(elem, 0); |
||||
} |
||||
}; |
||||
} |
||||
function makeClickHandler(elem) { |
||||
return function () { |
||||
if (elem === safeActiveElement() && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") { |
||||
Utils.moveCaret(elem, 0); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
// Returns a function that is used as a submit event handler on form elements that have children affected by this polyfill
|
||||
function makeSubmitHandler(form) { |
||||
return function () { |
||||
|
||||
// Turn off placeholders on all appropriate descendant elements
|
||||
disablePlaceholders(form); |
||||
}; |
||||
} |
||||
|
||||
// Bind event handlers to an element that we need to affect with the polyfill
|
||||
function newElement(elem) { |
||||
|
||||
// If the element is part of a form, make sure the placeholder string is not submitted as a value
|
||||
if (elem.form) { |
||||
form = elem.form; |
||||
|
||||
// If the type of the property is a string then we have a "form" attribute and need to get the real form
|
||||
if (typeof form === "string") { |
||||
form = document.getElementById(form); |
||||
} |
||||
|
||||
// Set a flag on the form so we know it's been handled (forms can contain multiple inputs)
|
||||
if (!form.getAttribute(ATTR_FORM_HANDLED)) { |
||||
Utils.addEventListener(form, "submit", makeSubmitHandler(form)); |
||||
form.setAttribute(ATTR_FORM_HANDLED, "true"); |
||||
} |
||||
} |
||||
|
||||
// Bind event handlers to the element so we can hide/show the placeholder as appropriate
|
||||
Utils.addEventListener(elem, "focus", makeFocusHandler(elem)); |
||||
Utils.addEventListener(elem, "blur", makeBlurHandler(elem)); |
||||
|
||||
// If the placeholder should hide on input rather than on focus we need additional event handlers
|
||||
if (hideOnInput) { |
||||
Utils.addEventListener(elem, "keydown", makeKeydownHandler(elem)); |
||||
Utils.addEventListener(elem, "keyup", makeKeyupHandler(elem)); |
||||
Utils.addEventListener(elem, "click", makeClickHandler(elem)); |
||||
} |
||||
|
||||
// Remember that we've bound event handlers to this element
|
||||
elem.setAttribute(ATTR_EVENTS_BOUND, "true"); |
||||
elem.setAttribute(ATTR_CURRENT_VAL, placeholder); |
||||
|
||||
// If the element doesn't have a value and is not focussed, set it to the placeholder string
|
||||
if (hideOnInput || elem !== safeActiveElement()) { |
||||
showPlaceholder(elem); |
||||
} |
||||
} |
||||
|
||||
Placeholders.nativeSupport = test.placeholder !== void 0; |
||||
|
||||
if (!Placeholders.nativeSupport) { |
||||
|
||||
// Get references to all the input and textarea elements currently in the DOM (live NodeList objects to we only need to do this once)
|
||||
inputs = document.getElementsByTagName("input"); |
||||
textareas = document.getElementsByTagName("textarea"); |
||||
|
||||
// Get any settings declared as data-* attributes on the root element (currently the only options are whether to hide the placeholder on focus or input and whether to auto-update)
|
||||
hideOnInput = root.getAttribute(ATTR_OPTION_FOCUS) === "false"; |
||||
liveUpdates = root.getAttribute(ATTR_OPTION_LIVE) !== "false"; |
||||
|
||||
// Create style element for placeholder styles (instead of directly setting style properties on elements - allows for better flexibility alongside user-defined styles)
|
||||
styleElem = document.createElement("style"); |
||||
styleElem.type = "text/css"; |
||||
|
||||
// Create style rules as text node
|
||||
styleRules = document.createTextNode("." + placeholderClassName + " { color:" + placeholderStyleColor + "; }"); |
||||
|
||||
// Append style rules to newly created stylesheet
|
||||
if (styleElem.styleSheet) { |
||||
styleElem.styleSheet.cssText = styleRules.nodeValue; |
||||
} else { |
||||
styleElem.appendChild(styleRules); |
||||
} |
||||
|
||||
// Prepend new style element to the head (before any existing stylesheets, so user-defined rules take precedence)
|
||||
head.insertBefore(styleElem, head.firstChild); |
||||
|
||||
// Set up the placeholders
|
||||
for (i = 0, len = inputs.length + textareas.length; i < len; i++) { |
||||
elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length]; |
||||
|
||||
// Get the value of the placeholder attribute, if any. IE10 emulating IE7 fails with getAttribute, hence the use of the attributes node
|
||||
placeholder = elem.attributes.placeholder; |
||||
if (placeholder) { |
||||
|
||||
// IE returns an empty object instead of undefined if the attribute is not present
|
||||
placeholder = placeholder.nodeValue; |
||||
|
||||
// Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
|
||||
if (placeholder && Utils.inArray(validTypes, elem.type)) { |
||||
newElement(elem); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If enabled, the polyfill will repeatedly check for changed/added elements and apply to those as well
|
||||
timer = setInterval(function () { |
||||
for (i = 0, len = inputs.length + textareas.length; i < len; i++) { |
||||
elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length]; |
||||
|
||||
// Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
|
||||
placeholder = elem.attributes.placeholder; |
||||
if (placeholder) { |
||||
placeholder = placeholder.nodeValue; |
||||
if (placeholder && Utils.inArray(validTypes, elem.type)) { |
||||
|
||||
// If the element hasn't had event handlers bound to it then add them
|
||||
if (!elem.getAttribute(ATTR_EVENTS_BOUND)) { |
||||
newElement(elem); |
||||
} |
||||
|
||||
// If the placeholder value has changed or not been initialised yet we need to update the display
|
||||
if (placeholder !== elem.getAttribute(ATTR_CURRENT_VAL) || (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE))) { |
||||
|
||||
// Attempt to change the type of password inputs (fails in IE < 9)
|
||||
if (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE) && Utils.changeType(elem, "text")) { |
||||
elem.setAttribute(ATTR_INPUT_TYPE, "password"); |
||||
} |
||||
|
||||
// If the placeholder value has changed and the placeholder is currently on display we need to change it
|
||||
if (elem.value === elem.getAttribute(ATTR_CURRENT_VAL)) { |
||||
elem.value = placeholder; |
||||
} |
||||
|
||||
// Keep a reference to the current placeholder value in case it changes via another script
|
||||
elem.setAttribute(ATTR_CURRENT_VAL, placeholder); |
||||
} |
||||
} |
||||
} else if (elem.getAttribute(ATTR_ACTIVE)) { |
||||
hidePlaceholder(elem); |
||||
elem.removeAttribute(ATTR_CURRENT_VAL); |
||||
} |
||||
} |
||||
|
||||
// If live updates are not enabled cancel the timer
|
||||
if (!liveUpdates) { |
||||
clearInterval(timer); |
||||
} |
||||
}, 100); |
||||
} |
||||
|
||||
Utils.addEventListener(global, "beforeunload", function () { |
||||
Placeholders.disable(); |
||||
}); |
||||
|
||||
// Expose public methods
|
||||
Placeholders.disable = Placeholders.nativeSupport ? noop : disablePlaceholders; |
||||
Placeholders.enable = Placeholders.nativeSupport ? noop : enablePlaceholders; |
||||
|
||||
}(this)); |
Loading…
Reference in new issue