diff --git a/bower.json b/bower.json
index 4eb2cfd..b1533b4 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,7 @@
{
"name": "angular-shims-placeholder",
"description": "Angular directive to emulate the `placeholder` attribute on text and password input fields for old browsers, such as IE9, IE8, and below",
- "version": "0.4.2",
+ "version": "0.4.3",
"homepage": "https://github.com/cvn/angular-shims-placeholder",
"authors": [
{
diff --git a/dist/angular-shims-placeholder.js b/dist/angular-shims-placeholder.js
index 7d97c10..4840a26 100644
--- a/dist/angular-shims-placeholder.js
+++ b/dist/angular-shims-placeholder.js
@@ -1,218 +1,299 @@
-/*! angular-shims-placeholder - v0.4.2 - 2015-05-11
+/*! angular-shims-placeholder - v0.4.3 - 2015-06-14
* https://github.com/cvn/angular-shims-placeholder
* Copyright (c) 2015 Chad von Nau; Licensed MIT */
-(function (angular, document, undefined) {
- 'use strict';
- angular.module('ng.shims.placeholder', []).service('placeholderSniffer', [
- '$document',
- function ($document) {
- this.emptyClassName = 'empty', this.hasPlaceholder = function () {
- var test = $document[0].createElement('input');
- return test.placeholder !== void 0;
- };
- }
- ]).directive('placeholder', [
- '$timeout',
- '$document',
- '$interpolate',
- '$injector',
- 'placeholderSniffer',
- function ($timeout, $document, $interpolate, $injector, placeholderSniffer) {
- if (placeholderSniffer.hasPlaceholder())
- return {};
- var documentListenersApplied = false, angularVersion = parseFloat(angular.version.full);
- try {
- var $animate = $injector.get('$animate');
- } catch (e) {
- }
- return {
- restrict: 'A',
- require: '?ngModel',
- priority: angularVersion >= 1.2 ? 110 : -10,
- link: function (scope, elem, attrs, ngModel) {
- var orig_val = getValue(), domElem = elem[0], elemType = domElem.nodeName.toLowerCase(), isInput = elemType === 'input' || elemType === 'textarea', is_pwd = attrs.type === 'password', text = attrs.placeholder, emptyClassName = placeholderSniffer.emptyClassName, hiddenClassName = 'ng-hide', clone;
- if (!isInput) {
- return;
- }
- attrs.$observe('placeholder', function (newValue) {
- changePlaceholder(newValue);
- });
- if (is_pwd) {
- setupPasswordPlaceholder();
- }
- setValue(orig_val);
- elem.bind('focus', function () {
- if (elem.hasClass(emptyClassName)) {
- elem.val('');
- elem.removeClass(emptyClassName);
- domElem.select();
- }
- });
- elem.bind('blur', updateValue);
- if (!ngModel) {
- elem.bind('change', function () {
- changePlaceholder($interpolate(elem.attr('placeholder'))(scope));
- });
- }
- if (ngModel) {
- ngModel.$render = function () {
- setValue(ngModel.$viewValue);
- if (domElem === document.activeElement && !elem.val()) {
- domElem.select();
- }
- };
- }
- if (!documentListenersApplied) {
- $document.bind('selectstart', function (e) {
- var elmn = angular.element(e.target);
- if (elmn.hasClass(emptyClassName) && elmn.prop('disabled')) {
- e.preventDefault();
- }
- });
- documentListenersApplied = true;
- }
- function updateValue(e) {
- var val = elem.val();
- if (elem.hasClass(emptyClassName) && val && val === text) {
- return;
- }
- conditionalDefer(function () {
- setValue(val);
- });
- }
- function conditionalDefer(callback) {
- if (document.documentMode <= 11) {
- $timeout(callback, 0);
- } else {
- callback();
- }
- }
- function setValue(val) {
- if (!val && val !== 0 && domElem !== document.activeElement) {
- elem.addClass(emptyClassName);
- elem.val(!is_pwd ? text : '');
- } else {
- elem.removeClass(emptyClassName);
- elem.val(val);
- }
- if (is_pwd) {
- updatePasswordPlaceholder();
- if ($animate) {
- asyncUpdatePasswordPlaceholder();
- }
- }
- }
- function getValue() {
- if (ngModel) {
- return scope.$eval(attrs.ngModel) || '';
- }
- return getDomValue() || '';
- }
- function getDomValue() {
- var val = elem.val();
- if (val === attrs.placeholder) {
- val = '';
- }
- return val;
- }
- function changePlaceholder(value) {
- if (elem.hasClass(emptyClassName) && elem.val() === text) {
- elem.val('');
- }
- text = value;
- updateValue();
- }
- function setAttrUnselectable(elmn, enable) {
- if (enable) {
- elmn.attr('unselectable', 'on');
- } else {
- elmn.removeAttr('unselectable');
- }
- }
- function setupPasswordPlaceholder() {
- clone = angular.element('');
- stylePasswordPlaceholder();
- hideElement(clone);
- clone.addClass(emptyClassName).bind('focus', hidePasswordPlaceholderAndFocus);
- domElem.parentNode.insertBefore(clone[0], domElem);
- var watchAttrs = [
- attrs.ngDisabled,
- attrs.ngReadonly,
- attrs.ngRequired,
- attrs.ngShow,
- attrs.ngHide
- ];
- for (var i = 0; i < watchAttrs.length; i++) {
- if (watchAttrs[i]) {
- scope.$watch(watchAttrs[i], flexibleUpdatePasswordPlaceholder);
- }
- }
- }
- function updatePasswordPlaceholder() {
- stylePasswordPlaceholder();
- if (isNgHidden()) {
- hideElement(clone);
- } else if (elem.hasClass(emptyClassName) && domElem !== document.activeElement) {
- showPasswordPlaceholder();
- } else {
- hidePasswordPlaceholder();
- }
- }
- function asyncUpdatePasswordPlaceholder() {
- if (angularVersion >= 1.3) {
- $animate.addClass(elem, '').then(updatePasswordPlaceholder);
- } else {
- $animate.addClass(elem, '', updatePasswordPlaceholder);
- }
- }
- function flexibleUpdatePasswordPlaceholder() {
- if ($animate) {
- asyncUpdatePasswordPlaceholder();
- } else {
- updatePasswordPlaceholder();
- }
- }
- function stylePasswordPlaceholder() {
- clone.val(text);
- clone.attr('class', elem.attr('class') || '').attr('style', elem.attr('style') || '').prop('disabled', elem.prop('disabled')).prop('readOnly', elem.prop('readOnly')).prop('required', elem.prop('required'));
- setAttrUnselectable(clone, elem.attr('unselectable') === 'on');
- }
- function showElement(elmn) {
- if (angularVersion >= 1.2) {
- elmn.removeClass(hiddenClassName);
- } else {
- elmn.css('display', '');
- }
- }
- function hideElement(elmn) {
- if (angularVersion >= 1.2) {
- elmn.addClass(hiddenClassName);
- } else {
- elmn.css('display', 'none');
- }
- }
- function showPasswordPlaceholder() {
- hideElement(elem);
- showElement(clone);
- }
- function hidePasswordPlaceholder() {
- hideElement(clone);
- showElement(elem);
- }
- function hidePasswordPlaceholderAndFocus() {
- hidePasswordPlaceholder();
- domElem.focus();
- }
- function isNgHidden() {
- var hasNgShow = typeof attrs.ngShow !== 'undefined', hasNgHide = typeof attrs.ngHide !== 'undefined';
- if (hasNgShow || hasNgHide) {
- return hasNgShow && !scope.$eval(attrs.ngShow) || hasNgHide && scope.$eval(attrs.ngHide);
- } else {
- return false;
- }
- }
- }
- };
- }
- ]);
-}(window.angular, window.document));
\ No newline at end of file
+(function(angular, document, undefined) {
+'use strict';
+
+angular.module('ng.shims.placeholder', [])
+.service('placeholderSniffer', ["$document", function($document){
+ this.emptyClassName = 'empty',
+ this.hasPlaceholder = function() {
+ // test for native placeholder support
+ var test = $document[0].createElement("input");
+ return (test.placeholder !== void 0);
+ };
+}])
+.directive('placeholder', ["$timeout", "$document", "$interpolate", "$injector", "placeholderSniffer", function($timeout, $document, $interpolate, $injector, placeholderSniffer) {
+ if (placeholderSniffer.hasPlaceholder()) return {};
+
+ var documentListenersApplied = false,
+ angularVersion = parseFloat(angular.version.full);
+
+ // load $animate if available, to coordinate with other directives that use it
+ try {
+ var $animate = $injector.get('$animate');
+ } catch (e) {}
+
+ // No native support for attribute placeholder
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ // run after ngModel (0) and BOOLEAN_ATTR (100) directives.
+ // priority order was reversed in Angular 1.2, so we must account for this
+ priority: (angularVersion >= 1.2) ? 110 : -10,
+ link: function(scope, elem, attrs, ngModel) {
+ var orig_val = getValue(),
+ domElem = elem[0],
+ elemType = domElem.nodeName.toLowerCase(),
+ isInput = elemType === 'input' || elemType === 'textarea',
+ is_pwd = attrs.type === 'password',
+ text = attrs.placeholder || '',
+ emptyClassName = placeholderSniffer.emptyClassName,
+ hiddenClassName = 'ng-hide',
+ clone;
+
+ if (!isInput) { return; }
+
+ attrs.$observe('placeholder', function (newValue) {
+ changePlaceholder(newValue);
+ });
+
+ if (is_pwd) { setupPasswordPlaceholder(); }
+
+ // init
+ setValue(orig_val);
+
+ // on focus, replace auto-label with empty field
+ elem.bind('focus', function() {
+ if (elem.hasClass(emptyClassName)) {
+ elem.val('');
+ elem.removeClass(emptyClassName);
+ domElem.select(); // IE8/9 show text cursor after tabbing in
+ }
+ });
+
+ // on blur, show placeholder if necessary
+ elem.bind('blur', updateValue);
+
+ // handler for model-less inputs to interact with non-angular code
+ if (!ngModel) {
+ elem.bind('change', function () {
+ changePlaceholder($interpolate(elem.attr('placeholder') || '')(scope));
+ });
+ }
+
+ // model -> view
+ if (ngModel) {
+ ngModel.$render = function() {
+ setValue(ngModel.$viewValue);
+ // IE8/9: show text cursor after updating value while
+ // focused, this happens when tabbing into a field, and the
+ // deferred keydown handler from the previous field fires
+ //
+ // TODO: remove when tab key behavior is fixed in
+ // angular core
+ if (domElem === document.activeElement && !elem.val()) {
+ domElem.select();
+ }
+ };
+ }
+
+ if (!documentListenersApplied) {
+ // cancel selection of placeholder text on disabled elements
+ // disabled elements do not emit selectstart events in IE8/IE9,
+ // so bind to $document and catch the event as it bubbles
+ $document.bind('selectstart', function (e) {
+ var elmn = angular.element(e.target);
+ if (elmn.hasClass(emptyClassName) && elmn.prop('disabled')) {
+ e.preventDefault();
+ }
+ });
+ documentListenersApplied = true;
+ }
+
+ function updateValue(e) {
+ var val = elem.val();
+
+ // don't update from placeholder, helps debounce
+ if (elem.hasClass(emptyClassName) && val && val === text) { return; }
+
+ conditionalDefer(function(){ setValue(val); });
+ }
+
+ function conditionalDefer(callback) {
+ // IE8/9: ngModel uses a keydown handler with deferrered
+ // execution to check for changes to the input. this $timeout
+ // prevents callback from firing before the keydown handler,
+ // which is an issue when tabbing out of an input.
+ // the conditional tests IE version, matches $sniffer.
+ //
+ // TODO: remove this function when tab key behavior is fixed in
+ // angular core
+ if (document.documentMode <= 11) {
+ $timeout(callback, 0);
+ } else {
+ callback();
+ }
+ }
+
+ function setValue(val) {
+ if (!val && val !== 0 && domElem !== document.activeElement) {
+ // show placeholder when necessary
+ elem.addClass(emptyClassName);
+ elem.val(!is_pwd ? text : '');
+ } else {
+ // otherwise set input to actual value
+ elem.removeClass(emptyClassName);
+ elem.val(val);
+ }
+ if (is_pwd) {
+ updatePasswordPlaceholder();
+ if ($animate) {
+ asyncUpdatePasswordPlaceholder();
+ }
+ }
+ }
+
+ function getValue() {
+ if (ngModel) {
+ // use eval because $viewValue isn't ready during init
+ // TODO: this might not to work during unit tests, investigate
+ return scope.$eval(attrs.ngModel) || '';
+ }
+ return getDomValue() || '';
+ }
+
+ // IE8/9: elem.val() on an empty field sometimes returns the
+ // placeholder value, so return an empty string instead
+ // http://stackoverflow.com/q/11208417/490592
+ // I believe IE is persisting the field value across refreshes
+ // TODO: vs `elem.attr('value')`
+ function getDomValue() {
+ var val = elem.val();
+ if (val === attrs.placeholder) {
+ val = '';
+ }
+ return val;
+ }
+
+ function changePlaceholder(value) {
+ if (elem.hasClass(emptyClassName) && elem.val() === text) {
+ elem.val('');
+ }
+ text = value;
+ updateValue();
+ }
+
+ function setAttrUnselectable(elmn, enable) {
+ if (enable) {
+ elmn.attr('unselectable', 'on');
+ } else {
+ elmn.removeAttr('unselectable');
+ }
+ }
+
+ // IE8: password inputs cannot display text, and inputs cannot
+ // change type, so create a new element to display placeholder
+ function setupPasswordPlaceholder() {
+ clone = angular.element('');
+ stylePasswordPlaceholder();
+ hideElement(clone);
+ clone.addClass(emptyClassName)
+ .bind('focus', hidePasswordPlaceholderAndFocus);
+ domElem.parentNode.insertBefore(clone[0], domElem);
+
+ // keep password placeholder in sync with original element.
+ // update element after $watches
+ var watchAttrs = [
+ attrs.ngDisabled,
+ attrs.ngReadonly,
+ attrs.ngRequired,
+ attrs.ngShow,
+ attrs.ngHide
+ ];
+ for (var i = 0; i < watchAttrs.length; i++) {
+ if (watchAttrs[i]) {
+ scope.$watch(watchAttrs[i], flexibleUpdatePasswordPlaceholder);
+ }
+ }
+ }
+
+ function updatePasswordPlaceholder() {
+ stylePasswordPlaceholder();
+ if (isNgHidden()) {
+ // force hide the placeholder when element is hidden by
+ // ngShow/ngHide. we cannot rely on stylePasswordPlaceholder
+ // above to copy the ng-hide class, because the ngShow/ngHide
+ // $watch functions apply the ng-hide class with $animate,
+ // so the class is not applied when our $watch executes
+ hideElement(clone);
+ } else if (elem.hasClass(emptyClassName) && domElem !== document.activeElement) {
+ showPasswordPlaceholder();
+ } else {
+ hidePasswordPlaceholder();
+ }
+ }
+ // update element after animation and animation-aware directives
+ function asyncUpdatePasswordPlaceholder() {
+ if (angularVersion >= 1.3) {
+ $animate.addClass(elem, '').then(updatePasswordPlaceholder);
+ } else {
+ $animate.addClass(elem, '', updatePasswordPlaceholder);
+ }
+ }
+ function flexibleUpdatePasswordPlaceholder() {
+ if ($animate) {
+ asyncUpdatePasswordPlaceholder();
+ } else {
+ updatePasswordPlaceholder();
+ }
+ }
+
+ function stylePasswordPlaceholder() {
+ clone.val(text);
+ // chaining was failing in v1.0.8
+ clone.attr('class', elem.attr('class') || '')
+ .attr('style', elem.attr('style') || '')
+ .prop('disabled', elem.prop('disabled'))
+ .prop('readOnly', elem.prop('readOnly'))
+ .prop('required', elem.prop('required'));
+ setAttrUnselectable(clone, elem.attr('unselectable') === 'on');
+ }
+
+ function showElement(elmn) {
+ if (angularVersion >= 1.2) {
+ elmn.removeClass(hiddenClassName);
+ } else {
+ elmn.css('display', '');
+ }
+ }
+
+ function hideElement(elmn) {
+ if (angularVersion >= 1.2) {
+ elmn.addClass(hiddenClassName);
+ } else {
+ elmn.css('display', 'none');
+ }
+ }
+
+ function showPasswordPlaceholder() {
+ hideElement(elem);
+ showElement(clone);
+ }
+
+ function hidePasswordPlaceholder() {
+ hideElement(clone);
+ showElement(elem);
+ }
+
+ function hidePasswordPlaceholderAndFocus() {
+ hidePasswordPlaceholder();
+ domElem.focus();
+ }
+
+ function isNgHidden() {
+ var hasNgShow = typeof attrs.ngShow !== 'undefined',
+ hasNgHide = typeof attrs.ngHide !== 'undefined';
+ if (hasNgShow || hasNgHide) {
+ return (hasNgShow && !scope.$eval(attrs.ngShow)) ||
+ (hasNgHide && scope.$eval(attrs.ngHide));
+ } else {
+ return false;
+ }
+ }
+
+ }
+ };
+}]);
+
+})(window.angular, window.document);
diff --git a/dist/angular-shims-placeholder.min.js b/dist/angular-shims-placeholder.min.js
index 62c43af..dcb4664 100644
--- a/dist/angular-shims-placeholder.min.js
+++ b/dist/angular-shims-placeholder.min.js
@@ -1,4 +1,4 @@
-/*! angular-shims-placeholder - v0.4.2 - 2015-05-11
+/*! angular-shims-placeholder - v0.4.3 - 2015-06-14
* https://github.com/cvn/angular-shims-placeholder
* Copyright (c) 2015 Chad von Nau; Licensed MIT */
-!function(a,b){"use strict";a.module("ng.shims.placeholder",[]).service("placeholderSniffer",["$document",function(a){this.emptyClassName="empty",this.hasPlaceholder=function(){var b=a[0].createElement("input");return void 0!==b.placeholder}}]).directive("placeholder",["$timeout","$document","$interpolate","$injector","placeholderSniffer",function(c,d,e,f,g){if(g.hasPlaceholder())return{};var h=!1,i=parseFloat(a.version.full);try{var j=f.get("$animate")}catch(k){}return{restrict:"A",require:"?ngModel",priority:i>=1.2?110:-10,link:function(f,k,l,m){function n(){var a=k.val();k.hasClass(M)&&a&&a===L||o(function(){p(a)})}function o(a){b.documentMode<=11?c(a,0):a()}function p(a){a||0===a||H===b.activeElement?(k.removeClass(M),k.val(a)):(k.addClass(M),k.val(K?"":L)),K&&(v(),j&&w())}function q(){return m?f.$eval(l.ngModel)||"":r()||""}function r(){var a=k.val();return a===l.placeholder&&(a=""),a}function s(a){k.hasClass(M)&&k.val()===L&&k.val(""),L=a,n()}function t(a,b){b?a.attr("unselectable","on"):a.removeAttr("unselectable")}function u(){F=a.element(''),y(),A(F),F.addClass(M).bind("focus",D),H.parentNode.insertBefore(F[0],H);for(var b=[l.ngDisabled,l.ngReadonly,l.ngRequired,l.ngShow,l.ngHide],c=0;c=1.3?j.addClass(k,"").then(v):j.addClass(k,"",v)}function x(){j?w():v()}function y(){F.val(L),F.attr("class",k.attr("class")||"").attr("style",k.attr("style")||"").prop("disabled",k.prop("disabled")).prop("readOnly",k.prop("readOnly")).prop("required",k.prop("required")),t(F,"on"===k.attr("unselectable"))}function z(a){i>=1.2?a.removeClass(N):a.css("display","")}function A(a){i>=1.2?a.addClass(N):a.css("display","none")}function B(){A(k),z(F)}function C(){A(F),z(k)}function D(){C(),H.focus()}function E(){var a="undefined"!=typeof l.ngShow,b="undefined"!=typeof l.ngHide;return a||b?a&&!f.$eval(l.ngShow)||b&&f.$eval(l.ngHide):!1}var F,G=q(),H=k[0],I=H.nodeName.toLowerCase(),J="input"===I||"textarea"===I,K="password"===l.type,L=l.placeholder,M=g.emptyClassName,N="ng-hide";J&&(l.$observe("placeholder",function(a){s(a)}),K&&u(),p(G),k.bind("focus",function(){k.hasClass(M)&&(k.val(""),k.removeClass(M),H.select())}),k.bind("blur",n),m||k.bind("change",function(){s(e(k.attr("placeholder"))(f))}),m&&(m.$render=function(){p(m.$viewValue),H!==b.activeElement||k.val()||H.select()}),h||(d.bind("selectstart",function(b){var c=a.element(b.target);c.hasClass(M)&&c.prop("disabled")&&b.preventDefault()}),h=!0))}}}])}(window.angular,window.document);
\ No newline at end of file
+!function(a,b,c){"use strict";a.module("ng.shims.placeholder",[]).service("placeholderSniffer",["$document",function(a){this.emptyClassName="empty",this.hasPlaceholder=function(){var b=a[0].createElement("input");return void 0!==b.placeholder}}]).directive("placeholder",["$timeout","$document","$interpolate","$injector","placeholderSniffer",function(c,d,e,f,g){if(g.hasPlaceholder())return{};var h=!1,i=parseFloat(a.version.full);try{var j=f.get("$animate")}catch(k){}return{restrict:"A",require:"?ngModel",priority:i>=1.2?110:-10,link:function(f,k,l,m){function n(a){var b=k.val();k.hasClass(M)&&b&&b===L||o(function(){p(b)})}function o(a){b.documentMode<=11?c(a,0):a()}function p(a){a||0===a||H===b.activeElement?(k.removeClass(M),k.val(a)):(k.addClass(M),k.val(K?"":L)),K&&(v(),j&&w())}function q(){return m?f.$eval(l.ngModel)||"":r()||""}function r(){var a=k.val();return a===l.placeholder&&(a=""),a}function s(a){k.hasClass(M)&&k.val()===L&&k.val(""),L=a,n()}function t(a,b){b?a.attr("unselectable","on"):a.removeAttr("unselectable")}function u(){F=a.element(''),y(),A(F),F.addClass(M).bind("focus",D),H.parentNode.insertBefore(F[0],H);for(var b=[l.ngDisabled,l.ngReadonly,l.ngRequired,l.ngShow,l.ngHide],c=0;c=1.3?j.addClass(k,"").then(v):j.addClass(k,"",v)}function x(){j?w():v()}function y(){F.val(L),F.attr("class",k.attr("class")||"").attr("style",k.attr("style")||"").prop("disabled",k.prop("disabled")).prop("readOnly",k.prop("readOnly")).prop("required",k.prop("required")),t(F,"on"===k.attr("unselectable"))}function z(a){i>=1.2?a.removeClass(N):a.css("display","")}function A(a){i>=1.2?a.addClass(N):a.css("display","none")}function B(){A(k),z(F)}function C(){A(F),z(k)}function D(){C(),H.focus()}function E(){var a="undefined"!=typeof l.ngShow,b="undefined"!=typeof l.ngHide;return a||b?a&&!f.$eval(l.ngShow)||b&&f.$eval(l.ngHide):!1}var F,G=q(),H=k[0],I=H.nodeName.toLowerCase(),J="input"===I||"textarea"===I,K="password"===l.type,L=l.placeholder||"",M=g.emptyClassName,N="ng-hide";J&&(l.$observe("placeholder",function(a){s(a)}),K&&u(),p(G),k.bind("focus",function(){k.hasClass(M)&&(k.val(""),k.removeClass(M),H.select())}),k.bind("blur",n),m||k.bind("change",function(){s(e(k.attr("placeholder")||"")(f))}),m&&(m.$render=function(){p(m.$viewValue),H!==b.activeElement||k.val()||H.select()}),h||(d.bind("selectstart",function(b){var c=a.element(b.target);c.hasClass(M)&&c.prop("disabled")&&b.preventDefault()}),h=!0))}}}])}(window.angular,window.document);
\ No newline at end of file
diff --git a/package.json b/package.json
index 541264c..12fd5b7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "angular-shims-placeholder",
"description": "Angular directive to emulate the `placeholder` attribute on text and password input fields for old browsers, such as IE9, IE8, and below",
- "version": "0.4.2",
+ "version": "0.4.3",
"homepage": "https://github.com/cvn/angular-shims-placeholder",
"author": {
"name": "Chad von Nau",