| /* |
| * 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", |
| |
| // 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() {} |
| |
| // 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) { |
| var type; |
| if (elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") { |
| elem.setAttribute(ATTR_ACTIVE, "false"); |
| elem.value = ""; |
| elem.className = elem.className.replace(classNameRegExp, ""); |
| |
| // 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, |
| val = elem.getAttribute(ATTR_CURRENT_VAL); |
| if (elem.value === "" && val) { |
| elem.setAttribute(ATTR_ACTIVE, "true"); |
| elem.value = val; |
| elem.className += " " + placeholderClassName; |
| |
| // 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 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; |
| |
| // Run the callback for each element |
| for (i = 0, len = handleInputs.length + handleTextareas.length; i < len; i++) { |
| elem = i < handleInputs.length ? handleInputs[i] : handleTextareas[i - handleInputs.length]; |
| 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 () { |
| var type; |
| |
| if (elem.getAttribute(ATTR_ACTIVE) === "true" && elem.value !== keydownVal) { |
| |
| // Remove the placeholder |
| elem.className = elem.className.replace(classNameRegExp, ""); |
| elem.value = elem.value.replace(elem.getAttribute(ATTR_CURRENT_VAL), ""); |
| elem.setAttribute(ATTR_ACTIVE, false); |
| |
| // If the type of element needs to change, change it (e.g. password inputs) |
| type = elem.getAttribute(ATTR_INPUT_TYPE); |
| if (type) { |
| elem.type = type; |
| } |
| } |
| |
| // 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 === document.activeElement && 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; |
| |
| // 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, set it to the placeholder string |
| 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); |
| } |
| } |
| } |
| } |
| |
| // If live updates are not enabled cancel the timer |
| if (!liveUpdates) { |
| clearInterval(timer); |
| } |
| }, 100); |
| } |
| |
| // Expose public methods |
| Placeholders.disable = Placeholders.nativeSupport ? noop : disablePlaceholders; |
| Placeholders.enable = Placeholders.nativeSupport ? noop : enablePlaceholders; |
| |
| }(this)); |