From 92db9338ecb34d061d5a175f80441e135c1de275 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:09:34 +0200 Subject: [PATCH 1/9] Add annotation support for HX-Location --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 56 ++++++++++++++++++- .../boot/mvc/HtmxMvcAutoConfiguration.java | 2 +- .../htmx/spring/boot/mvc/HxLocation.java | 55 ++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 15 +++++ .../htmx/spring/boot/mvc/TestController.java | 15 ++++- 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java diff --git a/README.md b/README.md index ad38bdcc..c997a4a0 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ The first is to use annotations, e.g. `@HxTrigger`, and the second is to use the See [Response Headers Reference](https://htmx.org/reference/#response_headers) for the related htmx documentation. The following annotations are currently supported: +* [@HxLocation](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 7bf23b46..ed525187 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -15,13 +15,24 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + public class HtmxHandlerInterceptor implements HandlerInterceptor { + + private final ObjectMapper objectMapper; + + public HtmxHandlerInterceptor(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + @Override public boolean preHandle(HttpServletRequest request, - HttpServletResponse response, - Object handler) { + HttpServletResponse response, + Object handler) { if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); + setHxLocation(response, method); setHxTrigger(response, method); setHxRefresh(response, method); setVary(request, response); @@ -36,6 +47,18 @@ private void setVary(HttpServletRequest request, HttpServletResponse response) { } } + private void setHxLocation(HttpServletResponse response, Method method) { + HxLocation methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxLocation.class); + if (methodAnnotation != null) { + var location = convertToLocation(methodAnnotation); + if (location.hasContextData()) { + setHeaderJsonValue(response, HtmxResponseHeader.HX_LOCATION.getValue(), location); + } else { + response.setHeader(HtmxResponseHeader.HX_LOCATION.getValue(), location.getPath()); + } + } + } + private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { @@ -62,4 +85,33 @@ private String getHeaderName(HxTriggerLifecycle lifecycle) { throw new IllegalArgumentException("Unknown lifecycle:" + lifecycle); } } + + 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); + } + } + + private HtmxLocation convertToLocation(HxLocation annotation) { + var location = new HtmxLocation(); + location.setPath(annotation.path()); + if (!annotation.source().isEmpty()) { + location.setSource(annotation.source()); + } + if (!annotation.event().isEmpty()) { + location.setEvent(annotation.event()); + } + if (!annotation.handler().isEmpty()) { + location.setHandler(annotation.handler()); + } + if (!annotation.target().isEmpty()) { + location.setTarget(annotation.target()); + } + if (!annotation.target().isEmpty()) { + location.setSwap(annotation.swap()); + } + return location; + } } 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 f11347a5..3524d651 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 @@ -43,7 +43,7 @@ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new HtmxHandlerInterceptor()); + registry.addInterceptor(new HtmxHandlerInterceptor(objectMapper)); } @Override diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java new file mode 100644 index 00000000..ef234108 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java @@ -0,0 +1,55 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to do a client side redirect that does not do a full page reload. + *

+ * Note that this annotation does not provide support for specifying {@code values} or {@code headers}. + * If you want to do this, use {@link HtmxResponse} instead. + * + * @see HX-Location Response Header + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxLocation { + + /** + * The url path to make the redirect. + *

This is an alias for {@link #path}. For example, + * {@code @HxLocation("/foo")} is equivalent to + * {@code @HxLocation(path="/foo")}. + */ + @AliasFor("path") + String value() default ""; + /** + * The url path to make the redirect. + */ + String path() default ""; + /** + * The source element of the request + */ + String source() default ""; + /** + * An event that "triggered" the request + */ + String event() default ""; + /** + * A callback that will handle the response HTML. + */ + String handler() default ""; + /** + * The target to swap the response into. + */ + String target() default ""; + /** + * How the response will be swapped in relative to the target + */ + String swap() default ""; + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index 74b8e34c..c1569c3e 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -81,4 +81,19 @@ public void testHxRefresh() throws Exception { .andExpect(status().isOk()) .andExpect(header().string("HX-Refresh", "true")); } + + @Test + public void testHxLocationWithContextData() throws Exception { + mockMvc.perform(get("/hx-location-with-context-data")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Location", "{\"path\":\"/path\",\"source\":\"source\",\"event\":\"event\",\"handler\":\"handler\",\"target\":\"target\",\"swap\":\"swap\"}")); + } + + @Test + public void testHxLocationWithoutContextData() throws Exception { + mockMvc.perform(get("/hx-location-without-context-data")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Location", "/path")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index f4cc67ea..a9d3a31e 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -50,7 +50,6 @@ public String hxTriggerWithAliasForOverride() { return ""; } - @GetMapping("/hx-refresh") @HxRefresh @ResponseBody @@ -64,4 +63,18 @@ public String hxVary() { return ""; } + @GetMapping("/hx-location-without-context-data") + @HxLocation("/path") + @ResponseBody + public String hxLocationWithoutContextData() { + return ""; + } + + @GetMapping("/hx-location-with-context-data") + @HxLocation(path = "/path", source = "source", event = "event", handler = "handler", target = "target", swap = "swap") + @ResponseBody + public String hxLocationWithContextData() { + return ""; + } + } From e52e3f614c2b7b9047b63ca5afd5a5c64d1067fe Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:24:37 +0200 Subject: [PATCH 2/9] Add annotation support for HX-Replace-Url --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 13 +++++--- .../htmx/spring/boot/mvc/HtmxValue.java | 13 ++++++++ .../htmx/spring/boot/mvc/HxReplaceUrl.java | 32 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 ++++ .../htmx/spring/boot/mvc/TestController.java | 7 ++++ 6 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java diff --git a/README.md b/README.md index c997a4a0..5afa63e5 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ See [Response Headers Reference](https://htmx.org/reference/#response_headers) f The following annotations are currently supported: * [@HxLocation](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) +* [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) >**Note** Please refer to the related Javadoc to learn more about the available options. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index ed525187..2f1576b3 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -1,9 +1,6 @@ package io.github.wimdeblauwe.htmx.spring.boot.mvc; -import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_REFRESH; -import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER; -import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE; -import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP; +import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.*; import java.lang.reflect.Method; @@ -33,6 +30,7 @@ public boolean preHandle(HttpServletRequest request, if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); setHxLocation(response, method); + setHxReplaceUrl(response, method); setHxTrigger(response, method); setHxRefresh(response, method); setVary(request, response); @@ -59,6 +57,13 @@ private void setHxLocation(HttpServletResponse response, Method method) { } } + private void setHxReplaceUrl(HttpServletResponse response, Method method) { + HxReplaceUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReplaceUrl.class); + if (methodAnnotation != null) { + response.setHeader(HX_REPLACE_URL.getValue(), methodAnnotation.value()); + } + } + private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java new file mode 100644 index 00000000..36356d9d --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java @@ -0,0 +1,13 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +/** + * Holder for constant values. + */ +public class HtmxValue { + + /** + * Constant for use in annotations that support a {@code false} value to disable functions. + */ + public static final String FALSE = "false"; + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java new file mode 100644 index 00000000..f0086ab6 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java @@ -0,0 +1,32 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to replace the current URL in the browser + * location history. + *

+ * The possible values are: + *

+ * + * @see HX-Replace-Url + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReplaceUrl { + + /** + * The value for the {@code HX-Replace-Url} response header. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index c1569c3e..88030549 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -96,4 +96,11 @@ public void testHxLocationWithoutContextData() throws Exception { .andExpect(header().string("HX-Location", "/path")); } + @Test + public void testHxReplaceUrl() throws Exception { + mockMvc.perform(get("/hx-replace-url")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Replace-Url", "/path")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index a9d3a31e..fb3e71aa 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -77,4 +77,11 @@ public String hxLocationWithContextData() { return ""; } + @GetMapping("/hx-replace-url") + @HxReplaceUrl("/path") + @ResponseBody + public String hxReplaceUrl() { + return ""; + } + } From 0708d1c5bce18012c1efcd5c1c215c1258c9f167 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:28:47 +0200 Subject: [PATCH 3/9] Add annotation support for HX-Redirect --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 8 +++++++ .../htmx/spring/boot/mvc/HxRedirect.java | 22 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 ++++++ .../htmx/spring/boot/mvc/TestController.java | 7 ++++++ 5 files changed, 45 insertions(+) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java diff --git a/README.md b/README.md index 5afa63e5..e7300b66 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ See [Response Headers Reference](https://htmx.org/reference/#response_headers) f The following annotations are currently supported: * [@HxLocation](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.html) +* [@HxRedirect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 2f1576b3..3eda005f 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -30,6 +30,7 @@ public boolean preHandle(HttpServletRequest request, if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); setHxLocation(response, method); + setHxRedirect(response, method); setHxReplaceUrl(response, method); setHxTrigger(response, method); setHxRefresh(response, method); @@ -57,6 +58,13 @@ private void setHxLocation(HttpServletResponse response, Method method) { } } + private void setHxRedirect(HttpServletResponse response, Method method) { + HxRedirect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRedirect.class); + if (methodAnnotation != null) { + response.setHeader(HX_REDIRECT.getValue(), methodAnnotation.value()); + } + } + private void setHxReplaceUrl(HttpServletResponse response, Method method) { HxReplaceUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReplaceUrl.class); if (methodAnnotation != null) { diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java new file mode 100644 index 00000000..4edf356a --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java @@ -0,0 +1,22 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to do a client-side redirect to a new location. + * + * @see HX-Redirect + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxRedirect { + + /** + * The URL to use to do a client-side redirect to a new location. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index 88030549..c7737216 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -96,6 +96,13 @@ public void testHxLocationWithoutContextData() throws Exception { .andExpect(header().string("HX-Location", "/path")); } + @Test + public void testHxRedirect() throws Exception { + mockMvc.perform(get("/hx-redirect")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Redirect", "/path")); + } + @Test public void testHxReplaceUrl() throws Exception { mockMvc.perform(get("/hx-replace-url")) diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index fb3e71aa..2efb6453 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -77,6 +77,13 @@ public String hxLocationWithContextData() { return ""; } + @GetMapping("/hx-redirect") + @HxRedirect("/path") + @ResponseBody + public String hxRedirect() { + return ""; + } + @GetMapping("/hx-replace-url") @HxReplaceUrl("/path") @ResponseBody From 788b1c06729bacc7e7c369e9964ac45e8ca8bd35 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:33:10 +0200 Subject: [PATCH 4/9] Add annotation support for HX-Push-Url --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 8 +++++ .../htmx/spring/boot/mvc/HtmxValue.java | 5 +++ .../htmx/spring/boot/mvc/HxPushUrl.java | 31 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 +++++ .../htmx/spring/boot/mvc/TestController.java | 7 +++++ 6 files changed, 59 insertions(+) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java diff --git a/README.md b/README.md index e7300b66..1c6d2bbb 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ See [Response Headers Reference](https://htmx.org/reference/#response_headers) f The following annotations are currently supported: * [@HxLocation](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.html) +* [@HxPushUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.html) * [@HxRedirect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 3eda005f..e1c45ccf 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -30,6 +30,7 @@ public boolean preHandle(HttpServletRequest request, if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); setHxLocation(response, method); + setHxPushUrl(response, method); setHxRedirect(response, method); setHxReplaceUrl(response, method); setHxTrigger(response, method); @@ -58,6 +59,13 @@ private void setHxLocation(HttpServletResponse response, Method method) { } } + private void setHxPushUrl(HttpServletResponse response, Method method) { + HxPushUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxPushUrl.class); + if (methodAnnotation != null) { + response.setHeader(HX_PUSH_URL.getValue(), methodAnnotation.value()); + } + } + private void setHxRedirect(HttpServletResponse response, Method method) { HxRedirect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRedirect.class); if (methodAnnotation != null) { diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java index 36356d9d..38f8a56a 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java @@ -10,4 +10,9 @@ public class HtmxValue { */ public static final String FALSE = "false"; + /** + * Constant for use in annotations that support a {@code true} value to enable functions. + */ + public static final String TRUE = "true"; + } diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java new file mode 100644 index 00000000..9af108e3 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java @@ -0,0 +1,31 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to push a URL into the browser + * location history. + *

+ * The possible values are: + *

    + *
  • {@link HtmxValue#TRUE}, which pushes the fetched URL into history.
  • + *
  • {@link HtmxValue#FALSE}, which disables pushing the fetched URL if it would otherwise be pushed.
  • + *
  • A URL to be pushed into the location bar. This may be relative or absolute, + * as per history.pushState().
  • + *
+ * + * @see HX-Push-Url Response Header + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxPushUrl { + + /** + * The value for the {@code HX-Push-Url} response header. + */ + String value() default HtmxValue.TRUE; + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index c7737216..48c35b55 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -96,6 +96,13 @@ public void testHxLocationWithoutContextData() throws Exception { .andExpect(header().string("HX-Location", "/path")); } + @Test + public void testHxPushUrl() throws Exception { + mockMvc.perform(get("/hx-push-url")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Push-Url", "/path")); + } + @Test public void testHxRedirect() throws Exception { mockMvc.perform(get("/hx-redirect")) diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index 2efb6453..c32bdfe8 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -77,6 +77,13 @@ public String hxLocationWithContextData() { return ""; } + @GetMapping("/hx-push-url") + @HxPushUrl("/path") + @ResponseBody + public String hxPushUrl() { + return ""; + } + @GetMapping("/hx-redirect") @HxRedirect("/path") @ResponseBody From 7dcfdfef43b66178d07171ddf3800a14e19855e8 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:48:43 +0200 Subject: [PATCH 5/9] Add annotation support for HX-Reswap --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 49 +++++++++++ .../htmx/spring/boot/mvc/HxReswap.java | 85 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 ++ .../htmx/spring/boot/mvc/TestController.java | 7 ++ 5 files changed, 149 insertions(+) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java diff --git a/README.md b/README.md index 1c6d2bbb..cbf8ab75 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ The following annotations are currently supported: * [@HxRedirect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) +* [@HxReswap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) >**Note** Please refer to the related Javadoc to learn more about the available options. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index e1c45ccf..e6309f34 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -3,6 +3,7 @@ import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.*; import java.lang.reflect.Method; +import java.time.Duration; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.HttpHeaders; @@ -33,6 +34,7 @@ public boolean preHandle(HttpServletRequest request, setHxPushUrl(response, method); setHxRedirect(response, method); setHxReplaceUrl(response, method); + setHxReswap(response, method); setHxTrigger(response, method); setHxRefresh(response, method); setVary(request, response); @@ -80,6 +82,13 @@ private void setHxReplaceUrl(HttpServletResponse response, Method method) { } } + private void setHxReswap(HttpServletResponse response, Method method) { + HxReswap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReswap.class); + if (methodAnnotation != null) { + response.setHeader(HX_RESWAP.getValue(), convertToReswap(methodAnnotation)); + } + } + private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { @@ -135,4 +144,44 @@ private HtmxLocation convertToLocation(HxLocation annotation) { } return location; } + + private String convertToReswap(HxReswap annotation) { + + var reswap = new HtmxReswap(annotation.value()); + if (annotation.swap() != -1) { + reswap.swap(Duration.ofMillis(annotation.swap())); + } + if (annotation.settle() != -1) { + reswap.swap(Duration.ofMillis(annotation.settle())); + } + if (annotation.transition()) { + reswap.transition(); + } + if (annotation.focusScroll() != HxReswap.FocusScroll.UNDEFINED) { + reswap.focusScroll(annotation.focusScroll() == HxReswap.FocusScroll.TRUE); + } + if (annotation.show() != HxReswap.Position.UNDEFINED) { + reswap.show(convertToPosition(annotation.show())); + if (!annotation.showTarget().isEmpty()) { + reswap.scrollTarget(annotation.showTarget()); + } + } + if (annotation.scroll() != HxReswap.Position.UNDEFINED) { + reswap.scroll(convertToPosition(annotation.scroll())); + if (!annotation.scrollTarget().isEmpty()) { + reswap.scrollTarget(annotation.scrollTarget()); + } + } + + return reswap.toString(); + } + + private HtmxReswap.Position convertToPosition(HxReswap.Position position) { + return switch (position) { + case TOP -> HtmxReswap.Position.TOP; + case BOTTOM -> HtmxReswap.Position.BOTTOM; + default -> throw new IllegalStateException("Unexpected value: " + position); + }; + } + } diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java new file mode 100644 index 00000000..813ee4a2 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java @@ -0,0 +1,85 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify how the response will be swapped. + * See hx-swap for possible values. + * + * @see HX-Reswap + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReswap { + + /** + * A value to specify how the response will be swapped. + * + * @see hx-swap + */ + HxSwapType value() default HxSwapType.INNER_HTML; + + /** + * Set the time in milliseconds that should elapse after receiving a response to swap the content. + */ + long swap() default -1; + + /** + * Set the time in milliseconds that should elapse between the swap and the settle logic. + */ + long settle() default -1; + + /** + * Changes the scrolling behavior of the target element. + */ + Position scroll() default Position.UNDEFINED; + + /** + * Used to target a different element for scrolling. + */ + String scrollTarget() default ""; + + /** + * Changes the scrolling behavior of the target element. + */ + Position show() default Position.UNDEFINED; + + /** + * Used to target a different element for showing. + */ + String showTarget() default ""; + + /** + * Enables the use of the new + * View Transitions API + * when a swap occurs. + */ + boolean transition() default false; + + /** + * Enable or disable auto-scrolling to focused inputs between requests. + */ + FocusScroll focusScroll() default FocusScroll.UNDEFINED; + + /** + * Represents the values for {@link #focusScroll()} + */ + public enum FocusScroll { + TRUE, + FALSE, + UNDEFINED + } + + /** + * Represents the position values for {@link #show()} and {@link #scroll()} + */ + public enum Position { + TOP, + BOTTOM, + UNDEFINED + } + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index 48c35b55..77b8f3ed 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -117,4 +117,11 @@ public void testHxReplaceUrl() throws Exception { .andExpect(header().string("HX-Replace-Url", "/path")); } + @Test + public void testHxReswap() throws Exception { + mockMvc.perform(get("/hx-reswap")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Reswap", "innerHTML swap:300ms")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index c32bdfe8..9242a9d0 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -98,4 +98,11 @@ public String hxReplaceUrl() { return ""; } + @GetMapping("/hx-reswap") + @HxReswap(value = HxSwapType.INNER_HTML, swap = 300) + @ResponseBody + public String hxReswap() { + return ""; + } + } From c8669d30b7a09866802016dc7f6359f2669801f8 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 16:51:50 +0200 Subject: [PATCH 6/9] Add annotation support for HX-Retarget --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 8 +++++++ .../htmx/spring/boot/mvc/HxRetarget.java | 23 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 ++++++ .../htmx/spring/boot/mvc/TestController.java | 7 ++++++ 5 files changed, 46 insertions(+) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java diff --git a/README.md b/README.md index cbf8ab75..92bf0fb4 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ The following annotations are currently supported: * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) * [@HxReswap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.html) +* [@HxRetarget](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) >**Note** Please refer to the related Javadoc to learn more about the available options. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index e6309f34..910489fc 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -35,6 +35,7 @@ public boolean preHandle(HttpServletRequest request, setHxRedirect(response, method); setHxReplaceUrl(response, method); setHxReswap(response, method); + setHxRetarget(response, method); setHxTrigger(response, method); setHxRefresh(response, method); setVary(request, response); @@ -89,6 +90,13 @@ private void setHxReswap(HttpServletResponse response, Method method) { } } + private void setHxRetarget(HttpServletResponse response, Method method) { + HxRetarget methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRetarget.class); + if (methodAnnotation != null) { + response.setHeader(HX_RETARGET.getValue(), methodAnnotation.value()); + } + } + private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java new file mode 100644 index 00000000..1e85ec86 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java @@ -0,0 +1,23 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify a CSS selector that updates the target of + * the content update to a different element on the page. + * + * @see HX-Retarget + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxRetarget { + + /** + * A CSS selector that updates the target of the content update to a different element on the page. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index 77b8f3ed..ebf4e880 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -124,4 +124,11 @@ public void testHxReswap() throws Exception { .andExpect(header().string("HX-Reswap", "innerHTML swap:300ms")); } + @Test + public void testHxRetarget() throws Exception { + mockMvc.perform(get("/hx-retarget")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Retarget", "#target")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index 9242a9d0..9f103595 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -105,4 +105,11 @@ public String hxReswap() { return ""; } + @GetMapping("/hx-retarget") + @HxRetarget("#target") + @ResponseBody + public String hxRetarget() { + return ""; + } + } From 9e8f23564a35e39e16c319e911300f2eea508eae Mon Sep 17 00:00:00 2001 From: xhaggi Date: Mon, 28 Aug 2023 17:00:44 +0200 Subject: [PATCH 7/9] Add annotation support for HX-Reselect --- README.md | 1 + .../boot/mvc/HtmxHandlerInterceptor.java | 8 +++++++ .../htmx/spring/boot/mvc/HxReselect.java | 24 +++++++++++++++++++ .../boot/mvc/HtmxHandlerInterceptorTest.java | 7 ++++++ .../htmx/spring/boot/mvc/TestController.java | 7 ++++++ 5 files changed, 47 insertions(+) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java diff --git a/README.md b/README.md index 92bf0fb4..c703c2a8 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ The following annotations are currently supported: * [@HxRedirect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.html) * [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html) * [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html) +* [@HxReselect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.html) * [@HxReswap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.html) * [@HxRetarget](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 910489fc..0fe27791 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -36,6 +36,7 @@ public boolean preHandle(HttpServletRequest request, setHxReplaceUrl(response, method); setHxReswap(response, method); setHxRetarget(response, method); + setHxReselect(response, method); setHxTrigger(response, method); setHxRefresh(response, method); setVary(request, response); @@ -97,6 +98,13 @@ private void setHxRetarget(HttpServletResponse response, Method method) { } } + private void setHxReselect(HttpServletResponse response, Method method) { + HxReselect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReselect.class); + if (methodAnnotation != null) { + response.setHeader(HX_RESELECT.getValue(), methodAnnotation.value()); + } + } + private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java new file mode 100644 index 00000000..08bc4769 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java @@ -0,0 +1,24 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify a CSS selector that allows you to choose which part + * of the response is used to be swapped in. + * + * @see HX-Retarget + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReselect { + + /** + * A CSS selector that allows you to choose which part of the response is used to be swapped in. + *

Overrides an existing hx-select on the triggering element. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index ebf4e880..ef98d93a 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -131,4 +131,11 @@ public void testHxRetarget() throws Exception { .andExpect(header().string("HX-Retarget", "#target")); } + @Test + public void testHxReselect() throws Exception { + mockMvc.perform(get("/hx-reselect")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Reselect", "#target")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index 9f103595..e9dd1433 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -112,4 +112,11 @@ public String hxRetarget() { return ""; } + @GetMapping("/hx-reselect") + @HxReselect("#target") + @ResponseBody + public String hxReselect() { + return ""; + } + } From 381de8604969cf5210d483cdef4cd59a2017e072 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Wed, 1 May 2024 21:23:22 +0200 Subject: [PATCH 8/9] Add dedicated annotations for HX-Trigger-After-Settle and HX-Trigger-After-Swap and support multiple events --- README.md | 2 + .../boot/mvc/HtmxHandlerInterceptor.java | 23 +++++++++- .../htmx/spring/boot/mvc/HxTrigger.java | 14 +++++- .../spring/boot/mvc/HxTriggerAfterSettle.java | 28 +++++++++++ .../spring/boot/mvc/HxTriggerAfterSwap.java | 28 +++++++++++ .../spring/boot/mvc/HxTriggerLifecycle.java | 2 + .../boot/mvc/HtmxHandlerInterceptorTest.java | 44 ++++++++++++++++++ .../htmx/spring/boot/mvc/TestController.java | 46 ++++++++++++++++++- 8 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSettle.java create mode 100644 htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSwap.java diff --git a/README.md b/README.md index c703c2a8..ab247590 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ The following annotations are currently supported: * [@HxReswap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.html) * [@HxRetarget](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.html) * [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html) +* [@HxTriggerAfterSettle](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSettle.html) +* [@HxTriggerAfterSwap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSwap.html) >**Note** Please refer to the related Javadoc to learn more about the available options. diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 0fe27791..29329d89 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -28,6 +28,7 @@ public HtmxHandlerInterceptor(ObjectMapper objectMapper) { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (handler instanceof HandlerMethod) { Method method = ((HandlerMethod) handler).getMethod(); setHxLocation(response, method); @@ -38,6 +39,8 @@ public boolean preHandle(HttpServletRequest request, setHxRetarget(response, method); setHxReselect(response, method); setHxTrigger(response, method); + setHxTriggerAfterSettle(response, method); + setHxTriggerAfterSwap(response, method); setHxRefresh(response, method); setVary(request, response); } @@ -108,7 +111,21 @@ private void setHxReselect(HttpServletResponse response, Method method) { private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { - response.setHeader(getHeaderName(methodAnnotation.lifecycle()), methodAnnotation.value()); + setHeader(response, getHeaderName(methodAnnotation.lifecycle()), methodAnnotation.value()); + } + } + + private void setHxTriggerAfterSettle(HttpServletResponse response, Method method) { + HxTriggerAfterSettle methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSettle.class); + if (methodAnnotation != null) { + setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE.getValue(), methodAnnotation.value()); + } + } + + private void setHxTriggerAfterSwap(HttpServletResponse response, Method method) { + HxTriggerAfterSwap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSwap.class); + if (methodAnnotation != null) { + setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP.getValue(), methodAnnotation.value()); } } @@ -140,6 +157,10 @@ private void setHeaderJsonValue(HttpServletResponse response, String name, Objec } } + private void setHeader(HttpServletResponse response, String name, String[] values) { + response.setHeader(name, String.join(",", values)); + } + private HtmxLocation convertToLocation(HxLocation annotation) { var location = new HtmxLocation(); location.setPath(annotation.path()); diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.java index 57428e84..a0931120 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.java @@ -6,14 +6,24 @@ import java.lang.annotation.Target; /** - * Annotation to trigger client side actions on the target element within a response to htmx. + * Annotation to trigger client side events as soon as the response is received on the target element. + *
+ * You can trigger a single event or as many uniquely named events as you would like. * * @see HX-Trigger Response Headers */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface HxTrigger { - String value(); + /** + * The events to trigger as soon as the response is received on the target element. + */ + String[] value(); + + /** + * @deprecated use annotation {@link HxTriggerAfterSettle} or {@link HxTriggerAfterSwap} instead. + */ + @Deprecated HxTriggerLifecycle lifecycle() default HxTriggerLifecycle.RECEIVE; } diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSettle.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSettle.java new file mode 100644 index 00000000..bd44d208 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSettle.java @@ -0,0 +1,28 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to trigger client side events after the + * settling step + * on the target element. + *
+ * You can trigger a single event or as many uniquely named events as you would like. + * + * @see HX-Trigger Response Headers + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxTriggerAfterSettle { + + /** + * The events to trigger after the + * settling step + * on the target element. + */ + String[] value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSwap.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSwap.java new file mode 100644 index 00000000..3d1c511d --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerAfterSwap.java @@ -0,0 +1,28 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to trigger client side events after the + * swap step + * on the target element. + *
+ * You can trigger a single event or as many uniquely named events as you would like. + * + * @see HX-Trigger Response Headers + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxTriggerAfterSwap { + + /** + * The events to trigger after the + * swap step + * on the target element. + */ + String[] value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerLifecycle.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerLifecycle.java index 775a2d47..418de349 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerLifecycle.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTriggerLifecycle.java @@ -4,7 +4,9 @@ * Represents the HX-Trigger Response Headers. * * @see HX-Trigger Response Headers + * @deprecated use annotation {@link HxTriggerAfterSettle} or {@link HxTriggerAfterSwap} instead. */ +@Deprecated public enum HxTriggerLifecycle { /** * Trigger events as soon as the response is received. diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index ef98d93a..d772088e 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -26,6 +26,13 @@ public void testHeaderIsSetOnResponseIfHxTriggerIsPresent() throws Exception { .andExpect(header().string("HX-Trigger", "eventTriggered")); } + @Test + public void testHeaderIsSetOnResponseWithMultipleEventsIfHxTriggerIsPresent() throws Exception { + mockMvc.perform(get("/with-trigger-multiple-events")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger", "event1,event2")); + } + @Test public void testAfterSettleHeaderIsSetOnResponseIfHxTriggerIsPresent() throws Exception { mockMvc.perform(get("/with-trigger-settle")) @@ -40,6 +47,43 @@ public void testAfterSwapHeaderIsSetOnResponseIfHxTriggerIsPresent() throws Exce .andExpect(header().string("HX-Trigger-After-Swap", "eventTriggered")); } + @Test + public void testAfterSettleHeaderIsSetOnResponseIfHxTriggerAfterSettleIsPresent() throws Exception { + mockMvc.perform(get("/with-trigger-after-settle")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger-After-Settle", "eventTriggered")); + } + + @Test + public void testAfterSettleHeaderIsSetOnResponseWithMultipleEventsIfHxTriggerAfterSettleIsPresent() throws Exception { + mockMvc.perform(get("/with-trigger-after-settle-multiple-events")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger-After-Settle", "event1,event2")); + } + + @Test + public void testAfterSwapHeaderIsSetOnResponseIfHxTriggerAfterSwapIsPresent() throws Exception { + mockMvc.perform(get("/with-trigger-after-swap")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger-After-Swap", "eventTriggered")); + } + + @Test + public void testAfterSwapHeaderIsSetOnResponseWithMultipleEventsIfHxTriggerAfterSwapIsPresent() throws Exception { + mockMvc.perform(get("/with-trigger-after-swap-multiple-events")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger-After-Swap", "event1,event2")); + } + + @Test + public void testHeadersAreSetOnResponseIfHxTriggersArePresent() throws Exception { + mockMvc.perform(get("/with-triggers")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Trigger", "event1,event2")) + .andExpect(header().string("HX-Trigger-After-Settle", "event1,event2")) + .andExpect(header().string("HX-Trigger-After-Swap", "event1,event2")); + } + @Test public void testHeaderIsNotSetOnResponseIfHxTriggerNotPresent() throws Exception { mockMvc.perform(get("/without-trigger")) diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index e9dd1433..85bfbc84 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -22,20 +22,64 @@ public String methodWithHxTrigger() { return ""; } + @GetMapping("/with-trigger-multiple-events") + @HxTrigger({ "event1", "event2" }) + @ResponseBody + public String methodWithHxTriggerAndMultipleEvents() { + return ""; + } + @GetMapping("/with-trigger-settle") @HxTrigger(value = "eventTriggered", lifecycle = HxTriggerLifecycle.SETTLE) @ResponseBody - public String methodWithHxTriggerAfterSettle() { + public String methodWithHxTriggerAndLifecycleSettle() { return ""; } @GetMapping("/with-trigger-swap") @HxTrigger(value = "eventTriggered", lifecycle = HxTriggerLifecycle.SWAP) @ResponseBody + public String methodWithHxTriggerAndLifecycleSwap() { + return ""; + } + + @GetMapping("/with-trigger-after-settle") + @HxTriggerAfterSettle("eventTriggered") + @ResponseBody + public String methodWithHxTriggerAfterSettle() { + return ""; + } + + @GetMapping("/with-trigger-after-settle-multiple-events") + @HxTriggerAfterSettle({ "event1", "event2" }) + @ResponseBody + public String methodWithHxTriggerAfterSettleAndMultipleEvents() { + return ""; + } + + @GetMapping("/with-trigger-after-swap") + @HxTriggerAfterSwap("eventTriggered") + @ResponseBody public String methodWithHxTriggerAfterSwap() { return ""; } + @GetMapping("/with-trigger-after-swap-multiple-events") + @HxTriggerAfterSwap({ "event1", "event2" }) + @ResponseBody + public String methodWithHxTriggerAfterSwapAndMultipleEvents() { + return ""; + } + + @GetMapping("/with-triggers") + @HxTrigger({ "event1", "event2" }) + @HxTriggerAfterSettle({ "event1", "event2" }) + @HxTriggerAfterSwap({ "event1", "event2" }) + @ResponseBody + public String methodWithHxTriggers() { + return ""; + } + @GetMapping("/updates-sidebar") @HxUpdatesSidebar @ResponseBody From eb12958af34c38f7132d9f549c48006658bba76a Mon Sep 17 00:00:00 2001 From: xhaggi Date: Thu, 2 May 2024 10:04:26 +0200 Subject: [PATCH 9/9] Add and adjust methods to use HtmxResponseHeader instead of String to set a header --- .../boot/mvc/HtmxHandlerInterceptor.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java index 29329d89..c393a03d 100644 --- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java @@ -59,9 +59,9 @@ private void setHxLocation(HttpServletResponse response, Method method) { if (methodAnnotation != null) { var location = convertToLocation(methodAnnotation); if (location.hasContextData()) { - setHeaderJsonValue(response, HtmxResponseHeader.HX_LOCATION.getValue(), location); + setHeaderJsonValue(response, HtmxResponseHeader.HX_LOCATION, location); } else { - response.setHeader(HtmxResponseHeader.HX_LOCATION.getValue(), location.getPath()); + setHeader(response, HtmxResponseHeader.HX_LOCATION, location.getPath()); } } } @@ -69,96 +69,100 @@ private void setHxLocation(HttpServletResponse response, Method method) { private void setHxPushUrl(HttpServletResponse response, Method method) { HxPushUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxPushUrl.class); if (methodAnnotation != null) { - response.setHeader(HX_PUSH_URL.getValue(), methodAnnotation.value()); + setHeader(response, HX_PUSH_URL, methodAnnotation.value()); } } private void setHxRedirect(HttpServletResponse response, Method method) { HxRedirect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRedirect.class); if (methodAnnotation != null) { - response.setHeader(HX_REDIRECT.getValue(), methodAnnotation.value()); + setHeader(response, HX_REDIRECT, methodAnnotation.value()); } } private void setHxReplaceUrl(HttpServletResponse response, Method method) { HxReplaceUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReplaceUrl.class); if (methodAnnotation != null) { - response.setHeader(HX_REPLACE_URL.getValue(), methodAnnotation.value()); + setHeader(response, HX_REPLACE_URL, methodAnnotation.value()); } } private void setHxReswap(HttpServletResponse response, Method method) { HxReswap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReswap.class); if (methodAnnotation != null) { - response.setHeader(HX_RESWAP.getValue(), convertToReswap(methodAnnotation)); + setHeader(response, HX_RESWAP, convertToReswap(methodAnnotation)); } } private void setHxRetarget(HttpServletResponse response, Method method) { HxRetarget methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRetarget.class); if (methodAnnotation != null) { - response.setHeader(HX_RETARGET.getValue(), methodAnnotation.value()); + setHeader(response, HX_RETARGET, methodAnnotation.value()); } } private void setHxReselect(HttpServletResponse response, Method method) { HxReselect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReselect.class); if (methodAnnotation != null) { - response.setHeader(HX_RESELECT.getValue(), methodAnnotation.value()); + setHeader(response, HX_RESELECT, methodAnnotation.value()); } } private void setHxTrigger(HttpServletResponse response, Method method) { HxTrigger methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTrigger.class); if (methodAnnotation != null) { - setHeader(response, getHeaderName(methodAnnotation.lifecycle()), methodAnnotation.value()); + setHeader(response, convertToHeader(methodAnnotation.lifecycle()), methodAnnotation.value()); } } private void setHxTriggerAfterSettle(HttpServletResponse response, Method method) { HxTriggerAfterSettle methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSettle.class); if (methodAnnotation != null) { - setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE.getValue(), methodAnnotation.value()); + setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE, methodAnnotation.value()); } } private void setHxTriggerAfterSwap(HttpServletResponse response, Method method) { HxTriggerAfterSwap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSwap.class); if (methodAnnotation != null) { - setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP.getValue(), methodAnnotation.value()); + setHeader(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP, methodAnnotation.value()); } } private void setHxRefresh(HttpServletResponse response, Method method) { HxRefresh methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRefresh.class); if (methodAnnotation != null) { - response.setHeader(HX_REFRESH.getValue(), "true"); + setHeader(response, HX_REFRESH, HtmxValue.TRUE); } } - private String getHeaderName(HxTriggerLifecycle lifecycle) { + private HtmxResponseHeader convertToHeader(HxTriggerLifecycle lifecycle) { switch (lifecycle) { case RECEIVE: - return HX_TRIGGER.getValue(); + return HX_TRIGGER; case SETTLE: - return HX_TRIGGER_AFTER_SETTLE.getValue(); + return HX_TRIGGER_AFTER_SETTLE; case SWAP: - return HX_TRIGGER_AFTER_SWAP.getValue(); + return HX_TRIGGER_AFTER_SWAP; default: throw new IllegalArgumentException("Unknown lifecycle:" + lifecycle); } } - private void setHeaderJsonValue(HttpServletResponse response, String name, Object value) { + private void setHeaderJsonValue(HttpServletResponse response, HtmxResponseHeader header, Object value) { try { - response.setHeader(name, objectMapper.writeValueAsString(value)); + response.setHeader(header.getValue(), objectMapper.writeValueAsString(value)); } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Unable to set header " + name + " to " + value, e); + throw new IllegalArgumentException("Unable to set header " + header.getValue() + " to " + value, e); } } - private void setHeader(HttpServletResponse response, String name, String[] values) { - response.setHeader(name, String.join(",", values)); + private void setHeader(HttpServletResponse response, HtmxResponseHeader header, String value) { + response.setHeader(header.getValue(), value); + } + + private void setHeader(HttpServletResponse response, HtmxResponseHeader header, String[] values) { + response.setHeader(header.getValue(), String.join(",", values)); } private HtmxLocation convertToLocation(HxLocation annotation) {