|
/*!
|
|
* 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) );
|
|
TYPO3.jQuery(document).ready(function() {
|
|
( function ( $, global ) {
|
|
|
|
'use strict';
|
|
|
|
var originalValFn = $.fn.val;
|
|
var originalPropFn = $.fn.prop;
|
|
|
|
if ( !global.Placeholders || !global.Placeholders.nativeSupport ) {
|
|
|
|
$.fn.val = function ( val ) {
|
|
var originalValue = originalValFn.apply(this, arguments);
|
|
var placeholder = this.eq(0).data('placeholder-value');
|
|
if (
|
|
val === undefined &&
|
|
this.eq(0).data('placeholder-active') &&
|
|
originalValue === placeholder
|
|
) {
|
|
return '';
|
|
}
|
|
return originalValue;
|
|
};
|
|
|
|
$.fn.prop = function ( name, val ) {
|
|
if (
|
|
val === undefined &&
|
|
this.eq(0).data('placeholder-active') &&
|
|
name === 'value'
|
|
) {
|
|
return '';
|
|
}
|
|
return originalPropFn.apply(this, arguments);
|
|
};
|
|
}
|
|
}(TYPO3.jQuery, this) );
|
|
});
|