Skip to content

Commit

Permalink
Merge pull request #83 from xhaggi/hx-reswap-modifiers
Browse files Browse the repository at this point in the history
Add support for specifying modifiers in HX-Reswap
  • Loading branch information
wimdeblauwe authored Oct 12, 2023
2 parents ad68c8e + ef2356f commit 9f1137a
Show file tree
Hide file tree
Showing 7 changed files with 506 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public final class HtmxResponse {
private boolean refresh;
private String redirect;
private String pushUrl;
private HxSwapType reswap;
private HtmxReswap reswap;

/**
* Return a builder to build a {@link HtmxResponse}.
Expand All @@ -52,7 +52,7 @@ public HtmxResponse() {
this.triggersAfterSwap = new LinkedHashSet<>();
}

HtmxResponse(Set<ModelAndView> views, Set<HtmxTrigger> triggers, Set<HtmxTrigger> triggersAfterSettle, Set<HtmxTrigger> triggersAfterSwap, String retarget, boolean refresh, String redirect, String pushUrl, HxSwapType reswap) {
HtmxResponse(Set<ModelAndView> views, Set<HtmxTrigger> triggers, Set<HtmxTrigger> triggersAfterSettle, Set<HtmxTrigger> triggersAfterSwap, String retarget, boolean refresh, String redirect, String pushUrl, HtmxReswap reswap) {
this.views = views;
this.triggers = triggers;
this.triggersAfterSettle = triggersAfterSettle;
Expand Down Expand Up @@ -221,7 +221,7 @@ public HtmxResponse retarget(String cssSelector) {
@Deprecated
public HtmxResponse reswap(HxSwapType swapType) {
Assert.notNull(swapType, "swapType should not be null");
this.reswap = swapType;
this.reswap = new HtmxReswap(swapType);
return this;
}

Expand Down Expand Up @@ -256,7 +256,7 @@ public HtmxResponse and(HtmxResponse otherResponse) {
this.retarget = otherResponse.getRetarget();
}
if (otherResponse.getReswap() != null) {
this.reswap = otherResponse.getReswap();
this.reswap = otherResponse.reswap;
}

return this;
Expand Down Expand Up @@ -299,7 +299,7 @@ String getHeaderPushHistory() {
*/
@Deprecated
public String getHeaderReswap() {
return reswap.getValue();
return reswap != null ? reswap.getType().getValue() : null;
}

/**
Expand All @@ -318,7 +318,7 @@ public String getRedirect() {
return redirect;
}

public HxSwapType getReswap() {
public HtmxReswap getReswap() {
return reswap;
}

Expand Down Expand Up @@ -379,7 +379,7 @@ public static final class Builder {
private String pushUrl;
private String redirect;
private boolean refresh;
private HxSwapType reswap;
private HtmxReswap reswap;
private String retarget;

/**
Expand Down Expand Up @@ -465,10 +465,24 @@ public Builder refresh() {
*
* @param swapType the swap style
* @return the builder
* @deprecated use {@link #reswap(HtmxReswap)} instead. Will be removed in 4.0.
*/
@Deprecated
public Builder reswap(HxSwapType swapType) {
Assert.notNull(swapType, "swapType should not be null");
this.reswap = swapType;
this.reswap = new HtmxReswap(swapType);
return this;
}

/**
* Set a new swap to specify how the response will be swapped.
*
* @param reswap the reswap options.
* @return the builder
*/
public Builder reswap(HtmxReswap reswap) {
Assert.notNull(reswap, "reswap should not be null");
this.reswap = reswap;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package io.github.wimdeblauwe.htmx.spring.boot.mvc;

import java.time.Duration;
import java.util.Objects;

/**
* Represents a HX-Reswap response header value.
*
* @see <a href="https://htmx.org/attributes/hx-swap/">hx-swap</a>
* @since 3.1
*/
public class HtmxReswap {

private final HxSwapType type;
private Duration swap;
private Duration settle;
private Position scroll;
private String scrollTarget;
private Position show;
private String showTarget;
private boolean transition;
private Boolean focusScroll;

/**
* Insert the response before the first child of the target element.
*/
public static HtmxReswap afterBegin() {
return new HtmxReswap(HxSwapType.AFTER_BEGIN);
}

/**
* Insert the response after the target element.
*/
public static HtmxReswap afterEnd() {
return new HtmxReswap(HxSwapType.AFTER_END);
}

/**
* Insert the response before the target element.
*/
public static HtmxReswap beforeBegin() {
return new HtmxReswap(HxSwapType.BEFORE_BEGIN);
}

/**
* Insert the response after the last child of the target element.
*/
public static HtmxReswap beforeEnd() {
return new HtmxReswap(HxSwapType.BEFORE_END);
}

/**
* Deletes the target element regardless of the response.
*/
public static HtmxReswap delete() {
return new HtmxReswap(HxSwapType.DELETE);
}

/**
* Replace the inner html of the target element.
*/
public static HtmxReswap innerHtml() {
return new HtmxReswap(HxSwapType.INNER_HTML);
}

/**
* Does not append the response to target element (out of band items will still be processed).
*/
public static HtmxReswap none() {
return new HtmxReswap(HxSwapType.NONE);
}

/**
* Replace the entire target element with the response.
*/
public static HtmxReswap outerHtml() {
return new HtmxReswap(HxSwapType.OUTER_HTML);
}

HtmxReswap(HxSwapType type) {
this.type = type;
}

public Boolean getFocusScroll() {
return focusScroll;
}

public Position getScroll() {
return scroll;
}

public String getScrollTarget() {
return scrollTarget;
}

public Duration getSettle() {
return settle;
}

public Position getShow() {
return show;
}

public String getShowTarget() {
return showTarget;
}

public Duration getSwap() {
return swap;
}

public HxSwapType getType() {
return type;
}

public boolean isTransition() {
return transition;
}

/**
* Returns a string representation for use as an HTTP header value.
*
* @return the value
*/
public String toHeaderValue() {

var value = new StringBuilder();
value.append(type.getValue());

if (transition) {
value.append(" transition:true");
}
if (focusScroll != null) {
value.append(" focus-scroll:").append(focusScroll);
}
if (swap != null) {
value.append(" swap:").append(swap.toMillis()).append("ms");
}
if (settle != null) {
value.append(" settle:").append(settle.toMillis()).append("ms");
}
if (scroll != null) {
if (scrollTarget != null) {
value.append(" scroll:").append(scrollTarget).append(":").append(scroll.getValue());
} else {
value.append(" scroll:").append(scroll.getValue());
}
}
if (show != null) {
if (showTarget != null) {
value.append(" show:").append(showTarget).append(":").append(show.getValue());
} else {
value.append(" show:").append(show.getValue());
}
}

return value.toString();
}

/**
* Enable or disable auto-scrolling to focused inputs between requests.
*
* @param enabled {@code true} if auto-scrolling to focused inputs between requests is enabled, otherwise {@code false}
* @return self
*/
public HtmxReswap focusScroll(boolean enabled) {
this.focusScroll = enabled;
return this;
}

/**
* Changes the scrolling behavior of the target element.
*
* @param position top or bottom
* @return self
*/
public HtmxReswap scroll(Position position) {
this.scroll = position;
return this;
}

/**
* Used to target a different element for scrolling.
*
* @param cssSelector a CSS selector
* @return self
*/
public HtmxReswap scrollTarget(String cssSelector) {
this.scrollTarget = cssSelector;
return this;
}

/**
* Set the time that should elapse between the swap and the settle logic.
*
* @param duration the time to wait
* @return self
*/
public HtmxReswap settle(Duration duration) {
this.settle = duration;
return this;
}

/**
* Changes the scrolling behavior of the target element.
*
* @param position top or bottom
* @return self
*/
public HtmxReswap show(Position position) {
this.show = position;
return this;
}

/**
* Used to target a different element for showing.
*
* @param cssSelector a CSS selector
* @return self
*/
public HtmxReswap showTarget(String cssSelector) {
this.showTarget = cssSelector;
return this;
}

/**
* Set the time that should elapse after receiving a response to swap the content.
*
* @param duration the time to wait
* @return self
*/
public HtmxReswap swap(Duration duration) {
this.swap = duration;
return this;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
HtmxReswap that = (HtmxReswap) object;
return transition == that.transition && Objects.equals(type, that.type) && Objects.equals(swap, that.swap) && Objects.equals(settle, that.settle) && scroll == that.scroll && Objects.equals(scrollTarget, that.scrollTarget) && show == that.show && Objects.equals(
showTarget, that.showTarget) && Objects.equals(focusScroll, that.focusScroll);
}

@Override
public int hashCode() {
return Objects.hash(type, swap, settle, scroll, scrollTarget, show, showTarget, transition, focusScroll);
}

@Override
public String toString() {
return toHeaderValue();
}

/**
* Enables the use of the new
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API">View Transitions API</a>
* when a swap occurs.
*
* @return self
*/
public HtmxReswap transition() {
this.transition = true;
return this;
}

/**
* Represents the position values used for {@link #scroll} or {@link #show}.
*/
public enum Position {

TOP("top"),
BOTTOM("bottom");

private final String value;

Position(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private void addHxHeaders(HtmxResponse htmxResponse, HttpServletResponse respons
response.setHeader(HtmxResponseHeader.HX_RETARGET.getValue(), htmxResponse.getRetarget());
}
if (htmxResponse.getReswap() != null) {
response.setHeader(HtmxResponseHeader.HX_RESWAP.getValue(), htmxResponse.getReswap().getValue());
response.setHeader(HtmxResponseHeader.HX_RESWAP.getValue(), htmxResponse.getReswap().toHeaderValue());
}
}

Expand Down
Loading

0 comments on commit 9f1137a

Please sign in to comment.