Changeset 1286


Ignore:
Timestamp:
Dec 21, 2010, 12:04:37 AM (7 years ago)
Author:
work@…
Message:
  • BIG refactoring / rewrite of the wizard to use the ajaxflow plugin, part one of ticket #183
Location:
trunk
Files:
44 added
5 deleted
6 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        /**
  • trunk/grails-app/views/common/_topnav.gsp

    r1266 r1286  
    1212                <li><g:link controller="study" action="list">View studies</g:link></li>
    1313                </sec:ifNotLoggedIn>
    14                 <li><g:link controller="wizard" action="index" params="[jump:'create']">Create a new study</g:link></li>
    15                 <li><g:link controller="wizard" action="index" params="[jump:'edit']">Edit a study</g:link></li>
     14                <li><g:link controller="studyWizard" action="index" params="[jump:'create']">Create a new study</g:link></li>
     15                <li><g:link controller="studyWizard" action="index" params="[jump:'edit']">Edit a study</g:link></li>
    1616                <li><g:link controller="importer" action="index">Import study data</g:link></li>
    1717        <li><g:link controller="simpleQuery" action="index">Search study data</g:link></li>
  • trunk/grails-app/views/study/list.gsp

    r1181 r1286  
    3434                                                <td><input type="checkbox" name="id" value="${studyInstance.id}" id="${studyInstance.title}"></td>
    3535                                                <td><g:link action="show" id="${studyInstance?.id}"><img src='${fam.icon(name: 'application_form_magnify')}' border="0" alt="view study" /></g:link></td>
    36                                                 <td><g:if test="${studyInstance.canWrite(loggedInUser)}"><g:link class="edit" controller="wizard" params="[jump:'edit']" id="${studyInstance?.id}"><img src='${fam.icon(name: 'application_form_edit')}' border="0" alt="edit study" /></g:link></g:if><g:else><img src='${fam.icon(name: 'lock')}' border="0" alt="you have no write access to shis study" /></g:else> </td>
     36                                                <td><g:if test="${studyInstance.canWrite(loggedInUser)}"><g:link class="edit" controller="studyWizard" params="[jump:'edit']" id="${studyInstance?.id}"><img src='${fam.icon(name: 'application_form_edit')}' border="0" alt="edit study" /></g:link></g:if><g:else><img src='${fam.icon(name: 'lock')}' border="0" alt="you have no write access to shis study" /></g:else> </td>
    3737                                                <td>${fieldValue(bean: studyInstance, field: "code")}</td>
    3838                                                <td>
     
    7777                <div class="buttons">
    7878                        <sec:ifLoggedIn>
    79                                 <span class="button"><g:link class="create" controller="wizard" params="[jump:'create']"><g:message code="default.new.label" args="[entityName]"/></g:link></span>
     79                                <span class="button"><g:link class="create" controller="studyWizard" params="[jump:'create']"><g:message code="default.new.label" args="[entityName]"/></g:link></span>
    8080                        </sec:ifLoggedIn>
    8181                </div>
  • trunk/grails-app/views/study/show.gsp

    r1266 r1286  
    216216                                <g:hiddenField name="id" value="${studyInstance?.id}"/>
    217217                                <g:if test="${studyInstance.canWrite(loggedInUser)}">
    218                                         <span class="button"><g:link class="edit" controller="wizard" params="[jump:'edit']" id="${studyInstance?.id}">${message(code: 'default.button.edit.label', default: 'Edit')}</g:link></span>
     218                                        <span class="button"><g:link class="edit" controller="studyWizard" params="[jump:'edit']" id="${studyInstance?.id}">${message(code: 'default.button.edit.label', default: 'Edit')}</g:link></span>
    219219                                </g:if>
    220220                                <g:if test="${studyInstance.isOwner(loggedInUser)}">
  • trunk/web-app/css/default_style.css

    r1266 r1286  
    1111        text-decoration: none;
    1212}*/
     13
    1314h1 {
    1415        color: #006dba;
  • trunk/web-app/css/importer.css

    r1277 r1286  
    11/**
    2  * ajaxflow css
     2 * importer specific css
    33 *
    44 * @author      Jeroen Wesbeek
     
    1010 * $Date$
    1111 */
    12 
    13 /** START :: AJAX FLOW **/
    14 .ajaxFlow {
    15         width: 100%;
    16 }
    17 .ajaxFlow h1 {
    18         color: #006DBA;
    19         font-weight: bold;
    20         font-size: 18px;
    21         padding-bottom: 10px;
    22 }
    23 .ajaxFlowError {
    24         width: 97.5%;
    25         border: 1px dotted red;
    26         padding: 10px 10px 10px 10px;
    27 }
    28 
    29 /** START :: TABS **/
    30 .tabs {
    31     display: block;
    32     padding-bottom: 2px;
    33     border-bottom: 2px solid #006DBA;
    34     width: 100%;
    35 }
    36 .tabs ul {
    37     list-style-type: none; /* suppression of useless elements */
    38     padding: 0px;
    39     font-size: 10px;
    40     margin: 0px;
    41     font-family: Verdana, Arial, Helvetica, sans-serif;
    42     font-size: 10px;
    43     height: 20px;
    44     width: 100%;
    45 }
    46 .tabs li {
    47     float: left;
    48     margin: 0px;
    49     height: 20px;
    50     float: left;
    51     display: block;
    52     text-align: center;
    53     text-decoration: none;
    54     color: #006DBA;
    55     background: #CCC;
    56     font-weight: bold;
    57 }
    58 .tabs li input {
    59         cursor: pointer;
    60         color: #006DBA;
    61         background-color: transparent;
    62         border: none;   
    63 }
    64 .tabs li:last-child:after {
    65         content: url('../images/importer/spacer.gif');
    66 }
    67 .tabs li:first-child:before {
    68         content: url('../images/importer/spacer.gif');
    69 }
    70 .tabs li:before {
    71         content:url(../images/importer/arrowR.gif);
    72         position:inherit;
    73         margin-top: 0px;
    74         margin-bottom: 0px;
    75         display:inline;
    76 }
    77 .tabs li:after {
    78         content:url(../images/importer/arrowL.gif);
    79         position:inherit;
    80         margin-top: 0px;
    81         margin-bottom: 0px;
    82         display:inline;
    83 }
    84 .tabs .content {
    85     padding: 0px 10px 0px 10px;
    86     display: inline;
    87     border: 0px;
    88         vertical-align: top;
    89         line-height: 20px;
    90 }
    91 .tabs .active {
    92     background-color: #006DBA !important;
    93     color: #fff !important;
    94     text-shadow: 0px 0px 1px #333;
    95 }
    96 
    97 /** START NAVIGATION **/
    98 .navigation {
    99     display: block;
    100     padding-top: 2px;
    101     border-top: 2px solid #006DBA;
    102     width: 100%;
    103         color: #666666;
    104         font-size: 10px;
    105 }
    106 
    107 .navigation .prevnext {
    108     cursor: pointer;
    109     color: #006DBA;
    110     background-color: transparent;
    111     border: none;
    112 }
    113 
    114 /** CONTENT STYLES **/
    115 .content {
    116         padding: 20px 0px 20px 0px;
    117 }
    118 
    119 .content h1 {
    120         color: #006DBA;
    121         font-weight: bold;
    122         font-size: 18px;
    123 }
    124 .content p {
    125         text-align: justify;
    126         padding: 10px 0px 10px 0px;
    127 }
    128 
    129 .content a, .content a:visited {
    130         color: red;
    131         text-decoration: none;
    132 }
    133 
    13412.header
    13513{
Note: See TracChangeset for help on using the changeset viewer.