source: trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy @ 374

Last change on this file since 374 was 374, checked in by duh, 9 years ago
  • added crypto plugin
  • implemented blowfish encryption in Wizard Tag Library
  • implemented blowfish descryption in TemplateEditorController?
  • added shared secret configuration to Config.groovy
  • Property svn:keywords set to Date Author Rev
File size: 20.7 KB
Line 
1package dbnp.studycapturing
2
3import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
4import dbnp.studycapturing.*
5import dbnp.data.*
6import cr.co.arquetipos.crypto.Blowfish
7
8/**
9 * Wizard tag library
10 *
11 * @author Jeroen Wesbeek
12 * @since 20100113
13 * @package wizard
14 *
15 * Revision information:
16 * $Rev: 374 $
17 * $Author: duh $
18 * $Date: 2010-04-23 09:02:31 +0000 (vr, 23 apr 2010) $
19 */
20class WizardTagLib extends JavascriptTagLib {
21        // define the tag namespace (e.g.: <wizard:action ... />
22        static namespace = "wizard"
23
24        // define the AJAX provider to use
25        static ajaxProvider = "jquery"
26
27        // define default text field width
28        static defaultTextFieldSize = 25;
29
30        /**
31         * ajaxButton tag, this is a modified version of the default
32         * grails submitToRemote tag to work with grails webflows.
33         * Usage is identical to submitToRemote with the only exception
34         * that a 'name' form element attribute is required. E.g.
35         * <wizard:ajaxButton name="myAction" value="myButton ... />
36         *
37         * you can also provide a javascript function to execute after
38         * success. This behaviour differs from the default 'after'
39         * action which always fires after a button press...
40         *
41         * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
42         * @see http://www.grails.org/WebFlow
43         * @see http://www.grails.org/Tag+-+submitToRemote
44         * @todo perhaps some methods should be moved to a more generic
45         *        'webflow' taglib or plugin
46         * @param Map attributes
47         * @param Closure body
48         */
49        def ajaxButton = {attrs, body ->
50                // get the jQuery version
51                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
52
53                // fetch the element name from the attributes
54                def elementName = attrs['name'].replaceAll(/ /, "_")
55
56                // javascript function to call after success
57                def afterSuccess = attrs['afterSuccess']
58
59                // src parameter?
60                def src = attrs['src']
61                def alt = attrs['alt']
62
63                // generate a normal submitToRemote button
64                def button = submitToRemote(attrs, body)
65
66                /**
67                 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
68                 * not properly work with AJAX as the submitToRemote button does not
69                 * handle and submit the form properly. In order to support webflows
70                 * this method modifies two parts of a 'normal' submitToRemote button:
71                 *
72                 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
73                 *    action refers to the button and / or the action upon that button.
74                 *    However, it should point to the form the button is part of as the
75                 *    the button should submit the form data.
76                 * 2) prepend the button name to the serialized data. The default behaviour
77                 *    of submitToRemote is to remove the element name altogether, while
78                 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
79                 *    the appropriate webflow action. Hence, we are going to prepend the
80                 *    serialized formdata with an _eventId_BUTTONNAME parameter.
81                 */
82                if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
83                        // fix for older jQuery plugin versions
84                        button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
85                } else {
86                        // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
87                        // this.form part has been fixed. Consequently, our wrapper has changed as well...
88                        button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
89                }
90
91                // add an after success function call?
92                // usefull for performing actions on success data (hence on refreshed
93                // wizard pages, such as attaching tooltips)
94                if (afterSuccess) {
95                        button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
96                }
97
98                // got an src parameter?
99                if (src) {
100                        def replace = 'type="image" src="' + src + '"'
101
102                        if (alt) replace = replace + ' alt="' + alt + '"'
103
104                        button = button.replaceFirst(/type="button"/, replace)
105                }
106
107                // replace double semi colons
108                button = button.replaceAll(/;{2,}/, ';')
109
110                // render button
111                out << button
112        }
113
114        /**
115         * generate a ajax submit JavaScript
116         * @see WizardTagLib::ajaxFlowRedirect
117         * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
118         */
119        def ajaxSubmitJs = {attrs, body ->
120                // define AJAX provider
121                setProvider([library: ajaxProvider])
122
123                // got a function name?
124                def functionName = attrs.remove('functionName')
125                if (functionName && !attrs.get('name')) {
126                        attrs.name = functionName
127                }
128
129                // generate an ajax button
130                def button = this.ajaxButton(attrs, body)
131
132                // strip the button part to only leave the Ajax call
133                button = button.replaceFirst(/<[^\"]*\"jQuery.ajax/, 'jQuery.ajax')
134                button = button.replaceFirst(/return false.*/, '')
135
136                // change form if a form attribute is present
137                if (attrs.get('form')) {
138                        button = button.replaceFirst(/this\.form/,
139                                "\\\$('" + attrs.get('form') + "')"
140                        )
141                }
142
143                out << button
144        }
145
146        /**
147         * generate ajax webflow redirect javascript
148         *
149         * As we have an Ajaxified webflow, the initial wizard page
150         * cannot contain a wizard form, as upon a failing submit
151         * (e.g. the form data does not validate) the form should be
152         * shown again. However, the Grails webflow then renders the
153         * complete initial wizard page into the success div. As this
154         * ruins the page layout (a page within a page) we want the
155         * initial page to redirect to the first wizard form to enter
156         * the webflow correctly. We do this by emulating an ajax post
157         * call which updates the wizard content with the first wizard
158         * form.
159         *
160         * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
161         * form = the form identifier
162         * name = the action to execute in the webflow
163         * update = the divs to update upon success or error
164         *
165         * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
166         *
167         * Example initial webflow action to work with this javascript:
168         * ...
169         * mainPage {*  render(view: "/wizard/index")
170         *      onRender {*             flow.page = 1
171         *}*    on("next").to "pageOne"
172         *}* ...
173         *
174         * @param Map attributes
175         * @param Closure body
176         */
177        def ajaxFlowRedirect = {attrs, body ->
178                // generate javascript
179                out << '<script type="text/javascript">'
180                out << '$(document).ready(function() {'
181                out << ajaxSubmitJs(attrs, body)
182                out << '});'
183                out << '</script>'
184        }
185
186        /**
187         * render the content of a particular wizard page
188         * @param Map attrs
189         * @param Closure body  (help text)
190         */
191        def pageContent = {attrs, body ->
192                // define AJAX provider
193                setProvider([library: ajaxProvider])
194
195                // render new body content
196                out << render(template: "/wizard/common/tabs")
197                out << '<div class="content">'
198                out << body()
199                out << '</div>'
200                out << render(template: "/wizard/common/navigation")
201                out << render(template: "/wizard/common/error")
202        }
203
204        /**
205         * generate a base form element
206         * @param String inputElement name
207         * @param Map attributes
208         * @param Closure help content
209         */
210        def baseElement = {inputElement, attrs, help ->
211                // work variables
212                def description = attrs.remove('description')
213                def addExampleElement = attrs.remove('addExampleElement')
214                def addExample2Element = attrs.remove('addExample2Element')
215                def helpText = help().trim()
216
217                // got an ajax onchange action?
218                def ajaxOnChange = attrs.remove('ajaxOnChange')
219                if (ajaxOnChange) {
220                        if (!attrs.onChange) attrs.onChange = ''
221
222                        // add onChange AjaxSubmit javascript
223                        attrs.onChange += ajaxSubmitJs(
224                                [
225                                        functionName: ajaxOnChange,
226                                        url: attrs.get('url'),
227                                        update: attrs.get('update'),
228                                        afterSuccess: attrs.get('afterSuccess')
229                                ],
230                                ''
231                        )
232                }
233
234                // execute inputElement call
235                def renderedElement = "$inputElement"(attrs)
236
237                // if false, then we skip this element
238                if (!renderedElement) return false
239
240                // render a form element
241                out << '<div class="element">'
242                out << ' <div class="description">'
243                out << description
244                out << ' </div>'
245                out << ' <div class="input">'
246                out << renderedElement
247                if (helpText.size() > 0) {
248                        out << '        <div class="helpIcon"></div>'
249                }
250
251                // add an disabled input box for feedback purposes
252                // @see dateElement(...)
253                if (addExampleElement) {
254                        def exampleAttrs = new LinkedHashMap()
255                        exampleAttrs.name = attrs.get('name') + 'Example'
256                        exampleAttrs.class = 'isExample'
257                        exampleAttrs.disabled = 'disabled'
258                        exampleAttrs.size = 30
259                        out << textField(exampleAttrs)
260                }
261
262                // add an disabled input box for feedback purposes
263                // @see dateElement(...)
264                if (addExample2Element) {
265                        def exampleAttrs = new LinkedHashMap()
266                        exampleAttrs.name = attrs.get('name') + 'Example2'
267                        exampleAttrs.class = 'isExample'
268                        exampleAttrs.disabled = 'disabled'
269                        exampleAttrs.size = 30
270                        out << textField(exampleAttrs)
271                }
272
273                out << ' </div>'
274
275                // add help content if it is available
276                if (helpText.size() > 0) {
277                        out << '  <div class="helpContent">'
278                        out << '    ' + helpText
279                        out << '  </div>'
280                }
281
282                out << '</div>'
283        }
284
285        /**
286         * render an ajaxButtonElement
287         * @param Map attrs
288         * @param Closure body  (help text)
289         */
290        def ajaxButtonElement = { attrs, body ->
291                baseElement.call(
292                        'ajaxButton',
293                        attrs,
294                        body
295                )
296        }
297
298        /**
299         * render a textFieldElement
300         * @param Map attrs
301         * @param Closure body  (help text)
302         */
303        def textFieldElement = {attrs, body ->
304                // set default size, or scale to max length if it is less than the default size
305                if (!attrs.get("size")) {
306                        if (attrs.get("maxlength")) {
307                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
308                        } else {
309                                attrs.size = defaultTextFieldSize
310                        }
311                }
312
313                // render template element
314                baseElement.call(
315                        'textField',
316                        attrs,
317                        body
318                )
319        }
320
321        /**
322         * render a select form element
323         * @param Map attrs
324         * @param Closure body  (help text)
325         */
326        def selectElement = {attrs, body ->
327                baseElement.call(
328                        'select',
329                        attrs,
330                        body
331                )
332        }
333
334        /**
335         * render a checkBox form element
336         * @param Map attrs
337         * @param Closure body  (help text)
338         */
339        def checkBoxElement = {attrs, body ->
340                baseElement.call(
341                        'checkBox',
342                        attrs,
343                        body
344                )
345        }
346
347        /**
348         * render a dateElement
349         * NOTE: datepicker is attached through wizard.js!
350         * @param Map attrs
351         * @param Closure body  (help text)
352         */
353        def dateElement = {attrs, body ->
354                // transform value?
355                if (attrs.value instanceof Date) {
356                        // transform date instance to formatted string (dd/mm/yyyy)
357                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
358                }
359
360                // add 'rel' field to identity the datefield using javascript
361                attrs.rel = 'date'
362
363                // set some textfield values
364                attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10
365                attrs.addExampleElement = true
366
367                // render a normal text field
368                //out << textFieldElement(attrs,body)
369                textFieldElement.call(
370                        attrs,
371                        body
372                )
373        }
374
375        /**
376         * render a dateElement
377         * NOTE: datepicker is attached through wizard.js!
378         * @param Map attrs
379         * @param Closure body  (help text)
380         */
381        def timeElement = {attrs, body ->
382                // transform value?
383                if (attrs.value instanceof Date) {
384                        // transform date instance to formatted string (dd/mm/yyyy)
385                        attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value)
386                }
387
388                // add 'rel' field to identity the field using javascript
389                attrs.rel = 'datetime'
390
391                attrs.addExampleElement = true
392                attrs.addExample2Element = true
393                attrs.maxlength = 16
394
395                // render a normal text field
396                //out << textFieldElement(attrs,body)
397                textFieldElement.call(
398                        attrs,
399                        body
400                )
401        }
402
403        /**
404         * Button form element
405         * @param Map attributes
406         * @param Closure help content
407         */
408        def buttonElement = {attrs, body ->
409                // render template element
410                baseElement.call(
411                        'ajaxButton',
412                        attrs,
413                        body
414                )
415        }
416
417
418        /**
419         * Term form element
420         * @param Map attributes
421         * @param Closure help content
422         */
423        def termElement = { attrs, body ->
424                // render term element
425                baseElement.call(
426                        'termSelect',
427                        attrs,
428                        body
429                )
430        }
431
432        /**
433         * Term select element
434         * @param Map attributes
435         */
436        def termSelect = { attrs ->
437                def from = []
438
439                // got ontologies?
440                if (attrs.ontology) {
441                        attrs.ontology.split(/\,/).each() { ncboId ->
442                                // trim the id
443                                ncboId.trim()
444                               
445                                // fetch all terms for this ontology
446                                def ontology = Ontology.findAllByNcboId(ncboId)
447
448                                // does this ontology exist?
449                                if (ontology) {
450                                        ontology.each() {
451                                                Term.findAllByOntology(it).each() {
452                                                        // key = ncboId:concept-id
453                                                        from[ from.size() ] = it.name
454                                                }
455                                        }
456                                }
457                        }
458
459                        // sort alphabetically
460                        from.sort()
461                       
462                        // define 'from'
463                        attrs.from = from
464
465                        // add 'rel' attribute
466                        attrs.rel = 'term'
467
468                        out << select(attrs)
469                } else {
470                        out << "you should specify: <i>ontology=\"id\"</i> or <i>ontology=\"id1,id2,...,idN\"</i>"
471                }
472        }
473
474        /**
475         * Ontology form element
476         * @param Map attributes
477         * @param Closure help content
478         */
479        def ontologyElement = { attrs, body ->
480                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
481                // @see ontology-chooser.js, table-editor.js
482                baseElement.call(
483                        'textField',
484                        [
485                            name: attrs.name,
486                                value: attrs.value,
487                                description: attrs.description,
488                                rel: 'ontology-' + ((attrs.ontology) ? attrs.ontology : 'all') + '-name',
489                                size: 25
490                        ],
491                        body
492                )
493                out << hiddenField(
494                        name: attrs.name + '-concept_id'
495                )
496                out << hiddenField(
497                        name: attrs.name + '-ontology_id'
498                )
499                out << hiddenField(
500                        name: attrs.name + '-full_id'
501                )
502        }
503
504        /**
505         * Study form element
506         * @param Map attributes
507         * @param Closure help content
508         */
509        def studyElement = { attrs, body ->
510                // render study element
511                baseElement.call(
512                        'studySelect',
513                        attrs,
514                        body
515                )
516        }
517
518        /**
519         * render a study select element
520         * @param Map attrs
521         */
522        def studySelect = { attrs ->
523                // for now, just fetch all studies
524                attrs.from = Study.findAll()
525
526                // got a name?
527                if (!attrs.name) {
528                        attrs.name = "study"
529                }
530
531                // got result?
532                if (attrs.from.size() > 0) {
533                        out << select(attrs)
534                } else {
535                        // no, return false to make sure this element
536                        // is not rendered in the template
537                        return false
538                }
539        }
540
541        /**
542         * Template form element
543         * @param Map attributes
544         * @param Closure help content
545         */
546        def templateElement = {attrs, body ->
547                // add a rel element if it does not exist
548                if (!attrs.rel) {
549                        attrs.rel = 'template'
550                }
551               
552                // render template element
553                baseElement.call(
554                        'templateSelect',
555                        attrs,
556                        body
557                )
558        }
559
560        /**
561         * render a template select element
562         * @param Map attrs
563         */
564        def templateSelect = {attrs ->
565                def entity = attrs.remove('entity')
566
567                // add the entity class name to the element
568                // do we have crypto information available?
569                if (grailsApplication.config.crypto) {
570                        // generate a Blowfish encrypted and Base64 encoded string.
571                        attrs['entity'] = Blowfish.encryptBase64(
572                                entity.toString().replaceAll(/^class /, ''),
573                                grailsApplication.config.crypto.shared.secret
574                        )
575                } else {
576                        // base64 only; this is INSECURE! As this class
577                        // is instantiated elsewehere. Possibly exploitable!
578                        attrs['entity'] = entity.toString().replaceAll(/^class /, '').bytes.encodeBase64()
579                }
580
581                // fetch templates
582                if (attrs.remove('addDummy')) {
583                        attrs.from = ['']
584                        if (entity && entity instanceof Class) {
585                                Template.findAllByEntity(entity).each() {
586                                        attrs.from[attrs.from.size()] = it
587                                }
588                        }
589                } else {
590                        attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
591                }
592
593                // got a name?
594                if (!attrs.name) {
595                        attrs.name = 'template'
596                }
597
598                // got result?
599                if (attrs.from.size() > 0) {
600                        out << select(attrs)
601                } else {
602                        // no, return false to make sure this element
603                        // is not rendered in the template
604                        return false
605                }
606        }
607
608        /**
609         * Protocol form element
610         * @param Map attributes
611         * @param Closure help content
612         */
613        def protocolElement = {attrs, body ->
614                // render protocol element
615                baseElement.call(
616                        'protocolSelect',
617                        attrs,
618                        body
619                )
620        }
621
622        /**
623         * render a protocol select element
624         * @param Map attrs
625         */
626        def protocolSelect = {attrs ->
627                // fetch all protocold
628                attrs.from = Protocol.findAll() // for now, all protocols
629
630                // got a name?
631                if (!attrs.name) {
632                        attrs.name = 'protocol'
633                }
634
635                out << select(attrs)
636        }
637
638        def show = {attrs ->
639                // is object parameter set?
640                def o = attrs.object
641
642                println o.getProperties();
643                o.getProperties().each {
644                        println it
645                }
646
647                out << "!! test version of 'show' tag !!"
648        }
649
650        /**
651         * render table headers for all subjectFields in a template
652         * @param Map attributes
653         */
654        def templateColumnHeaders = {attrs ->
655                def template = attrs.remove('template')
656
657                // output table headers for template fields
658                template.fields.each() {
659                        out << '<div class="' + attrs.get('class') + '">' + it + '</div>'
660                }
661        }
662
663        /**
664         * render table input elements for all subjectFields in a template
665         * @param Map attributes
666         */
667        def templateColumns = {attrs, body ->
668                def subject = attrs.remove('subject')
669                def subjectId = attrs.remove('id')
670                def template = attrs.remove('template')
671                def intFields = subject.templateIntegerFields
672                def stringFields = subject.templateStringFields
673                def floatFields = subject.templateFloatFields
674                def termFields = subject.templateTermFields
675
676                // output columns for these subjectFields
677                template.fields.each() {
678                        def fieldValue = subject.getFieldValue(it.name)
679
680                        // output div
681                        out << '<div class="' + attrs.get('class') + '">'
682
683                        // handle field types
684                        switch (it.type.toString()) {
685                                case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']:
686                                        out << textField(
687                                                name: attrs.name + '_' + it.escapedName(),
688                                                value: fieldValue
689                                        )
690                                        break
691                                case 'STRINGLIST':
692                                        // render stringlist subjectfield
693                                        if (!it.listEntries.isEmpty()) {
694                                                out << select(
695                                                        name: attrs.name + '_' + it.escapedName(),
696                                                        from: it.listEntries,
697                                                        value: fieldValue
698                                                )
699                                        } else {
700                                                out << '<span class="warning">no values!!</span>'
701                                        }
702                                        break
703                                case 'DATE':
704                                        // transform value?
705                                        if (fieldValue instanceof Date) {
706                                                if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
707                                                        // transform date instance to formatted string (dd/mm/yyyy)
708                                                        fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
709                                                } else {
710                                                        // transform to date + time
711                                                        fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
712                                                }
713                                        }
714
715                                        // output a date field (not the 'rel' which makes the
716                                        // javascript front-end bind the jquery-ui datepicker)
717                                        out << textField(
718                                                name: attrs.name + '_' + it.escapedName(),
719                                                value: fieldValue,
720                                                rel: 'date'
721                                        )
722                                        break
723                                case 'ONTOLOGYTERM':
724                                        // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
725                                        // @see ontology-chooser.js, table-editor.js
726                                        //out << it.getClass()
727                                        out << textField(
728                                                name: attrs.name + '_' + it.escapedName(),
729                                                value: fieldValue,
730                                                rel: 'ontology-all-name',
731                                                size: 100
732                                        )
733                                        out << hiddenField(
734                                                name: attrs.name + '_' + it.escapedName() + '-concept_id'
735                                        )
736                                        out << hiddenField(
737                                                name: attrs.name + '_' + it.escapedName() + '-ontology_id'
738                                        )
739                                        out << hiddenField(
740                                                name: attrs.name + '_' + it.escapedName() + '-full_id'
741                                        )
742                                        break
743                                default:
744                                        // unsupported field type
745                                        out << '<span class="warning">!' + it.type + '</span>'
746                                        break
747                        }
748
749                        out << '</div>'
750                }
751        }
752
753        /**
754         * render form elements based on an entity's template
755         * @param Map attributes
756         * @param String body
757         */
758        def templateElements = {attrs ->
759                def entity = (attrs.get('entity'))
760                def template = (entity && entity instanceof TemplateEntity) ? entity.template : null
761
762                // got a template?
763                if (template) {
764                        // render template fields
765                        template.fields.each() {
766                                def fieldValue = entity.getFieldValue(it.name)
767
768                                switch (it.type.toString()) {
769                                        case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']:
770                                                out << textFieldElement(
771                                                        description: it.name,
772                                                        name: it.escapedName(),
773                                                        value: fieldValue
774                                                )
775                                                break
776                                        case 'STRINGLIST':
777                                                if (!it.listEntries.isEmpty()) {
778                                                        out << selectElement(
779                                                                description: it.name,
780                                                                name: it.escapedName(),
781                                                                from: it.listEntries,
782                                                                value: fieldValue
783                                                        )
784                                                } else {
785                                                        out << '<span class="warning">no values!!</span>'
786                                                }
787                                                break
788                                        case 'ONTOLOGYTERM':
789                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
790                                                // @see ontology-chooser.js
791                                                out << textFieldElement(
792                                                        name: it.escapedName(),
793                                                        value: fieldValue,
794                                                        rel: 'ontology-all-name',
795                                                        size: 100
796                                                )
797                                                out << hiddenField(
798                                                        name: it.name + '-concept_id',
799                                                        value: fieldValue
800                                                )
801                                                out << hiddenField(
802                                                        name: it.escapedName() + '-ontology_id',
803                                                        value: fieldValue
804                                                )
805                                                out << hiddenField(
806                                                        name: it.escapedName() + '-full_id',
807                                                        value: fieldValue
808                                                )
809                                                break
810                                        case 'DATE':
811                                                out << dateElement(
812                                                        description: it.name,
813                                                        name: it.escapedName(),
814                                                        value: fieldValue
815                                                )
816                                                break
817                                        default:
818                                                out << "unkown field type '" + it.type + "'<br/>"
819                                                break
820                                }
821                        }
822                }
823        }
824}
Note: See TracBrowser for help on using the repository browser.