IE input element focus not working with angular directive - angularjs

I have a div populated dynamically with input elements and setting the focus on the first input field. The focus works on Chrome but not on IE. Here is the plnkr http://plnkr.co/edit/b6urKKilDVRwuqVQfbWt
The focus is in fact done inside a timeout function but still the focus does not seem to do anything on the input field. I am using an angular directive to create my form elements.
directive('ngppParameters', ["$compile","$timeout",function($compile,$timeout) {
return {
restrict: 'E',
link:function($scope,$element,$attrs)
{
$scope.$watch("tree.length", function (value) {
if(value.length===0)
return;
$timeout(function() {
var fields = $("ngpp-parameters input").filter(":visible:first");
console.log("Setting the focus");
if(fields.length>0)
{
console.log("Setting focus");
fields[0].focus();
}
},10);
});
}
};
Update:
This is the directive.

Finally found the issue, IE is treating the disabled parent and not letting the focus to be set on the children.
I have removed the disabled="disabled" attr from the directive pp-field and it works as expected. All other browsers except IE are not considering the parents disabled state.

Make sure you use ng-disabled="" instead of disabled attribute to make browsers to ignore it and behave as expected.

fields[0] isn't what you think it is (i've been caught by this same mistake before).
You want fields.eq(0)
https://api.jquery.com/eq/
You might also be able to just called fields.focus(). If there's only one in there that should work.

Related

Targeting keydown events to an angularJS custom directive

I am working on a problem wherein I am required to pick-up keydown events (specifically ctrl+p and then point to a print function which already exists) on a certain custom directive and under certain conditions (a certain tab should be selected). My current approach is to bind the keydown event on the document itself, broadcast it and then listen to it in the required custom directive. Following is the code I have placed in the app.run.. block -
angular.element($document).on('keydown', function(evt) {
if(evt.ctrlKey && evt.key==='p'){
$rootScope.$broadcast('printOnKeyPress');
}
});
This part is working as expected, the problem arises when I try to handle it in the required controller of the custom directive as follows:
$scope.$on('printOnKeyPress', function() {
//point to existing print function
}
This is where the problem arises. It goes into the print function but still the output is incorrect. I am missing something and I can't figure out what.
Also, this is not a good approach but I have searched and am unable to find a possible solution to just bind the keydown event on that custom directive itself (the component only appears if a document is selected).
(ng-keydown won't also work here)
Any help is appreciated!
You could put it in the directive and use the scope destroy event to remove the listener.
Within directive link function:
function keyHandler(evt) {
if (evt.ctrlKey && evt.key === 'p') {
// do your print
}
}
angular.element($document).on('keydown', keyHandler);
scope.$on('$destroy', function() {
angular.element($document).off('keydown', keyHandler);
});

focus on last input ng-repeat

I have been looking at a lot of focus directives, especially those posted here.
But none seem to work for my scenario. I have a tabset and a table inside with an ng-repeat that creates rows with inputs on it. The way I create them is using ng-blur at the end of the last row checkbox so the ng-blur fires the addNewRow method. What I want is to focus the first input on the newly created row but the focus goes to the browser search bar instead.
I created a fiddle to show the problem using a directive from the first posted link.
I tested this directive:
soccerApp.directive('setfocus', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var focus = !! attrs.setfocus && !attrs.setfocus.replace(/true/, '');
if (focus === true) {
//alert(element);
element[0].focus();
}
}
};
});
and the element[0].focus(); actually fires when I create a new row but doesn't set the focus.
Can someone please take a look and tell me what could be wrong?
NOTE: The idea is to use the TAB key to loop through the inputs until the last one.
As Deblaton Jean-Philippe pointed out
The problem is that, at the moment you "tab" from the last input, your browser sets the focus out of the page. So you don't have any control on it anymore. I don't know if it's possible to override that without creating a fake invisible input below to keep the focus into the page
i was missing a fake input so i added a fake at the bottom after my table and it worked

AngularJS: Attempt to dynamically apply directive using ngClass causing weird functional and performance issues

Requirement
I want a textarea that expands or contracts vertically as the user types, alla Facebook comment box.
When the textarea loses focus it contracts to one line (with ellipsis if content overflows) and re-expands to the size of the entered text upon re-focus (this functionality not found on Facebook)
Note: Clicking on the textarea should preserve caret position exactly where user clicked, which precludes any dynamic swapping of div for textarea as the control receives focus
Attempted Solution
I'm already well into an AngularJS implementation, so...
Use Monospaced's Angular Elastic plugin. Nice.
Two attempts...
Attempt A: <textarea ng-focus="isFocussed=true" ng-blur="isFocussed=false" ng-class="'msd-elastic': isFocussed"></textarea> Fails because ng-class triggers no re-$compile of the element after adding the class, so Angular Elastic is never invoked
Attempt B: create a custom directive that does the needed re-$compile upon class add. I used this solution by hassassin. Fails with the following problems
Attempt B problems
Here's a JSFiddle of Attempt B Note that Angular v1.2.15 is used
I. Disappearing text
go to the fiddle
type into one textarea
blur focus on that textarea (eg click in the other textarea)
focus back on the text-containing textarea
result: text disappears! (not expected or desired)
II. Increasingly excessive looping and eventual browser meltdown
click into one textarea
click into the other one
repeat the above for as long as you can until the browser stops responding and you get CPU 100% or unresponsive script warnings.
you'll notice that it starts out OK, but gets worse the more you click
I confirmed this using: XP/Firefox v27, XP/Chrome v33, Win7/Chrome v33
My investigations so far
It seems that traverseScopesLoop in AngularJS starting at line 12012 of v1.2.15, gets out of control, looping hundreds of times. I put a console.log just under the do { // "traverse the scopes" loop line and clocked thousands of loops just clicking.
Curiously, I don't get the same problems in Angular v1.0.4. See this JSFiddle which is identical, except for Angular version
I logged this as an AngularJS bug and it was closed immediately, because I'd not shown it to be a bug in Angular per se.
Questions
Is there another way to solve this to avoid the pattern in Attempt B?
Is there a better way to implement Attempt B? There's no activity on that stackoverflow issue after hassassin offered the solution I used
Are the problems with Attempt B in my code, angular-elastic, hassassin's code, or my implementation of it all? Appreciate tips on how to debug so I can "fish for myself" in future. I've been using Chrome debug and Batarang for a half day already without much success.
Does it seem sensible to make a feature request to the AngularJS team for the pattern in Attempt A? Since you can add directives by class in Angular, and ngClass can add classes dynamically, it seems only natural to solve the problem this way.
You are severely overthinking this, and I can't think of any reason you would ever need to dynamically add / remove a directive, you could just as easily, inside the directive, check if it should do anything. All you need to do is
Use the elastic plugin you are using
Use your own directive to reset height / add ellipsis when it doesn't have focus.
So something like this will work (not pretty, but just threw it together):
http://jsfiddle.net/ss6Y5/8/
angular.module("App", ['monospaced.elastic']).directive('dynamicClass', function($compile) {
return {
scope: { ngModel: '=' },
require: "?ngModel",
link: function(scope, elt, attrs, ngModel) {
var tmpModel = false;
var origHeight = elt.css('height');
var height = elt.css('height');
var heightChangeIndex = 0;
scope.$watch('ngModel', function() {
if (elt.css('height') > origHeight && !heightChangeIndex) {
heightChangeIndex = scope.ngModel.length;
console.log(heightChangeIndex);
}
else if (elt.css('height') <= origHeight && elt.is(':focus')) {
heightChangeIndex = 0;
}
});
elt.on('blur focus', function() {
var tmp = elt.css('height');
elt.css('height', height);
height = tmp;
});
elt.on('blur', function() {
if (height > origHeight) {
tmpModel = angular.copy(scope.ngModel);
ngModel.$setViewValue(scope.ngModel.substr(0, heightChangeIndex-4) + '...');
ngModel.$render();
}
});
elt.on('focus', function() {
if (tmpModel.length) {
scope.ngModel = tmpModel;
ngModel.$setViewValue(scope.ngModel);
ngModel.$render();
tmpModel = '';
}
});
}
};
})

How do you set the focus to a control in an angular app?

The question stems from an ng-grid interaction, but I've stubbed my toe on it a few other places.
An interaction with the page raises the need for the focus to be on a certain control.
For ng-grid this might be clicking on the filter button in the header. This causes a popup (but not really a modal dialog) input control to appear which then needs the focus, so the user doesn't then have to click a second time to enter the filter text. I'm assuming this should be done in the directive, but how do you get that directive at the point this is happening?
Another interaction might be after an attempted save on a form. Let say there is a validation that can't happen in the client, (multi-user app, race condition to acquire a resource). When the error returns from the promise, I'd like to put the cursor in the field that needs to be changed. )Which field depends on the error response from the server.)
I think what I'm really looking for is the equivalent of an $('#id').focus() that accepts an ng-model and finds the correct control on the page and puts the cursor in that field, but one that could be used at the completion of promise or in reaction to an event. I realize that linkage from model => DOM is problematic (a model could appear many places on the page), but in the case of form input, that probably isn't true, or could be made not-true to facilitate this kind of application response.
I'm a little lost as to where the logic should be and how I could get hold of the object that would contain it.
There's a similar question that has some good answers. I think the second answer comes closest to what you're asking for
How to set focus on input field?
Instead of binding a boolean "should be focused" field, the directive uses events to set focus on the desired element.
There are several advantages to this approach over the $('#id').focus() functionality you were describing. The biggest one is that with the event approach, your controller can be mocked and tested outside the DOM, because the tests can just look for fired events, rather than checking which DOM element has focus.
Here's a really generic example of how you might do it for field validation. Kudos to #blesh for the directive/service implementations
In your form you can add the focus-on directive. Just use a unique event name for each field:
<input type="text" name="name" focus-on="input-name" />
<input type="text" name="email" focus-on="input-email" />
In your controller, after you do field validation, you can call the "focus" service on the using the focus-on="input-{name}" value you specified in your template. Here I just prepend input- to the field name, but you could use any naming convention you like.
app.controller('MyCtrl', function($scope, focus) {
$scope.validate = function() {
// ...
// Do your field validation
// ...
if (invalidFields.length) {
focus('input-' + invalidFields[0].name);
}
};
});
Then just define your directive and service
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on('focusOn', function(e, name) {
if(name === attr.focusOn) {
elem[0].focus();
}
});
};
});
app.factory('focus', function ($rootScope, $timeout) {
return function(name) {
$timeout(function (){
$rootScope.$broadcast('focusOn', name);
});
}
});

Set focus when another element is clicked or other event occurs

I have a tab-like view on my page and I have a variety of events which need to set the focus to one of the tabs. (showing/hiding the div is easy since I just use a model variable.) The events that cause a div to be focused are clicking on the tab header, data load completion, and initial loading. I know where to intercept all these events, but I'm not sure how I tell the other element to set the focus (none of the intercept sites know about the other element, only about the model).
I've looked around but can't find a good reference for this. I assume I want to listen for some message and post it from the various other locations.
How does one setup this type of messaging event?
ANSWER: I built on the answer and came up with the below directive. It combines both a focus and show state for the div.
newsendApp.directive('showAndFocus', function() {
return {
link: function(scope, element, attr){
scope.$on('SetArticlesListFocus',function() {
if( scope.$eval( attr.showAndFocus ) ) {
$(element).focus();
}
})
scope.$watch(attr.showAndFocus, function(value) {
if( value ) {
$(element).show();
setTimeout( function() { $(element).focus(); }, 0 );
} else {
$(element).hide();
}
})
}
}
});
If an event occurs which may require resetting the focus then I do: $rootScope.$broadcast( 'SetArticlesListFocus' );
You can broadcast an event, and also have listeners that perform an action once such a broadcast has occured.
You can use $scope.$broadcast('changed-tab', objectSentWithBroadcast). The second parameter is an object you can optionally send, like the tab you want to focus on.
Then you would have a listener like this where you can select the tab wanted:
$scope.$on('changed-tab', function(e, objectSentWithBroadcast) {
// do something here to select the tab
});
Have a look at this issue raised in angularjs.
https://github.com/angular/angular.js/issues/1277#issuecomment-16012024
Here is the final plunk that creates a ng-focus directive that manages two way data binding between a variable and an element's focus state.
http://plnkr.co/edit/bntEsfngnJKuneg2raD1
This will allow you to bind a model to an elements focus state and then setting or unsetting this variable will make the element gain / lose focus. You will have to note that if you are using div's etc ( which are non-focusable by default! ) you will need to set tabIndex = -1 on them. Apart from that it should work fine and suit your need for manipulating models pretty well.

Resources