Директивы AngularJS: ng-click не запускается после размытия

DEMO

Рассмотрим следующий пример:

<input type="text" ng-model="client.phoneNumber" phone-number>
<button ng-click="doSomething()">Do Something</button>
.directive("phoneNumber", function($compile) {
  return {
    restrict: 'A',
    scope: true,
    link: function(scope, element, attrs) { 
      scope.mobileNumberIsValid = true;

      var errorTemplate = "<span ng-show='!mobileNumberIsValid'>Error</span>";

      element.after($compile(errorTemplate)(scope)).on('blur', function() {
        scope.$apply(function() { 
          scope.mobileNumberIsValid = /^\d*$/.test(element.val());
        });
      });
    }  
  };
});

Глядя на демонстрацию , если вы добавите say 'a' в конце номера телефона и нажмите кнопку doSomething() не называется. Если вы снова нажмете кнопку, вызывается doSomething().

Почему doSomething() не вызывается в первый раз? Любые идеи, как это исправить?

Примечание. Важно соблюдать валидацию при размытии.

Ответ 1

Здесь: http://jsbin.com/epEBECAy/25/edit

Как объясняется другими ответами, кнопка перемещается появлением диапазона перед событием onmouseup на кнопке, что вызывает проблему, с которой вы столкнулись. Самый простой способ выполнить то, что вы хотите, - это стиль формы таким образом, чтобы внешний вид диапазона не вызывал перемещение кнопки (это хорошая идея в целом с точки зрения UX). Вы можете сделать это, обернув элемент input в div стилем white-space:nowrap. Пока существует достаточное горизонтальное пространство для диапазона, кнопка не будет перемещаться, и событие ng-click будет работать, как ожидалось.

<div id="wrapper">
    <div style='white-space:nowrap;'>
        <input type="text" ng-model="client.phoneNumber" phone-number>
    </div>
    <button ng-click="doSomething()">Do Something</button>
</div>

Ответ 2

Объяснить

  • Используйте кнопку щелчка, событие mousedown запускается на элементе кнопки.
  • Вход включен blur, срабатывает обратный вызов размытия для проверки входного значения.
  • Если недействительно, отображается диапазон ошибок, нажатие кнопки тега вниз, таким образом, область левой кнопки курсора. Если пользователь освобождает мышь, событие mouseup не запускается. Это действует как кнопка на кнопке, но перемещается за ее пределы, прежде чем отпустить кнопку мыши, чтобы отменить нажатие кнопки. По этой причине ng-click не запускается. Поскольку mouseup событие не запускается на кнопке.

Решение

Используйте ng-pattern для динамической проверки входного значения и отображения/скрытия диапазона ошибок сразу в соответствии с свойством ngModel.$invalid.

Демо 1 http://jsbin.com/epEBECAy/14/edit

----- Обновление 1 -----

В соответствии с запросом автора обновленный ответ с другим решением.

Демо 2 http://jsbin.com/epEBECAy/21/edit?html,js

HTML

<body ng-app="Demo" ng-controller="DemoCtrl as demoCtrl">
  <pre>{{ client | json }}</pre>
  <div id="wrapper">
    <input type="text" phone-number
           ng-model="client.phoneNumber"
           ng-blur="demoCtrl.validateInput(client.phoneNumber)">
  </div>
  <button ng-mousedown="demoCtrl.pauseValidation()" 
          ng-mouseup="demoCtrl.resumeValidation()" 
          ng-click="doSomething()">Do Something</button>
</body>

Логика

Я использовал директиву ng-blur для ввода для запуска проверки. Если ng-mousedown запускается до ng-blur, обратный вызов ng-blur будет отложен до тех пор, пока ng-mouseup не будет запущен. Это достигается за счет использования службы $q.

Ответ 3

Это потому, что директива вставляет <span>Error</span> под которым находится кнопка в настоящее время, мешая месту события клика. Вы можете увидеть это, перемещая кнопку над текстовым полем, и все должно работать нормально.

EDIT:

Если вы действительно должны иметь ошибку в той же позиции и решить проблему, не создавая собственную директиву кликов, вы можете использовать ng-mousedown вместо ng-click. Это вызовет код щелчка перед обработкой события размытия.

Ответ 4

Не прямой ответ, но предложение для написания директивы по-другому (html то же самое):

http://jsbin.com/OTELeFe/1/

angular.module("Demo", [])
.controller("DemoCtrl", function($scope) {
  $scope.client = {
    phoneNumber: '0451785986'
  };
  $scope.doSomething = function() {
    console.log('Doing...');
  };
})
.directive("phoneNumber", function($compile) {

  var errorTemplate = "<span ng-show='!mobileNumberIsValid'> Error </span>";

   var link = function(scope, element, attrs) { 

    $compile(element.find("span"))(scope);

    scope.mobileNumberIsValid = true;
    scope.$watch('ngModel', function(v){
     scope.mobileNumberIsValid = /^\d*$/.test(v);
    });
  }; 

  var compile = function(element, attrs){
      var h = element[0].outerHTML;
      var newHtml = [
        '<div>',
        h.replace('phone-number', ''),
        errorTemplate,
        '</div>'
      ].join("\n");
      element.replaceWith(newHtml);
      return link;
    };

  return {
    scope: {
      ngModel: '='
    },
    compile: compile
  };
}); 

Ответ 5

Я бы предложил использовать $parsers и $setValidity во время проверки номера телефона.

app.directive('phoneNumber', function () {
    return {
        restrict: 'A',
        require: '?ngModel',
        link: function(scope, element, attrs, ctrl) { 
            if (!ctrl) return;
            ctrl.$parsers.unshift(function(viewValue) {
                var valid = /^\d*$/.test(viewValue);
                ctrl.$setValidity('phoneNumber', valid);
                return viewValue;
            });
            ctrl.$formatters.unshift(function(modelValue) {
                var valid = /^\d*$/.test(modelValue);
                ctrl.$setValidity('phoneNumber', valid);
                return modelValue;
            });

        }
    }
});

Итак, вы сможете использовать свойство $valid в поле в вашем представлении:

<form name="form" ng-submit="doSomething()" novalidate>
    <input type="text" name="phone" ng-model="phoneNumber" phone-number/>
    <p ng-show="form.phone.$invalid">(Show on error)Wrong phone number</p>
</form>

Если вы хотите показывать ошибки только на blur, вы можете использовать (здесь: AngularJS Forms - проверять поля после того, как пользователь имеет левое поле):

var focusDirective = function () {
  return {
    restrict: 'E',
    require: '?ngModel',
    link: function (scope, element, attrs, ctrl) {
      var elm = $(element);
      if (!ctrl) return;
      elm.on('focus', function () {
        elm.addClass('has-focus');
        ctrl.$hasFocus = true;
        if(!scope.$$phase) scope.$digest();
      });
      elm.on('blur', function () {
        elm.removeClass('has-focus');
        elm.addClass('has-visited');
        ctrl.$hasFocus = false;
        ctrl.$hasVisited = true;
        if(!scope.$$phase) scope.$digest();
      });
      elm.closest('form').on('submit', function () {
        elm.addClass('has-visited');
        ctrl.$hasFocus = false;
        ctrl.$hasVisited = true;
        if(!scope.$$phase) scope.$digest();
      })
    }
  }   
};
app.directive('input', focusDirective);

Итак, у вас будет свойство hasFocus, если поле теперь сфокусировано и hasVisited свойство, если это поле было размыто один или несколько раз:

<form name="form" ng-submit="doSomething()" novalidate>
    <input type="text" name="phone" ng-model="phoneNumber" phone-number/>
    <p ng-show="form.phone.$invalid">[error] Wrong phone number</p>
    <p ng-show="form.phone.$invalid 
             && form.phone.$hasVisited">[error && visited] Wrong phone number</p>
    <p ng-show="form.phone.$invalid 
            && !form.phone.$hasFocus">[error && blur] Wrong phone number</p>
    <div><input type="submit" value="Submit"/></div>
</form>

Демо: http://jsfiddle.net/zVpWh/4/

Ответ 6

Я установил его следующим образом.

<button class="submitButton form-control" type="submit" ng-mousedown="this.form.submit();" >