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

Last change on this file since 389 was 389, checked in by duh, 9 years ago
  • reverted changes of revisions 386, 387 and 388 as they completely broke the repository (you need to get cake now! ;)
  • committed development version of modified study capture wizard (works until events)
  • Property svn:keywords set to Date Author Rev
File size: 20.0 KB
RevLine 
[86]1package dbnp.studycapturing
[96]2
[86]3import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
[140]4import dbnp.studycapturing.*
5import dbnp.data.*
[374]6import cr.co.arquetipos.crypto.Blowfish
[86]7
8/**
9 * Wizard tag library
10 *
[96]11 * @author Jeroen Wesbeek
12 * @since 20100113
[86]13 * @package wizard
14 *
15 * Revision information:
16 * $Rev: 389 $
17 * $Author: duh $
18 * $Date: 2010-04-28 14:28:39 +0000 (wo, 28 apr 2010) $
19 */
20class WizardTagLib extends JavascriptTagLib {
[96]21        // define the tag namespace (e.g.: <wizard:action ... />
22        static namespace = "wizard"
[86]23
[96]24        // define the AJAX provider to use
25        static ajaxProvider = "jquery"
[89]26
[96]27        // define default text field width
28        static defaultTextFieldSize = 25;
[86]29
[96]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         *
[101]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         *
[96]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
[189]45         *        'webflow' taglib or plugin
[96]46         * @param Map attributes
47         * @param Closure body
48         */
[246]49        def ajaxButton = {attrs, body ->
[96]50                // get the jQuery version
51                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
[86]52
[96]53                // fetch the element name from the attributes
54                def elementName = attrs['name'].replaceAll(/ /, "_")
[86]55
[101]56                // javascript function to call after success
57                def afterSuccess = attrs['afterSuccess']
58
[216]59                // src parameter?
60                def src = attrs['src']
61                def alt = attrs['alt']
62
[96]63                // generate a normal submitToRemote button
64                def button = submitToRemote(attrs, body)
[86]65
[96]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                }
[246]90
[101]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                }
[88]97
[216]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
[101]107                // replace double semi colons
[138]108                button = button.replaceAll(/;{2,}/, ';')
[246]109
[96]110                // render button
111                out << button
112        }
[88]113
[96]114        /**
[240]115         * generate a ajax submit JavaScript
116         * @see WizardTagLib::ajaxFlowRedirect
117         * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
118         */
[246]119        def ajaxSubmitJs = {attrs, body ->
[240]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
[246]133                button = button.replaceFirst(/<[^\"]*\"jQuery.ajax/, 'jQuery.ajax')
134                button = button.replaceFirst(/return false.*/, '')
[240]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        /**
[101]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         *
[240]165         * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
166         *
[101]167         * Example initial webflow action to work with this javascript:
168         * ...
[246]169         * mainPage {*  render(view: "/wizard/index")
170         *      onRender {*             flow.page = 1
171         *}*    on("next").to "pageOne"
172         *}* ...
[101]173         *
174         * @param Map attributes
175         * @param Closure body
176         */
[246]177        def ajaxFlowRedirect = {attrs, body ->
[101]178                // generate javascript
[240]179                out << '<script type="text/javascript">'
[101]180                out << '$(document).ready(function() {'
[240]181                out << ajaxSubmitJs(attrs, body)
[101]182                out << '});'
183                out << '</script>'
184        }
185
186        /**
[96]187         * render the content of a particular wizard page
188         * @param Map attrs
[107]189         * @param Closure body  (help text)
[96]190         */
191        def pageContent = {attrs, body ->
192                // define AJAX provider
193                setProvider([library: ajaxProvider])
[89]194
[96]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")
[103]201                out << render(template: "/wizard/common/error")
[96]202        }
203
[98]204        /**
[145]205         * generate a base form element
[246]206         * @param String inputElement name
207         * @param Map attributes
208         * @param Closure help content
[98]209         */
[246]210        def baseElement = {inputElement, attrs, help ->
[389]211println ".rendering [" + inputElement + "] with name [" + attrs.get('name') + "] and value [" + ((attrs.value) ? attrs.get('value').toString() : "-") + "]"
[107]212                // work variables
[145]213                def description = attrs.remove('description')
[107]214                def addExampleElement = attrs.remove('addExampleElement')
[246]215                def addExample2Element = attrs.remove('addExample2Element')
[349]216                def helpText = help().trim()
[107]217
[240]218                // got an ajax onchange action?
219                def ajaxOnChange = attrs.remove('ajaxOnChange')
220                if (ajaxOnChange) {
221                        if (!attrs.onChange) attrs.onChange = ''
222
223                        // add onChange AjaxSubmit javascript
224                        attrs.onChange += ajaxSubmitJs(
225                                [
226                                        functionName: ajaxOnChange,
227                                        url: attrs.get('url'),
228                                        update: attrs.get('update'),
229                                        afterSuccess: attrs.get('afterSuccess')
230                                ],
231                                ''
232                        )
233                }
234
[238]235                // execute inputElement call
236                def renderedElement = "$inputElement"(attrs)
237
238                // if false, then we skip this element
239                if (!renderedElement) return false
240
[145]241                // render a form element
[96]242                out << '<div class="element">'
243                out << ' <div class="description">'
[145]244                out << description
[96]245                out << ' </div>'
246                out << ' <div class="input">'
[238]247                out << renderedElement
[349]248                if (helpText.size() > 0) {
[145]249                        out << '        <div class="helpIcon"></div>'
[107]250                }
251
252                // add an disabled input box for feedback purposes
253                // @see dateElement(...)
254                if (addExampleElement) {
255                        def exampleAttrs = new LinkedHashMap()
[246]256                        exampleAttrs.name = attrs.get('name') + 'Example'
257                        exampleAttrs.class = 'isExample'
[107]258                        exampleAttrs.disabled = 'disabled'
[145]259                        exampleAttrs.size = 30
[107]260                        out << textField(exampleAttrs)
261                }
262
[209]263                // add an disabled input box for feedback purposes
264                // @see dateElement(...)
265                if (addExample2Element) {
266                        def exampleAttrs = new LinkedHashMap()
[246]267                        exampleAttrs.name = attrs.get('name') + 'Example2'
268                        exampleAttrs.class = 'isExample'
[209]269                        exampleAttrs.disabled = 'disabled'
270                        exampleAttrs.size = 30
271                        out << textField(exampleAttrs)
272                }
273
[96]274                out << ' </div>'
275
[107]276                // add help content if it is available
[349]277                if (helpText.size() > 0) {
[107]278                        out << '  <div class="helpContent">'
[349]279                        out << '    ' + helpText
[96]280                        out << '  </div>'
281                }
282
283                out << '</div>'
284        }
[107]285
[145]286        /**
[349]287         * render an ajaxButtonElement
288         * @param Map attrs
289         * @param Closure body  (help text)
290         */
291        def ajaxButtonElement = { attrs, body ->
292                baseElement.call(
293                        'ajaxButton',
294                        attrs,
295                        body
296                )
297        }
298
299        /**
[145]300         * render a textFieldElement
301         * @param Map attrs
302         * @param Closure body  (help text)
303         */
[246]304        def textFieldElement = {attrs, body ->
[145]305                // set default size, or scale to max length if it is less than the default size
306                if (!attrs.get("size")) {
307                        if (attrs.get("maxlength")) {
308                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
309                        } else {
310                                attrs.size = defaultTextFieldSize
311                        }
312                }
[140]313
[145]314                // render template element
315                baseElement.call(
316                        'textField',
317                        attrs,
318                        body
319                )
[140]320        }
321
[107]322        /**
[209]323         * render a select form element
324         * @param Map attrs
325         * @param Closure body  (help text)
326         */
[246]327        def selectElement = {attrs, body ->
[209]328                baseElement.call(
329                        'select',
330                        attrs,
331                        body
332                )
333        }
334
335        /**
336         * render a checkBox form element
337         * @param Map attrs
338         * @param Closure body  (help text)
339         */
[246]340        def checkBoxElement = {attrs, body ->
[209]341                baseElement.call(
342                        'checkBox',
343                        attrs,
344                        body
345                )
346        }
347
348        /**
[107]349         * render a dateElement
[145]350         * NOTE: datepicker is attached through wizard.js!
[107]351         * @param Map attrs
352         * @param Closure body  (help text)
353         */
[246]354        def dateElement = {attrs, body ->
[138]355                // transform value?
356                if (attrs.value instanceof Date) {
357                        // transform date instance to formatted string (dd/mm/yyyy)
358                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
359                }
[246]360
[250]361                // add 'rel' field to identity the datefield using javascript
362                attrs.rel = 'date'
363
[107]364                // set some textfield values
[209]365                attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10
[107]366                attrs.addExampleElement = true
[246]367
[107]368                // render a normal text field
[145]369                //out << textFieldElement(attrs,body)
370                textFieldElement.call(
371                        attrs,
372                        body
373                )
[107]374        }
[209]375
376        /**
377         * render a dateElement
378         * NOTE: datepicker is attached through wizard.js!
379         * @param Map attrs
380         * @param Closure body  (help text)
381         */
[246]382        def timeElement = {attrs, body ->
[209]383                // transform value?
384                if (attrs.value instanceof Date) {
385                        // transform date instance to formatted string (dd/mm/yyyy)
386                        attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value)
387                }
388
[250]389                // add 'rel' field to identity the field using javascript
390                attrs.rel = 'datetime'
391
[209]392                attrs.addExampleElement = true
393                attrs.addExample2Element = true
394                attrs.maxlength = 16
395
396                // render a normal text field
397                //out << textFieldElement(attrs,body)
398                textFieldElement.call(
399                        attrs,
400                        body
401                )
402        }
[246]403
[145]404        /**
[353]405         * Button form element
[246]406         * @param Map attributes
407         * @param Closure help content
[145]408         */
[353]409        def buttonElement = {attrs, body ->
[145]410                // render template element
411                baseElement.call(
[353]412                        'ajaxButton',
[145]413                        attrs,
414                        body
415                )
416        }
[138]417
[353]418
[138]419        /**
[353]420         * Term form element
[246]421         * @param Map attributes
422         * @param Closure help content
[209]423         */
[353]424        def termElement = { attrs, body ->
425                // render term element
426                baseElement.call(
427                        'termSelect',
428                        attrs,
429                        body
430                )
431        }
432
433        /**
434         * Term select element
435         * @param Map attributes
436         */
437        def termSelect = { attrs ->
438                def from = []
439
440                // got ontologies?
441                if (attrs.ontology) {
442                        attrs.ontology.split(/\,/).each() { ncboId ->
443                                // trim the id
444                                ncboId.trim()
445                               
446                                // fetch all terms for this ontology
447                                def ontology = Ontology.findAllByNcboId(ncboId)
448
449                                // does this ontology exist?
450                                if (ontology) {
451                                        ontology.each() {
452                                                Term.findAllByOntology(it).each() {
453                                                        // key = ncboId:concept-id
454                                                        from[ from.size() ] = it.name
455                                                }
456                                        }
457                                }
458                        }
459
460                        // sort alphabetically
461                        from.sort()
462                       
463                        // define 'from'
464                        attrs.from = from
465
[356]466                        // add 'rel' attribute
467                        attrs.rel = 'term'
468
[353]469                        out << select(attrs)
470                } else {
471                        out << "you should specify: <i>ontology=\"id\"</i> or <i>ontology=\"id1,id2,...,idN\"</i>"
472                }
473        }
474
475        /**
[349]476         * Ontology form element
477         * @param Map attributes
478         * @param Closure help content
479         */
480        def ontologyElement = { attrs, body ->
481                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
482                // @see ontology-chooser.js, table-editor.js
483                baseElement.call(
484                        'textField',
485                        [
486                            name: attrs.name,
487                                value: attrs.value,
488                                description: attrs.description,
489                                rel: 'ontology-' + ((attrs.ontology) ? attrs.ontology : 'all') + '-name',
490                                size: 25
491                        ],
492                        body
493                )
494                out << hiddenField(
495                        name: attrs.name + '-concept_id'
496                )
497                out << hiddenField(
498                        name: attrs.name + '-ontology_id'
499                )
500                out << hiddenField(
501                        name: attrs.name + '-full_id'
502                )
503        }
[341]504
[140]505        /**
[341]506         * Study form element
507         * @param Map attributes
508         * @param Closure help content
509         */
510        def studyElement = { attrs, body ->
511                // render study element
512                baseElement.call(
513                        'studySelect',
514                        attrs,
515                        body
516                )
517        }
518
519        /**
520         * render a study select element
521         * @param Map attrs
522         */
523        def studySelect = { attrs ->
524                // for now, just fetch all studies
525                attrs.from = Study.findAll()
526
527                // got a name?
528                if (!attrs.name) {
529                        attrs.name = "study"
530                }
531
532                // got result?
533                if (attrs.from.size() > 0) {
534                        out << select(attrs)
535                } else {
536                        // no, return false to make sure this element
537                        // is not rendered in the template
538                        return false
539                }
540        }
541
542        /**
[145]543         * Template form element
[246]544         * @param Map attributes
545         * @param Closure help content
[145]546         */
[246]547        def templateElement = {attrs, body ->
[367]548                // add a rel element if it does not exist
549                if (!attrs.rel) {
550                        attrs.rel = 'template'
551                }
552               
[145]553                // render template element
554                baseElement.call(
555                        'templateSelect',
556                        attrs,
557                        body
558                )
559        }
[246]560
[145]561        /**
[140]562         * render a template select element
563         * @param Map attrs
564         */
[246]565        def templateSelect = {attrs ->
[238]566                def entity = attrs.remove('entity')
[140]567
[374]568                // add the entity class name to the element
569                // do we have crypto information available?
570                if (grailsApplication.config.crypto) {
571                        // generate a Blowfish encrypted and Base64 encoded string.
572                        attrs['entity'] = Blowfish.encryptBase64(
573                                entity.toString().replaceAll(/^class /, ''),
574                                grailsApplication.config.crypto.shared.secret
575                        )
576                } else {
577                        // base64 only; this is INSECURE! As this class
578                        // is instantiated elsewehere. Possibly exploitable!
579                        attrs['entity'] = entity.toString().replaceAll(/^class /, '').bytes.encodeBase64()
580                }
[372]581
[238]582                // fetch templates
[246]583                if (attrs.remove('addDummy')) {
584                        attrs.from = ['']
585                        if (entity && entity instanceof Class) {
586                                Template.findAllByEntity(entity).each() {
587                                        attrs.from[attrs.from.size()] = it
588                                }
589                        }
590                } else {
591                        attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
592                }
[238]593
[140]594                // got a name?
595                if (!attrs.name) {
596                        attrs.name = 'template'
597                }
[238]598
599                // got result?
[246]600                if (attrs.from.size() > 0) {
[389]601                        // transform all values into strings
602                        def from = []
603                        attrs.from.each { from[ from.size() ] = it.toString() }
604                        attrs.from = from
605                        attrs.value = (attrs.value) ? attrs.value.toString() : ''
606
607                        // output select element
[238]608                        out << select(attrs)
609                } else {
610                        // no, return false to make sure this element
611                        // is not rendered in the template
612                        return false
613                }
[140]614        }
[145]615
[157]616        /**
[246]617         * Protocol form element
618         * @param Map attributes
619         * @param Closure help content
620         */
621        def protocolElement = {attrs, body ->
622                // render protocol element
623                baseElement.call(
624                        'protocolSelect',
625                        attrs,
626                        body
627                )
628        }
629
630        /**
631         * render a protocol select element
632         * @param Map attrs
633         */
634        def protocolSelect = {attrs ->
635                // fetch all protocold
636                attrs.from = Protocol.findAll() // for now, all protocols
637
638                // got a name?
639                if (!attrs.name) {
640                        attrs.name = 'protocol'
641                }
642
643                out << select(attrs)
644        }
645
646        def show = {attrs ->
[213]647                // is object parameter set?
648                def o = attrs.object
649
650                println o.getProperties();
651                o.getProperties().each {
652                        println it
653                }
654
655                out << "!! test version of 'show' tag !!"
656        }
657
[209]658        /**
[157]659         * render table headers for all subjectFields in a template
660         * @param Map attributes
661         */
[246]662        def templateColumnHeaders = {attrs ->
[157]663                def template = attrs.remove('template')
664
665                // output table headers for template fields
[238]666                template.fields.each() {
[157]667                        out << '<div class="' + attrs.get('class') + '">' + it + '</div>'
[145]668                }
669        }
670
[381]671        def templateColumns = { attrs ->
672                // render template fields as columns
673                attrs.renderType = 'column'
674                out << renderTemplateFields(attrs)
675        }
[157]676
[381]677        def templateElements = { attrs ->
678                // render template fields as form elements
679                attrs.renderType = 'element'
680                out << renderTemplateFields(attrs)
[145]681        }
[246]682
683        /**
684         * render form elements based on an entity's template
685         * @param Map attributes
686         * @param String body
687         */
[381]688        def renderTemplateFields = { attrs ->
689                def renderType  = attrs.remove('renderType')
690                def entity              = (attrs.get('entity'))
[389]691                println entity
692                println entity.class
693                println entity instanceof TemplateEntity
[381]694                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
695                def inputElement= null
[246]696
697                // got a template?
698                if (template) {
699                        // render template fields
700                        template.fields.each() {
[296]701                                def fieldValue = entity.getFieldValue(it.name)
702
[381]703                                // output column opening element?
704                                if (renderType == 'column') {
705                                        out << '<div class="' + attrs.get('class') + '">'
706                                }
707
[250]708                                switch (it.type.toString()) {
709                                        case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']:
[381]710                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
711                                                out << "$inputElement"(
[250]712                                                        description: it.name,
[296]713                                                        name: it.escapedName(),
714                                                        value: fieldValue
[250]715                                                )
716                                                break
[246]717                                        case 'STRINGLIST':
[381]718                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
[246]719                                                if (!it.listEntries.isEmpty()) {
[381]720                                                        out << "$inputElement"(
[246]721                                                                description: it.name,
[296]722                                                                name: it.escapedName(),
[246]723                                                                from: it.listEntries,
[296]724                                                                value: fieldValue
[246]725                                                        )
726                                                } else {
727                                                        out << '<span class="warning">no values!!</span>'
728                                                }
729                                                break
[262]730                                        case 'ONTOLOGYTERM':
731                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
732                                                // @see ontology-chooser.js
[381]733                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
734                                                out << "$inputElement"(
[296]735                                                        name: it.escapedName(),
[262]736                                                        value: fieldValue,
737                                                        rel: 'ontology-all-name',
738                                                        size: 100
739                                                )
740                                                out << hiddenField(
[296]741                                                        name: it.name + '-concept_id',
742                                                        value: fieldValue
[262]743                                                )
744                                                out << hiddenField(
[296]745                                                        name: it.escapedName() + '-ontology_id',
746                                                        value: fieldValue
[262]747                                                )
748                                                out << hiddenField(
[296]749                                                        name: it.escapedName() + '-full_id',
750                                                        value: fieldValue
[262]751                                                )
752                                                break
[246]753                                        case 'DATE':
[381]754                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
755
756                                                // transform value?
757                                                if (fieldValue instanceof Date) {
758                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
759                                                                // transform date instance to formatted string (dd/mm/yyyy)
760                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
761                                                        } else {
762                                                                // transform to date + time
763                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
764                                                        }
765                                                }
766
767                                                // render element
768                                                out << "$inputElement"(
[246]769                                                        description: it.name,
[296]770                                                        name: it.escapedName(),
[381]771                                                        value: fieldValue,
772                                                        rel: 'date'
[246]773                                                )
774                                                break
775                                        default:
[381]776                                                // unsupported field type
777                                                out << '<span class="warning">!' + it.type + '</span>'
[246]778                                                break
779                                }
[381]780
781                                // output column closing element?
782                                if (renderType == 'column') {
783                                        out << '</div>'
784                                }
[246]785                        }
786                }
787        }
[86]788}
Note: See TracBrowser for help on using the repository browser.