Skip to content

Commit e670c6b

Browse files
committed
Documentation updates for fragment rendering
Closes gh-33195
1 parent 0bac8d4 commit e670c6b

File tree

6 files changed

+198
-2
lines changed

6 files changed

+198
-2
lines changed

framework-docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
**** xref:web/webmvc-view/mvc-freemarker.adoc[]
206206
**** xref:web/webmvc-view/mvc-groovymarkup.adoc[]
207207
**** xref:web/webmvc-view/mvc-script.adoc[]
208+
**** xref:web/webmvc-view/mvc-fragments.adoc[]
208209
**** xref:web/webmvc-view/mvc-jsp.adoc[]
209210
**** xref:web/webmvc-view/mvc-feeds.adoc[]
210211
**** xref:web/webmvc-view/mvc-document.adoc[]

framework-docs/modules/ROOT/pages/web/webflux-view.adoc

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[webflux-view]]
2-
= View Rendering
2+
= View Technologies
33
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
44

55
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
@@ -418,6 +418,81 @@ for more configuration examples.
418418

419419

420420

421+
[[webflux-view-fragments]]
422+
== HTML Fragment
423+
[.small]#xref:web/webmvc-view/mvc-fragments.adoc[See equivalent in the Servlet stack]#
424+
425+
https://htmx.org/[HTMX] and https://turbo.hotwired.dev/[Hotwire Turbo] emphasize an
426+
HTML-over-the-wire approach where clients receive server updates in HTML rather than in JSON.
427+
This allows the benefits of an SPA (single page app) without having to write much or even
428+
any JavaScript. For a good overview and to learn more, please visit their respective
429+
websites.
430+
431+
In Spring WebFlux, view rendering typically involves specifying one view and one model.
432+
However, in HTML-over-the-wire a common capability is to send multiple HTML fragments that
433+
the browser can use to update different parts of the page. For this, controller methods
434+
can return `Collection<Fragment>`. For example:
435+
436+
[tabs]
437+
======
438+
Java::
439+
+
440+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
441+
----
442+
@GetMapping
443+
List<Fragment> handle() {
444+
return List.of(Fragment.create("posts"), Fragment.create("comments"));
445+
}
446+
----
447+
448+
Kotlin::
449+
+
450+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
451+
----
452+
@GetMapping
453+
fun handle(): List<Fragment> {
454+
return listOf(Fragment.create("posts"), Fragment.create("comments"))
455+
}
456+
----
457+
======
458+
459+
The same can be done also by returning the dedicated type `FragmentsRendering`:
460+
461+
[tabs]
462+
======
463+
Java::
464+
+
465+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
466+
----
467+
@GetMapping
468+
FragmentsRendering handle() {
469+
return FragmentsRendering.with("posts").fragment("comments").build();
470+
}
471+
----
472+
473+
Kotlin::
474+
+
475+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
476+
----
477+
@GetMapping
478+
fun handle(): FragmentsRendering {
479+
return FragmentsRendering.with("posts").fragment("comments").build()
480+
}
481+
----
482+
======
483+
484+
Each fragment can have an independent model, and that model inherits attributes from the
485+
shared model for the request.
486+
487+
HTMX and Hotwire Turbo support streaming updates over SSE (server-sent events).
488+
A controller can create `FragmentsRendering` with a `Flux<Fragment>`, or with any other
489+
reactive producer adaptable to a Reactive Streams `Publisher` via `ReactiveAdapterRegistry`.
490+
It is also possible to return `Flux<Fragment>` directly without the `FragmentsRendering`
491+
wrapper.
492+
493+
494+
495+
421496
[[webflux-view-httpmessagewriter]]
422497
== JSON and XML
423498
[.small]#xref:web/webmvc-view/mvc-jackson.adoc[See equivalent in the Servlet stack]#

framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/return-types.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
7070

7171
| `FragmentsRendering`, `Flux<Fragment>`, `Collection<Fragment>`
7272
| For rendering one or more fragments each with its own view and model.
73+
See xref:web/webflux-view.adoc#webflux-view-fragments[HTML Fragments] for more details.
7374

7475
| `void`
7576
| A method with a `void`, possibly asynchronous (for example, `Mono<Void>`), return type (or a `null` return

framework-docs/modules/ROOT/pages/web/webmvc-view.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[mvc-view]]
2-
= View Rendering
2+
= View Technologies
33
:page-section-summary-toc: 1
44
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
55

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
[[mvc-view-fragments]]
2+
= HTML Fragments
3+
:page-section-summary-toc: 1
4+
5+
[.small]#xref:web/webflux-view.adoc#webflux-view-fragments[See equivalent in the Reactive stack]#
6+
7+
https://htmx.org/[HTMX] and https://turbo.hotwired.dev/[Hotwire Turbo] emphasize an
8+
HTML-over-the-wire approach where clients receive server updates in HTML rather than in JSON.
9+
This allows the benefits of an SPA (single page app) without having to write much or even
10+
any JavaScript. For a good overview and to learn more, please visit their respective
11+
websites.
12+
13+
In Spring MVC, view rendering typically involves specifying one view and one model.
14+
However, in HTML-over-the-wire a common capability is to send multiple HTML fragments that
15+
the browser can use to update different parts of the page. For this, controller methods
16+
can return `Collection<ModelAndView>`. For example:
17+
18+
[tabs]
19+
======
20+
Java::
21+
+
22+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
23+
----
24+
@GetMapping
25+
List<ModelAndView> handle() {
26+
return List.of(new ModelAndView("posts"), new ModelAndView("comments"));
27+
}
28+
----
29+
30+
Kotlin::
31+
+
32+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
33+
----
34+
@GetMapping
35+
fun handle(): List<ModelAndView> {
36+
return listOf(ModelAndView("posts"), ModelAndView("comments"))
37+
}
38+
----
39+
======
40+
41+
The same can be done also by returning the dedicated type `FragmentsRendering`:
42+
43+
[tabs]
44+
======
45+
Java::
46+
+
47+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
48+
----
49+
@GetMapping
50+
FragmentsRendering handle() {
51+
return FragmentsRendering.with("posts").fragment("comments").build();
52+
}
53+
----
54+
55+
Kotlin::
56+
+
57+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
58+
----
59+
@GetMapping
60+
fun handle(): FragmentsRendering {
61+
return FragmentsRendering.with("posts").fragment("comments").build()
62+
}
63+
----
64+
======
65+
66+
Each fragment can have an independent model, and that model inherits attributes from the
67+
shared model for the request.
68+
69+
HTMX and Hotwire Turbo support streaming updates over SSE (server-sent events).
70+
A controller can use `SseEmitter` to send `ModelAndView` to render a fragment per event:
71+
72+
[tabs]
73+
======
74+
Java::
75+
+
76+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
77+
----
78+
@GetMapping
79+
SseEmitter handle() {
80+
SseEmitter emitter = new SseEmitter();
81+
startWorkerThread(() -> {
82+
try {
83+
emitter.send(SseEmitter.event().data(new ModelAndView("posts")));
84+
emitter.send(SseEmitter.event().data(new ModelAndView("comments")));
85+
// ...
86+
}
87+
catch (IOException ex) {
88+
// Cancel sending
89+
}
90+
});
91+
return emitter;
92+
}
93+
----
94+
95+
Kotlin::
96+
+
97+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
98+
----
99+
@GetMapping
100+
fun handle(): SseEmitter {
101+
val emitter = SseEmitter()
102+
startWorkerThread{
103+
try {
104+
emitter.send(SseEmitter.event().data(ModelAndView("posts")))
105+
emitter.send(SseEmitter.event().data(ModelAndView("comments")))
106+
// ...
107+
}
108+
catch (ex: IOException) {
109+
// Cancel sending
110+
}
111+
}
112+
return emitter
113+
}
114+
----
115+
======
116+
117+
The same can also be done by returning `Flux<ModelAndView>`, or any other type adaptable
118+
to a Reactive Streams `Publisher` through the `ReactiveAdapterRegistry`.

framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/return-types.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ supported for all return values.
5858

5959
| `FragmentsRendering`, `Collection<ModelAndView>`
6060
| For rendering one or more fragments each with its own view and model.
61+
See xref:web/webmvc-view/mvc-fragments.adoc[HTML Fragments] for more details.
6162

6263
| `void`
6364
| A method with a `void` return type (or `null` return value) is considered to have fully

0 commit comments

Comments
 (0)