From fa533ae0cb4d36462046041f26b3cd9de69efe2e Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Tue, 30 Apr 2024 05:32:20 +0100 Subject: [PATCH] fix: Turbo Frame processing should use decoration (#796) As with TurboStream done here https://github.com/micronaut-projects/micronaut-views/pull/778 Fixes #792 --- .../io/micronaut/views/ModelAndViewTest.java | 18 ++++++ .../views/turbo/AbstractTurboRenderer.java | 58 ++++++++++++++----- .../turbo/DefaultTurboFrameRenderer.java | 25 +++++++- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/test-suite/src/test/java/io/micronaut/views/ModelAndViewTest.java b/test-suite/src/test/java/io/micronaut/views/ModelAndViewTest.java index 2b75c708d..262c912d3 100644 --- a/test-suite/src/test/java/io/micronaut/views/ModelAndViewTest.java +++ b/test-suite/src/test/java/io/micronaut/views/ModelAndViewTest.java @@ -9,6 +9,7 @@ import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Produces; @@ -21,6 +22,7 @@ import io.micronaut.views.model.ConfigViewModelProcessor; import io.micronaut.views.model.FruitsController; import io.micronaut.views.model.ViewModelProcessor; +import io.micronaut.views.turbo.TurboFrame; import io.micronaut.views.turbo.TurboStream; import io.micronaut.views.turbo.TurboStreamAction; import io.micronaut.views.turbo.http.TurboMediaType; @@ -41,6 +43,7 @@ @Property(name = "micronaut.security.enabled", value = StringUtils.FALSE) @MicronautTest class ModelAndViewTest { + @Inject @Client("/") HttpClient httpClient; @@ -175,6 +178,13 @@ void viewModelProcessorsWorkWithControllersReturningPOJOs() { request = HttpRequest.GET("/turboStreamBuilderWithProcessor").accept(TurboMediaType.TURBO_STREAM); html = client.retrieve(request, String.class); assertTrue(html.contains("

config: test

")); + + //when: + request = HttpRequest.GET("/turboFrameBuilderWithProcessor").accept(MediaType.TEXT_HTML); + html = client.retrieve(request, String.class); + assertTrue(html.startsWith("")); + assertTrue(html.endsWith("")); + assertTrue(html.contains("

config: test

")); } @Requires(property = "spec.name", value = "ModelAndViewSpec") @@ -193,6 +203,14 @@ public TurboStream.Builder turboStreamBuilder() { .action(TurboStreamAction.REPLACE) .template("fruits-processor", new Fruit("orange", "orange")); } + + @Produces(MediaType.TEXT_HTML) + @Get("/turboFrameBuilderWithProcessor") + public TurboFrame.Builder turboFrameBuilder() { + return (TurboFrame.Builder) TurboFrame.builder() + .templateView("fruits-processor") + .templateModel(new Fruit("orange", "orange")); + } } @Introspected diff --git a/views-core/src/main/java/io/micronaut/views/turbo/AbstractTurboRenderer.java b/views-core/src/main/java/io/micronaut/views/turbo/AbstractTurboRenderer.java index 02d81c3d7..99d1eaa0a 100644 --- a/views-core/src/main/java/io/micronaut/views/turbo/AbstractTurboRenderer.java +++ b/views-core/src/main/java/io/micronaut/views/turbo/AbstractTurboRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022 original authors + * Copyright 2017-2024 original authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,37 +15,61 @@ */ package io.micronaut.views.turbo; +import io.micronaut.core.annotation.NextMajorVersion; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.io.Writable; import io.micronaut.http.HttpRequest; +import io.micronaut.views.ModelAndView; import io.micronaut.views.TemplatedBuilder; +import io.micronaut.views.ViewsModelDecorator; import io.micronaut.views.ViewsRendererLocator; +import io.micronaut.views.turbo.http.TurboMediaType; +import jakarta.inject.Inject; import java.util.Optional; /** + * @param The class to be built * @author Sergio del Amo * @since 3.4.0 - * @param The class to be built */ public abstract class AbstractTurboRenderer> { + private final ViewsRendererLocator viewsRendererLocator; private final String mediaType; + @Nullable + @NextMajorVersion("remove the nullability annotation") + private final ViewsModelDecorator viewsModelDecorator; + /** - * * @param viewsRendererLocator Views renderer Locator - * @param mediaType Media Type + * @param viewsModelDecorator Views Model Decorator + * @param mediaType Media Type */ - protected AbstractTurboRenderer(ViewsRendererLocator viewsRendererLocator, - String mediaType) { + @Inject + protected AbstractTurboRenderer( + ViewsRendererLocator viewsRendererLocator, + ViewsModelDecorator viewsModelDecorator, + String mediaType + ) { this.viewsRendererLocator = viewsRendererLocator; + this.viewsModelDecorator = viewsModelDecorator; this.mediaType = mediaType; } /** - * + * @param viewsRendererLocator Views renderer Locator + * @param mediaType Media Type + */ + @Deprecated(since = "5.2.1", forRemoval = true) + protected AbstractTurboRenderer(ViewsRendererLocator viewsRendererLocator, + String mediaType) { + this(viewsRendererLocator, null, mediaType); + } + + /** * @param builder Builder * @param request The Request * @return An Optional Writable with the builder rendered @@ -54,13 +78,17 @@ protected AbstractTurboRenderer(ViewsRendererLocator viewsRendererLocator, public Optional render(@NonNull T builder, @Nullable HttpRequest request) { return builder.getTemplateView() - .map(viewName -> { - Object model = builder.getTemplateModel().orElse(null); - return viewsRendererLocator.resolveViewsRenderer(viewName, mediaType, model) - .flatMap(renderer -> builder.template(renderer.render(viewName, model, request)) - .build() - .render()); - }) - .orElseGet(() -> builder.build().render()); + .map(viewName -> { + Object model = builder.getTemplateModel().orElse(null); + ModelAndView modelAndView = new ModelAndView<>(viewName, model); + if (request != null && viewsModelDecorator != null) { + viewsModelDecorator.decorate(request, modelAndView); + } + Object decoratedModel = modelAndView.getModel().orElse(null); + return viewsRendererLocator.resolveViewsRenderer(viewName, TurboMediaType.TURBO_STREAM, decoratedModel) + .flatMap(renderer -> builder.template(renderer.render(viewName, decoratedModel, request)).build() + .render()); + }) + .orElseGet(() -> builder.build().render()); } } diff --git a/views-core/src/main/java/io/micronaut/views/turbo/DefaultTurboFrameRenderer.java b/views-core/src/main/java/io/micronaut/views/turbo/DefaultTurboFrameRenderer.java index c0e52c978..8577a03ec 100644 --- a/views-core/src/main/java/io/micronaut/views/turbo/DefaultTurboFrameRenderer.java +++ b/views-core/src/main/java/io/micronaut/views/turbo/DefaultTurboFrameRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022 original authors + * Copyright 2017-2024 original authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,11 @@ */ package io.micronaut.views.turbo; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpRequest; +import io.micronaut.views.ViewsModelDecorator; import io.micronaut.views.ViewsRendererLocator; +import jakarta.inject.Inject; import jakarta.inject.Singleton; /** @@ -24,12 +28,29 @@ * @since 3.4.0 */ @Singleton +@Requires(classes = HttpRequest.class) public class DefaultTurboFrameRenderer extends AbstractTurboRenderer implements TurboFrameRenderer { + /** * Constructor. * @param viewsRendererLocator Views Renderer Locator. + * @param viewsModelDecorator Views Model Decorator + */ + @Inject + public DefaultTurboFrameRenderer( + ViewsRendererLocator viewsRendererLocator, + ViewsModelDecorator viewsModelDecorator + ) { + super(viewsRendererLocator, viewsModelDecorator, "text/html"); + } + + /** + * + * @param viewsRendererLocator View Renderer Locator + * @deprecated Use {@link #DefaultTurboFrameRenderer(ViewsRendererLocator, ViewsModelDecorator)} instead. */ + @Deprecated(since = "5.2.1", forRemoval = true) public DefaultTurboFrameRenderer(ViewsRendererLocator viewsRendererLocator) { - super(viewsRendererLocator, "text/html"); + this(viewsRendererLocator, null); } }