Skip to content

Commit b58f34d

Browse files
ielatifsbrannen
authored andcommitted
Add FreeMarker macro support in spring-webflux
Closes gh-23002
1 parent 9b084bb commit b58f34d

File tree

6 files changed

+923
-2
lines changed

6 files changed

+923
-2
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/freemarker/FreeMarkerConfigurer.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
* instance via the "configuration" property. This allows to share a FreeMarker
4747
* Configuration for web and email usage for example.
4848
*
49-
* <p>TODO: macros
50-
*
5149
* <p>This configurer registers a template loader for this package, allowing to
5250
* reference the "spring.ftl" macro library contained in this package:
5351
*
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
<#ftl output_format="HTML" strip_whitespace=true>
2+
<#--
3+
* spring.ftl
4+
*
5+
* This file consists of a collection of FreeMarker macros aimed at easing
6+
* some of the common requirements of web applications - in particular
7+
* handling of forms.
8+
*
9+
* Spring's FreeMarker support will automatically make this file and therefore
10+
* all macros within it available to any application using Spring's
11+
* FreeMarkerConfigurer.
12+
*
13+
* To take advantage of these macros, the "exposeSpringMacroHelpers" property
14+
* of the FreeMarker class needs to be set to "true". This will expose a
15+
* RequestContext under the name "springMacroRequestContext", as needed by
16+
* the macros in this library.
17+
*
18+
* @author Darren Davison
19+
* @author Juergen Hoeller
20+
* @author Issam El-atif
21+
* @since 5.2
22+
-->
23+
24+
<#--
25+
* message
26+
*
27+
* Macro to translate a message code into a message.
28+
-->
29+
<#macro message code>${springMacroRequestContext.getMessage(code)?no_esc}</#macro>
30+
31+
<#--
32+
* messageText
33+
*
34+
* Macro to translate a message code into a message,
35+
* using the given default text if no message found.
36+
-->
37+
<#macro messageText code, text>${springMacroRequestContext.getMessage(code, text)?no_esc}</#macro>
38+
39+
<#--
40+
* messageArgs
41+
*
42+
* Macro to translate a message code with arguments into a message.
43+
-->
44+
<#macro messageArgs code, args>${springMacroRequestContext.getMessage(code, args)?no_esc}</#macro>
45+
46+
<#--
47+
* messageArgsText
48+
*
49+
* Macro to translate a message code with arguments into a message,
50+
* using the given default text if no message found.
51+
-->
52+
<#macro messageArgsText code, args, text>${springMacroRequestContext.getMessage(code, args, text)?no_esc}</#macro>
53+
54+
<#--
55+
* url
56+
*
57+
* Takes a relative URL and makes it absolute from the server root by
58+
* adding the context root for the web application.
59+
-->
60+
<#macro url relativeUrl extra...><#if extra?? && extra?size!=0>${springMacroRequestContext.getContextUrl(relativeUrl,extra)?no_esc}<#else>${springMacroRequestContext.getContextUrl(relativeUrl)?no_esc}</#if></#macro>
61+
62+
<#--
63+
* bind
64+
*
65+
* Exposes a BindStatus object for the given bind path, which can be
66+
* a bean (e.g. "person") to get global errors, or a bean property
67+
* (e.g. "person.name") to get field errors. Can be called multiple times
68+
* within a form to bind to multiple command objects and/or field names.
69+
*
70+
* This macro will participate in the default HTML escape setting for the given
71+
* RequestContext. This can be customized by calling "setDefaultHtmlEscape"
72+
* on the "springMacroRequestContext" context variable, or via the
73+
* "defaultHtmlEscape" context-param in web.xml (same as for the JSP bind tag).
74+
* Also regards a "htmlEscape" variable in the namespace of this library.
75+
*
76+
* Producing no output, the following context variable will be available
77+
* each time this macro is referenced (assuming you import this library in
78+
* your templates with the namespace 'spring'):
79+
*
80+
* spring.status : a BindStatus instance holding the command object name,
81+
* expression, value, and error messages and codes for the path supplied
82+
*
83+
* @param path the path (string value) of the value required to bind to.
84+
* Spring defaults to a command name of "command" but this can be
85+
* overridden by user configuration.
86+
-->
87+
<#macro bind path>
88+
<#if htmlEscape?exists>
89+
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)>
90+
<#else>
91+
<#assign status = springMacroRequestContext.getBindStatus(path)>
92+
</#if>
93+
<#-- assign a temporary value, forcing a string representation for any
94+
kind of variable. This temp value is only used in this macro lib -->
95+
<#if status.value?exists && status.value?is_boolean>
96+
<#assign stringStatusValue=status.value?string>
97+
<#else>
98+
<#assign stringStatusValue=status.value?default("")>
99+
</#if>
100+
</#macro>
101+
102+
<#--
103+
* bindEscaped
104+
*
105+
* Similar to spring:bind, but takes an explicit HTML escape flag rather
106+
* than relying on the default HTML escape setting.
107+
-->
108+
<#macro bindEscaped path, htmlEscape>
109+
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)>
110+
<#-- assign a temporary value, forcing a string representation for any
111+
kind of variable. This temp value is only used in this macro lib -->
112+
<#if status.value?exists && status.value?is_boolean>
113+
<#assign stringStatusValue=status.value?string>
114+
<#else>
115+
<#assign stringStatusValue=status.value?default("")>
116+
</#if>
117+
</#macro>
118+
119+
<#--
120+
* formInput
121+
*
122+
* Display a form input field of type 'text' and bind it to an attribute
123+
* of a command or bean.
124+
*
125+
* @param path the name of the field to bind to
126+
* @param attributes any additional attributes for the element
127+
* (such as class or CSS styles or size)
128+
-->
129+
<#macro formInput path attributes="" fieldType="text">
130+
<@bind path/>
131+
<input type="${fieldType}" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" value="<#if fieldType!="password">${stringStatusValue}</#if>" ${attributes?no_esc}<@closeTag/>
132+
</#macro>
133+
134+
<#--
135+
* formPasswordInput
136+
*
137+
* Display a form input field of type 'password' and bind it to an attribute
138+
* of a command or bean. No value will ever be displayed. This functionality
139+
* can also be obtained by calling the formInput macro with a 'type' parameter
140+
* of 'password'.
141+
*
142+
* @param path the name of the field to bind to
143+
* @param attributes any additional attributes for the element
144+
* (such as class or CSS styles or size)
145+
-->
146+
<#macro formPasswordInput path attributes="">
147+
<@formInput path, attributes, "password"/>
148+
</#macro>
149+
150+
<#--
151+
* formHiddenInput
152+
*
153+
* Generate a form input field of type 'hidden' and bind it to an attribute
154+
* of a command or bean. This functionality can also be obtained by calling
155+
* the formInput macro with a 'type' parameter of 'hidden'.
156+
*
157+
* @param path the name of the field to bind to
158+
* @param attributes any additional attributes for the element
159+
* (such as class or CSS styles or size)
160+
-->
161+
<#macro formHiddenInput path attributes="">
162+
<@formInput path, attributes, "hidden"/>
163+
</#macro>
164+
165+
<#--
166+
* formTextarea
167+
*
168+
* Display a text area and bind it to an attribute of a command or bean.
169+
*
170+
* @param path the name of the field to bind to
171+
* @param attributes any additional attributes for the element
172+
* (such as class or CSS styles or size)
173+
-->
174+
<#macro formTextarea path attributes="">
175+
<@bind path/>
176+
<textarea id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}>
177+
${stringStatusValue}</textarea>
178+
</#macro>
179+
180+
<#--
181+
* formSingleSelect
182+
*
183+
* Show a selectbox (dropdown) input element allowing a single value to be chosen
184+
* from a list of options.
185+
*
186+
* @param path the name of the field to bind to
187+
* @param options a map (value=label) of all the available options
188+
* @param attributes any additional attributes for the element
189+
* (such as class or CSS styles or size)
190+
-->
191+
<#macro formSingleSelect path options attributes="">
192+
<@bind path/>
193+
<select id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}>
194+
<#if options?is_hash>
195+
<#list options?keys as value>
196+
<option value="${value}"<@checkSelected value/>>${options[value]}</option>
197+
</#list>
198+
<#else>
199+
<#list options as value>
200+
<option value="${value}"<@checkSelected value/>>${value}</option>
201+
</#list>
202+
</#if>
203+
</select>
204+
</#macro>
205+
206+
<#--
207+
* formMultiSelect
208+
*
209+
* Show a listbox of options allowing the user to make 0 or more choices from
210+
* the list of options.
211+
*
212+
* @param path the name of the field to bind to
213+
* @param options a map (value=label) of all the available options
214+
* @param attributes any additional attributes for the element
215+
* (such as class or CSS styles or size)
216+
-->
217+
<#macro formMultiSelect path options attributes="">
218+
<@bind path/>
219+
<select multiple="multiple" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}>
220+
<#list options?keys as value>
221+
<#assign isSelected = contains(status.actualValue?default([""]), value)>
222+
<option value="${value}"<#if isSelected> selected="selected"</#if>>${options[value]}</option>
223+
</#list>
224+
</select>
225+
</#macro>
226+
227+
<#--
228+
* formRadioButtons
229+
*
230+
* Show radio buttons.
231+
*
232+
* @param path the name of the field to bind to
233+
* @param options a map (value=label) of all the available options
234+
* @param separator the HTML tag or other character list that should be used to
235+
* separate each option (typically '&nbsp;' or '<br>')
236+
* @param attributes any additional attributes for the element
237+
* (such as class or CSS styles or size)
238+
-->
239+
<#macro formRadioButtons path options separator attributes="">
240+
<@bind path/>
241+
<#list options?keys as value>
242+
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}">
243+
<input type="radio" id="${id}" name="${status.expression}" value="${value}"<#if stringStatusValue == value> checked="checked"</#if> ${attributes?no_esc}<@closeTag/>
244+
<label for="${id}">${options[value]}</label>${separator?no_esc}
245+
</#list>
246+
</#macro>
247+
248+
<#--
249+
* formCheckboxes
250+
*
251+
* Show checkboxes.
252+
*
253+
* @param path the name of the field to bind to
254+
* @param options a map (value=label) of all the available options
255+
* @param separator the HTML tag or other character list that should be used to
256+
* separate each option (typically '&nbsp;' or '<br>')
257+
* @param attributes any additional attributes for the element
258+
* (such as class or CSS styles or size)
259+
-->
260+
<#macro formCheckboxes path options separator attributes="">
261+
<@bind path/>
262+
<#list options?keys as value>
263+
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}">
264+
<#assign isSelected = contains(status.actualValue?default([""]), value)>
265+
<input type="checkbox" id="${id}" name="${status.expression}" value="${value}"<#if isSelected> checked="checked"</#if> ${attributes?no_esc}<@closeTag/>
266+
<label for="${id}">${options[value]}</label>${separator?no_esc}
267+
</#list>
268+
<input type="hidden" name="_${status.expression}" value="on"/>
269+
</#macro>
270+
271+
<#--
272+
* formCheckbox
273+
*
274+
* Show a single checkbox.
275+
*
276+
* @param path the name of the field to bind to
277+
* @param attributes any additional attributes for the element
278+
* (such as class or CSS styles or size)
279+
-->
280+
<#macro formCheckbox path attributes="">
281+
<@bind path />
282+
<#assign id="${status.expression?replace('[','')?replace(']','')}">
283+
<#assign isSelected = status.value?? && status.value?string=="true">
284+
<input type="hidden" name="_${status.expression}" value="on"/>
285+
<input type="checkbox" id="${id}" name="${status.expression}"<#if isSelected> checked="checked"</#if> ${attributes?no_esc}/>
286+
</#macro>
287+
288+
<#--
289+
* showErrors
290+
*
291+
* Show validation errors for the currently bound field, with
292+
* optional style attributes.
293+
*
294+
* @param separator the HTML tag or other character list that should be used to
295+
* separate each option (typically '&nbsp;' or '<br>')
296+
* @param classOrStyle either the name of a CSS class element (which is defined in
297+
* the template or an external CSS file) or an inline style. If the value passed
298+
* in here contains a colon (:) then a 'style=' attribute will be used,
299+
* otherwise a 'class=' attribute will be used.
300+
-->
301+
<#macro showErrors separator classOrStyle="">
302+
<#list status.errorMessages as error>
303+
<#if classOrStyle == "">
304+
<b>${error}</b>
305+
<#else>
306+
<#if classOrStyle?index_of(":") == -1><#assign attr="class"><#else><#assign attr="style"></#if>
307+
<span ${attr}="${classOrStyle}">${error}</span>
308+
</#if>
309+
<#if error_has_next>${separator?no_esc}</#if>
310+
</#list>
311+
</#macro>
312+
313+
<#--
314+
* checkSelected
315+
*
316+
* Check a value in a list to see if it is the currently selected value.
317+
* If so, add the 'selected="selected"' text to the output.
318+
* Handles values of numeric and string types.
319+
* This function is used internally but can be accessed by user code if required.
320+
*
321+
* @param value the current value in a list iteration
322+
-->
323+
<#macro checkSelected value>
324+
<#if stringStatusValue?is_number && stringStatusValue == value?number>selected="selected"</#if>
325+
<#if stringStatusValue?is_string && stringStatusValue == value>selected="selected"</#if>
326+
</#macro>
327+
328+
<#--
329+
* contains
330+
*
331+
* Macro to return true if the list contains the scalar, false if not.
332+
* Surprisingly not a FreeMarker builtin.
333+
* This function is used internally but can be accessed by user code if required.
334+
*
335+
* @param list the list to search for the item
336+
* @param item the item to search for in the list
337+
* @return true if item is found in the list, false otherwise
338+
-->
339+
<#function contains list item>
340+
<#list list as nextInList>
341+
<#if nextInList == item><#return true></#if>
342+
</#list>
343+
<#return false>
344+
</#function>
345+
346+
<#--
347+
* closeTag
348+
*
349+
* Simple macro to close an HTML tag that has no body with '>' or '/>',
350+
* depending on the value of a 'xhtmlCompliant' variable in the namespace
351+
* of this library.
352+
-->
353+
<#macro closeTag>
354+
<#if xhtmlCompliant?exists && xhtmlCompliant>/><#else>></#if>
355+
</#macro>

0 commit comments

Comments
 (0)