123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- /*
- * 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));
|