You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>&nbsp;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>&nbsp;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的下拉框重新渲染而丢失元素;而计量单位列的写法只是刚好在特定时机生效,属于不规范实现,存在潜在问题。

修复步骤

  1. 修改产品列的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>&nbsp;Registrar producto</a></li>`;
    },
    escapeMarkup: function (markup) {
        return markup;
    }
}
  1. 将点击事件绑定从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:39:51