I was developing a module where I need to create some text input manually (on enter or button clicking ) and auto focus on that input right after it's appended to the list. So far the function seems to work but when I open the console log, the $digest already in progress error appears. Kind of weird but if I remove some $eval or $apply the code won't work.
Here's my plnk demo for your reference: Demo
function keyEnter($document) {
return {
restrict: "A",
scope: false,
link: function(scope, ele, attrs) {
ele.bind("keydown keypress", function(event) {
if (event.which === 13) {
scope.$apply(function() {
scope.$eval(attrs.keyEnter);
});
event.preventDefault();
}
});
}
}
}
function customAutofocus() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch(function() {
return scope.$eval(attrs.customAutofocus);
}, function(newValue) {
if (newValue === true) {
element[0].focus();
}
});
}
};
}
I followed the auto focus from this thread, it doesn't show any error even when I applied the same logic. The only difference is I'm using angular 1.3 while his is 1.2
What should I do to improve the code to avoid those $digest error ? Any help is really appreciate, thanks in advance
I adapted your plunk, so it works.
have a look at the new directive:
function customAutofocus($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
//rember this gets run only only
//once just after creating the element, so I just neet to focus once, when
// this digest cycle is done!
$timeout(function() {
// use a timout to foucus outside this digest cycle!
element[0].focus(); //use focus function instead of autofocus attribute to avoid cross browser problem. And autofocus should only be used to mark an element to be focused when page loads.
}, 0);
}
};
}
This makes use of how angular works.
Related
Need help with several issues. Here is simplified code:
HTML
<input type="text" title="{{e.Name}}" ng-model="e.modelName" ng-required="true" typeahead-editable="false" ng-blur="vm.isUnchanged(i)" focus-me="vm.Event"
own-typeahead typeahead-on-select="vm.changeValue($item, $model, $label, i)"
uib-typeahead="$Event, $viewValue)" typeahead-min-length="0"/>
JS
app.directive("ownTypeahead", function() {
var directive = {
link: link,
scope: true,
restrict: 'A',
require: ["ngModel"]
};
return directive;
function link(scope, element, attrs, ctrls) {
element.bind('click', function () {
if (ctrls[0].$viewValue && ctrls[0].$viewValue == ' ') {
ctrls[0].$setViewValue('');
}
ctrls[0].$setViewValue(' ');
});
element.bind('focus', function () {
if (!ctrls[0].$viewValue || ctrls[0].$viewValue == '') {
ctrls[0].$setViewValue(' ');
}
});
}
});
/**
* Directive that places focus on the element it is applied to when the
* expression it binds to evaluates to true
*/
app.directive('focusMe', ['$timeout', function focusMe($timeout) {
return function (scope, elem, attrs) {
scope.$watch(attrs.focusMe, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
}]);
The problems/questions are:
1) The main one. Focus after clicking on certain items triggers typeahead ddl in the input field almost always, but there are several items that move focus but don't trigger the list to be opened. Any ideas where is the issue? (the code above works in about 90% of the cases and in 100% with a click on input field)
2) Not ideal solution, but an ok workaround could be trigger a click event on the focused input field to open the list. Can't manage to get that right the angular way. How this could be done?
Got it working by adding a 200ms timeout for the focusMe:
app.directive('focusMe', ['$timeout', function focusMe($timeout) {
return function (scope, elem, attrs) {
scope.$watch(attrs.focusMe, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 200, false);
}
});
};
}]);
If anyone has better suggestions would accept the answer.
Brief background: I'm trying to write a directive that will listen to my bootstrap dropdown menu's aria-expanded attribute, because I want to do something once its value becomes false. From what I understand, this is the "angularJS" way if you want to monitor class changes inside elements.
The aria-expanded class is in this img element. My directive's name is overlay-monitor:
<img ng-init="displayMainMenu()" overlay-monitor id="nav-burger" uib-dropdown-toggle ng-disabled="disabled" ng-click="sMainMenu=true; isSubMenu=resetMenu(); getLinks(); bStopPropagation=true;" src="img/burger.png">
What I really want it to do is to close the opaque overlay I have on the page if aria-expanded becomes false. But for now, I'm just trying to trigger an alert to see if I did it right:
app.directive('overlayMonitor', function () {
return {
restrict: 'A',
scope: { ariaExpanded: '#' },
link: function ($scope, element, attrs) {
if (element.attrs('aria-expanded') == "true") {
alert('directive');
}
}
}
});
When I tested it out, the alert didn't show. :(
What did I do wrong?
Please advise. Thank you!
P.S. I forgot to mention. We are not allowed to use jQuery. Thanks again for your replies!
Edit: After reading about $watch, I tried the following code:
app.directive('overlayMonitor', function () {
return {
restrict: 'A',
scope: { ariaExpanded: '#' },
link: function ($scope, element, attrs) {
$scope.$watch(function () {
if (!attrs.ariaExpanded) {
alert('false');
}
else {
alert('true');
}
});
}
}
});
Good news is that the alert popped up. Bad news is the alert only said "false". It never fired alert('true'). :/
You may use like this:
if (attrs.ariaExpanded) { // instead of element.attrs('..')
alert('directive');
}
The function link is executed once, when the rendering directives.
Therefore, to track changes in a variable you need to use a $watch.
Try following:
app.directive('overlayMonitor', function () {
return {
restrict: 'A',
scope: { ariaExpanded: '#' },
link: function (scope, element, attrs) {
scope.$watch(function(){ return attrs.ariaExpanded; },
function(val){
if (!val) {
alert('directive');
}
});
}
}
});
I'm trying to extend ngModel so that I have $empty flag that works even when the validation is failing. We're using it to show a "clear text" button.
The model override works fine, but I'm noticing that the flag changes don't apply immediately. Calling $digest throws an "in progress" error.
myApp.directive('ngModel', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var checkEmpty = function() {
ngModel.$empty = (ngModel.$$rawModelValue === undefined ||
ngModel.$$rawModelValue.length === 0);
};
checkEmpty();
element.on('keyup', checkEmpty);
}
}
});
http://jsfiddle.net/sxg7nnwm/2/
You could just simply $watch for changes and set the variable:
scope.$watch(function(){
return ngModel.$isEmpty(ngModel.$viewValue);
},
function(v){
ngModel.$empty = v;
})
EDIT:
Of course, this is inefficient as it registers a $watch.
As correctly pointed out in comments, $viewChangeListeners do not fire for invalid entry. Binding to element events is also bad, since ng-model should be DOM-agnostic.
The approach that could work is to register a $parser that runs first. This would work in most cases - but does not guarantee to work in all, since any other directive is free to register their own parsers (at any time) ahead of our parser and invalidate the value before our parser has a chance to run and re-set $empty.
priority: -100,
require: "ngModel",
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.unshift(function(v) {
ngModel.$empty = isEmpty();
return v;
});
// needed for first time
var unwatch = scope.$watch(function(){
return isEmpty();
}, function(v){
ngModel.$empty = v;
unwatch();
});
function isEmpty(){
return ngModel.$isEmpty(ngModel.$viewValue);
}
}
Are you forced to use the keyup event? To avoid issues with digest timings you should hook into ngModel's pipeline by using $viewChangeListeners instead.
link: function(scope, element, attrs, ngModel) {
var checkEmpty = function() {
ngModel.$empty = (ngModel.$$rawModelValue === undefined || ngModel.$$rawModelValue.length === 0);
console.log(ngModel.$empty);
};
checkEmpty();
ngModel.$viewChangeListeners.push(checkEmpty);
// element.on('keyup', checkEmpty);
}
Fiddle: http://jsfiddle.net/mwup0s9j/1/
Well, I've found that $timeout works, it sets up my code in the next cycle:
element.on('keyup', function() {
$timeout(checkEmpty);
});
I'm trying to create a simple pop-up indicator for when a form input has an invalid value.
Right now, I have a service which handles setting the form values to invalid based upon the results of a call to a server:
var item = form[error.PropertyName];
item.$setValidity('error', false);
item.ErrorMessage = error.Message;
So on these models I'm setting a property called 'ErrorMessage' to be whatever was sent from the server.
To get that message to popup, I've created this directive which uses the angular-ui bootstrap tooltip directive:
myApp.directive('validationPopup', function ($compile) {
return {
restrict: 'A',
priority: 10000,
terminal: true,
require: '^form',
compile: function compile($element, $attrs) {
delete ($attrs['validationPopup']);
$element.removeAttr('validation-popup');
$element.attr('tooltip', '{{ErrorMessage}}');
$element.attr('tooltip-trigger', 'focus');
return {
pre: function (scope, element, attrs, formCtrl) { },
post: function (scope, element, attrs, formCtrl) {
$compile(element)(scope);
scope.$watch(function () { return (formCtrl[attrs.name] == undefined) ? undefined : formCtrl[attrs.name].$invalid; }, function (invalid) {
if (invalid != undefined) {
scope.ErrorMessage = formCtrl[attrs.name].ErrorMessage || '';
}
});
}
};
}
};
Since I don't have a lot of experience making directives, and since I've basically cobbled this together from various other answers on here, my question is is this the correct way of adding other directives to an existing element? It appears to be working correctly; however, I wasn't sure if I was going to run into issues down the line (or performance problems).
I'm building an application using AngularJS and UniformJS. I'd like to have a reset button on the view that would reset my select's to their default value. If I use uniform.js, it isn't working.
You can examine it here:
http://plnkr.co/edit/QYZRzlRf1qqAYgi8VbO6?p=preview
If you click the reset button continuously, nothing happens.
If you remove the attribute, therefore no longer using uniform.js, everything behaves correctly.
Thanks
UPDATE:
Required the use of timeout.
app.controller('MainCtrl', function($scope, $timeout) {
$scope.reset = function() {
$scope.test = "";
$timeout(jQuery.uniform.update, 0);
};
});
Found it. For the sake of completeness, I'm copying my comment here:
It looks like Uniform is really hacky. It covers up the actual select element, and displays span instead. Angular is working. The actual select element's value is changing, but the span that Uniform displays is not changing.
So you need to tell Uniform that your values have changed with jQuery.uniform.update. Uniform reads the value from the actual element to place in the span, and angular doesn't update the actual element until after the digest loop, so you need to wait a little bit before calling update:
app.controller('MainCtrl', function($scope, $timeout) {
$scope.reset = function() {
$scope.test = "";
$timeout(jQuery.uniform.update, 0);
};
});
Alternatively, you can put this in your directive:
app.directive('applyUniform',function($timeout){
return {
restrict:'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
element.uniform({useID: false});
scope.$watch(function() {return ngModel.$modelValue}, function() {
$timeout(jQuery.uniform.update, 0);
} );
}
};
});
Just a slightly different take on #john-tseng's answer. I didn't want to apply a new attribute to all my check-boxes as we had quite a few in the application already. This also gives you the option to opt out of applying uniform to certain check-boxes by applying the no-uniform attribute.
/*
* Used to make sure that uniform.js works with angular by calling it's update method when the angular model value updates.
*/
app.directive('input', function ($timeout) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attr, ngModel) {
if (attr.type === 'checkbox' && attr.ngModel && attr.noUniform === undefined) {
element.uniform({ useID: false });
scope.$watch(function () { return ngModel.$modelValue }, function () {
$timeout(jQuery.uniform.update, 0);
});
}
}
};
});
Please try blow code.
app.directive('applyUniform', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
if (!element.parents(".checker").length) {
element.show().uniform();
// update selected item check mark
setTimeout(function () { $.uniform.update(); }, 300);
}
}
};
});
<input apply-uniform type="checkbox" ng-checked="vm.Message.Followers.indexOf(item.usrID) > -1" ng-click="vm.toggleSelection(item.usrID)" />