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

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