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

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