From 680123c57048d1edd9e4dac4636eae3723f20c33 Mon Sep 17 00:00:00 2001 From: xhaggi Date: Wed, 1 May 2024 21:23:22 +0200 Subject: [PATCH] Add dedicated annoations for HX-Trigger-After-Settle and HX-Trigger-After-Swap and support multiple events --- .../boot/mvc/HtmxHandlerInterceptor.java | 24 +++++++++++- .../htmx/spring/boot/mvc/HxTrigger.java | 7 +++- .../spring/boot/mvc/HxTriggerAfterSettle.java | 28 ++++++++++++++ .../spring/boot/mvc/HxTriggerAfterSwap.java | 28 ++++++++++++++ .../spring/boot/mvc/HxTriggerLifecycle.java | 2 + .../boot/mvc/HtmxHandlerInterceptorTest.java | 35 ++++++++++++++++++ .../htmx/spring/boot/mvc/TestController.java | 37 ++++++++++++++++++- 7 files changed, 157 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/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 0fe2779..0317131 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 @@ -38,6 +38,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 +110,27 @@ 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()); + response.setHeader( + getHeaderName(methodAnnotation.lifecycle()), + String.join( ",", methodAnnotation.value())); + } + } + + private void setHxTriggerAfterSettle(HttpServletResponse response, Method method) { + HxTriggerAfterSettle methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSettle.class); + if (methodAnnotation != null) { + response.setHeader( + HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE.getValue(), + String.join( ",", methodAnnotation.value())); + } + } + + private void setHxTriggerAfterSwap(HttpServletResponse response, Method method) { + HxTriggerAfterSwap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxTriggerAfterSwap.class); + if (methodAnnotation != null) { + response.setHeader( + HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP.getValue(), + String.join( ",", methodAnnotation.value())); } } 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 57428e8..38f2d20 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,17 @@ 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(); + + String[] value(); 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 0000000..bd44d20 --- /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 0000000..3d1c511 --- /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 775a2d4..418de34 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 ef98d93..18a9c65 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,34 @@ 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 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 e9dd143..8026655 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,55 @@ 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("/updates-sidebar") @HxUpdatesSidebar @ResponseBody