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

Last change on this file since 778 was 778, checked in by duh, 10 years ago
  • big refactoring of the Study Capture Wizard until the grouping page, removing all internal maps and directory work on the study object to
    • simplify internal representation and handling
    • added static incremental identifiers to TemplateEntity?, EventGroup? and Template for use in dynamic web forms
  • TODO: fix the sample page
  • TODO: check cascaded deletes, confirm database content
  • Property svn:keywords set to Date Author Rev
File size: 33.5 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.data.*
4
5// Grails convertors is imported in order to create JSON objects
6import grails.converters.*
7
8
9/**
10 * Wizard Controler
11 *
12 * The wizard controller handles the handeling of pages and data flow
13 * through the study capturing wizard.
14 *
15 * @author Jeroen Wesbeek
16 * @since 20100107
17 * @package studycapturing
18 *
19 * Revision information:
20 * $Rev: 778 $
21 * $Author: duh $
22 * $Date: 2010-08-05 20:58:09 +0000 (do, 05 aug 2010) $
23 */
24class WizardController {
25        /**
26         * index method, redirect to the webflow
27         * @void
28         */
29        def index = {
30                def jump = [:]
31
32                // allow quickjumps to:
33                //      edit a study    : /wizard?jump=edit&id=1
34                //      create a study  : /wizard?jump=create
35                if (params.get('jump')) {
36                        switch (params.get('jump')) {
37                                case 'create':
38                                        jump = [
39                                            action: 'create'
40                                        ]
41                                        break
42                                case 'edit':
43                                        jump = [
44                                            action      : 'edit',
45                                                id              : params.get('id')
46                                        ]
47                                        break
48                                default:
49                                        break
50                        }
51                }
52
53                // store in session
54                session.jump = jump
55
56                /**
57                 * Do you believe it in your head?
58                 * I can go with the flow
59                 * Don't say it doesn't matter (with the flow) matter anymore
60                 * I can go with the flow (I can go)
61                 * Do you believe it in your head?
62                 */
63                redirect(action: 'pages')
64        }
65
66        /**
67         * WebFlow definition
68         * @see http://grails.org/WebFlow
69         * @void
70         */
71        def pagesFlow = {
72                // start the flow
73                onStart {
74                        // define flow variables
75                        flow.page = 0
76                        flow.pages = [
77                                //[title: 'Templates'],                 // templates
78                                [title: 'Start'],                               // load or create a study
79                                [title: 'Study'],                               // study
80                                [title: 'Subjects'],                    // subjects
81                                [title: 'Events'],                              // events and event grouping
82                                [title: 'Groups'],                              // groups
83                                [title: 'Samples'],                             // samples
84                                [title: 'Confirmation'],                // confirmation page
85                                [title: 'Done']                                 // finish page
86                        ]
87                        flow.jump = session.jump
88                        success()
89                }
90
91                // render the main wizard page which immediately
92                // triggers the 'next' action (hence, the main
93                // page dynamically renders the study template
94                // and makes the flow jump to the study logic)
95                mainPage {
96                        render(view: "/wizard/index")
97                        onRender {
98                                flow.page = 1
99                                success()
100                        }
101                        on("next").to "handleJump"
102                }
103
104                // handle the jump parameter
105                //
106                // I came to get down [2x]
107                // So get out your seats and jump around
108                // Jump around [3x]
109                // Jump up Jump up and get down
110                // Jump [18x]
111                handleJump {
112                        action {
113                                if (flow.jump && flow.jump.action == 'edit' && flow.jump.id) {
114                                        // load study
115                                        if (this.loadStudy(flow, flash, [studyid:flow.jump.id])) {
116                                                toStudyPage()
117                                        } else {
118                                                toStartPage()
119                                        }
120                                } else if (flow.jump && flow.jump.action == 'create') {
121                                        toStudyPage()
122                                } else {
123                                        toStartPage()
124                                }
125                        }
126                        on("toStartPage").to "start"
127                        on("toStudyPage").to "study"
128                }
129
130                // create or modify a study
131                start {
132                        render(view: "_start")
133                        onRender {
134                                flow.page = 1
135                                success()
136                        }
137                        on("next") {
138                                // clean the flow scope
139                                flow.remove('study')
140
141                                // set 'quicksave' variable to false
142                                flow.quickSave = false
143                        }.to "study"
144                        on("modify").to "modify"
145                        on("import").to "redirectToImport"
146                }
147
148                // redirect to the import wizard
149                redirectToImport {
150                        render(view: "_redirect")
151                        onRender {
152                                flash.uri = "/importer/index"
153                        }
154                        on("next").to "start"
155                }
156
157                // load a study to modify
158                modify {
159                        render(view: "_modify")
160                        onRender {
161                                flow.page = 1
162                                flash.cancel = true
163                                success()
164                        }
165                        on("cancel") {
166                                flow.remove('study')
167
168                                success()
169                        }.to "start"
170                        on("next") {
171                                // load study
172                                if (this.loadStudy(flow, flash, params)) {
173                                        success()
174                                } else {
175                                        error()
176                                }
177                        }.to "study"
178                }
179
180                // render and handle the study page
181                study {
182                        render(view: "_study")
183                        onRender {
184                                flow.page = 2
185                                success()
186                        }
187                        on("refresh") {
188                                // handle form data
189                                studyPage(flow, flash, params)
190
191                                // force refresh of the template
192                                if (flow.study.template && flow.study.template instanceof Template) {
193                                        flow.study.template.refresh()
194                                }
195
196                                // reset errors
197                                flash.errors = [:]
198                                success()
199                        }.to "study"
200            on("switchTemplate") {
201                                // handle form data
202                                studyPage(flow, flash, params)
203
204                                // reset errors
205                                flash.errors = [:]
206                                success()
207                        }.to "study"
208                        on("previous") {
209                                // handle form data
210                                studyPage(flow, flash, params)
211
212                                // reset errors
213                                flash.errors = [:]
214                                success()
215                        }.to "start"
216                        on("next") {
217                                studyPage(flow, flash, params) ? success() : error()
218                        }.to "subjects"
219                        on("quickSave") {
220                                studyPage(flow, flash, params) ? success() : error()
221                        }.to "waitForSave"
222                }
223
224                // render and handle subjects page
225                subjects {
226                        render(view: "_subjects")
227                        onRender {
228                                flow.page = 3
229
230                                if (!flash.values || !flash.values.addNumber) flash.values = [addNumber:1]
231
232                                success()
233                        }
234                        on("refresh") {
235                                // remember the params in the flash scope
236                                flash.values = params
237
238                                // refresh templates
239                                flow.study.giveSubjectTemplates().each() {
240                                        it.refresh()
241                                }
242
243                                success()
244                        }.to "subjects"
245                        on("add") {
246                                // handle form data
247                                addSubjects(flow, flash, params) ? success() : error()
248                        }.to "subjects"
249                        on("delete") {
250                                // handle form data
251                                subjectPage(flow, flash, params)
252
253                                // reset errors
254                                flash.errors = [:]
255
256                                // remove subject
257                                def subjectToRemove = flow.study.subjects.find { it.identifier == (params.get('do') as int) }
258                                if (subjectToRemove) {
259                                        flow.study.deleteSubject( subjectToRemove )
260                                }
261                        }.to "subjects"
262                        on("previous") {
263                                // handle form data
264                                subjectPage(flow, flash, params)
265
266                                // reset errors
267                                flash.errors = [:]
268                                success()
269                        }.to "study"
270                        on("next") {
271                                // handle form data
272                                subjectPage(flow, flash, params) ? success() : error()
273                        }.to "events"
274                        on("quickSave") {                               
275                                // handle form data
276                                subjectPage(flow, flash, params) ? success() : error()
277                        }.to "waitForSave"
278                }
279
280                // render events page
281                events {
282                        render(view: "_events")
283                        onRender {
284                                flow.page = 4
285
286                                // add initial eventGroup to study
287                                if (!flow.study.eventGroups?.size()) {
288                                        flow.study.addToEventGroups(
289                                                new EventGroup(name: 'Group 1')
290                                        )
291                                }
292
293                                success()
294                        }
295                        on("clear") {
296                                // remove all events
297                                (flow.study.events + flow.study.samplingEvents).each() { event ->
298                                        if (event instanceof SamplingEvent) {
299                                                flow.study.deleteSamplingEvent( event )
300                                        } else {
301                                                flow.study.deleteEvent( event )
302                                        }
303                                }
304
305                                success()
306                        }.to "events"
307                        on("switchTemplate") {
308                                // handle form data
309                                eventPage(flow, flash, params)
310
311                                // get template
312                                def type        = params.get('eventType')
313                                def template= Template.findByName( params.get( type + 'Template' ) )
314
315                                // change template and/or instance?
316                                if (!flow.event || (flow.event instanceof Event && type == "sample") || (flow.event instanceof SamplingEvent && type == "event")) {
317                                        // create new instance
318                                        flow.event = (type == "event") ? new Event(template: template, parent: flow.study) : new SamplingEvent(template: template, parent: flow.study)
319                                } else {
320                                        flow.event.template = template
321                                }
322
323                                // reset errors
324                                flash.errors = [:]
325                                success()
326
327                        }.to "events"
328                        on("refresh") {
329                                // handle form data
330                                eventPage(flow, flash, params)
331
332                                // refresh templates
333                                flow.study.giveEventTemplates().each() {
334                                        it.refresh()
335                                }
336
337                                // refresh event template
338                                if (flow.event?.template) flow.event.template.refresh()
339
340                                // reset errors
341                                flash.errors = [:]
342                                success()
343                        }.to "events"
344                        on("add") {
345                                // handle form data
346                                eventPage(flow, flash, params)
347
348                                // reset errors
349                                flash.errors = [:]
350
351                                // validate event
352                                if (flow.event.validate()) {
353                                        // validates properly
354                                        if (flow.event instanceof SamplingEvent) {
355                                                flow.study.addToSamplingEvents( flow.event )
356                                        } else {
357                                                flow.study.addToEvents( flow.event )
358                                        }
359
360                                        // remove event from the flowscope
361                                        flow.remove('event')
362                                       
363                                        success()
364                                } else {
365                                        // event does not validate
366                                        this.appendErrors(flow.event, flash.errors)
367                                        error()
368                                }
369                        }.to "events"
370                        on("deleteEvent") {
371                                // handle form data
372                                eventPage(flow, flash, params)
373
374                                // reset errors
375                                flash.errors = [:]
376
377                                // find matching (sampling) event
378                                def event                       = flow.study.events.find { it.getIdentifier() == (params.get('do') as int) }
379                                def samplingEvent       = flow.study.samplingEvents.find { it.getIdentifier() == (params.get('do') as int) }
380
381                                // perform delete
382                                if (event) flow.study.deleteEvent( event )
383                                if (samplingEvent) flow.study.deleteSamplingEvent( samplingEvent )
384                        }.to "events"
385                        on("addEventGroup") {
386                                // handle form data
387                                eventPage(flow, flash, params)
388
389                                // first find the current maximum default name "Subject xxxx"
390                                def eventGroupNo = 0
391                                def eventGroupMax = 0
392                                flow.study.eventGroups.each() {
393                                        it.name.trim().eachMatch(/(\d+)$/){
394                                                eventGroupNo = it[1] as int
395                                                if (eventGroupNo > eventGroupMax) {
396                                                        eventGroupMax = eventGroupNo
397                                                }
398                                        }
399                                }
400
401                                // add a new eventGroup
402                                flow.study.addToEventGroups(
403                                        new EventGroup(
404                                                name    : 'Group ' + (eventGroupMax+1),
405                                                parent  : flow.study
406                                        )
407                                )
408
409                                // reset errors
410                                flash.errors = [:]
411                                success()
412                        }.to "events"
413                        on("deleteEventGroup") {
414                                // handle form data
415                                eventPage(flow, flash, params)
416
417                                // reset errors
418                                flash.errors = [:]
419
420                                // remove eventGroup
421                                def eventGroupToRemove = flow.study.eventGroups.find { it.getIdentifier() == (params.get('do') as int) }
422                                if (eventGroupToRemove) {
423                                        flow.study.deleteEventGroup( eventGroupToRemove )
424                                }
425                        }.to "events"
426                        on("previous") {
427                                // handle form data
428                                eventPage(flow, flash, params)
429
430                                // reset errors
431                                flash.errors = [:]
432                                success()
433                        }.to "subjects"
434                        on("next") {
435                                // handle form data
436                                eventPage(flow, flash, params) ? success() : error()
437                        }.to "groups"
438                        on("quickSave") {
439                                // handle form data
440                                eventPage(flow, flash, params) ? success() : error()
441                        }.to "waitForSave"
442                }
443
444                // groups page
445                groups {
446                        render(view: "_groups")
447                        onRender {
448                                flow.page = 5
449                                success()
450                        }
451                        on("previous") {
452                                // handle form data
453                                groupPage(flow, flash, params) ? success() : error()
454                        }.to "events"
455                        on("next") {
456                                // handle form data
457                                groupPage(flow, flash, params) ? success() : error()
458                        }.to "waitForSave"
459                        on("quickSave") {
460                                // handle form data
461                                groupPage(flow, flash, params) ? success() : error()
462                        }.to "waitForSave"
463                }
464
465                // sample 'previous' page with warning
466                samplePrevious {
467                        render(view: "_samples_previous_warning")
468                        onRender {
469                                flow.page = 6
470
471                                // TEMPORARY FIX TO REMOVE ALL SAMPLES AND REGENERATE THEM
472                                // THEN USER BROWSED BACK
473                                println ".removing samples from study"
474
475                                // remove samples from study
476                                flow.samples.each() {
477                                        flow.study.removeFromSamples(it.sample)
478                                }
479
480                                // remove samples from flow
481                                flow.remove('samples')
482                                // END FIX
483                        }
484                        on("next").to "samples"
485                        on("previous").to "groups"
486                }
487
488                // samples page
489                samples {
490                        render(view: "_samples")
491                        onRender {
492                                flow.page = 6
493                                /*
494                                // iterate through eventGroups
495                                if (!flow.samples) {
496                                        println ".generating samples"
497                                        flow.samplesWithTemplate = 0
498                                        flow.samples = []
499                                        flow.sampleTemplates = [:]
500                                        flow.eventGroups.each() { eventGroup ->
501                                                // iterate through events
502                                                eventGroup.samplingEvents.each() { samplingEvent ->
503                                                        def samplingEventName = this.ucwords(samplingEvent.template.name)
504
505                                                        // iterate through subjects
506                                                        eventGroup.subjects.each() { subject ->
507                                                                def sampleName = (this.ucwords(subject.name) + '_' + samplingEventName + '_' + new RelTime(samplingEvent.startTime).toString()).replaceAll("([ ]{1,})", "")
508                                                                def increment = flow.samples.size()
509
510                                                                flow.samples[increment] = [
511                                                                        sample: new Sample(
512                                                                                parent: flow.study,
513                                                                                parentSubject: subject,
514                                                                                parentEvent: samplingEvent,
515                                                                                name: sampleName
516                                                                        ),
517                                                                        name: sampleName,
518                                                                        eventGroup: eventGroup,
519                                                                        event: samplingEvent,
520                                                                        subject: subject
521                                                                ]
522
523                                                                // and add this sample to the study
524                                                                flow.study.addToSamples(flow.samples[increment].sample)
525                                                        }
526                                                }
527                                        }
528                                } else if (flash.check) {
529                                        println "CHECKING SAMPLE CONSISTENCY"
530                                        // check the consistency of the samples
531                                        flow.samples.each() { sampleData ->
532                                                println sampleData
533                                                println sampleData.event.template
534                                        }
535                                }
536
537                                success()
538                                */
539                        }
540                        on("switchTemplate") {
541                                //println params
542                                handleSamples(flow, flash, params)
543
544                                // ignore errors
545                                flash.errors = [:]
546                               
547                                succes()
548                        }.to "samples"
549                        on("refresh") {
550                                println ".refresh ${flow.sampleTemplates.size()} sample templates (${flow.samples.size()} samples present)"
551
552                                // refresh templates
553                                flow.sampleTemplates.each() {
554                                        println ".refresh template ["+it.value.name+"]"
555                                        it.value.template.refresh()
556                                        println "  --> fields: "+it.value.template.fields
557                                }
558
559                                // handle samples
560                                handleSamples(flow, flash, params)
561
562                                // ignore errors
563                                flash.errors = [:]
564
565                                success()
566                        }.to "samples"
567                        on("regenerate") {
568                                println ".removing 'samples' and 'sampleTemplates' from the flowscope, triggering regeneration of the samples..."
569                                flow.samples.each() {
570                                        flow.study.removeFromSamples( it.sample )
571                                }
572                                flow.remove('samples')
573                                flow.remove('sampleTemplates')
574                                println flow.study.samples
575                                success()
576                        }.to "samples"
577                        on("previous") {
578                                // handle samples
579                                handleSamples(flow, flash, params)
580
581                                // ignore errors
582                                flash.errors = [:]
583
584                                success()
585                        }.to "samplePrevious"
586                        on("next") {
587                                flash.values = params
588                                flash.errors = [:]
589
590// for now, development only!
591if (grails.util.GrailsUtil.environment == "development") {
592                                // do all samples have a template assigned?
593                                if (flow.samplesWithTemplate < flow.samples.size()) {
594                                        // handle samples
595                                        this.handleSamples(flow, flash, params)
596
597                                        // ignore errors
598                                        flash.errors = [:]
599                                       
600                                        // add error
601                                        this.appendErrorMap(['samples': 'you need to select a template for each sample'], flash.errors)
602
603                                        error()
604                                } else if (this.handleSamples(flow, flash, params)) {
605                                        success()
606                                } else {
607                                        error()
608                                }
609} else {
610        success()
611}
612                        }.to "confirm"
613                        on("quickSave") {
614                                // handle samples
615                                if (handleSamples(flow, flash, params)) {
616                                        success()
617                                } else {
618                                        error()
619                                }
620                        }.to "waitForSave"
621                }
622
623                // confirmation
624                confirm {
625                        render(view: "_confirmation")
626                        onRender {
627                                flow.page = 7
628                        }
629                        on("toStudy").to "study"
630                        on("toSubjects").to "subjects"
631                        on("toEvents").to "events"
632                        on("toGroups").to "groups"
633                        on("previous").to "samples"
634                        on("next").to "waitForSave"
635                        on("quickSave").to "waitForSave"
636                }
637
638                waitForSave {
639                        render(view: "_wait")
640                        onRender {
641                                flow.page = 8
642                        }
643                        on("next").to "save"
644                }
645
646                // store all study data
647                save {
648                        action {
649                                println "saving..."
650                                flash.errors = [:]
651
652                                // persist data to the database
653                                try {
654                                        // save study
655                                        println ".saving study"
656                                        if (!flow.study.save()) {
657                                                this.appendErrors(flow.study, flash.errors)
658                                                throw new Exception('error saving study')
659                                        }
660                                        println ".saved study "+flow.study+" (id: "+flow.study.id+")"
661
662                                        success()
663                                } catch (Exception e) {
664                                        // rollback
665                                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace' ], flash.errors)
666
667                                        // stacktrace in flash scope
668                                        flash.debug = e.getStackTrace()
669
670                                        error()
671                                }
672                        }
673                        on("error").to "error"
674                        on(Exception).to "error"
675                        on("success").to "done"
676                }
677
678                // error storing data
679                error {
680                        render(view: "_error")
681                        onRender {
682                                flow.page = 7
683                        }
684                        on("next").to "waitForSave"
685                        on("previous").to "samples"
686                }
687
688                // render finish page
689                done {
690                        render(view: "_done")
691                        onRender {
692                                flow.page = 8
693                        }
694                        onEnd {
695                                // clean flow scope
696                                flow.clear()
697                        }
698                }
699        }
700
701        /**
702         * load a study
703         * @param Map LocalAttributeMap (the flow scope)
704         * @param Map localAttributeMap (the flash scope)
705         * @param Map GrailsParameterMap (the flow parameters = form data)
706         * @returns boolean
707         */
708        def loadStudy(flow, flash, params) {
709                // load study
710                try {
711                        // load study
712                        flow.study = (params.studyid) ? Study.findById( params.studyid ) : Study.findByTitle( params.study )
713
714                        // set 'quicksave' variable
715                        flow.quickSave = true
716
717                        return true
718                } catch (Exception e) {
719                        // rollback
720                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace'], flash.errors)
721
722                        return false
723                }
724        }
725
726        /**
727         * re-usable code for handling samples
728         * @param Map LocalAttributeMap (the flow scope)
729         * @param Map localAttributeMap (the flash scope)
730         * @param Map GrailsParameterMap (the flow parameters = form data)
731         * @return boolean
732         */
733        def handleSamples(flow, flash, params) {
734                flash.errors = [:]
735                def errors = false             
736                def id = 0
737
738                // iterate through samples
739                flow.samples.each() { sampleData ->
740                        def sample = sampleData.sample
741                        def sampleTemplateName = params.get('template_'+id)
742                        def oldSampleTemplateName = sampleData.sample.template.toString()
743
744                        // has the sample template for this sample changed
745                        if (sampleTemplateName && sampleTemplateName.size() > 0 && oldSampleTemplateName != sampleTemplateName) {
746                                // yes, has the template changed?
747                                println ".changing template for sample ${id} to ${sampleTemplateName}"
748
749                                // decrease previous template count
750                                if (oldSampleTemplateName && flow.sampleTemplates[ oldSampleTemplateName ]) {
751                                        flow.sampleTemplates[ oldSampleTemplateName ].count--
752
753                                        if (flow.sampleTemplates[ oldSampleTemplateName ].count < 1) {
754                                                // no samples left, remove template altogether
755                                                flow.sampleTemplates.remove( oldSampleTemplateName )
756                                        }
757                                } else {
758                                        // increate main template counter?
759                                        flow.samplesWithTemplate++
760                                }
761
762                                // increase current template count
763                                if (!flow.sampleTemplates[ sampleTemplateName ]) {
764                                        flow.sampleTemplates[ sampleTemplateName ] = [
765                                                name            : sampleTemplateName,
766                                                template        : Template.findByName( sampleTemplateName ),
767                                                count           : 1
768                                        ]
769                                } else {
770                                        // increase count
771                                        flow.sampleTemplates[ sampleTemplateName ].count++
772                                }
773
774                                // change template
775                                sampleData.sample.template = flow.sampleTemplates[ sampleTemplateName ].template
776                        }
777
778                        // handle values
779                        sampleData.sample.giveFields().each() { sampleField ->
780                                if ( params.containsKey( 'sample_'+id+'_'+sampleField.escapedName() ) ) {
781                                        sampleData.sample.setFieldValue( sampleField.name, params.get( 'sample_'+id+'_'+sampleField.escapedName() ) )
782                                }
783                        }
784
785                        // validate sample
786                        if (!sampleData.sample.validate()) {
787                                errors = true
788                                this.appendErrors(sampleData.sample, flash.errors, 'sample_' + id + '_' )
789                        }
790
791                        // increase counter
792                        id++
793                }
794
795                return !errors
796        }
797
798        /**
799         * groovy / java equivalent of php's ucwords function
800         *
801         * Capitalize all first letters of seperate words
802         *
803         * @param String
804         * @return String
805         */
806        def ucwords(String text) {
807                def newText = ''
808
809                // change case to lowercase
810                text = text.toLowerCase()
811
812                // iterate through words
813                text.split(" ").each() {
814                        newText += it[0].toUpperCase() + it.substring(1) + " "
815                }
816
817                return newText.substring(0, newText.size()-1)
818        }
819
820        /**
821         * return the object from a map of objects by searching for a name
822         * @param String name
823         * @param Map map of objects
824         * @return Object
825         */
826        def getObjectByName(name, map) {
827                def result = null
828                map.each() {
829                        if (it.name == name) {
830                                result = it
831                        }
832                }
833
834                return result
835        }
836
837        /**
838         * transform domain class validation errors into a human readable
839         * linked hash map
840         * @param object validated domain class
841         * @return object  linkedHashMap
842         */
843        def getHumanReadableErrors(object) {
844                def errors = [:]
845                object.errors.getAllErrors().each() {
846                        def message = it.toString()
847
848                        //errors[it.getArguments()[0]] = it.getDefaultMessage()
849                        errors[it.getArguments()[0]] = message.substring(0, message.indexOf(';'))
850                }
851
852                return errors
853        }
854
855        /**
856         * append errors of a particular object to a map
857         * @param object
858         * @param map linkedHashMap
859         * @void
860         */
861        def appendErrors(object, map) {
862                this.appendErrorMap(this.getHumanReadableErrors(object), map)
863        }
864
865        def appendErrors(object, map, prepend) {
866                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
867        }
868
869        /**
870         * append errors of one map to another map
871         * @param map linkedHashMap
872         * @param map linkedHashMap
873         * @void
874         */
875        def appendErrorMap(map, mapToExtend) {
876                map.each() {key, value ->
877                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
878                }
879        }
880
881        def appendErrorMap(map, mapToExtend, prepend) {
882                map.each() {key, value ->
883                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
884                }
885        }
886
887        /**
888         * Parses a RelTime string and returns a nice human readable string
889         *
890         * @return Human Readable string or a HTTP response code 400 on error
891         */
892        def ajaxParseRelTime = {
893                if (params.reltime == null) {
894                        response.status = 400
895                        render('reltime parameter is expected')
896                }
897
898                try {
899                        def reltime = RelTime.parseRelTime(params.reltime)
900                        render reltime.toPrettyString()
901                } catch (IllegalArgumentException e) {
902                        response.status = 400
903                        render(e.getMessage())
904                }
905        }
906
907        /**
908         * Proxy for searching PubMed articles (or other articles from the Entrez DB).
909         *
910         * This proxy is needed because it is not allowed to fetch XML directly from a different
911         * domain using javascript. So we have the javascript call a function on our own domain
912         * and the proxy will fetch the data from Entrez
913         *
914         * @since       20100609
915         * @param       _utility        The name of the utility, without the complete path. Example: 'esearch.fcgi'
916         * @return      XML
917         */
918        def entrezProxy = {
919                // Remove unnecessary parameters
920                params.remove( "action" )
921                params.remove( "controller" )
922
923                def url = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils";
924                def util = params.remove( "_utility" )
925                def paramString = params.collect { k, v -> k + '=' + v.encodeAsURL() }.join( '&' );
926
927                def fullUrl = url + '/' + util + '?' + paramString;
928
929                // Return the output of the request
930                // render fullUrl;
931                render(
932                    text:           new URL( fullUrl ).getText(),
933                    contentType:    "text/xml",
934                    encoding:       "UTF-8"
935                );
936        }
937
938
939        /****** REFACTORED METHODS OVER HERE! ******/
940
941        /**
942         * Handle the wizard study page
943         *
944         * @param Map LocalAttributeMap (the flow scope)
945         * @param Map localAttributeMap (the flash scope)
946         * @param Map GrailsParameterMap (the flow parameters = form data)
947         * @returns boolean
948         */
949        def studyPage(flow, flash, params) {
950                // remember the params in the flash scope
951                flash.values = params
952               
953                // instantiate study of it is not yet present
954                if (!flow.study) flow.study = new Study()
955
956                // did the study template change?
957                if (params.get('template').size() && flow.study.template?.name != params.get('template')) {
958                        println ".change study template!"
959
960                        // yes, was the template already set?
961                        if (flow.study.template instanceof Template) {
962                                // yes, first make sure all values are unset?
963                                println "!!! check the database fields if data of a previous template remains in the database or is deleted by GORM!"
964                        }
965
966                        // set the template
967                        flow.study.template = Template.findByName(params.remove('template'))
968                }
969
970                // does the study have a template set?
971                if (flow.study.template && flow.study.template instanceof Template) {
972                        // yes, iterate through template fields
973                        flow.study.giveFields().each() {
974                                // and set their values
975                                flow.study.setFieldValue(it.name, params.get(it.escapedName()))
976                        }
977                }
978
979                // handle publications
980                handlePublications(flow, flash, params)
981
982                // handle contacts
983                handleContacts(flow, flash, params)
984
985                // validate the study
986                if (flow.study.validate()) {
987                        // instance is okay
988                        return true
989                } else {
990                        // validation failed
991                        flash.errors = [:]
992                        this.appendErrors(flow.study, flash.errors)
993                        return false
994                }
995        }
996
997        /**
998         * re-usable code for handling publications form data in a web flow
999         * @param Map LocalAttributeMap (the flow scope)
1000         * @param Map localAttributeMap (the flash scope)
1001         * @param Map GrailsParameterMap (the flow parameters = form data)
1002         * @returns boolean
1003         */
1004        def handlePublications(flow, flash, params) {
1005                if (!flow.study.publications) flow.study.publications = []
1006
1007                // Check the ids of the pubblications that should be attached
1008                // to this study. If they are already attached, keep 'm. If
1009                // studies are attached that are not in the selected (i.e. the
1010                // user deleted them), remove them
1011                def publicationIDs = params.get('publication_ids')
1012                if (publicationIDs) {
1013                        // Find the individual IDs and make integers
1014                        publicationIDs = publicationIDs.split(',').collect { Integer.parseInt(it, 10) }
1015
1016                        // First remove the publication that are not present in the array
1017                        flow.study.publications.removeAll { publication -> !publicationIDs.find { id -> id == publication.id } }
1018
1019                        // Add those publications not yet present in the database
1020                        publicationIDs.each { id ->
1021                                if (!flow.study.publications.find { publication -> id == publication.id }) {
1022                                        def publication = Publication.get(id)
1023                                        if (publication) {
1024                                                flow.study.addToPublications(publication)
1025                                        } else {
1026                                                println('.publication with ID ' + id + ' not found in database.')
1027                                        }
1028                                }
1029                        }
1030
1031                } else {
1032                        println('.no publications selected.')
1033                        flow.study.publications.clear()
1034                }
1035
1036        }
1037
1038        /**
1039         * re-usable code for handling contacts form data in a web flow
1040         * @param Map LocalAttributeMap (the flow scope)
1041         * @param Map localAttributeMap (the flash scope)
1042         * @param Map GrailsParameterMap (the flow parameters = form data)
1043         * @return boolean
1044         */
1045        def handleContacts(flow, flash, params) {
1046                if (!flow.study.persons) flow.study.persons = []
1047
1048                // Check the ids of the contacts that should be attached
1049                // to this study. If they are already attached, keep 'm. If
1050                // studies are attached that are not in the selected (i.e. the
1051                // user deleted them), remove them
1052
1053                // Contacts are saved as [person_id]-[role_id]
1054                def contactIDs = params.get('contacts_ids')
1055                if (contactIDs) {
1056                        // Find the individual IDs and make integers
1057                        contactIDs = contactIDs.split(',').collect {
1058                                def parts = it.split('-')
1059                                return [person: Integer.parseInt(parts[0]), role: Integer.parseInt(parts[1])]
1060                        }
1061
1062                        // First remove the contacts that are not present in the array
1063                        flow.study.persons.removeAll {
1064                                studyperson -> !contactIDs.find { ids -> (ids.person == studyperson.person.id) && (ids.role == studyperson.role.id) }
1065                        }
1066
1067                        // Add those contacts not yet present in the database
1068                        contactIDs.each { ids ->
1069                                if (!flow.study.persons.find { studyperson -> (ids.person == studyperson.person.id) && (ids.role == studyperson.role.id) }) {
1070                                        def person = Person.get(ids.person)
1071                                        def role = PersonRole.get(ids.role)
1072                                        if (person && role) {
1073                                                // Find a studyperson object with these parameters
1074                                                def studyPerson = StudyPerson.findAll().find { studyperson -> studyperson.person.id == person.id && studyperson.role.id == role.id }
1075
1076                                                // If if does not yet exist, save the example
1077                                                if (!studyPerson) {
1078                                                        studyPerson = new StudyPerson(
1079                                                                person: person,
1080                                                                role: role
1081                                                        )
1082                                                        studyPerson.save(flush: true)
1083                                                }
1084
1085                                                flow.study.addToPersons(studyPerson)
1086                                        } else {
1087                                                println('.person ' + ids.person + ' or Role ' + ids.role + ' not found in database.')
1088                                        }
1089                                }
1090                        }
1091                } else {
1092                        println('.no persons selected.')
1093                        flow.study.persons.clear()
1094                }
1095
1096        }
1097
1098        /**
1099         * Handle the wizard subject page
1100         *
1101         * @param Map LocalAttributeMap (the flow scope)
1102         * @param Map localAttributeMap (the flash scope)
1103         * @param Map GrailsParameterMap (the flow parameters = form data)
1104         * @returns boolean
1105         */
1106        def subjectPage(flow, flash, params) {
1107                def errors = false
1108                flash.errors = [:]
1109
1110                // remember the params in the flash scope
1111                flash.values = params
1112
1113                // iterate through subjects
1114                flow.study.subjects.each() { subject ->
1115                        // iterate through (template and domain) fields
1116                        subject.giveFields().each() { field ->
1117                                // set field
1118                                subject.setFieldValue(
1119                                        field.name,
1120                                        params.get('subject_' + subject.getIdentifier() + '_' + field.escapedName())
1121                                )
1122                        }
1123
1124                        // validate subject
1125                        if (!subject.validate()) {
1126                                errors = true
1127                                this.appendErrors(subject, flash.errors, 'subject_' + subject.getIdentifier() + '_')
1128                        }
1129                }
1130
1131                return !errors
1132        }
1133
1134        /**
1135         * Add a number of subjects to a study
1136         *
1137         * required params entities:
1138         * -addNumber (int)
1139         * -species   (string)
1140         * -template  (string)
1141         *
1142         * @param Map LocalAttributeMap (the flow scope)
1143         * @param Map localAttributeMap (the flash scope)
1144         * @param Map GrailsParameterMap (the flow parameters = form data)
1145         * @returns boolean
1146         */
1147        def addSubjects(flow, flash, params) {
1148                // remember the params in the flash scope
1149                flash.values = params
1150
1151                // handle the subject page
1152                subjectPage(flow, flash, params)
1153
1154                // (re)set error message
1155                flash.errors = [:]
1156
1157                // set work variables
1158                def errors              = false
1159                def number              = params.get('addNumber') as int
1160                def species             = Term.findByName(params.get('species'))
1161                def template    = Template.findByName(params.get('template'))
1162                def subjectNo   = 0
1163                def subjectMax  = 0
1164
1165                // can we add subjects?
1166                if (number > 0 && species && template) {
1167                        // first find the current maximum default name "Subject xxxx"
1168                        flow.study.subjects.each() {
1169                                it.name.trim().eachMatch(/(\d+)$/){
1170                                        subjectNo = it[1] as int
1171                                        if (subjectNo > subjectMax) {
1172                                                subjectMax = subjectNo
1173                                        }
1174                                }
1175                        }
1176
1177                        // add subjects to study
1178                        number.times {
1179                                subjectMax++
1180                               
1181                                // create a subject instance
1182                                def subject = new Subject(
1183                                        name            : 'Subject ' + subjectMax,
1184                                        species         : species,
1185                                        template        : template,
1186                                        parent          : flow.study
1187                                )
1188
1189                                // validate subject
1190                                if (subject.validate()) {
1191                                        // add it to the study
1192                                        flow.study.addToSubjects( subject )
1193                                        println ".added subject "+subject
1194                                } else {
1195                                        // whoops?
1196                                        this.appendErrors(subject, flash.errors)
1197                                        errors = true
1198                                }
1199                        }
1200                } else {
1201                        // add feedback
1202                        errors = true
1203                        if (number < 1) this.appendErrorMap(['addNumber': 'Enter a positive number of subjects to add'], flash.errors)
1204                        if (!species)   this.appendErrorMap(['species': 'You need to select a species, or add one if it is not yet present'], flash.errors)
1205                        if (!template)  this.appendErrorMap(['template': 'You need to select a template, or add one if it is not yet present'], flash.errors)
1206                }
1207
1208                return !errors
1209        }
1210
1211        /**
1212         * Handle the wizard event page
1213         *
1214         * @param Map LocalAttributeMap (the flow scope)
1215         * @param Map localAttributeMap (the flash scope)
1216         * @param Map GrailsParameterMap (the flow parameters = form data)
1217         * @returns boolean
1218         */
1219        def eventPage(flow, flash, params) {
1220                def errors = false
1221                flash.errors = [:]
1222
1223                // remember the params in the flash scope
1224                flash.values = params
1225
1226                // handle the 'add event' form
1227                if (flow.event) {
1228                        flow.event.giveFields().each() { field ->
1229                                // set field
1230                                flow.event.setFieldValue(
1231                                        field.name,
1232                                        params.get(field.escapedName())
1233                                )
1234                        }
1235                }
1236
1237                // handle the eventGroup names and grouping
1238                def name        = ""
1239                def tempName= ""
1240                flow.study.eventGroups.each() { eventGroup ->
1241                        // iterate through templates
1242                        flow.study.giveAllEventTemplates().each() { template ->
1243                                tempName = params.get( 'eventGroup_' + eventGroup.getIdentifier() + '_' + template.getIdentifier() )
1244
1245                                // is the name different?
1246                                if (tempName != eventGroup.name) {
1247                                        name = tempName
1248                                }
1249                        }
1250
1251                        // should the name change?
1252                        if (name) {
1253                                // yes, change it
1254                                eventGroup.name = name
1255                                name = ""
1256                        }
1257
1258                        // handle eventGrouping
1259                        ( ((flow.study.events) ? flow.study.events : []) + ((flow.study.samplingEvents) ? flow.study.samplingEvents : []) ) .each() { event ->
1260                                if (params.get( 'event_' + event.getIdentifier() + '_group_' + eventGroup.getIdentifier() )) {
1261                                        // add to eventGroup
1262                                        if (event instanceof SamplingEvent) {
1263                                                eventGroup.addToSamplingEvents(event)
1264                                        } else {
1265                                                eventGroup.addToEvents(event)
1266                                        }
1267                                } else {
1268                                        // remove from eventGroup
1269                                        if (event instanceof SamplingEvent) {
1270                                                eventGroup.removeFromSamplingEvents(event)
1271                                        } else {
1272                                                eventGroup.removeFromEvents(event)
1273                                        }
1274                                }
1275                        }
1276                }
1277
1278                // handle the (sampling) events
1279                ( ((flow.study.events) ? flow.study.events : []) + ((flow.study.samplingEvents) ? flow.study.samplingEvents : []) ) .each() { event ->
1280                        event.giveFields().each() { field ->
1281                                event.setFieldValue(
1282                                        field.name,
1283                                        params.get( 'event_' + event.getIdentifier() + '_' + field.escapedName())
1284                                )
1285                        }
1286
1287                        // validate event
1288                        if (!event.validate()) {
1289                                errors = true
1290                                this.appendErrors(event, flash.errors)
1291                        }
1292                }
1293
1294                // handle eventGroup names
1295                flow.study.eventGroups.each() { eventGroup ->
1296                       
1297                }
1298
1299                return !errors
1300        }
1301
1302        /**
1303         * Handle the wizard group page
1304         *
1305         * @param Map LocalAttributeMap (the flow scope)
1306         * @param Map localAttributeMap (the flash scope)
1307         * @param Map GrailsParameterMap (the flow parameters = form data)
1308         * @returns boolean
1309         */
1310        def groupPage(flow, flash, params) {
1311                def errors = false
1312                flash.errors = [:]
1313
1314                // remember the params in the flash scope
1315                flash.values = params
1316
1317                // iterate through groups
1318                flow.study.eventGroups.each() { eventGroup ->
1319                        // iterate through subjects
1320                        flow.study.subjects.each() { subject ->
1321                                if (params.get('subject_' + subject.getIdentifier() + '_group_' + eventGroup.getIdentifier() )) {
1322                                        // add to eventGroup
1323                                        eventGroup.addToSubjects(subject)
1324                                } else {
1325                                        // remove from eventGroup
1326                                        eventGroup.removeFromSubjects(subject)
1327                                }
1328                        }
1329                }
1330        }
1331
1332        /**
1333         * Handle the wizard samples page
1334         *
1335         * @param Map LocalAttributeMap (the flow scope)
1336         * @param Map localAttributeMap (the flash scope)
1337         * @param Map GrailsParameterMap (the flow parameters = form data)
1338         * @returns boolean
1339         */
1340        def samplePage(flow, flash, params) {
1341                def errors = false
1342                flash.errors = [:]
1343
1344                // remember the params in the flash scope
1345                flash.values = params
1346
1347                return !errors
1348        }
1349}
Note: See TracBrowser for help on using the repository browser.