web-dev-qa-db-fra.com

Comment afficher les erreurs de saisie de formulaire à l'aide de l'interface utilisateur AngularJS Bootstrap tooltip?

Par exemple, j'ai le formulaire où je suis montrant les erreurs de saisie du formulaire .

Je dois montrer un badge rouge (avec 'survoler pour afficher les erreurs') près de l'étiquette d'entrée s'il y a des erreurs. Si l'utilisateur survole ce badge rouge - il verra la liste des erreurs en utilisant AngularJS UI Bootstrap tooltip . Je ne veux pas mettre la liste des erreurs dans tooltip-html -unsafe attribut, car il n'est pas pratique de modifier et de maintenir.

Ce code est plus déclaratif:

<validation-tooltip ng-show="appForm.number.$invalid && appForm.number.$dirty">
    <ul>
        <li ng-show="appForm.number.$error.required">this field is required</li>
        <li ng-show="appForm.number.$error.number">should be number</li>
        <li ng-show="appForm.number.$error.min">minimum - 5</li>
        <li ng-show="appForm.number.$error.max">miximum - 20</li>
    </ul>
</validation-tooltip>

que ce code:

<span tooltip-html-unsafe="{{<ul><li>This field is required;</li><li>...</li></ul>}}">hover to show errors</span>

Comment puis-je écrire une telle directive de validation-info-bulle en utilisant AngularJS UI Bootstrap tooltip?

Ou pouvez-vous suggérer une autre approche pour conserver les messages d'erreur de validation?

20
webvitaly

violon de démonstration

Directive sur les info-bulles de validation

Le validationTooltip est la directive principale. Il a les responsabilités suivantes:

  1. Définissez le modèle d'info-bulle à travers son contenu inclus
  2. Gardez une trace des expressions de validation afin qu'elles puissent être évaluées à chaque cycle de résumé.
  3. Exposer une API de contrôleur pour permettre aux directives valiationMessage de s'enregistrer
  4. Fournissez un attribut "cible" sur la directive pour spécifier le champ de formulaire auquel le badge (et l'info-bulle associée) sera lié.

Notes supplémentaires

Le modèle d'infobulle utilise la fonction de transclusion de la fonction de lien pour lier le modèle à la portée de la directive. Le modèle peut se lier à deux propriétés à portée:

  1. $ form : lié au modèle de formulaire défini dans la portée parent. c'est-à-dire $ scope.myForm
  2. Champ $ : lié au modèle form.name dans la portée parent. c'est-à-dire $ scope.myForm.myInput

Cela permet au modèle de se lier à des propriétés de validation telles que $ valid, $ invalid, $ pristine, $ dirty et $ error sans se référer directement au nom du formulaire ou au nom du champ de saisie. Par exemple, toutes les expressions suivantes sont des expressions de liaison valides:

Propriétés de $ form:

  • `$ form. $ valid`
  • `$ form. $ invalide`
  • `$ form. $ dirty`
  • `$ form. $ pristine`
  • `$ form. $ error.required` etc ...

Propriétés du champ $:

  • `$ champ. $ valide`
  • `$ champ. $ invalide`
  • `$ field. $ dirty`
  • `$ field. $ pristine`
  • `$ field. $ error.required` etc ...

Mise en œuvre de la directive

app.directive('validationTooltip', function ($timeout) {
    return {
        restrict: 'E',
        transclude: true,
        require: '^form',
        scope: {},
        template: '<span class="label label-danger span1" ng-show="errorCount > 0">hover to show err</span>',
        controller: function ($scope) {
            var expressions = [];
            $scope.errorCount = 0;
            this.$addExpression = function (expr) {
                expressions.Push(expr);
            }
            $scope.$watch(function () {
                var count = 0;
                angular.forEach(expressions, function (expr) {
                    if ($scope.$eval(expr)) {
                        ++count;
                    }
                });
                return count;

            }, function (newVal) {
                $scope.errorCount = newVal;
            });

        },
        link: function (scope, element, attr, formController, transcludeFn) {
            scope.$form = formController;

            transcludeFn(scope, function (clone) {
                var badge = element.find('.label');
                var tooltip = angular.element('<div class="validationMessageTemplate tooltip-danger" />');
                tooltip.append(clone);
                element.append(tooltip);
                $timeout(function () {
                    scope.$field = formController[attr.target];
                    badge.tooltip({
                        placement: 'right',
                        html: true,
                        title: clone
                    });

                });
            });
        }
    }
});

Directive de message de validation

La directive validationMessage garde une trace des messages de validation à afficher dans l'info-bulle. Il utilise ng-if pour définir l'expression à évaluer. Si il n'y a pas ng-if trouvé sur l'élément, puis l'expression prend simplement la valeur true (toujours affichée).

app.directive('validationMessage', function () {
    return {
        restrict: 'A',
        priority: 1000,
        require: '^validationTooltip',
        link: function (scope, element, attr, ctrl) {
            ctrl.$addExpression(attr.ngIf || true );
        }
    }
});

Utilisation en HTML

  1. Ajouter un formulaire avec un attribut de nom
  2. Ajoutez un ou plusieurs champs de formulaire - chacun avec un attribut de nom et une directive ng-model.
  3. Déclarez un <validation-tooltip> élément avec un attribut target faisant référence au nom d'un des champs du formulaire.
  4. Appliquer le validation-message directive à chaque message avec une option ng-if expression de liaison.
<div ng-class="{'form-group': true, 'has-error':form.number.$invalid}">
    <div class="row">
        <div class="col-md-4">
            <label for="number">Number</label>
            <validation-tooltip target="number">
                <ul class="list-unstyled">
                    <li validation-message ng-if="$field.$error.required">this field is required </li>
                    <li validation-message ng-if="$field.$error.number">should be number</li>
                    <li validation-message ng-if="$field.$error.min">minimum - 5</li>
                    <li validation-message ng-if="$field.$error.max">miximum - 20</li>
                </ul>
            </validation-tooltip>
        </div>
    </div>
    <div class="row">
        <div class="col-md-4">
            <input type="number" min="5" max="20" ng-model="number" name="number" class="form-control" required />
        </div>
    </div>
</div>
38
pixelbits

@pixelbits est une excellente réponse. Je l'ai utilisé à la place:

  <div class="form-group" ng-class="{ 'has-error': form.name.$dirty && form.name.$invalid }">
    <label for="name" class="col-sm-4 control-label">What's your name?</label>
    <div class="col-sm-6">
      <input class="form-control has-feedback" id="name" name="name" 
        required
        ng-minlength="4"
        ng-model="formData.name"
        tooltip="{{form.name.$valid ? '' : 'How clients see your name.  Min 4 chars.'}}"  tooltip-trigger="focus" 
        tooltip-placement="below">
      <span class="glyphicon glyphicon-ok-sign text-success form-control-feedback" aria-hidden="true"
        ng-show="form.name.$valid"></span>
    </div>
  </div>

La technique est l'infobulle de ui-bootstrap et définit le texte de l'infobulle sur '' lorsqu'il est valide.

http://jsbin.com/ditekuvipa/2/edit

9
Michael Cole

Grande réponse de @pixelbits. J'ai utilisé ses directives et les ai légèrement modifiées pour permettre à l'infobulle de s'afficher sur l'entrée réelle comme certains utilisateurs l'ont demandé.

angular.module('app')
    .directive('validationTooltip', ['$timeout', function ($timeout) {

    function toggleTooltip(scope) {
        if (!scope.tooltipInstance) {
            return;
        }

        $timeout(function() {
            if (scope.errorCount > 0 && (scope.showWhen == undefined || scope.showWhen())) {
                scope.tooltipInstance.enable();
                scope.tooltipInstance.show();
            } else {
                scope.tooltipInstance.disable();
                scope.tooltipInstance.hide();
            }
        });
    }

    return {
        restrict: 'E',
        transclude: true,
        require: '^form',
        scope: {
            showWhen: '&',
            placement: '@',
        },
        template: '<div></div>',
        controller: ['$scope', function ($scope) {
            var expressions = [];
            $scope.errorCount = 0;
            this.$addExpression = function (expr) {
                expressions.Push(expr);
            }
            $scope.$watch(function () {
                var count = 0;
                angular.forEach(expressions, function (expr) {
                    if ($scope.$eval(expr)) {
                        ++count;
                    }
                });
                return count;

            }, function (newVal) {
                $scope.errorCount = newVal;

                toggleTooltip($scope);
            });

        }],
        link: function (scope, element, attr, formController, transcludeFn) {
            scope.$form = formController;

            transcludeFn(scope, function (clone) {

                var tooltip = angular.element('<div class="validationMessageTemplate" style="display: none;"/>');
                tooltip.append(clone);
                element.append(tooltip);
                $timeout(function () {
                    scope.$field = formController[attr.target];

                    var targetElm = $('[name=' + attr.target + ']');
                    targetElm.tooltip({
                        placement: scope.placement != null ? scope.placement : 'bottom',
                        html: true,
                        title: clone,
                    });

                    scope.tooltipInstance = targetElm.data('bs.tooltip');
                    toggleTooltip(scope);

                    if (scope.showWhen) {
                        scope.$watch(scope.showWhen, function () {
                            toggleTooltip(scope);
                        });
                    }
                });
            });
        }
    }
}]);

Le changement majeur est que la directive utilise jQuery pour trouver l'élément cible (qui devrait être une entrée) via l'attribut name et initialise l'info-bulle sur cet élément plutôt que sur l'élément de la directive. J'ai également ajouté une propriété showWhen à la portée car vous ne souhaitez pas toujours que votre info-bulle s'affiche lorsque l'entrée n'est pas valide (voir l'exemple ci-dessous).

La directive validationMessage est inchangée

angular.module('app').directive('validationMessage', function () {
    return {
        restrict: 'A',
        priority: 1000,
        require: '^validationTooltip',
        link: function (scope, element, attr, ctrl) {
            ctrl.$addExpression(attr.ngIf || true);
        }
    }
});

L'utilisation en Html est également similaire, avec juste l'ajout de showWhen si vous voulez:

<div class="form-group" ng-class="{ 'has-error' : aForm.note.$invalid && (aForm.note.$dirty) }">
    <label class="col-md-3 control-label">Note</label>
    <div class="col-md-15">
        <textarea
            name="note"
            class="form-control"
            data-ng-model="foo.Note"
            ng-required="bar.NoteRequired"></textarea>
        <validation-tooltip target="note" show-when="aForm.note.$invalid && (aForm.note.$dirty)">
            <ul class="validation-list">
                <li validation-message ng-if="$field.$error.required">Note required</li>
            </ul>
        </validation-tooltip>
    </div>
</div>
2
jstromwick

Mon objectif était de tirer parti à la fois des messages ng et du popover ui-bootstrap pour les commentaires de validation. Je préfère le popover à l'infobulle car il affiche plus clairement les styles de bloc d'aide.

Voici le code:

<!-- Routing Number -->
<div class="form-group-sm" ng-class="{ 'has-error' : form.routingNumber.$invalid && !form.routingNumber.$pristine }">
    <label class="control-label col-sm-4" for="routing-number">Routing #</label>
    <div class="col-sm-8">
        <input class="form-control input-sm text-box"
            id="routing-number"
            name="routingNumber"
            ng-model="entity.ROUTINGNUM"                        
            popover-class="help-block"
            popover-is-open="form.routingNumber.$invalid"
            popover-trigger="none"
            required
            uib-popover-template="'routing-number-validators'"
            string-to-number
            type="number" />
    </div>                
    <!-- Validators -->
    <script type="text/ng-template" id="routing-number-validators">
        <div ng-messages="form.routingNumber.$error">
            <div ng-messages-include="/app/modules/_core/views/validationMessages.html"></div>
        </div>
    </script>
</div>

Voici le validationMessages.html

<span ng-message="required">Required</span>
<span ng-message="max">Too high</span>
<span ng-message="min">Too low</span>
<span ng-message="minlength">Too short</span>
<span ng-message="maxlength">Too long</span>
<span ng-message="email">Invalid email</span>

Remarque: J'ai dû mettre à niveau vers jQuery 2.1.4 pour que la directive uib-popover-template fonctionne.

Dépendances:

1
Nathan Agersea

vous pouvez simplement utiliser la propriété tooltip-enable:

<div class="showtooltip" tooltip-placement="left" tooltip-enable="$isValid" tooltip="tooltip message"></div>
1
Greg LBD