web-dev-qa-db-ja.com

AngularJsフォームの最初の無効な入力にフォーカスを設定します

AngularJsでのフォーカスの設定に関するいくつかの記事とStackOverflowの質問を読みました。

残念ながら、私が読んだすべての例は、フォーカスを得るために要素に追加できる属性があることを前提としています。 focusMeディレクティブ

ただし、フォーカスを設定する入力が事前にわからない場合はどうなりますか?特に、$ invalidセットを持つフォームの最初の入力要素、つまり検証に失敗した要素にフォーカスを設定するにはどうすればよいですか?検証に失敗する入力がいくつかある可能性があるため、これに基づいて.focus()を呼び出そうとするディレクティブは使用できません。 (私はこれをアクセシビリティ/ WCAGの理由で行っています。検証に失敗した最初のフィールドを見つけるためにキー入力を最小限に抑えるために送信時にクリックすることをお勧めします)。

$ errorオブジェクトは、検証に失敗したすべてのコントロールを提供しますが、フォーム上の表示の順序ではなく、失敗の種類ごとにグループ化されます。

私はこれを行うためのいくつかの巧妙な方法を思いつくことができると確信しています。フォーム上のディレクティブ。フォーカスを設定する必要があるときにブロードキャストを受信します。そのディレクティブは最初の$ invalid要素を検索できます。しかし、これは非常に複雑に思えるので、これらがより「角度のある」方法であるかどうかを知りたいと思います。

60
iandotkelly

Angular.elementを使用することもできます

angular.element('input.ng-invalid').first().focus();

表示

<form name="myForm" novalidate="novalidate" data-ng-submit="myAction(myForm.$valid)" autocomplete="off"></form>

コントローラー

$scope.myAction= function(isValid) {
    if (isValid) {
        //You can place your ajax call/http request here
    } else {
        angular.element('input.ng-invalid').first().focus();
    }
};

検証にngMessagesを使用

jqueryなしの方法

angular.element($document[0].querySelector('input.ng-invalid')).focus();

このメソッドを使用する場合、angularコントローラのパラメータとして$documentを渡す必要があります

angular.module('myModule')
.controller('myController', ['$document', '$scope', function($document, $scope){
    // Code Here
}]);
14

わかりましたので、答えは思ったより簡単でした。

必要なのは、フォーム自体に送信するディレクティブだけで、イベントハンドラーが送信イベントを探します。これにより、DOMを走査して、.ng-invalidクラスを持つ最初の要素を検索できます。

JQLiteを使用した例:

myApp.directive('accessibleForm', function () {
    return {
        restrict: 'A',
        link: function (scope, elem) {

            // set up event handler on the form element
            elem.on('submit', function () {

                // find the first invalid element
                var firstInvalid = elem[0].querySelector('.ng-invalid');

                // if we find one, set focus
                if (firstInvalid) {
                    firstInvalid.focus();
                }
            });
        }
    };
});

ここの例ではAttributeディレクティブを使用していますが、例を拡張して要素ディレクティブ(制限: 'E')にし、これをに変換するテンプレートを含めることができます。ただし、これは個人的な好みです。

87
iandotkelly

ディレクティブを他の回答として作成するか、ng-submitでフックしてコントローラーにロジックを実装できます。

見る:

<form name='yourForm' novalidate ng-submit="save(yourForm)">
</form>

コントローラ:

$scope.save = function(yourForm) {
  if (!yourForm.$valid) {
    angular.element("[name='" + yourForm.$name + "']").find('.ng-invalid:visible:first').focus();
    return false;
  }
};
15
nnattawat

純粋なjQueryを使用して、最初の無効な入力を選択できます。

$('input.ng-invalid').first().focus();

7
Edmond Chui
    .directive('accessibleForm', function () {
        return {
            restrict: 'A',
            link: function (scope, elem) {
                // set up event handler on the form element
                elem.on('submit', function () {
                    // find the first invalid element
                    var firstInvalid = elem[0].querySelector('.ng-invalid');
                    if (firstInvalid && firstInvalid.tagName.toLowerCase() === 'ng-form') {
                        firstInvalid = firstInvalid.querySelector('.ng-invalid');
                    }
                    // if we find one, set focus
                    if (firstInvalid) {
                        firstInvalid.focus();
                    }
                });
            }
        };
    })
4
chaojidan

私はしばらくの間このアイデアを試してきましたが、私自身の解決策を思いつきました。それは、私のようなDOMのクロールに不利な人々を助けるかもしれません。

フォーム要素が一貫した順序(つまり、上から下)で登録され、フォーム名が何であれ($ scope.myFormなど)、名前と検証状態がスコープで利用可能であると言えます。

これにより、DOMをクロールせず、代わりにangular js。の内部構造をクロールすることなく、最初の無効なフォーム入力を見つける方法があったと思うようになりました。フォーム要素をフォーカスする他の方法、私はカスタムディレクティブにブロードキャストしています、ブロードキャストがフォーカスする要素の名前と一致する場合(それはあなたが最初のロードにフォーカスする要素を制御するのでそれ自体で便利です) 。

最初の無効な(理想的にはサービスを介してコントローラーに共有される)を見つける機能

function findFirstInvalid(form){
    for(var key in form){
        if(key.indexOf("$") !== 0){
            if(form[key].$invalid){
                return key;
            }
        }
    }
}

カスタムフォーカスディレクティブ

directives.directive('focus', function($timeout){
    return {
        require: 'ngModel',
        restrict: 'A',
        link: function(scope, elem, attrs, ctrl){
            scope.$on('inputFocus', function(e, name){
                if(attrs.name === name){
                    elem.focus();
                }
            });
        }
    }
});
2
h.coates

Iandotkellyによって書かれた素晴らしいソリューションにいくつかの小さな変更を加えました。このソリューションは、スクロールでトリガーされるアニメーションを追加し、その後、選択した要素にフォーカスを行います。

myApp.directive('accessibleForm', function () {
    return {
        restrict: 'A',
        link: function (scope, elem) {

            // set up event handler on the form element
            elem.on('submit', function () {

                // find the first invalid element
                var firstInvalid = elem[0].querySelector('.ng-invalid');

                // if we find one, we scroll with animation and then we set focus
                if (firstInvalid) {
                     angular.element('html:not(:animated),body:not(:animated)')
                    .animate({ scrollTop: angular.element(firstInvalid).parent().offset().top },
                        350,
                        'easeOutCubic',
                        function () {
                            firstInvalid.focus();
                        });
                }
            });
        }
    };
});
1
Mathemagician

1行だけ:

if($scope.formName.$valid){
    //submit
}
else{
    $scope.formName.$error.required[0].$$element.focus();
}
1
sonphuong

これは、jqLit​​eではfocus()がサポートされておらず、要素のAngular docsから)であるためです。

0
acacio.martins

@Sajanが言ったことをちょっとした微調整でうまくいきました。

angular.element("[name='" + this.formName.$name + "']").find('.ng-invalid:visible:first')[0].focus();
0

上記の chaojidan に触発され、ネストされたangular 1.5.9 ng-forms :

class FormFocusOnErr implements ng.IDirective
{
    static directiveId: string = 'formFocusOnErr';

    restrict: string = "A";

    link = (scope: ng.IScope, elem, attrs) =>
    {
        // set up event handler on the form element
        elem.on('submit', function () {

            // find the first invalid element
            var firstInvalid = angular.element(
                elem[0].querySelector('.ng-invalid'))[0];

            // if we find one, set focus
            if (firstInvalid) {
                firstInvalid.focus();
                // ng-invalid appears on ng-forms as well as 
                // the inputs that are responsible for the errors.
                // In such cases, the focus will probably fail 
                // because we usually put the ng-focus attribute on divs 
                // and divs don't support the focus method
                if (firstInvalid.tagName.toLowerCase() === 'ng-form' 
                    || firstInvalid.hasAttribute('ng-form') 
                    || firstInvalid.hasAttribute('data-ng-form')) {
                    // Let's try to put a finer point on it by selecting 
                    // the first visible input, select or textarea 
                    // that has the ng-invalid CSS class
                    var firstVisibleInvalidFormInput = angular.element(firstInvalid.querySelector("input.ng-invalid,select.ng-invalid,textarea.ng-invalid")).filter(":visible")[0];
                    if (firstVisibleInvalidFormInput) {
                        firstVisibleInvalidFormInput.focus();
                    }
                }
            }
        });            
    }
}

// Register in angular app
app.directive(FormFocusOnErr.directiveId, () => new FormFocusOnErr());
0
CAK2

フィールドIDを受け取る関数(理想的にはディレクティブ)である各フォーム要素に属性を追加できます。このフィールドIDは、何らかの方法で$ errorオブジェクトに関連付ける必要があります。この関数は、idが$ errorオブジェクトにあるかどうかを確認できます。その場合、エラーの属性設定を返します。

<input id="name" class="{{errorCheck('name')}}">

エラーが発生した場合、これが生成されます。

<input id="name" class="error">

これを使用してスタイルを設定すると、どのフィールドにエラーがあるかがわかります。残念ながら、どちらが最初のフィールドかわかりません。

1つの解決策は、jQueryと.firstフィルターを使用することです。このルートに行く場合は、 http://docs.angularjs.org/api/angular.element をチェックしてください。

別の解決策は、フォームフィールドに関数のフィールド順序パラメーター{{errorCheck( 'name'、1)}}を追加することです。エラーフィールド名を配列にプッシュし、フィールド順序パラメーターで並べ替えることができます。これにより、柔軟性が向上します。

お役に立てれば。

0
Darryl