验证事件侦听器上的动态 table 行

Validate dynamic table rows on event listeners

我有一个 html table,我正在使用 jQuery 数据表通过 AJAX 填充它。我在页面上有两种形式,第一种形式验证 table 参数,它工作正常。

第二次验证环绕整个 table,我的目标是使用工具提示创建自定义验证,除非有另一种方法我可以使用提交表单验证方法来验证多个输入[type='number'] 和每行一个日期选择器:

input[type=number] 事件 - 单击,键入输入字段 type=number

input[NAME=BUYDATE] (.hasDatePicker) 事件 - onfocusout

我应该如何触发提交表单中的行?

A: 一次验证一行,其中类似的元素使用 NAME=ELEMENT

B: 使用提交表单验证方法验证整个表单?

此 table 是一个动态订单项实用程序

这是我的带有示例行的数据表:

   <form id="ITEMS">
     <table id="table_001" class="xs-small table table-condensed" >
     <thead>
     <H5>Program: FRESH INCENTIVE</H5>
     <H5>Customer: 330-990076-033 (B/C MANISTEE CLARK)</H5>
     <p><font color="red">Delivery Days: Mon,Thu</font></p>
     <tr>
     <th></th>
     <th class="hidden">
     [
 { "size" : "lg",
     "upper_hidden" : [],
     "lower_hidden" : [1,2,3,4,5,6,7,8,9,10,11,12]
     },
     { "size" : "md",
     "upper_hidden" : [],
     "lower_hidden" : [1,2,3,4,5,6,7,8,9,10,11,12]
     },
     { "size" : "sm",
     "upper_hidden" : [3,4],
     "lower_hidden" : [1,2,5,6,7,8,9,10,11,12]
     }, 
     { "size" : "xs",
     "upper_hidden" : [3,4,5], 
     "lower_hidden" : [1,2,6,7,8,9,10,11,12]
     }
     ]
     </th>
    <th>Item</th>
    <th class='cupc'>UPC</th>
    <th>Pack</th>
    <th>Size</th>
    <th>Description</th
     <th>Mon<br>Qty</th>
     <th>Tue<br>Qty</th>
     <th>Wed<br>Qty</th>
     <th>Thu<br>Qty</th>
     <th>Fri<br>Qty</th>
      <th>Sat<br>Qty</th>
     <th>Start Date</th>
     </tr>
     </thead>
     <tbody>
   <tr role="row" class="odd"><td class="hidden-lg hidden-md"><span class="row-details row-details-close"><i class="fa fa-plus-square icon-large"></i></span></td><td class=" hidden">place holder</td><td><span class="EmptyRow itno live" title="ITEMNO:1525252" style="padding: 3px;">1525252</span></td><td><span class="UPC" title="UPC:010700807229">010700807229</span></td><td class="hidden-sm hidden-xs"><span class="pack" title="package qty:24">24</span></td><td class="hidden-sm hidden-xs"><span class="size" title="size:CT">CT</span></td><td class="hidden-xs"><span class="descrpt" title="desc:PAYDAY">PAYDAY</span></td><td><span title="Monday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between 1-99" value="" data-delday="1" data-dow="" class="qty non-day" maxlength="2"></span></td><td><span title="Tuesday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between 1-99" value="" maxlength="2" class="qty non-day" data-delday="2" data-dow=""></span></td><td><span title="Wednesday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between 1-99" value="" maxlength="2" class="qty non-day" data-delday="3" data-dow=""></span></td><td><span title="Thursday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between1-99" value="" maxlength="2" class="qty non-day" data-delday="4" data-dow=""></span></td><td><span title="Friday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between 1-99" value="" maxlength="2" class="qty delivery-day" data-delday="5" data-dow="5"></span></td><td><span title="Saturday:"><input type="number" min="1" max="99" name="QTY" title="Qty must be between1-99" value="" maxlength="2" class="qty non-day" data-delday="6" data-dow=""></span></td><td><span title="Start date for buying item"><input type="text" size="10" class="dp form-control-inline xs-small hasDatepicker" id="1" value="" name="BUYDATE" data-buydate=""><img class="ui-datepicker-trigger" src="/images/calendar.png" alt="Select a start buying date" title="Select a start buying date"></span></td></tr>
     </tbody>
     </table>
     </form>

是否可以使用 name=value 方法,即使它违背了典型的 DOM/WC3 指南(您有重复的唯一 IDs/Names)?

如果所有行都突出显示为红色,即使目标是用户关注的行,我猜它正在识别所有重复的唯一 IDs/Names。

注意:

每一行的最后一列都有自己的日期选择器,如果该行中的任何其他列有值,则必须提供该行的日期。

随后,如果提供了日期并且该行的其他输入字段的 none 也就是该行中的 [name='QTY'] 具有提供的值,那么我需要一个错误来触发。

基本上,

我有两种类型的行,分别由 类 .RecordRow.EmptyRow.

每行的 input[name='BUYDATE'] 必须仅在用户触发两个事件侦听器之一时输入有效日期。

有效:

AND

无效:

AND

这是我目前所拥有的

jQuery 验证:

 $("input").on("blur keyup", function(){
        row.children("td").each(function(){ 
           $(this).children('input').each(function () {
             if($(this).attr("name") === 'BUYDATE') && $(this).valid()){
                 //validate tds
             }
           });
        });
    });

form.validate({
        focusInvalid: false,  
        onkeyup: function(element) {  rule!!
            var element_id = $(element).attr('name');
            if (this.settings.rules[element_id]) {
                if (this.settings.rules[element_id].onkeyup !== false) {
                    $.validator.defaults.onkeyup.apply(this, arguments);
                }
            }
        },  
        rules: {
            "BUYDATE": { 
                required: { depends:function(){
                            //iterate through rows here?
                            //this only validate onn submit
                            //I guess maybe on could trigger submit onkeyup
                            //or blur?
                         }
                }
            },
            "DLOCN": { 
                required:{
                 depends: function(){
                            //iterate through rows here?
                            //this only validate onn submit
                            //I guess maybe on could trigger submit onkeyup
                            //or blur?
                }
               }
            }
        },
        messages: { // custom messages 
            "EVENT": {
                required: "Select a Program.",
                HTH_SelectValue: "Select a Program."            
            },
            "LOCN": {
                HTH_SingleLOCN: "A single location must be selected when using this option to load items."
            },
            "DLOCN": {
                required: "A customer location must be supplied when using this option to load items."
            }
        },          
        showErrors: function(errorMap, errorList) {
            FormError.hide();
            // Clean up any tooltips for valid elements
            $.each(this.validElements(), function (index, element) {
                element = $(element);
                NoError_ToolTip(element);
            });
            // Create new tooltips for invalid elements
            $.each(errorList, function (index, error) {
                element = $(error.element);
                message = error.message;
                Error_ToolTip(element,message);
                FormError.show();
            });
        },                  
        invalidHandler: function (event, validator) { //display error alert on form submit     
            success.hide();
            FormError.show();
            $(document).scrollTop( $(".form-body:first-of-type").offset().top ); 
        },
         submitHandler: function (form) {
            success.show();
            FormError.hide();
           // Submit1(form,FormError,success);
        }

    });
}

最后,

有人会建议用表格包装每一行并以这种方式进行验证吗?似乎如果我这样做会违反 DOM 准则,因为我需要使用 Id 来使用 jQuery Validate。我见过在单个页面上使用多个唯一 ID,它在某些情况下可以工作。

我决定使用两个事件侦听器一次验证动态 table 行,我在两个事件侦听器上重置验证;

A) 在输入[type=number] 上点击,keyup

&

B) 在datepicker输入focusout, and keyup

我使用内联规则重置了验证器,即:

$(".element").rules('add',{required: true});

$(".element").rules('remove',"required"); 即时。

我还在创建新行后动态创建新的 $("#id").datepicker() 对象。

我还使用 $(this).clone() 在处理原始行时创建动态行。我从没想过 $.clone() API 会有用,但我终于找到了一个有用的实例。

据我了解,克隆的对象未绑定到原始行的事件侦听器,我认为这取决于 API。我实际上有证据表明我克隆了一个带有日期选择器的嵌套输入文本的行。

我最终选择使用 jQuery.remove() 函数删除输入文本框,然后重新创建它并将其绑定到日期选择器 API 然后给它一个新的 ID 以使其工作正确。

现在的问题是 changeDate 事件没有绑定到新的 datepicker 对象,我记得我试图删除 id 并添加一个新的 id,但该元素仍然绑定到它被克隆的原始元素的事件从。

例如,日历会在原始元素上弹出,或者日历本身不会显示,具体取决于您选择如何从克隆的行中重新初始化嵌套的日期选择器。 (请参阅)。

还要注意您引用的是哪个日期选择器 API,因为有很多 API 用于此功能,例如 Bootstrap-datepicker and jQuery-datepicker,我的来自 Metronic,来自 jQuery-日期选择器API

我也会展示示例代码,以防有人遇到与我相同的问题。

注意:我还没有在 2017 年 2 月 23 日的所有浏览器中进行测试,所以我会在测试时进行更新。

下面是 $.rules(), $.datepicker(), and $.clone() API 的一些示例代码片段:

以下是我在不使用 $("form").submit() 技术的情况下验证和处理 table 行的方法。

注意: 我的表单环绕着我的 table 所以我确实有一个验证声明,但我不使用 form.submit() 技术,而是我根据我在每个事件触发器上重置和设置的动态验证规则进行处理。

function TableEventHandlers(){
    var form = $("#ITEMS");
    var FormError = $('.item-failure',form);
    var FormSuccess = $('.item-success',form);
    //used to trigger datepicker on calendar selection
    $(".hasDatepicker").on("changeDate",function(e){
       e.stopImmediatePropagation();
      $(this).trigger("focusout");
      //alert("onchange date" + $(this).val() + e.type);
    });

    //Processes a single qty at a time w/o popout
    $("#table_001").on("click keyup",".qty",function (e) {
        e.stopImmediatePropagation();
        //reset validators
        $(".qty").rules('remove','min');
        $(".qty").rules('remove','max');    
        $(".qty").rules('remove','required');
        $(".dp").rules('remove','required');
        $(".dp").rules('remove','UsaDate');
        $(".dp").removeClass("error").tooltip("disable").tooltip("hide");
        $(".qty").removeClass("error").tooltip("disable").tooltip("hide");
        var row = $(this).closest('tr');
        flag = true;
        row.find('.dp').rules('add',{required:true,messages:{required:"Must supply a start buy date."}});
        row.find('.dp').rules('add',{UsaDate:true,messages:{UsaDate:"Enter date in mm/dd/yyyy format"}});
        hasQtys = false;
        hadOtherQtys = false;
        var actualQty = parseInt($(this).val(), 10);
        var dow = $(this).data("dow");
        var qty = $();
        var num = 0;              
        var buydate = row.find(".dp");
        var delday = "";
        var quans = 0;  
        var Error = false;      
        //iterate thru tr check if multiple records 
        row.children("td").each(function(index){
            qty = $(this).find(".qty");
            if(qty.val() !== undefined){
                num = parseInt(qty.val(), 10);
                //console.log(isNaN(num)+ "index=" + index);
                if(isNaN(num))
                    num = 0;
                if(num > 0){
                    hasQtys = true;
                    if(quans > 1) 
                        hadOtherQtys = true;
                    quans++;
                }
                //Min max validation process  
                console.log(num + ">"+  +parseInt(qty.attr("max"),10) + "<"+ parseInt(qty.attr("min"),10) + "not 0 = "+ num);
                //qty out of range: min or Max attr or != 0?
                if(num != 0 && (num > parseInt(qty.attr("max"),10) || num < parseInt(qty.attr("min"),10)))
                {   
                     if(num > parseInt(qty.attr("max"),10)){        
                        qty.rules('add',{max: parseInt(qty.attr('max')), messages: {max: "Quantity must not be greater than " + qty.attr("max")}});
                     }
                     else{
                        qty.rules('add',{min: parseInt(qty.attr('min')), messages: {max: "Quantity must be greater than " + qty.attr("min")}});
                     }
                         qty.addClass("error").tooltip("enable").tooltip('show');
                        $('.item-failure').removeClass("hidden").show().html(qty.data("originalTitle"));
                      Error = true;
                }
                else
                    qty.removeClass("error").tooltip("disable").tooltip("hide");
            }//eof undefined qty
        });
        console.log("has qtys= " + hasQtys + "has hadotherQtys = " + hadOtherQtys);
        //.EmptyRow all require qtys when empty row
        if(row.find(".itno").hasClass("EmptyRow") && hasQtys === false)
        {
            row.find(".qty").rules('add',{required: true, messages: {required: "Quantity must be entered when the item request date is new"}});
            row.find(".qty").addClass("error").tooltip("enable");
            Error = true;
        }
        else
        {   //.EmptyRow has Qtys or .RecordRow
            row.find(".qty").rules('remove','required');
            //row.find(".qty").removeClass("error").tooltip("disable");
        }
        console.log("buydate valid = "  + buydate.valid() + "buydate.val = " +  buydate.val());
        //new date is valid
        if(buydate.valid() == false || buydate.val() == ""){ 
            $('.item-failure').removeClass("hidden").show().html("You have some errors. See below.");
             row.find(".dp").addClass("error").tooltip("enable");    
             Error = true;
        }
        if(Error === true)
            return true;
        else{
            //Qtys met requirements of >= max && <= min or 0    
            buydate.removeClass("error").tooltip("disable");
            delday = qty.data("delday");
            $('.item-failure').addClass("hidden").hide();
            $('.item-success').removeClass("hidden").html("Processing future order request...").show();
            ProcessRequest(row,actualQty,delday, buydate.val());
        }//eof valid date
    });//eof qty event listener

     //Iterates thru the entire row when date changed 
     //NOTE: no popout atm causes erroneous results
    $(".dp").on("keyup focusout",function (e) {
        e.stopImmediatePropagation();
        //reset validators
        $(".qty").rules('remove','min');
        $(".qty").rules('remove','max');    
        $(".qty").rules('remove','required');
        hasQtys = false;
        hadOtherQtys = false;
        var row = $(this).closest('tr');
        $(this).rules('add',{required:true,messages:{required:"Must supply a start buy date."}});
        $(this).rules('add',{UsaDate:true,messages:{UsaDate:"Enter date in mm/dd/yyyy format"}});
        var buydate = $(this);
        var num = 0;
        var dow = '';
        var delday = '';
        var quans = 0;
            flag = true;
        var qty = $();
        var Error = false;
        //console.log("dp triggered" + e.type);
        //only check date when manually entered. 
        if(e.type === "keyup" && ($(this).valid() === false || $(this).val() ===""))
        {   console.log(e.type + $(this).valid());
            $(this).addClass("error").tooltip("enable").show(); 
            $('item-failure').removeClass("hidden").html("You have some errors. See below.").show();
            Error = true;
        } 
        //check for qtys in row before processing    
        row.children("td").each(function(index){
            qty = $(this).find(".qty");
            if(qty.val() !== undefined){
                num = parseInt(qty.val(), 10);
                if(isNaN(num))
                    num = 0;
                if(num > 0){
                    hasQtys = true;
                    if(quans > 1) 
                        hadOtherQtys = true;
                    quans++;
                }
                //Min max or 0 validation process  
                console.log(num + ">"+  +parseInt(qty.attr("max"),10) + "<"+ parseInt(qty.attr("min"),10) + "not 0 = "+ num);
                 console.log(num > parseInt(qty.attr('max'),10));;
                //qty out of range: min or Max attr or != 0?
                if(num != 0 && (num > parseInt(qty.attr("max"),10) || num < parseInt(qty.attr("min"),10)))
                {   
                     if(num > parseInt(qty.attr("max"),10)){        
                        qty.rules('add',{max: parseInt(qty.attr('max')), messages: {max: "Quantity must not be greater than " + qty.attr("max")}});
                     }
                     else{
                        qty.rules('add',{min: parseInt(qty.attr('min')), messages: {max: "Quantity must be greater than " + qty.attr("min")}});
                     }
                         qty.addClass("error").tooltip("enable").tooltip('show');
                        $('.item-failure').removeClass("hidden").show().html(qty.data("originalTitle"));
                   Error = true;
                }
             }//eof undefined qty
        });
            //Empty rows require atleast one qty
            if(row.find(".itno").hasClass("EmptyRow") && hasQtys === false){
                row.find(".qty").rules('add',{required: true, messages: {required: "Quantity must be entered when the item request date is new"}});
                row.find(".qty").addClass("error").tooltip("enable");
                Error = true;
            }
            if(Error === true)
                return true;
            else{
            //Final stage of processing multiple records  
            row.children("td").each(function(){
            qty = $(this).find(".qty");      
            if(qty.val() !== undefined){
                num = parseInt(qty.val(), 10);
                if(isNaN(num))
                    num = 0;
                //console.log("buydate.valid() = " +buydate.valid());
                //console.log(qty.val() + "<="+ qty.attr("max") +qty.val() + ">="+ qty.attr("min"));
                if(buydate.valid() == "1" && buydate.val() !== "" && ((row.find(".itno").hasClass("EmptyRow") && hasQtys === true) || row.find(".itno").hasClass("RecordRow"))){
                    $('.item-failure').addClass("hidden").hide();
                    $('.item-success').removeClass("hidden").html("Processing future order requests...").show();
                    console.log("processing..");
                    ProcessRequest(row,num,delday);
                }

            }//eof qty.val undefined
          });//eof td children
         }//eof error
     });//eof event datepicker listener

    form.validate({
        focusInvalid: false, // do not focus the last invalid input
        onkeyup: function(element) { //only allow if 'onkeyup:false' is rule!!
            var element_id = $(element).attr('NAME');
            if (this.settings.rules[element_id]) {
                if (this.settings.rules[element_id].onkeyup !== false) {
                    $.validator.defaults.onkeyup.apply(this, arguments);
                }
            }
        },  
        rules: {
          //dynamic rules worked better in this instance
        },
        messages: { 
          // same with custom messages 
        },          
        showErrors: function(errorMap, errorList) { 
            // Clean up any tooltips for valid elements
            $.each(this.validElements(), function (index, element) {
                element = $(element);
                NoError_ToolTip(element);
            });
            // Create new tooltips for invalid elements
            $.each(errorList, function (index, error) {
                element = $(error.element);
                message = error.message;
                Error_ToolTip(element,message);
                FormError.html("You have some errors. Please check below.").show();
            });
        },                  
        invalidHandler: function (event, validator) { //display error alert on form submit     
            FormError.html("You have some errors. Please check below.").show();
        },
         submitHandler: function (form) { 
            FormSuccess.html("Processing request...").show();
        }

    });
    $.validator.addMethod("UsaDate", function(value, element) {
            return this.optional(element) || /^\b\d{1,2}[\/]\d{1,2}[\/]\d{4}\b/.test(value);
    }, "Please enter mm/dd/yyyy date format");
    }

以下是我如何使用 $.clone() API 从刚刚处理的行中创建一个新的空行:

var ProcessRequest = function(tr, count, dow){
        var success = $('#table_001_processing').css("color", "green").removeClass("hidden").show().html("Processing request...");
        //post vars
        var recureItemNo = tr.find(".itno").html();
        var itemCount = count;
        var dp = tr.find("[name='BUYDATE']");
        var newDate = dp.val();
        tempDate = dp.data("date");
        //clear all errors
        tr.children("td").each(function(){
           $(".qty").each(function(){
               $(this).removeClass("error").tooltip("disable").tooltip("hide"); 
           });
        });
        //insert new row because we create new row off emptyRow
        if(tr.find(".itno").hasClass("EmptyRow") && flag == true){
             console.log("this was an .EmptyRow");
            var randId = (Math.floor(Math.random() * 100 * 100)+1);
            var $clone = tr.clone();
            $clone.insertBefore(tr);
            //clear inputs 
            $clone.children("td").each(function(){
                var $input = $(this).find("input");
                $input.val("");  
            });  
            //destroy datepicker    
            var dp = $clone.find(".dp");
                dp.remove();
            //create new DatePicker(dp);
            var dpId = $('#table_001 tr').length + 1;
            $clone.find("td:eq(13) span").html("<input name='BUYDATE' class='dp form-control-inline' style='width:95px'; />");   
            var newDp = $clone.find(".dp").attr("id",dpId);
            DatePicker(newDp);

        }else{//update took place which means future distribution no matter what

        }
        //getJSON web0572 complete stuff
        //add-ons become future distributions by defaults
        if(tr.find(".itno").hasClass("EmptyRow"))
           tr.find(".itno").removeClass("EmptyRow").addClass("RecordRow").removeClass("live").addClass("future"); 
        if(tempDate > newDate) 
           tr.find(".itno").removeClass("live").addClass("future");
        if(tr.find(".itno").hasClass("future"))
           tr.addClass("pending"); 
         //Call web057s2 add all of this below: 
         console.log("hasqtys ="+hasQtys+"itemno");
          //Display successful processing        
          if(hasQtys == true){      
            console.log("processed request add or update");
            if(hadOtherQtys == true && tr.find(".itno").hasClass("RecordRow"))
                success.html("Item #" + recureItemNo + " successfully updated!");           
            else
                success.html("Item #" + recureItemNo + " successfully added!");
          }else{ 
            console.log("processed request deleted row");
            tr.remove(); 
            success.html("Item #" + recureItemNo + " for " + tempDate + " successfully removed!");
          }

          setTimeout(function(){
          success.addClass("hidden").hide().css("color","green").html("Processing...");
          }, 7000);
          flag = false;
}

我发现创建动态日期选择器的关键是当你克隆一个对象时覆盖旧的 id 甚至覆盖克隆对象的元素本身,可以在 ProcessRequest 函数中查看示例:

注意: 为了在日期选择器关闭时处理行,我不得不使用 onChangeDate 事件侦听器玩一些游戏。现在唯一的问题是 onChangeDate 事件侦听器没有在新的克隆对象上触发。我试图解除绑定事件并绑定它但没有运气 atm。

var DatePicker = function(that){
    if (jQuery().datepicker) {
    //destroy old datepicker from clone
    if(that.hasClass('hasDatepicker')){
      that.datepicker('remove');   
     }
      that.datepicker({
      showOn: "button",
      buttonImage: "/images/calendar.png",
      buttonImageOnly: true,
      buttonText: 'Select a start buying date',
      changeMonth: true,
      changeYear: true, 
      beforeShow: function() {
          setTimeout(function(){
              $('.ui-datepicker').css('z-index', 100100);
          }, 0);
      },
      onSelect: function () {
         $(this).removeClass("error").tooltip("disable").tooltip("hide");
         $('.ui-datepicker').css('z-index', -1);
         setTimeout(function(){  
          //allows date to catchup  
         },0);
     },
     onClose: function(){
        $(this).trigger("changeDate");
     },
      minDate: '+1', // The min date that can be selected, i.e. 30 days from the 'now'
      maxDate: '+1y'  // The max date that can be selected, i.e. + 1 month, 1 week, and 1 days from 'now'
      //                       HR   MIN  SEC  MILLI 
      //new Date().getTime() + 24 * 60 * 60 * 1000)
    }).datepicker();

  }
}

注意: 我一直在尝试创建 dynamic bootstrap popout confirmation dialogs 使用与使用随机 ID 的日期选择器类似的技术,但我在其中遇到了一些错误花了两次点击输入。如果我修复我会 post 结果。

我的日期选择器出现错误,第一个选择未注册可能是因为它嵌套在事件中?在做了一些研究后,我发现有一个 similar issue with angular where user had to select date twice。我在 OnClose 中设置了一个 setTimeout,它似乎解决了问题。我将对弹出窗口使用相同的方法,看看效果如何。我希望我能帮助别人!


更新: 感谢 artemisian I was able to solve the issue with the dynamic datepickers! Please see post 关于 克隆日期选择器

    var DatePicker = function(that){
    if (jQuery().datepicker) {
    //alert(that.attr('id'));
      that.datepicker({
      showOn: "button",
      buttonImage: "/images/calendar.png",
      buttonImageOnly: true,
      buttonText: 'Select a start buying date',
      changeMonth: true,
      changeYear: true, 
      beforeShow: function() {
          setTimeout(function(){
              $('.ui-datepicker').css('z-index', 100100);
          }, 0);
      },
      onSelect: function () {
        $('.item-failure').addClass("hidden").hide();
         $(this).removeClass("error").tooltip("disable").tooltip("hide");
         $('.ui-datepicker').css('z-index', -1);
         setTimeout(function(){  
          //allows date to catchup  
         },0);
     },
     onClose: function(){
        $(this).trigger("changeDate");
     },
      minDate: '+1', // The min date that can be selected, i.e. 30 days from the 'now'
      maxDate: '+1y'  // The max date that can be selected, i.e. + 1 month, 1 week, and 1 days from 'now'
      //                       HR   MIN  SEC  MILLI 
      //new Date().getTime() + 24 * 60 * 60 * 1000)
    }).datepicker();

    if($(this).hasClass("newDp")){
        $(this).bind("changeDate", function(e) { // this is the missing part in my opinion
            e.stopImmediatePropagation();
            $(this).trigger("focusout");
            alert("onchange date" + $(this).val());
        }); 
    //Dynamic binding on cloned datepickers only
    $(this).on("focusout",function(){
     RowValidation($(this));
    });
    } 
  }
}

然后我这样做了,所以我有了可重用的代码:

/******************************************** *********************************** 包装在函数中以允许动态绑定 ****************************************************** ******************************/

var RowValidation = function(that){
        //reset validators
        $(".qty").rules('remove','min');
        $(".qty").rules('remove','max');    
        $(".qty").rules('remove','required');
        hasQtys = false;
        hadOtherQtys = false;
        var row = that.closest('tr');
        that.rules('add',{required:true,messages:{required:"Must supply a start buy date."}});
        that.rules('add',{UsaDate:true,messages:{UsaDate:"Enter date in mm/dd/yyyy format"}});
        var buydate = that;
        var num = 0;
        var dow = '';
        var delday = '';
        var quans = 0;
            flag = true;
        var qty = $();
        var Error = false;
        //console.log("dp triggered" + e.type);
        //only check date when manually entered. 
        if(e.type === "keyup" && (that.valid() === false || that.val() ===""))
        {   console.log(e.type + $(that.valid());
            that.addClass("error").tooltip("enable").show();    
            $('item-failure').removeClass("hidden").html("You have some errors. See below.").show();
            Error = true;
        } 
        //check for qtys in row before processing    
        row.children("td").each(function(index){
            qty = $(this).find(".qty");
            if(qty.val() !== undefined){
                num = parseInt(qty.val(), 10);
                if(isNaN(num))
                    num = 0;
                if(num > 0){
                    hasQtys = true;
                    if(quans > 1) 
                        hadOtherQtys = true;
                    quans++;
                }
                //Min max or 0 validation process  
                console.log(num + ">"+  +parseInt(qty.attr("max"),10) + "<"+ parseInt(qty.attr("min"),10) + "not 0 = "+ num);
                 console.log(num > parseInt(qty.attr('max'),10));;
                //qty out of range: min or Max attr or != 0?
                if(num != 0 && (num > parseInt(qty.attr("max"),10) || num < parseInt(qty.attr("min"),10)))
                {   
                     if(num > parseInt(qty.attr("max"),10)){        
                        qty.rules('add',{max: parseInt(qty.attr('max')), messages: {max: "Quantity must not be greater than " + qty.attr("max")}});
                     }
                     else{
                        qty.rules('add',{min: parseInt(qty.attr('min')), messages: {max: "Quantity must be greater than " + qty.attr("min")}});
                     }
                         qty.addClass("error").tooltip("enable").tooltip('show');
                        $('.item-failure').removeClass("hidden").show().html(qty.data("originalTitle"));
                   Error = true;
                }
             }//eof undefined qty
        });
            //Empty rows require atleast one qty
            if(row.find(".itno").hasClass("EmptyRow") && hasQtys === false){
                row.find(".qty").rules('add',{required: true, messages: {required: "Quantity must be entered when the item request date is new"}});
                row.find(".qty").addClass("error").tooltip("enable");
                Error = true;
            }
            if(Error === true)
                return true;
            else{
            //Final stage of processing multiple records  
            row.children("td").each(function(){
            qty = $(this).find(".qty");      
            if(qty.val() !== undefined){
                num = parseInt(qty.val(), 10);
                if(isNaN(num))
                    num = 0;
                //console.log("buydate.valid() = " +buydate.valid());
                //console.log(qty.val() + "<="+ qty.attr("max") +qty.val() + ">="+ qty.attr("min"));
                if(buydate.valid() == "1" && buydate.val() !== "" && ((row.find(".itno").hasClass("EmptyRow") && hasQtys === true) || row.find(".itno").hasClass("RecordRow"))){
                    $('.item-failure').addClass("hidden").hide();
                    $('.item-success').removeClass("hidden").html("Processing future order requests...").show();
                    console.log("processing..");
                    ProcessRequest(row,num,delday);
                }

            }//eof qty.val undefined
          });//eof td children
         }//eof error

}