diff --git a/README.md b/README.md index 9b309cc..bcb165e 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ spring.view-component.local-development=true Gradle ```kotlin -implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.1") +implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.2-SNAPSHOT") sourceSets { main { resources { @@ -249,7 +249,7 @@ sourceSets { de.tschuehly spring-view-component-thymeleaf - 0.8.1 + 0.8.2-SNAPSHOT @@ -286,7 +286,7 @@ plugins { id("gg.jte.gradle") version("3.1.12") } -implementation("de.tschuehly:spring-view-component-jte:0.8.1") +implementation("de.tschuehly:spring-view-component-jte:0.8.2-SNAPSHOT") jte{ generate() @@ -305,7 +305,7 @@ jte{ de.tschuehly spring-view-component-jte - 0.8.1 + 0.8.2-SNAPSHOT @@ -343,7 +343,7 @@ jte{ Gradle ```kotlin -implementation("de.tschuehly:spring-view-component-kte:0.8.1") +implementation("de.tschuehly:spring-view-component-kte:0.8.2-SNAPSHOT") jte{ generate() sourceDirectory = Path("src/main/kotlin") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 15b94c1..fdee286 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -14,7 +14,7 @@ plugins { } group = "de.tschuehly" -version = "0.8.1" +version = "0.8.2-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_17 repositories { diff --git a/core/src/testFixtures/kotlin/de/tschuehly/spring/viewcomponent/core/IntegrationTestBase.kt b/core/src/testFixtures/kotlin/de/tschuehly/spring/viewcomponent/core/IntegrationTestBase.kt index f49f1da..46e240e 100644 --- a/core/src/testFixtures/kotlin/de/tschuehly/spring/viewcomponent/core/IntegrationTestBase.kt +++ b/core/src/testFixtures/kotlin/de/tschuehly/spring/viewcomponent/core/IntegrationTestBase.kt @@ -8,9 +8,11 @@ import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity + abstract class IntegrationTestBase { @Autowired lateinit var testRestTemplate: TestRestTemplate + @Test fun testIndexComponent() { val expectedHtml = @@ -37,6 +39,9 @@ abstract class IntegrationTestBase {

This is the SimpleViewComponent

Hello World
+
+ +
""".trimIndent() assertEndpointReturns("/simple", expectedHtml) @@ -45,23 +50,27 @@ abstract class IntegrationTestBase { @Test fun testLayoutComponent() { //language=html - val expectedHtml = - """ + val expectedHtml = """

This is the SimpleViewComponent

-
Hello World
-
This is a footer
+
Hello World
+
+ +
+ + +
This is a footer
+ """.trimIndent() assertEndpointReturns("/layout", expectedHtml) } fun assertEndpointReturns(url: String, expectedHtml: String) { - val response: ResponseEntity = this.testRestTemplate - .exchange(url, HttpMethod.GET, null, String::class.java) - assertThat(response.statusCode) - .isEqualTo(HttpStatus.OK) + val response: ResponseEntity = + this.testRestTemplate.exchange(url, HttpMethod.GET, null, String::class.java) + assertThat(response.statusCode).isEqualTo(HttpStatus.OK) Assertions.assertEquals( expectedHtml.rmWhitespaceBetweenHtmlTags(), response.body?.rmWhitespaceBetweenHtmlTags() ) @@ -69,10 +78,7 @@ abstract class IntegrationTestBase { fun String.rmWhitespaceBetweenHtmlTags(): String { // Replace whitespace between > and word - return this.replace("(?<=>)(\\s*)(?=\\w)".toRegex(), "") - .replace("(?<=\\w)(\\s*)(?=<)".toRegex(), "") - .replace("(?<=>)(\\s*)(?=<)".toRegex(), "") - .replace("\r\n","\n") - .trim() + return this.replace("(?<=>)(\\s*)(?=\\w)".toRegex(), "").replace("(?<=\\w)(\\s*)(?=<)".toRegex(), "") + .replace("(?<=>)(\\s*)(?=<)".toRegex(), "").replace("\r\n", "\n").trim() } } \ No newline at end of file diff --git a/examples/.run/Run all example tests.run.xml b/examples/.run/Run all example tests.run.xml index 12581ed..aa49eac 100644 --- a/examples/.run/Run all example tests.run.xml +++ b/examples/.run/Run all example tests.run.xml @@ -4,7 +4,7 @@ diff --git a/examples/jte-example/build.gradle.kts b/examples/jte-example/build.gradle.kts index 455abe0..2f9f4db 100644 --- a/examples/jte-example/build.gradle.kts +++ b/examples/jte-example/build.gradle.kts @@ -26,14 +26,14 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") - implementation("de.tschuehly:spring-view-component-jte:0.8.1") + implementation("de.tschuehly:spring-view-component-jte:0.8.2-SNAPSHOT") implementation("org.webjars.npm:htmx.org:1.9.11") implementation("org.webjars:webjars-locator-core:0.58") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.springframework.boot:spring-boot-devtools") - testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.1")) + testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT")) } tasks.withType { diff --git a/examples/jte-example/pom.xml b/examples/jte-example/pom.xml index 7266415..e789e49 100644 --- a/examples/jte-example/pom.xml +++ b/examples/jte-example/pom.xml @@ -15,7 +15,7 @@ JTE Example 17 - 0.8.1 + 0.8.2-SNAPSHOT diff --git a/examples/jte-example/src/main/java/de/tschuehly/example/jte/web/simple/SimpleViewComponent.jte b/examples/jte-example/src/main/java/de/tschuehly/example/jte/web/simple/SimpleViewComponent.jte index 33cc24d..55f9f9e 100644 --- a/examples/jte-example/src/main/java/de/tschuehly/example/jte/web/simple/SimpleViewComponent.jte +++ b/examples/jte-example/src/main/java/de/tschuehly/example/jte/web/simple/SimpleViewComponent.jte @@ -3,4 +3,7 @@

This is the SimpleViewComponent

${simpleView.helloWorld()}
+
+ +
\ No newline at end of file diff --git a/examples/kte-example/build.gradle.kts b/examples/kte-example/build.gradle.kts index beeb786..0b56f95 100644 --- a/examples/kte-example/build.gradle.kts +++ b/examples/kte-example/build.gradle.kts @@ -31,8 +31,8 @@ repositories { } dependencies { - implementation("de.tschuehly:spring-view-component-kte:0.8.1") - implementation("de.tschuehly:spring-view-component-core:0.8.1") + implementation("de.tschuehly:spring-view-component-kte:0.8.2-SNAPSHOT") + implementation("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT") implementation("io.github.wimdeblauwe:htmx-spring-boot:3.1.1") implementation("org.webjars.npm:htmx.org:1.9.11") @@ -47,7 +47,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-devtools") - testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.1")) + testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT")) } tasks.withType { diff --git a/examples/kte-example/src/main/kotlin/de/tschuehly/kteviewcomponentexample/web/simple/SimpleViewComponent.kte b/examples/kte-example/src/main/kotlin/de/tschuehly/kteviewcomponentexample/web/simple/SimpleViewComponent.kte index 5bcccff..991aeb0 100644 --- a/examples/kte-example/src/main/kotlin/de/tschuehly/kteviewcomponentexample/web/simple/SimpleViewComponent.kte +++ b/examples/kte-example/src/main/kotlin/de/tschuehly/kteviewcomponentexample/web/simple/SimpleViewComponent.kte @@ -2,4 +2,7 @@

This is the SimpleViewComponent

${simpleView.helloWorld}
+
+ +
\ No newline at end of file diff --git a/examples/thymeleaf-java-example/build.gradle.kts b/examples/thymeleaf-java-example/build.gradle.kts index 3cf7cf5..657811a 100644 --- a/examples/thymeleaf-java-example/build.gradle.kts +++ b/examples/thymeleaf-java-example/build.gradle.kts @@ -29,14 +29,14 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") - implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.1") + implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.2-SNAPSHOT") implementation("org.webjars.npm:htmx.org:1.9.11") implementation("org.webjars:webjars-locator-core:0.58") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-devtools") - testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.1")) + testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT")) } tasks.withType { diff --git a/examples/thymeleaf-java-example/pom.xml b/examples/thymeleaf-java-example/pom.xml index 1c058e9..3fb60d7 100644 --- a/examples/thymeleaf-java-example/pom.xml +++ b/examples/thymeleaf-java-example/pom.xml @@ -15,7 +15,7 @@ Thymeleaf Java Example 17 - 0.8.1 + 0.8.2-SNAPSHOT diff --git a/examples/thymeleaf-java-example/src/main/java/de/tschuehly/example/thymeleafjava/web/simple/SimpleViewComponent.html b/examples/thymeleaf-java-example/src/main/java/de/tschuehly/example/thymeleafjava/web/simple/SimpleViewComponent.html index b130dba..ebec03a 100644 --- a/examples/thymeleaf-java-example/src/main/java/de/tschuehly/example/thymeleafjava/web/simple/SimpleViewComponent.html +++ b/examples/thymeleaf-java-example/src/main/java/de/tschuehly/example/thymeleafjava/web/simple/SimpleViewComponent.html @@ -2,4 +2,8 @@

This is the SimpleViewComponent

+ +
+ +
\ No newline at end of file diff --git a/examples/thymeleaf-kotlin-example/build.gradle.kts b/examples/thymeleaf-kotlin-example/build.gradle.kts index 8f98caa..ba171b2 100644 --- a/examples/thymeleaf-kotlin-example/build.gradle.kts +++ b/examples/thymeleaf-kotlin-example/build.gradle.kts @@ -18,8 +18,8 @@ repositories { } dependencies { - implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.1") - implementation("de.tschuehly:spring-view-component-core:0.8.1") + implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.2-SNAPSHOT") + implementation("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT") implementation("org.webjars.npm:htmx.org:1.9.2") implementation("org.webjars:webjars-locator:0.47") @@ -35,7 +35,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-devtools") - testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.1")) + testImplementation(testFixtures("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT")) } tasks.withType { diff --git a/examples/thymeleaf-kotlin-example/src/main/kotlin/de/tschuehly/example/thymeleafkotlin/web/simple/SimpleViewComponent.html b/examples/thymeleaf-kotlin-example/src/main/kotlin/de/tschuehly/example/thymeleafkotlin/web/simple/SimpleViewComponent.html index 88b63ed..a85dea9 100644 --- a/examples/thymeleaf-kotlin-example/src/main/kotlin/de/tschuehly/example/thymeleafkotlin/web/simple/SimpleViewComponent.html +++ b/examples/thymeleaf-kotlin-example/src/main/kotlin/de/tschuehly/example/thymeleafkotlin/web/simple/SimpleViewComponent.html @@ -2,4 +2,7 @@

This is the SimpleViewComponent

+
+ +
\ No newline at end of file diff --git a/jte/build.gradle.kts b/jte/build.gradle.kts index 6a99277..e836a76 100644 --- a/jte/build.gradle.kts +++ b/jte/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } group = "de.tschuehly" -version = "0.8.1" +version = "0.8.2-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_17 repositories { @@ -22,7 +22,7 @@ repositories { } dependencies { api("gg.jte:jte:3.1.12") - api("de.tschuehly:spring-view-component-core:0.8.1") + api("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT") implementation("gg.jte:jte-spring-boot-starter-3:3.1.12") implementation("org.springframework.boot:spring-boot-starter-web") diff --git a/kte/build.gradle.kts b/kte/build.gradle.kts index 7ddb89d..ba43268 100644 --- a/kte/build.gradle.kts +++ b/kte/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } group = "de.tschuehly" -version = "0.8.1" +version = "0.8.2-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_17 repositories { @@ -21,7 +21,7 @@ repositories { } dependencies { api("gg.jte:jte-kotlin:3.1.12") - api("de.tschuehly:spring-view-component-core:0.8.1") + api("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT") implementation("gg.jte:jte-spring-boot-starter-3:3.1.12") implementation("org.springframework.boot:spring-boot-starter-web") diff --git a/thymeleaf/build.gradle.kts b/thymeleaf/build.gradle.kts index 1d34d2f..0e650bd 100644 --- a/thymeleaf/build.gradle.kts +++ b/thymeleaf/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } group = "de.tschuehly" -version = "0.8.1" +version = "0.8.2-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_17 repositories { @@ -21,7 +21,7 @@ repositories { } dependencies { - api("de.tschuehly:spring-view-component-core:0.8.1") + api("de.tschuehly:spring-view-component-core:0.8.2-SNAPSHOT") api("org.springframework.boot:spring-boot-devtools") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") diff --git a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentAutoConfiguration.kt b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentAutoConfiguration.kt index 8bb133b..744c491 100644 --- a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentAutoConfiguration.kt +++ b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentAutoConfiguration.kt @@ -3,9 +3,9 @@ package de.tschuehly.spring.viewcomponent.thymeleaf import de.tschuehly.spring.viewcomponent.core.ViewComponentAutoConfiguration import de.tschuehly.spring.viewcomponent.core.component.ViewComponentProperties import org.springframework.beans.factory.ObjectProvider -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties +import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import @@ -26,7 +26,8 @@ class ThymeleafViewComponentAutoConfiguration { fun templateEngine( properties: ThymeleafProperties, templateResolvers: ObjectProvider, - dialects: ObjectProvider + dialects: ObjectProvider, + applicationContext: ApplicationContext ): SpringTemplateEngine { val engine = SpringTemplateEngine() engine.enableSpringELCompiler = properties.isEnableSpringElCompiler @@ -41,7 +42,7 @@ class ThymeleafViewComponentAutoConfiguration { dialect ) } - engine.addDialect(ThymeleafViewComponentDialect()) + engine.addDialect(ThymeleafViewComponentDialect(applicationContext)) return engine } diff --git a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentDialect.kt b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentDialect.kt index 5799f53..d8d9bde 100644 --- a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentDialect.kt +++ b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentDialect.kt @@ -1,15 +1,16 @@ package de.tschuehly.spring.viewcomponent.thymeleaf +import org.springframework.context.ApplicationContext import org.thymeleaf.dialect.AbstractProcessorDialect import org.thymeleaf.processor.IProcessor import org.thymeleaf.standard.StandardDialect -class ThymeleafViewComponentDialect : AbstractProcessorDialect( +class ThymeleafViewComponentDialect(private val applicationContext: ApplicationContext) : AbstractProcessorDialect( "ViewComponent Dialect", "view", StandardDialect.PROCESSOR_PRECEDENCE ) { override fun getProcessors(dialectPrefix: String): MutableSet { return mutableSetOf( - ThymeleafViewComponentTagProcessor(dialectPrefix) + ThymeleafViewComponentTagProcessor(dialectPrefix,applicationContext) ) } diff --git a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentTagProcessor.kt b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentTagProcessor.kt index 77ea45c..89bc90b 100644 --- a/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentTagProcessor.kt +++ b/thymeleaf/src/main/kotlin/de/tschuehly/spring/viewcomponent/thymeleaf/ThymeleafViewComponentTagProcessor.kt @@ -2,6 +2,10 @@ package de.tschuehly.spring.viewcomponent.thymeleaf import de.tschuehly.spring.viewcomponent.core.IViewContext import org.slf4j.LoggerFactory +import org.springframework.context.ApplicationContext +import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.context.request.ServletRequestAttributes +import org.springframework.web.util.ContentCachingResponseWrapper import org.thymeleaf.context.ITemplateContext import org.thymeleaf.context.WebEngineContext import org.thymeleaf.engine.AttributeName @@ -9,12 +13,12 @@ import org.thymeleaf.engine.EngineEventUtils import org.thymeleaf.model.IProcessableElementTag import org.thymeleaf.processor.element.AbstractAttributeTagProcessor import org.thymeleaf.processor.element.IElementTagStructureHandler -import org.thymeleaf.spring6.SpringTemplateEngine -import org.thymeleaf.spring6.context.SpringContextUtils +import org.thymeleaf.spring6.view.ThymeleafViewResolver import org.thymeleaf.templatemode.TemplateMode +import java.nio.charset.StandardCharsets -class ThymeleafViewComponentTagProcessor(dialectPrefix: String) : +class ThymeleafViewComponentTagProcessor(dialectPrefix: String, private val applicationContext: ApplicationContext) : AbstractAttributeTagProcessor( /* templateMode = */ TemplateMode.HTML, /* dialectPrefix = */ dialectPrefix, @@ -40,9 +44,7 @@ class ThymeleafViewComponentTagProcessor(dialectPrefix: String) : attributeValue: String, structureHandler: IElementTagStructureHandler ) { - val expression = EngineEventUtils.computeAttributeExpression(context, tag, attributeName, attributeValue) - val webContext = context as WebEngineContext val viewContext = try { expression.execute(webContext) as IViewContext @@ -55,16 +57,20 @@ class ThymeleafViewComponentTagProcessor(dialectPrefix: String) : logger.error("Could not execute expression: \"${expression.stringRepresentation}\"") throw ThymeleafViewComponentException(e.message, e.cause) } - val appCtx = SpringContextUtils.getApplicationContext(webContext) - val engine = appCtx.getBean(SpringTemplateEngine::class.java) - - webContext.setVariable(viewContext.javaClass.simpleName.replaceFirstChar { it.lowercase() }, viewContext) - - // TODO: Cannot process attribute '{th:field,data-th-field}': no associated BindStatus could be found for the intended form binding operations. This can be due to the lack of a proper management of the Spring RequestContext, which is usually done through the ThymeleafView or ThymeleafReactiveView (template: - val modelFactory = webContext.modelFactory - engine.enableSpringELCompiler = true - val viewComponentBody = modelFactory.createText( - engine.process(IViewContext.getViewComponentTemplateWithoutSuffix(viewContext), webContext) + val response = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.response!! + val wrapper = ContentCachingResponseWrapper(response) + val thymeleafViewResolver = applicationContext.getBean(ThymeleafViewResolver::class.java) + val viewName = thymeleafViewResolver.resolveViewName( + IViewContext.getViewComponentTemplateWithoutSuffix(viewContext), + webContext.locale + ) ?: throw ThymeleafViewComponentException("No ViewName", null) + viewName.render( + mapOf(viewContext.javaClass.simpleName.replaceFirstChar { it.lowercase() } to viewContext), + (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.request!!, + wrapper + ) + val viewComponentBody = webContext.modelFactory.createText( + String(wrapper.contentAsByteArray, StandardCharsets.UTF_8) ) structureHandler.replaceWith(viewComponentBody, true)