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

Last change on this file since 603 was 603, checked in by duh, 11 years ago
  • development commit
  • 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: 603 $
17 * $Author: duh $
18 * $Date: 2010-06-23 13:51:12 +0000 (wo, 23 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
881println "template: "
882println template
883
884                // got a template?
885                if (template) {
886                        // render template fields
887                        entity.giveFields().each() {
888                                def fieldValue  = entity.getFieldValue(it.name)
889                                def helpText    = (it.comment && renderType == 'element') ? it.comment : ''
890                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
891
892                                // output column opening element?
893                                if (renderType == 'column') {
894                                        out << '<div class="' + attrs.get('class') + '">'
895                                }
896
897                                switch (it.type.toString()) {
898                                        case ['STRING', 'INTEGER', 'FLOAT', 'DOUBLE']:
899                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
900                                                out << "$inputElement"(
901                                                        description: ucName,
902                                                        name: prependName + it.escapedName(),
903                                                        value: fieldValue
904                                                ){helpText}
905                                                break
906                                        case 'TEXT':
907                                                inputElement = (renderType == 'element') ? 'textAreaElement' : 'textField'
908                                                out << "$inputElement"(
909                                                        description: ucName,
910                                                        name: prependName + it.escapedName(),
911                                                        value: fieldValue
912                                                ){helpText}
913                                                break
914                                        case 'STRINGLIST':
915                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
916                                                if (!it.listEntries.isEmpty()) {
917                                                        out << "$inputElement"(
918                                                                description: ucName,
919                                                                name: prependName + it.escapedName(),
920                                                                from: it.listEntries,
921                                                                value: fieldValue
922                                                        ){helpText}
923                                                } else {
924                                                        out << '<span class="warning">no values!!</span>'
925                                                }
926                                                break
927                                        case 'ONTOLOGYTERM':
928                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
929                                                // @see ontology-chooser.js
930                                                inputElement = (renderType == 'element') ? 'termElement' : 'termSelect'
931
932                                                if (it.ontologies) {
933                                                        out << "$inputElement"(
934                                                                description     : ucName,
935                                                                name            : prependName + it.escapedName(),
936                                                                value           : fieldValue.toString(),
937                                                                ontologies      : it.ontologies,
938                                                                addDummy        : addDummy
939                                                        ){helpText}
940                                                } else {
941                                                        out << "$inputElement"(
942                                                                description     : ucName,
943                                                                name            : prependName + it.escapedName(),
944                                                                value           : fieldValue.toString(),
945                                                                addDummy        : addDummy
946                                                        ){helpText}
947                                                }
948                                                break
949                                        case 'ONTOLOGYTERM-old':
950                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
951                                                // @see ontology-chooser.js
952                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
953                                                out << "$inputElement"(
954                                                        name: prependName + it.escapedName(),
955                                                        value: fieldValue,
956                                                        rel: 'ontology-all',
957                                                        size: 100
958                                                )
959                                                out << hiddenField(
960                                                        name: prependName + it.name + '-concept_id',
961                                                        value: fieldValue
962                                                )
963                                                out << hiddenField(
964                                                        name: prependName + it.escapedName() + '-ontology_id',
965                                                        value: fieldValue
966                                                )
967                                                out << hiddenField(
968                                                        name: prependName + it.escapedName() + '-full_id',
969                                                        value: fieldValue
970                                                )
971                                                break
972                                        case 'DATE':
973                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
974
975                                                // transform value?
976                                                if (fieldValue instanceof Date) {
977                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
978                                                                // transform date instance to formatted string (dd/mm/yyyy)
979                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
980                                                        } else {
981                                                                // transform to date + time
982                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
983                                                        }
984                                                }
985
986                                                // render element
987                                                out << "$inputElement"(
988                                                        description: ucName,
989                                                        name: prependName + it.escapedName(),
990                                                        value: fieldValue,
991                                                        rel: 'date'
992                                                ){helpText}
993                                                break
994                                        case ['RELTIME']:
995                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
996                                                out << "$inputElement"(
997                                                        description: ucName,
998                                                        name: prependName + it.escapedName(),
999                                                        value: new RelTime( fieldValue ).toString(),
1000                            addExampleElement: true,
1001                            onBlur: 'showExampleReltime(this)'
1002                                                ){helpText}
1003                                                break
1004                                        case ['FILE']:
1005                                                inputElement = (renderType == 'element') ? 'fileFieldElement' : 'fileField'
1006                                                out << "$inputElement"(
1007                                                        description: ucName,
1008                                                        name: prependName + it.escapedName(),
1009                                                        value: fieldValue ? fieldValue : "",
1010                            addExampleElement: true
1011                                                ){helpText}
1012                                                break
1013                                        case ['BOOLEAN']:
1014                                                inputElement = (renderType == 'element') ? 'checkBoxElement' : 'checkBox'
1015                                                out << "$inputElement"(
1016                                                        description: ucName,
1017                                                        name: prependName + it.escapedName(),
1018                                                        value: fieldValue
1019                                                ){helpText}
1020                                                break
1021                                        default:
1022                                                // unsupported field type
1023                                                out << '<span class="warning">!' + it.type + '</span>'
1024                                                break
1025                                }
1026
1027                                // output column closing element?
1028                                if (renderType == 'column') {
1029                                        out << '</div>'
1030                                }
1031                        }
1032                }
1033        }
1034
1035        def PublicationSelectElement = { attrs, body ->
1036                attrs.description = 'Publications';
1037                // render list with publications currently available
1038                baseElement.call(
1039                        '_publicationList',
1040                        attrs,
1041                        body
1042                )
1043
1044                attrs.description = '';
1045
1046                // render 'Add publication button'
1047                baseElement.call(
1048                        '_publicationAddButton',
1049                        attrs,
1050                        body
1051                )
1052        }
1053
1054        /**
1055         * Renders a input box for publications
1056         */
1057        def publicationSelect = { attrs, body ->
1058                if (attrs.get('value') == null) {
1059                        attrs.value = [];
1060                }
1061                if (attrs.get('description') == null) {
1062                        attrs.description = '';
1063                }
1064                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1065                out << textField(
1066                        name: attrs.get("name"),
1067                        value: '',
1068                        rel: 'publication-pubmed',
1069                        style: 'width: 400px;'
1070                );
1071                out << '</form>';
1072                out << '<script type="text/javascript">';
1073                out << '  var onSelect = function( chooserObject, inputElement, event, ui ) { selectPubMedAdd( chooserObject, inputElement, event, ui ); enableButton( ".' + attrs.name + '_publication_dialog", "Add", true ); };'
1074                out << '  iField = $( "#' + attrs.get('name') + '" );';
1075                out << '  new PublicationChooser().initAutocomplete( iField, { "select" : onSelect } );';
1076                out << '</script>';
1077        }
1078
1079        def _publicationList = { attrs, body ->
1080                def display_none = 'none';
1081                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1082                        display_none = 'inline';
1083                }
1084
1085                // Add a unordered list
1086                out << '<ul class="publication_list" id="' + attrs.name + '_list">';
1087
1088                out << '<li>';
1089                out << '<span class="publication_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1090                out << 'No publications selected';
1091                out << '</span>';
1092                out << '</li>';
1093
1094                out << '</ul>';
1095
1096                // Add the publications using javascript
1097                out << '<script type="text/javascript">'
1098                if (attrs.get('value') && attrs.get('value').size() > 0) {
1099                        def i = 0;
1100                        attrs.get('value').each {
1101                                out << 'showPublication( ';
1102                                out << '  "' + attrs.name + '",';
1103                                out << '  ' + it.id + ',';
1104                                out << '  "' + it.title + '",';
1105                                out << '  "' + it.authorsList + '",';
1106                                out << '  ' + i++;
1107                                out << ');';
1108                        }
1109                }
1110                out << '</script>';
1111
1112                def ids;
1113                if (attrs.get('value') && attrs.get('value').size() > 0) {
1114                        ids = attrs.get('value').id.join(',')
1115                } else {
1116                        ids = '';
1117                }
1118                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1119        }
1120
1121        def _publicationAddButton = { attrs, body ->
1122
1123                // Output the dialog for the publications
1124                out << '<div id="' + attrs.name + '_dialog">';
1125                out << '<p>Search for a publication on pubmed. You can search on a part of the title or authors. </p>';
1126                out << publicationSelect(attrs, body);
1127                out << '</div>';
1128                out << '<script type="text/javascript">';
1129                out << '  createPublicationDialog( "' + attrs.name + '" );'
1130                out << '</script>';
1131
1132                out << '<input type="button" onClick="openPublicationDialog(\'' + attrs.name + '\' );" value="Add Publication">';
1133        }
1134
1135        def ContactSelectElement = { attrs, body ->
1136
1137                attrs.description = 'Contacts';
1138                // render list with publications currently available
1139                baseElement.call(
1140                        '_contactList',
1141                        attrs,
1142                        body
1143                )
1144
1145                attrs.description = '';
1146
1147                // render 'publications list'
1148                out << '<div id="' + attrs.name + '_dialog" class="contacts_dialog" style="display: none;">'
1149                baseElement.call(
1150                        '_personSelect',
1151                        attrs,
1152                        body
1153                )
1154                baseElement.call(
1155                        '_roleSelect',
1156                        attrs,
1157                        body
1158                )
1159                baseElement.call(
1160                        '_contactAddButtonAddition',
1161                        attrs,
1162                        body
1163                )
1164                out << '</div>';
1165
1166                // render 'Add contact button'
1167                baseElement.call(
1168                        '_contactAddDialogButton',
1169                        attrs,
1170                        body
1171                )
1172        }
1173
1174        def _contactList = { attrs, body ->
1175                def display_none = 'none';
1176                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1177                        display_none = 'inline';
1178                }
1179
1180                // Add a unordered list
1181                out << '<ul class="contact_list" id="' + attrs.name + '_list">';
1182
1183                out << '<li>';
1184                out << '<span class="contacts_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1185                out << 'No contacts selected';
1186                out << '</span>';
1187                out << '</li>';
1188
1189                out << '</ul>';
1190
1191                // Add the contacts using javascript
1192                out << '<script type="text/javascript">'
1193                if (attrs.get('value') && attrs.get('value').size() > 0) {
1194                        def i = 0;
1195                        attrs.get('value').each {
1196                                out << 'showContact( ';
1197                                out << '  "' + attrs.name + '",';
1198                                out << '  "' + it.person.id + '-' + it.role.id + '",';
1199                                out << '  "' + it.person.lastName + ', ' + it.person.firstName + (it.person.prefix ? ' ' + it.person.prefix : '') + '",';
1200                                out << '  "' + it.role.name + '",';
1201                                out << '  ' + i++;
1202                                out << ');';
1203                        }
1204                }
1205                out << '</script>';
1206
1207                def ids = '';
1208                if (attrs.get('value') && attrs.get('value').size() > 0) {
1209                        ids = attrs.get('value').collect { it.person.id + '-' + it.role.id }
1210                        ids = ids.join(',');
1211                }
1212                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1213        }
1214
1215        def _contactAddSelect = { attrs, body ->
1216                out << _personSelect(attrs) + _roleSelect(attrs);
1217        }
1218
1219        def _contactAddButtonAddition = { attrs, body ->
1220                out << '<input type="button" onClick="addContact ( \'' + attrs.name + '\' ); $(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Add">';
1221                out << '<input type="button" onClick="$(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Close">';
1222        }
1223
1224        def _contactAddDialogButton = { attrs, body ->
1225                out << '<input type="button" onClick="$( \'#' + attrs.name + '_dialog\' ).show(); $(this).hide();" id="' + attrs.name + '_dialogButton" value="Add Contact">';
1226        }
1227        /**
1228         * Person select element
1229         * @param Map attributes
1230         */
1231        def _personSelect = { attrs ->
1232                def selectAttrs = new LinkedHashMap();
1233
1234                // define 'from'
1235                def persons = Person.findAll().sort({ a, b -> a.lastName == b.lastName ? (a.firstName <=> b.firstName) : (a.lastName <=> b.lastName) } as Comparator);
1236                selectAttrs.from = persons.collect { it.lastName + ', ' + it.firstName + (it.prefix ? ' ' + it.prefix : '') }
1237                selectAttrs.keys = persons.id;
1238
1239                // add 'rel' attribute
1240                selectAttrs.rel = 'person'
1241                selectAttrs.name = attrs.name + '_person';
1242
1243                // add a dummy field
1244                selectAttrs.from.add(0,'')
1245                selectAttrs.keys.add(0,'')
1246
1247                out << "Person: " + select(selectAttrs)
1248        }
1249
1250        /**
1251         * Role select element
1252         * @param Map attributes
1253         */
1254        def _roleSelect = { attrs ->
1255                def selectAttrs = new LinkedHashMap();
1256
1257                // define 'from'
1258                def roles = PersonRole.findAll();
1259                selectAttrs.from = roles.collect { it.name };
1260                selectAttrs.keys = roles.id;
1261
1262                // add 'rel' attribute
1263                selectAttrs.rel = 'role'
1264                selectAttrs.name = attrs.name + '_role';
1265
1266                // add a dummy field
1267                selectAttrs.from.add(0,'')
1268                selectAttrs.keys.add(0,'')
1269
1270                out << "Role: " + select(selectAttrs)
1271        }
1272}
Note: See TracBrowser for help on using the repository browser.