-
Notifications
You must be signed in to change notification settings - Fork 58
Open
Description
I propose adding a HtmxEndpoint Class that can be used to create HTTP Endpoints and can be directly called from Template Engines.
The Controller would look like this:
@Controller
class ExampleController {
public HtmxEndpoint<UserForm> createUserEndpoint = new HtmxEndpoint<>(
"/createUser",
HttpMethod.POST,
this::createUser
);
private ModelAndView createUser(UserForm userForm) {
return new ModelAndView("createUser", Map.of("createUserEndpoint", createUserEndpoint));
}
}
The class would like this:
public class HtmxEndpoint<T> implements RouterFunction<ServerResponse> {
private final String path;
private final HttpMethod method;
private final Supplier<ModelAndView> modelAndViewSupplier;
private final Function<T, ModelAndView> function;
ParameterizedTypeReference<T> requestType = new ParameterizedTypeReference<>() {
};
public HtmxEndpoint(String path, HttpMethod method, Function<T, ModelAndView> function) {
this.path = path;
this.method = method;
this.function = function;
this.modelAndViewSupplier = null;
}
public HtmxEndpoint(String path, HttpMethod method, Supplier<ModelAndView> modelAndViewSupplier) {
this.path = path;
this.method = method;
this.modelAndViewSupplier = modelAndViewSupplier;
this.function = null;
this.requestType = null;
}
@NotNull
@Override
public Optional<HandlerFunction<ServerResponse>> route(@NotNull ServerRequest request) {
RequestPredicate predicate = RequestPredicates.method(method).and(RequestPredicates.path(path));
if (predicate.test(request)) {
ModelAndView modelAndView = getBody(request);
return Optional.of(
req -> RenderingResponse.create(modelAndView.view())
.modelAttribute(modelAndView.model())
.build()
);
}
return Optional.empty();
}
private ModelAndView getBody(ServerRequest req) {
if (function == null) {
return modelAndViewSupplier.get();
}
try {
return function.apply(
req.body(requestType)
);
} catch (ServletException | IOException e) {
throw new RuntimeException(e);
}
}
public String call() {
return "hx-" + method.name().toLowerCase() + " =\"" + path + "\"";
}
}
In the template you would call it like this in Thymeleaf:
<div th:hx=${createUserEndpoint}>
<div>
And this template would render like this:
<div hx-post="/createUser">
<div>
Of course, the HtmxEndpoint could be expanded to all the possible Htmx attributes.
The Endpoints would be scanned at Startup using Reflection and added to the Spring RouterFunction
@Bean
ApplicationRunner applicationRunner() {
return args -> {
applicationContext.getBeansWithAnnotation(Controller.class)
.values().forEach(controller ->
{
List<Field> fieldList = Arrays.stream(controller.getClass().getDeclaredFields())
.filter(method -> method.getType() == HtmxEndpoint.class)
.toList();
fieldList.forEach(field -> {
RouterFunction<?> function = (RouterFunction<?>) ReflectionUtils.getField(field, controller);
if(routerFunctionMapping.getRouterFunction() == null){
routerFunctionMapping.setRouterFunction(function);
}
RouterFunction<?> routerFunction = routerFunctionMapping.getRouterFunction().andOther(function);
routerFunctionMapping.setRouterFunction(routerFunction);
});
}
);
};
}
landsmanlandsman
Metadata
Metadata
Assignees
Labels
No labels