Changeset 213

Show
Ignore:
Timestamp:
25-02-10 16:18:22 (4 years ago)
Author:
duh
Message:

- restructured wizard
- added information boxes
- improved error feedback (highlighted error fields)
- added confirmation page
- several smaller bugfixes and improvements

Location:
trunk
Files:
7 added
1 removed
11 modified

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy

    r209 r213  
    5151                                [title: 'Subjects'],                    // subjects 
    5252                                [title: 'Event Descriptions'],  // event descriptions 
    53                                 [title: 'Events'],                              // groups 
    54                                 [title: '---'],                         // events 
    55                                 [title: '---'],                         // samples 
    56                                 [title: '---'],                 // protocols 
    57                                 [title: '---'],                         // assays 
     53                                [title: 'Events'],                              // events 
     54                                [title: 'Confirmation'],        // confirmation page 
    5855                                [title: 'Done']                                 // finish page 
    5956                        ] 
     
    212209                                        // validation failed, feedback errors 
    213210                                        flash.errors = new LinkedHashMap() 
     211                                        flash.values = params 
    214212                                        this.appendErrors(eventDescription, flash.errors) 
    215213                                        error() 
     
    221219                                // handle form data 
    222220                                if (!this.handleEventDescriptions(flow, flash, params)) { 
     221                                        flash.values = params 
    223222                                        error() 
    224223                                } else { 
     
    233232                                if (flow.eventDescriptions.size() < 1) { 
    234233                                        // append error map 
     234                                        flash.values = params 
    235235                                        this.appendErrorMap(['eventDescriptions': 'You need at least to create one eventDescription for your study'], flash.errors) 
    236236                                        error() 
    237237                                } else if (!this.handleEventDescriptions(flow, flash, params)) { 
     238                                        flash.values = params 
    238239                                        error() 
    239240                                } else { 
     
    255256                                if (!flow.eventGroups) { 
    256257                                        flow.eventGroups = [] 
     258                                        flow.eventGroups[0] = new EventGroup(name: 'Group 1')   // 1 group by default 
    257259                                } 
    258260                        } 
     
    261263                                // @see WizardTagLibrary::timeElement{...} 
    262264                                if (params.get('startTime')) { 
    263                                         println params.get('startTime').toString() 
    264265                                        params.startTime = new Date().parse("d/M/yyyy HH:mm", params.get('startTime').toString()) 
    265266                                } 
     
    283284                                        // validation failed, feedback errors 
    284285                                        flash.errors = new LinkedHashMap() 
     286                                        flash.values = params 
    285287                                        this.appendErrors(event, flash.errors) 
    286288 
     
    294296                        on("addEventGroup") { 
    295297                                def increment = flow.eventGroups.size() 
    296                                 flow.eventGroups[ increment ] = new EventGroup(name: "group "+(increment+1)) 
     298                                flow.eventGroups[ increment ] = new EventGroup(name: "Group "+(increment+1)) 
    297299                        }.to "events" 
    298300                        on("previous") { 
     
    300302                        }.to "eventDescriptions" 
    301303                        on("next") { 
    302                                 // TODO 
    303                         }.to "events" 
    304                 } 
    305  
    306                 // render and handle group page 
    307                 groups { 
    308                         render(view: "_groups") 
     304                                flash.errors = new LinkedHashMap() 
     305 
     306                                // check if we have at least one subject 
     307                                // and check form data 
     308                                if (flow.events.size() < 1) { 
     309                                        // append error map 
     310                                        flash.values = params 
     311                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors) 
     312                                        error() 
     313                                } 
     314                        }.to "confirm" 
     315                } 
     316 
     317                confirm { 
     318                        render(view: "_confirmation") 
    309319                        onRender { 
    310320                                flow.page = 6 
    311  
    312                                 if (!flow.groups) { 
    313                                         flow.groups = [] 
    314                                 } 
    315                         } 
    316                         on("add") { 
    317                                 def increment = flow.groups.size() 
    318                                 flow.groups[increment] = new SubjectGroup(params) 
    319                         }.to "groups" 
    320                         on("next") { 
    321                                 // TODO 
    322                         }.to "groups" 
     321                        } 
    323322                        on("previous") { 
    324                                 // TODO 
    325                         }.to "subjects" 
    326                 } 
    327  
    328                 // render page three 
    329                 samples { 
    330                         render(view: "_samples") 
    331                         onRender { 
    332                                 flow.page = 7 
    333                         } 
    334                         on("previous") { 
    335                                 // TODO 
     323                                // do nothing 
    336324                        }.to "events" 
    337325                        on("next") { 
    338                                 // TODO 
    339                         }.to "protocols" 
    340                 } 
    341  
    342                 // render page three 
    343                 protocols { 
    344                         render(view: "_protocols") 
    345                         onRender { 
    346                                 flow.page = 8 
    347                         } 
    348                         on("previous") { 
    349                                 // TODO 
    350                         }.to "samples" 
    351                         on("next") { 
    352                                 // TODO 
    353                         }.to "assays" 
    354                 } 
    355  
    356                 // render page three 
    357                 assays { 
    358                         render(view: "_assays") 
    359                         onRender { 
    360                                 flow.page = 9 
    361                         } 
    362                         on("previous") { 
    363                                 // TODO 
    364                         }.to "protocols" 
    365                         on("next") { 
    366                                 // TODO 
    367                         }.to "done" 
     326                                // store everything in the database! 
     327                                success() 
     328                        }.to "confirm" 
    368329                } 
    369330 
     
    372333                        render(view: "_done") 
    373334                        onRender { 
    374                                 flow.page = 10 
     335                                flow.page = 6 
    375336                        } 
    376337                        on("previous") { 
    377338                                // TODO 
    378                         }.to "assays" 
     339                        }.to "confirm" 
    379340                } 
    380341        } 
     
    395356                if (params.get('startDate')) { 
    396357                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString()) 
     358                } else { 
     359                        params.remove('startDate') 
    397360                } 
    398361 
     
    428391         */ 
    429392        def handleEventDescriptions(flow, flash, params) { 
    430                 def names = new LinkedHashMap(); 
    431                 def errors = false; 
    432                 def id = 0; 
     393                def names = new LinkedHashMap() 
     394                def errors = false 
     395                def id = 0 
    433396 
    434397                flow.eventDescriptions.each() { 
    435                         it.name                 = params.get('eventDescription_' + id + '_name') 
     398                        it.name                         = params.get('eventDescription_' + id + '_name') 
    436399                        it.description          = params.get('eventDescription_' + id + '_description') 
    437400                        it.classification       = Term.findByName(params.get('eventDescription_' + id + '_classification')) 
     
    441404                        if (!it.validate()) { 
    442405                                errors = true 
    443                                 println id + ' :: ' + it.errors.getAllErrors() 
    444                                 this.appendErrors(it, flash.errors) 
     406                                this.appendErrors(it, flash.errors, 'eventDescription_' + id + '_') 
    445407                        } 
    446408 
    447409                        id++ 
    448410                } 
     411 
     412                return !(errors) 
    449413        } 
    450414 
     
    470434                        // remember name and check for duplicates 
    471435                        if (!names[it.name]) { 
    472                                 names[it.name] = [count: 1, first: 'subject_' + id + '_name'] 
     436                                names[it.name] = [count: 1, first: 'subject_' + id + '_name', firstId: id] 
    473437                        } else { 
    474438                                // duplicate name found, set error flag 
     
    479443                                        // yeah, also mention the first 
    480444                                        // occurrence in the error message 
    481                                         this.appendErrorMap([[names[it.name]['first']]: 'The subject name needs to be unique!'], flash.errors) 
     445                                        this.appendErrorMap(name: 'The subject name needs to be unique!', flash.errors, 'subject_'+names[it.name]['firstId']+'_') 
    482446                                } 
    483447 
    484448                                // add to error map 
    485                                 this.appendErrorMap([['subject_' + id + '_name']: 'The subject name needs to be unique!'], flash.errors) 
     449                                this.appendErrorMap([name: 'The subject name needs to be unique!'], flash.errors, 'subject_'+id+'_') 
    486450                                errors = true 
    487451                        } 
     
    529493                        if (!it.validate()) { 
    530494                                errors = true 
    531                                 println id + ' :: ' + it.errors.getAllErrors() 
    532495                                this.appendErrors(it, flash.errors) 
    533496                        } 
     
    582545                this.appendErrorMap(this.getHumanReadableErrors(object), map) 
    583546        } 
     547        def appendErrors(object, map, prepend) { 
     548                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend) 
     549        } 
    584550 
    585551        /** 
     
    591557        def appendErrorMap(map, mapToExtend) { 
    592558                map.each() {key, value -> 
    593                         mapToExtend[key] = value 
     559                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false] 
     560                } 
     561        } 
     562        def appendErrorMap(map, mapToExtend, prepend) { 
     563                map.each() {key, value -> 
     564                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true] 
    594565                } 
    595566        } 
  • trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy

    r209 r213  
    442442        } 
    443443 
     444        def show = { attrs -> 
     445                // is object parameter set? 
     446                def o = attrs.object 
     447 
     448                println o.getProperties(); 
     449                o.getProperties().each { 
     450                        println it 
     451                } 
     452 
     453                out << "!! test version of 'show' tag !!" 
     454        } 
     455 
    444456        /** 
    445457         * render table headers for all subjectFields in a template 
     
    483495                                                ) 
    484496                                        } else { 
    485                                                 out << '<span class="error">no values!!</span>' 
     497                                                out << '<span class="warning">no values!!</span>' 
    486498                                        } 
    487499                                        break; 
     
    502514                                default: 
    503515                                        // unsupported field type 
    504                                         out << '<span class="error">!'+it.type+'</span>' 
     516                                        out << '<span class="warning">!'+it.type+'</span>' 
    505517                                        break; 
    506518                        } 
  • trunk/grails-app/views/wizard/common/_error.gsp

    r105 r213  
    2020                        <p> 
    2121                                <g:if test="${!e}"><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 50px 0;"></span></g:if> 
    22                                 ${error.key} &rarr; ${error.value} 
     22                                ${error.value['key']} &rarr; ${error.value['value']} 
    2323                        </p> 
    2424                </g:each> 
    2525        </div> 
    2626        <script type="text/javascript"> 
     27                // mark error fields 
     28                <g:each in="${errors}" var="error"> 
     29                <g:if test="${error.value['dynamic']}"> 
     30                $("input:[name='${error.key}']").addClass('error'); 
     31                </g:if><g:else> 
     32                $("input:[name='${error.key}']").parent().parent().addClass('error'); 
     33                </g:else> 
     34                </g:each> 
     35 
    2736                // show error dialog 
    2837                $(function() { 
  • trunk/grails-app/views/wizard/pages/_eventDescriptions.gsp

    r209 r213  
    1616%> 
    1717<wizard:pageContent> 
    18         <wizard:termElement name="classification" description="Classification" error="classification" value="${classification}"> 
     18        <span class="info"> 
     19                <span class="title">Describe all unique event types that occur in your study</span> 
     20                These unique events are, for example, treatments, challenges and sampling events. Every event description 
     21                should be unique. If your study, for example, samples both blood as well as tissue, then create two sample 
     22                descriptions. One for 'sampling blood', and one for 'sampling tissue'. 
     23        </span> 
     24 
     25        <wizard:termElement name="classification" description="Classification" error="classification" value="${values?.classification}"> 
    1926                The classification 
    2027        </wizard:termElement> 
    21         <wizard:textFieldElement name="name" description="Name" error="name" value="${name}"> 
     28        <wizard:textFieldElement name="name" description="Name" error="name" value="${values?.name}"> 
    2229                The name of the event description you are creating 
    2330        </wizard:textFieldElement> 
    24         <wizard:textFieldElement name="description" description="Description" error="description" value="${description}"> 
     31        <wizard:textFieldElement name="description" description="Description" error="description" value="${values?.description}"> 
    2532                A short description summarizing your event description 
    2633        </wizard:textFieldElement> 
    27         <wizard:checkBoxElement name="isSamplingEvent" description="Sampling event" error="isSamplingEvent" value="${isSamplingEvent}"> 
     34        <wizard:checkBoxElement name="isSamplingEvent" description="Sampling event" error="isSamplingEvent" value="${values?.isSamplingEvent}"> 
    2835                Is this a sampling event description? 
    2936        </wizard:checkBoxElement> 
  • trunk/grails-app/views/wizard/pages/_events.gsp

    r209 r213  
    1616%> 
    1717<wizard:pageContent> 
    18         <wizard:selectElement name="eventDescription" description="Event Description" error="eventDescription" from="${eventDescriptions}" value="${eventDescription}"> 
     18        <span class="info"> 
     19                <span class="title">Define all events and their duration that occur in your study</span> 
     20                In the previous screen you defined the unique event types, in this screen you need to define 
     21                all events of a specific event type that occur in time. Select the type of event, and the 
     22                start and stop time of an event. As it is frequently the case that <i>sets</i> of events act 
     23                upon (groups of) subjects, you can define event groups, and add events to a particular group.<br/> 
     24                <i>Note that you can edit multiple events at once by selecting multpiple rows by either 
     25                ctrl-clicking them or dragging a selection over them.</i> 
     26        </span> 
     27 
     28        <wizard:selectElement name="eventDescription" description="Event Description" error="eventDescription" from="${eventDescriptions}" value="${values?.eventDescription}"> 
    1929                The event description for this event 
    2030        </wizard:selectElement> 
    21         <wizard:timeElement name="startTime" description="Start Time" error="startTime" value="${startTime}"> 
     31        <wizard:timeElement name="startTime" description="Start Time" error="startTime" value="${values?.startTime}"> 
    2232                The start time of the study 
    2333        </wizard:timeElement> 
    24         <wizard:timeElement name="endTime" description="End time" error="endTimee" value="${endTime}"> 
     34        <wizard:timeElement name="endTime" description="End time" error="endTimee" value="${values?.endTime}"> 
    2535                The end time of the study 
    2636        </wizard:timeElement> 
    2737        <wizard:buttonElement name="add" value="Add" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()"/> 
    28 <g:if test="${events}"> 
     38<g:if test="${events}">  
    2939        <div class="table"> 
    3040                <div class="header"> 
  • trunk/grails-app/views/wizard/pages/_study.gsp

    r195 r213  
    1616%> 
    1717<wizard:pageContent> 
     18        <span class="info"> 
     19                <span class="title">Define the basic properties of your study</span> 
     20                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce laoreet leo nec leo vehicula quis scelerisque elit pulvinar. Vivamus arcu dui, adipiscing eu vestibulum id, consectetur et erat. Aenean risus mauris, placerat et lacinia vulputate, commodo eget ligula. Pellentesque ornare blandit metus ac dictum. Donec scelerisque feugiat quam, a congue ipsum malesuada nec. Donec vulputate, diam eget porta rhoncus, est mauris ullamcorper turpis, vitae dictum risus justo quis justo. Aenean blandit feugiat accumsan. Donec porttitor bibendum elementum. 
     21        </span> 
     22         
    1823        <wizard:textFieldElement name="title" description="Title" error="title" value="${study?.title}"> 
    1924                The title of the study you are creating 
  • trunk/grails-app/views/wizard/pages/_subjects.gsp

    r209 r213  
    1616%> 
    1717<wizard:pageContent> 
     18        <span class="info"> 
     19                <span class="title">Add subjects to your study</span> 
     20                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce laoreet leo nec leo vehicula quis scelerisque elit pulvinar. Vivamus arcu dui, adipiscing eu vestibulum id, consectetur et erat. Aenean risus mauris, placerat et lacinia vulputate, commodo eget ligula. Pellentesque ornare blandit metus ac dictum. Donec scelerisque feugiat quam, a congue ipsum malesuada nec. Donec vulputate, diam eget porta rhoncus, est mauris ullamcorper turpis, vitae dictum risus justo quis justo. Aenean blandit feugiat accumsan. Donec porttitor bibendum elementum. 
     21        </span> 
     22 
    1823        <wizard:ajaxButton name="add" value="Add" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()" /> 
    1924        <input name="addNumber" size="4" maxlength="4" value="1"> 
  • trunk/grails-app/views/wizard/pages/_templates.gsp

    r195 r213  
    1616%> 
    1717<wizard:pageContent> 
     18        <span class="info"> 
     19                <span class="title">Select the template you would like to use</span> 
     20                A template is a predefined set of values to store with all elements of your study. 
     21        </span> 
     22 
    1823        <wizard:templateElement name="template" description="Template" value="${study?.template}"> 
    1924                The meta data template to use for this study 
  • trunk/web-app/css/wizard.css

    r209 r213  
    22    display: block; 
    33    padding-top: 10px; 
     4} 
     5 
     6.wizard .info { 
     7    display: block; 
     8    border: 1px solid #006DBA; 
     9    background-color: #EEE; 
     10    padding: 10px; 
     11    font-size: 10px; 
     12    margin: 10px 0px 10px 0px; 
     13} 
     14 
     15.wizard .info .title { 
     16    color:#006DBA; 
     17    font-size:14px; 
     18    font-weight:normal; 
     19    display: block; 
     20    margin-bottom: 5px; 
     21    padding-left: 20px; 
     22    background: url(../images/icons/famfamfam/information.png) no-repeat center left; 
    423} 
    524 
     
    7089} 
    7190 
    72 .wizard .error { 
    73     display: block; 
    74     padding-top: 10px; 
    75 } 
    76  
    7791.wizard .element { 
    7892    display: block; 
     
    88102.wizard .element .input { 
    89103    display: inline; 
     104} 
     105 
     106.wizard .error { 
     107    background: url(../images/icons/famfamfam/exclamation.png) no-repeat 230px 4px; 
    90108} 
    91109 
     
    184202} 
    185203 
    186 .wizard .table .row .error { 
     204.wizard .table .row .warning { 
    187205    color: red; 
    188206    font-size: 8px; 
     
    190208} 
    191209 
     210.wizard .table .row .error { 
     211    background-color: #ffb0b7; 
     212} 
     213 
    192214.wizard .table input, .wizard .table select { 
    193215    border: 1px solid #8e908f; 
     
    195217    padding: 2px 4px; 
    196218    background-color: transparent; 
     219    width: 100px; 
     220} 
     221.wizard .table .header input, .wizard .table .header select, .wizard .table .header button { 
     222    border: 1px solid #8e908f; 
     223    margin: 2px 0; 
     224    padding: 2px 4px; 
     225    background-color: #fff; 
    197226    width: 100px; 
    198227} 
  • trunk/web-app/js/table-editor.js

    r209 r213  
    1818    columnIdentifier:   null, 
    1919 
     20    /** 
     21     * initialize object 
     22     * @param tableIdentifier 
     23     * @param rowIdentifier 
     24     * @param columnIdentifier 
     25     */ 
    2026    init: function(tableIdentifier, rowIdentifier, columnIdentifier) { 
    2127        // store parameters globally 
     
    3541    }, 
    3642 
     43    /** 
     44     * initialize table 
     45     * @param table 
     46     */ 
    3747    initializeTable: function(table) { 
    3848        var that = this; 
     
    4959    }, 
    5060 
     61    /** 
     62     * attach handlers to the input elements in a table row 
     63     * @param row 
     64     */ 
    5165    attachColumnHandlers: function(row) { 
    5266        var that = this; 
     
    5569            var input = $(':input', $(this)); 
    5670            // does this column contain an input field 
    57 console.log(input) 
    5871            if (input) { 
    5972                var type = $(input).attr('type'); 
    60 console.log(input) 
    61 console.log('type: '+type) 
    6273 
    6374                switch (type) { 
     
    7485                        $(input).bind('change', function() { 
    7586                            that.updateSingleInputElements(input, columnNumber, 'select'); 
    76                         }) 
     87                        }); 
     88                        break; 
     89                    case 'checkbox': 
     90                        // checkbox 
     91                        var columnNumber = count; 
     92                        $(input).bind('click', function() { 
     93                            console.log(columnNumber+': update checkboxes with value '+input) 
     94                            that.updateSingleInputElements(input, columnNumber, 'input'); 
     95                        }); 
    7796                        break; 
    7897                    case 'hidden': 
     
    93112    }, 
    94113 
     114    /** 
     115     * update all input elements in a selected column 
     116     * @param element 
     117     * @param columnNumber 
     118     * @param elementSelector 
     119     */ 
    95120    updateSingleInputElements: function(element, columnNumber, elementSelector) { 
    96121        var that = this; 
     
    99124        var r = c.parent(); 
    100125        var t = r.parent(); 
    101  
    102         // get value(s) 
    103         // TODO for multiple selects... 
    104         var v = e.val(); 
     126        var v = this.getValue(e); 
     127        // TODO for multiples... 
    105128 
    106129        // select all input elements in the selected rows 
    107130        $('.ui-selected', t).each(function() { 
    108131            $(that.columnIdentifier + ':eq(' + columnNumber + ') ' + elementSelector, $(this)).each(function() { 
    109                 if ($(this).val() != v) { 
    110                     $(this).val(v); 
    111                     // TODO support multiple selects 
     132                var me = $(this) 
     133                var myVal = that.getValue(me); 
     134                if (myVal != v) { 
     135                    that.setValue(me,v); 
    112136                } 
    113137            }) 
     138        }) 
     139    }, 
    114140 
    115         }) 
     141    /** 
     142     * get the value /status of an input field based on it's type 
     143     * @param input 
     144     */ 
     145    getValue: function(input) { 
     146        var i = $(input); 
     147 
     148        switch (i.attr('type')) { 
     149            case 'checkbox': 
     150                return i.attr('checked'); 
     151                break; 
     152            default: 
     153                return i.val(); 
     154                break; 
     155        } 
     156    }, 
     157 
     158    /** 
     159     * set the value / status of an input field based on it's type 
     160     * @param input 
     161     * @param value 
     162     */ 
     163    setValue: function(input,value) { 
     164        var i = $(input); 
     165 
     166        switch (i.attr('type')) { 
     167            case 'checkbox': 
     168                return i.attr('checked',value); 
     169                break; 
     170            default: 
     171                return i.val(value); 
     172                break; 
     173        } 
    116174    } 
    117175} 
  • trunk/web-app/js/wizard.js

    r209 r213  
    3737    attachDateTimePickers(); 
    3838 
    39     // SUBJECT PAGE 
     39    // table handlers 
    4040    attachTableEvents(); 
    4141    resizeWizardTable(); 
    42     attachSubjectSlider(); 
     42    attachTableSlider(); 
    4343    new TableEditor().init('div.table','div.row','div.column'); 
    44      
    45     // GROUPING PAGE 
    46     new Grouping().init('div.subjects', 'div.subject', 'div.groups', 'div.group','div.add','div.remove'); 
     44 
     45    // accordeon(s) 
     46    $("#accordion").accordion(); 
    4747} 
    4848 
     
    189189// slide the contents of the table if the content of 
    190190// the table is wider than the table itself 
    191 function attachSubjectSlider() { 
     191function attachTableSlider() { 
    192192    var slider = $("div#wizard").find('div.slider'); 
    193193    var header = $("div#wizard").find('div.header');