GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / groovy / org / codehaus / groovy / grails / plugins / web / taglib / FormTagLib.groovy
blob5d74dd5bef82b7099659bb630707dcd490dd944f
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
23 /**
24 * A tag lib that provides tags for working with form controls
26 * @author Graeme Rocher
27 * @since 17-Jan-2006
30 class FormTagLib {
31 def out // to facilitate testing
33 /**
34 * Creates a new text field
36 def textField = {attrs ->
37 attrs.type = "text"
38 attrs.tagName = "textField"
39 def result = field(attrs)
40 if (result) {
41 out << result
45 /**
46 * Creates a new password field
48 def passwordField = {attrs ->
49 attrs.type = "password"
50 attrs.tagName = "passwordField"
51 def result = field(attrs)
52 if (result) {
53 out << result
57 /**
58 * Creates a hidden field
60 def hiddenField = {attrs ->
61 attrs.type = "hidden"
62 attrs.tagName = "hiddenField"
63 out << field(attrs)
65 /**
66 * Creates a submit button
68 def submitButton = {attrs ->
69 attrs.type = "submit"
70 attrs.tagName = "submitButton"
71 if (request['flowExecutionKey']) {
72 attrs.name = attrs.event ? "_eventId_${attrs.event}" : "_eventId_${attrs.name}"
74 out << field(attrs)
76 /**
77 * A general tag for creating fields
79 def field = {attrs ->
80 resolveAttributes(attrs)
81 attrs.id = attrs.id ? attrs.id : attrs.name
82 out << "<input type=\"${attrs.remove('type')}\" "
83 outputAttributes(attrs)
84 out << "/>"
88 /**
89 * A helper tag for creating checkboxes
90 **/
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
103 // or not.
104 def checked = true
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
115 // dot
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)
124 if (outputValue)
125 out << "value=\"${value}\" "
126 // process remaining attributes
127 outputAttributes(attrs)
129 // close the tag, with no body
130 out << ' />'
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'))
144 out << "<textarea "
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')
162 if (val) {
163 if (attrs.name.indexOf('.'))
164 attrs.name.split('\\.').each {val = val?."$it"}
165 else {
166 val = val[name]
168 attrs.value = val
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
179 attrs.each {k, v ->
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=\""
200 // create the link
201 out << createLink(attrs)
203 out << '\" '
204 // default to post
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)
214 out << ">"
215 if (request['flowExecutionKey']) {
216 out.println()
217 out << hiddenField(name: "_flowExecutionKey", value: request['flowExecutionKey'])
219 // output the body
220 def bodyContent = body()
221 out << bodyContent
223 // close tag
224 out << "</form>"
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"
238 if (!attrs.value) {
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)
251 // close tag
252 out << '/>'
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"
267 if (!attrs.value) {
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}\" "
277 // add image src
278 def src = attrs.remove('src')
279 if (src) {
280 out << "src=\"${src}\" "
283 // process remaining attributes
284 outputAttributes(attrs)
286 // close tag
287 out << '/>'
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")
305 } else {
306 xdefault = null
309 def value = attrs['value']
310 if (value.toString() == 'none') {
311 value = null
312 } else if (!value) {
313 value = xdefault
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"])
329 def day
330 def month
331 def year
332 def hour
333 def minute
334 def dfs = new java.text.DateFormatSymbols(RCU.getLocale(request))
336 def c = null
337 if (value instanceof Calendar) {
338 c = value
340 else if (value != null) {
341 c = new GregorianCalendar();
342 c.setTime(value)
345 if (c != null) {
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)
353 if (years == null) {
354 def tempyear
355 if (year == null) {
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)
360 } else {
361 tempyear = year
363 years = (tempyear - 100)..(tempyear + 100)
366 out << "<input type=\"hidden\" name=\"${name}\" value=\"struct\" />"
368 // create day select
369 if (precision >= PRECISION_RANKINGS["day"]) {
370 out.println "<select name=\"${name}_day\" id=\"${id}_day\">"
372 if (noSelection) {
373 renderNoSelectionOption(noSelection.key, noSelection.value, '')
374 out.println()
377 for (i in 1..31) {
378 out.println "<option value=\"${i}\""
379 if (i == day) {
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\">"
391 if (noSelection) {
392 renderNoSelectionOption(noSelection.key, noSelection.value, '')
393 out.println()
396 dfs.months.eachWithIndex {m, i ->
397 if (m) {
398 def monthIndex = i + 1
399 out << "<option value=\"${monthIndex}\""
400 if (month == i) out << " selected=\"selected\""
401 out << '>'
402 out << m
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\">"
413 if (noSelection) {
414 renderNoSelectionOption(noSelection.key, noSelection.value, '')
415 out.println()
418 for (i in years) {
419 out.println "<option value=\"${i}\""
420 if (i == year) {
421 out.println " selected=\"selected\""
423 out.println ">${i}</option>"
425 out.println '</select>'
428 // do hour select
429 if (precision >= PRECISION_RANKINGS["hour"]) {
430 out.println "<select name=\"${name}_hour\" id=\"${id}_hour\">"
432 if (noSelection) {
433 renderNoSelectionOption(noSelection.key, noSelection.value, '')
434 out.println()
437 for (i in 0..23) {
438 def h = '' + i
439 if (i < 10) h = '0' + h
440 out << "<option value=\"${h}\" "
441 if (hour == h.toInteger()) out << "selected=\"selected\""
442 out << '>' << h << '</option>'
443 out.println()
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"]) {
449 out.println '00'
453 // do minute select
454 if (precision >= PRECISION_RANKINGS["minute"]) {
455 out.println "<select name=\"${name}_minute\" id=\"${id}_minute\">"
457 if (noSelection) {
458 renderNoSelectionOption(noSelection.key, noSelection.value, '')
459 out.println()
462 for (i in 0..59) {
463 def m = '' + i
464 if (i < 10) m = '0' + m
465 out << "<option value=\"${m}\" "
466 if (minute == m.toInteger()) out << "selected=\"selected\""
467 out << '>' << m << '</option>'
468 out.println()
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
506 out << select(attrs)
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
523 out << select(attrs)
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']
535 try {
536 def currency = (attrs['value'] ? attrs['value'] : Currency.getInstance(RCU.getLocale(request)))
537 attrs.value = currency.currencyCode
539 catch (IllegalArgumentException iae) {
540 attrs.value = null
542 // invoke generic select
543 out << select(attrs)
547 * A helper tag for creating HTML selects
549 * Examples:
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)
556 def writer = out
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)
580 writer << '>'
581 writer.println()
583 if (noSelection) {
584 renderNoSelectionOption(noSelection.key, noSelection.value, value)
585 writer.println()
588 // create options from list
589 if (from) {
590 from.eachWithIndex {el, i ->
591 def keyValue = null
592 writer << '<option '
593 if (keys) {
594 keyValue = keys[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()
604 else {
605 keyValue = el[optionKey]
607 writeValueAndCheckIfSelected(keyValue, value, writer)
609 else {
610 keyValue = el
611 writeValueAndCheckIfSelected(keyValue, value, writer)
613 writer << '>'
614 if (optionValue) {
615 if (optionValue instanceof Closure) {
616 writer << optionValue(el).toString().encodeAsHTML()
618 else {
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()
627 else if (keyValue) {
628 writer << keyValue.encodeAsHTML()
630 else {
631 def s = el.toString()
632 if (s) writer << s.encodeAsHTML()
635 else {
636 def s = el.toString()
637 if (s) writer << s.encodeAsHTML()
639 writer << '</option>'
640 writer.println()
643 // close tag
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) {
659 try {
660 value = typeConverter.convertIfNecessary(value, keyClass)
661 selected = (keyValue == value)
662 } catch (Exception) {
663 // ignore
666 writer << "value=\"${keyValue}\" "
667 if (selected) {
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}\" "
686 if (checked) {
687 out << 'checked="checked" '
689 out << "value=\"${value.toString().encodeAsHTML()}\" "
690 // process remaining attributes
691 outputAttributes(attrs)
693 // close the tag, with no body
694 out << ' />'
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]
715 out << body(it)
716 out.println()