Documentation updates for fragment rendering

Closes gh-33195
This commit is contained in:
rstoyanchev 2024-08-07 15:10:25 +03:00
parent 0bac8d48a5
commit e670c6b50a
6 changed files with 198 additions and 2 deletions

View File

@ -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[]

View File

@ -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<Fragment>`. For example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@GetMapping
List<Fragment> handle() {
return List.of(Fragment.create("posts"), Fragment.create("comments"));
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping
fun handle(): List<Fragment> {
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<Fragment>`, or with any other
reactive producer adaptable to a Reactive Streams `Publisher` via `ReactiveAdapterRegistry`.
It is also possible to return `Flux<Fragment>` 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]#

View File

@ -70,6 +70,7 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
| `FragmentsRendering`, `Flux<Fragment>`, `Collection<Fragment>`
| 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<Void>`), return type (or a `null` return

View File

@ -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]#

View File

@ -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<ModelAndView>`. For example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@GetMapping
List<ModelAndView> handle() {
return List.of(new ModelAndView("posts"), new ModelAndView("comments"));
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping
fun handle(): List<ModelAndView> {
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<ModelAndView>`, or any other type adaptable
to a Reactive Streams `Publisher` through the `ReactiveAdapterRegistry`.

View File

@ -58,6 +58,7 @@ supported for all return values.
| `FragmentsRendering`, `Collection<ModelAndView>`
| 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