// // HTML5 Placeholder Attribute Polyfill (Span) // // Author: James Brumond (http://www.jbrumond.me) // (function(window, document, undefined) { // MSIE8 does not allow vars between functions... var regexCache = { }; // Don't run the polyfill if it isn't needed if ('placeholder' in document.createElement('input')) { document.placeholderPolyfill = function() { /* no-op */ }; document.placeholderPolyfill.active = false; return; } // Fetch NodeLists of the needed element types var inputs = document.getElementsByTagName('input'); var textareas = document.getElementsByTagName('textarea'); // // Define the exposed polyfill methods for manual calls // document.placeholderPolyfill = function(elems) { elems = elems ? validElements(elems) : validElements(inputs, textareas); each(elems, polyfillElement); }; // Expose whether or not the polyfill is in use (false means native support) document.placeholderPolyfill.active = true; // Run automatically document.placeholderPolyfill(); // ------------------------------------------------------------- // Use mutation events for auto-updating if (document.addEventListener) { document.addEventListener('DOMAttrModified', document.placeholderPolyfill); document.addEventListener('DOMNodeInserted', document.placeholderPolyfill); } // Use onpropertychange for auto-updating else if (document.attachEvent && 'onpropertychange' in document) { document.attachEvent('onpropertychange', document.placeholderPolyfill); } // No event-based auto-update else { // pass } // ------------------------------------------------------------- // Add some basic default styling for placeholders firstStylesheet().addRule('.-placeholder', 'color: #888;', 0); // ------------------------------------------------------------- // // Polyfill a single, specific element // function polyfillElement(elem) { // If the element is already polyfilled, skip it if (elem.__placeholder) { return updatePlaceholder(); } // Is there already a value in the field? If so, don't replace it with the placeholder var placeholder; drawPlaceholder(); checkPlaceholder(); // Define the events that cause these functions to be fired addEvent(elem, 'keyup', checkPlaceholder); addEvent(elem, 'keyDown', checkPlaceholder); addEvent(elem, 'blur', checkPlaceholder); addEvent(elem, 'focus', hidePlaceholder); addEvent(elem, 'click', hidePlaceholder); addEvent(placeholder, 'click', hidePlaceholder); addEvent(window, 'resize', redrawPlaceholder); // Use mutation events for auto-updating if (elem.addEventListener) { addEvent(elem, 'DOMAttrModified', updatePlaceholder); } // Use onpropertychange for auto-updating else if (elem.attachEvent && 'onpropertychange' in elem) { addEvent(elem, 'propertychange', updatePlaceholder); } // No event-based auto-update else { // pass } function drawPlaceholder() { placeholder = elem.__placeholder = createElement('span', { innerHTML: getPlaceholderFor(elem), style: { position: 'absolute', display: 'none', margin: '0', padding: '0', cursor: 'text' } }); elem.parentNode.appendChild(placeholder); redrawPlaceholder(); } function redrawPlaceholder() { // Update some basic styles to match that of the input var zIndex = getStyle(elem, 'zIndex'); zIndex = (zIndex === 'auto') ? 99999 : zIndex; setStyle(placeholder, { zIndex: (zIndex || 99999) + 1, backgroundColor: 'transparent' }); // Fix an old IE bug if (elem.offsetParent && getStyle(elem.offsetParent, 'position') === 'static') { elem.offsetParent.style.position = 'relative'; } // Reposition the span to make sure it stays in place var offset = getOffset(elem); setStyle(placeholder, { top: offset.top + 'px', left: offset.left + 'px' }); } function updatePlaceholder() { placeholder.innerHTML = getPlaceholderFor(elem); redrawPlaceholder(); } function checkPlaceholder(event) { if (elem.value) { var nofocus = true; if (event && event.type ){ nofocus = event.type === 'blur'; } hidePlaceholder(event, nofocus); } else { showPlaceholder(); } } function showPlaceholder() { placeholder.style.display = 'block'; addClass(placeholder, '-placeholder'); addClass(elem, '-placeholder-input'); } function hidePlaceholder(event, suppressFocus) { placeholder.style.display = 'none'; removeClass(placeholder, '-placeholder'); removeClass(elem, '-placeholder-input'); if (! suppressFocus) { elem.focus(); } } } // ------------------------------------------------------------- // // Build a list of valid (can have a placeholder) elements from the given parameters // function validElements() { var result = [ ]; each(arguments, function(arg) { if (typeof arg.length !== 'number') { arg = [ arg ]; } result.push.apply(result, filter(arg, isValidElement)); }); return result; } // // Check if a given element supports the placeholder attribute // function isValidElement(elem) { var tag = (elem.nodeName || '').toLowerCase(); return (tag === 'textarea' || (tag === 'input' && (elem.type === 'text' || elem.type === 'password'))); } // ------------------------------------------------------------- function addEvent(obj, event, func) { if (obj.addEventListener) { obj.addEventListener(event, func, false); } else if (obj.attachEvent) { obj.attachEvent('on' + event, func); } } function removeEvent(obj, event, func) { if (obj.removeEventListener) { obj.removeEventListener(event, func, false); } else if (obj.detachEvent) { obj.detachEvent('on' + event, func); } } // ------------------------------------------------------------- function each(arr, func) { if (arr.forEach) { return arr.forEach(func); } for (var i = 0, c = arr.length; i < c; i++) { func.call(null, arr[i], i, arr); } } function filter(arr, func) { if (arr.filter) { return arr.filter(func); } var result = [ ]; for (var i = 0, c = arr.length; i < c; i++) { if (func.call(null, arr[i], i, arr)) { result.push(arr[i]); } } return result; } // ------------------------------------------------------------- function classNameRegex(cn) { if (! regexCache[cn]) { regexCache[cn] = new RegExp('(^|\\s)+' + cn + '(\\s|$)+', 'g'); } return regexCache[cn]; } function addClass(elem, cn) { elem.className += ' ' + cn; } function removeClass(elem, cn) { elem.className = elem.className.replace(classNameRegex(cn), ' '); } // ------------------------------------------------------------- // Internet Explorer 10 in IE7 mode was giving me the wierest error // where e.getAttribute('placeholder') !== e.attributes.placeholder.nodeValue function getPlaceholderFor(elem) { return elem.getAttribute('placeholder') || (elem.attributes.placeholder && elem.attributes.placeholder.nodeValue) || ''; } // ------------------------------------------------------------- // Get the first stylesheet in the document, or, if there are none, create/inject // one and return it. function firstStylesheet() { var sheet = document.styleSheets && document.styleSheets[0]; if (! sheet) { var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.appendChild(document.createTextNode('')); document.head.appendChild(style); sheet = style.sheet; } return sheet; } // ------------------------------------------------------------- // Used internally in getStyle() function getStyleValue(elem, prop) { if (elem.currentStyle) { return elem.currentStyle[prop]; } else if (window.getComputedStyle) { return document.defaultView.getComputedStyle(elem, null)[prop]; } else if (prop in elem.style) { return elem.style[prop]; } return null; } // Get a style property from an element function getStyle(elem, prop) { var style; if (elem.parentNode == null) { elem = document.body.appendChild(elem); style = getStyleValue(elem, prop); elem = document.body.removeChild(elem); } else { style = getStyleValue(elem, prop); } return style; } // Set style properties to an element function setStyle(elem, props) { for (var i in props) { if (props.hasOwnProperty(i)) { elem.style[i] = props[i]; } } } // ------------------------------------------------------------- // Create an element function createElement(tag, props) { var elem = document.createElement(tag); for (var i in props) { if (props.hasOwnProperty(i)) { if (i === 'style') { setStyle(elem, props[i]); } else if (i === 'innerHTML') { elem.innerHTML = props[i]; } else { elem.setAttribute(i, props[i]); } } } return elem; } // ------------------------------------------------------------- // Find the offset position of a given element function getOffset(elem) { return { top: elem.offsetTop + parseFloat(getStyle(elem, 'paddingTop')) + parseFloat(getStyle(elem, 'borderTopWidth')), left: elem.offsetLeft + parseFloat(getStyle(elem, 'paddingLeft')) + parseFloat(getStyle(elem, 'borderLeftWidth')) }; } }(window, document));