Make input text selected upon appearance in angular - angularjs

Using angular I have a set of input elements that become present in the dom after a certain user action, i.e I use ng-if to determine if it should be present or not. I would like the first of these input elements to gain focus and for the text in it to be selected to that the user easily can change all the content in that element.
Searching the web I have seen several posts that say I should use either the focus or select method or both but I haven't been able to get the desired result. Here is a directive I created:
app.directive('selectMe', function() {
return function(scope, element) {
element[0].focus();
element[0].select();
};
})
And here is a plunkr demo. Can anyone tell me why it is not working?

Because when your directive is initializing, their values isn't attached yet, therefore you should set small $timeout.
It works, you can try in plnkr
app.directive('selectMe', function($timeout) {
return function(scope, element) {
$timeout(function() {
var ele = element[0];
ele.focus();
ele.select();
}, 500)

Related

Focus on the first field that is .ng-invalid at Submit - not working for radios-inline

I am using the directive from accepted answer on Set focus on first invalid input in AngularJs form to accomplish this:
app.directive('accessibleForm', function () {
return {
restrict: 'A',
link: function (scope, elem) {
// set up event handler on the form element
elem.on('submit', function () {
console.log("inside focus directive");
// find the first invalid element
var firstInvalid = elem[0].querySelector('.ng-invalid');
//if we find one, set focus
if (firstInvalid) {
firstInvalid.focus();
}
});
}
};
});
As long as I do not use radios-inline the focus works. Please refer: http://jsfiddle.net/mutharasus/mu7y4k8f/
But if the first error happens to be on a radios-inline field the focus does not work. Please refer: http://jsfiddle.net/mutharasus/00jzbL6g/
I am not sure how to fix. Please help.
The radio-inline is adding the ng-invalid class to the field label instead of to each individual radio input.
You could change that directive and implement your desired behaviour in your custom one (you would need to add ng-invalid to each radio input) or change the accessibleForm directive to check if the invalid element is a label and, in that case, find the the first radio input associated to it:
app.directive('accessibleForm', function () {
return {
restrict: 'A',
link: function (scope, elem) {
// set up event handler on the form element
elem.on('submit', function () {
console.log("inside focus directive");
// find the first invalid element
var firstInvalid = elem[0].querySelector('.ng-invalid');
// If we got a label, then it is a radio-inline
if(firstInvalid && firstInvalid.tagName === 'LABEL') {
firstInvalid = elem[0].querySelector('.ng-invalid + div input[type=radio]')
}
firstInvalid && firstInvalid.focus();
});
}
};
});
Although it may seem like the easiest solution, it does not look right for me to have that second query selector that depends so much on the structure of that specific directive, as if that changes then the accessibleForm directive will be broken again.
I think the problem is that the label of each radio has the '.ng-invalid' class, but not the radio input element itself, which comes afterwards anyway. Your query selector is inadvertently returning the label's element and then uselessly calling focus()' on it.
This is the element that focus is called on if the radio is invalid:
<label ng-model="model['Field1']" ng-model-options="form.ngModelOptions" schema-validate="form" class="control-label ng-valid-schema-form ng-dirty ng-invalid ng-invalid-tv4-302" ng-show="showTitle()">Field1</label>
The labels for the other types of controls do not have the .ng-invalid class so they are not bothered by this problem.
I'm not familiar with these libraries, so the answer depends. Either improve the selector or change how the labels of the radio inputs are styled.
Here's a version of the jsFiddle that I've hacked to demonstrate that it's the selector that's preventing the focus from occurring. Just click submit without changing the form.

Setting attrs dynamically for ui-bootstrap tooltip / popover

I'm trying to programmatically toggle tooltips (like mentioned here: https://stackoverflow.com/a/23377441) and got it fully functional except for one issue. In order for it to work I must have tooltip-trigger and tooltip attributes hardcoded as follows:
<input type="text" tooltip-trigger="show" tooltip="" field1>
In my working directive, I'm able to change the tooltip attributes and trigger a tooltip, but if I try to leave those two attributes out and attempt to set them dynamically, ui-bootstrap doesn't pick them up and no tooltip gets displayed.
html
<input type="text" field2>
js
myApp.directive('field2', function($timeout) {
return {
scope: true,
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch('errors', function() {
var id = "field2";
if (scope.errors[id]) {
$timeout(function(){
// these attrs dont take effect...
attrs.$set('tooltip-trigger', 'show');
attrs.$set('tooltip-placement', 'top');
attrs.$set('tooltip', scope.errors[id]);
element.triggerHandler('show');
});
element.bind("click", function(e){
element.triggerHandler('hide');
});
}
});
},
};
});
I'd prefer not to hardcode these attributes in the html, so how do I go about setting these attributes dynamically and get ui-bootstrap to pick them up?
Here is a plunker that has a working (field1) and non working (field2) directive: http://plnkr.co/edit/mP0JD8KHt4ZR3n0vF46e
You can do this, but you have to change a couple of things in your approach.
Plunker Demo
Directive
app.directive("errorTooltip", function($compile, $interpolate, $timeout) {
return {
scope: true,
link: function($scope, $element, $attrs) {
var errorObj = $attrs.errorTooltip;
var inputName = $attrs.name;
var startSym = $interpolate.startSymbol();
var endSym = $interpolate.endSymbol();
var content = startSym+errorObj+'.'+inputName+endSym;
$element.attr('tooltip-trigger', 'show');
$element.attr('tooltip-placement', 'top');
$element.attr('tooltip', content);
$element.removeAttr('error-tooltip');
$compile($element)($scope);
$scope.$watch(errorObj, function() {
$timeout(function(){
$element.triggerHandler('show');
});
}, true);
$element.on('click', function(e){
$element.triggerHandler('hide');
});
}
};
});
The super long detailed explanation:
Okay, so from the top: #Travis is correct in that you can't just inject the attributes after the fact. The tooltip attributes that you place on the element are directives themselves, so the tooltip needs to be compiled when it's appended. That's not a problem, you can use the $compile service to do this, but you need to do it just once for the element.
Also, you need to bind the tooltip text (the value given to the tooltip attribute) to an expression. I do that by passing in a concatenated value of $interpolate.startSymbol() + the scope value that you want to display (in the demo it is the fieldx property of the errors object) + the $interpolate.endSymbol(). This basically evaluates to something like: {{error.field1}}. I use the $interpolate service start and end symbols because it just makes the directive more componentized, so you can use it on other projects where you might have multiple frameworks and be using something other than double curly-braces for your Angular expressions. It's not necessary though and you could instead do: '{{'+errorObj+'.'+inputName+'}}'. In this case, you don't have to add the $interpolate service as a dependency.
As you can see, to make the directive truly reuseable, rather than hard-coding the error field, I set the value given to the directive attribute to the name of the object that will be watched and use the input name value as the object property.
The chief thing you need to remember is that before you compile, you have to remove the error-tooltip attribute from the element because if you don't you'll wind up in an infinite loop and crash hard! Basically, the compile service is going to take the element that the directive is attached to and compile it with all of the attributes your directive added, if you leave the error-tooltip attribute, it's going to try and recompile that directive too.
Lastly, you can take advantage of the fact that the tooltip will not display if its text value is empty or undefined (see line 192). That means you only have to watch the errors object not the individual property on the error associated with the tooltip. Make sure that you set the equality operator on the $watch to true, so that it will trigger if any of the object's properties are changed:
$scope.$watch('errors', function() {
$timeout(function(){
$element.triggerHandler('show');
});
}, true); //<--equality operator
In the demo, you can see the effect of changing the errors object. If you click the Set Errors button the tooltip will display for both the first and second inputs. Click the Change Error Values and the tooltip displays for the first and third inputs.
TL;DR:
Add the directive to your markup by setting the value to be the name of the object that will contain all of the errors. Make sure to give the field a name attribute that corresponds to the property key name in the object that will contain the errors for that input, such as:
<input class="form-control" ng-model="demo.field1" name="field1" error-tooltip="errors" />

Combining Polymer elements and angular

I have looked at a number of answers for binding Angular-JS scope data to Polymer components. (Use Angular-Bind-Polymer Another, And a third). It seems like this shouldn't be this hard, if the polymer components are truly just DOM. (Note that I'm using Chrome beta (36)).
I tried the Angular-Bind-Polymer suggestion, but no luck. My real interest is extending ngModel to work with Polymer so that I can use the Polymer check boxes, radio buttons, etc. For example, I tried getting paper-checkbox to work, so I tried the following, thinking that it should work:
var ngPaper = angular.module('ng-paper', []);
ngPaper.directive('paper-checkbox', function() {
console.log("Processing directive");
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attr, ctrl) {
console.log("Running linker");
element.on('click', function() {
scope.$apply(function() {
ctrl.$setViewValue(element[0].checked);
});
});
ctrl.$render = function() {
element[0].checked = ctrl.$viewValue;
};
ctrl.$isEmpty = function(value) {
return value != true;
};
ctrl.$formatters.push(function(value) {
return value === true;
});
ctrl.$parsers.push(function(value) {
return value ? true : false;
});
}
};
});
But no.
I then tried using angular-bind-polymer to bind the checked value on the paper-checkbox to a model attribute but didn't have any success.
I feel like if I could figure out how to get one of the form control elements to work, the others should fall quickly in line. Does anyone have a better idea on how to do this or an explanation as to why the directive I wrote isn't getting picked up and applied to the paper-checkbox?
I made this generic work-around that I use to watch changes on check-boxes and most Polymer elements from AngularJS, it's really useful while you find a more proper way, I hope it helps you.
You can also use it to manipulate Polymer Elements (E.g. Toggle).
in your HTML:
<paper-radio-group selected="firstOption">
<paper-radio-button label="First Option" id="firstOption" ng-click="change()"></paper-radio-button>
<paper-radio-button label="Second Option" id="secondOption" ng-click="change()"></paper-radio-button>
</paper-radio-group>
In the corresponding AngularJS controller, requites $scope.
var firstOption= document.querySelector('paper-radio-button[id="firstOption"]');
var secondOption= document.querySelector('paper-radio-button[id="secondOption"]');
console.log(firstOption);
console.log(secondOption);
$scope.change = function()
{
console.log(firstOption);
console.log(secondOption);
}
This way every time the user changes the selection, AngularJS will get notified so it can query the changes, you can scope the data you get back to something more specific, this is particularly useful to toggle Polymer Elements from AngularJS.
Let me know if this works for you, happy coding!

AngularJS - Calling function in directive when a 2-way bound value changes

I've got a directive all setup with 2-way data binding on the attributes using = and I can see everything is working well with that. Now I'm stuck at the need to call a function within the directive whenever one of my bound attributes changes in the parent scope, and I can't figure out how to pull that off.
I'm basically creating a version of the ui checkbox button that works with arrays of objects. You pass the directive an allowed array (which contains all the different options) and an applied array (which contains the same objects from allowed). For checking if an object is in the allowed array I have another array that is the just the id properties. Within the directive this is working great, but if the applied array changes outside of the directive the id array never gets updated.
The Directive:
angular.module('MyApp',[])
.directive('btnCheckboxGroup', function(){
return {
restrict: 'E',
// controller: DirCtrl,
templateUrl: 'btnCheckboxGroup.html',
scope: {
allowed: '=',
applied: '=',
id: '=',
title: '='
},
link: function(scope, elem, attrs){
scope.abp = [];
// this works right away, but how do I run it when the parent scope updates it?
angular.forEach(scope.applied, function(obj){
scope.abp.push( obj[scope.id] );
});
scope.addRemove = function(a){
var index = scope.abp.indexOf(a[scope.id]);
// doesn't exist, add it
if(index === -1){
scope.abp.push(a[scope.id]);
scope.applied.push(a);
// does exist, remove it
} else {
scope.abp.splice(index, 1);
for(var i in scope.applied){
if(scope.applied[i][scope.id]==a[scope.id]){
scope.applied.splice(i,1);
break;
}
}
}
}// end addRemove()
}
};
});
JSFiddle
I've tried lots of variations of things like scope.$watch, attrs.$observe, and attempted at one point to try one-way data-binding with # and that made lots of things crash.
So whats the magic I'm missing here?
You can pass a third parameter to $watch to change the way it compares the old and the new value. See the Angular docs
$watch(watchExpression, [listener], [objectEquality]);
If you set this to true, it will pick up changes in the content of the array and not only when the array reference changes. This does have a performance impact (depending on the length of the array). Checking only the length of the array does not cover the case where the number of elements stay the same but the elements themselves do change.
In your case you would need to do something like this:
scope.$watch(
"applied",
function() {
scope.abp = [];
angular.forEach(scope.applied, function(obj){
scope.abp.push( obj[scope.id] );
});
},
true);
Is this what you're looking for?
scope.$watch(function() {
return scope.applied.length;
}, function(val) {
console.log(val);
console.log(scope.applied);
});
The array on scope doesn't change but its length does, so if you were using the string-variant of $watch it won't fire, but using a function and looking at the length of the array will. More on this in the docs

AngularJS - adding directive dynamically to an element

I have created a directive that check if data was entered to an HTML element in the following way:
var myApp = angular.module('myApp', []);
myApp.directive("uiRequired", function () {
return function (scope, elem, attrs) {
elem.bind("blur", function () {
var $errorElm = $('#error_testReq');
$errorElm.empty();
if (angular.isDefined(attrs) && angular.isDefined(attrs.uiRequired) && attrs.uiRequired == "true" && elem.val() == "") {
$errorElm.append("<li>Field value is required.</li>");
$errorElm.toggleClass('nfx-hide', false);
$errorElm.toggleClass('nfx-block', true);
}
else
{
$errorElm.toggleClass('nfx-hide', true);
$errorElm.toggleClass('nfx-block', false);
}
});
};
});
A working example can be seen here
My question:
Is there a way of adding the directive (uiRequired) I have created dynamically to elements on screen on document ready.
I want to put the new directive on selected HTML elements according to pre-defined list I have. I can not know in advance on which field this directive has to be on.
So I have to put it while page is rendering.
I have tried putting it dynamically myself while page is loading, however AngularJS did interpret it.
I could not find an example on the internet that does that.
Can anyone help me?
You can dynamically add directives to a page during the compilation phase when Angular is walking the DOM and matching elements to directives. Each step of the compilation process may transform the DOM tree ahead of it, but you should never modify elements that Angular has already compiled. This point is important to remember because adding directives to elements that have already been walked will have no effect. Of course, there ways around this. For example, you could re-compile and re-link previously walked elements. However I strongly advise against this as it may lead to unintended side effects such as memory leaks, and slow performance.
To dynamically add uiRequired directives, you can create a parent directive - let's call it uiRequiredApplier.
app.directive('uiRequiredApplier', function($scope) {
return {
restrict: 'A',
compile: function(element, attr) {
// you can apply complex logic figure out which elements
// you want to add the uiRequired attribute to
$('input', element).attr('uiRequired','');
return function(scope, element, attr) {
}
}
}
});
You can then apply the attribute like this:
<div ui-required-applier>
<input type="text" ng-model="name" />
</div>
When the uiRequiredApplier is compiled, it will dynamically add uiRequired attributes to selected elements using jQuery that have not been compiled yet. And when Angular walks the DOM, eventually it will compile and link the uiRequired attributes, which will add the desired validation behavior.

Resources