Last updated by
4 years ago
Page: Contribute a Tag, Version:5
Contribute a Tag!
This page contains user submissions of custom tags that may or may not be included in the Grails core.For general information on using and creating custom tags see http://grails.org/Dynamic+Tag+Libraries.Add your tag below and please update this list, too:- #dateFormat
- #esc
- #format (Format date or number values using formats defined in the message bundle)
- #jsdatePicker
- jQuery tags
- textField enhanced
- Yahoo Calendar
- Javascript escape
- BeanField tagLib
- Auth taglib to help with ACEGI security
- image
- DOM id
- Enable or Disable Checkbox
- #ModelTagLib (easy rendering of bean properties with labels, errors, required indicators etc)
- #I18NTagLib (country selection, date display)
- renderRandom Tag
- XSL transformation
- ScaffoldTags Plugin
- radio group
- multiple select tag
- localeSelecter
- Sorting each
- State Drop Down
- Check Box List
- Label List
- last.fm Recent List
- ICQ status
- Remote paginate
Tags
dateFormat Tag
Description
Allows formatting of data objects.Example
<g:dateFormat value="${new Date()}" format="dd-MM-yyyy" />The Code!
def dateFormat = { attrs ->
out << new java.text.SimpleDateFormat(attrs.format)
.format(attrs.value)
}esc Tag
Description
Escapes HTML entities within its body to ensure no cross-site scripting (XSS) attacks can work.Example
<g:esc>${someobj.name}</g:esc>The Code!
private static final String AMP = "&" private static final String LT = "<" private static final String GT = ">" private static final String QUOTE = """ /** * Escape HTML entities within the body. */ def esc = { attrs, body -> def text = '' if(body instanceof Closure) { text = TagLibUtil.outToString(body, attrs) } else if(body instanceof String) { text = body } else if(attrs instanceof String) { text = attrs } out << escapeEntities(text); } /** * Return the given string with all HTML entities escaped into their * HTML equivalent. * * @param text String containing unsafe characters. * @return <var>text</var> with characters turned into HTML entities. */ public static String escapeEntities(String text) { if (text == null) text = "" String trim = text.trim() char[] c = trim.toCharArray() StringBuffer buffer = new StringBuffer() def i = -1; while (++i < c.length) { if (c[i]=='&') buffer.append(AMP) else if (c[i]=='<') buffer.append(LT) else if(c[i]=='>') buffer.append(GT) else if(c[i]=='"') buffer.append(QUOTE) else buffer.append(c[i]) } return buffer.toString() }
format
Description
The core formatDate tag now allows formats specified in the message bundle (since 1.0-RC2).Extend FormatTag to format dates and numbers using format specifications from the message bundle.
Example
test.gsp<g:format value="${new Date()}" formatName="format.date"/> <g:format value="${new Date()}" formatName="format.time"/> <g:format value="${new Integer(100)}" formatName="format.currency"/>
format.date=MM/dd/yyyy format.time=HH:mm:ss format.currency=$#,##0.00
The Code!
import org.springframework.web.servlet.support.RequestContextUtils as RCU; import org.codehaus.groovy.grails.plugins.web.taglib.FormatTagLib;class ExtendedFormatTagLib extends FormatTagLib { def format = { attrs -> def value = attrs.get('value') def formatName = attrs.get('formatName') if (formatName) { def locale = RCU.getLocale(request) def messageSource = grailsAttributes.getApplicationContext().getBean('messageSource') attrs.format = messageSource.getMessage(formatName, null, null, locale) } if (value instanceof Date) { formatDate.call(attrs + [date:value]) } else if (value instanceof Number) { formatNumber.call(attrs + [number:value]) } else { out << value } } }
jsdatePicker (DHTML JavaScript Date Picker) Tag
Description
Grails Calendar (Date Picker) TagLib using , dynarch.com's The DHTML / JavaScript Calendar. ~Sorry, it's not using YahooUI~
The following preparation is needed before using.
- Download jscalendar-1.0.zip from http://www.dynarch.com/projects/calendar/
- Extract the archive into {your-grails-app}/web-app/js/
- rename directory name jscalendar-1.0 to calendar.
Ex: {your-grails-app}/web-app/js/calendar/
Example
Set to <header> area.<g:jscalender></g:jscalender>
<g:jsdatePicker name="myDate" value="${new Date()}" />The Code!
import org.springframework.web.servlet.support.RequestContextUtils as RCU; import java.text.SimpleDateFormat; /** * A date picker by jscalendar-1.0 * * eg. <g:jsdatePicker name="myDate" value="${new Date()}" /> */ def jsdatePicker = { attrs -> def value = (attrs['value'] ? attrs['value'] : new Date()) def name = attrs['name'] //def locale = RCU.getLocale(request) def c = null if(value instanceof Calendar) { c = value } else { c = new GregorianCalendar(); c.setTime(value) } def day = c.get(GregorianCalendar.DAY_OF_MONTH) def month = c.get(GregorianCalendar.MONTH)+1 def year = c.get(GregorianCalendar.YEAR) def hour = c.get(GregorianCalendar.HOUR_OF_DAY) def minute = c.get(GregorianCalendar.MINUTE) // def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource") def button_label = "Select..";//messageSource.getMessage("calendar.select",null,locale) out << """ <input type='hidden' name='${name}' value='struct' /> <input type='text' id='${name}_year' name='${name}_year' value='${year}' style='width:35px;text-align:center;'> /<input type='text' id='${name}_month' name='${name}_month' value='${month}' style='width:20px;text-align:center;'> /<input type='text' id='${name}_day' name='${name}_day' value='${day}' style='width:20px;text-align:center;'> <input type='text' id='${name}_hour' name='${name}_hour' value='${hour}' style='width:20px;text-align:center;'> : <input type='text' id='${name}_minute' name='${name}_minute' value='${minute}' style='width:20px;text-align:center;'> <input type='button' value='${button_label}' id='${name}_trigger'> <script language='javascript'> Calendar.setup( {inputField:'${name}', button:'${name}_trigger', onSelect:${name}_dateChanged ,showsTime:true} ); function ${name}_dateChanged(calendar) { document.getElementById('${name}_year').value=calendar.date.getFullYear(); document.getElementById('${name}_month').value=calendar.date.getMonth()+1; document.getElementById('${name}_day').value=calendar.date.getDate(); document.getElementById('${name}_hour').value=calendar.date.getHours(); document.getElementById('${name}_minute').value=calendar.date.getMinutes(); } </script> """ } /** * Set this tag inside <head> tag when using js datepicker * <g:jscalender></g:jscalender> */ def jscalender = { attrs -> def applicationUri = grailsAttributes.getApplicationUri(request) out << """ <link rel="stylesheet" type="text/css" media="all" href="${applicationUri}/js/calendar/skins/aqua/theme.css" title="Aqua" /> <script type='text/javascript' src='${applicationUri}/js/calendar/calendar.js'></script> <script type='text/javascript' src='${applicationUri}/js/calendar/calendar-setup.js'></script> <script type='text/javascript' src='${applicationUri}/js/calendar/lang/calendar-en.js'></script> """ }
Using with another languages(locales)
dynarch.com's The DHTML / JavaScript Calendar can change language set by changing "en" of "/calendar/lang/calendar-en.js" to another if your language supported. Ex. "/calendar/lang/calendar-de.js"It is possible to change automatically using the locale,Here's sample:- Make sure that locale you want use is supported.
Check {your-grails-app}/web-app/js/calendar/lang/ directory.
- replace part of the jscalender closure like below
def jscalender = { attrs ->
def locale = RCU.getLocale(request)
def applicationUri = grailsAttributes.getApplicationUri(request)
out << """
<link rel="stylesheet" type="text/css" media="all"
href="${applicationUri}/js/calendar/skins/aqua/theme.css" title="Aqua" />
<script type='text/javascript' src='${applicationUri}/js/calendar/calendar.js'></script>
<script type='text/javascript' src='${applicationUri}/js/calendar/calendar-setup.js'></script>
<script type='text/javascript' src='${applicationUri}/js/calendar/lang/calendar-${locale}.js'></script>
"""
}jQuery Tags
Description
This set of tags allows you to include the JQuery JavaScript libary and register a "toggle" effect on an arbitrary HTML element referenced via id. Toggle means that the element you reference is magically disappearing and then shows up again if you hit the toggle link again. You first need to download jQuery which is only 15kB in size. Place the jquery.js file in a new web-app/js/jquery directory. To use the tags, you first need to reference the jquery.js from your HTML header, as shown below:Example
<script type="text/javascript" src="<g:createLinkTo dir="js/jquery" file="jquery.js" />"></script><g:jquery> <g:toggleelement elementId="notecontent" linkId="showhidelink" event="click" speed="slow"/> </g:jquery>
How it might look
Below are two example screenshots out of my app. The orange box is a div, referenced by id and styled via CSS. Note that the link is in a separate div box, resulting in the link beeing visible all the time.
Once you click the link, the box slides down and opens.
To have the boxed immediately closed after the page loaded, add this after the toggleelement tag of the example above: $("#notecontent").hide("slow");
toggleelement description
- elementId - references the id of the element that you want to toggle, e.g. disappear and appear
- linkId - the link that will trigger the action, e.g. a <a href="http://docs.codehaus.org/pages/editpage.action#" id="showhidelink">link</a> ...
- event - the event that is listened to: click means you have to click the link, can also be: dblclick, mouseover, etc. see the visual jQuery guide for a full list (go to events/mouse)
- speed - can be either slow, normal or fast
The Code!
import org.springframework.validation.Errors; import org.springframework.context.NoSuchMessageException; import org.springframework.web.servlet.support.RequestContextUtils as RCU; import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU;class JQueryTagLib { def jquery = { attrs, body -> out << '<script type="text/javascript"> $(document).ready(function(){' body() out << '}); </script>' } def toggleelement = { attrs -> out << /$("#${attrs['linkId']}").${attrs['event']}(function(){ $("#${attrs['elementId']}").toggle("${attrs['speed']}"); return false; });/ }}
textField enhanced
Description
Creates an input of type "text" specifically tailored to display and update a property on an object.Parameters
object (required) - the object the property of which is accessed field(required) - the name of the property to access id(optional) - the id of the resulting input element, defaults to the name of the propertyExample
<g:textField object="${book}" field="author.lastName" />
<input type="text" id="author.lastName" name="author.lastName" value="Auster" />
Code
import org.springframework.web.util.HtmlUtils as HU; def textField = { attrs -> attrs.type = "text" attrs.name = attrs.remove('field') attrs.id = (attrs.id == null) ? attrs.name : attrs.id def val = attrs.remove('object') attrs.name.split('\\.').each { val = val[it] } attrs.value = (val == null) ? "" : HU.htmlEscape(val.toString()) field(attrs) }
Yahoo Calendar
Description
3 tags that allow the Yahoo calendar control to be used to enter date values in a form. Note that this calendar tag is still a little rough, I placed it here because someone on the forum needed one and I figured that it was better than starting from scratch :-)Parameters
- yuiCalendarUtils
- No Parameters
- yuiCalendarHeader
- name - the name of the calendar instance (mandatory)
- value - the date value to set the calendar to (optional)
- yuiCalendarBody
- name - the name of the calendar instance
- bean - the bean containing the date parameter to set the display and hidden fields to
Dependencies
The calendar tags need the following resources:- An image file called office-calendar.png in the images directory
- date.js from http://www.javascripttoolbox.com/lib/date/ copied to web-app/js/toolbox/date.js
Example
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Create Action</title> <!-- YUI files -->
<script type="text/javascript" src="${createLinkTo(dir:'js/yahoo',file:'calendar-min.js')}"></script>
<!-- Calendar Utils -->
<script type="text/javascript" src="/appname/js/toolbox/date.js"/> <link rel="stylesheet" type="text/css" href="${createLinkTo(dir:'js/yahoo/assets',file:'calendar.css')}"/>
<g:yuiCalendarUtils/>
<g:yuiCalendarHeader name="dueDate" value="${action.dueDate}"/>
</head>
<body>
<div id="yui-main">
<div class="yui-b">
<g:form action="save" method="post" >
<div class="data_entry">
<label for='dueDateValue'>Due Date:</label>
<g:yuiCalendarBody name="dueDate" bean="${action}"/>
</div> <div class="buttons">
<span class="formButton">
<input type="submit" value="Create"></input>
</span>
</div>
</g:form>
</div>
</div>
<div class="yui-b">
This text is in the sidebar
</div>
</body>
</html>Code
import java.text.SimpleDateFormat/** * A tag lib that provides tags for working with Yahoo calendar controls inside forms * * @author Peter Kelley * @since 15-September-2006 */ class YahooCalendarTagLib { /** * Tag to output the correct javascript utility functions to support the calendar tags. * This tag takes no parameters. This tag should be placed in the page header once if * the yuiCalendarHeader and yuiCalendarBody tags are to be used. Only a single instance * of this tag is ever required. */ def yuiCalendarUtils = {attrs, body -> out << "<script language='javascript'>n" out << "YAHOO.namespace('example.calendar');n" out << "function showYUICalendar(calendarName, calendarInstance) {n" out << " var linkName = calendarName + 'Link';n" out << " var link = document.getElementById(linkName);n" out << " var pos = YAHOO.util.Dom.getXY(link);n" out << " calendarInstance.oDomContainer.style.display='block';n" out << " YAHOO.util.Dom.setXY(calendarInstance.oDomContainer, [pos[0]+link.offsetWidth+5,pos[1]] );n" out << "}n" out << "function setCalendarDateValue(calendarName, calendarInstance, displayFieldName) {n" out << " var selected = calendarInstance.getSelectedDates()[0];n" out << " calendarInstance.oDomContainer.style.display='none';n" out << " var submitField = document.getElementById(calendarName + '_day');n" out << " submitField.value = selected.format('dd');n" out << " submitField = document.getElementById(calendarName + '_month');n" out << " submitField.value = selected.format('MM');n" out << " submitField = document.getElementById(calendarName + '_year');n" out << " submitField.value = selected.format('yyyy');n" out << " submitField = document.getElementById(calendarName + '_hour');n" out << " submitField.value = selected.format('HH');n" out << " submitField = document.getElementById(calendarName + '_minute');n" out << " submitField.value = selected.format('mm');n" out << " var displayField = document.getElementById(displayFieldName);n" out << " displayField.innerHTML = selected.format('E d MMM yyyy');n" out << "}n" out << "</script>n" } /** * Tag to provide initialization code for a single Yahoo calendar instance. This tag * should be used once for each calendar instance used on the page. This tag * should be placed in the page header and paired with a yuiCalendarBody tag in the body with * the same value for the calendarName parameter. * @param name the name of the calendar instance * @param value the date value to set the calendar to */ def yuiCalendarHeader = {attrs, body -> def calendarName = attrs.name if (calendarName == null) { throw new IllegalArgumentException("name parameter must be specified") } Date dateValue = attrs.value def monthYearFormatter = new SimpleDateFormat("MM/yyyy") def monthDayFormatter = new SimpleDateFormat("MM/dd") out << "<script language='javascript'>n" out << 'YAHOO.namespace("example.calendar");n' out << "function init${calendarName}() {n" out << " YAHOO.example.calendar.${calendarName} = new YAHOO.widget.Calendar('YAHOO.example.calendar.${calendarName}', '${calendarName}Container'" if (dateValue != null) { out << ", '${monthYearFormatter.format(dateValue)}', '${monthDayFormatter.format(dateValue)}'" } out << ");n" out << " YAHOO.example.calendar.${calendarName}.title = 'Select ${calendarName}';n" out << " YAHOO.example.calendar.${calendarName}.onSelect = set${calendarName};n" out << " YAHOO.example.calendar.${calendarName}.render();n" out << "}n" out << "function set${calendarName}() {n" out << " var calendarInstance = YAHOO.example.calendar.${calendarName};n" out << " setCalendarDateValue('${calendarName}', calendarInstance, '${calendarName}Value');n" out << "}n" out << "function show${calendarName}() {n" out << " var calendarInstance = YAHOO.example.calendar.${calendarName};n" out << " showYUICalendar("${calendarName}", calendarInstance);n" out << "}n" out << "YAHOO.util.Event.addListener(window, 'load', init${calendarName});n" out << "</script>n" } /** * Tag to provide field code for a single Yahoo calendar instance. This tag * should be used once for each calendar instance used on the page. This tag * should be placed in the page body and paired with a yuiCalendarHeader tag in the header with * the same value for the calendarName parameter. * @param name the name of the calendar instance * @param bean the bean containing the date parameter to set the display and hidden fields to */ def yuiCalendarBody = { attrs, body -> def calendarName = attrs.name def bean = attrs.bean if (calendarName == null) { throw new IllegalArgumentException("name parameter must be specified") } if (bean == null) { throw new IllegalArgumentException("bean parameter must be specified") } def day = "" def month = "" def year = "" def hour = "" def minute = "" def dateDisplay = "" if (bean[calendarName] != null) { //Set the various date values to use in the output def calendar = new GregorianCalendar() calendar.setTime(bean[calendarName]) day = calendar.get(Calendar.DATE) month = calendar.get(Calendar.MONTH) + 1 // Stupid Java months start at 0 year = calendar.get(Calendar.YEAR) hour = calendar.get(Calendar.HOUR_OF_DAY) // 24 hour clock minute = calendar.get(Calendar.MINUTE) def formatter = new SimpleDateFormat("E d MMMM yyyy") dateDisplay = formatter.format(bean[calendarName]) } out << "<span class='value ${hasErrors('bean':bean,field:calendarName,'errors')}'>n" out << "<span id='${calendarName}Value'>${dateDisplay}</span>n" out << "<a id='choose${calendarName}' onclick='show${calendarName}()' href='javascript:void(null)' >n" out << " <img border='0' id='${calendarName}Link' style='margin: 5px; vertical-align: middle' src='" createLinkTo(dir:'images',file:'office-calendar.png') out << "' />n" out << "</a></span>n" out << "<div id='${calendarName}Container' class='form_calendar'></div>n" out << "<input id='${calendarName}' type='hidden' name='${calendarName}' value='struct'/>n" out << "<input id='${calendarName}_day' type='hidden' name='${calendarName}_day' value='${day}'/>n" out << "<input id='${calendarName}_month' type='hidden' name='${calendarName}_month' value='${month}'/>n" out << "<input id='${calendarName}_year' type='hidden' name='${calendarName}_year' value='${year}'/>n" out << "<input id='${calendarName}_hour' type='hidden' name='${calendarName}_hour' value='${hour}'/>n" out << "<input id='${calendarName}_minute' type='hidden' name='${calendarName}_minute' value='${minute}'/>n" }}
JavaScript escape Tag
Allows you to embed HTML in a JavaScript string by escape special JavaScript characters.Example usage
var content = '<g:escapeBody javaScriptEscape="true"><g:render template="/books/book" /></g:escapeBody>';
import org.springframework.web.util.JavaScriptUtilsclass MyTagLib { /** * Captures the given body's text and returns it as a String * * @param body the body Closure as provided to a tag */ static captureBody(body) { def sw = new StringWriter() def saveOut = body.delegate.out try { body.delegate.out = new PrintWriter(sw) body() } finally { body.delegate.out = saveOut } return sw.toString() } /** * Escape the body text for use in a Javascript string context * * Useful for Ajax stuff */ def escapeBody = { attrs, body -> // TODO maybe support more formats assert attrs.javaScriptEscape, "'javaScriptEscape' attribute was not provided, but it's the only supported escaping mechanism right now!?" out << JavaScriptUtils.javaScriptEscape(captureBody(body)) } }
image tag
/* src is the path to the image within the images directory <g:image src="grails_logo.jpg" alt="foo" title="bar" /> If you pass in the code attribute it will use that to create an alt and title attribute pulled from your i18n messages. This will supercede any alt or title attributes that you have manually passed in. The code attribute is the only case where it will escape quotes in your attribute's contents. */ def image = {attrs -> def src = attrs.remove('src'); def imageString = "<img src="${grailsAttributes.getApplicationUri(request)}/images/${src}""; def newAttrs = [:]; for (key in attrs.keySet()){ if (key != 'code'){ newAttrs[key] = attrs[key]; } } if(attrs['code']) { def messageSource = grailsAttributes .getApplicationContext() .getBean("messageSource"); def locale = RCU.getLocale(request); def code = attrs['code'] def message = messageSource.getMessage( code, null, null, locale ); if(message) { newAttrs['alt'] = message.replaceAll(""", "\\\""); newAttrs['title'] = message.replaceAll(""", "\\\""); } else { newAttrs['alt'] = code; newAttrs['title'] = code; } } for (key in newAttrs.keySet()){ imageString += " ${key}="${newAttrs[key]}""; } imageString += " />"; out << imageString; }
DOM ids
With all this Ajaxy goodness you need some easy consistent way to reference DOM elements. Until something like this is integrated into the core domain objects here's a simple tag to generate dom ids for you./** takes two attributes object (that you want the dom_id of) and optionally a prefix which will be prepended with an underscore before the object's class and id. <g:domId object="${aStudentObject}" prefix="foo" /> Assuming the Student object's id was 4 this outputs: foo_student_4 */ def domId = { attrs -> if (attrs['prefix']){ out << attrs['prefix']; out << '_'; } out << attrs['object'].getClass().getName().toLowerCase(); out << '_'; out << attrs['object'].id }
Enable or disable checkbox
This version of the g:checkbox is identical to the original one, except that you can set the "disabled=" property to true or false.def managedCheckBox = { attrs ->
def value = attrs.remove('value')
def name = attrs.remove('name')
def disabled = attrs.remove('disabled')
if(!value) value = false
out << '<input type="hidden" '
out << "name="_${name}" />"
out << '<input type="checkbox" '
out << "name="${name}" "
if(value) {
out << 'checked="checked" '
}
if(disabled != null && disabled == 'true')
{
out << 'disabled="disabled" '
}
out << "value="true" "
// process remaining attributes
outputAttributes(attrs) // close the tag, with no body
out << ' />'
}<g:managedCheckBox disabled="true" name="myCheckbox" value="not_editable_value" /> <g:managedCheckBox disabled="false" name="myCheckbox" value="editable_value" />
Model TagLib
This tag lib refines Bean TagLib to modularize the code and provide support for many more field types, and provides template-driven control of the rendering of fields, their labels, errors and required indicator.The code is currently running on 2 live sites, sadly without unit tests (TagLib testing was broken in head when I wrote this). modelRadioXXX is not implemented yet.There is a modelCountry tag which is dependent on another taglib called I18NTaglib.For the lengthy code see Draft_ModelTagLib.I18N TagLib
Embryonic support for country selection drop-downs, and localized date display. Country names in English only currently but I have them in 4 or so languages once I work out how to do this.In future will support restricting range of countries listed, and setting the order (default = server-locale sort order of country name).For the code see Draft_I18NTagLib.renderRandom Tag
render a configurable number of random items out of a collection, uses default g:renderExample usage<g:renderRandom template="/shared/product" max="4" var="item" collection="${Product.findAllByActive(true)}"/>
def renderRandom = { attrs, body ->
if(!attrs.collection)
throwTagError("Tag [renderRandom] is missing required attribute [collection]")
def max = (attrs.max == null)?5:attrs.max.toInteger()
def collection = attrs.collection
if(collection.size() > max){
Random rnd = new Random()
def newcollection = []
for(i in 0..<max){
def nextRandom = rnd.nextInt(collection.size() - 1)
newcollection.add(collection.remove(nextRandom))
}
attrs.collection = newcollection
}
render(attrs,body)
}transform Tag
Description
Applies an XSL stylesheet to a source XML document and writes the output to the response. The transformed XML will appear wherever the tag is used in a GSP.Parameters
- source - The source XML to transform. This can be one of: a DOM node, an _InputStream_, a _Reader_, or a file path.
- transformer (optional) - A javax.xml.transform.Transformer instance that will be used to transform the source XML.
- stylesheet (optional) - The name of the XSL stylesheet to apply. The stylesheet will be loaded from a stylesheets directory in the root of the web application (i.e. _<app>/web-app/stylesheets_). For example, if this parameter has the value "verbatim", then the stylesheet will be loaded from _<app>/web-app/stylesheets/verbatim.xsl_. Note Either transform or stylesheet must be specified, but not both.
- factory (optional) - This parameter can be used with stylesheet to specify a particular javax.xml.transform.TransformerFactory to use. It is the fully-qualified class name of the TransformerFactory you want to use.
Examples
The following example uses a JAXP transformer that has already been configured in the controller. The controller simply puts the transformer object in the model under the key 'transformer' and the source DOM document under the key 'doc'.<g:transform transformer="${transformer}" source="${doc}" />The Code!
import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamSource import javax.xml.transform.stream.StreamResultclass XmlTagLib { /** * Applies an XSL stylesheet to a source DOM document or file and * writes the result to the output stream. */ def transform = { attrs -> // Check that some XML to transform has been provided. def input = attrs['source'] if (!input) { throwTagError('Tag [transform] missing required attribute [source].') } // Check the various attributes. // // We must have one of 'transformer' or 'stylesheet'. def transformer = attrs['transformer'] if (!transformer) { // No transformer specified, so use a stylesheet instead. def stylesheet = attrs['stylesheet'] // If there's no stylesheet either, then we're stuck. if (!stylesheet) { throwTagError('Tag [transform] missing required attribute [transformer] or [stylesheet].') } // See whether a factory class has been specified. If so, // we use that one to create a transformer. Otherwise, we // just use the default factory. def factory if (attrs['factory']) { factory = Class.forName(attrs['factory']).newInstance() } else { factory = TransformerFactory.newInstance() } // Load up the stylesheet into a transformer instance. def xslStream = servletContext.getResourceAsStream("/stylesheets/${stylesheet}.xsl") transformer = factory.newTransformer(new StreamSource(xslStream)) } else if (attrs['stylesheet']) { throwTagError('Tag [transform]: [stylesheet] attribute can not be used with [transformer].') } // We have the transformer set up, so now prepare the source // XML and wrap the output writer in a Result. def output = new StreamResult(out) if (input instanceof Node) { input = new DOMSource(input) } else if (input instanceof InputStream || input instanceof Reader) { // Create a StreamSource for the given file. input = new StreamSource(input) } else { input = new StreamSource(new File(input)) } // Perform the transformation! transformer.transform(input, output) } }
ScaffoldTags
Moved to its own page under the plugins section, along with a walkthrough of what it provides. Also checked in on the Grails Plugin SVN.Features
- Display, list, or edit domain objects on your GSP pages using a single tag call.
- Modify how your domain objects are displayed without requiring significant rewrite (either for a specific domain class or in general).
- Modify how specific data types, or even specific domain properties are displayed without requiring significant rewrite.
- Modify how specific types of relationships are displayed.
- Either inherit from a shared template or use a controller-specific template
- Extendable to support other types of views, and to reuse templates between view types
radiogroup
Description
Similar functionality to the FormTagLib <g:select> but produces a radio group instead of a select box.Why
The main utility of this would be displaying a radio group based an a data base table. Radio groups are sometimes preferred to select lists for small numbers of items.Parameters
Same as <g:select>: from keys value noSelectionName change from <g:select>: optionKey to radioKey optionValue to radioValueNew horizontal: if horizontal="true" buttons are displayed horizontally name: name of radiogroup noSelectionChecked: Unsurprisingly, checks the noSelection radio button Example
<g:radiogroup> radioKey="id" from=$"$books.list()}" noSelection="['':'']" noSelectionChecked="true" name="books" </g:radiogroup>
/** author: Larry Headlund **/def renderRadio = { name, value, label, checked, remainingAttributes ->
out << '<INPUT TYPE=radio '
out << "NAME="${name}" "
out << "VALUE="${value.toString().encodeAsHTML()}" "
if(checked) {
out << 'CHECKED '
}
outputAttributes(remainingAttributes)
out << " ></INPUT>"
out << "${label}"
} /**
* A helper tag for creating HTML radiogroups following the select helper tag
*
* Examples:
* <g:radiogroup name="user.age" from="${18..65}" value="${age}" />
* <g:radiogroup name="user.company.id" from="${Company.list()}" value="${user?.company.id}" optionKey="id" />
*
* It has the additional attribute 'horizontal' ( which
* determines if the layour is horizontal or vertical
*/
def radiogroup = { attrs ->
def name = attrs.remove('name')
def from = attrs.remove('from')
def keys = attrs.remove('keys')
def radioKey = attrs.remove('radioKey')
def radioValue = attrs.remove('radioValue')
def value = attrs.remove('value')
def horizontal = attrs.remove('horizontal')
def noSelection = attrs.remove('noSelection')
def noSelectionChecked = (attrs.remove('noSelectionChecked') ? true : false) // create radio buttons from list
if(from) {
from.eachWithIndex { el,i ->
def val = "";
def label = "";
def checked = false; if(keys) {
val = keys[i]
if(keys[i] == value) {
checked = true;
}
}
else if(radioKey) {
def keyValue = null
if(radioKey instanceof Closure) {
keyValue = radioKey(el)
val = keyValue
}
else if(el !=null && radioKey == 'id' && grailsApplication.getGrailsDomainClass(el.getClass().name)) {
keyValue = el.ident()
val = keyValue
}
else {
keyValue = el.properties[radioKey]
val = keyValue
} if(keyValue == value) {
checked = true
}
}
else {
val = el
if(el == value) {
checked = true
}
}
if(radioValue) {
if(radioValue instanceof Closure) {
label = radioValue(el).toString().encodeAsHTML()
}
else {
label = el.properties[radioValue].toString().encodeAsHTML()
}
}
else {
def s = el.toString()
if(s) {
label = s.encodeAsHTML()
}
}
renderRadio(name, val, label, checked,attrs)
if (horizontal != "true") {
out << "<br>"
}
out << "n"
}
} if (noSelection != null) {
noSelection = noSelection.entrySet().iterator().next()
renderRadio(name, noSelection.key, noSelection.value, noSelectionChecked,attrs)
}
}multiple select tag
Description
The current (as of 1.0 RC3) GSP select tag does not deal with a 'value' of anything but a single string. This tag allows you to have a list as the 'value' parameter, so that select boxes with multiple options can display multiple values pre-selected.Example
<g:select from="${roles.list()}" values="${user.roles}" name="user.roles" multiple />
How to use
Copy the code below in to a file named "MultiselectTagLib.groovy" and place in the grails-app/taglib directory of your application.Code
/** * multiselect taglib * adapted from select functionality in Grails' FormTagLib.groovy file * * Intended as a stopgap until multiselect functionality is added * to Grails permanently * * Additional functionality for multiple select behaviour * provided by Michael Kimsal * * Original FormTagLib code * Copyright 2004-2005 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * */import org.springframework.web.servlet.support.RequestContextUtils as RCU;class MultiselectTagLib { /** * A helper tag for creating HTML selects * * Examples: * <g:select name="user.age" from="${18..65}" value="${age}" /> * <g:select name="user.company.id" from="${Company.list()}" value="${user?.company.id}" optionKey="id" /> */ def multiselect = { attrs -> def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource") def locale = RCU.getLocale(request) def writer = out attrs.id = attrs.id ? attrs.id : attrs.name def from = attrs.remove('from') def keys = attrs.remove('keys') def optionKey = attrs.remove('optionKey') def optionValue = attrs.remove('optionValue') def value = attrs.remove('value') def valueMessagePrefix = attrs.remove('valueMessagePrefix') def noSelection = attrs.remove('noSelection') if (noSelection != null) { noSelection = noSelection.entrySet().iterator().next() } writer << "<select name="${attrs.remove('name')}" " // process remaining attributes outputAttributes(attrs) writer << '>' writer.println() if (noSelection) { renderNoSelectionOption(noSelection.key, noSelection.value, value) writer.println() } // create options from list if(from) { from.eachWithIndex { el,i -> def keyValue = null writer << '<option ' if(keys) { keyValue = keys[i] writeValueAndCheckIfSelected(keyValue, value, writer) } else if(optionKey) { if(optionKey instanceof Closure) { keyValue = optionKey(el) } else if(el !=null && optionKey == 'id' && grailsApplication.getArtefact(DomainClassArtefactHandler.TYPE, el.getClass().name)) { keyValue = el.ident() } else { keyValue = el[optionKey] } writeValueAndCheckIfSelected(keyValue, value, writer) } else { keyValue = el writeValueAndCheckIfSelected(keyValue, value, writer) } writer << '>' if(optionValue) { if(optionValue instanceof Closure) { writer << optionValue(el).toString().encodeAsHTML() } else { writer << el[optionValue].toString().encodeAsHTML() } } else if(valueMessagePrefix) { def message = messageSource.getMessage("${valueMessagePrefix}.${keyValue}", null, null, locale) if(message != null) { writer << message.encodeAsHTML() } else if (keyValue) { writer << keyValue.encodeAsHTML() } else { def s = el.toString() if(s) writer << s.encodeAsHTML() } } else { def s = el.toString() if(s) writer << s.encodeAsHTML() } writer << '</option>' writer.println() } } // close tag writer << '</select>' } private writeValueAndCheckIfSelected(keyValue, value, writer){ writer << "value="${keyValue}" " if(keyValue?.toString() == value?.toString()) { writer << 'selected="selected" ' } if(value?.contains(keyValue?.toString())) { writer << 'selected="selected" ' } } /** * Dump out attributes in HTML compliant fashion */ void outputAttributes(attrs) { attrs.remove( 'tagName') // Just in case one is left attrs.each { k,v -> out << k << "="" << v.encodeAsHTML() << "" " } }}
localeSelecter Tag
Description
Creates a select to select from a list of available localesI know that there is a localeSelect, but this one only show languages for which there is a message_XX.properties file.And the language name is show in its own language, i.e. 'Deutch' instead of 'German' Example
<g:localeSelecter />The Code!
import org.springframework.web.servlet.support.RequestContextUtilsdef localeSelecter = {attrs -> List locales = [] new File('./grails-app/i18n').eachFile { def arr = it.name.split("[_.]") locales << (arr.length > 3 ? new Locale(arr[1], arr[2]) : arr.length > 2 ? new Locale(arr[1]) : new Locale("")) } attrs['from'] = locales attrs['name'] = "lang" attrs['value'] = RequestContextUtils.getLocale(request) attrs['optionValue'] = {"${it.getDisplayName(it)}"} out << select(attrs) }
Sorting each Tag
Description
An each tag that can sort the items by it's alphanumeric string value, not lexical string value (Test2 is before Test100)This uses AlphNumSorter.groovy from http://www.davekoelle.com/files/alphanum.groovy. Put AlphNumSorter.groovy in grails_app_home/src/groovy/. It support the var and status attributes of the standard g:each tag. It will sort if the sort attribute is set to "true".Example
<g:each sort="true" var="t" in="${test.testActionMixes}"> <li><g:link controller="testActionMix" action="show" id="${t.id}">${t}</g:link></li> </g:each>
The Code!
/* Author: Drew Gulino */
def each =
{
attrs, body ->
def var = attrs.var ? attrs.var : "num"
def status = attrs.status ? attrs.status : "status"
def unsorted = attrs.in
def output
def index = 0 if (attrs?.sort == "true")
{
output = new TreeSet(new AlphNumSorter())
output.addAll(unsorted)
} else
{
output = unsorted
} output.each()
{
num ->
out << body((var):num,(status):index)
index += 1
}
}stateDropDown Tag
Description
Creates a state drop down list (including DC) and allows for passing in of preselected state value. Does NOT require the "from" attribute of the g:select tag so you don't have to pass in a list of states.Example
<g:stateDropDown name="state" id="state" value="${someObj.state}" />The Code!
/* Author Ryan Headley */
def stateDropDown = { attrs ->
def stateList = [
AL:"Alabama",
AK:"Alaska",
AZ:"Arizona",
AR:"Arkansas",
CA:"California",
CO:"Colorado",
CT:"Connecticut",
DC:"District of Columbia",
DE:"Delaware",
FL:"Florida",
GA:"Georgia",
HI:"Hawaii",
ID:"Idaho",
IL:"Illinois",
IN:"Indiana",
IA:"Iowa",
KS:"Kansas",
KY:"Kentucky",
LA:"Louisiana",
ME:"Maine",
MD:"Maryland",
MA:"Massachusetts",
MI:"Michigan",
MN:"Minnesota",
MS:"Mississippi",
MO:"Missouri",
MT:"Montana",
NE:"Nebraska",
NV:"Nevada",
NH:"New Hampshire",
NJ:"New Jersey",
NM:"New Mexico",
NY:"New York",
NC:"North Carolina",
ND:"North Dakota",
OH:"Ohio",
OK:"Oklahoma",
OR:"Oregon",
PA:"Pennsylvania",
RI:"Rhode Island",
SC:"South Carolina",
SD:"South Dakota",
TN:"Tennessee",
TX:"Texas",
UT:"Utah",
VT:"Vermont",
VA:"Virginia",
WA:"Washington",
WI:"Wisconsin",
WV:"West Virginia",
WY:"Wyoming"] out << "<select name='${attrs.name}' id='${attrs.id}'>"
out << "<option value=''>Select...</option>"
stateList.each {
out << "<option value='${it.key}'"
if(attrs.selectedValue == it.key) {
out << " selected='selected'"
}
out << ">${it.value}</option>"
}
out << "</select>"
}Check Box List
Description
In a has-many relationship, Grials default implementation generates a list of links, and an add link where one can add a new item.The Check Box List allows selection of existing items by presenting them as an array of checkboxes. In other words, it allows the user to select a subset amongst a superset of all items - as a more user-friendly alternative to a multi-select listbox (where a single click without the modifier key de-selects everything!). The CSS rendering provides the list within a scrollable DIV element to keep it independent of the total number of items in the list.Tag parameters:name: Name of the checkboxlist - this will also be the name of the element from: A collection of the master set of elements (containing all possible items)value: A collection (subset) of the currently selected items.optionKey: same as the conventional option keyExample
<g:checkBoxList name="usersIds" from="${User.list()}" value="${claim?.users?.collect{it.id}}" optionKey="id"/>
Should render a checkbox list that looks like this

Code
A bit of CSS is needed to be included somewhere, either inline or in a CSS file. Needless to say, there's plenty of scope for further customization here:<style type="text/css">
.CheckBoxList {
height: 100px; overflow: auto; overflow-x: hidden; width: 200px; border: 0px solid #000;
list-style-type: none; margin: 0; padding:0px
}.CheckBoxList li {
padding:2px
}
</style>// Checkbox list that can be used as a more user-friendly alternative to
// a multiselect list box
def checkBoxList = {attrs, body -> def from = attrs.from
def value = attrs.value
def cname = attrs.name
def isChecked, ht, wd, style, html // sets the style to override height and/or width if either of them
// is specified, else the default from the CSS is taken
style = "style='"
if(attrs.height)
style += "height:${attrs.height};"
if(attrs.width)
style += "width:${attrs.width};" if (style.length() == "style='".length())
style = ""
else
style += "'" // closing single quote html = "<ul class='CheckBoxList' " + style + ">" out << html from.each { obj -> // if we wanted to select the checkbox using a click anywhere on the label (also hover effect)
// but grails does not recognize index suffix in the name as an array:
// cname = "${attrs.name}[${idx++}]"
// and put this inside the li: <label for='$cname'>...</label> isChecked = (value?.contains(obj."${attrs.optionKey}"))? true: false out << "<li>" <<
checkBox(name:cname, value:obj."${attrs.optionKey}", checked: isChecked) <<
"${obj}" << "</li>"
} out << "</ul>" }// this is a has-many defined as usual static hasMany = [users:User] // this additional setter is used in the multiselect list to convert // the ids selected in the checkbox list to the corresponding domain objects public void setUserIds(List ids) { this.users = ids.collect { User.get(it) } }
Label List
Description
Renders a delimiter-seperated list of values from an array. Could be used in conjunction with a CheckBoxList - placing it just above it to show the selected items in a label.Usage
Selected: <g:labelList value="${claim?.users}" delimiter="/ "/> or <g:labelList value="${[1,2,3,4]}" delimiter="/"/>
Code
def labelList = {attrs, body -> def value = attrs.value
def delim = attrs.delimiter if (!delim)
delim = "" if (value instanceof Collection && value.size() > 0) { StringBuilder html = new StringBuilder() value.each { obj ->
html.append("$obj$delim")
} out << html.substring(0, html.length() - delim.length())
} }
last.fm Recent List Tag
Description
recent: Retrieves the most recent playlist from a last.fm account and displays as a list using a similar style to last.fm Song titles link to the last.fm page for that song.weekly: Fetches the most popular artists for the current week for a user account and displays an ordered list.Usage
basic lists:<lastfm:recent username="someUserAccount"/> <lastfm:weekly username="someUserAccount" limit="15"/>
<lastfm:recent username="someUserAccount" ordered="true"/>
<lastfm:recent src="file:///home/me/myRecentList.xml"/>The Code
/** * LastFmTagLib provides two targets to retrieve user data from last.fm * (http://www.lastfm.com). * * 1. The "recent" tag pulls the 10 most recently listened to tracks for the * supplied username: * <lastfm:recent username="someuser"/> * * 2. The "weekly" tag pulls the most listened to artists for a supplied * username, sorted by most tracks played: * <lastfm:weekly username="someuser" limit="20"/> * * Each target can optionally take a "src" attribute to read the XML from * instead of hitting the last.fm site each time it's executed. This is * useful for a busy site or page that can grab the XML via a cron job or * quartz job, storing the data locally for some minutes at a time. * * @author Darren Davison (darren [at] davisononline (dot) org) */ class LastFmTagLib { static namespace = 'lastfm' def ws_prefix = "http://ws.audioscrobbler.com/1.0/user/" def ws_recent = "/recenttracks.xml" def ws_weekly = "/weeklyartistchart.xml" /* * supply a 'username' attribute to get the most recent * last.fm tracks for that user as a list. Optionally * supply a 'listClass' css attribute for the list. * * This target also takes an optional "ordered" attribute to * decide whether to display an ordered (<ol/>) or unordered * (<ul/>) list. */ def recent = { attrs, body -> def src = (attrs.src ? attrs.src : "${ws_prefix}${attrs.username}${ws_recent}") def xml = new URL(src).text def recent = new XmlSlurper().parseText(xml) def tracks = recent.track def listType = (attrs?.ordered) ? "ol" : "ul" def now = System.currentTimeMillis() / 1000 // render as a list with links out << "<${listType} id="lastfmRecentList" class="${attrs?.listClass}">n" tracks.each { def d = Long.valueOf(it.date.@uts.text()) def diff = now - d def showdiff if (diff < 180) showdiff = "just listened" else if (diff < 3600) showdiff = "${Math.round(diff/60)} minutes ago" else if (diff < 86400) showdiff = "${Math.round(diff/3600)} hours ago" else showdiff = "${Math.round(diff/86400)} days ago" out << " <li class="lastfmRecentListItem"><a href="${it.url}" title="listen at last.fm">${it.name}</a> - ${it.artist} (${showdiff})</li>n" } out << "</${listType}>" } /* * supply a 'username' attribute to get the most recent * last.fm tracks for that user as a list. Optionally * supply a 'listClass' css attribute for the list. * * This target also takes an optional "limit" attribute to * limit the number of entries shown from the 250 available. * Default is 10. */ def weekly = { attrs, body -> def src = (attrs.src ? attrs.src : "${ws_prefix}${attrs.username}${ws_weekly}") def xml = new URL(src).text def weekly = new XmlSlurper().parseText(xml) def artists = weekly.artist // render chart list with links to artists out << "<ol id="lastfmWeeklyList" class="${attrs?.listClass}"/>n" def limit = (attrs.limit ? Integer.valueOf(attrs.limit) : 10) (0..limit - 1).each { def a = artists[it] out << " <li class="lastfmWeeklyListItem"><a href="${a.url}" title="${a.name}">${a.name}</a> - ${a.playcount} plays </li>n" } out << "</ol>" } }
ICQStatus
Description
Displays a little icon showing your ICQ status. You have to specify the UIN (ICQ's user number) and optional a style for the image. I think image style 21 is the best one. That is the default if you omit the 'style' attribute.Example
<g:icq uin="12345" style="21"/>The Code
class IcqTagLib {
def ICQStatus = { attrs ->
def uin = attrs.uin?.toInteger()
if( uin ) {
def style = attrs.style? attrs.style.toInteger() : 21
out << '<img src="http://status.icq.com/online.gif?icq='+uin+'&img='+style+'"/>'
}
}
}Remote Paginate
Description
Copy of the standard paginate that calls ajax instead.Code
import org.springframework.web.servlet.support.RequestContextUtils as RCU;class RemotePaginateTagLib { /** * Creates next/previous links to support pagination for the current controller * * <g:paginate total="$ { Account.count() } " /> */ def remotePaginate = {attrs -> def writer = out if (attrs.total == null) throwTagError("Tag [remotePaginate] is missing required attribute [total]") if (attrs.update == null) throwTagError("Tag [remotePaginate] is missing required attribute [update]") def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource") def locale = RCU.getLocale(request) def total = attrs.total.toInteger() def update = attrs.update def action = (attrs.action ? attrs.action : (params.action ? params.action : "list")) def offset = params.offset?.toInteger() def max = params.max?.toInteger() def maxsteps = (attrs.maxsteps ? attrs.maxsteps.toInteger() : 10) if (!offset) offset = (attrs.offset ? attrs.offset.toInteger() : 0) if (!max) max = (attrs.max ? attrs.max.toInteger() : 10) def linkParams = [offset: offset - max, max: max] if (params.sort) linkParams.sort = params.sort if (params.order) linkParams.order = params.order if (attrs.params) linkParams.putAll(attrs.params) def linkTagAttrs = [url: "#"] if (attrs.controller) { linkTagAttrs.controller = attrs.controller } if (attrs.id != null) { linkTagAttrs.id = attrs.id } // determine paging variables def steps = maxsteps > 0 int currentstep = (offset / max) + 1 int firststep = 1 int laststep = Math.round(Math.ceil(total / max)) // display previous link when not on firststep if (currentstep > firststep) { linkTagAttrs.class = 'prevLink' writer << link(linkTagAttrs.clone()) { (attrs.prev ? attrs.prev : messageSource.getMessage('paginate.prev', null, messageSource.getMessage('default.paginate.prev', null, 'Previous', locale), locale)) } } // display steps when steps are enabled and laststep is not firststep if (steps && laststep > firststep) { linkTagAttrs.class = 'step' // determine begin and endstep paging variables int beginstep = currentstep - Math.round(maxsteps / 2) + (maxsteps % 2) int endstep = currentstep + Math.round(maxsteps / 2) - 1 if (beginstep < firststep) { beginstep = firststep endstep = maxsteps } if (endstep > laststep) { beginstep = laststep - maxsteps + 1 if (beginstep < firststep) { beginstep = firststep } endstep = laststep } // display firststep link when beginstep is not firststep if (beginstep > firststep) { linkParams.offset = 0 linkTagAttrs.onclick = getOnClick(update, linkParams.offset, params) writer << link(linkTagAttrs.clone()) {firststep.toString()} writer << '<span class="step">..</span>' } // display paginate steps (beginstep..endstep).each {i -> if (currentstep == i) { writer << "<span class="currentStep">${i}</span>" } else { linkParams.offset = (i - 1) * max linkTagAttrs.onclick = getOnClick(update, linkParams.offset, params) writer << link(linkTagAttrs.clone()) {i.toString()} } } // display laststep link when endstep is not laststep if (endstep < laststep) { writer << '<span class="step">..</span>' linkParams.offset = (laststep - 1) * max linkTagAttrs.onclick = getOnClick(update, linkParams.offset, params) writer << link(linkTagAttrs.clone()) { laststep.toString() } } } // display next link when not on laststep if (currentstep < laststep) { linkTagAttrs.class = 'nextLink' linkParams.offset = offset + max linkTagAttrs.onclick = getOnClick(update, linkParams.offset, params) writer << link(linkTagAttrs.clone()) { (attrs.next ? attrs.next : messageSource.getMessage('paginate.next', null, messageSource.getMessage('default.paginate.next', null, 'Next', locale), locale)) } } } /* make more object oriented? */ private String getOnClick(update, offset, params) { def contextPath = servletContext.getContextPath() def path = contextPath + "/" + params.controller + "/" + params.action def paramString = "?offset=" + offset.toString() params.each { if (it.key != "controller" && it.key != "action" && it.key != "offset" ) { paramString += "&" + it } } def onclick = "new Ajax.Updater('" + update + "','" + path onclick += paramString + "'," onclick += "{asynchronous:true, evalScripts:true,on404:function(e){alert('not found');}});return false;" return onclick }}