自定义DatePicker Angular Formly字段不会显示验证错误消息

时间:2016-12-15 13:06:24

标签: javascript angularjs validation datetimepicker angular-formly

我尝试将Angular Datetime Picker用作Angular Formly输入类型。我已经让它工作,我可以编辑和设置一个正确添加到绑定模型的值。

但是,我无法在常规输入字段中显示验证错误消息。

JS Bin with what I've got so far。正如您所看到的,当您退出该字段时,只有当您尝试提交时,才会显示红色。并且错误消息永远不会显示出来。

Formly Config

formlyConfigProvider.setType({
  name: 'datepicker',
  templateUrl: "custom-template.html",
  overwriteOk: true,
  wrapper: ['bootstrapHasError'],
  defaultOptions: function defaultOptions(options) {
    return {
      templateOptions: {
        validation: {
          show: true
        }
      }
    };
  }
});

formlyConfigProvider.setWrapper({
  name: 'validation',
  types: ['input', 'datepicker'],
  templateUrl: 'error-messages.html'
});

字段

vm.fields = [
  {
    key: 'text',
    type: 'input',
    templateOptions: {
      label: 'Text',
      placeholder: 'Write something',
      required: true
    },
  },
  {
    key: 'date',
    type: 'datepicker',
    templateOptions: {
      label: 'Date',
      placeholder: 'Pick a date',
      required: true
    },
  }
];

模板

<script type="text/ng-template" id="custom-template.html">
  <div class="form-group">

      <label class="control-label" for="{{::id}}">{{to.label}} {{to.required ? '*' : ''}}</label>
      <div class="dropdown">
          <a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
          <div class="input-group">
              <input id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model[options.key]"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
          </div>
        </a>
        <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
          <datetimepicker
              data-ng-model="model[options.key]"
              data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
        </ul>
      </div>
  </div>

</script>

<script type="text/ng-template" id="error-messages.html">
  <formly-transclude></formly-transclude>
  <div ng-messages="fc.$error" ng-if="form.$submitted || options.formControl.$touched" class="error-messages">
    <div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc.$viewValue, fc.$modelValue, this)}}</div>
  </div>
</script>

2 个答案:

答案 0 :(得分:3)

经过深入调查后,我发现您使用的控件Angular Datetime PickerAngular Formly不完全兼容。

  

这是因为它覆盖了AngularJS   ngModelController.$render()方法,因此不设置值   对于$touched和其他输入控件一样。

     

代码中的另一个原因是配置和模板   error-messages.html将自定义控件视为单个控件   包含fc.$touchedfc.$errorfc.$viewValue的元素   DatePicker呈现为元素组(数组)。

要解决所有这些问题,您可以使用自定义指令设置$touched,如下所示

app.directive('setTouched', function MainCtrl() {
    return {
      restrict: 'A', // only activate on element attribute
      require: '?ngModel', // get a hold of NgModelController
      link: function(scope, element, attrs, ngModel) {
         if (!ngModel) return; // do nothing if no ng-model
         element.on('blur', function() {
            var modelControllers = scope.$eval(attrs.setTouched);
            if(angular.isArray(modelControllers)) {
              angular.forEach(modelControllers, function(modelCntrl) {
                modelCntrl.$setTouched();
              });
            }            
         });
      }
    };
  });

custom-template.html

<div class="input-group">
   <input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>

在下面的配置中添加fc[0].$touched以处理字段数组

app.run(function run(formlyConfig, formlyValidationMessages) {
    formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted || fc.$touched || fc[0].$touched';
    formlyValidationMessages.addStringMessage('required', 'This field is required');
});

并在error-messages.html中添加以下部分以处理字段数组

<div ng-messages="fc[0].$error" ng-if="form.$submitted || options.formControl[0].$touched" class="error-messages">
    <div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc[0].$viewValue, fc[0].$modelValue, this)}}</div>
</div>

此更改将解决问题。

正如您可以看到一些设计问题,错误信息会进一步向下显示,

您可以通过删除div包装器custom-template.html

来更改<div class="form-group">,如下所示
<script type="text/ng-template" id="custom-template.html">
    <label class="control-label" for="{{::id}}"
           uib-popover="{{options.templateOptions.desc}}"
           popover-trigger="mouseenter"
           popover-placement="top-left"
           popover-popup-delay="500"
           popover-append-to-body="true">{{to.label}} {{to.required ? '*' : ''}}</label>

          <div class="dropdown">
              <a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
              <div class="input-group">
                  <input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
              </div>
            </a>
            <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
              <datetimepicker
                  data-ng-model="model[options.key]"
                  data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
            </ul>
          </div>

    </script>

我已根据这些更改更新了您的JSBin

<强>段

/* global angular */
(function() {
  
  'use strict';

  var app = angular.module('formlyExample', ['formly', 'formlyBootstrap', 'ngAnimate', 'ngMessages', 'ui.bootstrap.datetimepicker', 'ui.dateTimeInput'], function config(formlyConfigProvider) {
    
    formlyConfigProvider.setType({
        name: 'datepicker',
        templateUrl: "custom-template.html",
        overwriteOk: true,
        wrapper: ['bootstrapHasError'],
        defaultOptions: function defaultOptions(options) {
            return {
                templateOptions: {
                    validation: {
                        show: true
                    }
                }
            };
        }
    });
    
    formlyConfigProvider.setWrapper({
      name: 'validation',
      types: ['input', 'datepicker'],
      templateUrl: 'error-messages.html'
    });

  });
  
  
  
  app.run(function run(formlyConfig, formlyValidationMessages) {
    formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted || fc.$touched || fc[0].$touched';
    formlyValidationMessages.addStringMessage('required', 'This field is required');
  });
  

  app.directive('setTouched', function MainCtrl() {
    return {
      restrict: 'A', // only activate on element attribute
      require: '?ngModel', // get a hold of NgModelController
      link: function(scope, element, attrs, ngModel) {
         if (!ngModel) return; // do nothing if no ng-model
         element.on('blur', function() {
            var modelControllers = scope.$eval(attrs.setTouched);
            if(angular.isArray(modelControllers)) {
              angular.forEach(modelControllers, function(modelCntrl) {
                modelCntrl.$setTouched();
              });
            }            
         });
      }
    };
  });
  

  app.controller('MainCtrl', function MainCtrl(formlyVersion) {
    var vm = this;
    
    vm.onSubmit = onSubmit;
    vm.model = {};
    vm.options = {};
        vm.env = {
      angularVersion: angular.version.full,
      formlyVersion: formlyVersion
    };
    
    vm.fields = [
      {
        key: 'text',
        type: 'input',
        templateOptions: {
          label: 'Text',
          placeholder: 'Write something',
          required: true
        },
      },
      {
        key: 'moretext',
        type: 'input',
        templateOptions: {
          label: 'More Text',
          placeholder: 'Write something else',
        },
      },
      {
        key: 'date',
        type: 'datepicker',
        templateOptions: {
          label: 'Date',
          placeholder: 'Pick a date',
          required: true
        },
      }
    ];
    

    vm.originalFields = angular.copy(vm.fields);

    // function definition
    function onSubmit() {
      if (vm.form.$valid) {
        vm.options.updateInitialValue();
        alert(JSON.stringify(vm.model), null, 2);
      }
    }
  });

})();
body {
  margin: 20px
}

.formly-field {
  margin-bottom: 30px;
}

.error-messages {
  position: relative;
}

.error-messages, .message {
  opacity: 1;
  transition: .3s linear all;
}

.message {
  font-size: .8em;
  position: absolute;
  width: 100%;
  color: #a94442;
  margin-top: 4px;
}

.error-messages.ng-enter.ng-enter-active,
.message.ng-enter.ng-enter-active {
  opacity: 1;
  top: 0;
}

.error-messages.ng-enter,
.message.ng-enter {
  opacity: 0;
  top: -10px;
}

.error-messages.ng-leave,
.message.ng-leave {
  opacity: 1;
  top: 0;
}

.error-messages.ng-leave-active,
.message.ng-leave-active {
  opacity: 0;
  top: -10px;
}
<!DOCTYPE html>
<html>

  <head>
    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    
    <!-- Twitter bootstrap -->
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    
    <!-- apiCheck is used by formly to validate its api -->
    <script src="//npmcdn.com/api-check@latest/dist/api-check.js"></script>
    <!-- This is the latest version of angular (at the time this template was created) -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>

    <!-- This is the latest version of formly core. -->
    <script src="//npmcdn.com/angular-formly@latest/dist/formly.js"></script>
    <!-- This is the latest version of formly bootstrap templates -->
    <script src="//npmcdn.com/angular-formly-templates-bootstrap@latest/dist/angular-formly-templates-bootstrap.js"></script>

        <script src="https://rawgit.com/angular/bower-angular-messages/v1.4.4/angular-messages.js"></script>
    <script src="https://rawgit.com/angular/bower-angular-animate/v1.4.4/angular-animate.js"></script>
    
    <!-- Moment -->
    <script src="https://cdn.rawgit.com/moment/moment/develop/min/moment-with-locales.min.js"></script>
    
    <!-- Datetime picker -->
    <script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/js/datetimepicker.js"></script>
    <script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/js/datetimepicker.templates.js"></script>
    <link href="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/css/datetimepicker.css" rel="stylesheet">
    <script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-date-time-input/master/src/dateTimeInput.js"></script>
    
   
    <title>Angular Formly Example</title>
  </head>

  <body ng-app="formlyExample" ng-controller="MainCtrl as vm">
    <div>
      <form ng-submit="vm.onSubmit()" name="vm.form" novalidate>
        <formly-form model="vm.model" fields="vm.fields" options="vm.options" form="vm.form">
          <button type="submit" class="btn btn-primary submit-button">Submit</button>
          <button type="button" class="btn btn-default" ng-click="vm.options.resetModel()">Reset</button>
        </formly-form>
      </form>
      <hr />
      <h2>Model</h2>
      <pre>{{vm.model | json}}</pre>
      <h2>Fields <small>(note, functions are not shown)</small></h2>
      <pre>{{vm.originalFields | json}}</pre>
      <h2>Form</h2>
      <pre>{{vm.form | json}}</pre>
    </div>

    <!-- Put custom templates here -->
    <script type="text/ng-template" id="custom-template.html">
    <label class="control-label" for="{{::id}}"
           uib-popover="{{options.templateOptions.desc}}"
           popover-trigger="mouseenter"
           popover-placement="top-left"
           popover-popup-delay="500"
           popover-append-to-body="true">{{to.label}} {{to.required ? '*' : ''}}</label>

          <div class="dropdown">
              <a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
              <div class="input-group">
                  <input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
              </div>
            </a>
            <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
              <datetimepicker
                  data-ng-model="model[options.key]"
                  data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
            </ul>
          </div>
    </script>
    
    <script type="text/ng-template" id="error-messages.html">
      <formly-transclude></formly-transclude>
      <div ng-messages="fc.$error" ng-if="form.$submitted || options.formControl.$touched" class="error-messages">
        <div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc.$viewValue, fc.$modelValue, this)}}</div>
      </div>
      <div ng-messages="fc[0].$error" ng-if="form.$submitted || options.formControl[0].$touched" class="error-messages">
        <div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc[0].$viewValue, fc[0].$modelValue, this)}}</div>
      </div>
    </script>

  </body>

</html>

答案 1 :(得分:0)

您应该return validation formlyConfigProvider templateOptions而不将其作为validation: { show: true } 的值传递。返回

templateOptions: {
    validation: {
        show: true
    }
}

而不是

formlyConfigProvider

您的formlyConfigProvider.setType({ name: 'datepicker', templateUrl: "custom-template.html", overwriteOk: true, wrapper: ['bootstrapHasError'], defaultOptions: function defaultOptions(options) { return { validation: { show: true } }; } }); 应该是这样的:

<script src="js/angular-messages.js"></script>

Here 是工作代码的JSBin。