克隆带事件的Eonasdan DateTimePicker时的选择器联动异常问题
解决Eonasdan DateTimePicker克隆后事件冲突的问题
看起来你遇到的核心问题是克隆元素时连带复制了原DateTimePicker的事件绑定和实例数据,导致新选择器的事件触发时总是关联到第一个元素。下面是针对性的解决方案,结合你的代码一步步调整:
问题根源分析
- 使用
.clone(true, true)会完全复制原元素的事件处理函数和jQuery数据(包括DateTimePicker的实例),这会让新元素的事件上下文还是指向原选择器,导致操作新元素时触发原元素的逻辑。 - 原事件绑定
$("input.dispDtp").on("dp.hide", ...)是静态绑定,只对页面初始化时存在的元素生效,动态添加的元素需要用事件委托才能触发事件。
具体解决方案
1. 调整克隆逻辑,不克隆事件和数据
把克隆代码中的true, true去掉,只克隆结构,不复制事件和数据:
$(".addDisp button").click(function(event) { // 只克隆DOM结构,不克隆事件和数据 const newRow = $("#dispRow0").clone().appendTo(".disp"); const sibLength = $("#dispRow0").siblings().length; // 更新新行的ID和子元素属性 newRow.prop("id", `dispRow${sibLength}`); newRow.find("div[id^='dispDtpDivInicio']").attr("class", `dispDtpDivInicio${sibLength}`); newRow.find("input[id^='inicio']") .attr("name", `dataInicio${sibLength}`) .prop("id", `inicio${sibLength}`); newRow.find("div[id^='dispDtpDivFim']").attr("class", `dispDtpDivFim${sibLength}`); newRow.find("input[id^='fim']") .attr("name", `dataFim${sibLength}`) // 修正原代码中name重复的问题 .prop("id", `fim${sibLength}`); newRow.find(".remDisp button").prop("id", `remDispBtn${sibLength}`); // 只初始化新添加的选择器,无需销毁所有再重建 initDatePickers(`inicio${sibLength}`, `fim${sibLength}`); // 初始化后立即计算一次日期差 const startDate = newRow.find(`#inicio${sibLength}`).data("DateTimePicker").date(); const endDate = newRow.find(`#fim${sibLength}`).data("DateTimePicker").date(); getDisponibilidade(startDate, endDate, `dispRow${sibLength}`); });
2. 改用事件委托绑定dp.hide事件
把原来的静态事件绑定改成委托到父容器(比如.disp),这样动态添加的元素也能触发事件:
// 事件委托:绑定到父容器,监听所有.dispDtp元素的dp.hide事件 $(".disp").on("dp.hide", "input.dispDtp", function(event) { const $currentInput = $(this); const $currentRow = $currentInput.closest("div[id^='dispRow']"); const targetDiv = $currentRow.attr("id"); // 获取当前行的开始和结束日期选择器 const $startPicker = $currentRow.find("input[id^='inicio']"); const $endPicker = $currentRow.find("input[id^='fim']"); const startDate = $startPicker.data("DateTimePicker").date(); const endDate = $endPicker.data("DateTimePicker").date(); getDisponibilidade(startDate, endDate, targetDiv); });
这里用.closest()直接获取当前行,避免了复杂的兄弟元素查找,逻辑更清晰可靠。
3. 优化日期差计算函数的DOM操作
原getDisponibilidade中用.replaceWith()会替换元素,导致后续操作可能找不到目标元素,建议改成修改元素内容而非替换:
function getDisponibilidade(startDate, endDate, targetDiv) { const $row = $(`#${targetDiv}`); const $endInput = $row.find(".fim"); const $daysSpan = $row.find(".dias"); if (endDate.isSameOrBefore(startDate, 'day month year')) { $endInput.popover('show'); } else { $endInput.popover('hide'); } const duration = Math.round(moment.duration(endDate.diff(startDate)).as('days')); let daysText = "0 days"; if (duration > 1) { daysText = `${duration} days`; } else if (duration === 1) { daysText = `${duration} day`; } // 直接修改文本内容,而非替换元素 $daysSpan.text(daysText); return duration; }
4. 移除不必要的全局销毁/重建逻辑
原来的destroyPickers和updatePickers会销毁所有选择器再重建,既低效又可能引发冲突。现在只需初始化新添加的选择器,这两个函数可以移除;如果需要删除行功能,可保留针对单个选择器的销毁逻辑:
// 可选:添加删除行的逻辑,销毁对应选择器避免内存泄漏 $(".disp").on("click", ".remDisp button", function() { const $row = $(this).closest("div[id^='dispRow']"); const rowId = $row.attr("id"); const index = rowId.replace("dispRow", ""); // 销毁当前行的选择器实例 $(`#inicio${index}`).data("DateTimePicker").destroy(); $(`#fim${index}`).data("DateTimePicker").destroy(); // 删除行 $row.remove(); });
效果验证
调整后,每个克隆的日期选择器都会独立工作,事件触发仅影响当前行的日期差计算,同时保留了所有必要的事件绑定,不会再出现操作新选择器却触发第一个选择器响应的问题。
内容的提问来源于stack exchange,提问作者Dark Inspiration
相关产品推荐
相关产品推荐

