Ignore:
Timestamp:
Dec 21, 2010, 12:04:37 AM (13 years ago)
Author:
work@…
Message:
  • BIG refactoring / rewrite of the wizard to use the ajaxflow plugin, part one of ticket #183
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy

    r1224 r1286  
    11package dbnp.studycapturing
    22
    3 import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
    43import dbnp.studycapturing.*
    54import dbnp.authentication.SecUser
    65import dbnp.data.*
    76import cr.co.arquetipos.crypto.Blowfish
     7import nl.grails.plugins.ajaxflow.AjaxflowTagLib
    88
    99/**
     
    1919 * $Date$
    2020 */
    21 class WizardTagLib extends JavascriptTagLib {
     21class WizardTagLib extends AjaxflowTagLib {
    2222        def AuthenticationService
    2323       
     
    2525        static namespace = "wizard"
    2626
    27         // define the AJAX provider to use
    28         static ajaxProvider = "jquery"
    29 
    3027        // define default text field width
    3128        static defaultTextFieldSize = 25;
    32 
    33         /**
    34          * ajaxButton tag, this is a modified version of the default
    35          * grails submitToRemote tag to work with grails webflows.
    36          * Usage is identical to submitToRemote with the only exception
    37          * that a 'name' form element attribute is required. E.g.
    38          * <wizard:ajaxButton name="myAction" value="myButton ... />
    39          *
    40          * you can also provide a javascript function to execute after
    41          * success. This behaviour differs from the default 'after'
    42          * action which always fires after a button press...
    43          *
    44          * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
    45          * @see http://www.grails.org/WebFlow
    46          * @see http://www.grails.org/Tag+-+submitToRemote
    47          * @todo perhaps some methods should be moved to a more generic
    48          *        'webflow' taglib or plugin
    49          * @param Map attributes
    50          * @param Closure body
    51          */
    52         def ajaxButton = { attrs, body ->
    53                 // get the jQuery version
    54                 def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
    55 
    56                 // fetch the element name from the attributes
    57                 def elementName = attrs['name'].replaceAll(/ /, "_")
    58 
    59                 // javascript function to call after success
    60                 def afterSuccess = attrs['afterSuccess']
    61 
    62                 // src parameter?
    63                 def src = attrs['src']
    64                 def alt = attrs['alt']
    65 
    66                 // generate a normal submitToRemote button
    67                 def button = submitToRemote(attrs, body)
    68 
    69                 /**
    70                  * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
    71                  * not properly work with AJAX as the submitToRemote button does not
    72                  * handle and submit the form properly. In order to support webflows
    73                  * this method modifies two parts of a 'normal' submitToRemote button:
    74                  *
    75                  * 1) replace 'this' with 'this.form' as the 'this' selector in a button
    76                  *    action refers to the button and / or the action upon that button.
    77                  *    However, it should point to the form the button is part of as the
    78                  *    the button should submit the form data.
    79                  * 2) prepend the button name to the serialized data. The default behaviour
    80                  *    of submitToRemote is to remove the element name altogether, while
    81                  *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
    82                  *    the appropriate webflow action. Hence, we are going to prepend the
    83                  *    serialized formdata with an _eventId_BUTTONNAME parameter.
    84                  */
    85                 if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
    86                         // fix for older jQuery plugin versions
    87                         button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
    88                 } else {
    89                         // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
    90                         // this.form part has been fixed. Consequently, our wrapper has changed as well...
    91                         button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
    92                 }
    93 
    94                 // add an after success function call?
    95                 // usefull for performing actions on success data (hence on refreshed
    96                 // wizard pages, such as attaching tooltips)
    97                 if (afterSuccess) {
    98                         button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
    99                 }
    100 
    101                 // got an src parameter?
    102                 if (src) {
    103                         def replace = 'type="image" src="' + src + '"'
    104 
    105                         if (alt) replace = replace + ' alt="' + alt + '"'
    106 
    107                         button = button.replaceFirst(/type="button"/, replace)
    108                 }
    109 
    110                 // replace double semi colons
    111                 button = button.replaceAll(/;{2,}/, ';')
    112 
    113                 // render button
    114                 out << button
    115         }
    116 
    117         /**
    118          * generate a ajax submit JavaScript
    119          * @see WizardTagLib::ajaxFlowRedirect
    120          * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
    121          */
    122         def ajaxSubmitJs = { attrs, body ->
    123                 // define AJAX provider
    124                 setProvider([library: ajaxProvider])
    125 
    126                 // got a function name?
    127                 def functionName = attrs.remove('functionName')
    128                 if (functionName && !attrs.get('name')) {
    129                         attrs.name = functionName
    130                 }
    131 
    132                 // generate an ajax button
    133                 def button = this.ajaxButton(attrs, body)
    134 
    135                 // strip the button part to only leave the Ajax call
    136                 button = button.replaceFirst(/<[^\"]*onclick=\"/, '')
    137                 button = button.replaceFirst(/return false.*/, '')
    138 
    139                 // change form if a form attribute is present
    140                 if (attrs.get('form')) {
    141             button = button.replace(
    142                                 "jQuery(this).parents('form:first')",
    143                                 "\$('" + attrs.get('form') + "')"
    144                         )
    145                 }
    146 
    147                 // change 'this' if a this attribute is preset
    148                 if (attrs.get('this')) {
    149                         button = button.replace('this', attrs.get('this'))
    150                 }
    151 
    152                 out << button
    153         }
    154 
    155         /**
    156          * generate ajax webflow redirect javascript
    157          *
    158          * As we have an Ajaxified webflow, the initial wizard page
    159          * cannot contain a wizard form, as upon a failing submit
    160          * (e.g. the form data does not validate) the form should be
    161          * shown again. However, the Grails webflow then renders the
    162          * complete initial wizard page into the success div. As this
    163          * ruins the page layout (a page within a page) we want the
    164          * initial page to redirect to the first wizard form to enter
    165          * the webflow correctly. We do this by emulating an ajax post
    166          * call which updates the wizard content with the first wizard
    167          * form.
    168          *
    169          * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
    170          * form = the form identifier
    171          * name = the action to execute in the webflow
    172          * update = the divs to update upon success or error
    173          *
    174          * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
    175          *
    176          * Example initial webflow action to work with this javascript:
    177          * ...
    178          * mainPage {
    179          *      render(view: "/wizard/index")
    180          *      onRender {
    181          *              flow.page = 1
    182          *  }
    183          *      on("next").to "pageOne"
    184          * }
    185          * ...
    186          *
    187          * @param Map attributes
    188          * @param Closure body
    189          */
    190         def ajaxFlowRedirect = { attrs, body ->
    191                 // generate javascript
    192                 out << '<script type="text/javascript">'
    193                 out << '$(document).ready(function() {'
    194                 out << ajaxSubmitJs(attrs, body)
    195                 out << '});'
    196                 out << '</script>'
    197         }
    198 
    199         /**
    200          * render the content of a particular wizard page
    201          * @param Map attrs
    202          * @param Closure body  (help text)
    203          */
    204         def pageContent = { attrs, body ->
    205                 // define AJAX provider
    206                 setProvider([library: ajaxProvider])
    207 
    208                 // render new body content
    209                 //      - this JavaScript variable is used by baseElement to workaround an IE
    210                 //        specific issue (double submit on onchange events). The hell with IE!
    211                 //        @see baseElement
    212                 out << '<script type="text/javascript">var lastRequestTime = 0;</script>'
    213                 out << render(template: "/wizard/common/tabs")
    214                 out << '<div class="content">'
    215                 out << body()
    216                 out << '</div>'
    217                 out << render(template: "/wizard/common/navigation")
    218                 out << render(template: "/wizard/common/error")
    219         }
    22029
    22130        /**
Note: See TracChangeset for help on using the changeset viewer.