Search code examples
javascriptjavaspringspring-mvcthymeleaf

how we can bind a list of a list of object using thymeleaf


I have a form where the user can add as much as he want of Table object that also can contains as much as he want of Columns object (like building tables in SQL).. I've tried the code bellow but nothing works and the form dosnt appear anymore when I've tried to bind the two lists.

Controller

@ModelAttribute("page")
    public Page getTable() {
         TableColumn column = new TableColumn();
            List<TableColumn> columns = new ArrayList<>();
            columns.add(column);

            Table table = new Table();
            table.setColumns(columns);

            List<Table> tables = new ArrayList<>();
            tables.add(table);

            Page page = new Page();
            page.setTables(tables);
        return page;
    }


    @GetMapping("/scriptsqlgenerator")
    public String viewForm(@ModelAttribute("page") Page page) {
        return "ScriptSqlNext";
    }

    @PostMapping("/scriptsqlgenerator")
    public String generateScript(@ModelAttribute("page") Page page) {
    page.tables.forEach((t) ->{
    System.out.println(t.getName());
        t.getColumns().forEach((c) -> {
        System.out.println(c.getName());
        System.out.println(c.getType());
        System.out.println(c.getIndex());
        System.out.println(c.getNotnull());
        });
    });
    }

HTML

<form th:object="${page}" class="list-group" th:action="@{/filegenerated}" method="get">
<a class="list-group-item list-group-item-action" data-toggle="collapse" data-target="#target1"> Create Table </a>
    <div id="target1" class="collapse" style="margin: 30px;">
        <div id="tablelist">
            <div class="form-inline itemtable" th:each="table, itemStat :${page.tables}">
                <div class="form-group mb-2 d-none">
                   <input th:field="*{tables[__${itemStat.index}__].id}" type="text" class="form-control">
                </div>
                <div class="form-group mb-2">
                    <input th:field="*{tables[__${itemStat.index}__].name}" type="text" class="form-control" placeholder="Table name">
                </div>
                <input type="button" class="btn btn-danger mb-2 ml-2" onclick="addRow()" value="Add column">
                <div class="table-responsive">
                    <table class="table table-bordered">
                        <thead>
                        <tr>
                            <th scope="col" class="d-none">Id</th>
                            <th scope="col">Column Name</th>
                            <th scope="col">Type</th>
                            <th scope="col">Index</th>
                            <th scope="col">Null</th>
                        </tr>
                        </thead>
                        <tbody id="columnlist">
                        <tr class="item" th:each="column,status : 
                            ${table.columns}">
                            <td><input th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].name}" type="text" class="form-control" required></td>
                            <td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].type}" id="inputState" class="form-control" required>
                                <option value="" selected 
                                 disabled>Choose</option>
                                <option th:value="${type}">int</option>
                                <option th:value="${type}">varchar</option>
                                </select>
                            </td>
                            <td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].index}" id="inputState" class="form-control" required>
                                <option value="" selected 
                                 disabled>Choose</option>
                                <option th:value="${index}">on</option>
                                <option th:value="${index}">off</option>
                                </select>
                            </td>
                            <td><select th:field="*{tables[__${itemStat.index}__].columns[__${status.index}__].notnull}" id="inputState" class="form-control" required>
                                <option value="" selected 
                                 disabled>Choose</option>
                                <option th:value="${notnull}">on</option>
                                <option th:value="${notnull}">off</option>
                                </select>
                            </td>
                        </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    <button class="btn btn-danger mb-2 text-center" type="button" id="addTable" style="margin-top: 30px;">Add table</button>
</div>

<div class="text-center">
    <button type="submit" class="btn btn-outline-danger btn-lg" style="margin- 
     top: 50px;">Generate File</button>
</div>
</form>

For the JS part I was using some codes to implement the addRow() method which will add more Columns to the Table and addTable() method that would add another Table object, but nothing was working for my case

This is my view is looking like: enter image description here *PLEASE CAN ANYONE HELP ME TO SOLVE THAT .. I REALLY NEED IT .... *


Solution

  • Your approach is okay. But you need to fix a few things.

    In the getTable method, you are setting empty lists for tables and columns. So there is nothing to iterate over in the view layer to show the form. Change to:

    @ModelAttribute("page")
    public Page getTable() {
        Column column = new Column();
        List<Column> columns = new ArrayList<>();
        columns.add(column);
    
        Table table = new Table();
        table.setColumns(columns);
    
        List<Table> tables = new ArrayList<>();
        tables.add(table);
    
        Page page = new Page();
        page.setTables(tables);
    
        return page;
    }
    

    And

    Add missing } for th:field="*{tables[__${i.index}__].name" and close this input tag.


    NOTE: I am not sure how you wanted to handle the three select inputs. I tested omitting them, meaning, keeping only Column id and name in the form, data bind without any issue in that case.

    Also I didn't check your JS, as you have mentioned that you haven't tested it yet.

    Suggestions:
    I see you are returning a view name from your POST handler. Take a look at the following article on Wikipedia.

    Post/Redirect/Get