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

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