Skip to content

Commit

Permalink
feature: v0.4.2 ViewContextContainer to compose pages from multiple c…
Browse files Browse the repository at this point in the history
…omponents -> support htmx out of band response
  • Loading branch information
tschuehly committed Apr 1, 2023
1 parent 2805ad8 commit 022e599
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 11 deletions.
56 changes: 49 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ class Router(
val homeViewComponent: HomeViewComponent
) {
@GetMapping("/")
fun homeComponent(): Any {
return homeViewComponent.render()
}
fun homeComponent() = homeViewComponent.render()
}
```

Expand Down Expand Up @@ -128,6 +126,25 @@ If we now access the root url path of our spring application we can see that the

![viewcomponent-parameter](https://user-images.githubusercontent.com/33346637/222275688-7f301ff7-4a69-4062-ae69-1dd6c9983a7a.png)

### Composing pages from components

If you want to compose a page/response from multiple components you can use the `ViewContextContainer` as response in your controller, this can be used for [htmx out of band responses](https://htmx.org/examples/update-other-content/#oob).

```kotlin
@Controller
class Router(
private val homeViewComponent: HomeViewComponent,
private val navigationViewComponent: NavigationViewComponent,
) {

@GetMapping("/multi-component")
fun multipleComponent() = ViewContextContainer(
navigationViewComponent.render(),
homeViewComponent.render()
)
}
```

### Local Development

To enable live reload of the components on each save without rebuilding the application add this configuration:
Expand Down Expand Up @@ -206,15 +223,15 @@ We can then call the render method in our Controller
// Router.java
@Controller
public class Router {
private final HomeViewComponent HomeViewComponent;
private final HomeViewComponent homeViewComponent;

public Router(HomeViewComponent HomeViewComponent) {
this.HomeViewComponent = HomeViewComponent;
public Router(HomeViewComponent homeViewComponent) {
this.HomeViewComponent = homeViewComponent;
}

@GetMapping("/")
ViewContext homeView(){
return HomeViewComponent.render();
return homeViewComponent.render();
}
}
```
Expand Down Expand Up @@ -304,6 +321,31 @@ If we now access the root url path of our spring application we can see that the

![viewcomponent-parameter](https://user-images.githubusercontent.com/33346637/222275688-7f301ff7-4a69-4062-ae69-1dd6c9983a7a.png)

### Composing pages from components

If you want to compose a page/response from multiple components you can use the `ViewContextContainer` as response in your controller, this can be used for [htmx out of band responses](https://htmx.org/examples/update-other-content/#oob).

```java
// Router.java
@Controller
public class Router {
private final NavigationViewComponent navigationViewComponent;
private final TableViewComponent tableViewComponent;

public Router(NavigationViewComponent navigationViewComponent, TableViewComponent tableViewComponent) {
this.navigationViewComponent = navigationViewComponent;
this.tableViewComponent = tableViewComponent;
}

@GetMapping("/multi-component")
ViewContextContainer multipleComponent() {
return new ViewContextContainer(
this.navigationViewComponent.render(),
this.tableViewComponent.render()
);
}
}
```

### Local Development

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "de.tschuehly"
version = "0.4.0"
version = "0.4.2"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Component
class ViewComponentMvcConfigurer : WebMvcConfigurer {
class ViewComponentMvcConfigurer(
private val viewContextContainerMethodReturnValueHandler: ViewContextContainerMethodReturnValueHandler
) : WebMvcConfigurer {

override fun addReturnValueHandlers(handlers: MutableList<HandlerMethodReturnValueHandler>) {
handlers.add(ViewComponentMethodReturnValueHandler())
handlers.add(viewContextContainerMethodReturnValueHandler)
handlers.add(ViewContextMethodReturnValueHandler())
super.addReturnValueHandlers(handlers)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.tschuehly.thymeleafviewcomponent

class ViewContextContainer(
vararg val viewContexts: ViewContext
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package de.tschuehly.thymeleafviewcomponent

import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.core.MethodParameter
import org.springframework.stereotype.Component
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.context.support.WebApplicationObjectSupport
import org.springframework.web.method.support.HandlerMethodReturnValueHandler
import org.springframework.web.method.support.ModelAndViewContainer
import org.thymeleaf.spring6.view.ThymeleafView
import org.thymeleaf.spring6.view.ThymeleafViewResolver
import java.util.*

@Component
class ViewContextContainerMethodReturnValueHandler(
private val thymeleafViewResolver: ThymeleafViewResolver
) : HandlerMethodReturnValueHandler, WebApplicationObjectSupport() {

override fun supportsReturnType(returnType: MethodParameter): Boolean {
return ViewContextContainer::class.java.isAssignableFrom(returnType.parameterType)
}

override fun handleReturnValue(
returnValue: Any?,
returnType: MethodParameter,
mavContainer: ModelAndViewContainer,
webRequest: NativeWebRequest
) {
val request = webRequest.getNativeRequest(HttpServletRequest::class.java)!!
val response = webRequest.getNativeResponse(HttpServletResponse::class.java)!!
val viewContextContainer = returnValue as ViewContextContainer
viewContextContainer.viewContexts.forEach { viewContext ->
val view: ThymeleafView =
thymeleafViewResolver.resolveViewName(viewContext.componentTemplate!!, Locale.GERMAN) as ThymeleafView
view.render(viewContext.contextAttributes.toMap(), request, response)

}
mavContainer.isRequestHandled = true
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler
import org.springframework.web.method.support.ModelAndViewContainer


class ViewComponentMethodReturnValueHandler : HandlerMethodReturnValueHandler {
class ViewContextMethodReturnValueHandler : HandlerMethodReturnValueHandler {

override fun supportsReturnType(returnType: MethodParameter): Boolean {
return ViewContext::class.java.isAssignableFrom(returnType.parameterType)
Expand Down

0 comments on commit 022e599

Please sign in to comment.