/* eslint-disable */
/*!
* 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.
*/
(function (global) {
'use strict'
//
// Test for support. We do this as early as possible to optimise for browsers
// that have native support for the attribute.
//
var test = document.createElement('input')
var nativeSupport = test.placeholder !== void 0
global.Placeholders = {
nativeSupport: nativeSupport,
disable: nativeSupport ? noop : disablePlaceholders,
enable: nativeSupport ? noop : enablePlaceholders
}
if (nativeSupport) {
return
}
//
// If we reach this point then the browser does not have native support for
// the attribute.
//
// The list of input element types that support the placeholder attribute.
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.
var 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.
var placeholderStyleColor = '#ccc'
var placeholderClassName = 'placeholdersjs'
var classNameRegExp = new RegExp('(?:^|\\s)' + placeholderClassName + '(?!\\S)')
// The various data-* attributes used by the polyfill.
var ATTR_CURRENT_VAL = 'data-placeholder-value'
var ATTR_ACTIVE = 'data-placeholder-active'
var ATTR_INPUT_TYPE = 'data-placeholder-type'
var ATTR_FORM_HANDLED = 'data-placeholder-submit'
var ATTR_EVENTS_BOUND = 'data-placeholder-bound'
var ATTR_OPTION_FOCUS = 'data-placeholder-focus'
var ATTR_OPTION_LIVE = 'data-placeholder-live'
var ATTR_MAXLENGTH = 'data-placeholder-maxlength'
// Various other variables used throughout the rest of the script.
var UPDATE_INTERVAL = 100
var head = document.getElementsByTagName('head')[0]
var root = document.documentElement
var Placeholders = global.Placeholders
var keydownVal
// Get references to all the input and textarea elements currently in the DOM
// (live NodeList objects to we only need to do this once).
var inputs = document.getElementsByTagName('input')
var 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.
var hideOnInput = root.getAttribute(ATTR_OPTION_FOCUS) === 'false'
var 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).
var styleElem = document.createElement('style')
styleElem.type = 'text/css'
// Create style rules as text node.
var 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.
var placeholder
var elem
for (var i = 0, len = inputs.length + textareas.length; i < len; i++) {
// Find the next element. If we've already done all the inputs we move on
// to the textareas.
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 the element has a placeholder attribute we need to modify it.
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 && inArray(validTypes, elem.type)) {
newElement(elem)
}
}
}
// If enabled, the polyfill will repeatedly check for changed/added elements
// and apply to those as well.
var timer = setInterval(function () {
for (var 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 && 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) &&
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)
}
}, UPDATE_INTERVAL)
// Disabling placeholders before unloading the page prevents flash of
// unstyled placeholders on load if the page was refreshed.
addEventListener(global, 'beforeunload', function () {
Placeholders.disable()
})
//
// Utility functions
//
// 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) {}
}
// 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) {
for (var i = 0, len = arr.length; i < len; i++) {
if (arr[i] === item) {
return true
}
}
return false
}
// 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)
}
}
// 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
}
}
function handleElem (node, callback) {
// 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.
var handleInputs = node ? node.getElementsByTagName('input') : inputs
var handleTextareas = node ? node.getElementsByTagName('textarea') : textareas
var handleInputsLength = handleInputs ? handleInputs.length : 0
var handleTextareasLength = handleTextareas ? handleTextareas.length : 0
// Run the callback for each element.
var len = handleInputsLength + handleTextareasLength
var elem
for (var i = 0; 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)
}
// 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 valueChanged = !!keydownValue && elem.value !== keydownValue
var 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. Old FF returns -1 if attribute not set.
// See GH-56.
var maxLength = elem.getAttribute(ATTR_MAXLENGTH)
if (parseInt(maxLength, 10) >= 0) {
elem.setAttribute('maxLength', maxLength)
elem.removeAttribute(ATTR_MAXLENGTH)
}
// If the polyfill has changed the type of the element we need to change
// it back.
var 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 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.
var 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).
var type = elem.getAttribute(ATTR_INPUT_TYPE)
if (type) {
elem.type = 'text'
} else if (elem.type === 'password' && changeType(elem, 'text')) {
elem.setAttribute(ATTR_INPUT_TYPE, 'password')
}
return true
}
return false
}
// 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).
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)
}
}
// 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)
}
}
// 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' &&
keydownVal === elem.getAttribute(ATTR_CURRENT_VAL) &&
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()
moveCaret(elem, 0)
}
}
}
function makeClickHandler (elem) {
return function () {
if (
elem === safeActiveElement() &&
elem.value === elem.getAttribute(ATTR_CURRENT_VAL) &&
elem.getAttribute(ATTR_ACTIVE) === 'true'
) {
moveCaret(elem, 0)
}
}
}
// 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.
var form = elem.form
if (form && typeof form === 'string') {
// Get the real form.
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)) {
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.
addEventListener(elem, 'focus', makeFocusHandler(elem))
addEventListener(elem, 'blur', makeBlurHandler(elem))
// If the placeholder should hide on input rather than on focus we need
// additional event handlers
if (hideOnInput) {
addEventListener(elem, 'keydown', makeKeydownHandler(elem))
addEventListener(elem, 'keyup', makeKeyupHandler(elem))
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)
}
}
}(this))