简体   繁体   English

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

[英]Validation error message doesn't appear for custom DatePicker Angular Formly field

I'm trying to use Angular Datetime Picker as a Angular Formly input type. 我正在尝试使用Angular Datetime Picker作为Angular Formly输入类型。 I've got it working such that I can edit and set a value that is correctly added to the binded model. 我已经使它工作,我可以编辑和设置一个正确添加到绑定模型的值。

However, I can't get the validation error messages to display like on the regular input fields. 但是,我无法在常规输入字段中显示验证错误消息。

JS Bin with what I've got so far . JS Bin和我到目前为止所得到的 As you can see the red color doesn't appear when you exit the field, only when you try to submit. 正如您所看到的,当您退出该字段时,只有在您尝试提交时才会显示红色。 And the error message never shows up. 并且错误消息永远不会显示出来。

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'
});

Fields 字段

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
    },
  }
];

Templates 模板

<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>

After investigating this deeper, I can see the control you used Angular Datetime Picker is not fully compatible with Angular Formly . 在深入研究之后,我可以看到你使用Angular Datetime Picker的控件与Angular Formly不完全兼容。

This is because of the reason that it's overwriting the AngularJS's ngModelController.$render() method and hence not setting the value for $touched like other input controls. 这是因为,它的覆盖AngularJS的原因ngModelController.$render()方法,因此没有设定值$touched像其他输入控件。

Another reason in your code is, the config and the template error-messages.html are treating the custom control as single element with fc.$touched , fc.$error and fc.$viewValue whereas DatePicker is rendering as group of elements (array). 在代码的另一个原因是,在配置和模板error-messages.html被治疗定制控制与单个元件fc.$touchedfc.$errorfc.$viewValueDatePicker被渲染为元件组(阵列)。

To get rid of all these issues, you can have a custom directive to set $touched as below, 要摆脱所有这些问题,您可以使用自定义指令设置$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();
              });
            }            
         });
      }
    };
  });

And in custom-template.html , 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>

And add fc[0].$touched in below configuration to take care of array of fields, 并在下面的配置中添加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');
});

And also add below section in error-messages.html to take care of array of fields, 并在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>

This changes will fix the issue. 此更改将解决此问题。

As you can see a bit of design issue with the error message which is displayed further down, 正如您可以看到一些设计问题,错误消息进一步向下显示,

You can change the custom-template.html as below by removing the div wrapper <div class="form-group"> , 您可以通过删除div包装器<div class="form-group">来更改custom-template.html ,如下所示,

<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>

I have updated your JSBin with these changes. 我已经使用这些更改更新了您的JSBin

Snippet : 片段

 /* 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> 

You should return your validation in the formlyConfigProvider without passing it as the value for templateOptions . 您应该在formlyConfigProvider return validation ,而不将其作为templateOptions的值传递。 Return 返回

validation: {
    show: true
}

instead of 代替

templateOptions: {
    validation: {
        show: true
    }
}

Your formlyConfigProvider should look something like this: 你的formlyConfigProvider应该是这样的:

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

Here is the JSBin for the working code. 是工作代码的JSBin。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM