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

Last change on this file since 240 was 240, checked in by duh, 11 years ago
  • added ajaxOnChange parameter support for <wizard:...> elements. ajaxOnChange now performs an ajaxSubmit to the current form. Usefull for handeling select changes and dynamic forms. When using the ajaxOnChange attribute you can also add the 'url', 'update' and 'afterSuccess' parameters. In your webflow the action is triggered that you put in the ajaxOnChange argument.
  • example:

<wizard:templateElement name="template" description="Template" value="${study?.template}" entity="${dbnp.studycapturing.Subject}" ajaxOnChange="switchTemplate" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" afterSuccess="onWizardPage()" >

The template to use for this study

</wizard:templateElement>

Will submit on change, triggering the 'switchTemplate' action in your current webflow:

on("switchTemplate") {

println params

}.to "study"

  • Property svn:keywords set to
    Date
    Author
    Rev
File size: 16.8 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: 240 $
19 * $Author: duh $
20 * $Date: 2010-03-05 16:26:05 +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                // create or modify a study
74                start {
75                        render(view: "_start")
76                        onRender {
77                                flow.page = 1
78                        }
79                        on("next") {
80
81                        }.to "study"
82                }
83
84                // render and handle the study page
85                // TODO: make sure both template as well as logic will
86                //       handle Study templates as well!!!
87                study {
88                        render(view: "_study")
89                        onRender {
90                                flow.page = 2
91                        }
92                        on("switchTemplate") {
93                                println "switching template..."
94                                println params
95                                this.handleStudy(flow, flash, params)
96                        }.to "study"
97                        on("previous") {
98                                flash.errors = new LinkedHashMap()
99
100                                if (this.handleStudy(flow, flash, params)) {
101                                        success()
102                                } else {
103                                        error()
104                                }
105                        }.to "start"
106                        on("next") {
107                                flash.errors = new LinkedHashMap()
108
109                                if (this.handleStudy(flow, flash, params)) {
110                                        success()
111                                } else {
112                                        error()
113                                }
114                        }.to "subjects"
115                }
116
117                // render and handle subjects page
118                subjects {
119                        render(view: "_subjects")
120                        onRender {
121                                flow.page = 3
122
123                                if (!flow.subjects) {
124                                        flow.subjects = []
125                                        flow.subjectTemplates = new LinkedHashMap()
126                                }
127                        }
128                        on("add") {
129                                // fetch species by name (as posted by the form)
130                                def speciesTerm = Term.findByName(params.addSpecies)
131                                def subjectTemplateName = params.get('template')
132                                def subjectTemplate     = Template.findByName(subjectTemplateName)
133
134                                // add this subject template to the subject template array
135                                if (!flow.subjectTemplates[ subjectTemplateName ]) {
136                                        flow.subjectTemplates[ subjectTemplateName ] = [
137                                                name: subjectTemplateName,
138                                                template: subjectTemplate,
139                                                subjects: []
140                                        ]
141                                }
142
143                                // add x subject of species y
144                                (params.addNumber as int).times {
145                                        def increment = flow.subjects.size()
146                                        def subject = new Subject(
147                                                name: 'Subject ' + (increment + 1),
148                                                species: speciesTerm,
149                                                template: subjectTemplate
150                                        )
151
152                                        // instantiate a new Subject
153                                        flow.subjects[ increment ] = subject
154
155                                        // and remember the subject id with the template
156                                        def subjectsSize = flow.subjectTemplates[ subjectTemplateName ]['subjects'].size()
157                                        flow.subjectTemplates[ subjectTemplateName ]['subjects'][ subjectsSize ] = increment
158                                }
159                        }.to "subjects"
160                        on("next") {
161                                println flow.subjectTemplates
162                                println flow.subjects
163                                flash.errors = new LinkedHashMap()
164
165                                // check if we have at least one subject
166                                // and check form data
167                                if (flow.subjects.size() < 1) {
168                                        // append error map
169                                        this.appendErrorMap(['subjects': 'You need at least to create one subject for your study'], flash.errors)
170                                        error()
171                                } else if (!this.handleSubjects(flow, flash, params)) {
172                                        error()
173                                } else {
174                                        success()
175                                }
176                        }.to "eventDescriptions"
177                        on("previous") {
178                                flash.errors = new LinkedHashMap()
179
180                                // handle form data
181                                if (!this.handleSubjects(flow, flash, params)) {
182                                        error()
183                                } else {
184                                        success()
185                                }
186                        }.to "study"
187                }
188
189                // render page three
190                eventDescriptions {
191                        render(view: "_eventDescriptions")
192                        onRender {
193                                flow.page = 4
194
195                                if (!flow.eventDescriptions) {
196                                        flow.eventDescriptions = []
197                                }
198                        }
199                        on("add") {
200                                // fetch classification by name (as posted by the form)
201                                //params.classification = Term.findByName(params.classification)
202
203                                // transform checkbox form value to boolean
204                                params.isSamplingEvent = (params.containsKey('isSamplingEvent'))
205
206                                // instantiate EventDescription with parameters
207                                def eventDescription = new EventDescription(params)
208
209                                // validate
210                                if (eventDescription.validate()) {
211                                        def increment = flow.eventDescriptions.size()
212                                        flow.eventDescriptions[increment] = eventDescription
213                                        success()
214                                } else {
215                                        // validation failed, feedback errors
216                                        flash.errors = new LinkedHashMap()
217                                        flash.values = params
218                                        this.appendErrors(eventDescription, flash.errors)
219                                        error()
220                                }
221                        }.to "eventDescriptions"
222                        on("delete") {
223                                def delete = params.get('do') as int;
224
225                                // handle form data
226                                if (!this.handleEventDescriptions(flow, flash, params)) {
227                                        flash.values = params
228                                        error()
229                                } else {
230                                        success()
231                                }
232
233                                // remove eventDescription
234                                if (flow.eventDescriptions[ delete ] && flow.eventDescriptions[ delete ] instanceof EventDescription) {
235                                        // remove all events based on this eventDescription
236                                        for ( i in flow.events.size()..0 ) {
237                                                if (flow.events[ i ] && flow.events[ i ].eventDescription == flow.eventDescriptions[ delete ]) {
238                                                        flow.events.remove(i)
239                                                }
240                                        }
241
242                                        flow.eventDescriptions.remove(delete)
243                                }
244                        }.to "eventDescriptions"
245                        on("previous") {
246                                flash.errors = new LinkedHashMap()
247
248                                // handle form data
249                                if (!this.handleEventDescriptions(flow, flash, params)) {
250                                        flash.values = params
251                                        error()
252                                } else {
253                                        success()
254                                }
255                        }.to "subjects"
256                        on("next") {
257                                flash.errors = new LinkedHashMap()
258
259                                // check if we have at least one subject
260                                // and check form data
261                                if (flow.eventDescriptions.size() < 1) {
262                                        // append error map
263                                        flash.values = params
264                                        this.appendErrorMap(['eventDescriptions': 'You need at least to create one eventDescription for your study'], flash.errors)
265                                        error()
266                                } else if (!this.handleEventDescriptions(flow, flash, params)) {
267                                        flash.values = params
268                                        error()
269                                } else {
270                                        success()
271                                }
272                        }.to "events"
273                }
274
275                // render events page
276                events {
277                        render(view: "_events")
278                        onRender {
279                                flow.page = 5
280
281                                if (!flow.events) {
282                                        flow.events = []
283                                }
284
285                                if (!flow.eventGroups) {
286                                        flow.eventGroups = []
287                                        flow.eventGroups[0] = new EventGroup(name: 'Group 1')   // 1 group by default
288                                }
289                        }
290                        on("add") {
291                                // create date instances from date string?
292                                // @see WizardTagLibrary::timeElement{...}
293                                if (params.get('startTime')) {
294                                        params.startTime = new Date().parse("d/M/yyyy HH:mm", params.get('startTime').toString())
295                                }
296                                if (params.get('endTime')) {
297                                        params.get('endTime').toString()
298                                        params.endTime = new Date().parse("d/M/yyyy HH:mm", params.get('endTime').toString())
299                                }
300
301                                // get eventDescription instance by name
302                                params.eventDescription = this.getObjectByName(params.get('eventDescription'), flow.eventDescriptions)
303
304                                // instantiate Event with parameters
305                                def event = new Event(params)
306
307                                // handle event groupings
308                                this.handleEventGrouping(flow, flash, params)
309
310                                // validate event
311                                if (event.validate()) {
312                                        def increment = flow.events.size()
313                                        flow.events[increment] = event
314                                        success()
315                                } else {
316                                        // validation failed, feedback errors
317                                        flash.errors = new LinkedHashMap()
318                                        flash.values = params
319                                        this.appendErrors(event, flash.errors)
320
321                                        flash.startTime = params.startTime
322                                        flash.endTime = params.endTime
323                                        flash.eventDescription = params.eventDescription
324
325                                        error()
326                                }
327                        }.to "events"
328                        on("deleteEvent") {
329                                flash.values = params
330                                def delete = params.get('do') as int;
331
332                                // handle event groupings
333                                this.handleEventGrouping(flow, flash, params)
334
335                                // remove event
336                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
337                                        flow.events.remove(delete)
338                                }
339                        }.to "events"
340                        on("addEventGroup") {
341                                flash.values = params
342                               
343                                // handle event groupings
344                                this.handleEventGrouping(flow, flash, params)
345
346                                def increment = flow.eventGroups.size()
347                                def groupName = "Group " + (increment + 1)
348
349                                // check if group name exists
350                                def nameExists = true
351                                def u = 0
352
353                                // make sure a unique name is generated
354                                while (nameExists) {
355                                        u++
356                                        def count = 0
357                                       
358                                        flow.eventGroups.each() {
359                                                if (it.name == groupName) {
360                                                        groupName = "Group " + (increment + 1) + "," + u
361                                                } else {
362                                                        count++
363                                                }
364                                        }
365
366                                        nameExists = !(count == flow.eventGroups.size())
367                                }
368
369                                flow.eventGroups[increment] = new EventGroup(name: groupName)
370                        }.to "events"
371                        on("deleteEventGroup") {
372                                flash.values = params
373                               
374                                def delete = params.get('do') as int;
375
376                                // handle event groupings
377                                this.handleEventGrouping(flow, flash, params)
378
379                                // remove the group with this specific id
380                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
381                                        // remove this eventGroup
382                                        flow.eventGroups.remove(delete)
383                                }
384                        }.to "events"
385                        on("previous") {
386                                // handle event groupings
387                                this.handleEventGrouping(flow, flash, params)
388                        }.to "eventDescriptions"
389                        on("next") {
390                                flash.values = params
391                               
392                                flash.errors = new LinkedHashMap()
393
394                                // handle event groupings
395                                this.handleEventGrouping(flow, flash, params)
396
397                                // check if we have at least one subject
398                                // and check form data
399                                if (flow.events.size() < 1) {
400                                        // append error map
401                                        flash.values = params
402                                        this.appendErrorMap(['events': 'You need at least to create one event for your study'], flash.errors)
403                                        error()
404                                }
405                        }.to "events"
406                }
407
408                confirm {
409                        render(view: "_confirmation")
410                        onRender {
411                                flow.page = 6
412                        }
413                        on("previous") {
414                                // do nothing
415                        }.to "events"
416                        on("next") {
417                                // store everything in the database!
418                                success()
419                        }.to "confirm"
420                }
421
422                // render page three
423                done {
424                        render(view: "_done")
425                        onRender {
426                                flow.page = 6
427                        }
428                        on("previous") {
429                                // TODO
430                        }.to "confirm"
431                }
432        }
433
434        /**
435         * re-usable code for handling study form data in a web flow
436         * @param Map LocalAttributeMap (the flow scope)
437         * @param Map localAttributeMap (the flash scope)
438         * @param Map GrailsParameterMap (the flow parameters = form data)
439         * @returns boolean
440         */
441        def handleStudy(flow, flash, params) {
442                // create study instance if we have none
443                if (!flow.study) flow.study = new Study();
444
445                // create date instance from date string?
446                // @see WizardTagLibrary::dateElement{...}
447                if (params.get('startDate')) {
448                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
449                } else {
450                        params.remove('startDate')
451                }
452
453                // if a template is selected, get template instance
454                if (params.get('template')) {
455                        params.template = Template.findByName(params.get('template'))
456                }
457
458                // update study instance with parameters
459                params.each() {key, value ->
460                        if (flow.study.hasProperty(key)) {
461                                flow.study.setProperty(key, value);
462                        }
463                }
464
465                // validate study
466                if (flow.study.validate()) {
467                        return true
468                } else {
469                        // validation failed, feedback errors
470                        flash.errors = new LinkedHashMap()
471                        this.appendErrors(flow.study, flash.errors)
472                        return false
473                }
474        }
475
476        /**
477         * re-usable code for handling eventDescription form data in a web flow
478         * @param Map LocalAttributeMap (the flow scope)
479         * @param Map localAttributeMap (the flash scope)
480         * @param Map GrailsParameterMap (the flow parameters = form data)
481         * @returns boolean
482         */
483        def handleEventDescriptions(flow, flash, params) {
484                def names = new LinkedHashMap()
485                def errors = false
486                def id = 0
487
488                flow.eventDescriptions.each() {
489                        it.name = params.get('eventDescription_' + id + '_name')
490                        it.description = params.get('eventDescription_' + id + '_description')
491                        //it.classification = Term.findByName(params.get('eventDescription_' + id + '_classification'))
492                        it.isSamplingEvent = (params.containsKey('eventDescription_' + id + '_isSamplingEvent'))
493
494                        // validate eventDescription
495                        if (!it.validate()) {
496                                errors = true
497                                this.appendErrors(it, flash.errors, 'eventDescription_' + id + '_')
498                        }
499
500                        id++
501                }
502
503                return !errors
504        }
505
506        /**
507         * re-usable code for handling event grouping in a web flow
508         * @param Map LocalAttributeMap (the flow scope)
509         * @param Map localAttributeMap (the flash scope)
510         * @param Map GrailsParameterMap (the flow parameters = form data)
511         * @returns boolean
512         */
513        def handleEventGrouping(flow, flash, params) {
514                // walk through eventGroups
515                def g = 0
516                flow.eventGroups.each() {
517                        def e = 0
518                        def eventGroup = it
519
520                        // reset events
521                        eventGroup.events = new HashSet()
522
523                        // walk through events
524                        flow.events.each() {
525                                if (params.get('event_' + e + '_group_' + g) == 'on') {
526                                        eventGroup.addToEvents(it)
527                                }
528                                e++
529                        }
530                        g++
531                }
532        }
533
534        /**
535         * re-usable code for handling subject form data in a web flow
536         * @param Map LocalAttributeMap (the flow scope)
537         * @param Map localAttributeMap (the flash scope)
538         * @param Map GrailsParameterMap (the flow parameters = form data)
539         * @returns boolean
540         */
541        def handleSubjects(flow, flash, params) {
542                def names = new LinkedHashMap();
543                def errors = false;
544                def id = 0;
545
546                // iterate through subject templates
547                flow.subjectTemplates.each() {
548                        def subjectTemplate = it.getValue().template
549                        def templateFields      = subjectTemplate.fields
550
551                        // iterate through subjects
552                        it.getValue().subjects.each() { subjectId ->
553                                flow.subjects[ subjectId ].name = params.get('subject_' + subjectId + '_name')
554                                flow.subjects[ subjectId ].species = Term.findByName(params.get('subject_' + subjectId + '_species'))
555
556                                // remember name and check for duplicates
557                                if (!names[ flow.subjects[ subjectId ].name ]) {
558                                        names[ flow.subjects[ subjectId ].name ] = [count: 1, first: 'subject_' + subjectId + '_name', firstId: subjectId]
559                                } else {
560                                        // duplicate name found, set error flag
561                                        names[ flow.subjects[ subjectId ].name ]['count']++
562
563                                        // second occurence?
564                                        if (names[ flow.subjects[ subjectId ].name ]['count'] == 2) {
565                                                // yeah, also mention the first
566                                                // occurrence in the error message
567                                                this.appendErrorMap(name: 'The subject name needs to be unique!', flash.errors, 'subject_' + names[ flow.subjects[ subjectId ].name ]['firstId'] + '_')
568                                        }
569
570                                        // add to error map
571                                        this.appendErrorMap([name: 'The subject name needs to be unique!'], flash.errors, 'subject_' + subjectId + '_')
572                                        errors = true
573                                }
574
575                                // iterate through template fields
576                                templateFields.each() { subjectField ->
577                                        def value = params.get('subject_' + subjectId + '_' + subjectField.name)
578
579// TODO: UNCOMMENT THIS         if (value) flow.subjects[ subjectId ].setFieldValue(subjectField.name, value)
580                                }
581
582                                // validate subject
583                                if (!flow.subjects[ subjectId ].validate()) {
584                                        errors = true
585                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors)
586                                }
587                        }
588                }
589
590                return !errors
591        }
592
593        /**
594         * return the object from a map of objects by searching for a name
595         * @param String name
596         * @param Map map of objects
597         * @return Object
598         */
599        def getObjectByName(name, map) {
600                def result = null
601                map.each() {
602                        if (it.name == name) {
603                                result = it
604                        }
605                }
606
607                return result
608        }
609
610        /**
611         * transform domain class validation errors into a human readable
612         * linked hash map
613         * @param object validated domain class
614         * @returns object  linkedHashMap
615         */
616        def getHumanReadableErrors(object) {
617                def errors = new LinkedHashMap()
618
619                object.errors.getAllErrors().each() {
620                        errors[it.getArguments()[0]] = it.getDefaultMessage()
621                }
622
623                return errors
624        }
625
626        /**
627         * append errors of a particular object to a map
628         * @param object
629         * @param map linkedHashMap
630         * @void
631         */
632        def appendErrors(object, map) {
633                this.appendErrorMap(this.getHumanReadableErrors(object), map)
634        }
635
636        def appendErrors(object, map, prepend) {
637                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
638        }
639
640        /**
641         * append errors of one map to another map
642         * @param map linkedHashMap
643         * @param map linkedHashMap
644         * @void
645         */
646        def appendErrorMap(map, mapToExtend) {
647                map.each() {key, value ->
648                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
649                }
650        }
651
652        def appendErrorMap(map, mapToExtend, prepend) {
653                map.each() {key, value ->
654                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
655                }
656        }
657}
Note: See TracBrowser for help on using the repository browser.