Search code examples
javaspringspring-bootspring-mvcthymeleaf

Spring Thymeleaf Form Submission With Static Values


I am developing a Java application that, through the use of Spring and Thymeleaf I want to be able to add to the system a certain serial device.

I have a page that presents to the user all the serial devices connected to the computer and a simple button that allows the person to add that specific device to the system like shown below:

enter image description here

Now what I'm expecting when I press the blue button above is that a POST request is made to an URL and a DTO is sent containing all the information that is seen in each of the columns.

The problem is that I do not know how to send the DTO with that data because I'm unable to set the object fields with them as my attempt looks something like this:

        <tbody>
            <tr th:each="serialdevice : ${SerialDevices}">
                <td><span th:text="${serialdevice.getDescriptivePortName()}">Serial Port ID</span></td>
                <td><span th:text="${serialdevice.getPortDescription()}">Serial Port Description</span></td>
                <td><span th:text="${serialdevice.getBaudRate()}">Baud Rate</span></td>
                <td>
                    <form th:action="@{/serialdevices/add/submit}" th:object="${SerialDeviceDTO}" method="post">
                        <input hidden type="text" th:attr="value=${serialdevice.getDescriptivePortName()}" th:field="*{descriptivePortName}" class="form-control">
                        <input hidden type="text" th:field="*{portDescription}" th:value="${serialdevice.getPortDescription()}" class="form-control">
                        <input hidden type="text" th:field="*{baudRate}" th:value="${serialdevice.getBaudRate()}" class="form-control">
                        <button type="submit" class="btn btn-primary">Add Serial Device</button>
                    </form>
                </td>
            </tr>
        </tbody>

What is happening is that I retrieve a list of Serial Ports and present all of them to the user so he/she can select one and then I place a button whose intent is to submit a DTO with those same fields that I retrieved and presented to the user.

EDIT 1

In response to @JRichardsz:

  1. The endpoint is called and receives a DTO object that contains uninitialized values for all of its properties
  2. The endpoint from my controller that handles this DTO is the following:
    @RequestMapping(value = "/submit", method = RequestMethod.POST,
            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},
            produces= {MediaType.TEXT_PLAIN_VALUE})
    public String submit(AddSerialDevice dto) {
        System.out.println(dto.getDescriptivePortName());
        System.out.println(dto.getPortDescription());
        System.out.println(dto.getBaudRate());
        return null;
    }
  1. As mentioned in number one, the values that I print are all uninitialized therefor I print either null or 0.

EDIT 2

Updating the HTML code so as to conform with the suggestion of M.Deinum of changing the span tags to input ones.


Solution

  • Well after some time messing around with my code and searching through other answers such as this one I was able to figure out how to solve my problem but I'll still be searching for the reason why my initial approach didn't work.

    What I did to fix my issue and be able to have something similar to the picture below was make use of the th:data that Thymeleaf has and store in that attribute the value that I wanted to send to my Spring controller with the appropriate parameters that I present to the user (The Serial Port ID, Serial Port Description and Baud Rate)

    Screenshot of the interface

    As seen below, I made use of the previously mentioned th:data attribute and stored there the values that I would like to send to the controller if the user pressed the "Add Serial Device" button.

    The reason behind this was that when I tried using the th:value or even the th:attr=value... none of these would work as when the page finished loading, the value attribute of the input elements would be initialized as either null (for text) or 0 (for numbers)

            <tbody>
            <tr th:each="serialdevice : ${SerialDevices}">
                <td><span th:text="${serialdevice.getDescriptivePortName()}">Serial Port ID</span></td>
                <td><span th:text="${serialdevice.getPortDescription()}">Serial Port Description</span></td>
                <td><span th:text="${serialdevice.getBaudRate()}">Baud Rate</span></td>
                <td>
                    <form th:action="@{/serialdevices/add/submit}" th:object="${SerialDeviceDTO}" method="post">
                        <input hidden type="text" th:data1="${serialdevice.getDescriptivePortName()}" th:field="*{descriptivePortName}" class="form-control">
                        <input hidden type="text" th:data1="${serialdevice.getPortDescription()}" th:field="*{portDescription}" class="form-control">
                        <input hidden type="text" th:data1="${serialdevice.getBaudRate()}" th:field="*{baudRate}" class="form-control">
                        <button type="submit" th:onclick="javascript:setValueAttribute(this.parentElement);" class="btn btn-primary">Add Serial Device</button>
                    </form>
                </td>
            </tr>
        </tbody>
    

    Given this issue and the fact that I could not find an answer for why the th:value or th:attr wasn't working for the value attribute I did a not-so-elegant workaround which was to write a simple block of JavaScript code that you can see below that simply takes the <form></form> element (which is the parent element of the button "Add Serial Device") and proceeds to check for its children nodes. If these nodes are Input nodes, it will take the value stored in data1 attribute and set the value attribute equal to the data1 attribute.

    Since the onclick attribute executes the function before the form is actually submitted to the controller's endpoint, it will set all of the value attributes of the several input tags to its respective values and then send these to the controller.

    <script>
    function setValueAttribute(element) {
        var children = element.childNodes;
        children.forEach(child => setValue(child));
    }
    
    function setValue(element) {
        if(element.tagName === "INPUT") {
            element.setAttribute("value", element.getAttribute("data1"));
        }else{
            console.log(element.tagName)
        }
    }
    

    I hope this answer is able to help someone in the future that faces a similar kind of issue as I did and if someone could find a more elegant way of doing all of this without requiring the use of Javascript and simply make Thymeleaf do all of the rendering of the HTML before sending it to the client I would be very much appreciated since I don't think this is the proper or best way of achieving the intended result.