From 7a47bb237fd52b0acb0af1a07887c8e4e60c5784 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Sun, 24 Nov 2024 09:28:43 +0100 Subject: [PATCH] Mark HtmxView as deprecated in favor of HTML Fragments support in Spring Framework 6.2 See https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-fragments.html --- README.md | 30 +++------ .../boot/mvc/HtmxMvcAutoConfiguration.java | 18 +----- .../htmx/spring/boot/mvc/HtmxResponse.java | 4 +- .../htmx/spring/boot/mvc/HtmxView.java | 2 + .../mvc/HtmxViewMethodReturnValueHandler.java | 63 ++----------------- 5 files changed, 16 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 7ef9f0c..a1e3c77 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,9 @@ public String users() { ### HTML Fragments In Spring MVC, view rendering typically involves specifying one view and one model. However, in htmx a common capability is to send multiple HTML fragments that -htmx can use to update different parts of the page, which is called [Out Of Band Swaps](https://htmx.org/docs/#oob_swaps). For this, controller methods can return -[HtmxView](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.html) +htmx can use to update different parts of the page, which is called [Out Of Band Swaps](https://htmx.org/docs/#oob_swaps). Spring offers the ability to return +multiple HTML fragments using `Collection` or `FragmentsRendering` as return type of controller. Further information on this can be found in the +Spring Framework documentation under [HTML Fragments](https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-fragments.html). ```java @HxRequest @@ -185,30 +186,13 @@ public View users(Model model) { model.addAttribute("users", userRepository.findAll()); model.addAttribute("count", userRepository.count()); - var view = new HtmxView(); - view.add("users/list"); - view.add("users/count"); - - return view; + return FragmentsRendering + .with("users/list") + .fragment("users/count") + .build(); } ``` -An `HtmxView` can be formed from view names, as above, or fully resolved `View` instances, if the controller knows how -to do that, or from `ModelAndView` instances (resolved or unresolved). Each fragment can have its own model, which is merged with the controller model before rendering. - -```java -@HxRequest -@GetMapping("/users") -public View users(Model model) { - var view = new HtmxView(); - view.add("users/list", Map.of("users", userRepository.findAll())); - view.add("users/count", Map.of("count", userRepository.count())); - - return view; -} -``` - - ### Exceptions It is also possible to use `HtmxRequest` and `HtmxResponse` as method argument in handler methods annotated with `@ExceptionHandler`. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java index ab40759..72a252f 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import org.springframework.beans.factory.ObjectFactory; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -11,12 +9,9 @@ import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; -import org.springframework.util.Assert; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.View; -import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; @@ -28,19 +23,10 @@ @ConditionalOnWebApplication public class HtmxMvcAutoConfiguration implements WebMvcRegistrations, WebMvcConfigurer { - private final ObjectFactory viewResolverObjectFactory; - private final ObjectFactory localeResolverObjectFactory; private final ObjectMapper objectMapper; private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler; - HtmxMvcAutoConfiguration(@Qualifier("viewResolver") ObjectFactory viewResolverObjectFactory, - ObjectFactory localeResolverObjectFactory) { - - Assert.notNull(viewResolverObjectFactory, "viewResolverObjectFactory must not be null!"); - Assert.notNull(localeResolverObjectFactory, "localeResolverObjectFactory must not be null!"); - - this.viewResolverObjectFactory = viewResolverObjectFactory; - this.localeResolverObjectFactory = localeResolverObjectFactory; + HtmxMvcAutoConfiguration() { this.objectMapper = JsonMapper.builder().build(); this.handlerMethodAnnotationHandler = new HtmxHandlerMethodAnnotationHandler(this.objectMapper); } @@ -63,7 +49,7 @@ public void addArgumentResolvers(List resolvers) @Override public void addReturnValueHandlers(List handlers) { - handlers.add(new HtmxViewMethodReturnValueHandler(viewResolverObjectFactory.getObject(), localeResolverObjectFactory.getObject())); + handlers.add(new HtmxViewMethodReturnValueHandler()); } @Override diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.java index f070e54..50b107e 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.java @@ -2,9 +2,7 @@ import org.springframework.util.Assert; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; /** * A holder for htmx-related response headers that can be used as method argument in controllers. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.java index 20d6cdd..c910f02 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.java @@ -17,7 +17,9 @@ * this class. * * @since 3.6.0 + * @deprecated since 4.0.0 for removal in 4.1.0 in favor of HTML Fragments support. */ +@Deprecated public class HtmxView { private final Set views = new LinkedHashSet<>(); diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxViewMethodReturnValueHandler.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxViewMethodReturnValueHandler.java index 28de723..10f4c8a 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxViewMethodReturnValueHandler.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxViewMethodReturnValueHandler.java @@ -1,45 +1,20 @@ package io.github.wimdeblauwe.htmx.spring.boot.mvc; -import com.fasterxml.jackson.core.JsonProcessingException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.MethodParameter; -import org.springframework.lang.Nullable; -import org.springframework.ui.ModelMap; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.View; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.util.ContentCachingResponseWrapper; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; +import org.springframework.web.servlet.view.FragmentsRendering; /** * Handles return values that are of type {@link HtmxView}. * * @since 3.6.0 + * @deprecated since 4.0.0 for removal in 4.1.0 in favor of HTML Fragments support. */ +@Deprecated public class HtmxViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - private final ViewResolver viewResolver; - private final LocaleResolver localeResolver; - - public HtmxViewMethodReturnValueHandler(ViewResolver viewResolver, LocaleResolver localeResolver) { - this.viewResolver = viewResolver; - this.localeResolver = localeResolver; - } - @Override public boolean supportsReturnType(MethodParameter returnType) { return HtmxView.class.isAssignableFrom(returnType.getParameterType()); @@ -52,40 +27,10 @@ public void handleReturnValue(Object returnValue, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof HtmxView htmxView) { - mavContainer.setView(toView(htmxView)); + mavContainer.setView(FragmentsRendering.with(htmxView.getViews()).build()); } else if (returnValue != null) { throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } } - private View toView(HtmxView htmxView) { - - return (model, request, response) -> { - Locale locale = localeResolver.resolveLocale(request); - ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response); - - for (ModelAndView mav : htmxView.getViews()) { - View view; - if (mav.isReference()) { - view = viewResolver.resolveViewName(mav.getViewName(), locale); - if (view == null) { - throw new IllegalArgumentException("Could not resolve view with name '" + mav.getViewName() + "'."); - } - } else { - view = mav.getView(); - } - - for (String key : model.keySet()) { - if (!mav.getModel().containsKey(key)) { - mav.getModel().put(key, model.get(key)); - } - } - - view.render(mav.getModel(), request, wrapper); - } - - wrapper.copyBodyToResponse(); - }; - } - }