Skip to content

Commit 969a05e

Browse files
authored
flatten htmx dsl by lifting routes out of page to the top level (#91)
1 parent 9d8cec6 commit 969a05e

File tree

6 files changed

+216
-221
lines changed

6 files changed

+216
-221
lines changed

spring-funk-htmx/src/main/kotlin/com/github/wakingrufus/funk/htmx/HtmxDsl.kt

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,189 @@ package com.github.wakingrufus.funk.htmx
33
import com.github.wakingrufus.funk.core.SpringDsl
44
import com.github.wakingrufus.funk.core.SpringDslContainer
55
import com.github.wakingrufus.funk.core.SpringDslMarker
6+
import com.github.wakingrufus.funk.htmx.route.HxRoute
7+
import com.github.wakingrufus.funk.htmx.route.noParam
8+
import com.github.wakingrufus.funk.htmx.route.withAuth
9+
import com.github.wakingrufus.funk.htmx.route.withParam
10+
import com.github.wakingrufus.funk.htmx.route.withParamAndAuth
11+
import com.github.wakingrufus.funk.htmx.template.HtmxTemplate
12+
import kotlinx.html.BODY
13+
import kotlinx.html.TagConsumer
14+
import kotlinx.html.body
15+
import kotlinx.html.dom.createHTMLDocument
16+
import kotlinx.html.dom.serialize
17+
import kotlinx.html.head
18+
import kotlinx.html.html
19+
import kotlinx.html.script
20+
import org.springframework.beans.factory.BeanFactory
21+
import org.springframework.http.MediaType
22+
import org.springframework.web.servlet.function.RouterFunctionDsl
23+
import org.springframework.web.servlet.function.ServerResponse
24+
import java.security.Principal
625

726
@SpringDslMarker
827
class HtmxDsl : SpringDsl {
928
val pages = ArrayList<HtmxPage>()
29+
internal var routes: MutableList<HxRoute> = mutableListOf()
1030

1131
@SpringDslMarker
12-
fun page(path: String, config: HtmxPage.() -> Unit) {
13-
pages.add(HtmxPage(path).apply(config))
32+
fun page(path: String, template : BODY.() -> Unit = {}) {
33+
pages.add(HtmxPage(path).apply { initialLoad(template) })
34+
}
35+
36+
37+
fun addRoute(route: HxRoute) {
38+
routes.add(route)
39+
}
40+
41+
/**
42+
* use when declaring a GET route that takes no parameters
43+
*/
44+
@SpringDslMarker
45+
inline fun <reified CONTROLLER : Any, RESP : Any> get(
46+
path: String,
47+
noinline binding: CONTROLLER.() -> RESP,
48+
renderer: HtmxTemplate<RESP>
49+
): HxRoute {
50+
return noParam(
51+
RouterFunctionDsl::GET,
52+
path = path,
53+
controllerClass = CONTROLLER::class.java,
54+
binding = binding,
55+
renderer = renderer
56+
)
57+
.also { addRoute(it) }
58+
}
59+
60+
@SpringDslMarker
61+
inline fun <reified CONTROLLER : Any, RESP : Any> get(
62+
path: String,
63+
noinline binding: CONTROLLER.() -> RESP,
64+
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
65+
): HxRoute {
66+
return get(path, binding) { appendable, input -> renderer.invoke(appendable, input) }
67+
}
68+
69+
/**
70+
* use when declaring a GET route that takes no parameters, but uses security
71+
*/
72+
@SpringDslMarker
73+
inline fun <reified CONTROLLER : Any, USER : Principal, RESP : Any> get(
74+
path: String,
75+
noinline binding: CONTROLLER.(USER?) -> RESP,
76+
renderer: HtmxTemplate<RESP>
77+
): HxRoute {
78+
return withAuth(
79+
RouterFunctionDsl::GET,
80+
path = path,
81+
controllerClass = CONTROLLER::class.java,
82+
binding = binding,
83+
renderer = renderer
84+
)
85+
.also { addRoute(it) }
86+
}
87+
88+
@SpringDslMarker
89+
inline fun <reified CONTROLLER : Any, USER : Principal, RESP : Any> get(
90+
path: String,
91+
noinline binding: CONTROLLER.(USER?) -> RESP,
92+
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
93+
): HxRoute {
94+
return get(path, binding) { appendable, input -> renderer.invoke(appendable, input) }
95+
}
96+
97+
@SpringDslMarker
98+
inline fun <reified CONTROLLER : Any, reified REQ : Record, RESP : Any> route(
99+
verb: HttpVerb,
100+
path: String,
101+
noinline binding: CONTROLLER.(REQ) -> RESP,
102+
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
103+
): HxRoute {
104+
return route(verb, path, binding) { appendable, input -> renderer.invoke(appendable, input) }
105+
}
106+
107+
@SpringDslMarker
108+
inline fun <reified CONTROLLER : Any, reified REQ : Record, RESP : Any> route(
109+
verb: HttpVerb,
110+
path: String,
111+
noinline binding: CONTROLLER.(REQ) -> RESP,
112+
renderer: HtmxTemplate<RESP>
113+
): HxRoute {
114+
return withParam(
115+
routerFunction = when (verb) {
116+
HttpVerb.GET -> RouterFunctionDsl::GET
117+
HttpVerb.POST -> RouterFunctionDsl::POST
118+
HttpVerb.PUT -> RouterFunctionDsl::PUT
119+
HttpVerb.PATCH -> RouterFunctionDsl::PATCH
120+
HttpVerb.DELETE -> RouterFunctionDsl::DELETE
121+
},
122+
path = path,
123+
requestClass = REQ::class.java,
124+
controllerClass = CONTROLLER::class.java,
125+
binding = binding,
126+
renderer = renderer
127+
)
128+
.also { addRoute(it) }
129+
}
130+
131+
@SpringDslMarker
132+
inline fun <reified CONTROLLER : Any, reified REQ : Record, USER : Principal, RESP : Any> route(
133+
verb: HttpVerb,
134+
path: String,
135+
noinline binding: CONTROLLER.(USER?, REQ) -> RESP,
136+
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
137+
): HxRoute {
138+
return route(verb, path, binding) { appendable, input -> renderer.invoke(appendable, input) }
139+
}
140+
141+
@SpringDslMarker
142+
inline fun <reified CONTROLLER : Any, reified REQ : Record, USER : Principal, RESP : Any> route(
143+
verb: HttpVerb,
144+
path: String,
145+
noinline binding: CONTROLLER.(USER?, REQ) -> RESP,
146+
renderer: HtmxTemplate<RESP>
147+
): HxRoute {
148+
return withParamAndAuth(
149+
routerFunction = when (verb) {
150+
HttpVerb.GET -> RouterFunctionDsl::GET
151+
HttpVerb.POST -> RouterFunctionDsl::POST
152+
HttpVerb.PUT -> RouterFunctionDsl::PUT
153+
HttpVerb.PATCH -> RouterFunctionDsl::PATCH
154+
HttpVerb.DELETE -> RouterFunctionDsl::DELETE
155+
},
156+
path = path,
157+
requestClass = REQ::class.java,
158+
controllerClass = CONTROLLER::class.java,
159+
binding = binding,
160+
renderer = renderer
161+
)
162+
.also { addRoute(it) }
163+
}
164+
165+
fun registerRoutes(beanFactory: BeanFactory, dsl: RouterFunctionDsl) {
166+
dsl.apply {
167+
pages.forEach {
168+
GET(it.path) { request ->
169+
ServerResponse.ok()
170+
.contentType(MediaType.TEXT_HTML)
171+
.body(
172+
createHTMLDocument().html {
173+
head {
174+
script {
175+
src = "https://unpkg.com/htmx.org@2.0.4"
176+
}
177+
}
178+
body {
179+
it.initialLoad.invoke(this)
180+
}
181+
}.serialize(false)
182+
)
183+
}
184+
}
185+
routes.forEach { route ->
186+
route.registerRoutes(beanFactory, this)
187+
}
188+
}
14189
}
15190
}
16191

spring-funk-htmx/src/main/kotlin/com/github/wakingrufus/funk/htmx/HtmxInitializer.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import org.springframework.web.servlet.function.router
88

99
class HtmxInitializer : ApplicationContextInitializer<GenericApplicationContext> {
1010
override fun initialize(applicationContext: GenericApplicationContext) {
11-
applicationContext.getDsl<HtmxDsl>()?.pages?.forEach { page ->
12-
val routesForPage = router {
13-
page.registerRoutes(applicationContext, this)
14-
}
15-
applicationContext.registerBean(page.path.replace("/", "")) {
16-
routesForPage
17-
}
11+
val routesForPage = router {
12+
applicationContext.getDsl<HtmxDsl>()?.registerRoutes(applicationContext, this)
13+
}
14+
applicationContext.registerBean("funkHtmx") {
15+
routesForPage
1816
}
1917
}
2018
}

spring-funk-htmx/src/main/kotlin/com/github/wakingrufus/funk/htmx/HtmxPage.kt

Lines changed: 0 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,7 @@ package com.github.wakingrufus.funk.htmx
22

33
import com.github.wakingrufus.funk.core.SpringDslMarker
44
import com.github.wakingrufus.funk.htmx.route.HxRoute
5-
import com.github.wakingrufus.funk.htmx.route.noParam
6-
import com.github.wakingrufus.funk.htmx.route.withAuth
7-
import com.github.wakingrufus.funk.htmx.route.withParam
8-
import com.github.wakingrufus.funk.htmx.route.withParamAndAuth
9-
import com.github.wakingrufus.funk.htmx.template.HtmxTemplate
105
import kotlinx.html.BODY
11-
import kotlinx.html.TagConsumer
12-
import kotlinx.html.body
13-
import kotlinx.html.dom.createHTMLDocument
14-
import kotlinx.html.dom.serialize
15-
import kotlinx.html.head
16-
import kotlinx.html.html
17-
import kotlinx.html.script
18-
import org.springframework.beans.factory.BeanFactory
19-
import org.springframework.http.MediaType
20-
import org.springframework.web.servlet.function.RouterFunctionDsl
21-
import org.springframework.web.servlet.function.ServerResponse
22-
import java.security.Principal
236

247
class HtmxPage(val path: String) {
258
var initialLoad: BODY.() -> Unit = {}
@@ -29,160 +12,4 @@ class HtmxPage(val path: String) {
2912
fun initialLoad(dsl: BODY.() -> Unit) {
3013
initialLoad = dsl
3114
}
32-
33-
operator fun invoke() {
34-
35-
}
36-
37-
fun addRoute(route: HxRoute) {
38-
routes.add(route)
39-
}
40-
41-
/**
42-
* use when declaring a GET route that takes no parameters
43-
*/
44-
@SpringDslMarker
45-
inline fun <reified CONTROLLER : Any, RESP : Any> get(
46-
path: String,
47-
noinline binding: CONTROLLER.() -> RESP,
48-
renderer: HtmxTemplate<RESP>
49-
): HxRoute {
50-
return noParam(
51-
RouterFunctionDsl::GET,
52-
path = path,
53-
controllerClass = CONTROLLER::class.java,
54-
binding = binding,
55-
renderer = renderer
56-
)
57-
.also { addRoute(it) }
58-
}
59-
60-
@SpringDslMarker
61-
inline fun <reified CONTROLLER : Any, RESP : Any> get(
62-
path: String,
63-
noinline binding: CONTROLLER.() -> RESP,
64-
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
65-
): HxRoute {
66-
return get(path, binding) { appendable, input -> renderer.invoke(appendable, input) }
67-
}
68-
69-
/**
70-
* use when declaring a GET route that takes no parameters, but uses security
71-
*/
72-
@SpringDslMarker
73-
inline fun <reified CONTROLLER : Any, USER : Principal, RESP : Any> get(
74-
path: String,
75-
noinline binding: CONTROLLER.(USER?) -> RESP,
76-
renderer: HtmxTemplate<RESP>
77-
): HxRoute {
78-
return withAuth(
79-
RouterFunctionDsl::GET,
80-
path = path,
81-
controllerClass = CONTROLLER::class.java,
82-
binding = binding,
83-
renderer = renderer
84-
)
85-
.also { addRoute(it) }
86-
}
87-
88-
@SpringDslMarker
89-
inline fun <reified CONTROLLER : Any, USER : Principal, RESP : Any> get(
90-
path: String,
91-
noinline binding: CONTROLLER.(USER?) -> RESP,
92-
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
93-
): HxRoute {
94-
return get(path, binding) { appendable, input -> renderer.invoke(appendable, input) }
95-
}
96-
97-
@SpringDslMarker
98-
inline fun <reified CONTROLLER : Any, reified REQ : Record, RESP : Any> route(
99-
verb: HttpVerb,
100-
path: String,
101-
noinline binding: CONTROLLER.(REQ) -> RESP,
102-
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
103-
): HxRoute {
104-
return route(verb, path, binding) { appendable, input -> renderer.invoke(appendable, input) }
105-
}
106-
107-
@SpringDslMarker
108-
inline fun <reified CONTROLLER : Any, reified REQ : Record, RESP : Any> route(
109-
verb: HttpVerb,
110-
path: String,
111-
noinline binding: CONTROLLER.(REQ) -> RESP,
112-
renderer: HtmxTemplate<RESP>
113-
): HxRoute {
114-
return withParam(
115-
routerFunction = when (verb) {
116-
HttpVerb.GET -> RouterFunctionDsl::GET
117-
HttpVerb.POST -> RouterFunctionDsl::POST
118-
HttpVerb.PUT -> RouterFunctionDsl::PUT
119-
HttpVerb.PATCH -> RouterFunctionDsl::PATCH
120-
HttpVerb.DELETE -> RouterFunctionDsl::DELETE
121-
},
122-
path = path,
123-
requestClass = REQ::class.java,
124-
controllerClass = CONTROLLER::class.java,
125-
binding = binding,
126-
renderer = renderer
127-
)
128-
.also { addRoute(it) }
129-
}
130-
131-
@SpringDslMarker
132-
inline fun <reified CONTROLLER : Any, reified REQ : Record, USER : Principal, RESP : Any> route(
133-
verb: HttpVerb,
134-
path: String,
135-
noinline binding: CONTROLLER.(USER?, REQ) -> RESP,
136-
crossinline renderer: TagConsumer<*>.(RESP) -> Unit
137-
): HxRoute {
138-
return route(verb, path, binding) { appendable, input -> renderer.invoke(appendable, input) }
139-
}
140-
141-
@SpringDslMarker
142-
inline fun <reified CONTROLLER : Any, reified REQ : Record, USER : Principal, RESP : Any> route(
143-
verb: HttpVerb,
144-
path: String,
145-
noinline binding: CONTROLLER.(USER?, REQ) -> RESP,
146-
renderer: HtmxTemplate<RESP>
147-
): HxRoute {
148-
return withParamAndAuth(
149-
routerFunction = when (verb) {
150-
HttpVerb.GET -> RouterFunctionDsl::GET
151-
HttpVerb.POST -> RouterFunctionDsl::POST
152-
HttpVerb.PUT -> RouterFunctionDsl::PUT
153-
HttpVerb.PATCH -> RouterFunctionDsl::PATCH
154-
HttpVerb.DELETE -> RouterFunctionDsl::DELETE
155-
},
156-
path = path,
157-
requestClass = REQ::class.java,
158-
controllerClass = CONTROLLER::class.java,
159-
binding = binding,
160-
renderer = renderer
161-
)
162-
.also { addRoute(it) }
163-
}
164-
165-
fun registerRoutes(beanFactory: BeanFactory, dsl: RouterFunctionDsl) {
166-
dsl.apply {
167-
GET(path) { request ->
168-
ServerResponse.ok()
169-
.contentType(MediaType.TEXT_HTML)
170-
.body(
171-
createHTMLDocument().html {
172-
head {
173-
script {
174-
src = "https://unpkg.com/htmx.org@2.0.4"
175-
}
176-
}
177-
body {
178-
initialLoad(this)
179-
}
180-
}.serialize(false)
181-
)
182-
}
183-
routes.forEach { route ->
184-
route.registerRoutes(beanFactory, this)
185-
}
186-
}
187-
}
18815
}

0 commit comments

Comments
 (0)