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

Last change on this file since 519 was 519, checked in by roberth, 11 years ago

Added some text in the add publication dialog

  • Property svn:keywords set to Date Author Rev
File size: 31.1 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: 519 $
17 * $Author: roberth $
18 * $Date: 2010-06-03 10:31:58 +0000 (do, 03 jun 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(/<[^\"]*onclick=\"/, '')
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                //      - this JavaScript variable is used by baseElement to workaround an IE
197                //        specific issue (double submit on onchange events). The hell with IE!
198                //        @see baseElement
199                out << '<script type="text/javascript">var lastRequestTime = 0;</script>'
200                out << render(template: "/wizard/common/tabs")
201                out << '<div class="content">'
202                out << body()
203                out << '</div>'
204                out << render(template: "/wizard/common/navigation")
205                out << render(template: "/wizard/common/error")
206        }
207
208        /**
209         * generate a base form element
210         * @param String inputElement name
211         * @param Map attributes
212         * @param Closure help content
213         */
214        def baseElement = {inputElement, attrs, help ->
215println ".rendering [" + inputElement + "] with name [" + attrs.get('name') + "] and value [" + ((attrs.value) ? attrs.get('value').toString() : "-") + "]"
216                // work variables
217                def internetExplorer = (request.getHeader("User-Agent") =~ /MSIE/)
218                def description = attrs.remove('description')
219                def addExampleElement = attrs.remove('addExampleElement')
220                def addExample2Element = attrs.remove('addExample2Element')
221                def helpText = help().trim()
222
223                // got an ajax onchange action?
224                def ajaxOnChange = attrs.remove('ajaxOnChange')
225                if (ajaxOnChange) {
226                        if (!attrs.onChange) attrs.onChange = ''
227
228                        // add onChange AjaxSubmit javascript
229                        if (internetExplorer) {
230                                //              - somehow IE submits these onchanges twice which messes up some parts of the wizard
231                                //                (especially the events page). In order to bypass this issue I have introduced an
232                                //                if statement utilizing the 'before' and 'after' functionality of the submitToRemote
233                                //                function. This check expects lastRequestTime to be in the global Javascript scope,
234                                //                (@see pageContent) and calculates the time difference in miliseconds between two
235                                //                onChange executions. If this is more than 100 miliseconds the request is executed,
236                                //                otherwise it will be ignored... --> 20100527 - Jeroen Wesbeek
237                                attrs.onChange += ajaxSubmitJs(
238                                        [
239                                                before: "var execute=true;try { var currentTime=new Date().getTime();execute = ((currentTime-lastRequestTime) > 100);lastRequestTime=currentTime;  } catch (e) {};if (execute) { 1",
240                                                after: "}",
241                                                functionName: ajaxOnChange,
242                                                url: attrs.get('url'),
243                                                update: attrs.get('update'),
244                                                afterSuccess: attrs.get('afterSuccess')
245                                        ],
246                                        ''
247                                )
248                        } else {
249                                // this another W3C browser that actually behaves as expected... damn you IE, DAMN YOU!
250                                attrs.onChange += ajaxSubmitJs(
251                                        [
252                                                functionName: ajaxOnChange,
253                                                url: attrs.get('url'),
254                                                update: attrs.get('update'),
255                                                afterSuccess: attrs.get('afterSuccess')
256                                        ],
257                                        ''
258                                )
259                        }
260                }
261
262                // execute inputElement call
263                def renderedElement = "$inputElement"(attrs)
264
265                // if false, then we skip this element
266                if (!renderedElement) return false
267
268                // render a form element
269                if (attrs.get('elementId')) {
270                out << '<div class="element" id="'+ attrs.remove('elementId') +'">'
271                } else {
272                        out << '<div class="element">'
273                }
274                out << ' <div class="description">'
275                out << description
276                out << ' </div>'
277                out << ' <div class="input">'
278                out << renderedElement
279                if (helpText.size() > 0) {
280                        out << '        <div class="helpIcon"></div>'
281                }
282
283                // add an disabled input box for feedback purposes
284                // @see dateElement(...)
285                if (addExampleElement) {
286                        def exampleAttrs = new LinkedHashMap()
287                        exampleAttrs.name = attrs.get('name') + 'Example'
288                        exampleAttrs.class = 'isExample'
289                        exampleAttrs.disabled = 'disabled'
290                        exampleAttrs.size = 30
291                        out << textField(exampleAttrs)
292                }
293
294                // add an disabled input box for feedback purposes
295                // @see dateElement(...)
296                if (addExample2Element) {
297                        def exampleAttrs = new LinkedHashMap()
298                        exampleAttrs.name = attrs.get('name') + 'Example2'
299                        exampleAttrs.class = 'isExample'
300                        exampleAttrs.disabled = 'disabled'
301                        exampleAttrs.size = 30
302                        out << textField(exampleAttrs)
303                }
304
305                out << ' </div>'
306
307                // add help content if it is available
308                if (helpText.size() > 0) {
309                        out << '  <div class="helpContent">'
310                        out << '    ' + helpText
311                        out << '  </div>'
312                }
313
314                out << '</div>'
315        }
316
317        /**
318         * render an ajaxButtonElement
319         * @param Map attrs
320         * @param Closure body  (help text)
321         */
322        def ajaxButtonElement = { attrs, body ->
323                baseElement.call(
324                        'ajaxButton',
325                        attrs,
326                        body
327                )
328        }
329
330        /**
331         * render a textFieldElement
332         * @param Map attrs
333         * @param Closure body  (help text)
334         */
335        def textFieldElement = {attrs, body ->
336                // set default size, or scale to max length if it is less than the default size
337                if (!attrs.get("size")) {
338                        if (attrs.get("maxlength")) {
339                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
340                        } else {
341                                attrs.size = defaultTextFieldSize
342                        }
343                }
344
345                // render template element
346                baseElement.call(
347                        'textField',
348                        attrs,
349                        body
350                )
351        }
352
353        /**
354         * render a textAreaElement
355         * @param Map attrs
356         * @param Closure body  (help text)
357         */
358        def textAreaElement = {attrs, body ->
359                // set default size, or scale to max length if it is less than the default size
360
361                // render template element
362                baseElement.call(
363                        'textArea',
364                        attrs,
365                        body
366                )
367        }
368
369
370        /**
371         * render a select form element
372         * @param Map attrs
373         * @param Closure body  (help text)
374         */
375        def selectElement = {attrs, body ->
376                baseElement.call(
377                        'select',
378                        attrs,
379                        body
380                )
381        }
382
383        /**
384         * render a checkBox form element
385         * @param Map attrs
386         * @param Closure body  (help text)
387         */
388        def checkBoxElement = {attrs, body ->
389                baseElement.call(
390                        'checkBox',
391                        attrs,
392                        body
393                )
394        }
395
396        /**
397         * render a set of radio form elements
398         * @param Map attrs
399         * @param Closure body  (help text)
400         */
401        def radioElement = { attrs, body ->
402                baseElement.call(
403                        'radioList',
404                        attrs,
405                        body
406                )
407        }
408
409        /**
410         * render a set of radio elements
411         * @param Map attrs
412         * @param Closure body  (help text)
413         */
414        def radioList = { attrs ->
415                def checked = true
416
417                attrs.elements.each {
418                        out << radio(
419                                name: attrs.name,
420                                value: it,
421                                checked: (attrs.value == it || (!attrs.value && checked))
422                        )
423                        out << it
424                        checked = false
425                }
426        }
427
428        /**
429         * render a dateElement
430         * NOTE: datepicker is attached through wizard.js!
431         * @param Map attrs
432         * @param Closure body  (help text)
433         */
434        def dateElement = {attrs, body ->
435                // transform value?
436                if (attrs.value instanceof Date) {
437                        // transform date instance to formatted string (dd/mm/yyyy)
438                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
439                }
440
441                // add 'rel' field to identity the datefield using javascript
442                attrs.rel = 'date'
443
444                // set some textfield values
445                attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10
446                attrs.addExampleElement = true
447
448                // render a normal text field
449                //out << textFieldElement(attrs,body)
450                textFieldElement.call(
451                        attrs,
452                        body
453                )
454        }
455
456        /**
457         * render a dateElement
458         * NOTE: datepicker is attached through wizard.js!
459         * @param Map attrs
460         * @param Closure body  (help text)
461         */
462        def timeElement = {attrs, body ->
463                // transform value?
464                if (attrs.value instanceof Date) {
465                        // transform date instance to formatted string (dd/mm/yyyy)
466                        attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value)
467                }
468
469                // add 'rel' field to identity the field using javascript
470                attrs.rel = 'datetime'
471
472                attrs.addExampleElement = true
473                attrs.addExample2Element = true
474                attrs.maxlength = 16
475
476                // render a normal text field
477                //out << textFieldElement(attrs,body)
478                textFieldElement.call(
479                        attrs,
480                        body
481                )
482        }
483
484        /**
485         * Button form element
486         * @param Map attributes
487         * @param Closure help content
488         */
489        def buttonElement = {attrs, body ->
490                // render template element
491                baseElement.call(
492                        'ajaxButton',
493                        attrs,
494                        body
495                )
496        }
497
498
499        /**
500         * Term form element
501         * @param Map attributes
502         * @param Closure help content
503         */
504        def termElement = { attrs, body ->
505                // render term element
506                baseElement.call(
507                        'termSelect',
508                        attrs,
509                        body
510                )
511        }
512
513        /**
514         * File form element
515         * @param Map attributes
516         * @param Closure help content
517         */
518        def fileFieldElement = { attrs, body ->
519                // render term element
520                baseElement.call(
521                        'fileField',
522                        attrs,
523                        body
524                )
525        }
526
527        def fileField = { attrs ->
528            /*
529            out << '<input type="file" name="' + attrs.name + '"/>'
530            if( attrs.value ) {
531                out << '<a href="' + resource(dir: '') + '/file/get/' + attrs.value + '" class="isExample">Now contains: ' + attrs.value + '</a>'
532            }
533            */
534
535            out << '<div id="upload_button_' + attrs.name + '" class="upload_button">Upload</div>';
536            out << '<input type="hidden" name="' + attrs.name + '" id="' + attrs.name + '" value="' + attrs.value + '">';
537            out << '<div id="' + attrs.name + 'Example" class="upload_info"></div>';
538            out << '<script type="text/javascript">';
539            out << '  $(document).ready( function() { ';
540            out << '    var filename = "' + attrs.value + '";';
541            out << '    fileUploadField( "' + attrs.name + '" );';
542            out << '    if( filename != "" ) {';
543            out << '      $("#' + attrs.name + 'Example").html("Current file: " + createFileHTML( filename ) )';
544            out << '    }';
545            out << '  } );';
546            out << "</script>\n";
547        }
548
549        /**
550         * Term select element
551         * @param Map attributes
552         */
553        def termSelect = { attrs ->
554                def from = []
555
556                // got ontologies?
557                if (attrs.ontologies) {
558                        // are the ontologies a string?
559                        if (attrs.ontologies instanceof String) {
560                                attrs.ontologies.split(/\,/).each() { ncboId ->
561                                        // trim the id
562                                        ncboId.trim()
563
564                                        // fetch all terms for this ontology
565                                        def ontology = Ontology.findAllByNcboId(ncboId)
566
567                                        // does this ontology exist?
568                                        if (ontology) {
569                                                ontology.each() {
570                                                        Term.findAllByOntology(it).each() {
571                                                                // key = ncboId:concept-id
572                                                                from[ from.size() ] = it.name
573                                                        }
574                                                }
575                                        }
576                                }
577                        } else if (attrs.ontologies instanceof Set) {
578                                // are they a set instead?
579                                def ontologyList = ""
580
581                                // iterate through set
582                                attrs.ontologies.each() { ontology ->
583                                        ontologyList += ontology.ncboId + ","
584
585                                        Term.findAllByOntology(ontology).each() {
586                                                from[ from.size() ] = it.name
587                                        }
588
589                                        // strip trailing comma
590                                        attrs.ontologies = ontologyList[0..-2]
591                                }
592                        }
593
594                        // sort alphabetically
595                        from.sort()
596                       
597                        // define 'from'
598                        attrs.from = from
599
600                        // add 'rel' attribute
601                        attrs.rel = 'term'
602
603                        out << select(attrs)
604                } else {
605                        out << "<b>ontologies missing!</b>"
606                }
607        }
608
609        /**
610         * Ontology form element
611         * @param Map attributes
612         * @param Closure help content
613         */
614        def ontologyElement = { attrs, body ->
615                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
616                // @see ontology-chooser.js, table-editor.js
617                baseElement.call(
618                        'textField',
619                        [
620                            name: attrs.name,
621                                value: attrs.value,
622                                description: attrs.description,
623                                rel: 'ontology-' + ((attrs.ontology) ? attrs.ontology : 'all'),
624                                size: 25
625                        ],
626                        body
627                )
628                out << hiddenField(
629                        name: attrs.name + '-concept_id'
630                )
631                out << hiddenField(
632                        name: attrs.name + '-ontology_id'
633                )
634                out << hiddenField(
635                        name: attrs.name + '-full_id'
636                )
637        }
638
639        /**
640         * Study form element
641         * @param Map attributes
642         * @param Closure help content
643         */
644        def studyElement = { attrs, body ->
645                // render study element
646                baseElement.call(
647                        'studySelect',
648                        attrs,
649                        body
650                )
651        }
652
653        /**
654         * render a study select element
655         * @param Map attrs
656         */
657        def studySelect = { attrs ->
658                // for now, just fetch all studies
659                attrs.from = Study.findAll()
660
661                // got a name?
662                if (!attrs.name) {
663                        attrs.name = "study"
664                }
665
666                // got result?
667                if (attrs.from.size() > 0) {
668                        out << select(attrs)
669                } else {
670                        // no, return false to make sure this element
671                        // is not rendered in the template
672                        return false
673                }
674        }
675
676        /**
677         * Template form element
678         * @param Map attributes
679         * @param Closure help content
680         */
681        def templateElement = {attrs, body ->
682                // add a rel element if it does not exist
683                if (!attrs.rel) {
684                        attrs.rel = 'template'
685                }
686               
687                // render template element
688                baseElement.call(
689                        'templateSelect',
690                        attrs,
691                        body
692                )
693        }
694
695        /**
696         * render a template select element
697         * @param Map attrs
698         */
699        def templateSelect = {attrs ->
700                def entity = attrs.remove('entity')
701
702                // add the entity class name to the element
703                // do we have crypto information available?
704                if (grailsApplication.config.crypto) {
705                        // generate a Blowfish encrypted and Base64 encoded string.
706                        attrs['entity'] = Blowfish.encryptBase64(
707                                entity.toString().replaceAll(/^class /, ''),
708                                grailsApplication.config.crypto.shared.secret
709                        )
710                } else {
711                        // base64 only; this is INSECURE! As this class
712                        // is instantiated elsewehere. Possibly exploitable!
713                        attrs['entity'] = entity.toString().replaceAll(/^class /, '').bytes.encodeBase64()
714                }
715
716                // fetch templates
717                if (attrs.remove('addDummy')) {
718                        attrs.from = ['']
719                        if (entity && entity instanceof Class) {
720                                Template.findAllByEntity(entity).each() {
721                                        attrs.from[attrs.from.size()] = it
722                                }
723                        }
724                } else {
725                        attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
726                }
727
728                // got a name?
729                if (!attrs.name) {
730                        attrs.name = 'template'
731                }
732
733                // got result?
734                if (attrs.from.size() > 0) {
735                        // transform all values into strings
736                        def from = []
737                        attrs.from.each { from[ from.size() ] = it.toString() }
738
739                        // sort alphabetically
740                        from.sort()
741
742                        // set attributes
743                        attrs.from = from
744                        attrs.value = (attrs.value) ? attrs.value.toString() : ''
745
746                        // output select element
747                        out << select(attrs)
748                } else {
749                        // no, return false to make sure this element
750                        // is not rendered in the template
751                        return false
752                }
753        }
754
755        /**
756         * Protocol form element
757         * @param Map attributes
758         * @param Closure help content
759         */
760        def protocolElement = {attrs, body ->
761                // render protocol element
762                baseElement.call(
763                        'protocolSelect',
764                        attrs,
765                        body
766                )
767        }
768
769        /**
770         * render a protocol select element
771         * @param Map attrs
772         */
773        def protocolSelect = {attrs ->
774                // fetch all protocold
775                attrs.from = Protocol.findAll() // for now, all protocols
776
777                // got a name?
778                if (!attrs.name) {
779                        attrs.name = 'protocol'
780                }
781
782                out << select(attrs)
783        }
784
785        def show = {attrs ->
786                // is object parameter set?
787                def o = attrs.object
788
789                println o.getProperties();
790                o.getProperties().each {
791                        println it
792                }
793
794                out << "!! test version of 'show' tag !!"
795        }
796
797        /**
798         * render table headers for all subjectFields in a template
799         * @param Map attributes
800         */
801        def templateColumnHeaders = { attrs ->
802                def entity              = (attrs.get('entity'))
803                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
804
805                // got a template?
806                if (template) {
807                        // render template fields
808                        entity.giveFields().each() {
809                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
810
811                                out << '<div class="' + attrs.get('class') + '">' + ucName + (it.unit ? " (${it.unit})" : '')
812                                if (it.comment) {
813                                        out << '<div class="helpIcon"></div>'
814                                        out << '<div class="helpContent">' + it.comment + '</div>'
815                                }
816                                out << '</div>'
817                        }
818                }
819        }
820
821        def templateColumns = { attrs ->
822                // render template fields as columns
823                attrs.renderType = 'column'
824                out << renderTemplateFields(attrs)
825        }
826
827        def templateElements = { attrs ->
828                // render template fields as form elements
829                attrs.renderType = 'element'
830                out << renderTemplateFields(attrs)
831        }
832
833        /**
834         * render form elements based on an entity's template
835         * @param Map attributes
836         * @param String body
837         */
838        def renderTemplateFields = { attrs ->
839                def renderType  = attrs.remove('renderType')
840                def entity              = (attrs.get('entity'))
841                def prependName = (attrs.get('name')) ? attrs.remove('name')+'_' : ''
842                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
843                def inputElement= null
844
845                // got a template?
846                if (template) {
847                        // render template fields
848                        entity.giveFields().each() {
849                                def fieldValue  = entity.getFieldValue(it.name)
850                                def helpText    = (it.comment && renderType == 'element') ? it.comment : ''
851                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
852
853                                // output column opening element?
854                                if (renderType == 'column') {
855                                        out << '<div class="' + attrs.get('class') + '">'
856                                }
857
858                                switch (it.type.toString()) {
859                                        case ['STRING', 'INTEGER', 'FLOAT', 'DOUBLE']:
860                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
861                                                out << "$inputElement"(
862                                                        description: ucName,
863                                                        name: prependName + it.escapedName(),
864                                                        value: fieldValue
865                                                ){helpText}
866                                                break
867                                        case 'TEXT':
868                                                inputElement = (renderType == 'element') ? 'textAreaElement' : 'textField'
869                                                out << "$inputElement"(
870                                                        description: ucName,
871                                                        name: prependName + it.escapedName(),
872                                                        value: fieldValue
873                                                ){helpText}
874                                                break
875                                        case 'STRINGLIST':
876                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
877                                                if (!it.listEntries.isEmpty()) {
878                                                        out << "$inputElement"(
879                                                                description: ucName,
880                                                                name: prependName + it.escapedName(),
881                                                                from: it.listEntries,
882                                                                value: fieldValue
883                                                        ){helpText}
884                                                } else {
885                                                        out << '<span class="warning">no values!!</span>'
886                                                }
887                                                break
888                                        case 'ONTOLOGYTERM':
889                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
890                                                // @see ontology-chooser.js
891                                                inputElement = (renderType == 'element') ? 'termElement' : 'termSelect'
892
893                                                if (it.ontologies) {
894                                                        out << "$inputElement"(
895                                                                description     : ucName,
896                                                                name            : prependName + it.escapedName(),
897                                                                value           : fieldValue.toString(),
898                                                                ontologies      : it.ontologies
899                                                        ){helpText}
900                                                } else {
901                                                        out << "$inputElement"(
902                                                                description     : ucName,
903                                                                name            : prependName + it.escapedName(),
904                                                                value           : fieldValue.toString()
905                                                        ){helpText}
906                                                }
907                                                break
908                                        case 'ONTOLOGYTERM-old':
909                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
910                                                // @see ontology-chooser.js
911                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
912                                                out << "$inputElement"(
913                                                        name: prependName + it.escapedName(),
914                                                        value: fieldValue,
915                                                        rel: 'ontology-all',
916                                                        size: 100
917                                                )
918                                                out << hiddenField(
919                                                        name: prependName + it.name + '-concept_id',
920                                                        value: fieldValue
921                                                )
922                                                out << hiddenField(
923                                                        name: prependName + it.escapedName() + '-ontology_id',
924                                                        value: fieldValue
925                                                )
926                                                out << hiddenField(
927                                                        name: prependName + it.escapedName() + '-full_id',
928                                                        value: fieldValue
929                                                )
930                                                break
931                                        case 'DATE':
932                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
933
934                                                // transform value?
935                                                if (fieldValue instanceof Date) {
936                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
937                                                                // transform date instance to formatted string (dd/mm/yyyy)
938                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
939                                                        } else {
940                                                                // transform to date + time
941                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
942                                                        }
943                                                }
944
945                                                // render element
946                                                out << "$inputElement"(
947                                                        description: ucName,
948                                                        name: prependName + it.escapedName(),
949                                                        value: fieldValue,
950                                                        rel: 'date'
951                                                ){helpText}
952                                                break
953                                        case ['RELTIME']:
954                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
955                                                out << "$inputElement"(
956                                                        description: ucName,
957                                                        name: prependName + it.escapedName(),
958                                                        value: new RelTime( fieldValue ).toString(),
959                                                        addExampleElement: true,
960                                                        onBlur: 'showExampleReltime(this)'
961                                                ){helpText}
962                                                break
963                                        case ['FILE']:
964                                                inputElement = (renderType == 'element') ? 'fileFieldElement' : 'fileField'
965                                                out << "$inputElement"(
966                                                        description: ucName,
967                                                        name: prependName + it.escapedName(),
968                                                        value: fieldValue ? fieldValue : "",
969                                                        addExampleElement: true
970                                                ){helpText}
971                                                break
972                                        default:
973                                                // unsupported field type
974                                                out << '<span class="warning">!' + it.type + '</span>'
975                                                break
976                                }
977
978                                // output column closing element?
979                                if (renderType == 'column') {
980                                        out << '</div>'
981                                }
982                        }
983                }
984        }
985
986        def PublicationSelectElement = { attrs, body ->
987
988            attrs.description = 'Publications';
989            // render list with publications currently available
990            baseElement.call(
991                    '_publicationList',
992                    attrs,
993                    body
994            )
995
996            attrs.description = '';
997
998            // render 'Add publication button'
999            baseElement.call(
1000                    '_publicationAddButton',
1001                    attrs,
1002                    body
1003            )
1004        }
1005
1006        /**
1007         * Renders a input box for publications
1008         */
1009        def publicationSelect = { attrs, body ->
1010                if( attrs.get( 'value' ) == null ) {
1011                    attrs.value = [];
1012                }
1013                if( attrs.get( 'description' ) == null ) {
1014                    attrs.description = '';
1015                }
1016                out << '<form id="' + attrs.name + '_form">';
1017                out << textField(
1018                            name: attrs.get( "name" ),
1019                            value: '',
1020                            rel: 'publication-pubmed',
1021                            style: 'width: 400px;'
1022                );
1023                out << '</form>';
1024                out << '<script type="text/javascript">';
1025                out << '  iField = $( "#' + attrs.get( 'name' ) + '" );';
1026                out << '  new PublicationChooser().initAutocomplete( iField );';
1027                out << '</script>';
1028        }
1029
1030        def _publicationList = { attrs, body ->
1031           def display_none = 'none';
1032           if( !attrs.get( 'value' ) || attrs.get( 'value' ).size() == 0 ) {
1033                display_none =  'inline';
1034           }
1035           
1036            // Add a unordered list
1037            out << '<ul class="publication_list" id="' + attrs.name + '_list">';
1038
1039            out << '<li>';
1040            out << '<span class="publication_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1041            out << 'No publications selected';
1042            out << '</span>';
1043            out << '</li>';
1044
1045            out << '</ul>';
1046
1047           // Add the publications using javascript
1048           out << '<script type="text/javascript">'
1049           if( attrs.get( 'value' ) && attrs.get( 'value' ).size() > 0 ) {
1050               def i = 0;
1051               attrs.get( 'value' ).each {
1052                    out << 'showPublication( ';
1053                    out << '  "' + attrs.name + '",';
1054                    out << '  ' + it.id + ',';
1055                    out << '  "' + it.title + '",';
1056                    out << '  "' + it.authorsList + '",';
1057                    out << '  ' + i++;
1058                    out << ');';
1059                }
1060            }
1061            out << '</script>';
1062
1063            def ids;
1064            if( attrs.get( 'value' ) && attrs.get( 'value' ).size() > 0 ) {
1065                ids = attrs.get( 'value' ).id.join( ',' )
1066            } else {
1067                ids = '';
1068            }
1069            out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids" value="' + ids + '">';
1070        }
1071
1072        def _publicationAddButton = { attrs, body ->
1073
1074            // Output the dialog for the publications
1075            out << '<div id="' + attrs.name + '_dialog">';
1076            out << '<p>Search for a publication on pubmed. You can search on a part of the title or authors. </p>';
1077            out << publicationSelect( attrs, body );
1078            out << '</div>';
1079            out << '<script type="text/javascript">';
1080            out << '     $("#' + attrs.name + '_dialog").dialog({';
1081            out << '         title   : "Add publication",';
1082            out << '         autoOpen: false,';
1083            out << '         width   : 800,';
1084            out << '         height  : 400,';
1085            out << '         modal   : true,';
1086            out << '         position: "center",';
1087            out << '         buttons : {';
1088            out << '            Add  : function() { addPublication( "' + attrs.name + '" ); $(this).dialog("close"); },';
1089            out << '            Close  : function() { $(this).dialog("close"); }';
1090            out << '         },';
1091            out << '         close   : function() {';
1092            out << '             /* closeFunc(this); */';
1093            out << '         }';
1094            out << '     }).width(790).height(400);';
1095            out << '</script>';
1096
1097            out << '<input type="button" onClick="var field = $( \'#' + attrs.name + '\' ); field.autocomplete( \'close\' ); field.val( \'\' );$( \'#' + attrs.name + '_dialog\' ).dialog( \'open\' ); field.focus();" value="Add Publication">';
1098        }
1099
1100}
Note: See TracBrowser for help on using the repository browser.