Skip to content

Commit b830b19

Browse files
committed
Add FreeMarker macros
1 parent 1cfedb2 commit b830b19

File tree

2 files changed

+354
-2
lines changed
  • spring-webflux/src/main

2 files changed

+354
-2
lines changed

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

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

0 commit comments

Comments
 (0)