root/trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy @ 470

Revision 470, 19.4 KB (checked in by duh, 4 years ago)

-removed some debug lines

  • Property svn:keywords set to Date Author Rev
Line 
1package dbnp.studycapturing
2
3import dbnp.data.*
4
5/**
6 * Wizard Controler
7 *
8 * The wizard controller handles the handeling of pages and data flow
9 * through the study capturing wizard.
10 *
11 * TODO: refactor the 'handle*' methods to work as subflows instead
12 *               of methods outside of the flow
13 *
14 * @author Jeroen Wesbeek
15 * @since 20100107
16 * @package studycapturing
17 *
18 * Revision information:
19 * $Rev$
20 * $Author$
21 * $Date$
22 */
23class WizardController {
24        /**
25         * index method, redirect to the webflow
26         * @void
27         */
28        def index = {
29                /**
30                 * Do you believe it in your head?
31                 * I can go with the flow
32                 * Don't say it doesn't matter (with the flow) matter anymore
33                 * I can go with the flow (I can go)
34                 * Do you believe it in your head?
35                 */
36                redirect(action: 'pages')
37        }
38
39        /**
40         * WebFlow definition
41         * @see http://grails.org/WebFlow
42         * @void
43         */
44        def pagesFlow = {
45                // start the flow
46                onStart {
47                        // define flow variables
48                        flow.page = 0
49                        flow.pages = [
50                                //[title: 'Templates'],                 // templates
51                                [title: 'Start'],                               // load or create a study
52                                [title: 'Study'],                               // study
53                                [title: 'Subjects'],                    // subjects
54                                [title: 'Events'],                              // events and event grouping
55                                [title: 'Groups'],                              // groups
56                                [title: 'Confirmation'],                // confirmation page
57                                [title: 'Done']                                 // finish page
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").to "study"
80                        on("modify").to "modify"
81                }
82
83                // load a study to modify
84                modify {
85                        render(view: "_modify")
86                        onRender {
87                                flow.page = 1
88                                flash.cancel = true
89                        }
90                        on("cancel") {
91                                flow.study = null
92                        }.to "start"
93                        on("next") {
94                                // TODO: loading a study is not yet implemented
95                                //       create a error stating this feature is
96                                //       not yet implemented
97                                flash.errors = [:]
98                                this.appendErrorMap(
99                                        ['study': 'Loading a study and modifying it has not yet been implemented. Please press \'cancel\' to go back to the initial page...'],
100                                        flash.errors
101                                )
102                        }.to "modify"
103                }
104
105                // render and handle the study page
106                // TODO: make sure both template as well as logic will
107                //       handle Study templates as well!!!
108                study {
109                        render(view: "_study")
110                        onRender {
111                                flow.page = 2
112                        }
113                        on("refresh") {
114                                flash.values = params
115
116                                // handle study data
117                                this.handleStudy(flow, flash, params)
118
119                                // remove errors as we don't want any warnings now
120                                flash.errors = [:]                             
121                        }.to "study"
122                        on("switchTemplate") {
123                                flash.values = params
124
125                                // handle study data
126                                this.handleStudy(flow, flash, params)
127
128                                // remove errors as we don't want any warnings now
129                                flash.errors = [:]
130                        }.to "study"
131                        on("previous") {
132                                flash.errors = [:]
133
134                                // handle the study
135                                this.handleStudy(flow, flash, params)
136
137                                // reset errors
138                                flash.errors = [:]
139
140                                success()
141                        }.to "start"
142                        on("next") {
143                                flash.errors = [:]
144
145                                if (this.handleStudy(flow, flash, params)) {
146                                        success()
147                                } else {
148                                        error()
149                                }
150                        }.to "subjects"
151                }
152
153                // render and handle subjects page
154                subjects {
155                        render(view: "_subjects")
156                        onRender {
157                                flow.page = 3
158
159                                if (!flow.subjects) {
160                                        flow.subjects = [:]
161                                        flow.subjectTemplates = [:]
162                                }
163                        }
164                        on("refresh") {
165                                flash.values = params
166                        }.to "subjects"
167                        on("add") {
168                                // handle subjects
169                                this.handleSubjects(flow, flash, params)
170
171                                flash.errors = [:]
172                                flash.values = params
173                                def speciesTerm = Term.findByName(params.species);
174                                def subjectTemplateName = params.get('template');
175                                def subjectTemplate = Template.findByName(subjectTemplateName);
176
177                                // add this subject template to the subject template array
178                                if (!flow.subjectTemplates[ subjectTemplateName ]) {
179                                        flow.subjectTemplates[ subjectTemplateName ] = [
180                                                name: subjectTemplateName,
181                                                template: subjectTemplate,
182                                                subjects: [:]
183                                        ]
184                                }
185
186                                // add x subjects of species y
187                                (params.addNumber as int).times {
188                                        def increment = (flow.subjects.size()) ? (flow.subjects.keySet().max() + 1) : 0
189                                        def subject = new Subject(
190                                                name: 'Subject ' + (increment + 1),
191                                                species: speciesTerm,
192                                                template: subjectTemplate
193                                        )
194
195                                        // instantiate a new Subject
196                                        flow.subjects[ increment ] = subject
197
198                                        // and remember the subject id with the template
199                                        def subjectsSize = (flow.subjectTemplates[ subjectTemplateName ].subjects.size()) ? (flow.subjectTemplates[ subjectTemplateName ].subjects.keySet().max() + 1) : 0
200                                        flow.subjectTemplates[ subjectTemplateName ].subjects[ subjectsSize ] = increment
201                                }
202                        }.to "subjects"
203                        on("next") {
204                                flash.errors = [:]
205
206                                // check if we have at least one subject
207                                // and check form data
208                                if (flow.subjects.size() < 1) {
209                                        // append error map
210                                        this.appendErrorMap(['subjects': 'You need at least to create one subject for your study'], flash.errors)
211                                        error()
212                                } else if (!this.handleSubjects(flow, flash, params)) {
213                                        error()
214                                } else {
215                                        success()
216                                }
217                        }.to "events"
218                        on("delete") {
219                                // handle subjects
220                                this.handleSubjects(flow, flash, params)
221
222                                flash.errors = [:]
223                                def delete = params.get('do') as int;
224
225                                // remove subject
226                                if (flow.subjects[ delete ] && flow.subjects[ delete ] instanceof Subject) {
227                                        // remove subject from templates
228                                        flow.subjectTemplates.each() { templateName, templateData ->
229                                                templateData.subjects.remove( delete )
230                                        }
231
232                                        // remove subject altogether
233                                        flow.subjects.remove( delete )
234                                }
235                        }.to "subjects"
236                        on("previous") {
237                                flash.errors = [:]
238
239                                // handle form data
240                                if (!this.handleSubjects(flow, flash, params)) {
241                                        error()
242                                } else {
243                                        success()
244                                }
245                        }.to "study"
246                }
247
248                // render events page
249                events {
250                        render(view: "_events")
251                        onRender {
252                                flow.page = 4
253
254                                if (!flow.event) {
255                                        flow.event                      = new Event()
256                                        flow.events                     = []
257                                        flow.eventGroups        = []
258                                        flow.eventGroups[0]     = new EventGroup(name: 'Group 1')       // 1 group by default
259                                        flow.eventTemplates     = [:]
260                                } else if (!flash.values) {
261                                        // set flash.values.templateType based on the event instance
262                                        flash.values = [:]
263                                        flash.values.templateType = (flow.event instanceof Event) ? 'event' : 'sample'
264                                }
265                        }
266                        on("switchTemplate") {
267                                flash.values = params
268
269                                // handle study data
270                                this.handleEvents(flow, flash, params)
271
272                                // remove errors as we don't want any warnings now
273                                flash.errors = [:]
274                        }.to "events"
275                        on("add") {
276                                flash.values                    = params
277                                def eventTemplateName   = (params.get('eventType') == 'event') ? params.get('eventTemplate') : params.get('sampleTemplate')
278                                def eventTemplate               = Template.findByName(eventTemplateName)
279
280                                // handle study data
281                                this.handleEvents(flow, flash, params)
282
283                                // validate event object
284                                if (flow.event.validate()) {
285                                        // add this event template to the event template array
286                                        if (!flow.eventTemplates[ eventTemplateName ]) {
287                                                flow.eventTemplates[ eventTemplateName ] = [
288                                                        name: eventTemplateName,
289                                                        template: eventTemplate,
290                                                        events: []
291                                                ]
292                                        }
293
294                                        // it validated! Duplicate the event object...
295                                        def newEvent    = flow.event
296                                        def increment   = flow.events.size()
297
298                                        // ...store it in the events map in the flow scope...
299                                        flow.events[ increment ] = newEvent
300
301                                        // ...and 'reset' the event object in the flow scope
302                                        flow.event = new Event(template: newEvent.template)
303                                       
304                                        // remember the event id with the template
305                                        def eventSize = flow.eventTemplates[ eventTemplateName ]['events'].size()
306                                        flow.eventTemplates[ eventTemplateName ]['events'][ eventSize ] = increment
307
308                                        success()
309                                } else {
310                                        // it does not validate, show error feedback
311                                        flash.errors = [:]
312                                        this.appendErrors(flow.event, flash.errors)
313                                        error()
314                                }
315                        }.to "events"
316                        on("deleteEvent") {
317                                flash.values = params
318                                def delete = params.get('do') as int;
319
320                                // handle event groupings
321                                this.handleEventGrouping(flow, flash, params)
322
323                                // remove event
324                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
325                                        flow.events.remove(delete)
326                                }
327                        }.to "events"
328                        on("addEventGroup") {
329                                flash.values = params
330                               
331                                // handle event groupings
332                                this.handleEventGrouping(flow, flash, params)
333
334                                def increment = flow.eventGroups.size()
335                                def groupName = "Group " + (increment + 1)
336
337                                // check if group name exists
338                                def nameExists = true
339                                def u = 0
340
341                                // make sure a unique name is generated
342                                while (nameExists) {
343                                        u++
344                                        def count = 0
345                                       
346                                        flow.eventGroups.each() {
347                                                if (it.name == groupName) {
348                                                        groupName = "Group " + (increment + 1) + "," + u
349                                                } else {
350                                                        count++
351                                                }
352                                        }
353
354                                        nameExists = !(count == flow.eventGroups.size())
355                                }
356
357                                flow.eventGroups[increment] = new EventGroup( name: groupName )
358                        }.to "events"
359                        on("deleteEventGroup") {
360                                flash.values = params
361
362                                def delete = params.get('do') as int;
363
364                                // handle event groupings
365                                this.handleEventGrouping(flow, flash, params)
366
367                                // remove the group with this specific id
368                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
369                                        // remove this eventGroup
370                                        flow.eventGroups.remove(delete)
371                                }
372                        }.to "events"
373                        on("previous") {
374                                // handle event groupings
375                                this.handleEventGrouping(flow, flash, params)
376                        }.to "subjects"
377                        on("next") {
378                                flash.values = params
379                                flash.errors = [:]
380
381                                // handle study data
382                                if (flow.events.size() < 1) {
383                                        // append error map
384                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors)
385                                        error()                                         
386                                } else if (this.handleEvents(flow, flash, params)) {
387                                        success()
388                                } else {
389                                        error()
390                                }
391                        }.to "groups"
392                }
393
394                // groups page
395                groups {
396                        render(view: "_groups")
397                        onRender {
398                                flow.page = 5
399                        }
400                        on("previous").to "events"
401                        on("next").to "groups"
402                }
403
404                // confirmation
405                confirm {
406                        render(view: "_confirmation")
407                        onRender {
408                                flow.page = 6
409                        }
410                        on("toStudy").to "study"
411                        on("toSubjects").to "subjects"
412                        on("toEvents").to "events"
413                        on("toGroups").to "groups"
414                        on("previous").to "groups"
415                        on("next").to "save"
416                }
417
418                // store all study data
419                save {
420                        action {
421                                println "saving..."
422                                flash.errors = [:]
423
424                                // start transaction
425                                def transaction = sessionFactory.getCurrentSession().beginTransaction()
426
427                                // persist data to the database
428                                try {
429                                        // save EventDescriptions
430                                        flow.eventDescriptions.each() {
431                                                if (!it.save(flush:true)) {
432                                                        this.appendErrors(it, flash.errors)
433                                                        throw new Exception('error saving eventDescription')
434                                                }
435                                                println "saved eventdescription "+it
436                                        }
437
438                                        // TODO: eventDescriptions that are not linked to an event are currently
439                                        //               stored but end up in a black hole. We should either decide to
440                                        //               NOT store these eventDescriptions, or add "hasmany eventDescriptions"
441                                        //               to Study domain class
442
443                                        // save events
444                                        flow.events.each() {
445                                                if (!it.save(flush:true)) {
446                                                        this.appendErrors(it, flash.errors)
447                                                        throw new Exception('error saving event')
448                                                }
449                                                println "saved event "+it
450
451                                                // add to study
452                                                if (it instanceof SamplingEvent) {
453                                                        flow.study.addToSamplingEvents(it)
454                                                } else {
455                                                        flow.study.addToEvents(it)
456                                                }
457                                        }
458
459                                        // save eventGroups
460                                        flow.eventGroups.each() {
461                                                if (!it.save(flush:true)) {
462                                                        this.appendErrors(it, flash.errors)
463                                                        throw new Exception('error saving eventGroup')
464                                                }
465                                                println "saved eventGroup "+it
466
467                                                // add to study
468                                                flow.study.addToEventGroups(it)
469                                        }
470                                       
471                                        // save subjects
472                                        flow.subjects.each() {
473                                                if (!it.save(flush:true)) {
474                                                        this.appendErrors(it, flash.errors)
475                                                        throw new Exception('error saving subject')
476                                                }
477                                                println "saved subject "+it
478
479                                                // add this subject to the study
480                                                flow.study.addToSubjects(it)
481                                        }
482
483                                        // save study
484                                        if (!flow.study.save(flush:true)) {
485                                                this.appendErrors(flow.study, flash.errors)
486                                                throw new Exception('error saving study')
487                                        }
488                                        println "saved study "+flow.study+" (id: "+flow.study.id+")"
489
490                                        // commit transaction
491                                        println "commit"
492                                        transaction.commit()
493                                        success()
494                                } catch (Exception e) {
495                                        // rollback
496                                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace' ], flash.errors)
497
498                                        // stacktrace in flash scope
499                                        flash.debug = e.getStackTrace()
500
501                                        println "rollback"
502                                        transaction.rollback()
503                                        error()
504                                }
505                        }
506                        on("error").to "error"
507                        on(Exception).to "error"
508                        on("success").to "done"
509                }
510
511                // error storing data
512                error {
513                        render(view: "_error")
514                        onRender {
515                                flow.page = 6
516                        }
517                        on("next").to "save"
518                        on("previous").to "samples"
519                }
520
521                // render page three
522                done {
523                        render(view: "_done")
524                        onRender {
525                                flow.page = 7
526                        }
527                        on("previous") {
528                                // TODO
529                        }.to "confirm"
530                }
531        }
532
533        /**
534         * re-usable code for handling study form data in a web flow
535         * @param Map LocalAttributeMap (the flow scope)
536         * @param Map localAttributeMap (the flash scope)
537         * @param Map GrailsParameterMap (the flow parameters = form data)
538         * @returns boolean
539         */
540        def handleStudy(flow, flash, params) {
541                // create study instance if we have none
542                if (!flow.study) flow.study = new Study();
543
544                // create date instance from date string?
545                // @see WizardTagLibrary::dateElement{...}
546                if (params.get('startDate')) {
547                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
548                } else {
549                        params.remove('startDate')
550                }
551
552                // if a template is selected, get template instance
553                def template = params.remove('template')
554                if (template instanceof String && template.size() > 0) {
555                        flow.study.template = Template.findByName(template)
556                } else if (template instanceof Template) {
557                        flow.study.template = template
558                }
559
560                // iterate through fields
561                if (flow.study.template) {
562                        flow.study.giveFields().each() {
563                                flow.study.setFieldValue(it.name, params.get(it.escapedName()))
564                        }
565                }
566
567                // validate study
568                if (flow.study.validate()) {
569                        return true
570                } else {
571                        // validation failed, feedback errors
572                        flash.errors = [:]
573                        this.appendErrors(flow.study, flash.errors)
574                        return false
575                }
576        }
577
578        /**
579         * re-usable code for handling subject form data in a web flow
580         * @param Map LocalAttributeMap (the flow scope)
581         * @param Map localAttributeMap (the flash scope)
582         * @param Map GrailsParameterMap (the flow parameters = form data)
583         * @returns boolean
584         */
585        def handleSubjects(flow, flash, params) {
586                def names = [:];
587                def errors = false;
588                def id = 0;
589
590                // iterate through subject templates
591                flow.subjectTemplates.each() { subjectTemplate ->
592                        // iterate through subjects
593                        subjectTemplate.getValue().subjects.each() { subjectIncrement, subjectId ->
594                                // iterate through fields (= template fields and domain properties)
595                                flow.subjects[ subjectId ].giveFields().each() { subjectField ->
596                                        // set the field
597                                        flow.subjects[ subjectId ].setFieldValue(
598                                                subjectField.name,
599                                                params.get( 'subject_' + subjectId + '_' + subjectField.escapedName() )
600                                        )
601                                }
602
603                                // validate subject
604                                if (!flow.subjects[ subjectId ].validate()) {
605                                        errors = true
606                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors, 'subject_' + subjectId + '_')
607                                }
608                        }
609                }
610
611                return !errors
612        }
613
614        /**
615         * re-usable code for handling event form data in a web flow
616         * @param Map LocalAttributeMap (the flow scope)
617         * @param Map localAttributeMap (the flash scope)
618         * @param Map GrailsParameterMap (the flow parameters = form data)
619         * @returns boolean
620         */
621        def handleEvents(flow, flash, params) {
622                def errors = false
623                def template = null
624
625                // handle the type of event
626                if (params.eventType == 'event') {
627                        flow.event = new Event();
628                        template = params.remove('eventTemplate')
629                } else if (params.eventType == 'sample') {
630                        flow.event = new SamplingEvent();
631                        template = params.remove('sampleTemplate')
632                }
633
634                // got an event in the flow scope?
635                //if (!flow.event) flow.event = new Event()
636
637                // if a template is selected, get template instance
638                if (template instanceof String && template.size() > 0) {
639                        params.template = Template.findByName(template)
640                } else if (template instanceof Template) {
641                        params.template = template
642                } else {
643                        params.template = null
644                }
645
646                // set template
647                if (params.template) flow.event.template = params.template
648
649                // update event instance with parameters
650                flow.event.giveFields().each() { eventField ->
651                        flow.event.setFieldValue(eventField.name, params[ eventField.escapedName() ])   
652                }
653
654                // handle event objects
655                flow.eventTemplates.each() { eventTemplate ->
656                        // iterate through events
657                        eventTemplate.getValue().events.each() { eventId ->
658                                // iterate through template fields
659                                flow.events[ eventId ].giveFields().each() { eventField ->
660                                        flow.events[ eventId ].setFieldValue(eventField.name, params.get( 'event_' + eventId + '_' + eventField.escapedName() ) )
661                                }
662
663                                // validate event
664                                if (!flow.events[ eventId ].validate()) {
665                                        errors = true
666                                        this.appendErrors(flow.events[ eventId ], flash.errors, 'event_' + eventId + '_')
667                                }
668                        }
669                }
670
671                // handle event grouping
672                handleEventGrouping(flow, flash, params)
673
674                return !errors
675        }
676
677        /**
678         * re-usable code for handling event grouping in a web flow
679         * @param Map LocalAttributeMap (the flow scope)
680         * @param Map localAttributeMap (the flash scope)
681         * @param Map GrailsParameterMap (the flow parameters = form data)
682         * @returns boolean
683         */
684        def handleEventGrouping(flow, flash, params) {
685                // walk through eventGroups
686                def g = 0
687
688                flow.eventGroups.each() { eventGroup ->
689                        def e = 0
690
691                        // reset events
692                        eventGroup.events = new HashSet()
693
694                        // iterate through events
695                        flow.events.each() {
696                                if (params.get('event_' + e + '_group_' + g) == 'on') {
697                                        eventGroup.addToEvents(it)
698                                }
699                                e++
700                        }
701                        g++
702                }
703        }
704
705        /**
706         * return the object from a map of objects by searching for a name
707         * @param String name
708         * @param Map map of objects
709         * @return Object
710         */
711        def getObjectByName(name, map) {
712                def result = null
713                map.each() {
714                        if (it.name == name) {
715                                result = it
716                        }
717                }
718
719                return result
720        }
721
722        /**
723         * transform domain class validation errors into a human readable
724         * linked hash map
725         * @param object validated domain class
726         * @returns object  linkedHashMap
727         */
728        def getHumanReadableErrors(object) {
729                def errors = [:]
730                object.errors.getAllErrors().each() {
731                        def message = it.toString()
732
733                        //errors[it.getArguments()[0]] = it.getDefaultMessage()
734                        errors[it.getArguments()[0]] = message.substring(0, message.indexOf(';'))
735                }
736
737                return errors
738        }
739
740        /**
741         * append errors of a particular object to a map
742         * @param object
743         * @param map linkedHashMap
744         * @void
745         */
746        def appendErrors(object, map) {
747                this.appendErrorMap(this.getHumanReadableErrors(object), map)
748        }
749
750        def appendErrors(object, map, prepend) {
751                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
752        }
753
754        /**
755         * append errors of one map to another map
756         * @param map linkedHashMap
757         * @param map linkedHashMap
758         * @void
759         */
760        def appendErrorMap(map, mapToExtend) {
761                map.each() {key, value ->
762                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
763                }
764        }
765
766        def appendErrorMap(map, mapToExtend, prepend) {
767                map.each() {key, value ->
768                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
769                }
770        }
771}
Note: See TracBrowser for help on using the browser.