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

Last change on this file since 527 was 527, checked in by roberth, 14 years ago

Adding of contacts is made possible in the study creation wizard.

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