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

Revision 418, 18.2 KB (checked in by duh, 4 years ago)

- subjects can now be deleted properly
- event grouping works
- working on upgrading to grails 1.3 (added grails default stuff to ignore list)

  • 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: '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                                flash.values                    = params
263                                def eventTemplateName   = params.get('template')
264                                def eventTemplate               = Template.findByName(eventTemplateName)
265
266                                // add this event template to the event template array
267                                if (!flow.eventTemplates[ eventTemplateName ]) {
268                                        flow.eventTemplates[ eventTemplateName ] = [
269                                                name: eventTemplateName,
270                                                template: eventTemplate,
271                                                events: []
272                                        ]
273                                }
274
275                                // handle study data
276                                this.handleEvents(flow, flash, params)
277
278                                // validate event object
279                                if (flow.event.validate()) {
280                                        // it validated! Duplicate the event object...
281                                        def newEvent    = flow.event
282                                        def increment   = flow.events.size()
283
284                                        // ...store it in the events map in the flow scope...
285                                        flow.events[ increment ] = newEvent
286
287                                        // ...and 'reset' the event object in the flow scope
288                                        flow.event = new Event(template: newEvent.template)
289                                       
290                                        // remember the event id with the template
291                                        def eventSize = flow.eventTemplates[ eventTemplateName ]['events'].size()
292                                        flow.eventTemplates[ eventTemplateName ]['events'][ eventSize ] = increment
293
294                                        success()
295                                } else {
296                                        // it does not validate, show error feedback
297                                        flash.errors = [:]
298                                        this.appendErrors(flow.event, flash.errors)
299                                        error()
300                                }
301                        }.to "events"
302                        on("deleteEvent") {
303                                flash.values = params
304                                def delete = params.get('do') as int;
305
306                                // handle event groupings
307                                this.handleEventGrouping(flow, flash, params)
308
309                                // remove event
310                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
311                                        flow.events.remove(delete)
312                                }
313                        }.to "events"
314                        on("addEventGroup") {
315                                flash.values = params
316                               
317                                // handle event groupings
318                                this.handleEventGrouping(flow, flash, params)
319
320                                def increment = flow.eventGroups.size()
321                                def groupName = "Group " + (increment + 1)
322
323                                // check if group name exists
324                                def nameExists = true
325                                def u = 0
326
327                                // make sure a unique name is generated
328                                while (nameExists) {
329                                        u++
330                                        def count = 0
331                                       
332                                        flow.eventGroups.each() {
333                                                if (it.name == groupName) {
334                                                        groupName = "Group " + (increment + 1) + "," + u
335                                                } else {
336                                                        count++
337                                                }
338                                        }
339
340                                        nameExists = !(count == flow.eventGroups.size())
341                                }
342
343                                flow.eventGroups[increment] = new EventGroup( name: groupName )
344                        }.to "events"
345                        on("deleteEventGroup") {
346                                flash.values = params
347                               
348                                def delete = params.get('do') as int;
349
350                                // handle event groupings
351                                this.handleEventGrouping(flow, flash, params)
352
353                                // remove the group with this specific id
354                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
355                                        // remove this eventGroup
356                                        flow.eventGroups.remove(delete)
357                                }
358                        }.to "events"
359                        on("previous") {
360                                // handle event groupings
361                                this.handleEventGrouping(flow, flash, params)
362                        }.to "subjects"
363                        on("next") {
364                                flash.values = params
365                                flash.errors = [:]
366
367                                // handle study data
368                                if (flow.events.size() < 1) {
369                                        // append error map
370                                        flash.values = params
371                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors)
372                                        error()                                         
373                                } else if (this.handleEvents(flow, flash, params)) {
374                                        success()
375                                } else {
376                                        error()
377                                }
378                        }.to "events"
379                }
380
381                confirm {
382                        render(view: "_confirmation")
383                        onRender {
384                                flow.page = 5
385                        }
386                        on("toStudy").to "study"
387                        on("toSubjects").to "subjects"
388                        on("toEvents").to "events"
389                        on("previous").to "events"
390                        on("next").to "save"
391                }
392
393                // store all study data
394                save {
395                        action {
396                                println "saving..."
397                                flash.errors = [:]
398
399                                // start transaction
400                                def transaction = sessionFactory.getCurrentSession().beginTransaction()
401
402                                // persist data to the database
403                                try {
404                                        // save EventDescriptions
405                                        flow.eventDescriptions.each() {
406                                                if (!it.save(flush:true)) {
407                                                        this.appendErrors(it, flash.errors)
408                                                        throw new Exception('error saving eventDescription')
409                                                }
410                                                println "saved eventdescription "+it
411                                        }
412
413                                        // TODO: eventDescriptions that are not linked to an event are currently
414                                        //               stored but end up in a black hole. We should either decide to
415                                        //               NOT store these eventDescriptions, or add "hasmany eventDescriptions"
416                                        //               to Study domain class
417
418                                        // save events
419                                        flow.events.each() {
420                                                if (!it.save(flush:true)) {
421                                                        this.appendErrors(it, flash.errors)
422                                                        throw new Exception('error saving event')
423                                                }
424                                                println "saved event "+it
425
426                                                // add to study
427                                                if (it instanceof SamplingEvent) {
428                                                        flow.study.addToSamplingEvents(it)
429                                                } else {
430                                                        flow.study.addToEvents(it)
431                                                }
432                                        }
433
434                                        // save eventGroups
435                                        flow.eventGroups.each() {
436                                                if (!it.save(flush:true)) {
437                                                        this.appendErrors(it, flash.errors)
438                                                        throw new Exception('error saving eventGroup')
439                                                }
440                                                println "saved eventGroup "+it
441
442                                                // add to study
443                                                flow.study.addToEventGroups(it)
444                                        }
445                                       
446                                        // save subjects
447                                        flow.subjects.each() {
448                                                if (!it.save(flush:true)) {
449                                                        this.appendErrors(it, flash.errors)
450                                                        throw new Exception('error saving subject')
451                                                }
452                                                println "saved subject "+it
453
454                                                // add this subject to the study
455                                                flow.study.addToSubjects(it)
456                                        }
457
458                                        // save study
459                                        if (!flow.study.save(flush:true)) {
460                                                this.appendErrors(flow.study, flash.errors)
461                                                throw new Exception('error saving study')
462                                        }
463                                        println "saved study "+flow.study+" (id: "+flow.study.id+")"
464
465                                        // commit transaction
466                                        println "commit"
467                                        transaction.commit()
468                                        success()
469                                } catch (Exception e) {
470                                        // rollback
471                                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace' ], flash.errors)
472
473                                        // stacktrace in flash scope
474                                        flash.debug = e.getStackTrace()
475
476                                        println "rollback"
477                                        transaction.rollback()
478                                        error()
479                                }
480                        }
481                        on("error").to "error"
482                        on(Exception).to "error"
483                        on("success").to "done"
484                }
485
486                // error storing data
487                error {
488                        render(view: "_error")
489                        onRender {
490                                flow.page = 6
491                        }
492                        on("next").to "save"
493                        on("previous").to "events"
494                }
495
496                // render page three
497                done {
498                        render(view: "_done")
499                        onRender {
500                                flow.page = 7
501                        }
502                        on("previous") {
503                                // TODO
504                        }.to "confirm"
505                }
506        }
507
508        /**
509         * re-usable code for handling study form data in a web flow
510         * @param Map LocalAttributeMap (the flow scope)
511         * @param Map localAttributeMap (the flash scope)
512         * @param Map GrailsParameterMap (the flow parameters = form data)
513         * @returns boolean
514         */
515        def handleStudy(flow, flash, params) {
516                // create study instance if we have none
517                if (!flow.study) flow.study = new Study();
518
519                // create date instance from date string?
520                // @see WizardTagLibrary::dateElement{...}
521                if (params.get('startDate')) {
522                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
523                } else {
524                        params.remove('startDate')
525                }
526
527                // if a template is selected, get template instance
528                def template = params.remove('template')
529                if (template instanceof String && template.size() > 0) {
530                        flow.study.template = Template.findByName(template)
531                } else if (template instanceof Template) {
532                        flow.study.template = template
533                }
534
535                // iterate through fields
536                if (flow.study.template) {
537                        flow.study.giveFields().each() {
538                                flow.study.setFieldValue(it.name, params.get(it.escapedName()))
539                        }
540                }
541
542                // validate study
543                if (flow.study.validate()) {
544                        return true
545                } else {
546                        // validation failed, feedback errors
547                        flash.errors = [:]
548                        this.appendErrors(flow.study, flash.errors)
549                        return false
550                }
551        }
552
553        /**
554         * re-usable code for handling subject form data in a web flow
555         * @param Map LocalAttributeMap (the flow scope)
556         * @param Map localAttributeMap (the flash scope)
557         * @param Map GrailsParameterMap (the flow parameters = form data)
558         * @returns boolean
559         */
560        def handleSubjects(flow, flash, params) {
561                def names = [:];
562                def errors = false;
563                def id = 0;
564
565                // iterate through subject templates
566                flow.subjectTemplates.each() { subjectTemplate ->
567                        // iterate through subjects
568                        subjectTemplate.getValue().subjects.each() { subjectId ->
569                                // iterate through fields (= template fields and domain properties)
570                                flow.subjects[ subjectId ].giveFields().each() { subjectField ->
571                                        // set the field
572                                        flow.subjects[ subjectId ].setFieldValue(
573                                                subjectField.name,
574                                                params.get( 'subject_' + subjectId + '_' + subjectField.escapedName() )
575                                        )
576                                }
577
578                                // validate subject
579                                if (!flow.subjects[ subjectId ].validate()) {
580                                        errors = true
581                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors, 'subject_' + subjectId + '_')
582                                }
583                        }
584                }
585
586                return !errors
587        }
588
589        /**
590         * re-usable code for handling event form data in a web flow
591         * @param Map LocalAttributeMap (the flow scope)
592         * @param Map localAttributeMap (the flash scope)
593         * @param Map GrailsParameterMap (the flow parameters = form data)
594         * @returns boolean
595         */
596        def handleEvents(flow, flash, params) {
597                def errors = false
598               
599                // got an event in the flash scope?
600                if (!flow.event) flow.event = new Event()
601
602                // if a template is selected, get template instance
603                def template = params.remove('template')
604                if (template instanceof String && template.size() > 0) {
605                        params.template = Template.findByName(template)
606                } else if (template instanceof Template) {
607                        params.template = template
608                } else {
609                        params.template = null
610                }
611
612                // set template
613                if (params.template) flow.event.template = params.template
614
615                // update event instance with parameters
616                flow.event.giveFields().each() { eventField ->
617                        flow.event.setFieldValue(eventField.name, params[ eventField.escapedName() ])   
618                }
619
620                // handle event objects
621                flow.eventTemplates.each() { eventTemplate ->
622                        // iterate through events
623                        eventTemplate.getValue().events.each() { eventId ->
624                                // iterate through template fields
625                                flow.events[ eventId ].giveFields().each() { eventField ->
626                                        flow.events[ eventId ].setFieldValue(eventField.name, params.get( 'event_' + eventId + '_' + eventField.escapedName() ) )
627                                }
628
629                                // validate event
630                                if (!flow.events[ eventId ].validate()) {
631                                        errors = true
632                                        this.appendErrors(flow.events[ eventId ], flash.errors, 'event_' + eventId + '_')
633                                }
634                        }
635                }
636
637                // handle event grouping
638                handleEventGrouping(flow, flash, params)
639
640                return !errors
641        }
642
643        /**
644         * re-usable code for handling event grouping in a web flow
645         * @param Map LocalAttributeMap (the flow scope)
646         * @param Map localAttributeMap (the flash scope)
647         * @param Map GrailsParameterMap (the flow parameters = form data)
648         * @returns boolean
649         */
650        def handleEventGrouping(flow, flash, params) {
651                // walk through eventGroups
652                def g = 0
653
654                flow.eventGroups.each() { eventGroup ->
655                        def e = 0
656
657                        // reset events
658                        eventGroup.events = new HashSet()
659
660                        // iterate through events
661                        flow.events.each() {
662                                if (params.get('event_' + e + '_group_' + g) == 'on') {
663                                        eventGroup.addToEvents(it)
664                                }
665                                e++
666                        }
667                        g++
668                }
669        }
670
671        /**
672         * return the object from a map of objects by searching for a name
673         * @param String name
674         * @param Map map of objects
675         * @return Object
676         */
677        def getObjectByName(name, map) {
678                def result = null
679                map.each() {
680                        if (it.name == name) {
681                                result = it
682                        }
683                }
684
685                return result
686        }
687
688        /**
689         * transform domain class validation errors into a human readable
690         * linked hash map
691         * @param object validated domain class
692         * @returns object  linkedHashMap
693         */
694        def getHumanReadableErrors(object) {
695                def errors = [:]
696                object.errors.getAllErrors().each() {
697                        errors[it.getArguments()[0]] = it.getDefaultMessage()
698                }
699
700                return errors
701        }
702
703        /**
704         * append errors of a particular object to a map
705         * @param object
706         * @param map linkedHashMap
707         * @void
708         */
709        def appendErrors(object, map) {
710                this.appendErrorMap(this.getHumanReadableErrors(object), map)
711        }
712
713        def appendErrors(object, map, prepend) {
714                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
715        }
716
717        /**
718         * append errors of one map to another map
719         * @param map linkedHashMap
720         * @param map linkedHashMap
721         * @void
722         */
723        def appendErrorMap(map, mapToExtend) {
724                map.each() {key, value ->
725                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
726                }
727        }
728
729        def appendErrorMap(map, mapToExtend, prepend) {
730                map.each() {key, value ->
731                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
732                }
733        }
734}
Note: See TracBrowser for help on using the browser.