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

Last change on this file since 238 was 238, checked in by duh, 12 years ago

Refectored version of the wizard

  • initial template page has been removed, now is a generic 'start' page where one (in the future) may create a new study, or load and modify an already stored study
  • study page incorporates study template select element, but does not yet incorporate the study template fields
  • subjects page now allows creation of subjects based on a template. This change also implied the study page altogether had to change into a seperate table entity. Now the the page lists as many tables as unique templates have been selected. These tables contain all subjects that were added using that particular template. NOTE: data is not stored yet, due to the fact that templateEntity does not work properly yey (key/value pairs need to be set correctly when calling the setTemplate method)
  • the JavaScript? now handles multiple tables in a page as well, and automatically initializes any underlying slider div if that is required
  • Property svn:keywords set to
    Date
    Author
    Rev
File size: 17.4 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.studycapturing.*
4import dbnp.data.*
5import grails.converters.*
6
7/**
8 * Wizard Controler
9 *
10 * The wizard controller handles the handeling of pages and data flow
11 * through the study capturing wizard.
12 *
13 * @author Jeroen Wesbeek
14 * @since 20100107
15 * @package studycapturing
16 *
17 * Revision information:
18 * $Rev: 238 $
19 * $Author: duh $
20 * $Date: 2010-03-05 14:21:52 +0000 (vr, 05 mrt 2010) $
21 */
22class WizardController {
23        /**
24         * index method, redirect to the webflow
25         * @void
26         */
27        def index = {
28                /**
29                 * Do you believe it in your head?
30                 * I can go with the flow
31                 * Don't say it doesn't matter (with the flow) matter anymore
32                 * I can go with the flow (I can go)
33                 * Do you believe it in your head?
34                 */
35                redirect(action: 'pages')
36        }
37
38        /**
39         * WebFlow definition
40         * @see http://grails.org/WebFlow
41         * @void
42         */
43        def pagesFlow = {
44                // start the flow
45                onStart {
46                        // define flow variables
47                        flow.page = 0
48                        flow.pages = [
49                                //[title: 'Templates'],                 // templates
50                                [title: 'Start'],                               // load or create a study
51                                [title: 'Study'],                               // study
52                                [title: 'Subjects'],                    // subjects
53                                [title: 'Event Descriptions'],  // event descriptions
54                                [title: 'Events'],                              // events and event grouping
55                                [title: 'Confirmation'],                // confirmation page
56                                [title: 'Done']                                 // finish page
57                        ]
58
59                }
60
61                // render the main wizard page which immediately
62                // triggers the 'next' action (hence, the main
63                // page dynamically renders the study template
64                // and makes the flow jump to the study logic)
65                mainPage {
66                        render(view: "/wizard/index")
67                        onRender {
68                                flow.page = 1
69                        }
70                        on("next").to "start"
71                }
72
73                // select the templates to use for this study
74                templates {
75                        render(view: "_templates")
76                        onRender {
77                                flow.page = 1
78                        }
79                        on("next") {
80                                // if we don't have a study, instantiate a study with dummy values
81                                if (!flow.study) {
82                                        flow.study = new Study(
83                                                title: "my study",
84                                                code: "",
85                                                ecCode: "",
86                                                researchQuestion: "",
87                                                description: "",
88                                                startDate: new Date()
89                                        )
90                                }
91
92                                // assign template to study
93                                flow.study.template = Template.findByName(params.get('template'));
94
95                                // validate study
96                                if (flow.study.validate()) {
97                                        success()
98                                } else {
99                                        // validation failed, feedback errors
100                                        flash.errors = new LinkedHashMap()
101                                        this.appendErrors(flow.study, flash.errors)
102                                        error()
103                                }
104                        }.to "study"
105                }
106
107                // create or modify a study
108                start {
109                        render(view: "_start")
110                        onRender {
111                                flow.page = 1
112                        }
113                        on("next") {
114
115                        }.to "study"
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                        }
126                        on("previous") {
127                                flash.errors = new LinkedHashMap()
128
129                                if (this.handleStudy(flow, flash, params)) {
130                                        success()
131                                } else {
132                                        error()
133                                }
134                        }.to "start"
135                        on("next") {
136                                flash.errors = new LinkedHashMap()
137
138                                if (this.handleStudy(flow, flash, params)) {
139                                        success()
140                                } else {
141                                        error()
142                                }
143                        }.to "subjects"
144                }
145
146                // render and handle subjects page
147                subjects {
148                        render(view: "_subjects")
149                        onRender {
150                                flow.page = 3
151
152                                if (!flow.subjects) {
153                                        flow.subjects = []
154                                        flow.subjectTemplates = new LinkedHashMap()
155                                }
156                        }
157                        on("add") {
158                                // fetch species by name (as posted by the form)
159                                def speciesTerm = Term.findByName(params.addSpecies)
160                                def subjectTemplateName = params.get('template')
161                                def subjectTemplate     = Template.findByName(subjectTemplateName)
162
163                                // add this subject template to the subject template array
164                                if (!flow.subjectTemplates[ subjectTemplateName ]) {
165                                        flow.subjectTemplates[ subjectTemplateName ] = [
166                                                name: subjectTemplateName,
167                                                template: subjectTemplate,
168                                                subjects: []
169                                        ]
170                                }
171
172                                // add x subject of species y
173                                (params.addNumber as int).times {
174                                        def increment = flow.subjects.size()
175                                        def subject = new Subject(
176                                                name: 'Subject ' + (increment + 1),
177                                                species: speciesTerm,
178                                                template: subjectTemplate
179                                        )
180
181                                        // instantiate a new Subject
182                                        flow.subjects[ increment ] = subject
183
184                                        // and remember the subject id with the template
185                                        def subjectsSize = flow.subjectTemplates[ subjectTemplateName ]['subjects'].size()
186                                        flow.subjectTemplates[ subjectTemplateName ]['subjects'][ subjectsSize ] = increment
187                                }
188                        }.to "subjects"
189                        on("next") {
190                                println flow.subjectTemplates
191                                println flow.subjects
192                                flash.errors = new LinkedHashMap()
193
194                                // check if we have at least one subject
195                                // and check form data
196                                if (flow.subjects.size() < 1) {
197                                        // append error map
198                                        this.appendErrorMap(['subjects': 'You need at least to create one subject for your study'], flash.errors)
199                                        error()
200                                } else if (!this.handleSubjects(flow, flash, params)) {
201                                        error()
202                                } else {
203                                        success()
204                                }
205                        }.to "eventDescriptions"
206                        on("previous") {
207                                flash.errors = new LinkedHashMap()
208
209                                // handle form data
210                                if (!this.handleSubjects(flow, flash, params)) {
211                                        error()
212                                } else {
213                                        success()
214                                }
215                        }.to "study"
216                }
217
218                // render page three
219                eventDescriptions {
220                        render(view: "_eventDescriptions")
221                        onRender {
222                                flow.page = 4
223
224                                if (!flow.eventDescriptions) {
225                                        flow.eventDescriptions = []
226                                }
227                        }
228                        on("add") {
229                                // fetch classification by name (as posted by the form)
230                                //params.classification = Term.findByName(params.classification)
231
232                                // transform checkbox form value to boolean
233                                params.isSamplingEvent = (params.containsKey('isSamplingEvent'))
234
235                                // instantiate EventDescription with parameters
236                                def eventDescription = new EventDescription(params)
237
238                                // validate
239                                if (eventDescription.validate()) {
240                                        def increment = flow.eventDescriptions.size()
241                                        flow.eventDescriptions[increment] = eventDescription
242                                        success()
243                                } else {
244                                        // validation failed, feedback errors
245                                        flash.errors = new LinkedHashMap()
246                                        flash.values = params
247                                        this.appendErrors(eventDescription, flash.errors)
248                                        error()
249                                }
250                        }.to "eventDescriptions"
251                        on("delete") {
252                                def delete = params.get('do') as int;
253
254                                // handle form data
255                                if (!this.handleEventDescriptions(flow, flash, params)) {
256                                        flash.values = params
257                                        error()
258                                } else {
259                                        success()
260                                }
261
262                                // remove eventDescription
263                                if (flow.eventDescriptions[ delete ] && flow.eventDescriptions[ delete ] instanceof EventDescription) {
264                                        // remove all events based on this eventDescription
265                                        for ( i in flow.events.size()..0 ) {
266                                                if (flow.events[ i ] && flow.events[ i ].eventDescription == flow.eventDescriptions[ delete ]) {
267                                                        flow.events.remove(i)
268                                                }
269                                        }
270
271                                        flow.eventDescriptions.remove(delete)
272                                }
273                        }.to "eventDescriptions"
274                        on("previous") {
275                                flash.errors = new LinkedHashMap()
276
277                                // handle form data
278                                if (!this.handleEventDescriptions(flow, flash, params)) {
279                                        flash.values = params
280                                        error()
281                                } else {
282                                        success()
283                                }
284                        }.to "subjects"
285                        on("next") {
286                                flash.errors = new LinkedHashMap()
287
288                                // check if we have at least one subject
289                                // and check form data
290                                if (flow.eventDescriptions.size() < 1) {
291                                        // append error map
292                                        flash.values = params
293                                        this.appendErrorMap(['eventDescriptions': 'You need at least to create one eventDescription for your study'], flash.errors)
294                                        error()
295                                } else if (!this.handleEventDescriptions(flow, flash, params)) {
296                                        flash.values = params
297                                        error()
298                                } else {
299                                        success()
300                                }
301                        }.to "events"
302                }
303
304                // render events page
305                events {
306                        render(view: "_events")
307                        onRender {
308                                flow.page = 5
309
310                                if (!flow.events) {
311                                        flow.events = []
312                                }
313
314                                if (!flow.eventGroups) {
315                                        flow.eventGroups = []
316                                        flow.eventGroups[0] = new EventGroup(name: 'Group 1')   // 1 group by default
317                                }
318                        }
319                        on("add") {
320                                // create date instances from date string?
321                                // @see WizardTagLibrary::timeElement{...}
322                                if (params.get('startTime')) {
323                                        params.startTime = new Date().parse("d/M/yyyy HH:mm", params.get('startTime').toString())
324                                }
325                                if (params.get('endTime')) {
326                                        params.get('endTime').toString()
327                                        params.endTime = new Date().parse("d/M/yyyy HH:mm", params.get('endTime').toString())
328                                }
329
330                                // get eventDescription instance by name
331                                params.eventDescription = this.getObjectByName(params.get('eventDescription'), flow.eventDescriptions)
332
333                                // instantiate Event with parameters
334                                def event = new Event(params)
335
336                                // handle event groupings
337                                this.handleEventGrouping(flow, flash, params)
338
339                                // validate event
340                                if (event.validate()) {
341                                        def increment = flow.events.size()
342                                        flow.events[increment] = event
343                                        success()
344                                } else {
345                                        // validation failed, feedback errors
346                                        flash.errors = new LinkedHashMap()
347                                        flash.values = params
348                                        this.appendErrors(event, flash.errors)
349
350                                        flash.startTime = params.startTime
351                                        flash.endTime = params.endTime
352                                        flash.eventDescription = params.eventDescription
353
354                                        error()
355                                }
356                        }.to "events"
357                        on("deleteEvent") {
358                                flash.values = params
359                                def delete = params.get('do') as int;
360
361                                // handle event groupings
362                                this.handleEventGrouping(flow, flash, params)
363
364                                // remove event
365                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
366                                        flow.events.remove(delete)
367                                }
368                        }.to "events"
369                        on("addEventGroup") {
370                                flash.values = params
371                               
372                                // handle event groupings
373                                this.handleEventGrouping(flow, flash, params)
374
375                                def increment = flow.eventGroups.size()
376                                def groupName = "Group " + (increment + 1)
377
378                                // check if group name exists
379                                def nameExists = true
380                                def u = 0
381
382                                // make sure a unique name is generated
383                                while (nameExists) {
384                                        u++
385                                        def count = 0
386                                       
387                                        flow.eventGroups.each() {
388                                                if (it.name == groupName) {
389                                                        groupName = "Group " + (increment + 1) + "," + u
390                                                } else {
391                                                        count++
392                                                }
393                                        }
394
395                                        nameExists = !(count == flow.eventGroups.size())
396                                }
397
398                                flow.eventGroups[increment] = new EventGroup(name: groupName)
399                        }.to "events"
400                        on("deleteEventGroup") {
401                                flash.values = params
402                               
403                                def delete = params.get('do') as int;
404
405                                // handle event groupings
406                                this.handleEventGrouping(flow, flash, params)
407
408                                // remove the group with this specific id
409                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
410                                        // remove this eventGroup
411                                        flow.eventGroups.remove(delete)
412                                }
413                        }.to "events"
414                        on("previous") {
415                                // handle event groupings
416                                this.handleEventGrouping(flow, flash, params)
417                        }.to "eventDescriptions"
418                        on("next") {
419                                flash.values = params
420                               
421                                flash.errors = new LinkedHashMap()
422
423                                // handle event groupings
424                                this.handleEventGrouping(flow, flash, params)
425
426                                // check if we have at least one subject
427                                // and check form data
428                                if (flow.events.size() < 1) {
429                                        // append error map
430                                        flash.values = params
431                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors)
432                                        error()
433                                }
434                        }.to "events"
435                }
436
437                confirm {
438                        render(view: "_confirmation")
439                        onRender {
440                                flow.page = 6
441                        }
442                        on("previous") {
443                                // do nothing
444                        }.to "events"
445                        on("next") {
446                                // store everything in the database!
447                                success()
448                        }.to "confirm"
449                }
450
451                // render page three
452                done {
453                        render(view: "_done")
454                        onRender {
455                                flow.page = 6
456                        }
457                        on("previous") {
458                                // TODO
459                        }.to "confirm"
460                }
461        }
462
463        /**
464         * re-usable code for handling study form data in a web flow
465         * @param Map LocalAttributeMap (the flow scope)
466         * @param Map localAttributeMap (the flash scope)
467         * @param Map GrailsParameterMap (the flow parameters = form data)
468         * @returns boolean
469         */
470        def handleStudy(flow, flash, params) {
471                // create study instance if we have none
472                if (!flow.study) flow.study = new Study();
473
474                // create date instance from date string?
475                // @see WizardTagLibrary::dateElement{...}
476                if (params.get('startDate')) {
477                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
478                } else {
479                        params.remove('startDate')
480                }
481
482                // if a template is selected, get template instance
483                if (params.get('template')) {
484                        params.template = Template.findByName(params.get('template'))
485                }
486
487                // update study instance with parameters
488                params.each() {key, value ->
489                        if (flow.study.hasProperty(key)) {
490                                flow.study.setProperty(key, value);
491                        }
492                }
493
494                // validate study
495                if (flow.study.validate()) {
496                        return true
497                } else {
498                        // validation failed, feedback errors
499                        flash.errors = new LinkedHashMap()
500                        this.appendErrors(flow.study, flash.errors)
501                        return false
502                }
503        }
504
505        /**
506         * re-usable code for handling eventDescription form data in a web flow
507         * @param Map LocalAttributeMap (the flow scope)
508         * @param Map localAttributeMap (the flash scope)
509         * @param Map GrailsParameterMap (the flow parameters = form data)
510         * @returns boolean
511         */
512        def handleEventDescriptions(flow, flash, params) {
513                def names = new LinkedHashMap()
514                def errors = false
515                def id = 0
516
517                flow.eventDescriptions.each() {
518                        it.name = params.get('eventDescription_' + id + '_name')
519                        it.description = params.get('eventDescription_' + id + '_description')
520                        //it.classification = Term.findByName(params.get('eventDescription_' + id + '_classification'))
521                        it.isSamplingEvent = (params.containsKey('eventDescription_' + id + '_isSamplingEvent'))
522
523                        // validate eventDescription
524                        if (!it.validate()) {
525                                errors = true
526                                this.appendErrors(it, flash.errors, 'eventDescription_' + id + '_')
527                        }
528
529                        id++
530                }
531
532                return !errors
533        }
534
535        /**
536         * re-usable code for handling event grouping in a web flow
537         * @param Map LocalAttributeMap (the flow scope)
538         * @param Map localAttributeMap (the flash scope)
539         * @param Map GrailsParameterMap (the flow parameters = form data)
540         * @returns boolean
541         */
542        def handleEventGrouping(flow, flash, params) {
543                // walk through eventGroups
544                def g = 0
545                flow.eventGroups.each() {
546                        def e = 0
547                        def eventGroup = it
548
549                        // reset events
550                        eventGroup.events = new HashSet()
551
552                        // walk through events
553                        flow.events.each() {
554                                if (params.get('event_' + e + '_group_' + g) == 'on') {
555                                        eventGroup.addToEvents(it)
556                                }
557                                e++
558                        }
559                        g++
560                }
561        }
562
563        /**
564         * re-usable code for handling subject form data in a web flow
565         * @param Map LocalAttributeMap (the flow scope)
566         * @param Map localAttributeMap (the flash scope)
567         * @param Map GrailsParameterMap (the flow parameters = form data)
568         * @returns boolean
569         */
570        def handleSubjects(flow, flash, params) {
571                def names = new LinkedHashMap();
572                def errors = false;
573                def id = 0;
574
575                // iterate through subject templates
576                flow.subjectTemplates.each() {
577                        def subjectTemplate = it.getValue().template
578                        def templateFields      = subjectTemplate.fields
579
580                        // iterate through subjects
581                        it.getValue().subjects.each() { subjectId ->
582                                flow.subjects[ subjectId ].name = params.get('subject_' + subjectId + '_name')
583                                flow.subjects[ subjectId ].species = Term.findByName(params.get('subject_' + subjectId + '_species'))
584
585                                // remember name and check for duplicates
586                                if (!names[ flow.subjects[ subjectId ].name ]) {
587                                        names[ flow.subjects[ subjectId ].name ] = [count: 1, first: 'subject_' + subjectId + '_name', firstId: subjectId]
588                                } else {
589                                        // duplicate name found, set error flag
590                                        names[ flow.subjects[ subjectId ].name ]['count']++
591
592                                        // second occurence?
593                                        if (names[ flow.subjects[ subjectId ].name ]['count'] == 2) {
594                                                // yeah, also mention the first
595                                                // occurrence in the error message
596                                                this.appendErrorMap(name: 'The subject name needs to be unique!', flash.errors, 'subject_' + names[ flow.subjects[ subjectId ].name ]['firstId'] + '_')
597                                        }
598
599                                        // add to error map
600                                        this.appendErrorMap([name: 'The subject name needs to be unique!'], flash.errors, 'subject_' + subjectId + '_')
601                                        errors = true
602                                }
603
604                                // iterate through template fields
605                                templateFields.each() { subjectField ->
606                                        def value = params.get('subject_' + subjectId + '_' + subjectField.name)
607
608// TODO: UNCOMMENT THIS         if (value) flow.subjects[ subjectId ].setFieldValue(subjectField.name, value)
609                                }
610
611                                // validate subject
612                                if (!flow.subjects[ subjectId ].validate()) {
613                                        errors = true
614                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors)
615                                }
616                        }
617                }
618
619                return !errors
620        }
621
622        /**
623         * return the object from a map of objects by searching for a name
624         * @param String name
625         * @param Map map of objects
626         * @return Object
627         */
628        def getObjectByName(name, map) {
629                def result = null
630                map.each() {
631                        if (it.name == name) {
632                                result = it
633                        }
634                }
635
636                return result
637        }
638
639        /**
640         * transform domain class validation errors into a human readable
641         * linked hash map
642         * @param object validated domain class
643         * @returns object  linkedHashMap
644         */
645        def getHumanReadableErrors(object) {
646                def errors = new LinkedHashMap()
647
648                object.errors.getAllErrors().each() {
649                        errors[it.getArguments()[0]] = it.getDefaultMessage()
650                }
651
652                return errors
653        }
654
655        /**
656         * append errors of a particular object to a map
657         * @param object
658         * @param map linkedHashMap
659         * @void
660         */
661        def appendErrors(object, map) {
662                this.appendErrorMap(this.getHumanReadableErrors(object), map)
663        }
664
665        def appendErrors(object, map, prepend) {
666                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
667        }
668
669        /**
670         * append errors of one map to another map
671         * @param map linkedHashMap
672         * @param map linkedHashMap
673         * @void
674         */
675        def appendErrorMap(map, mapToExtend) {
676                map.each() {key, value ->
677                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
678                }
679        }
680
681        def appendErrorMap(map, mapToExtend, prepend) {
682                map.each() {key, value ->
683                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
684                }
685        }
686}
Note: See TracBrowser for help on using the repository browser.