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

基于Vanilla JS的动态输入表单验证需求实现问询

动态表单验证功能实现与修复

需求说明

  1. 注意:第二个标签页中的price输入项是从第一个标签页添加元素时动态生成的。
  2. 具体需求:
    • 从第一个标签页添加元素后点击保存按钮,需弹出提示“存在2个空字段”。
    • 保存按钮默认禁用,向第二个标签页添加元素后启用;点击该按钮弹出“存在3个空字段”提示;所有字段填满后点击按钮弹出“字段已填满”提示。
    • 向第二个标签页的表格添加元素时,点击按钮提示对应空字段数,每新增一个元素空字段数递增(如新增后提示“存在4个空字段”)。
    • 向空input填写内容时,按钮上的空字段数递减;点击保存按钮提示“存在n个空输入框”,同时焦点定位到第一个空input。

现有问题

现有代码部分功能可用,但动态生成的input验证存在问题(如添加5次后第4个输入项为空时,验证无法正确统计),需完善实现。

修复后的完整代码

HTML代码

<html>
<head>
    <title>动态表单验证</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
</head>
<body>
<section class="section">
    <div class="tabs">
        <ul class="tabs-menu">
            <li class="is-active" data-target="first-tab"><a>标签页一</a></li>
            <li data-target="second-tab"><a>标签页二</a></li>
        </ul>
    </div>
    <div class="tab-content" id="first-tab">
        <table id="articulos" class="table">
            <thead>
                <tr>
                    <TH>id</TH>
                    <TH>产品id</TH>
                    <TH>可用数量</TH>
                    <TH>鞋码</TH>
                    <TH>添加</TH>
                    <TH>添加</TH>
                </tr>
            </thead>
            <tbody id="productos_content">
                <tr class="tr">
                    <td class="id_n">15</td>
                    <td class="cantidad_disponible">3</td>
                    <td class="talla">36</td>
                    <td class="td"><button id="15" onclick="add(15)" class="button">添加</button></td>
                </tr>
                <tr class="tr">
                    <td class="id_n">1</td>
                    <td class="cantidad_disponible">7</td>
                    <td class="talla">40</td>
                    <td class="td"><button id="1" onclick="add(1)" class="button">添加</button></td>
                </tr>
            </tbody>
        </table>
    </div>
    <div class="tab-content" id="second-tab">
        <div class="shopping-cart-items shoppingCartItemsContainer">
            <form id="form2">
                <input type="text" name="campo" id="campo" class="form-control" required>
                <input type="text" id="content" class="form-control">
                <input type="submit" id="guardardatos" value="保存" disabled />
                <table id="tabla" class="table">
                    <tr>
                        <TH>id</TH>
                        <TH>产品id</TH>
                        <TH>可用数量</TH>
                        <TH>鞋码</TH>
                        <TH>数量</TH>
                        <TH>价格</TH>
                    </tr>
                    <tbody id="carrito_content" class="tbody"></tbody>
                </table>
                <div class="float-right">
                    总计:<input type="text" id="resultado_total" value="0" disabled />
                </div>
            </form>
        </div>
    </div>
</section>
<script>
    const tabSystem = {
        init(){
            document.querySelectorAll('.tabs-menu').forEach(tabMenu => {
                Array.from(tabMenu.children).forEach((child, ind) => {
                    child.addEventListener('click', () => {
                        tabSystem.toggle(child.dataset.target);
                    });
                    if(child.className.includes('is-active')){
                        tabSystem.toggle(child.dataset.target);
                    }
                });
            });
        },
        toggle(targetId){
            document.querySelectorAll('.tab-content').forEach(contentElement=>{
                contentElement.style.display = contentElement.id === targetId ? 'block' : 'none';
                document.querySelector(`[data-target="${contentElement.id}"]`).classList[contentElement.id === targetId ? 'add' : 'remove']('is-active');
            })
        },
    };
    tabSystem.init()
</script>
</body>
</html>

JavaScript代码

function updateCantidadNuevo(input) {
    if(input.checkValidity()){
        input.setAttribute("data-oldvalue", input.value);
    } else {
        input.value = input.getAttribute("data-oldvalue") || input.min;
    }
}

function add(id, cantidad=1){
    let input = tbody.querySelector('.ItemCarrito>.form__element-input_cantidad>#shoppingCartItemQuantity_'+id);
    if (input === null) {
        return;
    }
    if (typeof input.value === 'undefined' || input.value === null) {
        input.value = cantidad;
    } else {
        input.value = (input.valueAsNumber + cantidad);
    }
    updateCantidadNuevo(input);
    validarFormulario();
}

const tbody = document.querySelector('#second-tab >.shoppingCartItemsContainer > #form2 >#tabla >.tbody')
const addToShoppingCartButtons = document.querySelectorAll('#first-tab > #articulos > tbody > tr > .td > .button')
const form = document.getElementById("form2");
const botonGuardar = document.getElementById("guardardatos");

addToShoppingCartButtons.forEach((addToCartButton) => {
    addToCartButton.addEventListener('click', addToCartClicked);
});

function addToCartClicked(event) {
    const button = event.target;
    const item = button.closest('.tr');
    const id_prod = item.querySelector('.id_n').textContent;
    const cantidad_disponible = item.querySelector('.cantidad_disponible').textContent;
    const talla = item.querySelector('.talla').textContent;
    addItemToShoppingCart(id_prod, cantidad_disponible, talla);
}

function addItemToShoppingCart(id_prod,cantidad_disponible,talla) {
    const elementsTitle = document.getElementsByClassName('title');
    for (let i = 0; i < elementsTitle.length; i++) {
        if (elementsTitle[i].innerText === id_prod) {
            return;
        }
    }
    const tr = document.createElement('tr')
    tr.classList.add('ItemCarrito')
    tr.setAttribute("id", `tr_${id_prod}`);
    const Content = `
        <th scope="row">${id_prod}</th>
        <td class="table__productos">
            <input id="id_productos_${id_prod}" type="text" value="${id_prod}" class="id_productos"
                style="border: none; border-color: transparent;outline: 0;width: 40px;">
            <h6 id="title_${id_prod}" class="title" hidden>${id_prod}</h6>
        </td>
        <td class="table__cantidad_disponible">
            <input id="cantidad_disponible_${id_prod}" type="text" value="${cantidad_disponible}" class="cantidad_disponible"
                style="border: none; border-color: transparent;outline: 0;width: 40px;" >
        </td>
        <td class="table__talla">
            <input id="talla_${id_prod}" type="text" value="${talla}" class="talla"
                style="border: none; border-color: transparent;outline: 0;width: 40px;">
        </td>
        <td class="form__element-input_cantidad">
            <input id="shoppingCartItemQuantity_${id_prod}" max="${cantidad_disponible}"  class="shopping-cart-quantity-input cantidad" type="number"
                value="1" min="1">
        </td>
        <td class="form__element-input_precio">
            <input id="precio_${id_prod}" type="text" class="precio" min="0">
        </td>
        <td class="table__delete">
            <button class="delete btn btn-danger">x</button>
        </td>
    `
    tr.innerHTML = Content 
    tbody.append(tr);
    // 给新元素绑定事件
    const newInputs = tr.querySelectorAll('input');
    newInputs.forEach(input => {
        input.addEventListener('input', validarFormulario);
    });
    tr.querySelector('.delete').addEventListener('click', () => {
        tr.remove();
        validarFormulario();
    });
    validarFormulario();
}

// 表单提交逻辑
form.addEventListener("submit", (event) => {
    event.preventDefault();
    const camposVacios = getEmptyFieldsCount();
    const firstEmptyInput = getFirstEmptyInput();
    if (camposVacios > 0) {
        alert(`存在${camposVacios}个空输入框`);
        firstEmptyInput?.focus();
    } else {
        alert("字段已填满");
    }
});

// 统计空字段数量
function getEmptyFieldsCount() {
    let count = 0;
    form.querySelectorAll("input").forEach(input => {
        if (input.value.trim() === "") count++;
    });
    return count;
}

// 获取第一个空输入框
function getFirstEmptyInput() {
    for (const input of form.querySelectorAll("input")) {
        if (input.value.trim() === "") return input;
    }
    return null;
}

// 更新按钮状态与显示文本
function validarFormulario() {
    const camposVacios = getEmptyFieldsCount();
    const hasItems = tbody.querySelectorAll('.ItemCarrito').length > 0;
    if (camposVacios > 0) {
        botonGuardar.disabled = !hasItems;
        botonGuardar.value = `存在${camposVacios}个空字段`;
    } else {
        botonGuardar.disabled = false;
        botonGuardar.value = "保存";
    }
}

// 初始化绑定事件
document.querySelectorAll("#form2 input").forEach(input => {
    input.addEventListener('input', validarFormulario);
});

// 初始验证
validarFormulario();

关键修改说明

  1. 动态元素事件绑定:在添加新表格行后,给所有动态生成的input绑定输入事件,同时给删除按钮绑定移除事件,确保元素变化后能实时触发验证。
  2. 实时输入框统计:每次验证时重新获取所有输入框,避免缓存导致动态添加的元素无法被统计。
  3. 按钮启用逻辑:只有当表格中存在添加的元素时,才根据空字段数决定按钮状态,满足“默认禁用,添加元素后启用”的需求。
  4. 提交交互优化:点击保存按钮时,精准弹出空字段提示,并自动定位到第一个空输入框。
  5. 代码拆分复用:将空字段统计、空输入框查找拆分为独立函数,提升代码可读性和维护性。

内容的提问来源于stack exchange,提问作者FelipeDeveloper

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:04:52