Last active
July 15, 2023 06:01
-
-
Save alexanderankin/d22f080e6f37d90979c8b62b6102ef28 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.example; | |
| import lombok.RequiredArgsConstructor; | |
| import org.springframework.boot.SpringApplication; | |
| import org.springframework.boot.autoconfigure.SpringBootApplication; | |
| import org.springframework.context.annotation.Bean; | |
| import org.springframework.context.annotation.Configuration; | |
| import org.springframework.http.MediaType; | |
| import org.springframework.util.MultiValueMap; | |
| import org.springframework.web.reactive.function.server.RouterFunction; | |
| import org.springframework.web.reactive.function.server.RouterFunctions; | |
| import org.springframework.web.reactive.function.server.ServerResponse; | |
| import org.thymeleaf.TemplateEngine; | |
| import org.thymeleaf.TemplateSpec; | |
| import org.thymeleaf.context.IContext; | |
| import org.thymeleaf.templatemode.TemplateMode; | |
| import org.thymeleaf.templateresolver.StringTemplateResolver; | |
| import reactor.core.publisher.Flux; | |
| import reactor.core.publisher.Mono; | |
| import java.time.Duration; | |
| import java.time.temporal.ChronoUnit; | |
| import java.util.*; | |
| @SpringBootApplication | |
| class HtmxApp { | |
| private static final List<Map<String, Object>> examples = new ArrayList<>(); | |
| public static void main(String[] args) { | |
| SpringApplication.run(HtmxApp.class, args); | |
| examples.add(Map.of("id", 0, "name", "initial example")); | |
| Flux.interval(Duration.of(10, ChronoUnit.SECONDS)) | |
| .doOnNext(i -> examples.add(Map.of("id", i + 1000, "name", i + "th example"))) | |
| .subscribe(); | |
| } | |
| @Configuration | |
| static class TemplateConfig { | |
| @Bean | |
| StringTemplateResolver stringTemplateResolver() { | |
| return new StringTemplateResolver(); | |
| } | |
| } | |
| @RequiredArgsConstructor | |
| @Configuration | |
| static class ServerConfig { | |
| private final TemplateEngine templateEngine; | |
| @Bean | |
| RouterFunction<ServerResponse> routerFunction() { | |
| return RouterFunctions.route() | |
| .GET("/", request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(templateEngine.process(new TemplateSpec(""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <script src="https://unpkg.com/[email protected]"></script> | |
| <title>Welcome</title> | |
| </head> | |
| <body> | |
| <h1>hello, <span th:text="${name} ?: 'world'"></span></h1> | |
| <h2>check out these examples:</h2> | |
| <button | |
| hx-get=/examples | |
| hx-trigger=click | |
| hx-target=#examples-table | |
| hx-swap=innerHTML | |
| >refresh</button> | |
| <div | |
| id=examples-table | |
| hx-get=/examples | |
| hx-trigger=load | |
| hx-target=#examples-table | |
| hx-swap=innerHTML | |
| ></div> | |
| <button | |
| hx-get=/examples/form | |
| hx-trigger=click | |
| hx-target=#examples-form | |
| hx-swap=innerHTML | |
| > | |
| Add another | |
| </button> | |
| <div id=examples-form></div> | |
| </body> | |
| </html> | |
| """, TemplateMode.HTML), new MapContext(Map.of("items", examples))))) | |
| .GET("/examples/form", r -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(templateEngine.process(new TemplateSpec(""" | |
| <form hx-post=/examples hx-> | |
| <input name="name" type="text"> | |
| <button type="submit">Submit</button> | |
| </form> | |
| """, TemplateMode.HTML), new MapContext(Map.of())))) | |
| .GET("/examples", request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(templateEngine.process(new TemplateSpec(""" | |
| <table> | |
| <thead><tr><th>ID</th><th>Name</th></tr></thead> | |
| <tbody> | |
| <tr th:each="prod: ${items}"> | |
| <td th:text="${prod.id}">id</td> | |
| <td th:text="${prod.name}">name</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| """, TemplateMode.HTML), new MapContext(Map.of("items", examples))))) | |
| .POST("/examples", request -> request.formData().map(MultiValueMap::toSingleValueMap) | |
| .doOnNext(m -> m.computeIfAbsent("id", k -> UUID.randomUUID().toString())) | |
| .cast(Map.class).doOnNext(examples::add) | |
| .thenReturn(ServerResponse.ok().build()).flatMap(Mono::from)) | |
| .build(); | |
| } | |
| } | |
| @RequiredArgsConstructor | |
| static class MapContext implements IContext { | |
| private final Map<String, Object> map; | |
| @Override | |
| public Locale getLocale() { | |
| return Locale.getDefault(); | |
| } | |
| @Override | |
| public boolean containsVariable(String name) { | |
| return map.containsKey(name); | |
| } | |
| @Override | |
| public Set<String> getVariableNames() { | |
| return map.keySet(); | |
| } | |
| @Override | |
| public Object getVariable(String name) { | |
| return map.get(name); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment