J'ai décidé de valider les lignes de table dynamiques une à la fois en utilisant deux écouteurs d'événements où j'ai réinitialisé la validation sur deux écouteurs d'événements;
A) sur l'entrée [type = nombre] clique, keyUp
&
B) sur la focusout d'entrée datepicker et keyUp
réinitialiser les valideurs en utilisant la ligne règles ie:
$(".element").rules('add',{required: true});
$(".element").rules('remove',"required");
à la volée.
Je crée également de nouveaux objets $("#id").datepicker()
dynamiquement après la création des nouvelles lignes.
J'ai également utilisé $(this).clone()
pour créer des lignes dynamiques lors du traitement de la ligne d'origine. Je n'ai jamais pensé que le $.clone() API serait utile mais j'ai finalement trouvé un exemple où cela devient utile. D'après ce que je comprends, les objets clonés ne sont pas liés aux écouteurs d'événement de la ligne d'origine, ce qui selon moi dépend de l'API. J'ai réellement la preuve où je clone une rangée avec un texte d'entrée imbriqué d'un Dateur de date. J'ai finalement choisi de supprimer la zone de texte d'entrée en utilisant la fonction jQuery.remove()
puis de la recréer et de la lier à l'API datepicker, puis lui ai donné un nouvel identifiant afin de le faire fonctionner correctement.Le problème est maintenant que l'événement changeDate ne lie pas le nouvel objet datepicker et je me souviens avoir tenté de supprimer l'identifiant et d'ajouter un nouvel identifiant mais l'élément était toujours lié aux événements de l'élément d'origine dont il était cloné . Par exemple, le calendrier apparaît sur l'élément d'origine ou le calendrier lui-même ne s'affiche pas selon la façon dont vous choisissez de réinitialiser le datpick imbriqué à partir de la ligne clonée. (S'il vous plaît voir le follow-up question I posted regarding the onchangeDate issue).
Regardez aussi pour quelle API datepicker vous faites référence parce qu'il ya beaucoup d'API pour cette fonctionnalité, comme Bootstrap-datepicker et jQuery-datepicker qui était le mien de Metronic et est de la jQuery-datepicker API
Je vais montrer l'échantillon codes de ceci aussi au cas où quelqu'un rencontre le même problème que moi.
NOTE: Je n'ai pas testé dans tous les navigateurs le 23/2/2017 donc je vais mettre à jour lors des tests.
Voici quelques extraits de code exemple pour $.rules(), $.datepicker(), and $.clone()
API:
Voici comment je valide et les lignes de table de processus sans utiliser la technique $("form").submit()
.
REMARQUE: Ma forme enroule autour de ma table donc j'ai une déclaration de validation, mais je ne PAS utiliser la technique form.submit()
, plutôt je processus en fonction des règles de validation dynamique I reset et sur chaque événement déclencheur.
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");
}
Voici comment je créé une nouvelle ligne vide en utilisant l'API à partir d'une ligne qui vient d'être traitée .clone $():
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;
}
Je trouve que la clé de la création dynamique datepicker est quand vous cloner un objet override l'ancien id ou même l'élément lui-même de l'objet cloné un exemple peut être vu dans la fonction ProcessRequest:
NOTE: I ha d jouer à des jeux avec l'écouteur d'événement onChangeDate afin de traiter les lignes sur datepicker close. Le seul problème est maintenant que l'écouteur d'événement onChangeDate n'est pas déclenché sur le nouvel objet cloné. J'ai essayé de délier l'événement et le lier mais pas de chance 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: J'ai essayé de créer dynamic bootstrap popout confirmation dialogs en utilisant la technique similaire que je le datepicker en utilisant Ids au hasard mais je rencontre des erreurs où il prend deux fois de cliquer sur entrée. Si je répare je posterai des résultats.
J'ai eu une erreur avec le programme datepicker où la première sélection ne s'inscrivait probablement pas parce qu'elle était imbriquée dans un événement? Après avoir fait des recherches, j'ai trouvé qu'il y avait un similar issue with angular where user had to select date twice. J'ai placé un setTimeout à l'intérieur de OnClose et il a semblé corriger le problème. Je vais utiliser la même approche avec les pop-ups et voir comment ça se passe. J'espère pouvoir aider quelqu'un!
MISE À JOUR: Merci à artemisian j'ai pu résoudre le problème avec les datepickers dynamiques! S'il vous plaît voir this après en ce qui concerne clonage datepickers.
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));
});
}
}
}
J'ai alors fait cela donc je devais code réutilisable:
/**************************** ************************************************** * Enveloppé dans la fonction pour permettre la liaison **************************************** dynamique *************************************/
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
}