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

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