source: trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy @ 140

Last change on this file since 140 was 140, checked in by duh, 10 years ago
  • temporary backup commit
  • Property svn:keywords set to Rev Author Date
File size: 8.8 KB
Line 
1package dbnp.studycapturing
2
3import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
4import dbnp.studycapturing.*
5import dbnp.data.*
6
7/**
8 * Wizard tag library
9 *
10 * @author Jeroen Wesbeek
11 * @since 20100113
12 * @package wizard
13 *
14 * Revision information:
15 * $Rev: 140 $
16 * $Author: duh $
17 * $Date: 2010-01-27 12:32:41 +0000 (wo, 27 jan 2010) $
18 */
19class WizardTagLib extends JavascriptTagLib {
20        // define the tag namespace (e.g.: <wizard:action ... />
21        static namespace = "wizard"
22
23        // define the AJAX provider to use
24        static ajaxProvider = "jquery"
25
26        // define default text field width
27        static defaultTextFieldSize = 25;
28
29        /**
30         * ajaxButton tag, this is a modified version of the default
31         * grails submitToRemote tag to work with grails webflows.
32         * Usage is identical to submitToRemote with the only exception
33         * that a 'name' form element attribute is required. E.g.
34         * <wizard:ajaxButton name="myAction" value="myButton ... />
35         *
36         * you can also provide a javascript function to execute after
37         * success. This behaviour differs from the default 'after'
38         * action which always fires after a button press...
39         *
40         * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
41         * @see http://www.grails.org/WebFlow
42         * @see http://www.grails.org/Tag+-+submitToRemote
43         * @todo perhaps some methods should be moved to a more generic
44         *        'webflow' taglib
45         * @param Map attributes
46         * @param Closure body
47         */
48        def ajaxButton = { attrs, body ->
49                // get the jQuery version
50                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
51
52                // fetch the element name from the attributes
53                def elementName = attrs['name'].replaceAll(/ /, "_")
54
55                // javascript function to call after success
56                def afterSuccess = attrs['afterSuccess']
57
58                // generate a normal submitToRemote button
59                def button = submitToRemote(attrs, body)
60
61                /**
62                 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
63                 * not properly work with AJAX as the submitToRemote button does not
64                 * handle and submit the form properly. In order to support webflows
65                 * this method modifies two parts of a 'normal' submitToRemote button:
66                 *
67                 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
68                 *    action refers to the button and / or the action upon that button.
69                 *    However, it should point to the form the button is part of as the
70                 *    the button should submit the form data.
71                 * 2) prepend the button name to the serialized data. The default behaviour
72                 *    of submitToRemote is to remove the element name altogether, while
73                 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
74                 *    the appropriate webflow action. Hence, we are going to prepend the
75                 *    serialized formdata with an _eventId_BUTTONNAME parameter.
76                 */
77                if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
78                        // fix for older jQuery plugin versions
79                        button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
80                } else {
81                        // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
82                        // this.form part has been fixed. Consequently, our wrapper has changed as well...
83                        button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
84                }
85 
86                // add an after success function call?
87                // usefull for performing actions on success data (hence on refreshed
88                // wizard pages, such as attaching tooltips)
89                if (afterSuccess) {
90                        button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
91                }
92
93                // replace double semi colons
94                button = button.replaceAll(/;{2,}/, ';')
95               
96                // render button
97                out << button
98        }
99
100        /**
101         * generate ajax webflow redirect javascript
102         *
103         * As we have an Ajaxified webflow, the initial wizard page
104         * cannot contain a wizard form, as upon a failing submit
105         * (e.g. the form data does not validate) the form should be
106         * shown again. However, the Grails webflow then renders the
107         * complete initial wizard page into the success div. As this
108         * ruins the page layout (a page within a page) we want the
109         * initial page to redirect to the first wizard form to enter
110         * the webflow correctly. We do this by emulating an ajax post
111         * call which updates the wizard content with the first wizard
112         * form.
113         *
114         * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
115         * form = the form identifier
116         * name = the action to execute in the webflow
117         * update = the divs to update upon success or error
118         *
119         * Example initial webflow action to work with this javascript:
120         * ...
121         * mainPage {
122         *      render(view: "/wizard/index")
123         *      onRender {
124         *              flow.page = 1
125         *      }
126         *      on("next").to "pageOne"
127         * }
128         * ...
129         *
130         * @param Map attributes
131         * @param Closure body
132         */
133        def ajaxFlowRedirect = { attrs, body ->
134                // define AJAX provider
135                setProvider([library: ajaxProvider])
136
137                // generate an ajax button
138                def button = this.ajaxButton(attrs, body)
139
140                // strip the button part to only leave the Ajax call
141                button = button.replaceFirst(/<[^\"]*\"jQuery.ajax/,'jQuery.ajax')
142                button = button.replaceFirst(/return false.*/,'')
143
144                // change form if a form attribute is present
145                if (attrs.get('form')) {
146                        button = button.replaceFirst(/this\.form/,
147                                "\\\$('" + attrs.get('form') + "')"
148                        )
149                }
150
151                // generate javascript
152                out << '<script language="JavaScript">'
153                out << '$(document).ready(function() {'
154                out << button
155                out << '});'
156                out << '</script>'
157        }
158
159        /**
160         * render the content of a particular wizard page
161         * @param Map attrs
162         * @param Closure body  (help text)
163         */
164        def pageContent = {attrs, body ->
165                // define AJAX provider
166                setProvider([library: ajaxProvider])
167
168                // render new body content
169                out << render(template: "/wizard/common/tabs")
170                out << '<div class="content">'
171                out << body()
172                out << '</div>'
173                out << render(template: "/wizard/common/navigation")
174                out << render(template: "/wizard/common/error")
175        }
176
177        /**
178         * render a textFieldElement
179         * @param Map attrs
180         * @param Closure body  (help text)
181         */
182        def textFieldElement = {attrs, body ->
183                // set default size, or scale to max length if it is less than the default size
184                if (!attrs.get("size")) {
185                        if (attrs.get("maxlength")) {
186                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
187                        } else {
188                                attrs.size = defaultTextFieldSize
189                        }
190                }
191
192                // work variables
193                def addExampleElement = attrs.remove('addExampleElement')
194                //attrs.remove('addExampleElement')
195
196                // render a text element
197                out << '<div class="element">'
198                out << ' <div class="description">'
199                out << attrs.get('description')
200                out << ' </div>'
201                out << ' <div class="input">'
202
203                // add text input field
204                out << textField(attrs)
205
206                // add a help icon if help information is available
207                if (body()) {
208                        out << '        <div class="helpIcon" />'
209                }
210
211                // add an disabled input box for feedback purposes
212                // @see dateElement(...)
213                if (addExampleElement) {
214                        def exampleAttrs = new LinkedHashMap()
215                        exampleAttrs.name = attrs.get('name')+'Example'
216                        exampleAttrs.class  = 'isExample'
217                        exampleAttrs.disabled = 'disabled'
218                        out << textField(exampleAttrs)
219                }
220
221                // end HTML
222                out << ' </div>'
223
224                // add help content if it is available
225                if (body()) {
226                        out << '  <div class="helpContent">'
227                        out << '    ' + body()
228                        out << '  </div>'
229                }
230
231                out << '</div>'
232        }
233
234        //def baseElement
235
236        def templateElement = { attrs, body ->
237
238        }
239
240        /**
241         * render a dateElement
242         * @param Map attrs
243         * @param Closure body  (help text)
244         */
245        def dateElement = { attrs, body ->
246                // transform value?
247                if (attrs.value instanceof Date) {
248                        // transform date instance to formatted string (dd/mm/yyyy)
249                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
250                }
251               
252                // set some textfield values
253                attrs.maxlength = 10
254                attrs.addExampleElement = true
255               
256                // render a normal text field
257                out << textFieldElement(attrs,body)
258
259                // and attach the jquery-ui datepicker
260                out << '<script type="text/javascript">'
261                out << '$(document).ready(function() {'
262                out << '        $("#' + attrs.get('name') + '").datepicker({'
263                out << '                dateFormat: \'dd/mm/yy\','
264                out << '                altField: \'#' + attrs.get('name') + 'Example\', altFormat: \'DD, d MM, yy\''
265                out << '        });'
266                out << '});'
267                out << '</script>'
268        }
269
270        /**
271         * render a species select element
272         * @param Map attrs
273         */
274        def speciesSelect = { attrs ->
275                // fetch all species
276                attrs.from = Term.findAll()     // for now, all terms, should be refactored to be species ontology only!
277
278                // got a name?
279                if (!attrs.name) {
280                        attrs.name = 'species'
281                }
282
283                out << select(attrs)
284        }
285
286        /**
287         * render a template select element
288         * @param Map attrs
289         */
290        def templateSelect = { attrs ->
291                // fetch all templates
292                attrs.from = Template.findAll() // for now, all templates
293
294                // got a name?
295                if (!attrs.name) {
296                        attrs.name = 'template'
297                }
298
299                out << select(attrs)
300        }
301}
Note: See TracBrowser for help on using the repository browser.