I manage to find the problem and the solution.
The key problem is that our $formatter is executed before the model binder finish loading.
which caused the placeholder to be applied as a value.
You can guess, this lead to by passing your required validation.
So the key solution is to let the model binder finish their work first and then we do watermark of our control.
Key of the day : $timeout
Feel free to see the whole angular placeholder from my gist
https://gist.github.com/kkurni/7018564
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MyApp.directive('placeholder', function($timeout) { | |
return { | |
restrict: 'A', | |
require: 'ngModel', | |
link: function(scope, element, attr, ctrl) { | |
//check whether it support placeholder and cache it | |
scope.supportsPlaceholders = scope.supportsPlaceholders || function() { | |
return "placeholder" in document.createElement("input"); | |
}; | |
if (scope.supportsPlaceholders()) { | |
return; | |
} | |
//this function is used to move the caret to the left | |
var caretTo = function(el, index) { | |
if (el.createTextRange) { | |
var range = el.createTextRange(); | |
range.move("character", index); | |
range.select(); | |
} else if (el.selectionStart != null) { | |
el.focus(); | |
el.setSelectionRange(index, index); | |
} | |
}; | |
var setPlaceholder = function () { | |
element.val(attr.placeholder) | |
}; | |
var clearPlaceholder = function () { | |
element.val(''); | |
}; | |
element.bind('focus', function() { | |
//on focus, set the caret into 0 position | |
if (element.val() == attr.placeholder) { | |
caretTo(element[0], 0); | |
} | |
}); | |
element.bind('click', function() { | |
//on click, set the caret into 0 position | |
if (element.val() == attr.placeholder) { | |
caretTo(element[0], 0); | |
} | |
}); | |
element.bind('keydown', function(event) { | |
//disable left or right key code when there is placeholder | |
if (element.val() == attr.placeholder) { | |
if (event.keyCode == 37 || event.keyCode == 39) { | |
event.preventDefault(); | |
} | |
} | |
}); | |
element.bind('keypress', function() { | |
//on key press, clear the placeholder | |
if (element.val() == attr.placeholder) { | |
clearPlaceholder(); | |
} | |
}); | |
element.bind('blur', function () { | |
//on blur, we just set the placeholder if there is no value | |
if (element.val() == '') setPlaceholder(); | |
}); | |
//formatter is executed when the model is changed | |
ctrl.$formatters.unshift(function (val) { | |
if (!val) { | |
//this timeout is needed so that the validation can finish their binding before we put our placeholder value | |
$timeout(function(){ | |
setPlaceholder(); | |
return attr.placeholder; | |
}); | |
} | |
return val; | |
}); | |
} | |
}; | |
}); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe('placeholder', function() { | |
var element, $scope, $timeout, $compile; | |
beforeEach(module('MyApp')); | |
beforeEach(angular.mock.inject(function($rootScope, _$compile_, _$timeout_) { | |
$scope = $rootScope.$new(); | |
$timeout = _$timeout_; | |
$compile = _$compile_; | |
$scope.supportsPlaceholders = function() { | |
return false; | |
}; | |
element = angular.element('<input type="text" placeholder="enter placeholder" ng-model="model" />'); | |
$compile(element)($scope); | |
$scope.$digest(); | |
$scope = element.scope(); | |
})); | |
describe('$formatter', function() { | |
it('should set element value to placeholder if model is undefined', function() { | |
$timeout.flush(); | |
expect(element.val()).toEqual('enter placeholder'); | |
}); | |
it('should set element value to model value if model is defined', function() { | |
$timeout.flush();//flush the first time out | |
$scope.model = 'some model'; | |
$compile(element)($scope); | |
$scope.$digest(); | |
expect(element.val()).toEqual('some model'); | |
}); | |
}); | |
describe('on keypress', function() { | |
it('should clear element value if it is the same as a placeholder', function() { | |
element.triggerHandler('keypress'); | |
expect(element.val()).toEqual(''); | |
}); | |
}); | |
}); |