Search code examples
javascriptspring-bootspring-mvcthymeleaf

Update content in Thymeleaf without reloading whole page


i want to update the content of div "output" without reloading the whold page. When the user inputs something and hits the run button, the server will output the evaluation on the div "output". But the following code puts only div "output" on the browser. The other piece of page is gone. what i have done wrong ?

<body>
    <div class="container mx-auto">
        <div th:insert="fragments/body :: navigation (true, ${spieler.name}, ${spieler.punkte}, ${spieler.level})"></div>
    
        <div class="d-flex justify-content-center">
            <label class="text-center mt-4" id="thetext">Du liebst Abenteuer und möchte endlich reisen.<br>
                Du hast ein bisschen Geld und das reicht für ein Ticket zur nächsten Insel.<br>
                Unglücklicherweise ist dein Schiff in einem Sturm gesunken. <br>
                Du hat glück, an einen Strand zu landen. <br><br>
                Jetzt musst du die Herausforderungen mit deinen SQL-Kenntnissen meistern.<br>
                Je schneller du die Probleme löst, desto besser ist dein Ranking.<br><br>
                Viel Spaß !!!<br>
            </label>
        </div>
    
        <div class="d-flex justify-content-center">
            <input id="startButton" onclick="nachsteFrage()" class="btn-secondary mt-4"
                   type="button" value="Weiter">
        </div>
    
        <form method="post"  th:action="@{/level1-answer}" th:object="${answer}">
            <div class="form-group">
                <input type="text" class="form-control mt-4" id="codeArea" name="antwort" th:field = "*{SQL}"
                          rows="3" placeholder="Hier steht dein Code ...">
                <br>
                <div class="d-flex justify-content-center">
                    <button onclick="updateOutput()" class="btn-secondary mt-4"
                             id="ausfuehrenBtn">run</button>
                </div>
            </div>
        </form>
        <div id="output" style="overflow:scroll; height:400px;">
             <span th:text = "${evaluation}"></>
        </div>
    
    </div>
    
    <script th:inline="javascript">
        function updateOutput() {
            $.get("output").done(function(fragment) { 
                $("#output").replaceWith(fragment);
            });
        }
    </script>
</body>

the controller

@RequestMapping(value="/level1-answer", method=RequestMethod.POST)
public String postSQL (Model map, @ModelAttribute("answer") Answer answer) {
    map.addAttribute("evaluation",answer.getSQL() + "  this is correct or incorrect"); 
    return "level1 :: #output";
}

the html file is in templates/level1.html


Solution

  • Here is a very basic demo, to show one approach.

    I start with the main web page testajax.html:

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="x-ua-compatible" content="ie=edge">
            <title>Test Ajax Fragment</title>
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
        </head>
        <body>
            <h3>A Permanent Heading</h3>
            <div id="output">
                Some initial text displayed here. This will be replaced.
            </div>
            <br>
            <button onclick="updateOutput()" 
                    id="ausfuehrenBtn">run</button>
            <script>
                function updateOutput() {
                    $.post("test_ajax_frag").done(function (fragment) {
                        console.log(fragment);
                        $("#output").replaceWith(fragment);
                    });
                }
            </script>
        </body>
    </html>
    

    Note that there is no <form> here, because we are not going to be submitting any form data - we will, instead, be using an Ajax request, later on.

    The controller used to display this page:

    @RequestMapping(value="/test_ajax", method=RequestMethod.GET)
    public String sendHtml(Model map) {
        //map.addAttribute("foo", "bar");
        return "testajax";
    }
    

    It's a simple GET handler, used to display the initial page. I don't even have any specific Model data in this case, just to keep the demo simple.

    The Thymeleaf fragment template is testajaxfragment.html:

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
        <body>
            <div th:fragment="test_frag">
                <div id="output">
                    Some replacement content.
                </div>
            </div>
        </body>
    </html>
    

    The main page contains an Ajax call, which is executed when the button is clicked:

    $.post("test_ajax_frag")
    

    We need a separate controller for that:

    @RequestMapping(value="/test_ajax_frag", method=RequestMethod.POST)
    public String sendHtmlFragment(Model map) {
        //map.addAttribute("foo", "bar");
        return "testajaxfragment :: test_frag";
    }
    

    In this case, I chose to use a POST request in the Ajax call - so the controller has to also be a POST handler.

    When the controller runs, it returns a fragment of HTML to the JavaScript function which called it. That function then replaces the output div:

    $("#output").replaceWith(fragment);
    

    This is a fairly crude approach, but shows the technique you are asking about in the question and in your comments.