From e670c6b50a9a309b808b0349703a234e53925054 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 7 Aug 2024 15:10:25 +0300 Subject: [PATCH] Documentation updates for fragment rendering Closes gh-33195 --- framework-docs/modules/ROOT/nav.adoc | 1 + .../modules/ROOT/pages/web/webflux-view.adoc | 77 +++++++++++- .../controller/ann-methods/return-types.adoc | 1 + .../modules/ROOT/pages/web/webmvc-view.adoc | 2 +- .../pages/web/webmvc-view/mvc-fragments.adoc | 118 ++++++++++++++++++ .../ann-methods/return-types.adoc | 1 + 6 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index ddd24c8a129..27d7bfd9551 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -205,6 +205,7 @@ **** xref:web/webmvc-view/mvc-freemarker.adoc[] **** xref:web/webmvc-view/mvc-groovymarkup.adoc[] **** xref:web/webmvc-view/mvc-script.adoc[] +**** xref:web/webmvc-view/mvc-fragments.adoc[] **** xref:web/webmvc-view/mvc-jsp.adoc[] **** xref:web/webmvc-view/mvc-feeds.adoc[] **** xref:web/webmvc-view/mvc-document.adoc[] diff --git a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc index efe180d8812..6f8726d4866 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc @@ -1,5 +1,5 @@ [[webflux-view]] -= View Rendering += View Technologies [.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]# The rendering of views in Spring WebFlux is pluggable. Whether you decide to @@ -418,6 +418,81 @@ for more configuration examples. +[[webflux-view-fragments]] +== HTML Fragment +[.small]#xref:web/webmvc-view/mvc-fragments.adoc[See equivalent in the Servlet stack]# + +https://htmx.org/[HTMX] and https://turbo.hotwired.dev/[Hotwire Turbo] emphasize an +HTML-over-the-wire approach where clients receive server updates in HTML rather than in JSON. +This allows the benefits of an SPA (single page app) without having to write much or even +any JavaScript. For a good overview and to learn more, please visit their respective +websites. + +In Spring WebFlux, view rendering typically involves specifying one view and one model. +However, in HTML-over-the-wire a common capability is to send multiple HTML fragments that +the browser can use to update different parts of the page. For this, controller methods +can return `Collection`. For example: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @GetMapping + List handle() { + return List.of(Fragment.create("posts"), Fragment.create("comments")); + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @GetMapping + fun handle(): List { + return listOf(Fragment.create("posts"), Fragment.create("comments")) + } +---- +====== + +The same can be done also by returning the dedicated type `FragmentsRendering`: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @GetMapping + FragmentsRendering handle() { + return FragmentsRendering.with("posts").fragment("comments").build(); + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @GetMapping + fun handle(): FragmentsRendering { + return FragmentsRendering.with("posts").fragment("comments").build() + } +---- +====== + +Each fragment can have an independent model, and that model inherits attributes from the +shared model for the request. + +HTMX and Hotwire Turbo support streaming updates over SSE (server-sent events). +A controller can create `FragmentsRendering` with a `Flux`, or with any other +reactive producer adaptable to a Reactive Streams `Publisher` via `ReactiveAdapterRegistry`. +It is also possible to return `Flux` directly without the `FragmentsRendering` +wrapper. + + + + [[webflux-view-httpmessagewriter]] == JSON and XML [.small]#xref:web/webmvc-view/mvc-jackson.adoc[See equivalent in the Servlet stack]# diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/return-types.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/return-types.adoc index 780da316c50..22fe3570b6c 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/return-types.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/return-types.adoc @@ -70,6 +70,7 @@ Controllers can then return a `Flux>`; Reactor provides a dedicated oper | `FragmentsRendering`, `Flux`, `Collection` | For rendering one or more fragments each with its own view and model. + See xref:web/webflux-view.adoc#webflux-view-fragments[HTML Fragments] for more details. | `void` | A method with a `void`, possibly asynchronous (for example, `Mono`), return type (or a `null` return diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view.adoc index 2680a05fc84..a72efa0b4f4 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view.adoc @@ -1,5 +1,5 @@ [[mvc-view]] -= View Rendering += View Technologies :page-section-summary-toc: 1 [.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]# diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc new file mode 100644 index 00000000000..b09fdd86330 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc @@ -0,0 +1,118 @@ +[[mvc-view-fragments]] += HTML Fragments +:page-section-summary-toc: 1 + +[.small]#xref:web/webflux-view.adoc#webflux-view-fragments[See equivalent in the Reactive stack]# + +https://htmx.org/[HTMX] and https://turbo.hotwired.dev/[Hotwire Turbo] emphasize an +HTML-over-the-wire approach where clients receive server updates in HTML rather than in JSON. +This allows the benefits of an SPA (single page app) without having to write much or even +any JavaScript. For a good overview and to learn more, please visit their respective +websites. + +In Spring MVC, view rendering typically involves specifying one view and one model. +However, in HTML-over-the-wire a common capability is to send multiple HTML fragments that +the browser can use to update different parts of the page. For this, controller methods +can return `Collection`. For example: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @GetMapping + List handle() { + return List.of(new ModelAndView("posts"), new ModelAndView("comments")); + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @GetMapping + fun handle(): List { + return listOf(ModelAndView("posts"), ModelAndView("comments")) + } +---- +====== + +The same can be done also by returning the dedicated type `FragmentsRendering`: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @GetMapping + FragmentsRendering handle() { + return FragmentsRendering.with("posts").fragment("comments").build(); + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @GetMapping + fun handle(): FragmentsRendering { + return FragmentsRendering.with("posts").fragment("comments").build() + } +---- +====== + +Each fragment can have an independent model, and that model inherits attributes from the +shared model for the request. + +HTMX and Hotwire Turbo support streaming updates over SSE (server-sent events). +A controller can use `SseEmitter` to send `ModelAndView` to render a fragment per event: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @GetMapping + SseEmitter handle() { + SseEmitter emitter = new SseEmitter(); + startWorkerThread(() -> { + try { + emitter.send(SseEmitter.event().data(new ModelAndView("posts"))); + emitter.send(SseEmitter.event().data(new ModelAndView("comments"))); + // ... + } + catch (IOException ex) { + // Cancel sending + } + }); + return emitter; + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @GetMapping + fun handle(): SseEmitter { + val emitter = SseEmitter() + startWorkerThread{ + try { + emitter.send(SseEmitter.event().data(ModelAndView("posts"))) + emitter.send(SseEmitter.event().data(ModelAndView("comments"))) + // ... + } + catch (ex: IOException) { + // Cancel sending + } + } + return emitter + } +---- +====== + +The same can also be done by returning `Flux`, or any other type adaptable +to a Reactive Streams `Publisher` through the `ReactiveAdapterRegistry`. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/return-types.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/return-types.adoc index 5d0b6a1af4e..189bf5e0f8f 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/return-types.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/return-types.adoc @@ -58,6 +58,7 @@ supported for all return values. | `FragmentsRendering`, `Collection` | For rendering one or more fragments each with its own view and model. + See xref:web/webmvc-view/mvc-fragments.adoc[HTML Fragments] for more details. | `void` | A method with a `void` return type (or `null` return value) is considered to have fully