Skip to content

Commit

Permalink
feat: new "morph" and "refresh" action types for turbo-stream, issue #…
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterFokkinga committed Apr 19, 2024
1 parent 6ee4683 commit ea0bf68
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
97 changes: 97 additions & 0 deletions views-core/src/main/java/io/micronaut/views/turbo/TurboStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,16 @@ public final class TurboStream implements Renderable {
private static final String MEMBER_ACTION = "action";
private static final String MEMBER_TARGET_DOM_ID = "targetDomId";
private static final String MEMBER_TARGET_CSS_QUERY_SELECTOR = "targetCssQuerySelector";
private static final String MEMBER_REQUEST_ID = "requestId";
private static final String MEMBER_CHILDREN_ONLY = "childrenOnly";
private static final String TURBO_TEMPLATE_TAG = "template";
private static final String TURBO_STREAM_TAG = "turbo-stream";
private static final String TURBO_STREAM_CLOSING_TAG = "</turbo-stream>";
private static final String TURBO_STREAM_ATTRIBUTE_TARGET = "target";
private static final String TURBO_STREAM_ATTRIBUTE_ACTION = "action";
private static final String TURBO_STREAM_ATTRIBUTE_TARGETS = "targets";
private static final String TURBO_STREAM_ATTRIBUTE_REQUEST_ID = "request-id";
private static final String TURBO_STREAM_ATTRIBUTE_CHILDREN_ONLY = "children-only";
private static final String CLOSE_TAG = ">";
private static final String OPEN_TAG = "<";
private static final String SPACE = " ";
Expand All @@ -69,16 +73,49 @@ public final class TurboStream implements Renderable {
@Nullable
private final String targetCssQuerySelector;

/**
* request-id attribute, only relevant when action=refresh
*/
@Nullable
private final String requestId;

/**
* Morph only the children of the element designated by the target dom id.
*/
private final boolean childrenOnly;

@Nullable
private final Object template;

/**
*
* @deprecated use the constructor that takes also the {@code requestId} and
* {@code childrenOnly} parameters instead
*/
@Deprecated
TurboStream(@NonNull TurboStreamAction action,
@Nullable String targetDomId,
@Nullable String targetCssQuerySelector,
@Nullable Object template) {
this.action = action;
this.targetDomId = targetDomId;
this.targetCssQuerySelector = targetCssQuerySelector;
this.requestId = null;
this.childrenOnly = false;
this.template = template;
}

TurboStream(@NonNull TurboStreamAction action,
@Nullable String targetDomId,
@Nullable String targetCssQuerySelector,
@Nullable String requestId,
boolean childrenOnly,
@Nullable Object template) {
this.action = action;
this.targetDomId = targetDomId;
this.targetCssQuerySelector = targetCssQuerySelector;
this.requestId = requestId;
this.childrenOnly = childrenOnly;
this.template = template;
}

Expand Down Expand Up @@ -109,6 +146,22 @@ public Optional<String> getTargetCssQuerySelector() {
return Optional.ofNullable(targetCssQuerySelector);
}

/**
*
* @return request-id attribute, only relevant when action=refresh
*/
public Optional<String> getRequestId() {
return Optional.ofNullable(requestId);
}

/**
*
* @return Morph only the children of the element designated by the target dom id.
*/
public boolean getChildrenOnly() {
return childrenOnly;
}

/**
*
* @return Template.
Expand Down Expand Up @@ -164,6 +217,8 @@ private String renderTurboStreamOpeningTag() {
return OPEN_TAG + TURBO_STREAM_TAG + SPACE + htmlAttribute(TURBO_STREAM_ATTRIBUTE_ACTION, getAction().toString())
+ getTargetDomIdHtmlAttribute().orElse("")
+ getTargetCssQuerySelectorHtmlAttribute().orElse("")
+ getRequestIdAttribute().orElse("")
+ getChildrenOnlyAttribute().orElse("")
+ CLOSE_TAG;
}

Expand All @@ -179,6 +234,19 @@ private Optional<String> getTargetCssQuerySelectorHtmlAttribute() {
.map(cssSelector -> SPACE + htmlAttribute(TURBO_STREAM_ATTRIBUTE_TARGETS, cssSelector));
}

@NonNull
private Optional<String> getRequestIdAttribute() {
return getRequestId()
.map(requestId -> SPACE + htmlAttribute(TURBO_STREAM_ATTRIBUTE_REQUEST_ID, requestId));
}

@NonNull
private Optional<String> getChildrenOnlyAttribute() {
return getChildrenOnly()
? Optional.of(SPACE + TURBO_STREAM_ATTRIBUTE_CHILDREN_ONLY)
: Optional.empty();
}

@NonNull
private String htmlAttribute(@NonNull String key, @NonNull String value) {
return String.join(EQUALS, key, DOUBLE_QUOTE + value + DOUBLE_QUOTE);
Expand Down Expand Up @@ -208,6 +276,8 @@ public static class Builder {
private TurboStreamAction action;
private String targetDomId;
private String targetCssQuerySelector;
private String requestId;
private boolean childrenOnly;
private Object template;
private String templateView;
private Object templateModel;
Expand Down Expand Up @@ -273,6 +343,29 @@ public Builder targetCssQuerySelector(@NonNull String targetCssQuerySelector) {
return this;
}


/**
*
* @param requestId request-id attribute, only relevant when action=refresh
* @return The Builder
*/
@NonNull
public Builder requestId(@NonNull String requestId) {
this.requestId = requestId;
return this;
}

/**
*
* @param childrenOnly Morph only the children of the element designated by the target dom id.
* @return The Builder
*/
@NonNull
public Builder childrenOnly(boolean childrenOnly) {
this.childrenOnly = childrenOnly;
return this;
}

/**
* Sets the template with a View and Model.
* @param view The View name
Expand Down Expand Up @@ -409,6 +502,8 @@ public TurboStream build() {
return new TurboStream(action,
targetDomId,
targetCssQuerySelector,
requestId,
childrenOnly,
template);
}

Expand Down Expand Up @@ -473,6 +568,8 @@ private static Optional<TurboStream.Builder> of(@NonNull HttpRequest<?> request,
route.getValue(TurboView.class, MEMBER_ACTION, TurboStreamAction.class).ifPresent(builder::action);
route.stringValue(TurboView.class, MEMBER_TARGET_DOM_ID).ifPresent(builder::targetDomId);
route.stringValue(TurboView.class, MEMBER_TARGET_CSS_QUERY_SELECTOR).ifPresent(builder::targetCssQuerySelector);
route.stringValue(TurboView.class, MEMBER_REQUEST_ID).ifPresent(builder::requestId);
route.booleanValue(TurboView.class, MEMBER_CHILDREN_ONLY).ifPresent(builder::childrenOnly);

if (!builder.getTargetCssQuerySelector().isPresent() &&
!builder.getTargetDomId().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@ public enum TurboStreamAction {
/**
* Inserts the content within the template tag after the element designated by the target dom id.
*/
AFTER("after");
AFTER("after"),

/**
* Replaces the element designated by the target dom id via morph.
*/
MORPH("morph"),

/**
* Initiates a Page Refresh to render new content with morphing.
*/
REFRESH("refresh");

@NonNull
private final String action;
Expand Down
12 changes: 12 additions & 0 deletions views-core/src/main/java/io/micronaut/views/turbo/TurboView.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,16 @@
* @return Target CSS Query Selector.
*/
String targetCssQuerySelector() default "";

/**
*
* @return request-id attribute, only relevant when action=refresh
*/
String requestId() default "";

/**
*
* @return morph only the children of the element designated by the target dom id
*/
boolean childrenOnly() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,28 @@ class TurboStreamSpec extends Specification {
"<turbo-stream action=\"update\" target=\"main-container\"><template><div class=\"message\">Hello World</div></template></turbo-stream>" == html
}

void "verify TurboView annotation can specify requestId"() {
given:
BlockingHttpClient client = httpClient.toBlocking()

when:
String html = client.retrieve(HttpRequest.GET("/turbo/requestId").accept(TurboMediaType.TURBO_STREAM, MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML))

then:
"<turbo-stream action=\"refresh\" request-id=\"abcd-1234\"><template><div class=\"message\">Hello World</div></template></turbo-stream>" == html
}

void "verify TurboView annotation can specify childrenOnly"() {
given:
BlockingHttpClient client = httpClient.toBlocking()

when:
String html = client.retrieve(HttpRequest.GET("/turbo/childrenOnly").accept(TurboMediaType.TURBO_STREAM, MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML))

then:
"<turbo-stream action=\"morph\" children-only><template><div class=\"message\">Hello World</div></template></turbo-stream>" == html
}

void "verify TurboView annotation can specify targetCssQuerySelector"() {
given:
BlockingHttpClient client = httpClient.toBlocking()
Expand Down Expand Up @@ -409,7 +431,7 @@ class TurboStreamSpec extends Specification {
@TurboView("fragments/message")
@Get("/update")
String update() {
"Hello World";
"Hello World"
}

@Produces(TurboMediaType.TURBO_STREAM)
Expand All @@ -419,6 +441,20 @@ class TurboStreamSpec extends Specification {
"Hello World"
}

@Produces(TurboMediaType.TURBO_STREAM)
@TurboView(value = "fragments/message", action=TurboStreamAction.REFRESH, requestId = "abcd-1234")
@Get("/requestId")
String requestId() {
"Hello World"
}

@Produces(TurboMediaType.TURBO_STREAM)
@TurboView(value = "fragments/message", action=TurboStreamAction.MORPH, childrenOnly = true)
@Get("/childrenOnly")
String childrenOnly() {
"Hello World"
}

@Produces(TurboMediaType.TURBO_STREAM)
@TurboView(action = TurboStreamAction.REMOVE)
@Get("/del")
Expand Down

0 comments on commit ea0bf68

Please sign in to comment.