Changeset 209
- Timestamp:
- Feb 22, 2010, 6:35:37 PM (13 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy
r204 r209 47 47 flow.page = 0 48 48 flow.pages = [ 49 [title: 'Templates'], // templates 50 [title: 'Study'], // study 51 [title: 'Subjects'], // subjects 52 [title: 'Groups'], // groups 53 [title: 'Events'], // events 54 [title: 'Samples'], // samples 55 [title: 'Protocols'], // protocols 56 [title: 'Assays'], // assays 57 [title: 'Done'] // finish page 49 [title: 'Templates'], // templates 50 [title: 'Study'], // study 51 [title: 'Subjects'], // subjects 52 [title: 'Event Descriptions'], // event descriptions 53 [title: 'Events'], // groups 54 [title: '---'], // events 55 [title: '---'], // samples 56 [title: '---'], // protocols 57 [title: '---'], // assays 58 [title: 'Done'] // finish page 58 59 ] 59 60 … … 170 171 success() 171 172 } 172 }.to " groups"173 }.to "eventDescriptions" 173 174 on("previous") { 174 175 flash.errors = new LinkedHashMap() … … 181 182 } 182 183 }.to "study" 184 } 185 186 // render page three 187 eventDescriptions { 188 render(view: "_eventDescriptions") 189 onRender { 190 flow.page = 4 191 192 if (!flow.eventDescriptions) { 193 flow.eventDescriptions = [] 194 } 195 } 196 on("add") { 197 // fetch classification by name (as posted by the form) 198 params.classification = Term.findByName(params.classification) 199 200 // transform checkbox form value to boolean 201 params.isSamplingEvent = (params.containsKey('isSamplingEvent')) 202 203 // instantiate EventDescription with parameters 204 def eventDescription = new EventDescription(params) 205 206 // validate 207 if (eventDescription.validate()) { 208 def increment = flow.eventDescriptions.size() 209 flow.eventDescriptions[increment] = eventDescription 210 success() 211 } else { 212 // validation failed, feedback errors 213 flash.errors = new LinkedHashMap() 214 this.appendErrors(eventDescription, flash.errors) 215 error() 216 } 217 }.to "eventDescriptions" 218 on("previous") { 219 flash.errors = new LinkedHashMap() 220 221 // handle form data 222 if (!this.handleEventDescriptions(flow, flash, params)) { 223 error() 224 } else { 225 success() 226 } 227 }.to "subjects" 228 on("next") { 229 flash.errors = new LinkedHashMap() 230 231 // check if we have at least one subject 232 // and check form data 233 if (flow.eventDescriptions.size() < 1) { 234 // append error map 235 this.appendErrorMap(['eventDescriptions': 'You need at least to create one eventDescription for your study'], flash.errors) 236 error() 237 } else if (!this.handleEventDescriptions(flow, flash, params)) { 238 error() 239 } else { 240 success() 241 } 242 }.to "events" 243 } 244 245 // render events page 246 events { 247 render(view: "_events") 248 onRender { 249 flow.page = 5 250 251 if (!flow.events) { 252 flow.events = [] 253 } 254 255 if (!flow.eventGroups) { 256 flow.eventGroups = [] 257 } 258 } 259 on("add") { 260 // create date instances from date string? 261 // @see WizardTagLibrary::timeElement{...} 262 if (params.get('startTime')) { 263 println params.get('startTime').toString() 264 params.startTime = new Date().parse("d/M/yyyy HH:mm", params.get('startTime').toString()) 265 } 266 if (params.get('endTime')) { 267 params.get('endTime').toString() 268 params.endTime = new Date().parse("d/M/yyyy HH:mm", params.get('endTime').toString()) 269 } 270 271 // get eventDescription instance by name 272 params.eventDescription = this.getObjectByName(params.get('eventDescription'),flow.eventDescriptions) 273 274 // instantiate Event with parameters 275 def event = new Event(params) 276 277 // validate event 278 if (event.validate()) { 279 def increment = flow.events.size() 280 flow.events[increment] = event 281 success() 282 } else { 283 // validation failed, feedback errors 284 flash.errors = new LinkedHashMap() 285 this.appendErrors(event, flash.errors) 286 287 flash.startTime = params.startTime 288 flash.endTime = params.endTime 289 flash.eventDescription = params.eventDescription 290 291 error() 292 } 293 }.to "events" 294 on("addEventGroup") { 295 def increment = flow.eventGroups.size() 296 flow.eventGroups[ increment ] = new EventGroup(name: "group "+(increment+1)) 297 }.to "events" 298 on("previous") { 299 // TODO 300 }.to "eventDescriptions" 301 on("next") { 302 // TODO 303 }.to "events" 183 304 } 184 305 … … 187 308 render(view: "_groups") 188 309 onRender { 189 flow.page = 4310 flow.page = 6 190 311 191 312 if (!flow.groups) { … … 206 327 207 328 // render page three 208 events {209 render(view: "_events")210 onRender {211 flow.page = 5212 }213 on("previous") {214 // TODO215 }.to "subjects"216 on("next") {217 // TODO218 }.to "samples"219 }220 221 // render page three222 329 samples { 223 330 render(view: "_samples") 224 331 onRender { 225 flow.page = 6332 flow.page = 7 226 333 } 227 334 on("previous") { … … 237 344 render(view: "_protocols") 238 345 onRender { 239 flow.page = 7346 flow.page = 8 240 347 } 241 348 on("previous") { … … 251 358 render(view: "_assays") 252 359 onRender { 253 flow.page = 8360 flow.page = 9 254 361 } 255 362 on("previous") { … … 265 372 render(view: "_done") 266 373 onRender { 267 flow.page = 9374 flow.page = 10 268 375 } 269 376 on("previous") { … … 310 417 this.appendErrors(flow.study, flash.errors) 311 418 return false 419 } 420 } 421 422 /** 423 * re-usable code for handling eventDescription form data in a web flow 424 * @param Map LocalAttributeMap (the flow scope) 425 * @param Map localAttributeMap (the flash scope) 426 * @param Map GrailsParameterMap (the flow parameters = form data) 427 * @returns boolean 428 */ 429 def handleEventDescriptions(flow, flash, params) { 430 def names = new LinkedHashMap(); 431 def errors = false; 432 def id = 0; 433 434 flow.eventDescriptions.each() { 435 it.name = params.get('eventDescription_' + id + '_name') 436 it.description = params.get('eventDescription_' + id + '_description') 437 it.classification = Term.findByName(params.get('eventDescription_' + id + '_classification')) 438 it.isSamplingEvent = (params.containsKey('eventDescription_' + id + '_isSamplingEvent')) 439 440 // validate eventDescription 441 if (!it.validate()) { 442 errors = true 443 println id + ' :: ' + it.errors.getAllErrors() 444 this.appendErrors(it, flash.errors) 445 } 446 447 id++ 312 448 } 313 449 } … … 403 539 } 404 540 541 542 /** 543 * return the object from a map of objects by searching for a name 544 * @param String name 545 * @param Map map of objects 546 * @return Object 547 */ 548 def getObjectByName(name, map) { 549 def result = null 550 map.each() { 551 if (it.name == name) { 552 result = it 553 } 554 } 555 556 return result 557 } 558 405 559 /** 406 560 * transform domain class validation errors into a human readable -
trunk/grails-app/domain/dbnp/studycapturing/Event.groovy
r201 r209 7 7 * to multiple subjects at the same time. That is why the actual description of the event is factored out into a more 8 8 * general EventDescription class. Events that also lead to sample(s) should be instantiated as SamplingEvents. 9 * 10 * Revision information: 11 * $Rev$ 12 * $Author$ 13 * $Date$ 9 14 */ 10 class Event { 11 15 class Event implements Serializable { 12 16 Subject subject 13 17 EventDescription eventDescription … … 19 23 20 24 static hasMany = [ 21 parameterStringValues: String, // stores both STRING and STRINGLIST items (latter should be checked against the list)22 parameterIntegerValues 23 parameterFloatValues : float25 parameterStringValues: String, // stores both STRING and STRINGLIST items (latter should be checked against the list) 26 parameterIntegerValues: int, 27 parameterFloatValues: float, 24 28 ] 25 29 30 static constraints = { 31 subject(nullable: true, blank: true) // TODO: subject is to be removed from Event, and into EventGroup 32 startTime(nullable:false) 33 endTime(validator: {val, obj -> 34 if (val && val.before(obj.startTime)) { 35 return 'endTimeshouldbegreater' 36 } 37 }) 38 } 26 39 27 40 // time diff between end and start date 28 41 // thus, do this manually as follows 29 def getDuration( date1, date2 ) { 42 43 def getDuration(date1, date2) { 30 44 def timeMillis = (date2.getTime() - date1.getTime()).abs() 31 45 def days = (timeMillis / (1000 * 60 * 60 * 24)).toInteger() … … 38 52 } 39 53 40 41 54 def getDuration() { 42 55 return getDuration(startTime, endTime) 43 56 } 44 57 45 46 58 // return a string that prints the duration sensibly. 47 59 // the largest date unit (sec, min, h, day, week, month, or year) 48 60 // is output 49 def getPrettyDuration( duration ) { 50 def handleNumerus = { number ,string -> 51 return number.toString() + (number==1 ? string : string + 's' ) 52 } 53 if( duration.getYears() > 0 ) return handleNumerus( duration.getYears(), " year" ) 54 if( duration.getMonths() > 0 ) return handleNumerus( duration.getMonths(), " month" ) 55 if( duration.getDays() > 0 ) return handleNumerus( duration.getDays(), " day" ) 56 if( duration.getHours() > 0 ) return handleNumerus( duration.getHours(), " hour" ) 57 if( duration.getMinutes() > 0 ) return handleNumerus( duration.getMinutes(), " minute" ) 58 return handleNumerus( duration.getSeconds(), " second" ) 61 62 def getPrettyDuration(duration) { 63 def handleNumerus = {number, string -> 64 return number.toString() + (number == 1 ? string : string + 's') 65 } 66 if (duration.getYears() > 0) return handleNumerus(duration.getYears(), " year") 67 if (duration.getMonths() > 0) return handleNumerus(duration.getMonths(), " month") 68 if (duration.getDays() > 0) return handleNumerus(duration.getDays(), " day") 69 if (duration.getHours() > 0) return handleNumerus(duration.getHours(), " hour") 70 if (duration.getMinutes() > 0) return handleNumerus(duration.getMinutes(), " minute") 71 return handleNumerus(duration.getSeconds(), " second") 59 72 } 60 61 73 62 74 // convenience method. gives formatted string output for a duration 63 75 def getPrettyDuration() { 64 getPrettyDuration( getDuration())76 getPrettyDuration(getDuration()) 65 77 } 66 78 67 68 79 // convenience method. gives formatted string output for a duration 69 def getPrettyDuration( date1, date2 ) 70 { 71 return getPrettyDuration( getDuration( date1, date2 ) ) 80 def getPrettyDuration(date1, date2) { 81 return getPrettyDuration(getDuration(date1, date2)) 72 82 } 73 74 83 75 84 def getDurationString() { … … 78 87 } 79 88 89 def getShortDuration() { 90 def d = getDuration() 91 def days = d.days 92 def hours = d.hours - (24 * days) 93 def minutes = d.minutes - (24 * 60 * days) - (60 * hours) 94 return "${days}d ${hours}:${minutes}" 95 } 80 96 81 97 def isSamplingEvent() { 82 return ( this instanceof SamplingEvent ) 83 } 84 98 return (this instanceof SamplingEvent) 99 } 85 100 86 101 def String toString() { 87 102 return eventDescription ? eventDescription.name : "" 88 103 } 89 90 104 } -
trunk/grails-app/domain/dbnp/studycapturing/EventDescription.groovy
r190 r209 21 21 22 22 static constraints = { 23 name(nullable: false, blank: false) 24 description(nullable: false, blank: false) 23 25 classification(nullable: true, blank: true) 26 protocol(nullable: true, blank: true) 27 } 28 29 def String toString() { 30 return name 24 31 } 25 32 } -
trunk/grails-app/domain/dbnp/studycapturing/EventGroup.groovy
r208 r209 1 1 package dbnp.studycapturing 2 2 3 class EventGroup { 4 3 /** 4 * EventGroup groups events 5 * 6 * Revision information: 7 * $Rev$ 8 * $Author$ 9 * $Date$ 10 */ 11 class EventGroup implements Serializable { 5 12 String name 6 13 7 14 static hasMany = [ 8 subjects: Subject,9 events 15 subjects: Subject, 16 events: Event 10 17 ] 11 18 12 13 19 static constraints = { 20 } 14 21 } -
trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy
r189 r209 185 185 def description = attrs.remove('description') 186 186 def addExampleElement = attrs.remove('addExampleElement') 187 def addExample2Element = attrs.remove('addExample2Element') 187 188 188 189 // render a form element … … 208 209 } 209 210 211 // add an disabled input box for feedback purposes 212 // @see dateElement(...) 213 if (addExample2Element) { 214 def exampleAttrs = new LinkedHashMap() 215 exampleAttrs.name = attrs.get('name')+'Example2' 216 exampleAttrs.class = 'isExample' 217 exampleAttrs.disabled = 'disabled' 218 exampleAttrs.size = 30 219 out << textField(exampleAttrs) 220 } 221 210 222 out << ' </div>' 211 223 … … 243 255 } 244 256 257 258 /** 259 * render a select form element 260 * @param Map attrs 261 * @param Closure body (help text) 262 */ 263 def selectElement = { attrs, body -> 264 baseElement.call( 265 'select', 266 attrs, 267 body 268 ) 269 } 270 271 /** 272 * render a checkBox form element 273 * @param Map attrs 274 * @param Closure body (help text) 275 */ 276 def checkBoxElement = { attrs, body -> 277 baseElement.call( 278 'checkBox', 279 attrs, 280 body 281 ) 282 } 283 245 284 /** 246 285 * render a dateElement … … 257 296 258 297 // set some textfield values 259 attrs.maxlength = 10298 attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10 260 299 attrs.addExampleElement = true 261 300 … … 267 306 ) 268 307 } 308 309 /** 310 * render a dateElement 311 * NOTE: datepicker is attached through wizard.js! 312 * @param Map attrs 313 * @param Closure body (help text) 314 */ 315 def timeElement = { attrs, body -> 316 // transform value? 317 if (attrs.value instanceof Date) { 318 // transform date instance to formatted string (dd/mm/yyyy) 319 attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value) 320 } 321 322 attrs.addExampleElement = true 323 attrs.addExample2Element = true 324 attrs.maxlength = 16 325 326 // render a normal text field 327 //out << textFieldElement(attrs,body) 328 textFieldElement.call( 329 attrs, 330 body 331 ) 332 } 269 333 270 334 /** … … 283 347 284 348 /** 349 * Button form element 350 * @param Map attributes 351 * @param Closure help content 352 */ 353 def buttonElement = { attrs, body -> 354 // render template element 355 baseElement.call( 356 'ajaxButton', 357 attrs, 358 body 359 ) 360 } 361 362 /** 285 363 * render a species select element 286 364 * @param Map attrs … … 289 367 // fetch the speciesOntology 290 368 // note that this is a bit nasty, probably the ontologyName should 291 // be configured in a configuration file... --> TODO 369 // be configured in a configuration file... --> TODO: centralize species configuration 292 370 def speciesOntology = Ontology.findByName('NCBI Taxonomy') 293 371 … … 331 409 } 332 410 411 out << select(attrs) 412 } 413 414 /** 415 * Term form element 416 * @param Map attributes 417 * @param Closure help content 418 */ 419 def termElement = { attrs, body -> 420 // render term element 421 baseElement.call( 422 'termSelect', 423 attrs, 424 body 425 ) 426 } 427 428 /** 429 * render a term select element 430 * @param Map attrs 431 */ 432 def termSelect = { attrs -> 433 // fetch all terms 434 attrs.from = Term.findAll() // for now, all terms as we cannot identify terms as being treatment terms... 435 436 // got a name? 437 if (!attrs.name) { 438 attrs.name = 'term' 439 } 440 333 441 out << select(attrs) 334 442 } -
trunk/grails-app/views/wizard/index.gsp
r172 r209 24 24 <script type="text/javascript" src="${resource(dir: 'js', file: 'grouping.js')}"></script> 25 25 <script type="text/javascript" src="${resource(dir: 'js', file: 'table-editor.js')}"></script> 26 <script type="text/javascript" src="${resource(dir: 'js', file: 'timepicker-0.2.1.js')}"></script> 26 27 <script type="text/javascript" src="${resource(dir: 'js', file: 'wizard.js')}"></script> 27 28 </head> -
trunk/grails-app/views/wizard/pages/_events.gsp
r195 r209 16 16 %> 17 17 <wizard:pageContent> 18 todo... 18 <wizard:selectElement name="eventDescription" description="Event Description" error="eventDescription" from="${eventDescriptions}" value="${eventDescription}"> 19 The event description for this event 20 </wizard:selectElement> 21 <wizard:timeElement name="startTime" description="Start Time" error="startTime" value="${startTime}"> 22 The start time of the study 23 </wizard:timeElement> 24 <wizard:timeElement name="endTime" description="End time" error="endTimee" value="${endTime}"> 25 The end time of the study 26 </wizard:timeElement> 27 <wizard:buttonElement name="add" value="Add" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()"/> 28 <g:if test="${events}"> 29 <div class="table"> 30 <div class="header"> 31 <div class="firstColumn">#</div> 32 <div class="column">eventDescription</div> 33 <div class="column">startTime</div> 34 <div class="column">endTime</div> 35 <div class="column">duration</div> 36 <g:if test="${eventGroups}"><g:each var="eventGroup" status="i" in="${eventGroups}"> 37 <div class="column"><g:textField name="eventGroup_${i}_name" value="${eventGroup.name}" /></div> 38 </g:each></g:if> 39 <div class="column"><wizard:ajaxButton name="addEventGroup" value="+" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()" /></div> 40 </div> 41 <g:each var="event" status="i" in="${events}"> 42 <div class="row"> 43 <div class="firstColumn">${i+1}</div> 44 <div class="column">${event.eventDescription}</div> 45 <div class="column"><g:formatDate format="dd/MM/yyyy hh:mm" date="${event.startTime}" /></div> 46 <div class="column"><g:formatDate format="dd/MM/yyyy hh:mm" date="${event.endTime}" /></div> 47 <div class="column">${event.getShortDuration()}</div> 48 <g:if test="${eventGroups}"><g:each var="eventGroup" status="j" in="${eventGroups}"> 49 <div class="column"><input type="checkbox" name="event_${i}_group_${j}"/></div> 50 </g:each></g:if> 51 <div class="column"></div> 52 </div> 53 </g:each> 54 </div> 55 </g:if> 19 56 </wizard:pageContent> -
trunk/grails-app/views/wizard/pages/_subjects.gsp
r180 r209 16 16 %> 17 17 <wizard:pageContent> 18 <wizard:ajaxButton name="add" value="Add" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()" />19 <input name="addNumber" size="4" maxlength="4" value="1">20 subjects of species21 <wizard:speciesSelect name="addSpecies" />18 <wizard:ajaxButton name="add" value="Add" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()" /> 19 <input name="addNumber" size="4" maxlength="4" value="1"> 20 subjects of species 21 <wizard:speciesSelect name="addSpecies" /> 22 22 <g:if test="${subjects}"> 23 <div class="table"> 24 <div class="header"> 25 <div class="firstColumn">#</div> 26 <div class="column">name</div> 27 <div class="column">species</div> 28 <wizard:templateColumnHeaders template="${study.template}" class="column" /> 23 <div class="table"> 24 <div class="header"> 25 <div class="firstColumn">#</div> 26 <div class="column">name</div> 27 <div class="column">species</div> 28 <wizard:templateColumnHeaders template="${study.template}" class="column" /> 29 </div> 30 <g:each var="subject" status="i" in="${subjects}"> 31 <div class="row"> 32 <div class="firstColumn">${i}</div> 33 <div class="column"><g:textField name="subject_${i}_name" value="${subject.name}" size="12" maxlength="12" /></div> 34 <div class="column"> 35 <wizard:speciesSelect value="${subject.species}" name="subject_${i}_species" /> 36 </div> 37 <wizard:templateColumns id="${i}" template="${study.template}" name="subject_${i}" class="column" subject="${subject}" /> 38 </div> 39 </g:each> 29 40 </div> 30 <g:each var="subject" status="i" in="${subjects}"> 31 <div class="row"> 32 <div class="firstColumn">${i}</div> 33 <div class="column"><g:textField name="subject_${i}_name" value="${subject.name}" size="12" maxlength="12" /></div> 34 <div class="column"> 35 <wizard:speciesSelect value="${subject.species}" name="subject_${i}_species" /> 36 </div> 37 <wizard:templateColumns id="${i}" template="${study.template}" name="subject_${i}" class="column" subject="${subject}" /> 41 <div class="sliderContainer"> 42 <div class="slider"/> 38 43 </div> 39 </g:each>40 </div>41 <div class="sliderContainer">42 <div class="slider"/>43 </div>44 44 </g:if> 45 45 </wizard:pageContent> -
trunk/web-app/css/wizard.css
r204 r209 140 140 font-size: 11px; 141 141 overflow: hidden; 142 white-space: nowrap; 142 143 } 143 144 -
trunk/web-app/js/table-editor.js
r172 r209 54 54 $(this.columnIdentifier, $(row)).each(function() { 55 55 var input = $(':input', $(this)); 56 57 56 // does this column contain an input field 57 console.log(input) 58 58 if (input) { 59 59 var type = $(input).attr('type'); 60 console.log(input) 61 console.log('type: '+type) 60 62 61 63 switch (type) { … … 73 75 that.updateSingleInputElements(input, columnNumber, 'select'); 74 76 }) 77 break; 78 case 'hidden': 79 // hidden is hidden :) 75 80 break; 76 81 case null: -
trunk/web-app/js/wizard.js
r204 r209 21 21 var wizard = $('div#wizard') 22 22 if (wizard.find("#warning").length == 0) { 23 wizard.html('<span id="warning" style="color:red;font-size:8px;">Firefox 3.6 contains <a href="http://code.google.com/p/fbug/issues/detail?id=2746" target="_new">a bug</a> in combination with Firebug\'s XMLHttpRequest spy which causes the wizard to not function anymore. Please make sure you have firebug\'s XMLHttpRequest spy disabled useuse Firefox 3.5.7 instead...</span>' + wizard.html())23 wizard.html('<span id="warning" style="color:red;font-size:8px;">Firefox 3.6 contains <a href="http://code.google.com/p/fbug/issues/detail?id=2746" target="_new">a bug</a> in combination with Firebug\'s XMLHttpRequest spy which causes the wizard to not function anymore. Please make sure you have Firebug\'s XMLHttpRequest spy disabled or use Firefox 3.5.7 instead...</span>' + wizard.html()) 24 24 } 25 25 } … … 35 35 attachHelpTooltips(); 36 36 attachDatePickers(); 37 attachDateTimePickers(); 37 38 38 39 // SUBJECT PAGE … … 126 127 } 127 128 129 // add datetimepickers to date fields 130 function attachDateTimePickers() { 131 $('div#wizard').find("input[type=text][name$='Time']").each(function() { 132 $(this).datepicker({ 133 dateFormat : 'dd/mm/yy', 134 altField : '#' + $(this).attr('name') + 'Example', 135 altTimeField : '#' + $(this).attr('name') + 'Example2', 136 altFormat : 'DD, d MM, yy', 137 showTime : true, 138 time24h : true 139 }); 140 }) 141 } 142 128 143 // attach subject events 129 144 function attachTableEvents() {
Note: See TracChangeset
for help on using the changeset viewer.