Skip to content

htmx: Initial add of htmx-api, htmx-nima request support and initial example #435

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions htmx-api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.avaje</groupId>
<artifactId>avaje-http-parent</artifactId>
<version>2.7</version>
</parent>

<artifactId>avaje-htmx-api</artifactId>

<properties>
</properties>

<dependencies>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-lang</artifactId>
<version>1.0</version>
</dependency>
</dependencies>

</project>
141 changes: 141 additions & 0 deletions htmx-api/src/main/java/io/avaje/htmx/api/DHxRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package io.avaje.htmx.api;

import io.avaje.lang.Nullable;

final class DHxRequest implements HtmxRequest {

private final boolean htmxRequest;

private final boolean boosted;
private final String currentUrl;
private final boolean historyRestoreRequest;
private final String promptResponse;
private final String target;
private final String triggerName;
private final String triggerId;

DHxRequest() {
this.htmxRequest = false;
this.boosted = false;
this.currentUrl = null;
this.historyRestoreRequest = false;
this.promptResponse = null;
this.target = null;
this.triggerName = null;
this.triggerId = null;
}

DHxRequest(boolean boosted, String currentUrl, boolean historyRestoreRequest, String promptResponse, String target, String triggerName, String triggerId) {
this.htmxRequest = true;
this.boosted = boosted;
this.currentUrl = currentUrl;
this.historyRestoreRequest = historyRestoreRequest;
this.promptResponse = promptResponse;
this.target = target;
this.triggerName = triggerName;
this.triggerId = triggerId;
}

@Override
public boolean isHtmxRequest() {
return htmxRequest;
}

@Override
public boolean isBoosted() {
return boosted;
}

@Nullable
@Override
public String currentUrl() {
return currentUrl;
}

@Override
public boolean isHistoryRestoreRequest() {
return historyRestoreRequest;
}

@Nullable
@Override
public String promptResponse() {
return promptResponse;
}

@Nullable
@Override
public String target() {
return target;
}

@Nullable
@Override
public String triggerName() {
return triggerName;
}

@Nullable
public String triggerId() {
return triggerId;
}

static final class DBuilder implements Builder {

private boolean boosted;
private String currentUrl;
private boolean historyRestoreRequest;
private String promptResponse;
private String target;
private String triggerName;
private String triggerId;

@Override
public DBuilder boosted(boolean boosted) {
this.boosted = boosted;
return this;
}

@Override
public DBuilder currentUrl(String currentUrl) {
this.currentUrl = currentUrl;
return this;
}

@Override
public DBuilder historyRestoreRequest(boolean historyRestoreRequest) {
this.historyRestoreRequest = historyRestoreRequest;
return this;
}

@Override
public DBuilder promptResponse(String promptResponse) {
this.promptResponse = promptResponse;
return this;
}

@Override
public DBuilder target(String target) {
this.target = target;
return this;
}

@Override
public DBuilder triggerName(String triggerName) {
this.triggerName = triggerName;
return this;
}

@Override
public DBuilder triggerId(String triggerId) {
this.triggerId = triggerId;
return this;
}

@Override
public HtmxRequest build() {
return new DHxRequest(boosted, currentUrl, historyRestoreRequest, promptResponse, target, triggerName, triggerId);
}
}

}
18 changes: 18 additions & 0 deletions htmx-api/src/main/java/io/avaje/htmx/api/Html.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.avaje.htmx.api;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Mark a controller as producing HTML by default and using "Templating"
* meaning that response objects are expected to a "Model View" passed to
* the "Templating" library.
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface Html {

}
106 changes: 106 additions & 0 deletions htmx-api/src/main/java/io/avaje/htmx/api/HtmxRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.avaje.htmx.api;

import io.avaje.lang.Nullable;

/**
* This class can be used as a controller method argument to access
* the <a href="https://htmx.org/reference/#request_headers">htmx Request Headers</a>.
*
* <pre>{@code
*
* @HxRequest
* @Get("/users")
* String users(HtmxRequest htmxRequest) {
* if (htmxRequest.isBoosted()) {
* ...
* }
* }
*
* }</pre>
*
* @see <a href="https://htmx.org/reference/#request_headers">Request Headers Reference</a>
*/
public interface HtmxRequest {

/**
* Represents a non-Htmx request.
*/
HtmxRequest EMPTY = new DHxRequest();

/**
* Return a new builder for the HtmxRequest.
*/
static Builder builder() {
return new DHxRequest.DBuilder();
}

/**
* Return true if this is an Htmx request.
*/
boolean isHtmxRequest();

/**
* Indicates that the request is via an element using hx-boost.
*
* @return true if the request was made via HX-Boost, false otherwise
*/
boolean isBoosted();

/**
* Return the current URL of the browser when the htmx request was made.
*/
@Nullable
String currentUrl();

/**
* Indicates if the request is for history restoration after a miss in the local history cache
*
* @return true if this request is for history restoration, false otherwise
*/
boolean isHistoryRestoreRequest();

/**
* Return the user response to an HX-Prompt.
*/
@Nullable
String promptResponse();

/**
* Return the id of the target element if it exists.
*/
@Nullable
String target();

/**
* Return the name of the triggered element if it exists.
*/
@Nullable
String triggerName();

/**
* Return the id of the triggered element if it exists.
*/
@Nullable
String triggerId();

/**
* Builder for {@link HtmxRequest}.
*/
interface Builder {
Builder boosted(boolean boosted);

Builder currentUrl(String currentUrl);

Builder historyRestoreRequest(boolean historyRestoreRequest);

Builder promptResponse(String promptResponse);

Builder target(String target);

Builder triggerName(String triggerName);

Builder triggerId(String triggerId);

HtmxRequest build();
}
}
54 changes: 54 additions & 0 deletions htmx-api/src/main/java/io/avaje/htmx/api/HxRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.avaje.htmx.api;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Mark a controller method as handling Htmx requests and potentially restrict
* the handler to only be used for specific Htmx target or Htmx trigger.
* <p>
* Controller methods with {@code @HxRequest} require the {@code HX-Request}
* HTTP Header to be set for the handler to process the request. Additionally,
* we can specify {@link #target()}, {@link #triggerId()}, or {@link #triggerName()}
* such that the handler is only invoked specifically for requests with those
* matching headers.
*/
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface HxRequest {

/**
* Restricts the mapping to the {@code id} of a specific target element.
*
* @see <a href="https://htmx.org/reference/#request_headers">HX-Target</a>
*/
String target() default "";

/**
* Restricts the mapping to the {@code id} of a specific triggered element.
*
* @see <a href="https://htmx.org/reference/#request_headers">HX-Trigger</a>
*/
String triggerId() default "";

/**
* Restricts the mapping to the {@code name} of a specific triggered element.
*
* @see <a href="https://htmx.org/reference/#request_headers">HX-Trigger-Name</a>
*/
String triggerName() default "";

/**
* Restricts the mapping to the {@code id}, if any, or to the {@code name} of a specific triggered element.
* <p>
* If you want to be explicit use {@link #triggerId()} or {@link #triggerName()}.
*
* @see <a href="https://htmx.org/reference/#request_headers">HX-Trigger</a>
* @see <a href="https://htmx.org/reference/#request_headers">HX-Trigger-Name</a>
*/
String value() default "";
}
6 changes: 6 additions & 0 deletions htmx-api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module io.avaje.htmx.api {

exports io.avaje.htmx.api;

requires static io.avaje.lang;
}
Loading
Loading