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

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