当前位置: 动力学知识库 > 问答 > 编程问答 >

jquery - Validate dynamic table rows on event listeners

问题描述:

I have an html table that I am populating via AJAX using jQuery dataTables. I have two forms on the page the first form validates the table parameters which works fine.

The second validation wraps around the entire table and my goal is to create a custom validation using tooltips unless there is another way I can use the submit form validate approach to validate multiple input[type='number'] and a datepicker in each row:

input[type=number] events - click, keyup the input field type=number

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

How Should I trigger submission of the rows in a form?

A: Validate one row at a time where like elements use the NAME=ELEMENT

or

B: Validate the entire form using the submit form validation method?

Here is a sample table row:

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

},

{ "size" : "md",

"upper_hidden" : [],

"lower_hidden" : [1,2,3,4,5,6,7,8]

},

{ "size" : "sm",

"upper_hidden" : [],

"lower_hidden" : [1,2,3,4,5,6,7,8]

},

{ "size" : "xs",

"upper_hidden" : [],

"lower_hidden" : [1,2,3,4,5,6,7,8]

}

]

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

<td><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>5796719</td>

<td>1111111111111</td>

<td>6</td>

<td>12 OZ</td>

<td>TF ASIAN PASTA TOSS</td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="1" name="DLOCN"></td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="" name="DLOCN"></td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="" name="DLOCN"></td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="2" name="DLOCN"></td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="" name="DLOCN"></td>

<td><input type="tel" size="3" maxlength="3" class="form-control-inline" value="" name="DLOCN"></td>

<td><input type="text" size="10" id="buydate" class="dp form-control-inline xs-small" name="BUYDATE" /></td>

</tr>

</tbody>

</table>

</form>

Is it possible to use the name=value approach even though it goes against the typical DOM guidelines where you have multiple identical unique Ids/Names? I guess it doesn't matter if all rows highlight as red even though the target is the row in which the user has focus in.

NOTE:

Each row's last column have their own datepicker and if any of the other columns in that row have a value the date for that row must be supplied.

Subsequently, if the date is supplied and none of the row's other input fields aka [name='DLOCN'] in that row have a supplied value then I need an error to trigger.

Basically,

At least one input[name='DLOCN'] must have a value and that row's input[name='BUYDATE'] must have a date entered only when the user has entered either a date or a single value.

If the row has all blank fields then that row is valid as well.

Valid:

AND

Invalid:

AND

This is what I have so far

jQuery Validate:

 $("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);

}

});

}

Finally,

Would anyone suggest wrapping each row with a form and validate that way? Seems like if I did that it would go against DOM guidelines as I need to use Id to use jQuery Validate. I have seen the use of multiple unique Ids on a single page and it can work in certain circumstances.

网友答案:

I decided to validate dynamic table rows one at a time using two event listeners where I reset the validation on two event listeners;

A) on input[type=number] click, keyup

&

B) on the datepicker input focusout, and keyup

I reset the validators using the inline rules ie:

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

$(".element").rules('remove',"required"); on the fly.

I also create new $("#id").datepicker() objects dynamically after the new rows are created.

I also used $(this).clone() to create dynamic row(s) upon processing the original row. I never thought the $.clone() API would be useful but I finally found an instance where it becomes useful.

From what I understand the cloned objects are not bound to the original row's event listeners which I think depends on the API. I actually have proof where I clone a row with a nested input text of a datepicker where I chose to remove the input textbox then re-create it and bind it to the datepicker API and then gave it a new id in order to make it work correctly.

The problem now is the changeDate event is not binding to the new datepicker object and I recall that I attempted to remove the id and add a new id but the element was still bound to the original element's events that it was cloned from.

For example, the calendar pops up on the original element or the calendar itself does not display depending on how you choose to re-initialize the nested datepicker from the cloned row. (Please see the follow-up question I posted regarding the onchangeDate issue).

Also look out for which datepicker API you are referencing because there are many APIs for this functionality such as Bootstrap-datepicker and jQuery-datepicker which mine was from Metronic and is from the jQuery-datepicker API

I will show sample codes of this too in case someone comes across the same problem as me.

NOTE: I haven't tested in all browser a of 2/23/2017 so I will update upon testing.

Here is some sample code snippets for $.rules(), $.datepicker(), and $.clone() APIs:

Here is how I validate and process table rows WITHOUT using the $("form").submit() technique.

NOTE: My form wraps around my table so I do have a validate declaration, but I do NOT use the form.submit() technique, rather I process based on my dynamic validation.

function TableEventHandlers(){
    var form = $("#ITEMS");
    var FormError = $('#table_001_processing',form);
    var FormSuccess = $('#table_001_processing',form);
    $(".dp").on("changeDate",function(){
      $(this).trigger("focusout");
      console.log("onchange date");
    });
    //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');
        $(".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;      
        //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($(this).attr('max')), messages: {max: "Quantity must not be greater than " + $(this).attr("max")}});
                     else 
                        qty.rules('add',{min: parseInt($(this).attr('min')), messages: {max: "Quantity must be greater than " + $(this).attr("min")}});

                     qty.addClass("error").tooltip("enable").tooltip("show");
                     $('.item-failure').removeClass("hidden").show().html($(this).data("originalTitle"));
                }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");
            return 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");    
        }else{
            //Qtys met requirements of >= max && <= min or 0    
            buydate.removeClass("error").tooltip("disable");
            delday = qty.data("delday");
              $('.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-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.css("color","red").html("You have some errors. Please check below.").show();
            });
        },                  
        invalidHandler: function (event, validator) { //display error alert on form submit     
            FormError.css("color","red").html("You have some errors. Please check below.").show();
        },
         submitHandler: function (form) { 
            FormSuccess.css("color","green").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");
    }

Here is how I created a new empty row using the $.clone() API from a row that was just processed:

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

I found that the key to creating dynamic datepicker is when you clone an object override the old id or even the element itself of the cloned object which an example can be viewed inside the ProcessRequest function:

NOTE: I had to play some games with the onChangeDate event listener in order to process rows on datepicker close. The only problem now is that the onChangeDate event listener is not being fired on the new cloned object. I tried to unbind the event and bind it but no luck 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();

  }
}

NOTE: I have been trying to create dynamic bootstrap popout confirmation dialogs using the similar technique that I used with the datepicker using random Ids but I am having some errors where it is taking it twice to click input. If I fix I will post results.

I had an error with the datepicker where the first selection was not registering probably because it was nested inside an event? After doing some research I found that there was a similar issue with angular where user had to select date twice. I set a setTimeout inside the OnClose and it seemed to correct problem. I am going to use the same approach with the popouts and see how its goes. I hope I could help someone!

分享给朋友:
您可能感兴趣的文章:
随机阅读: