jquery + thymeleaf = how to dynamically add forms with the object fields

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

jquery + thymeleaf = how to dynamically add forms with the object fields

Sadek
This post was updated on .
I am struggling with a problem. I am trying to dynamically add row to my form. My first row (static) of form is working fine, but when I add another one, the form is being added but I cannot obtain any value, like it would stay empty. Please help

OK it is my form:
<div th:object="${risk}" >

    <h2>Risk management</h2>
        <table class="table table-striped" id="myTable">
            <tr>
                <th>Type of work</th>
                <th>Type of threat</th>
                <th>Person</th>
                <th>Intial risk</th>
                <th>Countermeasure</th>
                <th>Final risk</th>
            </tr>
            <tr>

                <th><input type="text" th:field="*{typeOfWork}" /> </th>
                <th><input type="text" th:field="*{typeOfThreat}" /> </th>
                <th><input type="text" th:field="*{person}" /> </th>
                <th><input type="text" th:field="*{initialRisk}" /> </th>
                <th><input type="text" th:field="*{countermeasure}" /> </th>
                <th><input type="text" th:field="*{finalRisk}" /> </th>

            </tr>
            <button type="button" onclick="addFields()">Insert new row</button>
        </table>
 
When I add row by using jQuery, row is added but the values written in form are not being sent to Model by Thymeleaf
<script th:inline="javascript">

    function addFields()
    {
        document.getElementById("myTable").insertRow(-1).innerHTML = 
            '                <th><input type="text" th:field="*{typeOfWork}" /> </th>\n' +
            '                <th><input type="text" th:field="*{typeOfThreat}" /> </th>\n' +
            '                <th><input type="text" th:field="*{person}" /> </th>\n' +
            '                <th><input type="text" th:field="*{initialRisk}" /> </th>\n' +
            '                <th><input type="text" th:field="*{countermeasure}" /> </th>\n' +
            '                <th><input type="text" th:field="*{finalRisk}" /> </th>';
    }

</script>

Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

Reiju
This post was updated on .
1. If u need store many values - u should pass collection of objects.
2. Itarate this collection in your form:

<tr th:each="risk, status : ${someCollectionWrapper.risks}">
        <td><input th:field="*{risks[__${status.index}__].typeOfWork}"/></td>
        <td><input th:field="*{risks[__${status.index}__].typeOfThreat}"/></td>
        <td><input th:field="*{risks[__${status.index}__].person}"/></td>
        <td><input th:field="*{risks[__${status.index}__].initialRisk}"/></td>
        <td><input th:field="*{risks[__${status.index}__].countermeasure}"/></td>
        <td><input th:field="*{risks[__${status.index}__].finalRisk}"/></td>
</tr>

3. As u can notice - thymeleaf turns
<input th:field="*{risks[__${status.index}__].finalRisk}"/>
  into
<input id="risks0.finalRisk" name="risks[0].finalRisk">
 

4. So in your js-function u need to create element like this:
<input id='listName + rowIndex + . + fieldName' name='listName[rowIndex].fieldName'/>
for each field in new row


PS: see this
Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

Sadek
thanks for your help, I've corrected inputs:

<tr th:each="risk, status : ${riskWrapper.riskList}">
            <td><input th:field="*{riskList[__${status.index}__].etapPrac}"/></td>
            <td><input th:field="*{riskList[__${status.index}__].rodzajZagrozenia}"/></td>
            <td><input th:field="*{riskList[__${status.index}__].zagrozonaOsoba}"/></td>
            <td><input th:field="*{riskList[__${status.index}__].wstepneRyzyko}"/></td>
            <td><input th:field="*{riskList[__${status.index}__].przeciwdzialanie}"/></td>
            <td><input th:field="*{riskList[__${status.index}__].koncoweRyzyko}"/></td>
        </tr>

GET and POST are working properly.

In my controller I have:

 @GetMapping("/riskForm")
    public String showCreateRiskForm(@ModelAttribute RiskWrapper riskWrapper,Risk risk,  Model model) {

        riskWrapper.setRiskList(Arrays.asList(risk));

        return "riskForm";
    }

I've tried create JS function as you suggested:
 <input id='listName + rowIndex + . + fieldName' name='listName[rowIndex].fieldName'/> 
, but I get error about 'rowIndex', I don't know how to create it properly
Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

Reiju
This post was updated on .
rowIndex == count of elements in collection

For examle - 5 elems (indexes 0, 1, 2, 3, 4). That means, rowIndex of next elem - will be 5.

1. You can just calculate count of existing rows (without heading)
let rowIndex = document.getElementById('myTable').getElementsByTagName('tr').length - 1;

2. Create input using this rowIndex
let createInput = function (listName, rowIndex, fieldName) {
    let input = document.createElement('input');
    input.id = listName + rowIndex + '.' + fieldName;
    input.setAttribute('name', listName + '[' + rowIndex + '].' + fieldName);
    return input;
};

3. Append input to row and table (I didn't test this function. This is ONLY an example of how it might look like)
let addRow = function () {
    let fieldNames = ['etapPrac', 'rodzajZagrozenia', 'zagrozonaOsoba', 'wstepneRyzyko', 'przeciwdzialanie', 'koncoweRyzyko'];
    let listName = 'riskList';
    let table = document.getElementById('myTable');
    let rowIndex = table.getElementsByTagName('tr').length - 1;

    let tr = document.createElement('tr');
    fieldNames.forEach((fieldName) => {
        let td = document.createElement('td');
        let input = createInput(listName, rowIndex, fieldName);
        td.appendChild(input);
        tr.appendChild(td);
    });
    table.appendChild(tr);
};
Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

janejc
I have a similar problem when I try to access the input in a form, but I can't understand the replays.

In my controller I have a model.addAttribute("peliculas", new ArrayList<Pelicula>()) and I want to put the information in a form like this:

<table>
	            <tr>
	                <td><label >Title: </label></td>
	                <td><input type="text" th:field="*{title}" /></td>
	            </tr>
	            <tr>
	                <td><label>Info: </label></td>
	                <td><input type="text" th:field="*{info}" /></td>
	            </tr>
                    <tr>
	                <td>
	    			<input type="submit" value="Aceptar">
	                </td>
	            </tr>
</table>


But I don't want to use th:each="p, status : ${peliculas}" because I want to put the values one by one because I want to show then in the page one by one.


Can you help me, please? I know that the previous code has no sense because I have a ArrayList but I try with th:field="*{peliculas[__${1}__].title}" and its doesn't work...
Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

Reiju
This post was updated on .
1. U can pass 2 objects: object that will be used in form, and result list
@GetMapping("/movie/add")
public String addMovieForm(@ModelAttribute("movie") Movie movie, Model model) {
        model.addAttribute("movies", movieService.getAllMovies());
        return "moviesPage";
}

@PostMapping("/movie/add")
public String addMovie(@ModelAttribute("movie") Movie movie) {
        movieService.addMovie(movie);
        return "redirect:/movie/add";
}

2. View
<div>
    <form th:action="${'/movie/add'}" method="post" th:object="${movie}">
        <div>
            <title>Title</title>
            <input type="text" th:field="*{title}"/>
        </div>
        <div>
            <title>Info</title>
            <input type="text" th:field="*{info}"/>
        </div>
        <div>
            <button type="submit">Add</button>
        </div>
    </form>
</div>
<div>
    <table>
        <tr>
            <th>Title</th>
            <th>Info</th>
        </tr>
        <tr th:each="m : ${movies}">
            <td th:text="${m.title}"></td>
            <td th:text="${m.info}"></td>
        </tr>
    </table>
</div>

If i misunderstood you, please describe more your situation.
Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

janejc
Thank you for your help. And sorry because I didn't explain my issue very well.

I am using an ArrayList for the movies so I need to specify an index on the html page, but I don't know how to do it, because it doesn't work.

Really, my case is a little bit more complex. I have the next method for create the form:

        @RequestMapping(value = "/formadd", method = RequestMethod.GET)
        public String addForm(Model m1, Model m2) {

                m1.addAttribute("catalogo", new Catalogo());
                m2.addAttribute("peliculas", new ArrayList<Pelicula>());

                return "formadd";
        }

I want to create a movies catalog, so in the html I have two forms, one for create the catalog and another for add a list of movies to the current catalog. I think that the catalog form works ok (I can't be sure yet because to submit the catalog form I have to submit before the movies form because is not possible to have a catalog without movies).
In peliculaService I have the method "insertarCatalog" where I insert a new catalog (I need the catalog's id for the movies) and the in a for I iterate and add the movies to the catalog.

In the controller, when I click on submit, I have the next method:

        @RequestMapping(value = "/submitcatalog", method = RequestMethod.POST)
        public String addCatalog(@ModelAttribute("catalogo") Catalogo c, @ModelAttribute("peliculas") List<Pelicula> pelis)
                        throws InstantiationException {

                peliculaService.insertarCatalogo(c, pelis);

                return "submit";
        }

Here is the form to create a catalog:

<form th:action="@{/submitcatalog}" th:object="${catalogo}" method="post">
	            <tr>
	                <td><label th:text="#{nombre}">Nombre: </label></td>
	                <td><input type="text" th:field="*{nombre}" /></td>
	            </tr>
	             <td>
	    			<input type="submit" value="Aceptar">
	                </td>

	</form>

I want to click on a "add" button on the movies form in order to add this movie to the catalog. So when I press this button, I can to add another movie, and when I finish to add movies to the catalog, I have to press "submit" on the catalog form to insert all the data on the database (the catalog's id is generated by hibernate, and the movie's id is generated by me on the service).

I hope you can understand me, and thank you so much!

Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

Reiju
There is no need in 2 models and 2 form. U can do next:

1. Objects
public class Catalog {
    private Long id;
    private Long name;
    private List<Movie> movies;
}

public class Movie {
    private Long id;
    private String title;
    private String info;
}

2. Controller
@Controller
public class CatalogController {

    @ModelAttribute("catalog")
    public Catalog getCatalog() {
        Catalog catalog = new Catalog();
        catalog.setMovies(new ArrayList<>());
        return catalog;
    }

    @GetMapping("/addCatalog")
    public String addCatalogForm(@ModelAttribute("catalog") Catalog catalog) {
        return "addCatalogForm";
    }

    @PostMapping("/addCatalog")
    public String addCatalog(@ModelAttribute("catalog") Catalog catalog) {
        //some logic
    }
}


3. Html (i used bootstrap4, but u can do it with tables)
<form th:object="${catalog}" method="post" th:action="${'/addCatalog'}">
       
<!--INPUT FIELDS-->
        <div class="row">
            <div class="col">

                <!--CATALOG FIELDS-->
                <div>
                    <div class="row">
                        <div class="col form-group">
                            <label for="catalogName">catalogName</label>
                            <input th:field="*{name}" type="text" class="form-control" id="catalogName">
                        </div>
                    </div>
                </div>

                <!--LIST OF MOVIES-->
                <div id="movieList">
                    <div class="row">
                        <div class="col form-group d-none">id</div>
                        <div class="col form-group">title</div>
                        <div class="col form-group">info</div>
                    </div>
                    <div class="row item" th:each="movie, status : ${catalog.movies}">
                        <div class="col form-group d-none">
                            <input th:field="*{movies[__${status.index}__].id}" type="text" class="form-control">
                        </div>
                        <div class="col form-group">
                            <input th:field="*{movies[__${status.index}__].title}" type="text" class="form-control">
                        </div>
                        <div class="col form-group">
                            <input th:field="*{movies[__${status.index}__].info}" type="text" class="form-control">
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <!--ADD NEW ROW BUTTON-->
        <div class="row">
            <div class="col">
                <button type="button" class="btn btn-success" onclick="addRow()">Add row</button>
            </div>
        </div>

        <!--SUBMIT FORM BUTTON-->
        <div class="row text-right">
            <div class="col">
                <button type="submit" class="btn btn-primary">Submit</button>
            </div>
        </div>
        
    </form>

4. JS (for addRow button, some css classes bootstrap-specific, except '.item' class)
let addRow = function () {
        let listName = 'movies'; //list name in Catalog.class
        let fieldsNames = ['id', 'title', 'info']; //field names from Movie.class
        let rowIndex = document.querySelectorAll('.item').length; //we can add mock class to each movie-row

        let row = document.createElement('div');
        row.classList.add('row', 'item');

        fieldsNames.forEach((fieldName) => {
            let col = document.createElement('div');
            col.classList.add('col', 'form-group');
            if (fieldName === 'id') {
                col.classList.add('d-none'); //field with id - hidden
            }

            let input = document.createElement('input');
            input.type = 'text';
            input.classList.add('form-control');
            input.id = listName + rowIndex + '.' + fieldName;
            input.setAttribute('name', listName + '[' + rowIndex + '].' + fieldName);

            col.appendChild(input);
            row.appendChild(col);
        });

        document.getElementById('movieList').appendChild(row);
};

5. Result
There is single form with catalog and list of movies. And you can save them in @Post method, separately or together in your service-method.

Reply | Threaded
Open this post in threaded view
|

Re: jquery + thymeleaf = how to dynamically add forms with the object fields

janejc
It worked! Thank you so much for your help!