Search code examples
javascriptjavaajaxspringthymeleaf

How to get a Thymeleaf fragment from a Spring ResponseEntity with ajax.post()


I want to implement the suppression of several objectfs, and handle in my controller the different errors that might happen. I call the controller with ajax, and want to load up a fragment in my html that will indicates what happened.

I don't understand what makes ajax get my thymeleaf fragment when he receives a string in response.

Controller :

@PostMapping("/deleteMultipleMyObjects")
@ResponseBody
public ResponseEntity<Object> deleteMultipleMyObjects(Model model, @ModelAttribute("myObjectIds") String myObjectIds) {

        if (myObjectIds.length() == 0) {
            MessageHelper.addInfoAttribute(model, "objects.delete.none");
            return ResponseEntity.badRequest().body("fragments/components :: ajaxResponse");
        }

        // The service brings back two lists : the ids deleted and those non-deleted (for any reason)
        Pair<List<Long>, List<Long>> res = myObjectService.deleteMultipleMyObjects(myObjectIds);

        if (!res.getValue1().isEmpty()) {
            // Here I want to send back a warning, and handle the objects I did not delete
            MessageHelper.addWarningAttribute(model, "objects.delete.warn", res.getValue1());
            return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).body(new Pair<>(res.getValue0(), "fragments/components :: ajaxResponse"));
        }
        // If all goes well, success
        MessageHelper.addSuccessAttribute(model, "objects.delete.ok");
        return ResponseEntity.accepted().body("fragments/components :: ajaxResponse");
    }

The fragment I want to get (and that is used many times in my code) - components.html :

<div id="ajaxResponse" th:fragment="ajaxResponse (type, message)" class="alert alert-dismissable" th:classappend="'alert-' + ${message.type.name().toLowerCase()}">
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
  <span th:if="${message.args==null or message.args.length==0}" th:text="#{${message.message}}">An error happened</span>
  <span th:if="${message.args!=null and message.args.length==1}" th:text="#{${message.message}(${message.args[0]})}">An error happened</span>
</div>

The html where I visualize and handle my objects - objects.html :

<div id="alertDiv">
</div>
<div id="deleteMultipleMyObjects" class="btn" onclick="deleteMultipleMyObjects()">Delete multiple objects</div>
  <table>
  <!-- a table containing my objects and with a checkbox to select those who will be deleted-->
  </table>

And finally my javascript - objects.js :

function deleteMultipleMyObjects() {
  let myObjectIds = getAllCheckedObjects(); // sends back a string with the ids of the checked objects : "id1,id3,id6"

  let fragment;
  $.post("deleteMultipleMyObjects", {myObjectIds: myObjectIds})
  .done(function (data, status, xhr){
    hideAllCheckedObjects();
    fragment = data; // !!! Here I get the name of the fragment instead of the html
  })
  .fail(function(xhr, status, error){
    if (undefined != xhr.responseJSON && xhr.responseJSON.value0.length > 0) {
      hideLinesWithId(xhr.responseJSON.value0);
      fragment = xhr.responseJSON.value1; // !!! Here I get the name of the fragment instead of the html
    } else {
      fragment = xhr.responseText; // !!! Here I get the name of the fragment instead of the html
    }
  })
  .always(function(xhr, status, error) {
    $("#alertDiv").text("");
    $("#alertDiv").append(fragment); // !!! Here I'd like my alertDiv to receive the full html of my fragment
  });
}

Each time I get an xhr or data containing literally the name of the fragment ("fragments/components :: ajaxResponse") instead of the html of it.
I got other places in my code where I use $.ajax or $.post and I get back the html of the fragment but at those places my Controller sends back a String and not a ResponseEntity. Here I'd really like to have my http response code to handle the different responses and also a body with data and also my fragment. It seems I can't have them all...


Solution

  • When you use @RestController or @ResponseBody you are saying that you want to return data rather than render an MVC view. Similarly, I don't think you can use ResponseEntity if you want to render a view, just return the view name String or a ModelAndView.