Skip to content

Commit 7a47bb2

Browse files
committed
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
1 parent e9c2d78 commit 7a47bb2

File tree

5 files changed

+16
-101
lines changed

5 files changed

+16
-101
lines changed

README.md

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,9 @@ public String users() {
175175
### HTML Fragments
176176

177177
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
178-
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
179-
[HtmxView](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.html)
178+
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
179+
multiple HTML fragments using `Collection<ModelAndView>` or `FragmentsRendering` as return type of controller. Further information on this can be found in the
180+
Spring Framework documentation under [HTML Fragments](https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-fragments.html).
180181

181182
```java
182183
@HxRequest
@@ -185,30 +186,13 @@ public View users(Model model) {
185186
model.addAttribute("users", userRepository.findAll());
186187
model.addAttribute("count", userRepository.count());
187188

188-
var view = new HtmxView();
189-
view.add("users/list");
190-
view.add("users/count");
191-
192-
return view;
189+
return FragmentsRendering
190+
.with("users/list")
191+
.fragment("users/count")
192+
.build();
193193
}
194194
```
195195

196-
An `HtmxView` can be formed from view names, as above, or fully resolved `View` instances, if the controller knows how
197-
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.
198-
199-
```java
200-
@HxRequest
201-
@GetMapping("/users")
202-
public View users(Model model) {
203-
var view = new HtmxView();
204-
view.add("users/list", Map.of("users", userRepository.findAll()));
205-
view.add("users/count", Map.of("count", userRepository.count()));
206-
207-
return view;
208-
}
209-
```
210-
211-
212196
### Exceptions
213197

214198
It is also possible to use `HtmxRequest` and `HtmxResponse` as method argument in handler methods annotated with `@ExceptionHandler`.

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,16 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.fasterxml.jackson.databind.json.JsonMapper;
5-
import org.springframework.beans.factory.ObjectFactory;
6-
import org.springframework.beans.factory.annotation.Qualifier;
75
import org.springframework.boot.autoconfigure.AutoConfiguration;
86
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
97
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
108
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
119
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
1210
import org.springframework.context.annotation.Bean;
1311
import org.springframework.core.Ordered;
14-
import org.springframework.util.Assert;
1512
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
1613
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
17-
import org.springframework.web.servlet.LocaleResolver;
1814
import org.springframework.web.servlet.View;
19-
import org.springframework.web.servlet.ViewResolver;
2015
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
2116
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
2217
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
@@ -28,19 +23,10 @@
2823
@ConditionalOnWebApplication
2924
public class HtmxMvcAutoConfiguration implements WebMvcRegistrations, WebMvcConfigurer {
3025

31-
private final ObjectFactory<ViewResolver> viewResolverObjectFactory;
32-
private final ObjectFactory<LocaleResolver> localeResolverObjectFactory;
3326
private final ObjectMapper objectMapper;
3427
private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler;
3528

36-
HtmxMvcAutoConfiguration(@Qualifier("viewResolver") ObjectFactory<ViewResolver> viewResolverObjectFactory,
37-
ObjectFactory<LocaleResolver> localeResolverObjectFactory) {
38-
39-
Assert.notNull(viewResolverObjectFactory, "viewResolverObjectFactory must not be null!");
40-
Assert.notNull(localeResolverObjectFactory, "localeResolverObjectFactory must not be null!");
41-
42-
this.viewResolverObjectFactory = viewResolverObjectFactory;
43-
this.localeResolverObjectFactory = localeResolverObjectFactory;
29+
HtmxMvcAutoConfiguration() {
4430
this.objectMapper = JsonMapper.builder().build();
4531
this.handlerMethodAnnotationHandler = new HtmxHandlerMethodAnnotationHandler(this.objectMapper);
4632
}
@@ -63,7 +49,7 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
6349

6450
@Override
6551
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
66-
handlers.add(new HtmxViewMethodReturnValueHandler(viewResolverObjectFactory.getObject(), localeResolverObjectFactory.getObject()));
52+
handlers.add(new HtmxViewMethodReturnValueHandler());
6753
}
6854

6955
@Override

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import org.springframework.util.Assert;
44

5-
import java.util.Collection;
6-
import java.util.LinkedHashSet;
7-
import java.util.Set;
5+
import java.util.*;
86

97
/**
108
* A holder for htmx-related response headers that can be used as method argument in controllers.

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxView.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
* this class.
1818
*
1919
* @since 3.6.0
20+
* @deprecated since 4.0.0 for removal in 4.1.0 in favor of <a href="https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-fragments.html">HTML Fragments</a> support.
2021
*/
22+
@Deprecated
2123
public class HtmxView {
2224

2325
private final Set<ModelAndView> views = new LinkedHashSet<>();
Lines changed: 4 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,20 @@
11
package io.github.wimdeblauwe.htmx.spring.boot.mvc;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import jakarta.servlet.http.HttpServletRequest;
5-
import jakarta.servlet.http.HttpServletResponse;
6-
import org.springframework.context.i18n.LocaleContextHolder;
73
import org.springframework.core.MethodParameter;
8-
import org.springframework.lang.Nullable;
9-
import org.springframework.ui.ModelMap;
10-
import org.springframework.util.Assert;
11-
import org.springframework.util.CollectionUtils;
124
import org.springframework.web.context.request.NativeWebRequest;
135
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
146
import org.springframework.web.method.support.ModelAndViewContainer;
15-
import org.springframework.web.servlet.LocaleResolver;
16-
import org.springframework.web.servlet.ModelAndView;
17-
import org.springframework.web.servlet.View;
18-
import org.springframework.web.servlet.ViewResolver;
19-
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
20-
import org.springframework.web.util.ContentCachingResponseWrapper;
21-
22-
import java.util.Collection;
23-
import java.util.HashMap;
24-
import java.util.Locale;
25-
import java.util.Map;
26-
import java.util.stream.Collectors;
7+
import org.springframework.web.servlet.view.FragmentsRendering;
278

289
/**
2910
* Handles return values that are of type {@link HtmxView}.
3011
*
3112
* @since 3.6.0
13+
* @deprecated since 4.0.0 for removal in 4.1.0 in favor of <a href="https://docs.spring.io/spring-framework/reference/web/webmvc-view/mvc-fragments.html">HTML Fragments</a> support.
3214
*/
15+
@Deprecated
3316
public class HtmxViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
3417

35-
private final ViewResolver viewResolver;
36-
private final LocaleResolver localeResolver;
37-
38-
public HtmxViewMethodReturnValueHandler(ViewResolver viewResolver, LocaleResolver localeResolver) {
39-
this.viewResolver = viewResolver;
40-
this.localeResolver = localeResolver;
41-
}
42-
4318
@Override
4419
public boolean supportsReturnType(MethodParameter returnType) {
4520
return HtmxView.class.isAssignableFrom(returnType.getParameterType());
@@ -52,40 +27,10 @@ public void handleReturnValue(Object returnValue,
5227
NativeWebRequest webRequest) throws Exception {
5328

5429
if (returnValue instanceof HtmxView htmxView) {
55-
mavContainer.setView(toView(htmxView));
30+
mavContainer.setView(FragmentsRendering.with(htmxView.getViews()).build());
5631
} else if (returnValue != null) {
5732
throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
5833
}
5934
}
6035

61-
private View toView(HtmxView htmxView) {
62-
63-
return (model, request, response) -> {
64-
Locale locale = localeResolver.resolveLocale(request);
65-
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response);
66-
67-
for (ModelAndView mav : htmxView.getViews()) {
68-
View view;
69-
if (mav.isReference()) {
70-
view = viewResolver.resolveViewName(mav.getViewName(), locale);
71-
if (view == null) {
72-
throw new IllegalArgumentException("Could not resolve view with name '" + mav.getViewName() + "'.");
73-
}
74-
} else {
75-
view = mav.getView();
76-
}
77-
78-
for (String key : model.keySet()) {
79-
if (!mav.getModel().containsKey(key)) {
80-
mav.getModel().put(key, model.get(key));
81-
}
82-
}
83-
84-
view.render(mav.getModel(), request, wrapper);
85-
}
86-
87-
wrapper.copyBodyToResponse();
88-
};
89-
}
90-
9136
}

0 commit comments

Comments
 (0)