root/trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy @ 1182

Revision 1182, 39.8 KB (checked in by robert@…, 3 years ago)

Improved file upload fields so users can delete existing files and are able to access uploaded files (using a tag in the tag library). See ticket #17

  • Property svn:keywords set to Author Date Rev
Line 
1package dbnp.studycapturing
2
3import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib
4import dbnp.studycapturing.*
5import dbnp.authentication.SecUser
6import dbnp.data.*
7import cr.co.arquetipos.crypto.Blowfish
8
9/**
10 * Wizard tag library
11 *
12 * @author Jeroen Wesbeek
13 * @since 20100113
14 * @package wizard
15 *
16 * Revision information:
17 * $Rev$
18 * $Author$
19 * $Date$
20 */
21class WizardTagLib extends JavascriptTagLib {
22        def AuthenticationService
23       
24        // define the tag namespace (e.g.: <wizard:action ... />
25        static namespace = "wizard"
26
27        // define the AJAX provider to use
28        static ajaxProvider = "jquery"
29
30        // define default text field width
31        static defaultTextFieldSize = 25;
32
33        /**
34         * ajaxButton tag, this is a modified version of the default
35         * grails submitToRemote tag to work with grails webflows.
36         * Usage is identical to submitToRemote with the only exception
37         * that a 'name' form element attribute is required. E.g.
38         * <wizard:ajaxButton name="myAction" value="myButton ... />
39         *
40         * you can also provide a javascript function to execute after
41         * success. This behaviour differs from the default 'after'
42         * action which always fires after a button press...
43         *
44         * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
45         * @see http://www.grails.org/WebFlow
46         * @see http://www.grails.org/Tag+-+submitToRemote
47         * @todo perhaps some methods should be moved to a more generic
48         *        'webflow' taglib or plugin
49         * @param Map attributes
50         * @param Closure body
51         */
52        def ajaxButton = { attrs, body ->
53                // get the jQuery version
54                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
55
56                // fetch the element name from the attributes
57                def elementName = attrs['name'].replaceAll(/ /, "_")
58
59                // javascript function to call after success
60                def afterSuccess = attrs['afterSuccess']
61
62                // src parameter?
63                def src = attrs['src']
64                def alt = attrs['alt']
65
66                // generate a normal submitToRemote button
67                def button = submitToRemote(attrs, body)
68
69                /**
70                 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
71                 * not properly work with AJAX as the submitToRemote button does not
72                 * handle and submit the form properly. In order to support webflows
73                 * this method modifies two parts of a 'normal' submitToRemote button:
74                 *
75                 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
76                 *    action refers to the button and / or the action upon that button.
77                 *    However, it should point to the form the button is part of as the
78                 *    the button should submit the form data.
79                 * 2) prepend the button name to the serialized data. The default behaviour
80                 *    of submitToRemote is to remove the element name altogether, while
81                 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
82                 *    the appropriate webflow action. Hence, we are going to prepend the
83                 *    serialized formdata with an _eventId_BUTTONNAME parameter.
84                 */
85                if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
86                        // fix for older jQuery plugin versions
87                        button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
88                } else {
89                        // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
90                        // this.form part has been fixed. Consequently, our wrapper has changed as well...
91                        button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
92                }
93
94                // add an after success function call?
95                // usefull for performing actions on success data (hence on refreshed
96                // wizard pages, such as attaching tooltips)
97                if (afterSuccess) {
98                        button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
99                }
100
101                // got an src parameter?
102                if (src) {
103                        def replace = 'type="image" src="' + src + '"'
104
105                        if (alt) replace = replace + ' alt="' + alt + '"'
106
107                        button = button.replaceFirst(/type="button"/, replace)
108                }
109
110                // replace double semi colons
111                button = button.replaceAll(/;{2,}/, ';')
112
113                // render button
114                out << button
115        }
116
117        /**
118         * generate a ajax submit JavaScript
119         * @see WizardTagLib::ajaxFlowRedirect
120         * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
121         */
122        def ajaxSubmitJs = { attrs, body ->
123                // define AJAX provider
124                setProvider([library: ajaxProvider])
125
126                // got a function name?
127                def functionName = attrs.remove('functionName')
128                if (functionName && !attrs.get('name')) {
129                        attrs.name = functionName
130                }
131
132                // generate an ajax button
133                def button = this.ajaxButton(attrs, body)
134
135                // strip the button part to only leave the Ajax call
136                button = button.replaceFirst(/<[^\"]*onclick=\"/, '')
137                button = button.replaceFirst(/return false.*/, '')
138
139                // change form if a form attribute is present
140                if (attrs.get('form')) {
141            button = button.replace(
142                                "jQuery(this).parents('form:first')",
143                                "\$('" + attrs.get('form') + "')"
144                        )
145                }
146
147                // change 'this' if a this attribute is preset
148                if (attrs.get('this')) {
149                        button = button.replace('this', attrs.get('this'))
150                }
151
152                out << button
153        }
154
155        /**
156         * generate ajax webflow redirect javascript
157         *
158         * As we have an Ajaxified webflow, the initial wizard page
159         * cannot contain a wizard form, as upon a failing submit
160         * (e.g. the form data does not validate) the form should be
161         * shown again. However, the Grails webflow then renders the
162         * complete initial wizard page into the success div. As this
163         * ruins the page layout (a page within a page) we want the
164         * initial page to redirect to the first wizard form to enter
165         * the webflow correctly. We do this by emulating an ajax post
166         * call which updates the wizard content with the first wizard
167         * form.
168         *
169         * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
170         * form = the form identifier
171         * name = the action to execute in the webflow
172         * update = the divs to update upon success or error
173         *
174         * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
175         *
176         * Example initial webflow action to work with this javascript:
177         * ...
178         * mainPage {
179         *      render(view: "/wizard/index")
180         *      onRender {
181         *              flow.page = 1
182         *  }
183         *      on("next").to "pageOne"
184         * }
185         * ...
186         *
187         * @param Map attributes
188         * @param Closure body
189         */
190        def ajaxFlowRedirect = { attrs, body ->
191                // generate javascript
192                out << '<script type="text/javascript">'
193                out << '$(document).ready(function() {'
194                out << ajaxSubmitJs(attrs, body)
195                out << '});'
196                out << '</script>'
197        }
198
199        /**
200         * render the content of a particular wizard page
201         * @param Map attrs
202         * @param Closure body  (help text)
203         */
204        def pageContent = { attrs, body ->
205                // define AJAX provider
206                setProvider([library: ajaxProvider])
207
208                // render new body content
209                //      - this JavaScript variable is used by baseElement to workaround an IE
210                //        specific issue (double submit on onchange events). The hell with IE!
211                //        @see baseElement
212                out << '<script type="text/javascript">var lastRequestTime = 0;</script>'
213                out << render(template: "/wizard/common/tabs")
214                out << '<div class="content">'
215                out << body()
216                out << '</div>'
217                out << render(template: "/wizard/common/navigation")
218                out << render(template: "/wizard/common/error")
219        }
220
221        /**
222         * generate a base form element
223         * @param String inputElement name
224         * @param Map attributes
225         * @param Closure help content
226         */
227        def baseElement = { inputElement, attrs, help ->
228                log.info ".rendering [" + inputElement + "] with name [" + attrs.get('name') + "] and value [" + ((attrs.value) ? attrs.get('value').toString() : "-") + "]"
229
230                // work variables
231                def description = attrs.remove('description')
232                def addExampleElement = attrs.remove('addExampleElement')
233                def addExample2Element = attrs.remove('addExample2Element')
234                def helpText = help().trim()
235
236                // execute inputElement call
237                def renderedElement = "$inputElement"(attrs)
238
239                // if false, then we skip this element
240                if (!renderedElement) return false
241
242                // render a form element
243                out << '<div class="element'+ ((attrs.get('required')) ? ' required' : '') +'"'+ ((attrs.get('elementId')) ? 'id="'+attrs.remove('elementId')+'"': '') + '>'
244                out << ' <div class="description">'
245                out << ((description) ? description.replaceAll(/[a-z][A-Z][a-z]/) { it[0] + ' ' + it[1..2] }.replaceAll(/\w+/) { it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '') } : '')
246                out << ' </div>'
247                out << ' <div class="input">'
248                out << renderedElement
249                out << ((helpText.size() > 0) ? '       <div class="helpIcon"></div>' : '')
250
251                // add an disabled input box for feedback purposes
252                // @see dateElement(...)
253                if (addExampleElement) {
254                        def exampleAttrs = new LinkedHashMap()
255                        exampleAttrs.name = attrs.get('name') + 'Example'
256                        exampleAttrs.class = 'isExample'
257                        exampleAttrs.disabled = 'disabled'
258                        exampleAttrs.size = 30
259                        out << textField(exampleAttrs)
260                }
261
262                // add an disabled input box for feedback purposes
263                // @see dateElement(...)
264                if (addExample2Element) {
265                        def exampleAttrs = new LinkedHashMap()
266                        exampleAttrs.name = attrs.get('name') + 'Example2'
267                        exampleAttrs.class = 'isExample'
268                        exampleAttrs.disabled = 'disabled'
269                        exampleAttrs.size = 30
270                        out << textField(exampleAttrs)
271                }
272
273                out << ' </div>'
274
275                // add help content if it is available
276                if (helpText.size() > 0) {
277                        out << '  <div class="helpContent">'
278                        out << '    ' + helpText
279                        out << '  </div>'
280                }
281
282                out << '</div>'
283        }
284
285        /**
286         * bind an ajax submit to an onChange event
287         * @param attrs
288         * @return attrs
289         */
290        private getAjaxOnChange = { attrs ->
291                // work variables
292                def internetExplorer = (request.getHeader("User-Agent") =~ /MSIE/)
293                def ajaxOnChange = attrs.remove('ajaxOnChange')
294
295                // is ajaxOnChange defined
296                if ( ajaxOnChange ) {
297                        if (!attrs.onChange) attrs.onChange = ''
298
299                        // add onChange AjaxSubmit javascript
300                        if (internetExplorer) {
301                                //              - somehow IE submits these onchanges twice which messes up some parts of the wizard
302                                //                (especially the events page). In order to bypass this issue I have introduced an
303                                //                if statement utilizing the 'before' and 'after' functionality of the submitToRemote
304                                //                function. This check expects lastRequestTime to be in the global Javascript scope,
305                                //                (@see pageContent) and calculates the time difference in miliseconds between two
306                                //                onChange executions. If this is more than 100 miliseconds the request is executed,
307                                //                otherwise it will be ignored... --> 20100527 - Jeroen Wesbeek
308                                attrs.onChange += ajaxSubmitJs(
309                                        [
310                                                before: "var execute=true;try { var currentTime=new Date().getTime();execute = ((currentTime-lastRequestTime) > 100);lastRequestTime=currentTime;  } catch (e) {};if (execute) { 1",
311                                                after: "}",
312                                                functionName: ajaxOnChange,
313                                                url: attrs.get('url'),
314                                                update: attrs.get('update'),
315                                                afterSuccess: attrs.get('afterSuccess')
316                                        ],
317                                        ''
318                                )
319                        } else {
320                                // this another W3C browser that actually behaves as expected... damn you IE, DAMN YOU!
321                                attrs.onChange += ajaxSubmitJs(
322                                        [
323                                                functionName: ajaxOnChange,
324                                                url: attrs.get('url'),
325                                                update: attrs.get('update'),
326                                                afterSuccess: attrs.get('afterSuccess')
327                                        ],
328                                        ''
329                                )
330                        }
331                }
332
333                return attrs
334        }
335
336        /**
337         * render an ajaxButtonElement
338         * @param Map attrs
339         * @param Closure body  (help text)
340         */
341        def ajaxButtonElement = { attrs, body ->
342                baseElement.call(
343                        'ajaxButton',
344                        attrs,
345                        body
346                )
347        }
348
349        /**
350         * render a textFieldElement
351         * @param Map attrs
352         * @param Closure body  (help text)
353         */
354        def textFieldElement = { attrs, body ->
355                // set default size, or scale to max length if it is less than the default size
356                if (!attrs.get("size")) {
357                        if (attrs.get("maxlength")) {
358                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
359                        } else {
360                                attrs.size = defaultTextFieldSize
361                        }
362                }
363
364                // render template element
365                baseElement.call(
366                        'textField',
367                        attrs,
368                        body
369                )
370        }
371
372        /**
373         * render a textAreaElement
374         * @param Map attrs
375         * @param Closure body  (help text)
376         */
377        def textAreaElement = { attrs, body ->
378                // set default size, or scale to max length if it is less than the default size
379
380                // render template element
381                baseElement.call(
382                        'textArea',
383                        attrs,
384                        body
385                )
386        }
387
388
389        /**
390         * render a select form element
391         * @param Map attrs
392         * @param Closure body  (help text)
393         */
394        def selectElement = { attrs, body ->
395                baseElement.call(
396                        'select',
397                        attrs,
398                        body
399                )
400        }
401
402        /**
403         * render a checkBox form element
404         * @param Map attrs
405         * @param Closure body  (help text)
406         */
407        def checkBoxElement = { attrs, body ->
408                baseElement.call(
409                        'checkBox',
410                        attrs,
411                        body
412                )
413        }
414
415        /**
416         * render a set of radio form elements
417         * @param Map attrs
418         * @param Closure body  (help text)
419         */
420        def radioElement = { attrs, body ->
421                baseElement.call(
422                        'radioList',
423                        attrs,
424                        body
425                )
426        }
427
428        /**
429         * render a set of radio elements
430         * @param Map attrs
431         * @param Closure body  (help text)
432         */
433        def radioList = { attrs ->
434                def checked = true
435
436                attrs.elements.each {
437                        out << radio(
438                                name: attrs.name,
439                                value: it,
440                                checked: (attrs.value == it || (!attrs.value && checked))
441                        )
442                        out << it
443                        checked = false
444                }
445        }
446
447        /**
448         * render a dateElement
449         * NOTE: datepicker is attached through wizard.js!
450         * @param Map attrs
451         * @param Closure body  (help text)
452         */
453        def dateElement = { attrs, body ->
454                // transform value?
455                if (attrs.value instanceof Date) {
456                        // transform date instance to formatted string (dd/mm/yyyy)
457                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
458                }
459
460                // add 'rel' field to identity the datefield using javascript
461                attrs.rel = 'date'
462
463                // set some textfield values
464                attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10
465                attrs.addExampleElement = true
466
467                // render a normal text field
468                //out << textFieldElement(attrs,body)
469                textFieldElement.call(
470                        attrs,
471                        body
472                )
473        }
474
475        /**
476         * render a dateElement
477         * NOTE: datepicker is attached through wizard.js!
478         * @param Map attrs
479         * @param Closure body  (help text)
480         */
481        def timeElement = { attrs, body ->
482                // transform value?
483                if (attrs.value instanceof Date) {
484                        // transform date instance to formatted string (dd/mm/yyyy)
485                        attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value)
486                }
487
488                // add 'rel' field to identity the field using javascript
489                attrs.rel = 'datetime'
490
491                attrs.addExampleElement = true
492                attrs.addExample2Element = true
493                attrs.maxlength = 16
494
495                // render a normal text field
496                //out << textFieldElement(attrs,body)
497                textFieldElement.call(
498                        attrs,
499                        body
500                )
501        }
502
503        /**
504         * Button form element
505         * @param Map attributes
506         * @param Closure help content
507         */
508        def buttonElement = { attrs, body ->
509                // render template element
510                baseElement.call(
511                        'ajaxButton',
512                        attrs,
513                        body
514                )
515        }
516
517
518        /**
519         * Term form element
520         * @param Map attributes
521         * @param Closure help content
522         */
523        def termElement = { attrs, body ->
524                // render term element
525                baseElement.call(
526                        'termSelect',
527                        attrs,
528                        body
529                )
530        }
531
532        /**
533         * Term select element
534         * @param Map attributes
535         */
536        // TODO: change termSelect to use Term accessions instead of preferred names, to make it possible to track back
537        // terms from multiple ontologies with possibly the same preferred name
538        def termSelect = { attrs ->
539                def from = []
540
541                // got ontologies?
542                if (attrs.ontologies) {
543                        // are the ontologies a string?
544                        if (attrs.ontologies instanceof String) {
545                                attrs.ontologies.split(/\,/).each() { ncboId ->
546                                        // trim the id
547                                        ncboId.trim()
548
549                                        // fetch all terms for this ontology
550                                        def ontology = Ontology.findAllByNcboId(ncboId)
551
552                                        // does this ontology exist?
553                                        if (ontology) {
554                                                ontology.each() {
555                                                        Term.findAllByOntology(it).each() {
556                                                                // key = ncboId:concept-id
557                                                                from[ from.size() ] = it.name
558                                                        }
559                                                }
560                                        }
561                                }
562                        } else if (attrs.ontologies instanceof Set) {
563                                // are they a set instead?
564                                def ontologyList = ""
565
566                                // iterate through set
567                                attrs.ontologies.each() { ontology ->
568                                        if (ontology) {
569                                                ontologyList += ontology.ncboId + ","
570
571                                                Term.findAllByOntology(ontology).each() {
572                                                        from[ from.size() ] = it.name
573                                                }
574
575                                                // strip trailing comma
576                                                attrs.ontologies = ontologyList[0..-2]
577                                        }
578                                }
579                        }
580
581                        // sort alphabetically
582                        from.sort()
583
584                        // add a dummy field?
585                        if (attrs.remove('addDummy')) {
586                                from.add(0,'')
587                        }
588
589                        // define 'from'
590                        attrs.from = from
591
592                        // add 'rel' attribute
593                        attrs.rel = 'term'
594
595                        // got an ajaxOnChange defined?
596                        attrs = getAjaxOnChange.call(
597                                attrs
598                        )
599
600                        out << select(attrs)
601                } else {
602                        out << "<b>ontologies missing!</b>"
603                }
604        }
605
606        /**
607         * Ontology form element
608         * @param Map attributes
609         * @param Closure help content
610         */
611        def ontologyElement = { attrs, body ->
612                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
613                // @see ontology-chooser.js, table-editor.js
614                baseElement.call(
615                        'textField',
616                        [
617                            name: attrs.name,
618                                value: attrs.value,
619                                description: attrs.description,
620                                rel: 'ontology-' + ((attrs.ontology) ? attrs.ontology : 'all'),
621                                size: 25
622                        ],
623                        body
624                )
625                out << hiddenField(
626                        name: attrs.name + '-concept_id'
627                )
628                out << hiddenField(
629                        name: attrs.name + '-ontology_id'
630                )
631                out << hiddenField(
632                        name: attrs.name + '-full_id'
633                )
634        }
635
636        /**
637         * Study form element
638         * @param Map attributes
639         * @param Closure help content
640         */
641        def studyElement = { attrs, body ->
642                // render study element
643                baseElement.call(
644                        'studySelect',
645                        attrs,
646                        body
647                )
648        }
649
650        /**
651         * render a study select element
652         * @param Map attrs
653         */
654        def studySelect = { attrs ->
655                // Find all studies the user has access to
656                def user = AuthenticationService.getLoggedInUser()
657
658                def c = Study.createCriteria()
659                attrs.from = c.list {
660                    or {
661                        eq( "owner", user )
662                        writers {
663                            eq( "id", user.id )
664                        }
665                    }
666                }
667
668                // got a name?
669                if (!attrs.name) {
670                        attrs.name = "study"
671                }
672
673                // got result?
674                if (attrs.from.size() > 0) {
675                        out << select(attrs)
676                } else {
677                        // no, return false to make sure this element
678                        // is not rendered in the template
679                        return false
680                }
681        }
682
683        /**
684         * Template form element
685         * @param Map attributes
686         * @param Closure help content
687         */
688        def templateElement = { attrs, body ->
689                // render template element
690                baseElement.call(
691                        'templateSelect',
692                        attrs,
693                        body
694                )
695        }
696
697        /**
698         * render a template select element
699         * @param Map attrs
700         */
701        def templateSelect = { attrs ->
702                def entity = attrs.remove('entity')
703
704                // add the entity class name to the element
705                // do we have crypto information available?
706                if (grailsApplication.config.crypto) {
707                        // generate a Blowfish encrypted and Base64 encoded string.
708                        attrs['entity'] = URLEncoder.encode(
709                                Blowfish.encryptBase64(
710                                        entity.toString().replaceAll(/^class /, ''),
711                                        grailsApplication.config.crypto.shared.secret
712                                )
713                        )
714                } else {
715                        // base64 only; this is INSECURE! As this class
716                        // is instantiated elsewehere. Possibly exploitable!
717                        attrs['entity'] = URLEncoder.encode(entity.toString().replaceAll(/^class /, '').bytes.encodeBase64())
718                }
719               
720                // fetch templates
721                attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
722
723                // got a name?
724                if (!attrs.name) {
725                        attrs.name = 'template'
726                }
727
728                // add a rel element if it does not exist
729                if (!attrs.rel) {
730                        attrs.rel = 'template'
731                }
732
733                // got an ajaxOnChange defined?
734                attrs = getAjaxOnChange.call(
735                        attrs
736                )
737
738                // got result?
739                if (attrs.from.size() > 0 || attrs.get('addDummy')) {
740                        // transform all values into strings
741                        def from = []
742                        attrs.from.each { from[ from.size() ] = it.toString() }
743
744                        // sort alphabetically
745                        from.sort()
746
747                        // add a dummy field?
748                        if (attrs.remove('addDummy')) {
749                                from.add(0,'')
750                        }
751
752                        // set attributes
753                        attrs.from = from
754                        attrs.value = (attrs.value) ? attrs.value.toString() : ''
755
756                        // output select element
757                        out << select(attrs)
758                } else {
759                        // no, return false to make sure this element
760                        // is not rendered in the template
761                        return false
762                }
763        }
764
765
766        /**
767         * File form element
768         * @param Map attributes
769         * @param Closure help content
770         */
771        def fileFieldElement = { attrs, body ->
772                // render term element
773                baseElement.call(
774                        'fileField',
775                        attrs,
776                        body
777                )
778        }
779
780        /**
781         * file field.
782         * @param attributes
783         */
784        def fileField = { attrs ->
785                /*
786                out << '<input type="file" name="' + attrs.name + '"/>'
787                if( attrs.value ) {
788                        out << '<a href="' + resource(dir: '') + '/file/get/' + attrs.value + '" class="isExample">Now contains: ' + attrs.value + '</a>'
789                }
790                */
791
792                out << '<div id="upload_button_' + attrs.name + '" class="upload_button">Upload</div>';
793                out << '<input type="hidden" name="' + attrs.name + '" id="' + attrs.name + '" value="' + attrs.value + '">';
794                out << '<div id="' + attrs.name + 'Example" class="upload_info"></div>';
795                out << '<a id="' + attrs.name + 'Delete" class="upload_del" href="#" onClick="if( confirm( \'Are you sure to delete this file?\' ) ) { deleteFile( \'' + attrs.name + '\' ); } return false;"><img src="' + resource( dir: 'images/icons', file: 'delete.png', plugin: 'famfamfam' ) + '"></a>';
796                out << '<script type="text/javascript">';
797                out << '  $(document).ready( function() { ';
798                out << '    var filename = "' + attrs.value + '";';
799                out << '    fileUploadField( "' + attrs.name + '" );';
800                out << '    if( filename != "" ) {';
801                out << '      $("#' + attrs.name + 'Delete").show();';
802                out << '      $("#' + attrs.name + 'Example").html("Current file: " + createFileHTML( filename ) )';
803                out << '    }';
804                out << '  } );';
805                out << "</script>\n";
806        }
807
808        /**
809         * Protocol form element
810         * @param Map attributes
811         * @param Closure help content
812         */
813        def protocolElement = { attrs, body ->
814                // render protocol element
815                baseElement.call(
816                        'protocolSelect',
817                        attrs,
818                        body
819                )
820        }
821
822        /**
823         * render a protocol select element
824         * @param Map attrs
825         */
826        def protocolSelect = { attrs ->
827                // fetch all protocold
828                attrs.from = Protocol.findAll() // for now, all protocols
829
830                // got a name?
831                if (!attrs.name) {
832                        attrs.name = 'protocol'
833                }
834
835                out << select(attrs)
836        }
837
838        def show = { attrs ->
839                // is object parameter set?
840                def o = attrs.object
841
842                println o.getProperties();
843                o.getProperties().each {
844                        println it
845                }
846
847                out << "!! test version of 'show' tag !!"
848        }
849
850        /**
851         * render table headers for all subjectFields in a template
852         * @param Map attributes
853         */
854        def templateColumnHeaders = { attrs ->
855                def entity              = (attrs.get('entity'))
856                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
857                def columnWidths= (attrs.get('columnWidths')) ? attrs.remove('columnWidths') : []
858
859                // got a template?
860                if (template) {
861                        // render template fields
862                        entity.giveFields().each() {
863                                // Format the column name by:
864                                // - separating combined names (SampleName --> Sample Name)
865                                // - capitalizing every seperate word
866                                def ucName = it.name.replaceAll(/[a-z][A-Z][a-z]/) {
867                                        it[0] + ' ' + it[1..2]
868                                }.replaceAll(/\w+/) {
869                                        it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '')
870                                }
871
872                                // strip spaces
873                                def ucNameSpaceless = ucName.replaceAll(/ /) { '' }
874
875                                // do we have to use a specific width for this column?
876                                if (columnWidths[ucName]) {
877                                        out << '<div class="' + attrs.get('class') + '" style="width:' + columnWidths[ucNameSpaceless] + 'px;" rel="resized">' + ucName + (it.unit ? " (${it.unit})" : '')
878                                } else {
879                                        out << '<div class="' + attrs.get('class') + '">' + ucName + (it.unit ? " (${it.unit})" : '')
880                                }
881                                if (it.comment) {
882                                        out << '<div class="helpIcon"></div>'
883                                        out << '<div class="helpContent">' + it.comment + '</div>'
884                                }
885                                out << '</div>'
886                        }
887                }
888        }
889
890        def templateColumns = { attrs ->
891                // render template fields as columns
892                attrs.renderType = 'column'
893                out << renderTemplateFields(attrs)
894        }
895
896        def templateElements = { attrs ->
897                // render template fields as form elements
898                attrs.renderType = 'element'
899                out << renderTemplateFields(attrs)
900        }
901
902        /**
903         * render form elements based on an entity's template
904         * @param Map attributes
905         * @param String body
906         */
907        def renderTemplateFields = { attrs ->
908                def renderType  = attrs.remove('renderType')
909                def entity              = (attrs.get('entity'))
910                def prependName = (attrs.get('name')) ? attrs.remove('name')+'_' : ''
911                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
912                def inputElement= null
913                def addDummy    = (attrs.get('addDummy')) ? true : false
914
915                // got a template?
916                if (template) {
917                        // render template fields
918                        entity.giveFields().each() {
919                                def fieldValue  = entity.getFieldValue(it.name)
920                                def helpText    = (it.comment && renderType == 'element') ? it.comment : ''
921                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
922
923                                // output column opening element?
924                                if (renderType == 'column') {
925                                        out << '<div class="' + attrs.get('class') + '">'
926                                }
927
928                                switch (it.type.toString()) {
929                                        case ['STRING', 'DOUBLE', 'LONG']:
930                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
931                                                out << "$inputElement"(
932                                                        description     : ucName,
933                                                        name            : prependName + it.escapedName(),
934                                                        value           : fieldValue,
935                                                        required        : it.isRequired()
936                                                ){helpText}
937                                                break
938                                        case 'TEXT':
939                                                inputElement = (renderType == 'element') ? 'textAreaElement' : 'textField'
940                                                out << "$inputElement"(
941                                                        description     : ucName,
942                                                        name            : prependName + it.escapedName(),
943                                                        value           : fieldValue,
944                                                        required        : it.isRequired()
945                                                ){helpText}
946                                                break
947                                        case 'STRINGLIST':
948                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
949                                                if (!it.listEntries.isEmpty()) {
950                                                        out << "$inputElement"(
951                                                                description     : ucName,
952                                                                name            : prependName + it.escapedName(),
953                                                                from            : it.listEntries,
954                                                                value           : fieldValue,
955                                                                required        : it.isRequired()
956                                                        ){helpText}
957                                                } else {
958                                                        out << '<span class="warning">no values!!</span>'
959                                                }
960                                                break
961                                        case 'ONTOLOGYTERM':
962                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
963                                                // @see ontology-chooser.js
964                                                inputElement = (renderType == 'element') ? 'termElement' : 'termSelect'
965
966                                                // override addDummy to always add the dummy...
967                                                addDummy = true
968
969                                                if (it.ontologies) {
970                                                        out << "$inputElement"(
971                                                                description     : ucName,
972                                                                name            : prependName + it.escapedName(),
973                                                                value           : fieldValue.toString(),
974                                                                ontologies      : it.ontologies,
975                                                                addDummy        : addDummy,
976                                                                required        : it.isRequired()
977                                                        ){helpText}
978                                                } else {
979                                                        out << "$inputElement"(
980                                                                description     : ucName,
981                                                                name            : prependName + it.escapedName(),
982                                                                value           : fieldValue.toString(),
983                                                                addDummy        : addDummy,
984                                                                required        : it.isRequired()
985                                                        ){helpText}
986                                                }
987                                                break
988                                        case 'ONTOLOGYTERM-old':
989                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
990                                                // @see ontology-chooser.js
991                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
992                                                out << "$inputElement"(
993                                                        name    : prependName + it.escapedName(),
994                                                        value   : fieldValue,
995                                                        rel             : 'ontology-all',
996                                                        size    : 100,
997                                                        required: it.isRequired()
998                                                )
999                                                out << hiddenField(
1000                                                        name: prependName + it.name + '-concept_id',
1001                                                        value: fieldValue
1002                                                )
1003                                                out << hiddenField(
1004                                                        name: prependName + it.escapedName() + '-ontology_id',
1005                                                        value: fieldValue
1006                                                )
1007                                                out << hiddenField(
1008                                                        name: prependName + it.escapedName() + '-full_id',
1009                                                        value: fieldValue
1010                                                )
1011                                                break
1012                                        case 'DATE':
1013                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
1014
1015                                                // transform value?
1016                                                if (fieldValue instanceof Date) {
1017                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
1018                                                                // transform date instance to formatted string (dd/mm/yyyy)
1019                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
1020                                                        } else {
1021                                                                // transform to date + time
1022                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
1023                                                        }
1024                                                }
1025
1026                                                // render element
1027                                                out << "$inputElement"(
1028                                                        description     : ucName,
1029                                                        name            : prependName + it.escapedName(),
1030                                                        value           : fieldValue,
1031                                                        rel                     : 'date',
1032                                                        required        : it.isRequired()
1033                                                ){helpText}
1034                                                break
1035                                        case ['RELTIME']:
1036                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
1037                                                out << "$inputElement"(
1038                                                        description                     : ucName,
1039                                                        name                            : prependName + it.escapedName(),
1040                                                        value                           : new RelTime( fieldValue ).toString(),
1041                            addExampleElement   : true,
1042                            onBlur                              : 'showExampleReltime(this)',
1043                                                        required                        : it.isRequired()
1044                                                ){helpText}
1045                                                break
1046                                        case ['FILE']:
1047                                                inputElement = (renderType == 'element') ? 'fileFieldElement' : 'fileField'
1048                                                out << "$inputElement"(
1049                                                        description                     : ucName,
1050                                                        name                            : prependName + it.escapedName(),
1051                                                        value                           : fieldValue ? fieldValue : "",
1052                            addExampleElement   : true,
1053                                                        required                        : it.isRequired()
1054                                                ){helpText}
1055                                                break
1056                                        case ['BOOLEAN']:
1057                                                inputElement = (renderType == 'element') ? 'checkBoxElement' : 'checkBox'
1058                                                out << "$inputElement"(
1059                                                        description     : ucName,
1060                                                        name            : prependName + it.escapedName(),
1061                                                        value           : fieldValue,
1062                                                        required        : it.isRequired()
1063                                                ){helpText}
1064                                                break
1065                                        case ['TEMPLATE']:
1066                                                inputElement = (renderType == 'element') ? 'templateElement' : 'templateSelect'
1067                                                out << "$inputElement"(
1068                                                        description     : ucName,
1069                                                        name            : prependName + it.escapedName(),
1070                                                        addDummy        : true,
1071                                                        entity          : it.entity,
1072                                                        value           : fieldValue,
1073                                                        required        : it.isRequired()
1074                                                ){helpText}
1075                                                break
1076                                        case ['MODULE']:
1077                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
1078                                                out << "$inputElement"(
1079                                                        description     : ucName,
1080                                                        name            : prependName + it.escapedName(),
1081                                                        from            : AssayModule.findAll(),
1082                                                        value           : fieldValue,
1083                                                        required        : it.isRequired()
1084                                                ){helpText}
1085                                        break
1086                                                break
1087                                        default:
1088                                                // unsupported field type
1089                                                out << '<span class="warning">!' + it.type + '</span>'
1090                                                break
1091                                }
1092
1093                                // output column closing element?
1094                                if (renderType == 'column') {
1095                                        out << '</div>'
1096                                }
1097                        }
1098                }
1099        }
1100
1101        def PublicationSelectElement = { attrs, body ->
1102                attrs.description = 'Publications';
1103                // render list with publications currently available
1104                baseElement.call(
1105                        '_publicationList',
1106                        attrs,
1107                        body
1108                )
1109
1110                attrs.description = '';
1111
1112                // render 'Add publication button'
1113                baseElement.call(
1114                        '_publicationAddButton',
1115                        attrs,
1116                        body
1117                )
1118        }
1119
1120        /**
1121         * Renders a input box for publications
1122         */
1123        def publicationSelect = { attrs, body ->
1124                if (attrs.get('value') == null) {
1125                        attrs.value = [];
1126                }
1127                if (attrs.get('description') == null) {
1128                        attrs.description = '';
1129                }
1130                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1131                out << textField(
1132                        name: attrs.get("name"),
1133                        value: '',
1134                        rel: 'publication-pubmed',
1135                        style: 'width: 400px;'
1136                );
1137                out << '</form>';
1138                out << '<script type="text/javascript">';
1139                out << '  var onSelect = function( chooserObject, inputElement, event, ui ) { selectPubMedAdd( chooserObject, inputElement, event, ui ); enableButton( ".' + attrs.name + '_publication_dialog", "Add", true ); };'
1140                out << '  iField = $( "#' + attrs.get('name') + '" );';
1141                out << '  new PublicationChooser().initAutocomplete( iField, { "select" : onSelect } );';
1142                out << '</script>';
1143        }
1144
1145        def _publicationList = { attrs, body ->
1146                def display_none = 'none';
1147                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1148                        display_none = 'inline';
1149                }
1150
1151                // Add a unordered list
1152                out << '<ul class="publication_list" id="' + attrs.name + '_list">';
1153
1154                out << '<li>';
1155                out << '<span class="publication_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1156                out << 'No publications selected';
1157                out << '</span>';
1158                out << '</li>';
1159
1160                out << '</ul>';
1161
1162                // Add the publications using javascript
1163                out << '<script type="text/javascript">'
1164                if (attrs.get('value') && attrs.get('value').size() > 0) {
1165                        def i = 0;
1166                        attrs.get('value').each {
1167                                out << 'showPublication( ';
1168                                out << '  "' + attrs.name + '",';
1169                                out << '  ' + it.id + ',';
1170                                out << '  "' + it.title + '",';
1171                                out << '  "' + it.authorsList + '",';
1172                                out << '  ' + i++;
1173                                out << ');';
1174                        }
1175                }
1176                out << '</script>';
1177
1178                def ids;
1179                if (attrs.get('value') && attrs.get('value').size() > 0) {
1180                        ids = attrs.get('value').id.join(',')
1181                } else {
1182                        ids = '';
1183                }
1184                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1185        }
1186
1187        def _publicationAddButton = { attrs, body ->
1188
1189                // Output the dialog for the publications
1190                out << '<div id="' + attrs.name + '_dialog">';
1191                out << '<p>Search for a publication on pubmed. You can search on a part of the title or authors. </p>';
1192                out << publicationSelect(attrs, body);
1193                out << '</div>';
1194                out << '<script type="text/javascript">';
1195                out << '  createPublicationDialog( "' + attrs.name + '" );'
1196                out << '</script>';
1197
1198                out << '<input type="button" onClick="openPublicationDialog(\'' + attrs.name + '\' );" value="Add Publication">';
1199        }
1200
1201        def ContactSelectElement = { attrs, body ->
1202
1203                attrs.description = 'Contacts';
1204                // render list with publications currently available
1205                baseElement.call(
1206                        '_contactList',
1207                        attrs,
1208                        body
1209                )
1210
1211                attrs.description = '';
1212
1213                // render 'publications list'
1214                out << '<div id="' + attrs.name + '_dialog" class="contacts_dialog" style="display: none;">'
1215                baseElement.call(
1216                        '_personSelect',
1217                        attrs,
1218                        body
1219                )
1220                baseElement.call(
1221                        '_roleSelect',
1222                        attrs,
1223                        body
1224                )
1225                baseElement.call(
1226                        '_contactAddButtonAddition',
1227                        attrs,
1228                        body
1229                )
1230                out << '</div>';
1231
1232                // render 'Add contact button'
1233                baseElement.call(
1234                        '_contactAddDialogButton',
1235                        attrs,
1236                        body
1237                )
1238        }
1239
1240        def _contactList = { attrs, body ->
1241                def display_none = 'none';
1242                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1243                        display_none = 'inline';
1244                }
1245
1246                // Add a unordered list
1247                out << '<ul class="contact_list" id="' + attrs.name + '_list">';
1248
1249                out << '<li>';
1250                out << '<span class="contacts_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1251                out << 'No contacts selected';
1252                out << '</span>';
1253                out << '</li>';
1254
1255                out << '</ul>';
1256
1257                // Add the contacts using javascript
1258                out << '<script type="text/javascript">'
1259                if (attrs.get('value') && attrs.get('value').size() > 0) {
1260                        def i = 0;
1261                        attrs.get('value').each {
1262                                out << 'showContact( ';
1263                                out << '  "' + attrs.name + '",';
1264                                out << '  "' + it.person.id + '-' + it.role.id + '",';
1265                                out << '  "' + it.person.lastName + ', ' + it.person.firstName + (it.person.prefix ? ' ' + it.person.prefix : '') + '",';
1266                                out << '  "' + it.role.name + '",';
1267                                out << '  ' + i++;
1268                                out << ');';
1269                        }
1270                }
1271                out << '</script>';
1272
1273                def ids = '';
1274                if (attrs.get('value') && attrs.get('value').size() > 0) {
1275                        ids = attrs.get('value').collect { it.person.id + '-' + it.role.id }
1276                        ids = ids.join(',');
1277                }
1278                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1279        }
1280
1281        def _contactAddSelect = { attrs, body ->
1282                out << _personSelect(attrs) + _roleSelect(attrs);
1283        }
1284
1285        def _contactAddButtonAddition = { attrs, body ->
1286                out << '<input type="button" onClick="if( addContact ( \'' + attrs.name + '\' ) ) { $(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show(); }" value="Add">';
1287                out << '<input type="button" onClick="$(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Close">';
1288        }
1289
1290        def _contactAddDialogButton = { attrs, body ->
1291                out << '<input type="button" onClick="$( \'#' + attrs.name + '_dialog\' ).show(); $(this).hide();" id="' + attrs.name + '_dialogButton" value="Add Contact">';
1292        }
1293        /**
1294         * Person select element
1295         * @param Map attributes
1296         */
1297        def _personSelect = { attrs ->
1298                def selectAttrs = new LinkedHashMap();
1299
1300                // define 'from'
1301                def persons = Person.findAll().sort({ a, b -> a.lastName == b.lastName ? (a.firstName <=> b.firstName) : (a.lastName <=> b.lastName) } as Comparator);
1302                selectAttrs.from = persons.collect { it.lastName + ', ' + it.firstName + (it.prefix ? ' ' + it.prefix : '') }
1303                selectAttrs.keys = persons.id;
1304
1305                // add 'rel' attribute
1306                selectAttrs.rel = 'person'
1307                selectAttrs.name = attrs.name + '_person';
1308
1309                // add a dummy field
1310                selectAttrs.from.add(0,'')
1311                selectAttrs.keys.add(0,'')
1312
1313                out << "Person: " + select(selectAttrs)
1314        }
1315
1316        /**
1317         * Role select element
1318         * @param Map attributes
1319         */
1320        def _roleSelect = { attrs ->
1321                def selectAttrs = new LinkedHashMap();
1322
1323                // define 'from'
1324                def roles = PersonRole.findAll();
1325                selectAttrs.from = roles.collect { it.name };
1326                selectAttrs.keys = roles.id;
1327
1328                // add 'rel' attribute
1329                selectAttrs.rel = 'role'
1330                selectAttrs.name = attrs.name + '_role';
1331
1332                // add a dummy field
1333                selectAttrs.from.add(0,'')
1334                selectAttrs.keys.add(0,'')
1335
1336                out << "Role: " + select(selectAttrs)
1337        }
1338
1339
1340        def UserSelectElement = { attrs, body ->
1341                // render list with publications currently available
1342                baseElement.call(
1343                        '_userList',
1344                        attrs,
1345                        body
1346                )
1347
1348                attrs.description = '';
1349
1350                // render 'Add user button'
1351                baseElement.call(
1352                        '_userAddButton',
1353                        attrs,
1354                        body
1355                )
1356        }
1357
1358        /**
1359         * Renders an input box for publications
1360         */
1361        def userSelect = { attrs, body ->
1362                if (attrs.get('value') == null) {
1363                        attrs.value = [];
1364                }
1365                if (attrs.get('description') == null) {
1366                        attrs.description = '';
1367                }
1368               
1369                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1370                out << select(
1371                        name: attrs.get("name"),
1372                        value: '',
1373                        from: SecUser.list(),
1374                        optionValue: 'username',
1375                        optionKey: 'id',
1376                        style: 'width: 400px;'
1377                );
1378                out << '</form>';
1379        }
1380
1381        def _userList = { attrs, body ->
1382                def display_none = 'none';
1383                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1384                        display_none = 'inline';
1385                }
1386
1387                // Add a unordered list
1388                out << '<ul class="user_list" id="' + attrs.name + '_list">';
1389
1390                out << '<li>';
1391                out << '<span class="user_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1392                out << '-';
1393                out << '</span>';
1394                out << '</li>';
1395
1396                out << '</ul>';
1397
1398                // Add the publications using javascript
1399                out << '<script type="text/javascript">'
1400                if (attrs.get('value') && attrs.get('value').size() > 0) {
1401                        def i = 0;
1402                        attrs.get('value').each {
1403                                out << 'showUser( ';
1404                                out << '  "' + attrs.name + '",';
1405                                out << '  ' + it.id + ',';
1406                                out << '  "' + it.username + '",';
1407                                out << '  ' + i++;
1408                                out << ');';
1409                        }
1410                }
1411                out << '</script>';
1412
1413                def ids;
1414                if (attrs.get('value') && attrs.get('value').size() > 0) {
1415                        ids = attrs.get('value').id.join(',')
1416                } else {
1417                        ids = '';
1418                }
1419                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1420        }
1421
1422        def _userAddButton = { attrs, body ->
1423
1424                // Output the dialog for the publications
1425                out << '<div id="' + attrs.name + '_dialog">';
1426                out << '<p>Select a user from the database.</p>';
1427                out << userSelect(attrs, body);
1428                out << '</div>';
1429                out << '<script type="text/javascript">';
1430                out << '  createUserDialog( "' + attrs.name + '" );'
1431                out << '</script>';
1432
1433                out << '<input type="button" onClick="openUserDialog(\'' + attrs.name + '\' );" value="Add User">';
1434        }
1435
1436        def showTemplateField = { attrs, body ->
1437                def field = attrs.get( 'field' );
1438                def entity = attrs.get( 'entity' );
1439                def fieldName = '';
1440                def fieldType = '';
1441               
1442                if( entity ) {
1443                        if( field instanceof String ) {
1444                                fieldName = field;
1445                                fieldType = '';
1446                        } else if( field instanceof TemplateField ) {
1447                                fieldName = field.name
1448                                fieldType = field.type.toString();
1449                        } else {
1450                                return;
1451                        }
1452
1453                        def value = entity.getFieldValue( fieldName );
1454
1455                        if( fieldType == 'FILE' && value != "" ) {
1456                          out << '<a href="' + g.createLink( controller: "file", action: "get",  id: value ) + '">' + value + '</a>';
1457                        } else {
1458                                out << value;
1459                        }
1460
1461                }
1462        }
1463
1464}
Note: See TracBrowser for help on using the browser.