Technology, Continued...

Ramblings about business, technology, development and my life

NAVIGATION - SEARCH

Yet another Angular focus directive

I’ve seen lots of Angular directives that set focus of an element based on its html name or id.  And I’ve seen a few other methods as well.  But I couldn’t find one that could handle these requirements:

  1. Keep trying to focus the element for a period of time (useful when there’s a delay in the element actually being displayed in the dom)
  2. If you put the attribute on multiple input elements, the first one would get the initial focus (useful if you have the first one conditionally displaying, and want to fall back to a second one if it isn’t)
  3. Allows you to conditionally have the next input field displayed on the page get the focus (useful if clicking a button on your page, for example, adds an input field you want focused)

So, I wrote the one you see below called, originally enough, anotherFocus. 

It should be added to an element with the attribute…

another-focus="domFocus"            

where, on your controller,

$scope.domFocus = { setFocus: true }

Then, after page is loaded and maybe you've focused an element, you can set $scope.domFocus.setFocus = true right before causing another input to be added with the directive on it.  The next element added with the directive would get the focus.

The directive as written will retry the focus call every 100ms for up to 2 seconds, but of course, you can always change that in the code.  If your page takes a while to load (mine sometimes takes half a second because of some processing it’s doing), this is crucial.

Hope this helps someone.

angular.module('app').directive('anotherFocus',
function ($timeout) {
    return {
        scope: {
            //should be set up like another-focus="domFocus"
            //where, on controller, $scope.domFocus = { setFocus: true }
            //then, after page is loaded an maybe you've focused an element, 
            //you can set $scope.domFocus.setFocus = true right before causing another
            //input to be added with the directive on it
            trigger: '=anotherFocus'
        },

        link: function (scope, element) {
            var attempts = 0;

            function timeout(ele) {
                ele.focus();

                //we're willing to try 20 times at 100ms for a total of 2 seconds
                //to wait for the element to be on the page and active
                if (ele != document.activeElement && attempts++ < 20)
                    $timeout(function () {
                        timeout(ele);
                    }, 100);
                //console.log('attempt' + attempts);
            }

            //start the initial attempt to focus this element
            scope.$watch('trigger', function (value) {
                if (value.setFocus) {
                    //stop future elements with directive from getting focus unless we turn on setFocus from controller again
                    value.setFocus = false;
                    timeout(element[0]);
                }
            });
        },

    };
}
);

Angular, Select Inputs and 0 Values With a Required Attribute

Have you ever had a situation where you were binding your select input to a set of objects like this:

[{ value: 0, key: ‘please choose’}, { value: 1, key: ‘texas’}, {value: 2, key: ‘georgia’}]

I ran into a situation where a service that was giving me the options for the select input was setting the value for the ‘Please choose’ option to 0 instead of to blank.  This meant that if this select input was required via a “required” or “ng-required” attribute, the “please choose” would cause the browser and angular to consider the field as having a valid selection.

I had two options: change the data itself so that it was set to null or blank instead of 0, or… I thought I might create a directive to handle this.  So I created the directive.  As you can see below, this is a simple directive I threw together to consider the select to be invalid if it’s required and the value of the option selected is 0.  Not complicated, but so far it’s worked great.  The nice thing is that it’s not meant to replace the required attribute, so you can leave it or the ng-required directive, and if you’re using the ng-required directive, setting it to an expression still works fine. 

Enjoy.

/* 
Directive: required-zero-doesnt-count
JW - 2014-11-16

There are some instances where a select input has an option with a value of 0, where 0 is considered to be a placeholder option.  In other words, if the select is required, 0 doesn't count as fulfilling the 
required validation.  However, just putting the "required" attribute on the select doesn't work, because while we know the business doesn't consider "0" a valid selection, the browser thinks it is.
This directive sets required validation to false if the 0 option is selected.  

Note that this is not in place of the required attribute, but is used alongside.  The required or ng-required is still needed, or else this directive will not check for a 0 selection.
*/

(function () {
    'use strict';

    var app = angular.module('app');

    app.directive('requiredZeroDoesntCount', [function () {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attrs, ngModel) {
                scope.$watch(function () {
                    //just returning a string that will be different if either view value has changed or required attribute has changed
                     return ngModel.$viewValue + "___" + attrs.required;
                }, function () {
                    if (attrs.required)
                        ngModel.$setValidity('required', ngModel.$viewValue && ngModel.$viewValue != "0");
                    else {
                        ngModel.$setValidity('required', true);
                    }
                });
            }
        };
    }]);
})();