2

Basically, what I'm trying to accomplish, is to set focus to the first invalid element after a form submit has been attempted. At this point, I have the element being flagged as invalid, and I can get the $name of the element so I know which one it is.

It's "working" but a "$apply already in progress" error is being thrown...
So I must be doing something wrong here :)

Here's my code so far:

$scope.submit = function () {

    if ($scope.formName.$valid) {
        // Good job.
    }
    else 
    {
        var field = null,
            firstError = null;
        for (field in $scope.formName) {
            if (field[0] != '$')
            {
                if (firstError === null && !$scope.formName[field].$valid) {
                    firstError = $scope.formName[field].$name;
                }

                if ($scope.formName[field].$pristine) {
                    $scope.formName[field].$dirty = true;
                }
            }
        }

        formName[firstError].focus();
    }
}

My field looping is based on this solution, and I've read over this question a few times. It seems like the preferred solution is to create a directive, but adding a directive to every single form element just seems like overkill.

Is there a better way to approach this with a directive?

Community
  • 1
  • 1
Kara
  • 161
  • 1
  • 1
  • 8

3 Answers3

2

Directive code:

app.directive('ngFocus', function ($timeout, $log) {
return {
    restrict: 'A',
    link: function (scope, elem, attr) {

        scope.$on('focusOn', function (e, name) {
            // The timeout lets the digest / DOM cycle run before attempting to set focus
            $timeout(function () {
                if (name === attr.ngFocusId) {
                    if (attr.ngFocusMethod === "click")
                        angular.element(elem[0]).click();
                    else
                        angular.element(elem[0]).focus();
                }
            });
        })
    }
}
});

Factory to use in the controller:

app.factory('focus', function ($rootScope, $timeout) {
    return function (name) {
        $timeout(function () {
            $rootScope.$broadcast('focusOn', name);
        }, 0, false);
    };
});

Sample controller:

angular.module('test', []).controller('myCtrl', ['focus', function(focus) {
  focus('myElement');
}
jklemmack
  • 3,518
  • 3
  • 30
  • 56
0

Building a directive is definitely the way to go. There is otherwise no clean way to select in element in angularjs. It's just not designed like this. I would recommend you to check out this question on this matter.

You wouldn't have to create a single directive for every form-element. On for each form should suffice. Inside the directive you can use element.find('input');. For the focus itself I suppose that you need to include jQuery and use its focus-function.

You can howerever - and I would not recommend this - use jQuery directly inside your controller. Usually angular form-validation adds classes like ng-invalid-required and the like, which you can use as selector. e.g:

$('input.ng-valid').focus();
Community
  • 1
  • 1
hugo der hungrige
  • 12,382
  • 9
  • 57
  • 84
  • Wouldn't this approach just be the "using directives as the place to throw a bunch of jQuery" approach referenced in the question you linked? In this case, I'm not sure `element.find`is sufficient since I need the first **invalid** element, not just the first form element on the page... or am I missing something? – Kara Nov 08 '13 at 14:50
  • You can use ng-invalid or ng-invalid-required. It may seem strange, as you can save a lot of code with just using jQuery. Thats why I recommend reading the answers from the question above. One advantage of making a directive is that you not only can reuse it easily on other forms but more importantly that it fits better to the mindset, which lets you generally write better angularjs-code. I think it is nice that you usually just have to look at a template which makes it very easy to see what is going on on the page. – hugo der hungrige Nov 09 '13 at 23:55
0

Based on the feedback from hugo I managed to pull together a directive:

.directive( 'mySubmitDirty', function () {
    return {
        scope: true,
        link: function (scope, element, attrs) {

            var form = scope[attrs.name];

            element.bind('submit', function(event) {
                var field = null;
                for (field in form) {
                    if (form[field].hasOwnProperty('$pristine') && form[field].$pristine) {
                        form[field].$dirty = true;
                    }
                }

                var invalid_elements = element.find('.ng-invalid');
                if (invalid_elements.length > 0)
                {
                   invalid_elements[0].focus();
                }

                event.stopPropagation();
                event.preventDefault();
            });
        }
    };
})

This approach requires jquery as the element.find() uses a class to find the first invalid element in the dom.

Kara
  • 161
  • 1
  • 1
  • 8