使用 jQuery UI 日期选择器进行日期范围验证:事件顺序

Date range validation with jQuery UI datepicker: order of events

我将 Jörn Zaefferer 的 jQuery 验证插件与 jQuery UI 日期选择器一起使用。我创建了一组自定义规则来验证开始日期是否早于结束日期。

我遇到的问题是,当我有一个无效范围并使用日期选择器 UI 更改日期以使范围有效时,我看到验证 运行 旧值(和从而使字段无效)在日期选择器的 onSelect 回调触发之前。

我希望日期选择器会在用户 select 时更新输入的值,并且任何验证代码都会 运行 发生这种情况并看到新值。但这似乎并没有发生。

我已经尝试在初始化验证之前初始化日期选择器,希望事件的连接顺序会有所不同,但你可以看到它没有帮助。

Here's the fiddle

要重现问题,请在开头输入给定月份的 15 号,在结尾输入同月的 7 号。单击开始字段,然后单击或跳出以触发验证。这些字段正确地失效。现在单击开始字段和 select 同月 1 日。注意此时控制台上的输出。

代码,供参考:

HTML

<form id="daterange-form">
  <div class="form-group">
      <label for="startDate">Start Date</label>
      <input type="text" id="startDate" name="startDate" class="validDate form-control" />
  </div>

  <div class="form-group">
      <label for="endDate">End Date</label>
      <input type="text" id="endDate" name="endDate" class="validDate form-control" />
  </div>
  <button type="submit" class="btn">Submit</button>
</form>

JavaScript

// Custom Rules
$.validator.addMethod('dateBefore', function(value, element, params) {
  // if end date is valid, validate it as well
  console.log('dateBefore', value, element, params)
  var end = $(params);
  if (!end.data('validation.running')) {
    $(element).data('validation.running', true);
    // The validator internally keeps track of which element is being validated currently.  This ensures that validating 'end' will not trample 'start'
    // see 
    setTimeout($.proxy(
      function() {
        this.element(end);
      }, this), 0);
    // Ensure clearing the 'flag' happens after the validation of 'end' to prevent endless looping
    setTimeout(function(){
      $(element).data('validation.running', false);
    }, 0);
  }
  return this.optional(element) || this.optional(end[0]) || new Date(value) < new Date(end.val());    
}, 'Must be before its end date');

$.validator.addMethod('dateAfter', function(value, element, params) {
  // if start date is valid, validate it as well
  console.log('dateAfter', value, element, params)
  var start = $(params);
  if (!start.data('validation.running')) {
    $(element).data('validation.running', true);
    // The validator internally keeps track of which element is being validated currently.  This ensures that validating 'end' will not trample 'start'
    // see 
    setTimeout($.proxy(
      function() {
        this.element(start);
      }, this), 0);
    // Ensure clearing the 'flag' happens after the validation of 'end' to prevent endless looping
    setTimeout(function() {
      $(element).data('validation.running', false);
    }, 0);
  }
  return this.optional(element) || this.optional(start[0]) || new Date(value) > new Date($(params).val());    
}, 'Must be after its start date');


// Code setting up datepicker and validation
$('#startDate, #endDate').datepicker({
  onSelect: function(dateText, inst) {
    console.log('onSelect', dateText, inst)
  }
});
$('#daterange-form').validate({
  debug: true,
  rules: {
    startDate: {dateBefore: '#endDate'},
    endDate: {dateAfter: '#startDate'}
  }
});

旁注:规则中的超时和代理调用是因为此版本的库在内部采用串行验证。如果您尝试验证规则中间的另一个字段,就会发生坏事。 validation.running信号量代码是为了防止无限循环。

I've tried initializing the datepicker before initializing validation in hopes that the order the events were wired in would make the difference, but you can see it hasn't helped.

顺序 should/would 无关紧要,因为如您所知,这只是初始化,而不是实现。

I would expect that the datepicker would update the input's value when the user selects, and that any validation code would run when that happens and see the new value. But that doesn't seem to happen.

日期选择器确实会更新 input 值,但是不会触发验证,因为这不是验证插件所期望的正常事件。验证仅在 submitkeyupfocusout 事件上触发,其中 none 在使用 .datepicker().

时发生

因此,您可以随时使用 the .valid() method 以编程方式强制执行验证测试...

$('#startDate, #endDate').datepicker({
    onSelect: function(dateText, inst) {
        $(this).valid();
    }
});

演示:jsfiddle.net/nkvpsumq/2/

.valid() 可以附加到个人 inputselecttextarea 或整个 form。附加到包含多个对象的选择器时,必须使用 jQuery .each() 将此方法括起来。在您的情况下,不需要 .each() 因为您已经在自定义规则中使用 this.element() 以编程方式重新触发对立字段的验证。现在您已经了解如何通过 .datepicker() 事件触发 .valid() 方法,您可能希望稍微重构一下其余代码。

这是我最终用来解决问题并保持干燥的代码。

$.datepicker._selectDate_super = $.datepicker._selectDate; 
$.datepicker._selectDate = function(id, dateStr) {
    $.datepicker._selectDate_super(id, dateStr);
    $(id).valid();
};    

(或者如果你更喜欢 es6 和 ASI ;)

$.datepicker._selectDate_super = $.datepicker._selectDate 
$.datepicker._selectDate = (id, dateStr) => {
    $.datepicker._selectDate_super(id, dateStr)
    $(id).valid()
}