DataTable行内双Select2无结果时仅第二个显示添加按钮问题
问题:DataTable中产品列Select2无结果时不显示添加按钮
我开发了一个DataTable,每行包含两个Select2组件,分别用于产品和计量单位的自动补全,基础功能正常。需求是当搜索无结果时,每个Select2需显示添加按钮,点击可触发模态框新增对应产品或计量单位。目前计量单位列的Select2在无结果时能正常显示按钮,但产品列的Select2无法显示。
我的实现思路是在Select2的语言定义中,通过noResults函数创建按钮并绑定点击事件,尝试过在init.dt事件、createdCell和createdRow中初始化Select2,结果都一样。
列定义摘要
"columnDefs": [ { name: "productoFactura", "targets": 1, "searchable": false, "orderable": false, width: '300px', render: function (data, type, full) { return `<input type="hidden" class="input-csrf-token" name="detalle_factura__token" value="${full['detalle_factura__token']}"/><select name="detalle_factura_producto_producto" class="selector-producto form-control-sm d-none" ><option value="${data.id}" selected>${data.descripcion}</option></select>`; } }, { name: "unidadDeMedida", "targets": 2, "searchable": false, "orderable": false, width: '150px', render: function (data, type, full) { return '<select name="detalle_factura_producto_unidad_de_medida" class="selector-unidadmedida form-control-sm d-none" ><option value="' + data.id + '" selected>' + data.descripcion + '</option></select>'; } }, // 其他列定义 ],
createdRow回调定义(用createdCell或init.dt事件结果一致)
createdRow: function (row, data, dataIndex, cells) { let tdProducto = $(row).find('td:eq(1)'); let tdUM = $(row).find('td:eq(2)'); $(row).find('select.selector-producto').select2({ theme: 'bootstrap4', width: '299px', dropdownParent: tdProducto, minimumInputLength: 1, delay: 400, ajax: { url: Routing.generate('producto_autocompletar'), processResults: function (data) { return data; } }, language: { noResults: function (term) { let g = $('#modalReader_contenedorForm').find('#prov').data('r'); if (g === '0') { return "No hay resultados"; } tdProducto.find('span.select2-results > ul.select2-results__options').append(`<li class="select2-results__options"><a class="btnProductoNuevo btn btn-block btn-default btn-xs text-secondary" data-src="${Routing.generate('producto_nuevo')}" data-mainform-name="producto" data-cerrar-dialogo="0" data-original-title="Registrar producto" data-modal-dialog-fullscreen="modal-fullscreen" data-modal-dialog-scrollable="modal-dialog-scrollable" data-modal-dialog-textsize="text-sm"><span class="fa fa-plus-circle"></span> Registrar producto</a></li>`).on('click', 'a.btnProductoNuevo', function (e) { e.preventDefault(); e.stopPropagation(); var target = $(this); const onCreateProductoFromFactura = function (data) { let newOption = new Option(data.producto.nombre, data.producto.id, true, true); tdProducto.find('select.selector-producto').append(newOption).trigger('change'); }; secondModalFormV2(target, false, null, onCreateProductoFromFactura); }); }, escapeMarkup: function (markup) { return markup; } } }).on('select2:select', function (e) { let precio = e.params.data.precio; let filaActual = $(tdProducto).parents('tr'); filaActual.find('input.input-precio:first').val(numberParser.parse(precio).toFixed(precisionPrecio)); recalcularImporteFila(filaActual); recalcularSubtotalProductos(importeFormat); }); $(row).find('select.selector-unidadmedida').select2({ theme: 'bootstrap4', dropdownParent: tdUM, placeholder: 'Unidad de Medidas', width: '149px', minimumInputLength: 1, delay: 400, ajax: { url: Routing.generate('unidadDeMedida_autocompletar'), processResults: function (data) { return data; } }, language: { noResults: function () { let g = $('#modalReader_contenedorForm').find('#prov').data('r'); if (g === '0') { return "No hay resultados"; } tdUM.find('span.select2-results > ul.select2-results__options').append('<li class="select2-results__options"><a class="btnUnidadMedidaNuevo btn btn-block btn-default btn-xs' + (g === '0' ? 'disabled' : '') + ' text-secondary" data-src="' + Routing.generate('unidadDeMedida_nueva') + '" data-mainform-name="unidad_de_medida" data-cerrar-dialogo="1" data-original-title="Registrar Unidad de Medida"><span class="fa fa-plus-circle"></span> Agregar nueva</a></li>').on('click', 'a.btnUnidadMedidaNuevo', function (e) { e.preventDefault(); e.stopPropagation(); var target = $(this); const onUnidadDeMedidaCreated = function (data) { let newOption = new Option(data.unidadDeMedida.texto, data.unidadDeMedida.id, true, true); $(tdUM).find('select.selector-unidadmedida').append(newOption).trigger('change'); }; secondModalFormV2(target, false, null, onUnidadDeMedidaCreated); }); } } }); }
实际效果
- 预期:搜索无结果时,产品列Select2显示“Registrar producto”添加按钮(与计量单位列效果一致)
- 实际:产品列Select2仅显示“No hay resultados”文本,无添加按钮
解决方案
问题根源
产品列的noResults函数没有返回HTML标记,而是直接尝试手动操作DOM追加元素。Select2的noResults函数设计要求返回要渲染的HTML字符串,手动DOM操作会因为Select2的下拉框重新渲染而丢失元素;而计量单位列的写法只是刚好在特定时机生效,属于不规范实现,存在潜在问题。
修复步骤
- 修改产品列的
noResults函数,直接返回包含添加按钮的HTML字符串,替代手动DOM追加操作:
language: { noResults: function (term) { let g = $('#modalReader_contenedorForm').find('#prov').data('r'); if (g === '0') { return "No hay resultados"; } // 直接返回带按钮的HTML字符串 return `<li class="select2-results__options"><a class="btnProductoNuevo btn btn-block btn-default btn-xs text-secondary" data-src="${Routing.generate('producto_nuevo')}" data-mainform-name="producto" data-cerrar-dialogo="0" data-original-title="Registrar producto" data-modal-dialog-fullscreen="modal-fullscreen" data-modal-dialog-scrollable="modal-dialog-scrollable" data-modal-dialog-textsize="text-sm"><span class="fa fa-plus-circle"></span> Registrar producto</a></li>`; }, escapeMarkup: function (markup) { return markup; } }
- 将点击事件绑定从
noResults函数中移出,改为全局委托绑定,避免重复绑定并适配动态生成元素:
在DataTable初始化完成后添加以下代码:
// 全局委托绑定产品添加按钮点击事件 $(document).on('click', 'a.btnProductoNuevo', function (e) { e.preventDefault(); e.stopPropagation(); var target = $(this); // 找到当前对应的Select2元素 let selectEl = target.closest('.select2-container').prev('select.selector-producto'); const onCreateProductoFromFactura = function (data) { let newOption = new Option(data.producto.nombre, data.producto.id, true, true); selectEl.append(newOption).trigger('change'); }; secondModalFormV2(target, false, null, onCreateProductoFromFactura); }); // 同步优化计量单位列的事件绑定 $(document).on('click', 'a.btnUnidadMedidaNuevo', function (e) { e.preventDefault(); e.stopPropagation(); var target = $(this); let selectEl = target.closest('.select2-container').prev('select.selector-unidadmedida'); const onUnidadDeMedidaCreated = function (data) { let newOption = new Option(data.unidadDeMedida.texto, data.unidadDeMedida.id, true, true); selectEl.append(newOption).trigger('change'); }; secondModalFormV2(target, false, null, onUnidadDeMedidaCreated); });
修复逻辑说明
- 遵循Select2的
noResults函数设计规范,返回HTML字符串让组件自动渲染内容,避免DOM操作被组件重渲染覆盖 - 全局事件委托解决了动态生成元素的事件绑定问题,同时避免了每次无结果渲染时重复绑定事件的冗余操作
内容的提问来源于stack exchange,提问作者Francisco
相关产品推荐
相关产品推荐

