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

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