1 /* Copyright 2004-2005 the original author or authors.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT c;pWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
15 package org
.codehaus
.groovy
.grails
.plugins
.web
.taglib
17 import org
.springframework
.web
.servlet
.support
.RequestContextUtils as RCU
19 import java
.text
.DateFormat
20 import org
.codehaus
.groovy
.grails
.commons
.DomainClassArtefactHandler
21 import org
.springframework
.beans
.SimpleTypeConverter
24 * A tag lib that provides tags for working with form controls
26 * @author Graeme Rocher
31 def out
// to facilitate testing
34 * Creates a new text field
36 def textField
= {attrs
->
38 attrs
.tagName
= "textField"
39 def result
= field(attrs
)
46 * Creates a new password field
48 def passwordField
= {attrs
->
49 attrs
.type
= "password"
50 attrs
.tagName
= "passwordField"
51 def result
= field(attrs
)
58 * Creates a hidden field
60 def hiddenField
= {attrs
->
62 attrs
.tagName
= "hiddenField"
66 * Creates a submit button
68 def submitButton
= {attrs
->
70 attrs
.tagName
= "submitButton"
71 if (request
['flowExecutionKey']) {
72 attrs
.name
= attrs
.event ?
"_eventId_${attrs.event}" : "_eventId_${attrs.name}"
77 * A general tag for creating fields
80 resolveAttributes(attrs
)
81 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
82 out
<< "<input type=\"${attrs.remove('type')}\" "
83 outputAttributes(attrs
)
89 * A helper tag for creating checkboxes
91 def checkBox
= {attrs
->
92 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
93 def value
= attrs
.remove('value')
94 def name
= attrs
.remove('name')
95 def disabled
= attrs
.remove('disabled')
96 if (disabled
&& Boolean
.valueOf(disabled
)) {
97 attrs
.disabled
= 'disabled'
100 // Deal with the "checked" attribute. If it doesn't exist, we
101 // default to a value of "true", otherwise we use Groovy Truth
102 // to determine whether the HTML attribute should be displayed
105 if (attrs
.containsKey('checked')) {
106 checked
= attrs
.remove('checked')
109 if (checked
instanceof String
) checked
= Boolean
.valueOf(checked
)
111 if (value
== null) value
= false
113 // the hidden field name should begin with an underscore unless it is
114 // a dotted name, then the underscore should be inserted after the last
116 def lastDotInName
= name
.lastIndexOf('.')
117 def hiddenFieldName
= lastDotInName
== -1 ?
'_' + name
: name
[0..lastDotInName
] + '_' + name
[(lastDotInName
+1)..-1]
119 out
<< "<input type=\"hidden\" name=\"${hiddenFieldName}\" /><input type=\"checkbox\" name=\"${name}\" "
120 if (value
&& checked
) {
121 out
<< 'checked="checked" '
123 def outputValue
= !(value
instanceof Boolean
|| value?
.class == boolean.class)
125 out
<< "value=\"${value}\" "
126 // process remaining attributes
127 outputAttributes(attrs
)
129 // close the tag, with no body
134 * A general tag for creating textareas
136 def textArea
= {attrs
->
137 resolveAttributes(attrs
)
138 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
139 // Pull out the value to use as content not attrib
140 def value
= attrs
.remove('value')
141 def escapeHtml
= true
142 if (attrs
.escapeHtml
) escapeHtml
= Boolean
.valueOf(attrs
.remove('escapeHtml'))
145 outputAttributes(attrs
)
146 out
<< ">" << (escapeHtml ? value
.encodeAsHTML() : value
) << "</textarea>"
150 * Check required attributes, set the id to name if no id supplied, extract bean values etc.
152 void resolveAttributes(attrs
)
154 if (!attrs
.name
&& !attrs
.field
) {
155 throwTagError("Tag [${attrs.tagName}] is missing required attribute [name] or [field]")
157 attrs
.remove('tagName')
159 attrs
.id
= (!attrs
.id ? attrs
.name
: attrs
.id
)
161 def val
= attrs
.remove('bean')
163 if (attrs
.name
.indexOf('.'))
164 attrs
.name
.split('\\.').each
{val
= val?
."$it"}
170 attrs
.value
= (attrs
.value ? attrs
.value
: "")
174 * Dump out attributes in HTML compliant fashion
176 void outputAttributes(attrs
)
178 attrs
.remove('tagName') // Just in case one is left
180 out
<< k
<< "=\"" << v
.encodeAsHTML() << "\" "
185 * Same as <g:form>, except sets the relevant enctype for a file upload form
187 def uploadForm
= {attrs
, body
->
188 attrs
.enctype
= "multipart/form-data"
189 out
<< form(attrs
, body
)
193 * General linking to controllers, actions etc. Examples:
195 * <g:form action="myaction">...</gr:form>
196 * <g:form controller="myctrl" action="myaction">...</gr:form>
198 def form
= {attrs
, body
->
199 out
<< "<form action=\""
201 out
<< createLink(attrs
)
205 if (!attrs
['method']) {
206 out
<< 'method="post" '
208 // process remaining attributes
209 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
210 if (attrs
.id
== null) attrs
.remove('id')
212 outputAttributes(attrs
)
215 if (request
['flowExecutionKey']) {
217 out
<< hiddenField(name
: "_flowExecutionKey", value
: request
['flowExecutionKey'])
220 def bodyContent
= body()
227 * Creates a submit button that submits to an action in the controller specified by the form action
228 * The name of the action attribute is translated into the action name, for example "Edit" becomes
229 * "_action_edit" or "List People" becomes "_action_listPeople"
230 * If the action attribute is not specified, the value attribute will be used as part of the action name
232 * <g:actionSubmit value="Edit" />
233 * <g:actionSubmit action="Edit" value="Some label for editing" />
236 def actionSubmit
= {attrs
->
237 attrs
.tagName
= "actionSubmit"
239 throwTagError("Tag [$attrs.tagName] is missing required attribute [value]")
242 // add action and value
243 def value
= attrs
.remove('value')
244 def action
= attrs
.action ? attrs
.remove('action') : value
246 out
<< "<input type=\"submit\" name=\"_action_${action}\" value=\"${value}\" "
248 // process remaining attributes
249 outputAttributes(attrs
)
256 * Creates a an image submit button that submits to an action in the controller specified by the form action
257 * The name of the action attribute is translated into the action name, for example "Edit" becomes
258 * "_action_edit" or "List People" becomes "_action_listPeople"
259 * If the action attribute is not specified, the value attribute will be used as part of the action name
261 * <g:actionSubmitImage src="/images/submitButton.gif" action="Edit" />
264 def actionSubmitImage
= {attrs
->
265 attrs
.tagName
= "actionSubmitImage"
268 throwTagError("Tag [$attrs.tagName] is missing required attribute [value]")
271 // add action and value
272 def value
= attrs
.remove('value')
273 def action
= attrs
.action ? attrs
.remove('action') : value
275 out
<< "<input type=\"image\" name=\"_action_${action}\" value=\"${value}\" "
278 def src
= attrs
.remove('src')
280 out
<< "src=\"${src}\" "
283 // process remaining attributes
284 outputAttributes(attrs
)
292 * A simple date picker that renders a date as selects
293 * eg. <g:datePicker name="myDate" value="${new Date()}" />
295 def datePicker
= {attrs
->
296 def xdefault
= attrs
['default']
297 if (xdefault
== null) {
298 xdefault
= new Date()
299 } else if (xdefault
.toString() != 'none') {
300 if (xdefault
instanceof String
) {
301 xdefault
= DateFormat
.getInstance().parse(xdefault
)
302 }else if(!(xdefault
instanceof Date
)){
303 throwTagError("Tag [datePicker] requires the default date to be a parseable String or a Date")
309 def value
= attrs
['value']
310 if (value
.toString() == 'none') {
315 def name
= attrs
['name']
316 def id
= attrs
['id'] ? attrs
['id'] : name
318 def noSelection
= attrs
['noSelection']
319 if (noSelection
!= null)
321 noSelection
= noSelection
.entrySet().iterator().next()
324 def years
= attrs
['years']
326 final PRECISION_RANKINGS
= ["year": 0, "month": 10, "day": 20, "hour": 30, "minute": 40]
327 def precision
= (attrs
['precision'] ? PRECISION_RANKINGS
[attrs
['precision']] : PRECISION_RANKINGS
["minute"])
334 def dfs
= new java
.text
.DateFormatSymbols(RCU
.getLocale(request
))
337 if (value
instanceof Calendar
) {
340 else if (value
!= null) {
341 c
= new GregorianCalendar();
346 day
= c
.get(GregorianCalendar
.DAY_OF_MONTH
)
347 month
= c
.get(GregorianCalendar
.MONTH
)
348 year
= c
.get(GregorianCalendar
.YEAR
)
349 hour
= c
.get(GregorianCalendar
.HOUR_OF_DAY
)
350 minute
= c
.get(GregorianCalendar
.MINUTE
)
356 // If no year, we need to get current year to setup a default range... ugly
357 def tempc
= new GregorianCalendar()
358 tempc
.setTime(new Date())
359 tempyear
= tempc
.get(GregorianCalendar
.YEAR
)
363 years
= (tempyear
- 100)..(tempyear
+ 100)
366 out
<< "<input type=\"hidden\" name=\"${name}\" value=\"struct\" />"
369 if (precision
>= PRECISION_RANKINGS
["day"]) {
370 out
.println
"<select name=\"${name}_day\" id=\"${id}_day\">"
373 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, '')
378 out
.println
"<option value=\"${i}\""
380 out
.println
" selected=\"selected\""
382 out
.println
">${i}</option>"
384 out
.println
'</select>'
387 // create month select
388 if (precision
>= PRECISION_RANKINGS
["month"]) {
389 out
.println
"<select name=\"${name}_month\" id=\"${id}_month\">"
392 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, '')
396 dfs
.months
.eachWithIndex
{m
, i
->
398 def monthIndex
= i
+ 1
399 out
<< "<option value=\"${monthIndex}\""
400 if (month
== i
) out
<< " selected=\"selected\""
403 out
.println
'</option>'
406 out
.println
'</select>'
409 // create year select
410 if (precision
>= PRECISION_RANKINGS
["year"]) {
411 out
.println
"<select name=\"${name}_year\" id=\"${id}_year\">"
414 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, '')
419 out
.println
"<option value=\"${i}\""
421 out
.println
" selected=\"selected\""
423 out
.println
">${i}</option>"
425 out
.println
'</select>'
429 if (precision
>= PRECISION_RANKINGS
["hour"]) {
430 out
.println
"<select name=\"${name}_hour\" id=\"${id}_hour\">"
433 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, '')
439 if (i
< 10) h
= '0' + h
440 out
<< "<option value=\"${h}\" "
441 if (hour
== h
.toInteger()) out
<< "selected=\"selected\""
442 out
<< '>' << h
<< '</option>'
445 out
.println
'</select> :'
447 // If we're rendering the hour, but not the minutes, then display the minutes as 00 in read-only format
448 if (precision
< PRECISION_RANKINGS
["minute"]) {
454 if (precision
>= PRECISION_RANKINGS
["minute"]) {
455 out
.println
"<select name=\"${name}_minute\" id=\"${id}_minute\">"
458 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, '')
464 if (i
< 10) m
= '0' + m
465 out
<< "<option value=\"${m}\" "
466 if (minute
== m
.toInteger()) out
<< "selected=\"selected\""
467 out
<< '>' << m
<< '</option>'
470 out
.println
'</select>'
474 def renderNoSelectionOption
= {noSelectionKey
, noSelectionValue
, value
->
475 // If a label for the '--Please choose--' first item is supplied, write it out
476 out
<< '<option value="' << (noSelectionKey
== null ?
"" : noSelectionKey
) << '"'
477 if (noSelectionKey
.equals(value
)) {
478 out
<< ' selected="selected" '
480 out
<< '>' << noSelectionValue
.encodeAsHTML() << '</option>'
484 * A helper tag for creating TimeZone selects
485 * eg. <g:timeZoneSelect name="myTimeZone" value="${tz}" />
487 def timeZoneSelect
= {attrs
->
488 attrs
['from'] = TimeZone
.getAvailableIDs();
489 attrs
['value'] = (attrs
['value'] ? attrs
['value'].ID
: TimeZone
.getDefault().ID
)
490 def date
= new Date()
492 // set the option value as a closure that formats the TimeZone for display
493 attrs
['optionValue'] = {
494 TimeZone tz
= TimeZone
.getTimeZone(it
);
495 def shortName
= tz
.getDisplayName(tz
.inDaylightTime(date
), TimeZone
.SHORT
);
496 def longName
= tz
.getDisplayName(tz
.inDaylightTime(date
), TimeZone
.LONG
);
498 def offset
= tz
.rawOffset
;
499 def hour
= offset
/ (60 * 60 * 1000);
500 def min
= Math
.abs(offset
/ (60 * 1000)) % 60;
502 return "${shortName}, ${longName} ${hour}:${min}"
505 // use generic select
510 * A helper tag for creating locale selects
512 * eg. <g:localeSelect name="myLocale" value="${locale}" />
514 def localeSelect
= {attrs
->
515 attrs
['from'] = Locale
.getAvailableLocales()
516 attrs
['value'] = (attrs
['value'] ? attrs
['value'] : RCU
.getLocale(request
))
517 // set the key as a closure that formats the locale
518 attrs
['optionKey'] = {"${it.language}_${it.country}"}
519 // set the option value as a closure that formats the locale for display
520 attrs
['optionValue'] = {"${it.language}, ${it.country}, ${it.displayName}"}
522 // use generic select
527 * A helper tag for creating currency selects
529 * eg. <g:currencySelect name="myCurrency" value="${currency}" />
531 def currencySelect
= {attrs
, body
->
532 if (!attrs
['from']) {
533 attrs
['from'] = ['EUR', 'XCD', 'USD', 'XOF', 'NOK', 'AUD', 'XAF', 'NZD', 'MAD', 'DKK', 'GBP', 'CHF', 'XPF', 'ILS', 'ROL', 'TRL']
536 def currency
= (attrs
['value'] ? attrs
['value'] : Currency
.getInstance(RCU
.getLocale(request
)))
537 attrs
.value
= currency
.currencyCode
539 catch (IllegalArgumentException iae
) {
542 // invoke generic select
547 * A helper tag for creating HTML selects
550 * <g:select name="user.age" from="${18..65}" value="${age}" />
551 * <g:select name="user.company.id" from="${Company.list()}" value="${user?.company.id}" optionKey="id" />
553 def select
= {attrs
->
554 def messageSource
= grailsAttributes
.getApplicationContext().getBean("messageSource")
555 def locale
= RCU
.getLocale(request
)
557 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
558 def from
= attrs
.remove('from')
559 def keys
= attrs
.remove('keys')
560 def optionKey
= attrs
.remove('optionKey')
561 def optionValue
= attrs
.remove('optionValue')
562 def value
= attrs
.remove('value')
563 if (value
instanceof Collection
&& attrs
.multiple
== null) {
564 attrs
.multiple
= 'multiple'
566 def valueMessagePrefix
= attrs
.remove('valueMessagePrefix')
567 def noSelection
= attrs
.remove('noSelection')
568 if (noSelection
!= null) {
569 noSelection
= noSelection
.entrySet().iterator().next()
571 def disabled
= attrs
.remove('disabled')
572 if (disabled
&& Boolean
.valueOf(disabled
)) {
573 attrs
.disabled
= 'disabled'
576 writer
<< "<select name=\"${attrs.remove('name')}\" "
577 // process remaining attributes
578 outputAttributes(attrs
)
584 renderNoSelectionOption(noSelection
.key
, noSelection
.value
, value
)
588 // create options from list
590 from
.eachWithIndex
{el
, i
->
595 writeValueAndCheckIfSelected(keyValue
, value
, writer
)
597 else if (optionKey
) {
598 if (optionKey
instanceof Closure
) {
599 keyValue
= optionKey(el
)
601 else if (el
!= null && optionKey
== 'id' && grailsApplication
.getArtefact(DomainClassArtefactHandler
.TYPE
, el
.getClass().name
)) {
602 keyValue
= el
.ident()
605 keyValue
= el
[optionKey
]
607 writeValueAndCheckIfSelected(keyValue
, value
, writer
)
611 writeValueAndCheckIfSelected(keyValue
, value
, writer
)
615 if (optionValue
instanceof Closure
) {
616 writer
<< optionValue(el
).toString().encodeAsHTML()
619 writer
<< el
[optionValue
].toString().encodeAsHTML()
622 else if (valueMessagePrefix
) {
623 def message
= messageSource
.getMessage("${valueMessagePrefix}.${keyValue}", null, null, locale
)
624 if (message
!= null) {
625 writer
<< message
.encodeAsHTML()
628 writer
<< keyValue
.encodeAsHTML()
631 def s
= el
.toString()
632 if (s
) writer
<< s
.encodeAsHTML()
636 def s
= el
.toString()
637 if (s
) writer
<< s
.encodeAsHTML()
639 writer
<< '</option>'
644 writer
<< '</select>'
647 def typeConverter
= new SimpleTypeConverter()
648 private writeValueAndCheckIfSelected(keyValue
, value
, writer
) {
650 boolean selected
= false
651 def keyClass
= keyValue?
.getClass()
652 if (keyClass
.isInstance(value
)) {
653 selected
= (keyValue
== value
)
655 else if (value
instanceof Collection
) {
656 selected
= value
.contains(keyValue
)
658 else if (keyClass
&& value
) {
660 value
= typeConverter
.convertIfNecessary(value
, keyClass
)
661 selected
= (keyValue
== value
)
662 } catch (Exception
) {
666 writer
<< "value=\"${keyValue}\" "
668 writer
<< 'selected="selected" '
673 * A helper tag for creating radio buttons
675 def radio
= {attrs
->
676 def value
= attrs
.remove('value')
677 attrs
.id
= attrs
.id ? attrs
.id
: attrs
.name
678 def name
= attrs
.remove('name')
679 def disabled
= attrs
.remove('disabled')
680 if (disabled
&& Boolean
.valueOf(disabled
)) {
681 attrs
.disabled
= 'disabled'
683 def checked
= (attrs
.remove('checked') ?
true : false)
684 out
<< '<input type="radio" '
685 out
<< "name=\"${name}\" "
687 out
<< 'checked="checked" '
689 out
<< "value=\"${value.toString().encodeAsHTML()}\" "
690 // process remaining attributes
691 outputAttributes(attrs
)
693 // close the tag, with no body
698 * A helper tag for creating radio button groups
700 def radioGroup
= {attrs
, body
->
701 def value
= attrs
.remove('value')
702 def values
= attrs
.remove('values')
703 def labels
= attrs
.remove('labels')
704 def name
= attrs
.remove('name')
705 values
.eachWithIndex
{val
, idx
->
706 def it
= new Expando();
707 it
.radio
= "<input type=\"radio\" name=\"${name}\" "
708 if (value?
.toString().equals(val
.toString())) {
709 it
.radio
+= 'checked '
711 it
.radio
+= "value=\"${val.toString().encodeAsHTML()}\" />"
713 it
.label
= labels
== null ?
'Radio ' + val
: labels
[idx
]