ChatGPT解决这个技术问题 Extra ChatGPT

Spring MVC 3.2 Thymeleaf Ajax Fragments

I'm building application with Spring MVC 3.2 and Thymeleaf templating engine. I'm a beginner in Thymeleaf.

I have everything working, including Thymeleaf but I was wondering if anyone knows of a simple and clear toturial on how to do simple Ajax request to controller and in result rendering only a part of a template (fragment).

My app has everything configured (Spring 3.2, spring-security, thymeleaf, ...) and works as expected. Now I would like to do Ajax request (pretty simple with jQuery but I don't wan't to use is since Thymeleaf in its tutorial, chapter 11: Rendering Template Fragments (link) mentiones it can be done with fragments.

Currently I have in my Controller

@RequestMapping("/dimensionMenuList")
public String showDimensionMenuList(Model model) {

    Collection<ArticleDimensionVO> articleDimensions;
    try {
        articleDimensions = articleService.getArticleDimension(ArticleTypeVO.ARTICLE_TYPE);
    } catch (DataAccessException e) {
        // TODO: return ERROR
        throw new RuntimeException();
    }

    model.addAttribute("dimensions", articleDimensions);

    return "/admin/index :: dimensionMenuList";
}

the part of the view where I would like to replace <ul></ul> menu items:

<ul th:fragment="dimensionMenuList" class="dropdown-menu">
    <li th:unless="${#lists.isEmpty(dimensions)}" th:each="dimension : ${dimensions}">
        <a href="#" th:text="${dimension.dimension}"></a>
    </li>
</ul>

Any clue is greatly appreciated. Especially if I don't have to include any more frameworks. It's already too much for java web app as it is.


v
vdkkj

Here is an approach I came across in a blog post:

I didn't want to use those frameworks so in this section I'm using jQuery to send an AJAX request to the server, wait for the response and partially update the view (fragment rendering).

The Form

<form>
    <span class="subtitle">Guest list form</span>
    <div class="listBlock">
        <div class="search-block">
            <input type="text" id="searchSurname" name="searchSurname"/>
            <br />
            <label for="searchSurname" th:text="#{search.label}">Search label:</label>
            <button id="searchButton" name="searchButton" onclick="retrieveGuests()" type="button" 
                    th:text="#{search.button}">Search button</button>
        </div>

        <!-- Results block -->
        <div id="resultsBlock">

        </div>
    </div>
</form>

This form contains an input text with a search string (searchSurname) that will be sent to the server. There's also a region (resultsBlock div) which will be updated with the response received from the server.

When the user clicks the button, the retrieveGuests() function will be invoked.

function retrieveGuests() {
    var url = '/th-spring-integration/spring/guests';

    if ($('#searchSurname').val() != '') {
        url = url + '/' + $('#searchSurname').val();
    }

    $("#resultsBlock").load(url);
}

The jQuery load function makes a request to the server at the specified url and places the returned HTML into the specified element (resultsBlock div).

If the user enters a search string, it will search for all guests with the specified surname. Otherwise, it will return the complete guest list. These two requests will reach the following controller request mappings:

@RequestMapping(value = "/guests/{surname}", method = RequestMethod.GET)
public String showGuestList(Model model, @PathVariable("surname") String surname) {
    model.addAttribute("guests", hotelService.getGuestsList(surname));

    return "results :: resultsList";
}

@RequestMapping(value = "/guests", method = RequestMethod.GET)
public String showGuestList(Model model) {
    model.addAttribute("guests", hotelService.getGuestsList());

    return "results :: resultsList";
}

Since Spring is integrated with Thymeleaf, it will now be able to return fragments of HTML. In the above example, the return string "results :: resultsList" is referring to a fragment named resultsList which is located in the results page. Let's take a look at this results page:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org" lang="en">

<head>
</head>

<body>
    <div th:fragment="resultsList" th:unless="${#lists.isEmpty(guests)}" id="results-block">
        <table>
            <thead>
                <tr>
                    <th th:text="#{results.guest.id}">Id</th>
                    <th th:text="#{results.guest.surname}">Surname</th>
                    <th th:text="#{results.guest.name}">Name</th>
                    <th th:text="#{results.guest.country}">Country</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="guest : ${guests}">
                    <td th:text="${guest.id}">id</td>
                    <td th:text="${guest.surname}">surname</td>
                    <td th:text="${guest.name}">name</td>
                    <td th:text="${guest.country}">country</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

The fragment, which is a table with registered guests, will be inserted in the results block.


A
Abdullah Khan

Rendering only Thymeleaf fragments also works well with ModelAndView.

Your controller

@RequestMapping(value = "/feeds", method = RequestMethod.GET)
public ModelAndView getFeeds() {
    LOGGER.debug("Feeds method called..");
    return new ModelAndView("feeds :: resultsList");
}

Your view

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">

<head></head>
<body>
    <div th:fragment="resultsList" id="results-block">
        <div>A test fragment</div>
        <div>A test fragment</div>
    </div>
</body>
</html>

What's actually rendered

<div id="results-block">
    <div>A test fragment</div>
    <div>A test fragment
    </div>
</div>

Does it makes a page refresh?
Depends on the way you call it. If its an ajax call the page wont refresh otherwise it will.
I really liked this. I tried returning view name approach and it works too. Like this: public String getFeeds() { return "feeds :: resultsList"; }
How to return multiple fragment?? @AbdullahKhan
J
Jorge.V

As an alternate version to Sohail's great answer, I want to give a version that using javascript can send the whole th:object to the controller, integrating Thymeleaf in your forms, not having to use @PathVariable which becomes messy or not usable at all when you've forms with many fields.

For the form (using an example which returns an object which has an id and a name Strings, and feeds a combobox with a Map that has some of those objects as values) we have:

<form method="post" th:action="@{/yourMapping}" th:object="${object}" id="yourFormId">
    <select th:field="*{mapOfObjects}">
       <option 
          th:each="entry: ${mapOfObjects}"
          th:value="${entry.value.id}"
          th:text="${entry.value.name}" >
       </option>
    </select>

    <p>Name: 
       <input type="text" th:field="*{name}" />
    </p>
</form>

When this form is submited (using a button with type submit for example) the whole document will be replaced. However we can intercept this submit with javascript and do it the ajax-way. To achieve this, we will add an interceptor to our form using a function. First call the function that adds the interceptor right after the form:

<script>formInterceptor("yourFormId");</script>

And the function looks like this (place it in the head of the document or wherever suits your needs):

<script>
function formInterceptor(formName) {
    var $form = $("#" + formName);

    $form.on('submit', function(e) {
        e.preventDefault();
        $.ajax({
            url : $form.attr('action'),
            type : 'post',
            data : $form.serialize(),
            success : function(response) {
                if ($(response).find('.has-error').length) {
                    $form.replaceWith(response);
                }
                else{
                    $("#ajaxLoadedContent").replaceWith(response);
                }
            }
        });
    });
};
</script>

Now whenever the form is submited, this function will trigger, and it will:

Prevent the original form submit

Make an ajax call using the url defined in the form's th:action

Serialize the form data. Your controller will be able to recieve this in an object

Replace the part of your html code with the returned fragment

The replaced part should look like this

<div id="ajaxLoadedContent"></div>

And the controller can recieve the th:object in the form, with it's values filled, like this (Replace Object with your object's type and "object" with a proper name):

@PostMapping(value = /yourMapping)
public String ajaxCtrlExample(@ModelAttribute("object") Object object, Model model) {
    return yourFragmentPath;
}

And that's everything. Call the function that adds the interceptor after every form you need in ajax-version.