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

Last change on this file since 808 was 808, checked in by gav, 8 years ago

Make custom taglib domainValidator more lenient.

File size: 11.0 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    def resources = { attrs ->
13        ///@todo: should include our javascript and do setup here.
14    }
15
16    /**
17    * Checkbox list that can be used as a more user-friendly alternative to a multiselect list box.
18     * Usage:
19     * To map the selected ids to corresponding domain objects,
20     * an additional set method is required in the containing domain class:
21     *       //  This additional setter is used to convert the checkBoxList string or string array
22     *       //  of ids selected to the corresponding domain objects.
23     *       public void setAssetSubItemsFromCheckBoxList(ids) {
24     *           def idList = []
25     *           if(ids instanceof String) {
26     *                   if(ids.isInteger())
27     *                       idList << ids.toInteger()
28     *           }
29     *           else {
30     *               ids.each() {
31     *                   if(it.isInteger())
32     *                       idList << it.toInteger()
33     *               }
34     *           }
35     *           this.assetSubItems = idList.collect { AssetSubItem.get( it ) }
36     *       }
37     *
38     * Then a line in the controller:
39     *      assetInstance.setAssetSubItemsFromCheckBoxList(params.assetSubItems)
40     *
41     * Fields:
42     *    name - the property name.
43     *    from - the list to select from.
44     *    value - the current value.
45     *    optionKey - the key to use.
46     *    sortBy - (optional) the attribute to sort the from list by.
47     *    displayFields - (optional) defaults to the objects toString()
48     *    displayFieldsSeparator - (optional) defaults to a space.
49     *    linkController - (optional, requires linkAction.) the controller to use for a link to the objects in the checkBoxList.
50     *    linkAction - (optional, requires linkController.) the action to use for a link to the objects in the checkBoxList.
51     *
52     * Example:
53     *    <!--
54     *    <custom:checkBoxList name="assetSubItems"
55     *                                    from="${AssetSubItem.list()}"
56     *                                    value="${assetInstance?.assetSubItems.collect{it.id}}"
57     *                                    optionKey="id"
58     *                                    sortBy="description"
59     *                                    displayFields="['id', 'name']"
60     *                                    displayFieldsSeparator=', '
61     *                                    linkController="assetSubItemDetailed"
62     *                                    linkAction="show"/>
63     *    -->
64     *
65     */
66
67    def checkBoxList = {attrs, body ->
68
69        def from = attrs.from
70        def value = attrs.value
71        def cname = attrs.name
72        def isChecked, ht, wd, style, html
73
74        def sortBy = attrs.sortBy
75        def displayFields = attrs.displayFields
76        def displayFieldsSeparator = attrs.displayFieldsSeparator ?: ' '
77        def linkController = attrs.linkController
78        def linkAction = attrs.linkAction
79
80        def displayValue = " "
81
82        // sets the style to override height and/or width if either of them
83        // is specified, else the default from the CSS is taken
84        style = "style='"
85        if(attrs.height)
86            style += "height:${attrs.height};"
87        if(attrs.width)
88            style += "width:${attrs.width};"
89        if(style.length() == "style='".length())
90            style = ""
91        else
92            style += "'" // closing single quote
93
94        html = "<ul class='CheckBoxList' " + style + ">"
95
96        out << html
97
98        if(sortBy)
99            from.sort { p1, p2 -> p1[sortBy].compareToIgnoreCase(p2[sortBy]) }
100
101        from.each { obj ->
102
103            displayValue = " "
104
105            if(linkController && linkAction)
106                   displayValue += "<a href=\"${createLink(controller: linkController, action: linkAction, id: obj.id).encodeAsHTML()}\">"
107
108            if(displayFields) {
109                displayValue += displayFields.collect { obj[it] }.join(displayFieldsSeparator)
110            }
111            else displayValue += obj // use the obj's default toString()
112
113            if(linkController && linkAction)
114                displayValue += "</a>"
115
116            // if we wanted to select the checkbox using a click anywhere on the label (also hover effect)
117            // but grails does not recognize index suffix in the name as an array:
118            // cname = "${attrs.name}[${idx++}]"
119            // and put this inside the li: <label for='$cname'>...</label>
120
121            isChecked = (value?.contains(obj."${attrs.optionKey}"))? true: false
122
123            out << "<li>" << checkBox(name:cname, value:obj."${attrs.optionKey}", checked: isChecked) << displayValue << "</li>"
124        }
125
126        out << "</ul>"
127
128    } // checkBoxList
129
130    def sortableColumnWithImg = { attrs, body ->
131        def writer = out
132        if(!attrs.property)
133            throwTagError("Tag [sortableColumn] is missing required attribute [property]")
134
135//         if(!attrs.title && !attrs.titleKey)
136//             throwTagError("Tag [sortableColumn] is missing required attribute [title] or [titleKey]")
137
138        def property = attrs.remove("property")
139        def action = attrs.action ? attrs.remove("action") : (actionName ?: "list")
140
141        def defaultOrder = attrs.remove("defaultOrder")
142        if(defaultOrder != "desc") defaultOrder = "asc"
143
144        // current sorting property and order
145        def sort = params.sort
146        def order = params.order
147
148        // add sorting property and params to link params
149        def linkParams = [:]
150        if(params.id) linkParams.put("id",params.id)
151        if(attrs.params) linkParams.putAll(attrs.remove("params"))
152        linkParams.sort = property
153
154        // determine and add sorting order for this column to link params
155        attrs.class = (attrs.class ? "${attrs.class} sortable" : "sortable")
156        if(property == sort) {
157            attrs.class = attrs.class + " sorted " + order
158            if(order == "asc")
159                linkParams.order = "desc"
160            else
161                linkParams.order = "asc"
162        }
163        else
164            linkParams.order = defaultOrder
165
166        // determine column title
167//         def title = attrs.remove("title")
168//         def titleKey = attrs.remove("titleKey")
169//         if(titleKey) {
170//             if(!title) title = titleKey
171//             def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource")
172//             def locale = RCU.getLocale(request)
173//
174//             title = messageSource.getMessage(titleKey, null, title, locale)
175//         }
176
177        // Image.
178        def img = "<img "
179        def imgAttrs = [:]
180        imgAttrs.src = attrs.remove("imgSrc")
181        imgAttrs.alt = attrs.remove("imgAlt")
182        imgAttrs.title = attrs.remove("imgTitle")
183        imgAttrs.each { k, v ->
184            if(v)
185                img += "${k}=\"${v.encodeAsHTML()}\" "
186        }
187        img += "/>"
188
189        writer << "<th "
190
191        // process remaining attributes
192        attrs.each { k, v ->
193            writer << "${k}=\"${v.encodeAsHTML()}\" "
194        }
195        writer << ">${link(action:action, params:linkParams) { img } }"
196        writer << "</th>"
197
198    } // sortableColumnWithImg
199
200    /**
201    * Customised version of jasperButton as found in jaser plugin.
202     * custom:jasperButtons is intended to be wrapped by g:jasperForm
203     */
204    def jasperButtons = {attrs ->
205        if(!attrs['format']){throw new Exception(message(code:"jasper.taglib.missingAttribute", args:'format'))}
206        if(!attrs['jasper']){throw new Exception(message(code:"jasper.taglib.missingAttribute", args:'jasper'))}
207        String jasper = attrs['jasper']
208        String buttonClass = attrs['class'] ?:  "jasperButton"
209        String format = attrs['format'].toUpperCase()
210        String text = attrs['text']
211        String heightAttr = attrs['height'] ? ' height="' + attrs['height'] + '"' : '' // leading space on purpose
212        String imgSrc = ''
213        String delimiter = attrs['delimiter'] ?: "|"
214        String delimiterBefore = attrs['delimiterBefore'] ?: delimiter
215        String delimiterAfter = attrs['delimiterAfter'] ?: delimiter
216
217        out << '''
218                    <script type="text/javascript">
219                        function submit_jasperForm(name, fmt) {
220                            var jasperForm = document.getElementsByName(name).item(0)
221                            jasperForm._format.value = fmt;
222                            jasperForm.submit();
223                            return false;
224                        }
225                    </script>
226                    '''
227
228        out << delimiterBefore
229
230        attrs['format'].toUpperCase().split(",").eachWithIndex { it, i ->
231            if (i > 0) out << delimiter
232            imgSrc = g.resource(plugin:"jasper", dir:'images/icons', file:"${it.trim()}.gif")
233            def fmt = it.trim()
234            out << """
235                        <a href="#" class="${buttonClass}" title="${it.trim()}" onClick="return submit_jasperForm('${jasper}', '${fmt}')">
236                        <img border="0" src="${imgSrc}"${heightAttr} /></a>
237                        """
238        }
239
240        out << delimiterAfter
241    } // jasperButtons
242
243    /**
244    * Easily create a link from a hopeful url string.
245    * If the supplied url is not considered a valid url the string is simply displayed.
246    *
247    * Fields:
248    *  url - String url to use.
249    *  body - If no body is supplied in the GSP then url.encodeAsHTML() is displayed.
250    *
251    * Example:
252    * <!--
253    * <custom:easyUrl url="${docRef.location}" />
254    * -->
255    */
256    def easyUrl = {attrs, body ->
257
258        def url = attrs.url
259
260        def html
261
262        body = body()
263        if(!body)
264            body = url.encodeAsHTML()
265
266        html = "${body}"
267
268        if(isURLValid(url)) {
269            html = """
270                        <a href="${url}">
271                            ${html}
272                        </a>
273                        """
274        }
275
276        out << html
277    }
278
279    /**
280    * Determine if a supplied string is considered a url or not.
281    * The scheme/protocol can be adjusted, file:// has been excluded here.
282    * A domainValidator regex is used to allow localhost domain.
283    * A Grails branched version of commons.validator is used, this should
284    * be compatible with the apache 1.4 version release.
285    * See: http://jira.codehaus.org/browse/GRAILS-1692 for more on Grails url validation.
286    */
287    private Boolean isURLValid(url) {
288
289        def schemes = ["http","https", "ftp"] as String[]
290        //def domainValidator = new RegexValidator("localhost(:(\\d{1,5}))?")
291        def domainValidator = new RegexValidator(".*(:(\\d{1,5}))?") // Any domain, incl user@host:port
292        def validator = new UrlValidator(schemes, domainValidator, UrlValidator.ALLOW_2_SLASHES)
293        return validator.isValid(url)
294
295    }
296
297} // end class
Note: See TracBrowser for help on using the repository browser.