diff --git a/demos/span-wrap.html b/demos/span-wrap.html new file mode 100644 index 0000000..828ee9e --- /dev/null +++ b/demos/span-wrap.html @@ -0,0 +1,50 @@ + + + + Placeholder Demo (Span w/ wrapper) + + + +

Placeholder Polyfill Demo (span-wrap.js)

+ +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md index 7907342..0f22ca1 100644 --- a/readme.md +++ b/readme.md @@ -80,6 +80,32 @@ A class is added to inputs that are currently displaying a placeholder as well a +### span-wrap.js + +[download span-wrap.js](https://raw.github.com/UmbraEngineering/Placeholder/master/src/span-wrap.js) | [demo](http://umbraengineering.github.io/Placeholder/demos/span-wrap.html) + +The `span-wrap.js` variant works almost identically as the `span.js` except that it's also creating a second `` element wrapping both the placeholder span and the input field. This way, the placeholder automatically sticks to the position of the input field, whereas with `span.js` it is positioned absolutely on the page — which can lead to unexpected behavior in case of dynamic layout changes (e.g. when modifying element class names or style values). So basically, + +```html +
+ +
+``` + +becomes + +```html +
+ + My Field Placeholder + + +
+``` + +Apart from that, there's no functional difference between `span-wrap.js` and `span.js` and the same caveats / notes apply here as well. + + ### ie-behavior.js [download ie-behavior.js](https://raw.github.com/UmbraEngineering/Placeholder/master/src/ie-behavior.js) | [demo](http://umbraengineering.github.io/Placeholder/demos/ie-behavior.html) @@ -138,5 +164,4 @@ The `ie-behavior-span.js` polyfill is a combination of the ideas behind the `spa -``` - +``` \ No newline at end of file diff --git a/src/span-wrap.js b/src/span-wrap.js new file mode 100644 index 0000000..51213f4 --- /dev/null +++ b/src/span-wrap.js @@ -0,0 +1,345 @@ + +// +// HTML5 Placeholder Attribute Polyfill (Span) +// +// Author: James Brumond (http://www.jbrumond.me), Joschi Kuphal (http://jkphl.is) +// + +(function(window, document, undefined) { + + // 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() { + var wrapper = createElement('span', {'style': {'position': 'relative'}}); + placeholder = elem.__placeholder = createElement('span', { + innerHTML: getPlaceholderFor(elem), + style: { + position: 'absolute', + display: 'none', + margin: '0', + padding: '0', + cursor: 'text' + } + }); + wrapper.appendChild(placeholder); + elem.parentNode.insertBefore(wrapper, elem); + wrapper.appendChild(elem); + 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', + marginTop: getStyleValue(elem, 'marginTop'), + marginLeft: getStyleValue(elem, 'marginLeft'), + paddingTop: getStyleValue(elem, 'paddingTop'), + paddingLeft: getStyleValue(elem, 'paddingLeft'), + borderTop: getStyleValue(elem, 'borderTopWidth') + ' solid transparent', + borderLeft: getStyleValue(elem, 'borderLeftWidth') + ' solid transparent' + }); + } + + function updatePlaceholder() { + placeholder.innerHTML = getPlaceholderFor(elem); + redrawPlaceholder(); + } + + function checkPlaceholder() { + if (elem.value) { + hidePlaceholder(); + } else { + showPlaceholder(); + } + } + + function showPlaceholder() { + placeholder.style.display = 'block'; + addClass(placeholder, '-placeholder'); + addClass(elem, '-placeholder-input'); + } + + function hidePlaceholder() { + placeholder.style.display = 'none'; + removeClass(placeholder, '-placeholder'); + removeClass(elem, '-placeholder-input'); + 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; + } + +// ------------------------------------------------------------- + + var regexCache = { }; + 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)); \ No newline at end of file