source: trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy @ 209

Last change on this file since 209 was 209, checked in by duh, 12 years ago
  • added events, eventDescriptions, etc
  • Property svn:keywords set to
    Date
    Author
    Rev
File size: 13.9 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.studycapturing.*
4import dbnp.data.*
5import grails.converters.*
6
7/**
8 * Wizard Controler
9 *
10 * The wizard controller handles the handeling of pages and data flow
11 * through the study capturing wizard.
12 *
13 * @author Jeroen Wesbeek
14 * @since 20100107
15 * @package studycapturing
16 *
17 * Revision information:
18 * $Rev: 209 $
19 * $Author: duh $
20 * $Date: 2010-02-22 17:35:37 +0000 (ma, 22 feb 2010) $
21 */
22class WizardController {
23        /**
24         * index method, redirect to the webflow
25         * @void
26         */
27        def index = {
28                /**
29                 * Do you believe it in your head?
30                 * I can go with the flow
31                 * Don't say it doesn't matter (with the flow) matter anymore
32                 * I can go with the flow (I can go)
33                 * Do you believe it in your head?
34                 */
35                redirect(action: 'pages')
36        }
37
38        /**
39         * WebFlow definition
40         * @see http://grails.org/WebFlow
41         * @void
42         */
43        def pagesFlow = {
44                // start the flow
45                onStart {
46                        // define flow variables
47                        flow.page = 0
48                        flow.pages = [
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
59                        ]
60
61                }
62
63                // render the main wizard page which immediately
64                // triggers the 'next' action (hence, the main
65                // page dynamically renders the study template
66                // and makes the flow jump to the study logic)
67                mainPage {
68                        render(view: "/wizard/index")
69                        onRender {
70                                flow.page = 1
71                        }
72                        on("next").to "templates"
73                }
74
75                // select the templates to use for this study
76                templates {
77                        render(view: "_templates")
78                        onRender {
79                                flow.page = 1
80                        }
81                        on("next") {
82                                // if we don't have a study, instantiate a study with dummy values
83                                if (!flow.study) {
84                                        flow.study = new Study(
85                                                title: "my study",
86                                                code: "",
87                                                ecCode: "",
88                                                researchQuestion: "",
89                                                description: "",
90                                                startDate: new Date()
91                                        )
92                                }
93
94                                // assign template to study
95                                flow.study.template = Template.findByName(params.get('template'));
96
97                                // validate study
98                                if (flow.study.validate()) {
99                                        success()
100                                } else {
101                                        // validation failed, feedback errors
102                                        flash.errors = new LinkedHashMap()
103                                        this.appendErrors(flow.study, flash.errors)
104                                        error()
105                                }
106                        }.to "study"
107                }
108
109                // render and handle the study page
110                study {
111                        render(view: "_study")
112                        onRender {
113                                flow.page = 2
114                        }
115                        on("previous") {
116                                flash.errors = new LinkedHashMap()
117
118                                if (this.handleStudy(flow, flash, params)) {
119                                        success()
120                                } else {
121                                        error()
122                                }
123                        }.to "templates"
124                        on("next") {
125                                flash.errors = new LinkedHashMap()
126
127                                if (this.handleStudy(flow, flash, params)) {
128                                        success()
129                                } else {
130                                        error()
131                                }
132                        }.to "subjects"
133                }
134
135                // render and handle subjects page
136                subjects {
137                        render(view: "_subjects")
138                        onRender {
139                                flow.page = 3
140
141                                if (!flow.subjects) {
142                                        flow.subjects = []
143                                }
144                        }
145                        on("add") {
146                                // fetch species by name (as posted by the form)
147                                def speciesTerm = Term.findByName(params.addSpecies)
148
149                                // add x subject of species y
150                                (params.addNumber as int).times {
151                                        def increment = flow.subjects.size()
152                                        flow.subjects[increment] = new Subject(
153                                                name: 'Subject ' + (increment + 1),
154                                                species: speciesTerm,
155                                                template: flow.study.template
156                                        )
157                                }
158                        }.to "subjects"
159                        on("next") {
160                                flash.errors = new LinkedHashMap()
161
162                                // check if we have at least one subject
163                                // and check form data
164                                if (flow.subjects.size() < 1) {
165                                        // append error map
166                                        this.appendErrorMap(['subjects': 'You need at least to create one subject for your study'], flash.errors)
167                                        error()
168                                } else if (!this.handleSubjects(flow, flash, params)) {
169                                        error()
170                                } else {
171                                        success()
172                                }
173                        }.to "eventDescriptions"
174                        on("previous") {
175                                flash.errors = new LinkedHashMap()
176
177                                // handle form data
178                                if (!this.handleSubjects(flow, flash, params)) {
179                                        error()
180                                } else {
181                                        success()
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"
304                }
305
306                // render and handle group page
307                groups {
308                        render(view: "_groups")
309                        onRender {
310                                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"
323                        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
336                        }.to "events"
337                        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"
368                }
369
370                // render page three
371                done {
372                        render(view: "_done")
373                        onRender {
374                                flow.page = 10
375                        }
376                        on("previous") {
377                                // TODO
378                        }.to "assays"
379                }
380        }
381
382        /**
383         * re-usable code for handling study form data in a web flow
384         * @param Map LocalAttributeMap (the flow scope)
385         * @param Map localAttributeMap (the flash scope)
386         * @param Map GrailsParameterMap (the flow parameters = form data)
387         * @returns boolean
388         */
389        def handleStudy(flow, flash, params) {
390                // create study instance if we have none
391                if (!flow.study) flow.study = new Study();
392
393                // create date instance from date string?
394                // @see WizardTagLibrary::dateElement{...}
395                if (params.get('startDate')) {
396                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
397                }
398
399                // if a template is selected, get template instance
400                if (params.get('template')) {
401                        params.template = Template.findByName(params.get('template'))
402                }
403
404                // update study instance with parameters
405                params.each() {key, value ->
406                        if (flow.study.hasProperty(key)) {
407                                flow.study.setProperty(key, value);
408                        }
409                }
410
411                // validate study
412                if (flow.study.validate()) {
413                        return true
414                } else {
415                        // validation failed, feedback errors
416                        flash.errors = new LinkedHashMap()
417                        this.appendErrors(flow.study, flash.errors)
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++
448                }
449        }
450
451        /**
452         * re-usable code for handling subject form data in a web flow
453         * @param Map LocalAttributeMap (the flow scope)
454         * @param Map localAttributeMap (the flash scope)
455         * @param Map GrailsParameterMap (the flow parameters = form data)
456         * @returns boolean
457         */
458        def handleSubjects(flow, flash, params) {
459                def names = new LinkedHashMap();
460                def errors = false;
461                def id = 0;
462
463                // iterate through subjects
464                flow.subjects.each() {
465                        // store subject properties
466                        def name = params.get('subject_' + id + '_name')
467                        it.name = params.get('subject_' + id + '_name')
468                        it.species = Term.findByName(params.get('subject_' + id + '_species'))
469
470                        // remember name and check for duplicates
471                        if (!names[it.name]) {
472                                names[it.name] = [count: 1, first: 'subject_' + id + '_name']
473                        } else {
474                                // duplicate name found, set error flag
475                                names[it.name]['count']++
476
477                                // second occurence?
478                                if (names[it.name]['count'] == 2) {
479                                        // yeah, also mention the first
480                                        // occurrence in the error message
481                                        this.appendErrorMap([[names[it.name]['first']]: 'The subject name needs to be unique!'], flash.errors)
482                                }
483
484                                // add to error map
485                                this.appendErrorMap([['subject_' + id + '_name']: 'The subject name needs to be unique!'], flash.errors)
486                                errors = true
487                        }
488
489                        // clear lists
490                        def stringList = new LinkedHashMap();
491                        def intList = new LinkedHashMap();
492                        def floatList = new LinkedHashMap();
493                        def termList = new LinkedHashMap();
494
495                        // get all template fields
496                        flow.study.template.subjectFields.each() {
497                                // valid type?
498                                if (!it.type) throw new NoSuchFieldException("Field name ${fieldName} not recognized")
499
500                                // get value
501                                def value = params.get('subject_' + id + '_' + it.name);
502                                if (value) {
503                                        // add to template parameters
504                                        switch (it.type) {
505                                                case 'STRINGLIST':
506                                                        stringList[it.name] = value
507                                                        break;
508                                                case 'INTEGER':
509                                                        intList[it.name] = value
510                                                        break;
511                                                case 'FLOAT':
512                                                        floatList[it.name] = value
513                                                        break;
514                                                default:
515                                                        // unsupported type?
516                                                        throw new NoSuchFieldException("Field type ${it.type} not recognized")
517                                                        break;
518                                        }
519                                }
520                        }
521
522                        // set field data
523                        it.templateStringFields = stringList
524                        it.templateIntegerFields = intList
525                        it.templateFloatFields = floatList
526                        it.templateTermFields = termList
527
528                        // validate subject
529                        if (!it.validate()) {
530                                errors = true
531                                println id + ' :: ' + it.errors.getAllErrors()
532                                this.appendErrors(it, flash.errors)
533                        }
534
535                        id++;
536                }
537
538                return !errors
539        }
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
559        /**
560         * transform domain class validation errors into a human readable
561         * linked hash map
562         * @param object validated domain class
563         * @returns object  linkedHashMap
564         */
565        def getHumanReadableErrors(object) {
566                def errors = new LinkedHashMap()
567
568                object.errors.getAllErrors().each() {
569                        errors[it.getArguments()[0]] = it.getDefaultMessage()
570                }
571
572                return errors
573        }
574
575        /**
576         * append errors of a particular object to a map
577         * @param object
578         * @param map linkedHashMap
579         * @void
580         */
581        def appendErrors(object, map) {
582                this.appendErrorMap(this.getHumanReadableErrors(object), map)
583        }
584
585        /**
586         * append errors of one map to another map
587         * @param map linkedHashMap
588         * @param map linkedHashMap
589         * @void
590         */
591        def appendErrorMap(map, mapToExtend) {
592                map.each() {key, value ->
593                        mapToExtend[key] = value
594                }
595        }
596}
Note: See TracBrowser for help on using the repository browser.