16

I am new to angular js. I want to show the form error in angular bootstrap popover at the right hand side of the element.I tried to create the directive and I got an element when it changes classes. But I don't have an idea how to move next.

(function(angular) {
  'use strict';
var app=angular.module('formExample', [])
  .controller('ExampleController', ['$scope', function($scope) {
    $scope.master = {};

    $scope.update = function(user) {
      $scope.master = angular.copy(user);
    };

    $scope.reset = function(form) {
      if (form) {
        form.$setPristine();
        form.$setUntouched();
      }
      $scope.user = angular.copy($scope.master);
    };

    $scope.reset();
  }]);
app.directive("alert", function(){
    return {
        restrict: 'C',
        priority: -1000,
        link: function(scope, ele, attrs, ctrl){
          scope.$watch(function() {console.log(ele.attr('class')); })
          if (ctrl) {
            console.log("applying custom behaviour to input: ", ele.attr('id'));
            // ... awesomeness here
          }
        }
    };
});
})(window.angular);

I just want to show the error message

  1. when user clicks save button(all form fields error message)
  2. blur of an element(only for the element which is lost the focus)

Here is my plnkr which I tried to get the message.

Update

Somehow I displayed the angular bootstrap popover and close button which closes the popover.

I have two issues in the current plunker.

  1. I want to display the error message inside my popover template respective to the element where it is getting opened. I need this template because I need a close button.
  2. Once I closed the popover if the field is empty and user clicks submit the popover is not opening next time. I want to display error message each and every time of submit.
svk
  • 4,513
  • 18
  • 63
  • 100
  • In my experience, `uib-popover`s were not designed to handle such dynamic content (like error validation). I tried a similar approach in a project, and it ended up not being worth the trouble of using the `uib-popover`. Instead, we just used css and `ng-class` to mimic the feel of the popovers. Then our directives only had to handle when to hide/show the "popovers" based on our form. – ryanyuyu Feb 14 '17 at 20:54
  • Please see my answer below – Tjaart van der Walt Feb 17 '17 at 04:59

2 Answers2

2

How about putting your template like this:

<script type="text/ng-template" id="myPopoverTemplate.html">
  <div class="gmePopover">
    <div class="popover-header">
      <button type="button" class="close" popover-toggle><span aria-hidden="true">&times;</span></button>
    </div>
    <div class="popover-content">
        somecontent
    </div>
  </div>
</script>

Working Plunker here.

UPDATE:

You can use angularjs foreach to loop through all errors in your form, then from there you can show the popover base on your element. Something like this: working plunker

<script type="text/javascript">
  var app=angular.module('testApp', ['ngAnimate', 'ngSanitize'], function($httpProvider) {});
  app.controller("PopoverDemoCtrl", function($scope, $http, $window) {
    $scope.validate = function() {
        var _popover;
        var error = $scope.testForm.$error;
        angular.forEach(error.required, function(field){
            var message = 'This field (' + field.$name + ') is required';
            _popover = $('#' + field.$name).popover({
              trigger: 'manual',
              title: '<span class="text-info"><strong>title</strong></span>'+
            '<button type="button" id="close" class="close" onclick="$(&quot;#' + field.$name + '&quot;).popover(&quot;hide&quot;);">&times;</button>',
              content: message,
              html: true
            });

            return $('#' + field.$name).popover("show")
        });
    };
  });
</script>
jomsk1e
  • 3,585
  • 7
  • 34
  • 59
  • I need the content for popover respective to the parent element . I dont know what is the difference between my updated plunker with yours. – svk Feb 13 '17 at 16:29
  • The difference: I placed the myPopoverTemplate.html inside a ng-template. I'm not sure what do you mean by respective to the parent element. A simplified example would help, for us to help. – jomsk1e Feb 13 '17 at 16:51
  • Ok, for example, in the given plunker , consider first textbox is username and second text box password. if user clicks submit button, without enter any values (i.e both text box is empty) , then we have to show 'Please enter username ' in first popover, and in second popover we have to show 'Please enter password'. – svk Feb 13 '17 at 20:04
  • ...Is there a way if we can find out the element already opened the popover. When I print the error object if the element didnt match the certain pattern then for same element I am getting two objects with in the error. In that case, I have to stop the element opening another popover.i.e.Required is not only the validation. – svk Feb 17 '17 at 07:44
  • not tested but I believe you want $yourElement.data()['bs.popover'].tip().hasClass('in') it will return true if the popover is visible – jomsk1e Feb 17 '17 at 11:31
2

You can create a directive that intercepts the $setSubmitted method of the FormController.

You can find more information regarding the method here

Please find working example here

When this directive intercepts the $setSubmitted method, we can notify another directive to show the validation errors in a bootstrap popover.

I am working under the following assumptions(feel free to correct me):

  • you will be using a form tag
  • on your form tag you will have ng-submit="nameOfForm.$valid && vm.onSubmit()"

The solution works with two directives:

submitNotify and popoverValidation

submitNotify notifies popoverValidation when the form is submitted, the popoverValidation directive then shows the form errors if there are any.

Directive 1 : submitNotify

directive('submitNotify', function () {
    return {
        restrict: 'A',
        require: 'form',
        controller: function SubmitNotify() { },
        link: function (scope, element, attrs, form) {                
            var $setSubmitted = form.$setSubmitted;
            form.$setSubmitted = function () {
                $setSubmitted.bind(form)();
                scope.$broadcast('onSubmitNotify');
            };
        }
    };
})

Explanation:

  • Can only be used as a attribute directive
  • Requires a form tag, or ngForm

The link function:

The link function replaces the $setSubmitted function with a callback function. The callback function notifies the popoverValidation directive that the form has been submitted.

Directive 2: popoverValidation

directive('popoverValidation', [function () {
    return {
        restrict: 'A',
        require: ['ngModel', '^submitNotify'],
        link: function (scope, element, attrs, require) {
            scope.$on('onSubmitNotify', function () {
                var ngModel = require[0];
                if (!ngModel.$valid) {
                    showPopover(ngModel.$error);
                }
            });

            function showPopover( $error) {
                var options = {
                    content: getValidationErrorsHtml($error),
                    html: true,
                    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content popover-content-errors"></div></div>',
                    title: '<span class="text-info"><strong>Error</strong></span><button type="button" data-dismiss="popover" class="close">&times;</button>',
                    trigger: 'manual'
                }
                $(element).popover(options);
                $(element).on('shown.bs.popover', hidePopover);
                $(element).popover('show');                    
            }

            function hidePopover() {
                $(this).next('.popover').find('button[data-dismiss="popover"]').click(function (e) {
                    $(element).popover('hide');
                });
            }

            function getValidationErrorsHtml($error) {
                var errors = [];

                if ($error.required) {
                    errors.push(requiredErrorMessage());
                }

                if ($error.email) {
                    errors.push(invalidEmailAddress());
                }

                var errorHtml = '<ul class="list-group">';

                for (var i = 0; i < errors.length; i++) {
                    errorHtml += '<li class="list-group-item">' + errors[i] + '</li>';
                }

                errorHtml += '</ul>';

                return errorHtml;
            }

            function requiredErrorMessage() {
                return 'This field is required';
            }

            function invalidEmailAddress() {
                return 'Please enter a valid email address';
            }
        }
    };
}]);

Explanation:

  • Can only be used as a attribute directive
  • Requires a submitNotify tag on the parent form

The link function:

  • The popoverValidation directive gets notified that the form is submitted
  • It checks if the ng-model binded property is valid
  • If not valid a popover gets displayed

Complete HTML:

<form name="myForm" ng-controller="MyFormController as vm" ng-submit="myForm.$valid && vm.onSubmit()" submit-notify="" novalidate>
    <div class="panel panel-primary">
        <div class="panel-heading">Form Validation with Popovers</div>
        <div class="panel-body">
            <div class="form-group">
                <label>First name</label>
                <input type="text" name="firstName" class="form-control" required ng-model="person.firstName" popover-validation="" />
            </div>
            <div class="form-group">
                <label>Surname</label>
                <input type="text" name="surname" class="form-control" required ng-model="person.surname" popover-validation="" />
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="email" name="email" class="form-control" ng-model="person.email" popover-validation="" />
            </div>
        </div>
        <div class="panel-footer">
            <button type="submit" class="btn btn-success">Submit</button>
        </div>
    </div>
</form>

Some CSS:

<style type="text/css">
    .popover-content-errors {
        padding:0px;
    }

    .popover-content-errors .list-group {
        margin-bottom:0px
    }

    .popover-content-errors .list-group-item {
        border-left:none;
        white-space:nowrap;
    }

    .popover-content-errors .list-group-item:first-child {
        border-top:none;
    }

    .popover-content-errors .list-group-item:last-child {
        border-bottom:none;
    }
</style>

MyFormController

controller('MyFormController', ['$scope', function ($scope) {
    var self = this;
    $scope.person = {
        email:'john.doe.com'
    }
    self.onSubmit = function () {   
        console.log('MyFormController.onSubmit');
    };
}]);
Tjaart van der Walt
  • 5,149
  • 2
  • 30
  • 50
  • ...I didn't went through completely. But I just got into the plunker and I entered the correct email address.Still the error is not disappeared. – svk Feb 17 '17 at 07:41