使用jQuery UI datepicker验证日期范围:事件顺序

Date range validation with jQuery UI datepicker: order of events

本文关键字:范围 事件 顺序 日期 验证 jQuery UI datepicker 使用      更新时间:2023-09-26

我正在使用Jörn Zaefferer的jQuery验证插件以及jQuery UI日期拾取器。我已经创建了一组自定义规则,用于验证开始日期先于结束日期。

我的问题是,当我有一个无效的范围,并使用日期拾取器UI来改变日期,使范围有效,我看到验证运行与旧值(从而保持字段无效)之前的onSelect回调触发日期拾取器。

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

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

小提琴来了

要重现问题,开头输入给定月份的15号,最后输入当月的7号。单击start字段,然后单击or选项卡以触发验证。字段正确地失效。现在单击start字段并选择同一个月的1号。注意控制台上此时的输出。

代码,供参考:

<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 http://stackoverflow.com/questions/22107742/jquery-validation-date-range-issue
    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 http://stackoverflow.com/questions/22107742/jquery-validation-date-range-issue
    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信号量代码是为了防止无限循环。

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

顺序应该/不重要,因为正如你所知道的,这只是初始化,而不是实现。

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

日期选择器确实更新了input值,但是没有触发验证,因为这不是验证插件期望的正常事件。验证仅在submit, keyupfocusout事件上触发,而在使用.datepicker()时都不会发生。

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

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

演示:jsfiddle.net/nkvpsumq/2/

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

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

$.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()
}