Skip to content

Commit

Permalink
Remove HtmxViewHandlerInterceptor
Browse files Browse the repository at this point in the history
The HtmxResponseHandlerMethodReturnValueHandler alone is enough to cover the HtmxResponse return value from normal controller methods and exception handlers

Fixes #94
  • Loading branch information
wimdeblauwe committed Dec 10, 2023
1 parent 1f29cd7 commit 81669e3
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.util.Assert;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
Expand Down Expand Up @@ -45,7 +44,6 @@ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HtmxHandlerInterceptor());
registry.addInterceptor(new HtmxViewHandlerInterceptor(htmxResponseHelper()));
}

@Override
Expand All @@ -55,16 +53,6 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)

@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(htmxResponseHandlerMethodReturnValueHandler(htmxResponseHelper()));
}

@Bean
public HtmxResponseHelper htmxResponseHelper() {
return new HtmxResponseHelper(resolver.getObject(), locales, objectMapper);
}

@Bean
public HtmxResponseHandlerMethodReturnValueHandler htmxResponseHandlerMethodReturnValueHandler(HtmxResponseHelper helper) {
return new HtmxResponseHandlerMethodReturnValueHandler(helper);
handlers.add(new HtmxResponseHandlerMethodReturnValueHandler(resolver.getObject(), locales, objectMapper));
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
package io.github.wimdeblauwe.htmx.spring.boot.mvc;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
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.util.ContentCachingResponseWrapper;

import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.stream.Collectors;

public class HtmxResponseHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private final HtmxResponseHelper responseHelper;
private final ViewResolver views;
private final ObjectFactory<LocaleResolver> locales;
private final ObjectMapper objectMapper;

public HtmxResponseHandlerMethodReturnValueHandler(HtmxResponseHelper responseHelper) {
this.responseHelper = responseHelper;
public HtmxResponseHandlerMethodReturnValueHandler(ViewResolver views,
ObjectFactory<LocaleResolver> locales,
ObjectMapper objectMapper) {
this.views = views;
this.locales = locales;
this.objectMapper = objectMapper;
}

@Override
Expand All @@ -25,8 +45,98 @@ public void handleReturnValue(Object returnValue,
NativeWebRequest webRequest) throws Exception {

HtmxResponse htmxResponse = (HtmxResponse) returnValue;
mavContainer.setView(responseHelper.toView(htmxResponse));
mavContainer.setView(toView(htmxResponse));

addHxHeaders(htmxResponse, webRequest.getNativeResponse(HttpServletResponse.class));
}

private View toView(HtmxResponse htmxResponse) {

Assert.notNull(htmxResponse, "HtmxResponse must not be null!");

return (model, request, response) -> {
Locale locale = locales.getObject().resolveLocale(request);
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response);
for (ModelAndView modelAndView : htmxResponse.getViews()) {
View view = modelAndView.getView();
if (view == null) {
view = views.resolveViewName(modelAndView.getViewName(), locale);
}
for (String key : model.keySet()) {
if (!modelAndView.getModel().containsKey(key)) {
modelAndView.getModel().put(key, model.get(key));
}
}
Assert.notNull(view, "Template '" + modelAndView + "' could not be resolved");
view.render(modelAndView.getModel(), request, wrapper);
}
wrapper.copyBodyToResponse();
};
}

private void addHxHeaders(HtmxResponse htmxResponse, HttpServletResponse response) {
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER, htmxResponse.getTriggersInternal());
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE, htmxResponse.getTriggersAfterSettleInternal());
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP, htmxResponse.getTriggersAfterSwapInternal());

if (htmxResponse.getLocation() != null) {
if (htmxResponse.getLocation().hasContextData()) {
setHeaderJsonValue(response, HtmxResponseHeader.HX_LOCATION.getValue(), htmxResponse.getLocation());
} else {
response.setHeader(HtmxResponseHeader.HX_LOCATION.getValue(), htmxResponse.getLocation().getPath());
}
}
if (htmxResponse.getReplaceUrl() != null) {
response.setHeader(HtmxResponseHeader.HX_REPLACE_URL.getValue(), htmxResponse.getReplaceUrl());
}
if (htmxResponse.getPushUrl() != null) {
response.setHeader(HtmxResponseHeader.HX_PUSH_URL.getValue(), htmxResponse.getPushUrl());
}
if (htmxResponse.getRedirect() != null) {
response.setHeader(HtmxResponseHeader.HX_REDIRECT.getValue(), htmxResponse.getRedirect());
}
if (htmxResponse.isRefresh()) {
response.setHeader(HtmxResponseHeader.HX_REFRESH.getValue(), "true");
}
if (htmxResponse.getRetarget() != null) {
response.setHeader(HtmxResponseHeader.HX_RETARGET.getValue(), htmxResponse.getRetarget());
}
if (htmxResponse.getReselect() != null) {
response.setHeader(HtmxResponseHeader.HX_RESELECT.getValue(), htmxResponse.getReselect());
}
if (htmxResponse.getReswap() != null) {
response.setHeader(HtmxResponseHeader.HX_RESWAP.getValue(), htmxResponse.getReswap().toHeaderValue());
}
}

private void addHxTriggerHeaders(HttpServletResponse response, HtmxResponseHeader headerName, Collection<HtmxTrigger> triggers) {
if (triggers.isEmpty()) {
return;
}

// separate event names by commas if no additional details are available
if (triggers.stream().allMatch(t -> t.getEventDetail() == null)) {
String value = triggers.stream()
.map(HtmxTrigger::getEventName)
.collect(Collectors.joining(","));

response.setHeader(headerName.getValue(), value);
return;
}

// multiple events with or without details
var triggerMap = new HashMap<String, Object>();
for (HtmxTrigger trigger : triggers) {
triggerMap.put(trigger.getEventName(), trigger.getEventDetail());
}
setHeaderJsonValue(response, headerName.getValue(), triggerMap);
}

responseHelper.addHxHeaders(htmxResponse, webRequest.getNativeResponse(HttpServletResponse.class));
private void setHeaderJsonValue(HttpServletResponse response, String name, Object value) {
try {
response.setHeader(name, objectMapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to set header " + name + " to " + value, e);
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@Controller
@RequestMapping("/hvhi")
public class HtmxViewHandlerInterceptorController {
public class HtmxResponseHandlerMethodReturnValueHandlerController {

@GetMapping("/hx-location-with-context-data")
public HtmxResponse hxLocationWithContextData() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(HtmxViewHandlerInterceptorController.class)
@WebMvcTest(HtmxResponseHandlerMethodReturnValueHandlerController.class)
@WithMockUser
public class HtmxViewHandlerInterceptorTest {
public class HtmxResponseHandlerMethodReturnValueHandlerTest {

@Autowired
private MockMvc mockMvc;
Expand Down

0 comments on commit 81669e3

Please sign in to comment.