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

Thymeleaf+Spring中动态生成List<String>表单输入字段的方法

实现Thymeleaf动态生成绑定List的输入字段方案

我之前做项目时刚好碰到过一模一样的需求,硬编码固定数量的输入框确实不够灵活,结合Thymeleaf的表达式和JavaScript就能完美解决,下面给你分步骤拆解实现方案:

1. 后端DTO与Controller准备

首先确保你的PriceSearchDTO里的destinationList提前初始化,避免Thymeleaf渲染时因为空列表报错:

public class PriceSearchDTO {
    private List<String> destinationList;

    // 构造方法里初始化空列表,默认加一个空元素让页面加载时显示第一个输入框
    public PriceSearchDTO() {
        this.destinationList = new ArrayList<>();
        this.destinationList.add("");
    }

    // getter和setter省略
}

在Controller里返回页面时,确保传入的PriceSearchDTO实例是正确初始化的:

@GetMapping("/search-form")
public String showSearchForm(Model model) {
    model.addAttribute("priceSearchDTO", new PriceSearchDTO());
    return "search-form";
}

2. Thymeleaf模板基础渲染

用th:each循环渲染已有的列表元素,关键是输入框的th:field要使用索引绑定,这样Spring才能正确识别List的顺序:

<form th:action="@{/search}" th:object="${priceSearchDTO}" method="post">
    <div id="destination-container">
        <!-- 循环渲染已有的目的地输入框 -->
        <div th:each="dest, iterStat : ${priceSearchDTO.destinationList}" class="input-group mb-2">
            <input type="text" 
                   th:field="*{destinationList[__${iterStat.index}__]}" 
                   class="form-control" 
                   placeholder="输入目的地"/>
            <!-- 第一个输入框不显示删除按钮 -->
            <button type="button" th:if="${iterStat.index > 0}" class="btn btn-danger remove-btn">删除</button>
        </div>
    </div>
    <button type="button" id="add-dest-btn" class="btn btn-primary mt-2">添加目的地</button>
    <button type="submit" class="btn btn-success mt-3">提交搜索</button>
</form>

3. JavaScript动态添加/删除输入框

写一段JS逻辑实现点击按钮添加新输入框,以及删除选中的输入框,重点是删除后要更新所有输入框的索引,保证Spring能正确绑定连续的List元素:

// 绑定添加按钮事件
document.getElementById('add-dest-btn').addEventListener('click', function() {
    const container = document.getElementById('destination-container');
    const destCount = container.children.length;
    
    // 创建新的输入组div
    const newDestDiv = document.createElement('div');
    newDestDiv.className = 'input-group mb-2';
    
    // 注意输入框的name要对应destinationList[新索引]
    newDestDiv.innerHTML = `
        <input type="text" 
               name="destinationList[${destCount}]" 
               class="form-control" 
               placeholder="输入目的地"/>
        <button type="button" class="btn btn-danger remove-btn">删除</button>
    `;
    
    container.appendChild(newDestDiv);
    // 给新的删除按钮绑定事件
    bindRemoveEvent(newDestDiv.querySelector('.remove-btn'));
});

// 初始化所有删除按钮的事件
document.querySelectorAll('.remove-btn').forEach(btn => {
    bindRemoveEvent(btn);
});

// 封装删除按钮的事件逻辑
function bindRemoveEvent(btn) {
    btn.addEventListener('click', function() {
        this.parentElement.remove();
        // 删除后必须更新所有输入框的索引,避免断层导致Spring绑定失败
        updateDestIndices();
    });
}

// 更新输入框的索引值
function updateDestIndices() {
    const container = document.getElementById('destination-container');
    const inputs = container.querySelectorAll('input');
    
    inputs.forEach((input, index) => {
        // 更新name属性,这是Spring绑定的关键
        input.name = `destinationList[${index}]`;
        // 如果需要同步id(比如和th:field生成的id对应),也可以一起更新
        input.id = `destinationList${index}`;
    });
}

4. 后端接收表单数据

最后在Controller里用@ModelAttribute接收提交的DTO即可,Spring会自动把动态生成的输入框值绑定到destinationList中:

@PostMapping("/search")
public String handleSearch(@ModelAttribute PriceSearchDTO priceSearchDTO, Model model) {
    // 这里就能拿到用户输入的所有目的地列表了
    List<String> destinations = priceSearchDTO.getDestinationList();
    // 后续业务逻辑处理...
    
    model.addAttribute("destinations", destinations);
    return "search-result";
}

小提示

  • 如果是编辑场景(比如已有目的地数据要回显),只需要在Controller里给destinationList赋值已有数据即可,Thymeleaf会自动渲染对应数量的输入框
  • 可以给输入框加一些前端校验,比如不允许空值提交,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:42:55