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

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