Angularjs input[placeholder] directive breaking with ng-model - javascript

So first day on the job with angularjs and i'm not quite getting it. I'm trying to mimic an html5 placeholder using an angular directive. It totally works until I add an ng-model to the field and then it only works after a user interacts with the field and also breaks any value the field had.
code up here
http://jsbin.com/esujax/32/edit
the directive
App.directive('placehold', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
var insert = function() {
element.val(attrs.placehold);
};
element.bind('blur', function(){
if(element.val() === '')
insert();
});
element.bind('focus', function(){
if(element.val() === attrs.placehold)
element.val('');
});
if(element.val() === '')
insert();
}
}
});
the html
<textarea ng-model="comment" placehold="with a model it doesn't work"></textarea>
seems super simple but i'm lost

Just few modifications in your sample:
app.directive('placehold', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
var value;
var placehold = function () {
element.val(attr.placehold)
};
var unplacehold = function () {
element.val('');
};
scope.$watch(attr.ngModel, function (val) {
value = val || '';
});
element.bind('focus', function () {
if(value == '') unplacehold();
});
element.bind('blur', function () {
if (element.val() == '') placehold();
});
ctrl.$formatters.unshift(function (val) {
if (!val) {
placehold();
value = '';
return attr.placehold;
}
return val;
});
}
};
});
You can test it here: http://plnkr.co/edit/8m54JO?p=preview
Not sure, that it is the best solution, anyway it works. Even if you type the same text, that you have in your placehold attribute, cause it checks the model's value on focus.

You can also checkout an Angular.JS module that implements the "placeholder" attribute for older web browsers:
https://github.com/urish/angular-placeholder-shim

I have created a placeholder directive that can take angularjs expressions and also hides the placeholder text on input. You can read about the placeholder at http://blog.f1circle.com/2013/09/supporting-placeholders-in-non-html5.html
Here is the gist.
(function(angular, app) {
"use strict";
app.directive('placeholder',["$document", "$timeout", function($document, $timeout){
var link = function(scope,element,attrs,ctrl){
// if you dont use modernizr library use the solution given at
// http://stackoverflow.com/questions/5536236/javascript-check-for-native-placeholder-support-in-ie8
// to check if placeholder is supported natively
if(Modernizr.input.placeholder){
return;
}
/*
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
*/
var pTxt, modelValue, placeholding = false, badKeys = [27,33,34,35,36,37,38,39,40,8,46];
var unplacehold = function(){
if(!placeholding){
return;
}
placeholding = false;
element.removeClass('placeholder');
element.val('');
};
var placehold = function(){
if(placeholding || modelValue){
return;
}
placeholding = true;
element.addClass('placeholder');
element.val(pTxt);
};
var moveCaret = function(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);
}
};
attrs.$observe('placeholder',function(value){
pTxt = value;
placeholding = false;
placehold();
});
ctrl.$parsers.unshift(function (value){
modelValue = value;
if(!value){
placehold();
}
if(placeholding){
return '';
}
return value;
});
ctrl.$formatters.unshift(function (value){
if(!value){
placehold();
modelValue = '';
return pTxt;
}
return value;
});
element.on('click focus contextmenu',function(event){
if($document[0].activeElement !== this){
return;
}
if(!modelValue){
moveCaret(this,0);
}
});
element.on('blur',function(){
placehold();
});
element.on('keydown',function(e){
if(!placeholding){
return;
}
if(_.contains(badKeys,e.keyCode)){
if(e.preventDefault){
e.preventDefault();
}
return false;
}
unplacehold();
});
element.on('keyup',function(e){
if(modelValue){
return;
}
placehold();
moveCaret(this,0);
});
element.on('paste',function(e){
$timeout(function(){
modelValue = element.val();
},0);
});
};
return{
restrict: 'A',
require: 'ngModel',
link : link,
priority:3,
};
}]);
})(angular, app);
This works on all cases except when copy and paste the same placeholder text.

Related

Angular js directive that would allow alpha characters and would include capslock and space

Below is my angular js directive that would only allow alphabetical characters but the issue is that it disable capslock/space. The goal is not allowing special characters and numbers as input only alphabet but would not disable capslock/space/shift cause in the app the user can input his name in capital letter.Any idea?. I am using directive compared to ng-pattern library.
app.directive('validEn', function () {
return {
require: '?ngModel',
link: function (scope, element, attrs, ngModelCtrl, SweetAlert) {
if (!ngModelCtrl) {
return;
}
ngModelCtrl.$parsers.push(function (val) {
var clean = val.replace(/[^a-z]+/g, '');
console.log("sfdsfd")
if (val !== clean) {
ngModelCtrl.$setViewValue(clean);
ngModelCtrl.$render();
}
return clean;
});
element.bind('keypress', function (event) {
if (event.keyCode === 32) {
event.preventDefault();
}
});
}
};
});
Just change your following regex var clean = val.replace(/[^a-z]+/g, ''); to var clean = val.replace(/[^a-z|^A-z|^\s]+/g, '');

Directive error $rootScope:infdig Infinite $digest Loop

I downloaded a directive that receives only numbers and has some additional options; but, after running it I get a rootScope error in one of the options that is:
<input type="text" ng-model="mynumber" nks-only-number allow-decimal="false" />
I believe the false conditional is making this error appear, but I don't know why.
Here is the demo:
http://jsfiddle.net/RmDuw/896/
Code:
(function(){
angular.module('myApp', [])
.directive('nksOnlyNumber', function () {
return {
restrict: 'EA',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
var spiltArray = String(newValue).split("");
if(attrs.allowNegative == "false") {
if(spiltArray[0] == '-') {
newValue = newValue.replace("-", "");
ngModel.$setViewValue(newValue);
ngModel.$render();
}
}
if(attrs.allowDecimal == "false") {
newValue = parseInt(newValue);
ngModel.$setViewValue(newValue);
ngModel.$render();
}
if(attrs.allowDecimal != "false") {
if(attrs.decimalUpto) {
var n = String(newValue).split(".");
if(n[1]) {
var n2 = n[1].slice(0, attrs.decimalUpto);
newValue = [n[0], n2].join(".");
ngModel.$setViewValue(newValue);
ngModel.$render();
}
}
}
if (spiltArray.length === 0) return;
if (spiltArray.length === 1 && (spiltArray[0] == '-' || spiltArray[0] === '.' )) return;
if (spiltArray.length === 2 && newValue === '-.') return;
/*Check it is number or not.*/
if (isNaN(newValue)) {
ngModel.$setViewValue(oldValue || '');
ngModel.$render();
}
});
}
};
});
}());
I believe the problem, looking at your pasted code (not the different JSFiddle), is that ngModel.$render() gets called twice. If I delete it from either the attrs.allowDecimal == false conditional or the end isNaN(newValue) conditional, the code runs fine.
Since I'm not sure what your end goal is, I've neglected to actually rewrite your code. But, that solved the infinite $digest loop error.

md-char-count not updating in md-input-container

Consider the following simple md-input-container with textarea inside:
<md-input-container class="md-block">
<textarea aria-label="tt" ng-model="obj.prop" md-maxlength="250" rows="5"></textarea>
</md-input-container>
When i update obj.prop in my controller, the text is changed (therefore i should call $scope.$evalAsync() too). However, the md-char-count is still not updated. In order it to be updated, user will click on text-area and change it. Only then it is being changed.
I think its a known bug but is there any improvements or is there a -at least- a workaround to this problem?
Here you find a codepen
ps(if needed):angular-material version 1.0.1 & angular 1.4.5
I resolved this issue by override - md-maxlength directive.
Try this codepen - http://codepen.io/himvins/pen/JEgyOK?editors=1010
Function for overriding md-maxlength:
function override_mdMaxlength($provide) {
$provide.decorator(
'mdMaxlengthDirective',
function($delegate) {
var mdMaxlength = $delegate[0];
var link = mdMaxlength.link;
mdMaxlength.compile = function() {
//Line 18 to 64: Code of md-maxlength directive. Change in line 62
return function(scope, element, attr, ctrls) {
var maxlength;
var ngModelCtrl = ctrls[0];
var containerCtrl = ctrls[1];
var charCountEl = angular.element('<div class="md-char-counter">');
attr.$set('ngTrim', 'false');
containerCtrl.element.append(charCountEl);
ngModelCtrl.$formatters.push(renderCharCount);
ngModelCtrl.$viewChangeListeners.push(renderCharCount);
element.on(
'input keydown',
function() {
renderCharCount(); //make sure it's called with no args
}
);
scope.$watch(attr.mdMaxlength, function(value) {
maxlength = value;
if (angular.isNumber(value) && value > 0) {
if (!charCountEl.parent().length) {
$animate.enter(
charCountEl,
containerCtrl.element,
angular.element(containerCtrl.element[0].lastElementChild)
);
}
renderCharCount();
} else {
$animate.leave(charCountEl);
}
});
ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
if (!angular.isNumber(maxlength) || maxlength < 0) {
return true;
}
return (modelValue || element.val() || viewValue || '').length <= maxlength;
};
function renderCharCount(value) {
//Original code commented
debugger;
if(ngModelCtrl.$modelValue !== "")
charCountEl.text( ( element.val() || value || '' ).length + '/' + maxlength );
else
charCountEl.text((ngModelCtrl.$modelValue || '').length + '/' + maxlength);
return value;
}
};
};
return $delegate;
}
);
}
Add this function into your module's config like below:
yourModule.config(override_mdMaxlength);
This will correct character-count behavior.
I meet this bug too, after look deeper into the mdMaxlength directive, I found that we could solve this problem easily like this(according to your code):
angular.module('app').controller(function($timeout){
$scope.obj={prop:""};
//after user input some value reset the form
$scope.reset=function(){
$scope.obj.prop=null;
$scope.formName.$setPristine();
$scope.formName.$setValidity();
$scope.formName.$setUntouched();
$timeout(function(){
$scope.obj.prop="";
})
}
})

Which event listner should I use? (if value of ng model xy='bla', change it)

My goal:
Input field for end date:
<input class="input-sm form-control" unlimited-input="" ng-model="something.EndDate" placeholder="{{'PLACEHOLDER_DDMMYYYY' | translate}}" ng-change-on-blur="updateSomething(something)">
Directive unlimitedInput:
app.directive('unlimitedInput', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, ngModelCtrl) {
var unlimitedEndValue = "31.12.2099";
var getter = $parse(attrs.ngModel); // get the value of ng-model
var oldValue = getter(scope);
elm.on('load', function() {
scope.$apply(function() {
ngModelCtrl.$viewValue = 'unlimited';
ngModelCtrl.$render();
});
});
elm.on('focus',function() {
if(oldValue==unlimitedEndValue){
scope.$apply(function() {
ngModelCtrl.$viewValue = '';
ngModelCtrl.$render();
});
}
});
elm.on('blur', function() {
scope.$apply(function() {
var newValue = elm.val();
if ((newValue != '') && (newValue!=oldValue)){
}else if(newValue == ''){
ngModelCtrl.$viewValue = oldValue;
ngModelCtrl.$render();
}
});
});
}
};
});
The Problem is the first event listener (load, ready does not work also), it does not work at all. What I want to achieve: when ng-model has a value and it is 31.12.2099 the user should see "unlimited". Blur and Focus work fine!
Thank you and sorry for my english, if something is not clear, ask:D
Update:
Now I have reached what I wanted, but one thing. I want to update the 'something.UnlimitedEnd' (unlimited in the input field) from the directive. I already tried with setter ($parse ...), I get the error, that "something" ia read only. In my understanding I achieve through scope:true I create a child sope with the two way binding, so scope.unlimited = false should work. My new problem is it assigns the value 'false' only in the directive scope also only one way binding. What I want now to achieve is two way binding for unlimited.
input field
<input class="input-sm form-control" unlimited-input ng-model="something.EndDate" placeholder="{{'PLACEHOLDER_DDMMYYYY' | translate}}"
ng-change-on-blur="dosomething(something)" unlimited="something.UnlimitedEnd"/>
Part of the directive:
restrict: 'EA',
require: 'ngModel',
scope: true,
link: function(scope, elm, attrs, ngModelCtrl) {
if (!ngModelCtrl) return;
var unlimitedEndValue = "31.12.2099";
var getter = $parse(attrs.ngModel);
var unlimitedGetter = $parse(attrs.unlimited);
var oldValue = getter(scope);
function setUnlimited(){
if(getter(scope)==unlimitedEndValue){
return 'unlimited';
}else{
return oldValue;
}
}
ngModelCtrl.$formatters.push(setUnlimited);
elm.on('focus',function() {
if(oldValue==unlimitedEndValue){
scope.$apply(function() {
ngModelCtrl.$setViewValue('');
scope.unlimited = should be updated!! so set false
ngModelCtrl.$render();
});
}
});
elm.on('blur', function() {
scope.$apply(function() {
var newValue = elm.val();
if(newValue == ''){
ngModelCtrl.$setViewValue(oldValue);
ngModelCtrl.$modelValue = oldValue;
ngModelCtrl.$render();
}else if(newValue=='unlimited' || unlimitedGetter(scope)){
ngModelCtrl.$setViewValue(unlimitedEndValue);
ngModelCtrl.$modelValue = unlimitedEndValue;
ngModelCtrl.$render();
}
});
});
}

Contenteditable placeholder needs to be empty when saved

At the company I work at, we took over a project made with symfony2 and angular.js.
It is a platform containing courses. These courses are actually "books", that are made interactive with videos and places to discuss course content.
A course consists of a number of chapters, each with a set of pages.
The person who developed this in angular, used div with contenteditable attributes to enter text. (see screenshot here : http://imgur.com/kqpelaG ) The divs also have a sk-placeholder attribute.
I know the basics to angular and I presumed the sk-placeholder attribute was a directive, replacing the content of the div with some text. Appearently, the contenteditable is the directive using the attribute sk-placeholder's content, to fill the element. (in this case a div).
When in an editable element, if you press enter, the content is saved. If however, you don't fill in anything, the placeholder text is not removed and it is presumed the content of the specific section of the page you're working on. (I hope I am clear enough, if there are any questions please do ask). It should clear the content of all divs with attribute "contenteditable" by default when saving. And that's the part I can't seem to figure out.
SEK.app.directive('contenteditable', function($location, sekApi, $q){
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
var richText = attrs.richText || false,
focused = false;
function renderMath () {
if(richText) {
var math = element[0];
MathJax.Hub.Queue(["Typeset",MathJax.Hub,math]);
}
}
function renderElement() {
if(!ctrl.$viewValue && attrs.skPlaceholder) {
element.addClass("sk-placeholding");
element.html(attrs.skPlaceholder);
} else {
element.removeClass("sk-placeholding");
element.html(ctrl.$viewValue);
renderMath();
}
}
ctrl.$render = function() {
renderElement();
};
element[0].onpaste = function(e) {
var pastedText = undefined;
if (window.clipboardData && window.clipboardData.getData) { // IE
pastedText = window.clipboardData.getData('Text');
} else if (e.clipboardData && e.clipboardData.getData) {
pastedText = e.clipboardData.getData('text/plain');
}
SEK.utilities.insertTextAtCursor(pastedText);
// Prevent the default handler from running.
return false;
};
element.bind('focus', function () {
element.html(ctrl.$viewValue || "");
element.removeClass("sk-placeholding");
focused = true;
});
element.bind('blur', function(event) {
var newViewValue = false;
if(element.html().length > 0){
var htmlContent = element.html();
htmlContent = htmlContent.replace(/<div><br><\/div>/g, "<br>");
htmlContent = htmlContent.replace(/<div><br \/><\/div>/g, "<br>");
htmlContent = htmlContent.replace(/<div>/g, "<br>");
htmlContent = htmlContent.replace(/<\/div>/g, "");
newViewValue = htmlContent;
}
if(element.html().length == 0 && attrs.skPlaceholder) {
newViewValue = "";
};
if(typeof newViewValue === "string") {
scope.$apply(function() {
ctrl.$setViewValue(newViewValue);
});
}
renderElement();
focused = false;
});
element.bind('keydown', function(event) {
var esc = event.which == 27,
enter = event.which == 13,
el = event.target;
if(!richText && esc) {
element.html(ctrl.$viewValue);
el.blur();
event.preventDefault();
}
if (esc || (!richText && enter)) {
scope.ngModel = element.html();
el.blur();
event.preventDefault();
}
});
}
}
});
Any questions are more than welcome. Please do note that I'm a novice when it comes to Angular.js
On save check:
attrs.skPlaceholder === element.html() || attrs.skPlaceholder === element.text()
if this expression returns true, the element seems to be empty.
Or you may check:
element.hasClass("sk-placeholding")

Categories