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

Last change on this file since 247 was 247, checked in by duh, 9 years ago
  • set keyword expansion
  • Property svn:keywords set to Author Rev Date
File size: 17.0 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: 247 $
19 * $Author: duh $
20 * $Date: 2010-03-08 17:29:14 +0000 (ma, 08 mrt 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: 'Start'],                               // load or create a study
51                                [title: 'Study'],                               // study
52                                [title: 'Subjects'],                    // subjects
53                                [title: 'Event Descriptions'],  // event descriptions
54                                [title: 'Events'],                              // events and event grouping
55                                [title: 'Confirmation'],                // confirmation page
56                                [title: 'Done']                                 // finish page
57                        ]
58
59                }
60
61                // render the main wizard page which immediately
62                // triggers the 'next' action (hence, the main
63                // page dynamically renders the study template
64                // and makes the flow jump to the study logic)
65                mainPage {
66                        render(view: "/wizard/index")
67                        onRender {
68                                flow.page = 1
69                        }
70                        on("next").to "start"
71                }
72
73                // create or modify a study
74                start {
75                        render(view: "_start")
76                        onRender {
77                                flow.page = 1
78                        }
79                        on("next") {
80
81                        }.to "study"
82                }
83
84                // render and handle the study page
85                // TODO: make sure both template as well as logic will
86                //       handle Study templates as well!!!
87                study {
88                        render(view: "_study")
89                        onRender {
90                                flow.page = 2
91                        }
92                        on("switchTemplate") {
93                                this.handleStudy(flow, flash, params)
94                        }.to "study"
95                        on("previous") {
96                                flash.errors = new LinkedHashMap()
97
98                                if (this.handleStudy(flow, flash, params)) {
99                                        success()
100                                } else {
101                                        error()
102                                }
103                        }.to "start"
104                        on("next") {
105                                flash.errors = new LinkedHashMap()
106
107                                if (this.handleStudy(flow, flash, params)) {
108                                        success()
109                                } else {
110                                        error()
111                                }
112                        }.to "subjects"
113                }
114
115                // render and handle subjects page
116                subjects {
117                        render(view: "_subjects")
118                        onRender {
119                                flow.page = 3
120
121                                if (!flow.subjects) {
122                                        flow.subjects = []
123                                        flow.subjectTemplates = new LinkedHashMap()
124                                }
125                        }
126                        on("add") {
127                                // fetch species by name (as posted by the form)
128                                def speciesTerm = Term.findByName(params.addSpecies)
129                                def subjectTemplateName = params.get('template')
130                                def subjectTemplate     = Template.findByName(subjectTemplateName)
131
132                                // add this subject template to the subject template array
133                                if (!flow.subjectTemplates[ subjectTemplateName ]) {
134                                        flow.subjectTemplates[ subjectTemplateName ] = [
135                                                name: subjectTemplateName,
136                                                template: subjectTemplate,
137                                                subjects: []
138                                        ]
139                                }
140
141                                // add x subject of species y
142                                (params.addNumber as int).times {
143                                        def increment = flow.subjects.size()
144                                        def subject = new Subject(
145                                                name: 'Subject ' + (increment + 1),
146                                                species: speciesTerm,
147                                                template: subjectTemplate
148                                        )
149
150                                        // instantiate a new Subject
151                                        flow.subjects[ increment ] = subject
152
153                                        // and remember the subject id with the template
154                                        def subjectsSize = flow.subjectTemplates[ subjectTemplateName ]['subjects'].size()
155                                        flow.subjectTemplates[ subjectTemplateName ]['subjects'][ subjectsSize ] = increment
156                                }
157                        }.to "subjects"
158                        on("next") {
159                                flash.errors = new LinkedHashMap()
160
161                                // check if we have at least one subject
162                                // and check form data
163                                if (flow.subjects.size() < 1) {
164                                        // append error map
165                                        this.appendErrorMap(['subjects': 'You need at least to create one subject for your study'], flash.errors)
166                                        error()
167                                } else if (!this.handleSubjects(flow, flash, params)) {
168                                        error()
169                                } else {
170                                        success()
171                                }
172                        }.to "eventDescriptions"
173                        on("previous") {
174                                flash.errors = new LinkedHashMap()
175
176                                // handle form data
177                                if (!this.handleSubjects(flow, flash, params)) {
178                                        error()
179                                } else {
180                                        success()
181                                }
182                        }.to "study"
183                }
184
185                // render page three
186                eventDescriptions {
187                        render(view: "_eventDescriptions")
188                        onRender {
189                                flow.page = 4
190
191                                if (!flow.eventDescriptions) {
192                                        flow.eventDescriptions = []
193                                }
194                        }
195                        on("add") {
196                                // fetch classification by name (as posted by the form)
197                                //params.classification = Term.findByName(params.classification)
198
199                                // fetch protocol by name (as posted by the form)
200                                params.protocol = Protocol.findByName(params.protocol)
201
202                                // transform checkbox form value to boolean
203                                params.isSamplingEvent = (params.containsKey('isSamplingEvent'))
204
205                                // instantiate EventDescription with parameters
206                                def eventDescription = new EventDescription(params)
207
208                                // validate
209                                if (eventDescription.validate()) {
210                                        def increment = flow.eventDescriptions.size()
211                                        flow.eventDescriptions[increment] = eventDescription
212                                        success()
213                                } else {
214                                        // validation failed, feedback errors
215                                        flash.errors = new LinkedHashMap()
216                                        flash.values = params
217                                        this.appendErrors(eventDescription, flash.errors)
218                                        error()
219                                }
220                        }.to "eventDescriptions"
221                        on("delete") {
222                                def delete = params.get('do') as int;
223
224                                // handle form data
225                                if (!this.handleEventDescriptions(flow, flash, params)) {
226                                        flash.values = params
227                                        error()
228                                } else {
229                                        success()
230                                }
231
232                                // remove eventDescription
233                                if (flow.eventDescriptions[ delete ] && flow.eventDescriptions[ delete ] instanceof EventDescription) {
234                                        // remove all events based on this eventDescription
235                                        for ( i in flow.events.size()..0 ) {
236                                                if (flow.events[ i ] && flow.events[ i ].eventDescription == flow.eventDescriptions[ delete ]) {
237                                                        flow.events.remove(i)
238                                                }
239                                        }
240
241                                        flow.eventDescriptions.remove(delete)
242                                }
243                        }.to "eventDescriptions"
244                        on("previous") {
245                                flash.errors = new LinkedHashMap()
246
247                                // handle form data
248                                if (!this.handleEventDescriptions(flow, flash, params)) {
249                                        flash.values = params
250                                        error()
251                                } else {
252                                        success()
253                                }
254                        }.to "subjects"
255                        on("next") {
256                                flash.errors = new LinkedHashMap()
257
258                                // check if we have at least one subject
259                                // and check form data
260                                if (flow.eventDescriptions.size() < 1) {
261                                        // append error map
262                                        flash.values = params
263                                        this.appendErrorMap(['eventDescriptions': 'You need at least to create one eventDescription for your study'], flash.errors)
264                                        error()
265                                } else if (!this.handleEventDescriptions(flow, flash, params)) {
266                                        flash.values = params
267                                        error()
268                                } else {
269                                        success()
270                                }
271                        }.to "events"
272                }
273
274                // render events page
275                events {
276                        render(view: "_events")
277                        onRender {
278                                flow.page = 5
279
280                                if (!flow.events) {
281                                        flow.events = []
282                                }
283
284                                if (!flow.eventGroups) {
285                                        flow.eventGroups = []
286                                        flow.eventGroups[0] = new EventGroup(name: 'Group 1')   // 1 group by default
287                                }
288                        }
289                        on("add") {
290                                // create date instances from date string?
291                                // @see WizardTagLibrary::timeElement{...}
292                                if (params.get('startTime')) {
293                                        params.startTime = new Date().parse("d/M/yyyy HH:mm", params.get('startTime').toString())
294                                }
295                                if (params.get('endTime')) {
296                                        params.get('endTime').toString()
297                                        params.endTime = new Date().parse("d/M/yyyy HH:mm", params.get('endTime').toString())
298                                }
299
300                                // get eventDescription instance by name
301                                params.eventDescription = this.getObjectByName(params.get('eventDescription'), flow.eventDescriptions)
302
303                                // instantiate Event with parameters
304                                def event = new Event(params)
305
306                                // handle event groupings
307                                this.handleEventGrouping(flow, flash, params)
308
309                                // validate event
310                                if (event.validate()) {
311                                        def increment = flow.events.size()
312                                        flow.events[increment] = event
313                                        success()
314                                } else {
315                                        // validation failed, feedback errors
316                                        flash.errors = new LinkedHashMap()
317                                        flash.values = params
318                                        this.appendErrors(event, flash.errors)
319
320                                        flash.startTime = params.startTime
321                                        flash.endTime = params.endTime
322                                        flash.eventDescription = params.eventDescription
323
324                                        error()
325                                }
326                        }.to "events"
327                        on("deleteEvent") {
328                                flash.values = params
329                                def delete = params.get('do') as int;
330
331                                // handle event groupings
332                                this.handleEventGrouping(flow, flash, params)
333
334                                // remove event
335                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
336                                        flow.events.remove(delete)
337                                }
338                        }.to "events"
339                        on("addEventGroup") {
340                                flash.values = params
341                               
342                                // handle event groupings
343                                this.handleEventGrouping(flow, flash, params)
344
345                                def increment = flow.eventGroups.size()
346                                def groupName = "Group " + (increment + 1)
347
348                                // check if group name exists
349                                def nameExists = true
350                                def u = 0
351
352                                // make sure a unique name is generated
353                                while (nameExists) {
354                                        u++
355                                        def count = 0
356                                       
357                                        flow.eventGroups.each() {
358                                                if (it.name == groupName) {
359                                                        groupName = "Group " + (increment + 1) + "," + u
360                                                } else {
361                                                        count++
362                                                }
363                                        }
364
365                                        nameExists = !(count == flow.eventGroups.size())
366                                }
367
368                                flow.eventGroups[increment] = new EventGroup(name: groupName)
369                        }.to "events"
370                        on("deleteEventGroup") {
371                                flash.values = params
372                               
373                                def delete = params.get('do') as int;
374
375                                // handle event groupings
376                                this.handleEventGrouping(flow, flash, params)
377
378                                // remove the group with this specific id
379                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
380                                        // remove this eventGroup
381                                        flow.eventGroups.remove(delete)
382                                }
383                        }.to "events"
384                        on("previous") {
385                                // handle event groupings
386                                this.handleEventGrouping(flow, flash, params)
387                        }.to "eventDescriptions"
388                        on("next") {
389                                flash.values = params
390                               
391                                flash.errors = new LinkedHashMap()
392
393                                // handle event groupings
394                                this.handleEventGrouping(flow, flash, params)
395
396                                // check if we have at least one subject
397                                // and check form data
398                                if (flow.events.size() < 1) {
399                                        // append error map
400                                        flash.values = params
401                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors)
402                                        error()
403                                }
404                        }.to "events"
405                }
406
407                confirm {
408                        render(view: "_confirmation")
409                        onRender {
410                                flow.page = 6
411                        }
412                        on("previous") {
413                                // do nothing
414                        }.to "events"
415                        on("next") {
416                                // store everything in the database!
417                                success()
418                        }.to "confirm"
419                }
420
421                // render page three
422                done {
423                        render(view: "_done")
424                        onRender {
425                                flow.page = 6
426                        }
427                        on("previous") {
428                                // TODO
429                        }.to "confirm"
430                }
431        }
432
433        /**
434         * re-usable code for handling study form data in a web flow
435         * @param Map LocalAttributeMap (the flow scope)
436         * @param Map localAttributeMap (the flash scope)
437         * @param Map GrailsParameterMap (the flow parameters = form data)
438         * @returns boolean
439         */
440        def handleStudy(flow, flash, params) {
441                // create study instance if we have none
442                if (!flow.study) flow.study = new Study();
443
444                // create date instance from date string?
445                // @see WizardTagLibrary::dateElement{...}
446                if (params.get('startDate')) {
447                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
448                } else {
449                        params.remove('startDate')
450                }
451
452                // if a template is selected, get template instance
453                def template = params.remove('template')
454                if (template instanceof String && template.size() > 0) {
455                        params.template = Template.findByName(template)
456                } else if (template instanceof Template) {
457                        params.template = template
458                }
459
460                // update study instance with parameters
461                params.each() {key, value ->
462                        if (flow.study.hasProperty(key)) {
463                                flow.study.setProperty(key, value);
464                        }
465                }
466
467                // validate study
468                if (flow.study.validate()) {
469                        return true
470                } else {
471                        // validation failed, feedback errors
472                        flash.errors = new LinkedHashMap()
473                        this.appendErrors(flow.study, flash.errors)
474                        return false
475                }
476        }
477
478        /**
479         * re-usable code for handling eventDescription form data in a web flow
480         * @param Map LocalAttributeMap (the flow scope)
481         * @param Map localAttributeMap (the flash scope)
482         * @param Map GrailsParameterMap (the flow parameters = form data)
483         * @returns boolean
484         */
485        def handleEventDescriptions(flow, flash, params) {
486                def names = new LinkedHashMap()
487                def errors = false
488                def id = 0
489
490                flow.eventDescriptions.each() {
491                        it.name = params.get('eventDescription_' + id + '_name')
492                        it.description = params.get('eventDescription_' + id + '_description')
493                        it.protocol = Protocol.findByName(params.get('eventDescription_' + id + '_protocol'))
494                        //it.classification = Term.findByName(params.get('eventDescription_' + id + '_classification'))
495                        it.isSamplingEvent = (params.containsKey('eventDescription_' + id + '_isSamplingEvent'))
496
497                        // validate eventDescription
498                        if (!it.validate()) {
499                                errors = true
500                                this.appendErrors(it, flash.errors, 'eventDescription_' + id + '_')
501                        }
502
503                        id++
504                }
505
506                return !errors
507        }
508
509        /**
510         * re-usable code for handling event grouping in a web flow
511         * @param Map LocalAttributeMap (the flow scope)
512         * @param Map localAttributeMap (the flash scope)
513         * @param Map GrailsParameterMap (the flow parameters = form data)
514         * @returns boolean
515         */
516        def handleEventGrouping(flow, flash, params) {
517                // walk through eventGroups
518                def g = 0
519                flow.eventGroups.each() {
520                        def e = 0
521                        def eventGroup = it
522
523                        // reset events
524                        eventGroup.events = new HashSet()
525
526                        // walk through events
527                        flow.events.each() {
528                                if (params.get('event_' + e + '_group_' + g) == 'on') {
529                                        eventGroup.addToEvents(it)
530                                }
531                                e++
532                        }
533                        g++
534                }
535        }
536
537        /**
538         * re-usable code for handling subject form data in a web flow
539         * @param Map LocalAttributeMap (the flow scope)
540         * @param Map localAttributeMap (the flash scope)
541         * @param Map GrailsParameterMap (the flow parameters = form data)
542         * @returns boolean
543         */
544        def handleSubjects(flow, flash, params) {
545                def names = new LinkedHashMap();
546                def errors = false;
547                def id = 0;
548
549                // iterate through subject templates
550                flow.subjectTemplates.each() {
551                        def subjectTemplate = it.getValue().template
552                        def templateFields      = subjectTemplate.fields
553
554                        // iterate through subjects
555                        it.getValue().subjects.each() { subjectId ->
556                                flow.subjects[ subjectId ].name = params.get('subject_' + subjectId + '_name')
557                                flow.subjects[ subjectId ].species = Term.findByName(params.get('subject_' + subjectId + '_species'))
558
559                                // remember name and check for duplicates
560                                if (!names[ flow.subjects[ subjectId ].name ]) {
561                                        names[ flow.subjects[ subjectId ].name ] = [count: 1, first: 'subject_' + subjectId + '_name', firstId: subjectId]
562                                } else {
563                                        // duplicate name found, set error flag
564                                        names[ flow.subjects[ subjectId ].name ]['count']++
565
566                                        // second occurence?
567                                        if (names[ flow.subjects[ subjectId ].name ]['count'] == 2) {
568                                                // yeah, also mention the first
569                                                // occurrence in the error message
570                                                this.appendErrorMap(name: 'The subject name needs to be unique!', flash.errors, 'subject_' + names[ flow.subjects[ subjectId ].name ]['firstId'] + '_')
571                                        }
572
573                                        // add to error map
574                                        this.appendErrorMap([name: 'The subject name needs to be unique!'], flash.errors, 'subject_' + subjectId + '_')
575                                        errors = true
576                                }
577
578                                // iterate through template fields
579                                templateFields.each() { subjectField ->
580                                        def value = params.get('subject_' + subjectId + '_' + subjectField.name)
581
582// TODO: UNCOMMENT THIS         if (value) flow.subjects[ subjectId ].setFieldValue(subjectField.name, value)
583                                }
584
585                                // validate subject
586                                if (!flow.subjects[ subjectId ].validate()) {
587                                        errors = true
588                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors)
589                                }
590                        }
591                }
592
593                return !errors
594        }
595
596        /**
597         * return the object from a map of objects by searching for a name
598         * @param String name
599         * @param Map map of objects
600         * @return Object
601         */
602        def getObjectByName(name, map) {
603                def result = null
604                map.each() {
605                        if (it.name == name) {
606                                result = it
607                        }
608                }
609
610                return result
611        }
612
613        /**
614         * transform domain class validation errors into a human readable
615         * linked hash map
616         * @param object validated domain class
617         * @returns object  linkedHashMap
618         */
619        def getHumanReadableErrors(object) {
620                def errors = new LinkedHashMap()
621
622                object.errors.getAllErrors().each() {
623                        errors[it.getArguments()[0]] = it.getDefaultMessage()
624                }
625
626                return errors
627        }
628
629        /**
630         * append errors of a particular object to a map
631         * @param object
632         * @param map linkedHashMap
633         * @void
634         */
635        def appendErrors(object, map) {
636                this.appendErrorMap(this.getHumanReadableErrors(object), map)
637        }
638
639        def appendErrors(object, map, prepend) {
640                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
641        }
642
643        /**
644         * append errors of one map to another map
645         * @param map linkedHashMap
646         * @param map linkedHashMap
647         * @void
648         */
649        def appendErrorMap(map, mapToExtend) {
650                map.each() {key, value ->
651                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
652                }
653        }
654
655        def appendErrorMap(map, mapToExtend, prepend) {
656                map.each() {key, value ->
657                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
658                }
659        }
660}
Note: See TracBrowser for help on using the repository browser.