AngularJS directive not executing everytime - javascript

I have written an AngularJS directive to validate percent value,
AngularJS Directive
app.directive('validatePercent', function () {
return {
restrict: 'A',
link: function ($scope, elem, attr) {
$scope.$watch(function () { return elem.val(); },
function (newVal, oldVal) {
console.log("old : ", oldVal);
console.log("new : ", newVal);
if (newVal < 0 || newVal > 100)
{
elem.val(oldVal);
}
}
);
}
};
});
Here's my HTML
<input validate-percent ng-model="obj.progress" type="number" class="form-control" />
Note : obj.progress is of type int, also input type is number
The issue is when I try to change value of this input field multiple times quickly one after the another value goes to -1 or even 101 sometimes. Although condition in my directive is newVal < 0 || newVal > 100
Need help.
UPDATE 1:
This happens only when user changes values using mouse wheel. It doesn't happens while increment or decrements by arrow keys on keyboard.

Instead of using $watch, you can handle it using focus/blur events.
app.directive('validatePercent', function () {
return {
restrict: 'A',
link: function ($scope, elem, attr) {
var oldVal = elem.val();
elem.bind('focus', function(e) {
oldVal = elem.val();
});
elem.bind('blur', function(e) {
if ( elem.val()< 0 || elem.val() > 100)
{
elem.val(oldVal);
}
});
}
};
});
Hope it helps.

Related

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.

Handling Input validation in AngularJs?

I am very new to angularjs and I just cant figure out how I would go about validating my inputs.
I have an input which should never be empty. The model will always have a value. Whenever a user inputs invalid data (i.e nothing or maybe invalid characters) I would like the input to revert to its original value.
For instance, If the initial value was 50 and I deleted all of that (or entered text) and deselected the input, I would expect the input's value to change back to 50.
Here is my controller:
var MyController = null;
app.controller("MyController", ["$scope", function($scope) {
$scope.data = myData;
$scope.validate = function(value, oldValue) {
if(!value || value == "") {
// Do something after validation
value = oldValue;
}
// Our value should be valid now (either replaced or changed)
}
MyController = $scope;
}]);
And my HTML (i would rather not have to type data.something twice if possible):
<input type="text" ng-model="data.something" ng-change="validate()" />
Small Clarification: If the value of the input is "50" and a user removes the "0", the input would be at "5" which is valid. However if the user adds a "x" after I DONT want the input to change, it should still be at "5". However if all the input is empty, I would like onBlur for the original value to be placed back into the input and the model unchanged.
Yep, you need ng-blur and probably ng-focus:
<input type="text" ng-model="data.something" ng-focus="store()" ng-blur="revertIfInvalid()"/>
$scope.store = function() {
$scope.stored = $scope.data.something;
}
$scope.revertIfInvalid= function() {
if (!$scope.data.something) {
$scope.data.something = $scope.stored;
}
}
This will work, but to make it reusable, you then want to make directive for this, like:
app.directive('fancydirective', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(sc, elem, attrs, ngModelCtrl) {
var stored;
elem.on('focus', function() {
sc.$apply(function() {
stored = ngModelCtrl.$modelValue;
});
});
elem.on('blur', function() {
sc.$apply(function() {
if (ngModelCtrl.$invalid) {
ngModelCtrl.$setViewValue(stored);
ngModelCtrl.$render();
}
});
});
}
}
});
http://plnkr.co/edit/fiRKS765Kyh6ikRqc8if?p=preview

scope.$watch not firing on variable change

I have the following snippet of code below. The problem I'm having is that whenever the value of modal.isOpen is set to true, the $watch statement does not fire. I'm trying to avoid using scope.$apply. What am I doing wrong here...
Inside Link function in an Angular directive:
link: function (scope, elem, attrs) {
scope.$watch('modal.isOpen', function(newValue, oldValue) {
if (newValue)
console.log("This does not trigger...");
}, true);
$document.bind('keydown', function (e) {
if(e.keyCode >= 48 && e.keyCode <= 90) {
scope.modal.isOpen = true;
elem.find('input')[0].focus();
}
..........
});
You should keep watch on property which shouldn't have scope in the string given to $watch function.
scope.$watch('modal.isOpen', function(newValue, oldValue) {
And the while modifying scope from custom event, wouldn't update binding. You need to kick digest cycle using $timeout/$apply() to update binding.(If any code ran ouside of angular context, angular doesn't run digest cycle to update bindings).
$document.bind('keydown', function(e) {
if (e.keyCode >= 48 && e.keyCode <= 90) {
$timeout(function() {
scope.modal.isOpen = true;
elem.find('input')[0].focus();
});
}
..........
});

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();
}
});
});
}

angularjs ng-blur not working in Chrome or Firefox

I am using ui.bootstrap collapse directive to display drop down menu.
I would like to automatically collapse it when user clicks outside of the div.
When user clicks on Filter button I set focus using:
.directive('setFocus', function () {
return {
restrict: 'A',
scope: {
focusValue: "=setFocus"
},
link: function ($scope, $element, attrs) {
$scope.$watch("focusValue", function (currentValue, previousValue) {
if (currentValue === true && !previousValue) {
$element[0].focus();
console.log('set focus from setFocus directive');
} else if (currentValue === false && previousValue) {
$element[0].blur();
console.log('blurr from setFocus directive');
}
})
}
}
});
HTML
<div collapse="isDropDownCollapsed" class='div2' align-element-right='el1' set-focus='!isDropDownCollapsed' ng-blur="toggleMenu()">
Controller
app.controller('testCtrl', function ($scope) {
$scope.isDropDownCollapsed = true;
$scope.toggleMenu = function () {
$scope.isDropDownCollapsed = !$scope.isDropDownCollapsed;
}
});
It works in IE v11 but focus is also lost when check box is selected.
It doesn't work in Chrome v38 or Firefox v33.1.
example code
In the end I have used this approach:
https://web.archive.org/web/20161104225152/http://vadimpopa.com/onblur-like-for-a-div-in-angularjs-to-close-a-popup/

Categories