diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index 7600246..cd43911 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -33,9 +33,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; +import io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponse; import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxRequest; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; /** @@ -119,21 +118,22 @@ public String htmxInitFindForm() { } @GetMapping("/owners") - public String ownersList(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, Model model, - HttpServletResponse response) { - return processFindForm(page, owner, result, model, response, "owners/findOwners", "owners/ownersList"); + public String ownersList(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, + Model model) { + return processFindForm(page, owner, result, model, "owners/findOwners", "owners/ownersList"); } @HxRequest @GetMapping("/owners") - public String htmxOwnersList(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, - Model model, HttpServletResponse response) { - return processFindForm(page, owner, result, model, response, FRAGMENTS_OWNERS_FIND_FORM, + public HtmxResponse htmxOwnersList(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, + Model model) { + String view = processFindForm(page, owner, result, model, FRAGMENTS_OWNERS_FIND_FORM, "fragments/owners :: list"); + return new HtmxResponse().addTemplate(view); } public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result, - Model model, HttpServletResponse response, String emptyView, String listView) { + Model model, String emptyView, String listView) { // allow parameterless GET request for /owners to return all records if (owner.getLastName() == null) { owner.setLastName(""); // empty string signifies broadest possible search @@ -154,18 +154,16 @@ public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner } // multiple owners found - return addPaginationModel(owner.getLastName(), page, model, ownersResults, response, listView); + return addPaginationModel(owner.getLastName(), page, model, ownersResults, listView); } - private String addPaginationModel(String lastName, int page, Model model, Page paginated, - HttpServletResponse response, String listView) { + private String addPaginationModel(String lastName, int page, Model model, Page paginated, String listView) { model.addAttribute("listOwners", paginated); List listOwners = paginated.getContent(); model.addAttribute("currentPage", page); model.addAttribute("totalPages", paginated.getTotalPages()); model.addAttribute("totalItems", paginated.getTotalElements()); model.addAttribute("listOwners", listOwners); - response.addHeader("HX-Push-Url", "/owners?lastName=" + lastName + "&page=" + page); return listView; } @@ -182,9 +180,7 @@ public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model mo @HxRequest @GetMapping("/owners/{ownerId}/edit") - public String htmxInitUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model, HttpServletRequest request, - HttpServletResponse response) { - response.addHeader("HX-Push-Url", request.getServletPath()); + public String htmxInitUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { return handleInitUpdateOwnerForm(ownerId, model, FRAGMENTS_OWNERS_EDIT); } @@ -229,8 +225,7 @@ public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { @HxRequest @GetMapping("/owners/{ownerId}") - public ModelAndView htmxShowOwner(@PathVariable("ownerId") int ownerId, HttpServletResponse response) { - response.addHeader("HX-Push-Url", "/owners/" + ownerId); + public ModelAndView htmxShowOwner(@PathVariable("ownerId") int ownerId) { return handleShowOwner(ownerId, "fragments/owners :: details"); } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java index b76c704..fa84fa5 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java @@ -30,8 +30,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxRequest; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; /** @@ -87,9 +85,7 @@ public String initCreationForm(Owner owner, ModelMap model) { @HxRequest @GetMapping("/pets/new") - public String htmxInitCreationForm(Owner owner, ModelMap model, HttpServletRequest request, - HttpServletResponse response) { - response.addHeader("HX-Push-Url", request.getServletPath()); + public String htmxInitCreationForm(Owner owner, ModelMap model) { return handleInitCreationForm(owner, model, FRAGMENTS_PETS_EDIT); } @@ -135,8 +131,7 @@ public String initUpdateForm(Owner owner, @PathVariable("petId") int petId, Mode @HxRequest @GetMapping("/pets/{petId}/edit") public String htmxInitUpdateForm(@PathVariable("ownerId") int ownerId, Owner owner, - @PathVariable("petId") int petId, ModelMap model, HttpServletResponse response) { - response.addHeader("HX-Push-Url", "/owners/" + ownerId + "/pets/" + petId + "/edit"); + @PathVariable("petId") int petId, ModelMap model) { return handleInitUpdateForm(owner, petId, model, FRAGMENTS_PETS_EDIT); } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java index 132dfd1..283f648 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java @@ -27,8 +27,6 @@ import org.springframework.web.bind.annotation.PostMapping; import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxRequest; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; /** @@ -85,8 +83,7 @@ public String initNewVisitForm() { @HxRequest @GetMapping("/owners/{ownerId}/pets/{petId}/visits/new") - public String htmxInitNewVisitForm(HttpServletRequest request, HttpServletResponse response) { - response.addHeader("HX-Push-Url", request.getServletPath()); + public String htmxInitNewVisitForm() { return FRAGMENTS_PETS_VISITS; } diff --git a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java b/src/main/java/org/springframework/samples/petclinic/vet/VetController.java index 5d18bc8..defe677 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/VetController.java @@ -27,7 +27,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxRequest; -import jakarta.servlet.http.HttpServletResponse; /** * @author Juergen Hoeller @@ -46,34 +45,31 @@ public VetController(VetRepository clinicService) { } @GetMapping("/vets.html") - public String showVetList(@RequestParam(defaultValue = "1") int page, Model model, HttpServletResponse response) { - return handleVetList(page, model, "vets/vetList", response); + public String showVetList(@RequestParam(defaultValue = "1") int page, Model model) { + return handleVetList(page, model, "vets/vetList"); } @HxRequest @GetMapping("/vets.html") - public String htmxShowVetList(@RequestParam(defaultValue = "1") int page, Model model, - HttpServletResponse response) { - return handleVetList(page, model, "fragments/vets :: list", response); + public String htmxShowVetList(@RequestParam(defaultValue = "1") int page, Model model) { + return handleVetList(page, model, "fragments/vets :: list"); } - protected String handleVetList(int page, Model model, String view, HttpServletResponse response) { + protected String handleVetList(int page, Model model, String view) { // Here we are returning an object of type 'Vets' rather than a collection of Vet // objects so it is simpler for Object-Xml mapping Vets vets = new Vets(); Page paginated = findPaginated(page); vets.getVetList().addAll(paginated.toList()); - return addPaginationModel(page, paginated, model, view, response); + return addPaginationModel(page, paginated, model, view); } - private String addPaginationModel(int page, Page paginated, Model model, String view, - HttpServletResponse response) { + private String addPaginationModel(int page, Page paginated, Model model, String view) { List listVets = paginated.getContent(); model.addAttribute("currentPage", page); model.addAttribute("totalPages", paginated.getTotalPages()); model.addAttribute("totalItems", paginated.getTotalElements()); model.addAttribute("listVets", listVets); - response.addHeader("HX-Push-Url", "/vets.html"); return view; } diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index e10a525..63ae5fd 100755 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -37,7 +37,7 @@
  • + hx:get="@{__${link}__}" hx:target="${target}" hx-push-url="true"> Template diff --git a/src/main/resources/templates/fragments/owners.html b/src/main/resources/templates/fragments/owners.html index 75ca0ce..834feb4 100644 --- a/src/main/resources/templates/fragments/owners.html +++ b/src/main/resources/templates/fragments/owners.html @@ -1,7 +1,7 @@

    Find Owners

    -
    @@ -25,7 +25,7 @@

    Find Owners

    Add Owner + hx-push-url="true" th:href="@{/owners/new}">Add Owner
    @@ -47,7 +47,7 @@

    Owners

    + hx:get="@{/owners/__${owner.id}__}" hx-push-url="true" hx-target="#block-content"/> @@ -83,10 +83,10 @@

    Owner Information

    - Edit Owner - Add + Add New Pet @@ -122,9 +122,13 @@

    Pets and Visits

    Edit Pet + hx:get="@{__${owner.id}__/pets/__${pet.id}__/edit}" + hx-push-url="true" + hx-target="#block-content">Edit Pet Add Visit + hx:get="@{__${owner.id}__/pets/__${pet.id}__/visits/new}" + hx-push-url="true" + hx-target="#block-content">Add Visit diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java index 1afda8b..0f1cc55 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java @@ -16,11 +16,28 @@ package org.springframework.samples.petclinic.owner; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.springframework.samples.petclinic.htmx.HtmxTestUtils.toggleHtmx; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +import java.time.LocalDate; +import java.util.List; + import org.assertj.core.util.Lists; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; @@ -32,19 +49,6 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import java.time.LocalDate; -import java.util.List; - -import static org.hamcrest.Matchers.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.given; -import static org.springframework.samples.petclinic.htmx.HtmxTestUtils.toggleHtmx; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * Test class for {@link OwnerController} @@ -151,7 +155,7 @@ void testProcessFindFormSuccess(boolean hxRequest, String expectedViewName) thro Mockito.when(this.owners.findByLastName(anyString(), any(Pageable.class))).thenReturn(tasks); mockMvc.perform(toggleHtmx(get("/owners?page=1"), hxRequest)) .andExpect(status().isOk()) - .andExpect(view().name(expectedViewName)); + .andExpect(view().name(expectedViewName.contains("::") ? null : expectedViewName)); } @ValueSource(booleans = { false, true }) @@ -161,7 +165,7 @@ void testProcessFindFormByLastName(boolean hxRequest) throws Exception { Mockito.when(this.owners.findByLastName(eq("Franklin"), any(Pageable.class))).thenReturn(tasks); mockMvc.perform(toggleHtmx(get("/owners?page=1"), hxRequest).param("lastName", "Franklin")) .andExpect(status().is3xxRedirection()) - .andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID)); + .andExpect(view().name(!hxRequest ? "redirect:/owners/" + TEST_OWNER_ID : null)); } @CsvSource({ "false,owners/findOwners", "true,fragments/owners :: find-form" }) @@ -173,7 +177,7 @@ void testProcessFindFormNoOwnersFound(boolean hxRequest, String expectedViewName .andExpect(status().isOk()) .andExpect(model().attributeHasFieldErrors("owner", "lastName")) .andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound")) - .andExpect(view().name(expectedViewName)); + .andExpect(view().name(expectedViewName.contains("::") ? null : expectedViewName)); }