source: trunk/grails-app/taglib/CustomTagLib.groovy @ 932

Last change on this file since 932 was 932, checked in by gav, 13 years ago

Configure demo mode.

File size: 14.3 KB
Line 
1//import org.apache.commons.validator.UrlValidator
2import org.codehaus.groovy.grails.validation.routines.UrlValidator
3import org.codehaus.groovy.grails.validation.routines.RegexValidator
4
5/**
6* General use custom tags.
7* Some are taken from http://www.grails.org/Contribute+a+Tag#checkBoxList
8*/
9class CustomTagLib {
10    static namespace = 'custom'
11
12    private static final Object helpBalloonLockable = new Object();
13    private static long helpBalloonCount = 0L;
14
15    def resources = { attrs ->
16        ///@todo: should include our javascript and do setup here.
17    }
18
19    /**
20    * Checkbox list that can be used as a more user-friendly alternative to a multiselect list box.
21     * Usage:
22     * To map the selected ids to corresponding domain objects,
23     * an additional set method is required in the containing domain class:
24     *       //  This additional setter is used to convert the checkBoxList string or string array
25     *       //  of ids selected to the corresponding domain objects.
26     *       public void setAssetSubItemsFromCheckBoxList(ids) {
27     *           def idList = []
28     *           if(ids instanceof String) {
29     *                   if(ids.isInteger())
30     *                       idList << ids.toInteger()
31     *           }
32     *           else {
33     *               ids.each() {
34     *                   if(it.isInteger())
35     *                       idList << it.toInteger()
36     *               }
37     *           }
38     *           this.assetSubItems = idList.collect { AssetSubItem.get( it ) }
39     *       }
40     *
41     * Then a line in the controller:
42     *      assetInstance.setAssetSubItemsFromCheckBoxList(params.assetSubItems)
43     *
44     * Fields:
45     *    name - the property name.
46     *    from - the list to select from.
47     *    value - the current value.
48     *    optionKey - the key to use.
49     *    sortBy - (optional) the attribute to sort the from list by.
50     *    displayFields - (optional) defaults to the objects toString()
51     *    displayFieldsSeparator - (optional) defaults to a space.
52     *    linkController - (optional, requires linkAction.) the controller to use for a link to the objects in the checkBoxList.
53     *    linkAction - (optional, requires linkController.) the action to use for a link to the objects in the checkBoxList.
54     *
55     * Example:
56     *    <!--
57     *    <custom:checkBoxList name="assetSubItems"
58     *                                    from="${AssetSubItem.list()}"
59     *                                    value="${assetInstance?.assetSubItems.collect{it.id}}"
60     *                                    optionKey="id"
61     *                                    sortBy="description"
62     *                                    displayFields="['id', 'name']"
63     *                                    displayFieldsSeparator=', '
64     *                                    linkController="assetSubItemDetailed"
65     *                                    linkAction="show"/>
66     *    -->
67     *
68     */
69
70    def checkBoxList = {attrs, body ->
71
72        def from = attrs.from
73        def value = attrs.value
74        def cname = attrs.name
75        def isChecked, ht, wd, style, html
76
77        def sortBy = attrs.sortBy
78        def displayFields = attrs.displayFields
79        def displayFieldsSeparator = attrs.displayFieldsSeparator ?: ' '
80        def linkController = attrs.linkController
81        def linkAction = attrs.linkAction
82
83        def displayValue = " "
84
85        // sets the style to override height and/or width if either of them
86        // is specified, else the default from the CSS is taken
87        style = "style='"
88        if(attrs.height)
89            style += "height:${attrs.height};"
90        if(attrs.width)
91            style += "width:${attrs.width};"
92        if(style.length() == "style='".length())
93            style = ""
94        else
95            style += "'" // closing single quote
96
97        html = "<ul class='CheckBoxList' " + style + ">"
98
99        out << html
100
101        if(sortBy)
102            from.sort { p1, p2 -> p1[sortBy].compareToIgnoreCase(p2[sortBy]) }
103
104        from.each { obj ->
105
106            displayValue = " "
107
108            if(linkController && linkAction)
109                   displayValue += "<a href=\"${createLink(controller: linkController, action: linkAction, id: obj.id).encodeAsHTML()}\">"
110
111            if(displayFields) {
112                displayValue += displayFields.collect { obj[it] }.join(displayFieldsSeparator)
113            }
114            else displayValue += obj // use the obj's default toString()
115
116            if(linkController && linkAction)
117                displayValue += "</a>"
118
119            // if we wanted to select the checkbox using a click anywhere on the label (also hover effect)
120            // but grails does not recognize index suffix in the name as an array:
121            // cname = "${attrs.name}[${idx++}]"
122            // and put this inside the li: <label for='$cname'>...</label>
123
124            isChecked = (value?.contains(obj."${attrs.optionKey}"))? true: false
125
126            out << "<li>" << checkBox(name:cname, value:obj."${attrs.optionKey}", checked: isChecked) << displayValue << "</li>"
127        }
128
129        out << "</ul>"
130
131    } // checkBoxList
132
133    def sortableColumnWithImg = { attrs, body ->
134        def writer = out
135        if(!attrs.property)
136            throwTagError("Tag [sortableColumn] is missing required attribute [property]")
137
138//         if(!attrs.title && !attrs.titleKey)
139//             throwTagError("Tag [sortableColumn] is missing required attribute [title] or [titleKey]")
140
141        def property = attrs.remove("property")
142        def action = attrs.action ? attrs.remove("action") : (actionName ?: "list")
143
144        def defaultOrder = attrs.remove("defaultOrder")
145        if(defaultOrder != "desc") defaultOrder = "asc"
146
147        // current sorting property and order
148        def sort = params.sort
149        def order = params.order
150
151        // add sorting property and params to link params
152        def linkParams = [:]
153        if(params.id) linkParams.put("id",params.id)
154        if(attrs.params) linkParams.putAll(attrs.remove("params"))
155        linkParams.sort = property
156
157        // determine and add sorting order for this column to link params
158        attrs.class = (attrs.class ? "${attrs.class} sortable" : "sortable")
159        if(property == sort) {
160            attrs.class = attrs.class + " sorted " + order
161            if(order == "asc")
162                linkParams.order = "desc"
163            else
164                linkParams.order = "asc"
165        }
166        else
167            linkParams.order = defaultOrder
168
169        // determine column title
170//         def title = attrs.remove("title")
171//         def titleKey = attrs.remove("titleKey")
172//         if(titleKey) {
173//             if(!title) title = titleKey
174//             def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource")
175//             def locale = RCU.getLocale(request)
176//
177//             title = messageSource.getMessage(titleKey, null, title, locale)
178//         }
179
180        // Image.
181        def img = "<img "
182        def imgAttrs = [:]
183        imgAttrs.src = attrs.remove("imgSrc")
184        imgAttrs.alt = attrs.remove("imgAlt")
185        imgAttrs.title = attrs.remove("imgTitle")
186        imgAttrs.each { k, v ->
187            if(v)
188                img += "${k}=\"${v.encodeAsHTML()}\" "
189        }
190        img += "/>"
191
192        writer << "<th "
193
194        // process remaining attributes
195        attrs.each { k, v ->
196            writer << "${k}=\"${v.encodeAsHTML()}\" "
197        }
198        writer << ">${link(action:action, params:linkParams) { img } }"
199        writer << "</th>"
200
201    } // sortableColumnWithImg
202
203    /**
204    * Customised version of jasperButton as found in jaser plugin.
205     * custom:jasperButtons is intended to be wrapped by g:jasperForm
206     */
207    def jasperButtons = {attrs ->
208        if(!attrs['format']){throw new Exception(message(code:"jasper.taglib.missingAttribute", args:'format'))}
209        if(!attrs['jasper']){throw new Exception(message(code:"jasper.taglib.missingAttribute", args:'jasper'))}
210        String jasper = attrs['jasper']
211        String buttonClass = attrs['class'] ?:  "jasperButton"
212        String format = attrs['format'].toUpperCase()
213        String text = attrs['text']
214        String heightAttr = attrs['height'] ? ' height="' + attrs['height'] + '"' : '' // leading space on purpose
215        String imgSrc = ''
216        String delimiter = attrs['delimiter'] ?: "|"
217        String delimiterBefore = attrs['delimiterBefore'] ?: delimiter
218        String delimiterAfter = attrs['delimiterAfter'] ?: delimiter
219
220        out << '''
221                    <script type="text/javascript">
222                        function submit_jasperForm(name, fmt) {
223                            var jasperForm = document.getElementsByName(name).item(0)
224                            jasperForm._format.value = fmt;
225                            jasperForm.submit();
226                            return false;
227                        }
228                    </script>
229                    '''
230
231        out << delimiterBefore
232
233        attrs['format'].toUpperCase().split(",").eachWithIndex { it, i ->
234            if (i > 0) out << delimiter
235            imgSrc = g.resource(plugin:"jasper", dir:'images/icons', file:"${it.trim()}.gif")
236            def fmt = it.trim()
237            out << """
238                        <a href="#" class="${buttonClass}" title="${it.trim()}" onClick="return submit_jasperForm('${jasper}', '${fmt}')">
239                        <img border="0" src="${imgSrc}"${heightAttr} /></a>
240                        """
241        }
242
243        out << delimiterAfter
244    } // jasperButtons
245
246    /**
247    * Easily create a link from a hopeful url string.
248    * If the supplied url is not considered a valid url the string is simply displayed.
249    *
250    * Fields:
251    *  url - String url to use.
252    *  body - If no body is supplied in the GSP then url.encodeAsHTML() is displayed.
253    *
254    * Example:
255    * <!--
256    * <custom:easyUrl url="${docRef.location}" />
257    * -->
258    */
259    def easyUrl = {attrs, body ->
260
261        def url = attrs.url
262
263        def html
264
265        body = body()
266        if(!body)
267            body = url.encodeAsHTML()
268
269        html = "${body}"
270
271        if(isURLValid(url)) {
272            html = """
273                        <a href="${url}">
274                            ${html}
275                        </a>
276                        """
277        }
278
279        out << html
280    }
281
282    /**
283    * Get a list of the machines assigned on a taskProcedureRevision.
284    *
285    * Fields:
286    *  taskProcedureRevision - TaskProcedureRevision to use.
287    *
288    * Example:
289    * <!--
290    * <custom:taskProcedureMachines taskProcedureRevision="${taskInstance.taskProcedureRevision}" />
291    * -->
292    */
293    def taskProcedureMachines = {attrs ->
294        def taskProcedureRevision = attrs.taskProcedureRevision
295        def machines = taskProcedureRevision.maintenanceActions.collect {it.assetSubItem.parentItem}.unique()
296        out << machines.encodeAsHTML()
297    }
298
299    /**
300    * Customised version of helpBalloon as found in help-balloon plugin.
301    * This version can be used in ajax rendered templates where the original fails.
302    *
303    * Fields added:
304    * iconId - Optional to specify the anchor elements id, by default an id is generated if iconSrc is supplied.
305    * iconSrc - Optional to specify anchor image src, if not supplied no anchor image is output by the taglib.
306    *
307    * Example:
308    * <!--
309    * <custom:helpBalloon code="entry.date.done" iconSrc="${resource(plugin:'help-balloons', dir:'images', file:'balloon-icon.gif')}"/>
310    * or
311    * <a href="#" id="mynewanchor" onclick="return false;">this</a>
312    * <custom:helpBalloon code="entry.date.done" iconId="mynewanchor" />
313    * -->
314    */
315    def helpBalloon = {attrs, body ->
316        def mb = new groovy.xml.MarkupBuilder(out)
317
318        def title = attrs["title"]
319        def content = attrs["content"]
320        def code = attrs["code"]
321        def suffix = attrs["suffix"] ?: ".help"
322        def encodeAs = attrs["encodeAs"]
323        def iconId = attrs["iconId"]
324        def iconSrc =  attrs["iconSrc"]
325
326        if (!title && code) title = g.message(code: code)
327        if (!content && code) content = g.message(code: code + suffix)
328
329        title = title ?: ""
330        content = content ?: ""
331
332        if (encodeAs) {
333            switch (encodeAs.toUpperCase()) {
334
335                case "HTML":
336                    title = title.encodeAsHTML()
337                    content = content.encodeAsHTML()
338                    break
339
340                case "XML":
341                    title = title.encodeAsXML()
342                    content = content.encodeAsXML()
343                    break
344            }
345        }
346
347        def num
348        synchronized (helpBalloonLockable) {
349            num = helpBalloonCount++;
350        }
351
352        if(iconSrc) {
353            iconId = iconId ?: "customHb$num"
354            mb.img(id: iconId, src: iconSrc)
355        }
356
357        def javascript = """var customHb$num = new HelpBalloon({
358    title: '${title.encodeAsJavaScript()}',
359    content: '${content.encodeAsJavaScript()}'"""
360
361        if(iconId) {
362        javascript +=""",
363    icon: \$('$iconId')"""
364        }
365
366        javascript += """
367});"""
368
369        mb.script(type: "text/javascript") {
370            mkp.yieldUnescaped(javascript)
371        }
372
373    }
374
375    /**
376     * Returns the correct headerId for the main header div.
377     */
378    def headerId = { attrs, body ->
379        def headerId = ''
380        if(grails.util.Environment.isDevelopmentMode()) headerId = 'HeaderDev'
381        else if(grailsApplication.config.demoMode.enabled) headerId = 'HeaderDemo'
382        else headerId = 'Header'
383        out << headerId
384    }
385
386    /**
387    * Determine if a supplied string is considered a url or not.
388    * The scheme/protocol can be adjusted, file:// has been excluded here.
389    * A domainValidator regex is used to allow localhost domain.
390    * A Grails branched version of commons.validator is used, this should
391    * be compatible with the apache 1.4 version release.
392    * See: http://jira.codehaus.org/browse/GRAILS-1692 for more on Grails url validation.
393    */
394    private Boolean isURLValid(url) {
395
396        def schemes = ["http","https", "ftp"] as String[]
397        //def domainValidator = new RegexValidator("localhost(:(\\d{1,5}))?")
398        def domainValidator = new RegexValidator(".*(:(\\d{1,5}))?") // Any domain, incl user@host:port
399        def validator = new UrlValidator(schemes, domainValidator, UrlValidator.ALLOW_2_SLASHES)
400        return validator.isValid(url)
401
402    }
403
404} // end class
Note: See TracBrowser for help on using the repository browser.