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

Last change on this file since 1224 was 1224, checked in by robert@…, 10 years ago

Fixed issue in study overview with double columns. See ticket #155

  • Property svn:keywords set to Author Date Rev
File size: 39.8 KB
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: 1224 $
18 * $Author: robert@isdat.nl $
19 * $Date: 2010-11-30 16:12:08 +0000 (di, 30 nov 2010) $
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 (max 100)
656                attrs.from = Study.giveWritableStudies(AuthenticationService.getLoggedInUser(), 100);
657
658                // got a name?
659                if (!attrs.name) {
660                        attrs.name = "study"
661                }
662
663                // got result?
664                if (attrs.from.size() > 0) {
665                        out << select(attrs)
666                } else {
667                        // no, return false to make sure this element
668                        // is not rendered in the template
669                        return false
670                }
671        }
672
673        /**
674         * Template form element
675         * @param Map attributes
676         * @param Closure help content
677         */
678        def templateElement = { attrs, body ->
679                // render template element
680                baseElement.call(
681                        'templateSelect',
682                        attrs,
683                        body
684                )
685        }
686
687        /**
688         * render a template select element
689         * @param Map attrs
690         */
691        def templateSelect = { attrs ->
692                def entity = attrs.remove('entity')
693
694                // add the entity class name to the element
695                // do we have crypto information available?
696                if (grailsApplication.config.crypto) {
697                        // generate a Blowfish encrypted and Base64 encoded string.
698                        attrs['entity'] = URLEncoder.encode(
699                                Blowfish.encryptBase64(
700                                        entity.toString().replaceAll(/^class /, ''),
701                                        grailsApplication.config.crypto.shared.secret
702                                )
703                        )
704                } else {
705                        // base64 only; this is INSECURE! As this class
706                        // is instantiated elsewehere. Possibly exploitable!
707                        attrs['entity'] = URLEncoder.encode(entity.toString().replaceAll(/^class /, '').bytes.encodeBase64())
708                }
709               
710                // fetch templates
711                attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
712
713                // got a name?
714                if (!attrs.name) {
715                        attrs.name = 'template'
716                }
717
718                // add a rel element if it does not exist
719                if (!attrs.rel) {
720                        attrs.rel = 'template'
721                }
722
723                // got an ajaxOnChange defined?
724                attrs = getAjaxOnChange.call(
725                        attrs
726                )
727
728                // got result?
729                if (attrs.from.size() > 0 || attrs.get('addDummy')) {
730                        // transform all values into strings
731                        def from = []
732                        attrs.from.each { from[ from.size() ] = it.toString() }
733
734                        // sort alphabetically
735                        from.sort()
736
737                        // add a dummy field?
738                        if (attrs.remove('addDummy')) {
739                                from.add(0,'')
740                        }
741
742                        // set attributes
743                        attrs.from = from
744                        attrs.value = (attrs.value) ? attrs.value.toString() : ''
745
746                        // output select element
747                        out << select(attrs)
748                } else {
749                        // no, return false to make sure this element
750                        // is not rendered in the template
751                        return false
752                }
753        }
754
755
756        /**
757         * File form element
758         * @param Map attributes
759         * @param Closure help content
760         */
761        def fileFieldElement = { attrs, body ->
762                // render term element
763                baseElement.call(
764                        'fileField',
765                        attrs,
766                        body
767                )
768        }
769
770        /**
771         * file field.
772         * @param attributes
773         */
774        def fileField = { attrs ->
775                /*
776                out << '<input type="file" name="' + attrs.name + '"/>'
777                if( attrs.value ) {
778                        out << '<a href="' + resource(dir: '') + '/file/get/' + attrs.value + '" class="isExample">Now contains: ' + attrs.value + '</a>'
779                }
780                */
781
782                out << '<div id="upload_button_' + attrs.name + '" class="upload_button">Upload</div>';
783                out << '<input type="hidden" name="' + attrs.name + '" id="' + attrs.name + '" value="' + attrs.value + '">';
784                out << '<div id="' + attrs.name + 'Example" class="upload_info"></div>';
785                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>';
786                out << '<script type="text/javascript">';
787                out << '  $(document).ready( function() { ';
788                out << '    var filename = "' + attrs.value + '";';
789                out << '    fileUploadField( "' + attrs.name + '" );';
790                out << '    if( filename != "" ) {';
791                out << '      $("#' + attrs.name + 'Delete").show();';
792                out << '      $("#' + attrs.name + 'Example").html("Current file: " + createFileHTML( filename ) )';
793                out << '    }';
794                out << '  } );';
795                out << "</script>\n";
796        }
797
798        /**
799         * Protocol form element
800         * @param Map attributes
801         * @param Closure help content
802         */
803        def protocolElement = { attrs, body ->
804                // render protocol element
805                baseElement.call(
806                        'protocolSelect',
807                        attrs,
808                        body
809                )
810        }
811
812        /**
813         * render a protocol select element
814         * @param Map attrs
815         */
816        def protocolSelect = { attrs ->
817                // fetch all protocold
818                attrs.from = Protocol.findAll() // for now, all protocols
819
820                // got a name?
821                if (!attrs.name) {
822                        attrs.name = 'protocol'
823                }
824
825                out << select(attrs)
826        }
827
828        def show = { attrs ->
829                // is object parameter set?
830                def o = attrs.object
831
832                println o.getProperties();
833                o.getProperties().each {
834                        println it
835                }
836
837                out << "!! test version of 'show' tag !!"
838        }
839
840        /**
841         * render table headers for all subjectFields in a template
842         * @param Map attributes
843         */
844        def templateColumnHeaders = { attrs ->
845                def entity              = (attrs.get('entity'))
846                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
847                def columnWidths= (attrs.get('columnWidths')) ? attrs.remove('columnWidths') : []
848
849                // got a template?
850                if (template) {
851                        // render template fields
852                        entity.giveFields().each() {
853                                // Format the column name by:
854                                // - separating combined names (SampleName --> Sample Name)
855                                // - capitalizing every seperate word
856                                def ucName = it.name.replaceAll(/[a-z][A-Z][a-z]/) {
857                                        it[0] + ' ' + it[1..2]
858                                }.replaceAll(/\w+/) {
859                                        it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '')
860                                }
861
862                                // strip spaces
863                                def ucNameSpaceless = ucName.replaceAll(/ /) { '' }
864
865                                // do we have to use a specific width for this column?
866                                if (columnWidths[ucName]) {
867                                        out << '<div class="' + attrs.get('class') + '" style="width:' + columnWidths[ucNameSpaceless] + 'px;" rel="resized">' + ucName + (it.unit ? " (${it.unit})" : '')
868                                } else {
869                                        out << '<div class="' + attrs.get('class') + '">' + ucName + (it.unit ? " (${it.unit})" : '')
870                                }
871                                if (it.comment) {
872                                        out << '<div class="helpIcon"></div>'
873                                        out << '<div class="helpContent">' + it.comment + '</div>'
874                                }
875                                out << '</div>'
876                        }
877                }
878        }
879
880        def templateColumns = { attrs ->
881                // render template fields as columns
882                attrs.renderType = 'column'
883                out << renderTemplateFields(attrs)
884        }
885
886        def templateElements = { attrs ->
887                // render template fields as form elements
888                attrs.renderType = 'element'
889                out << renderTemplateFields(attrs)
890        }
891
892        /**
893         * render form elements based on an entity's template
894         * @param Map attributes
895         * @param String body
896         */
897        def renderTemplateFields = { attrs ->
898                def renderType  = attrs.remove('renderType')
899                def entity              = (attrs.get('entity'))
900                def prependName = (attrs.get('name')) ? attrs.remove('name')+'_' : ''
901                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
902                def inputElement= null
903                def addDummy    = (attrs.get('addDummy')) ? true : false
904
905                // got a template?
906                if (template) {
907                        // render template fields
908                        entity.giveFields().each() {
909                                def fieldValue  = entity.getFieldValue(it.name)
910                                def helpText    = (it.comment && renderType == 'element') ? it.comment : ''
911                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
912
913                                // output column opening element?
914                                if (renderType == 'column') {
915                                        out << '<div class="' + attrs.get('class') + '">'
916                                }
917
918                                switch (it.type.toString()) {
919                                        case ['STRING', 'DOUBLE', 'LONG']:
920                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
921                                                out << "$inputElement"(
922                                                        description     : ucName,
923                                                        name            : prependName + it.escapedName(),
924                                                        value           : fieldValue,
925                                                        required        : it.isRequired()
926                                                ){helpText}
927                                                break
928                                        case 'TEXT':
929                                                inputElement = (renderType == 'element') ? 'textAreaElement' : 'textField'
930                                                out << "$inputElement"(
931                                                        description     : ucName,
932                                                        name            : prependName + it.escapedName(),
933                                                        value           : fieldValue,
934                                                        required        : it.isRequired()
935                                                ){helpText}
936                                                break
937                                        case 'STRINGLIST':
938                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
939                                                if (!it.listEntries.isEmpty()) {
940                                                        out << "$inputElement"(
941                                                                description     : ucName,
942                                                                name            : prependName + it.escapedName(),
943                                                                from            : it.listEntries,
944                                                                value           : fieldValue,
945                                                                required        : it.isRequired()
946                                                        ){helpText}
947                                                } else {
948                                                        out << '<span class="warning">no values!!</span>'
949                                                }
950                                                break
951                                        case 'ONTOLOGYTERM':
952                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
953                                                // @see ontology-chooser.js
954                                                inputElement = (renderType == 'element') ? 'termElement' : 'termSelect'
955
956                                                // override addDummy to always add the dummy...
957                                                addDummy = true
958
959                                                if (it.ontologies) {
960                                                        out << "$inputElement"(
961                                                                description     : ucName,
962                                                                name            : prependName + it.escapedName(),
963                                                                value           : fieldValue.toString(),
964                                                                ontologies      : it.ontologies,
965                                                                addDummy        : addDummy,
966                                                                required        : it.isRequired()
967                                                        ){helpText}
968                                                } else {
969                                                        out << "$inputElement"(
970                                                                description     : ucName,
971                                                                name            : prependName + it.escapedName(),
972                                                                value           : fieldValue.toString(),
973                                                                addDummy        : addDummy,
974                                                                required        : it.isRequired()
975                                                        ){helpText}
976                                                }
977                                                break
978                                        case 'ONTOLOGYTERM-old':
979                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
980                                                // @see ontology-chooser.js
981                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
982                                                out << "$inputElement"(
983                                                        name    : prependName + it.escapedName(),
984                                                        value   : fieldValue,
985                                                        rel             : 'ontology-all',
986                                                        size    : 100,
987                                                        required: it.isRequired()
988                                                )
989                                                out << hiddenField(
990                                                        name: prependName + it.name + '-concept_id',
991                                                        value: fieldValue
992                                                )
993                                                out << hiddenField(
994                                                        name: prependName + it.escapedName() + '-ontology_id',
995                                                        value: fieldValue
996                                                )
997                                                out << hiddenField(
998                                                        name: prependName + it.escapedName() + '-full_id',
999                                                        value: fieldValue
1000                                                )
1001                                                break
1002                                        case 'DATE':
1003                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
1004
1005                                                // transform value?
1006                                                if (fieldValue instanceof Date) {
1007                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
1008                                                                // transform date instance to formatted string (dd/mm/yyyy)
1009                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
1010                                                        } else {
1011                                                                // transform to date + time
1012                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
1013                                                        }
1014                                                }
1015
1016                                                // render element
1017                                                out << "$inputElement"(
1018                                                        description     : ucName,
1019                                                        name            : prependName + it.escapedName(),
1020                                                        value           : fieldValue,
1021                                                        rel                     : 'date',
1022                                                        required        : it.isRequired()
1023                                                ){helpText}
1024                                                break
1025                                        case ['RELTIME']:
1026                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
1027                                                out << "$inputElement"(
1028                                                        description                     : ucName,
1029                                                        name                            : prependName + it.escapedName(),
1030                                                        value                           : new RelTime( fieldValue ).toString(),
1031                            addExampleElement   : true,
1032                            onBlur                              : 'showExampleReltime(this)',
1033                                                        required                        : it.isRequired()
1034                                                ){helpText}
1035                                                break
1036                                        case ['FILE']:
1037                                                inputElement = (renderType == 'element') ? 'fileFieldElement' : 'fileField'
1038                                                out << "$inputElement"(
1039                                                        description                     : ucName,
1040                                                        name                            : prependName + it.escapedName(),
1041                                                        value                           : fieldValue ? fieldValue : "",
1042                            addExampleElement   : true,
1043                                                        required                        : it.isRequired()
1044                                                ){helpText}
1045                                                break
1046                                        case ['BOOLEAN']:
1047                                                inputElement = (renderType == 'element') ? 'checkBoxElement' : 'checkBox'
1048                                                out << "$inputElement"(
1049                                                        description     : ucName,
1050                                                        name            : prependName + it.escapedName(),
1051                                                        value           : fieldValue,
1052                                                        required        : it.isRequired()
1053                                                ){helpText}
1054                                                break
1055                                        case ['TEMPLATE']:
1056                                                inputElement = (renderType == 'element') ? 'templateElement' : 'templateSelect'
1057                                                out << "$inputElement"(
1058                                                        description     : ucName,
1059                                                        name            : prependName + it.escapedName(),
1060                                                        addDummy        : true,
1061                                                        entity          : it.entity,
1062                                                        value           : fieldValue,
1063                                                        required        : it.isRequired()
1064                                                ){helpText}
1065                                                break
1066                                        case ['MODULE']:
1067                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
1068                                                out << "$inputElement"(
1069                                                        description     : ucName,
1070                                                        name            : prependName + it.escapedName(),
1071                                                        from            : AssayModule.findAll(),
1072                                                        value           : fieldValue,
1073                                                        required        : it.isRequired()
1074                                                ){helpText}
1075                                        break
1076                                                break
1077                                        default:
1078                                                // unsupported field type
1079                                                out << '<span class="warning">!' + it.type + '</span>'
1080                                                break
1081                                }
1082
1083                                // output column closing element?
1084                                if (renderType == 'column') {
1085                                        out << '</div>'
1086                                }
1087                        }
1088                }
1089        }
1090
1091        def PublicationSelectElement = { attrs, body ->
1092                attrs.description = 'Publications';
1093                // render list with publications currently available
1094                baseElement.call(
1095                        '_publicationList',
1096                        attrs,
1097                        body
1098                )
1099
1100                attrs.description = '';
1101
1102                // render 'Add publication button'
1103                baseElement.call(
1104                        '_publicationAddButton',
1105                        attrs,
1106                        body
1107                )
1108        }
1109
1110        /**
1111         * Renders a input box for publications
1112         */
1113        def publicationSelect = { attrs, body ->
1114                if (attrs.get('value') == null) {
1115                        attrs.value = [];
1116                }
1117                if (attrs.get('description') == null) {
1118                        attrs.description = '';
1119                }
1120                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1121                out << textField(
1122                        name: attrs.get("name"),
1123                        value: '',
1124                        rel: 'publication-pubmed',
1125                        style: 'width: 400px;'
1126                );
1127                out << '</form>';
1128                out << '<script type="text/javascript">';
1129                out << '  var onSelect = function( chooserObject, inputElement, event, ui ) { selectPubMedAdd( chooserObject, inputElement, event, ui ); enableButton( ".' + attrs.name + '_publication_dialog", "Add", true ); };'
1130                out << '  iField = $( "#' + attrs.get('name') + '" );';
1131                out << '  new PublicationChooser().initAutocomplete( iField, { "select" : onSelect } );';
1132                out << '</script>';
1133        }
1134
1135        def _publicationList = { attrs, body ->
1136                def display_none = 'none';
1137                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1138                        display_none = 'inline';
1139                }
1140
1141                // Add a unordered list
1142                out << '<ul class="publication_list" id="' + attrs.name + '_list">';
1143
1144                out << '<li>';
1145                out << '<span class="publication_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1146                out << 'No publications selected';
1147                out << '</span>';
1148                out << '</li>';
1149
1150                out << '</ul>';
1151
1152                // Add the publications using javascript
1153                out << '<script type="text/javascript">'
1154                if (attrs.get('value') && attrs.get('value').size() > 0) {
1155                        def i = 0;
1156                        attrs.get('value').each {
1157                                out << 'showPublication( ';
1158                                out << '  "' + attrs.name + '",';
1159                                out << '  ' + it.id + ',';
1160                                out << '  "' + it.title + '",';
1161                                out << '  "' + it.authorsList + '",';
1162                                out << '  ' + i++;
1163                                out << ');';
1164                        }
1165                }
1166                out << '</script>';
1167
1168                def ids;
1169                if (attrs.get('value') && attrs.get('value').size() > 0) {
1170                        ids = attrs.get('value').id.join(',')
1171                } else {
1172                        ids = '';
1173                }
1174                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1175        }
1176
1177        def _publicationAddButton = { attrs, body ->
1178
1179                // Output the dialog for the publications
1180                out << '<div id="' + attrs.name + '_dialog">';
1181                out << '<p>Search for a publication on pubmed. You can search on a part of the title or authors. </p>';
1182                out << publicationSelect(attrs, body);
1183                out << '</div>';
1184                out << '<script type="text/javascript">';
1185                out << '  createPublicationDialog( "' + attrs.name + '" );'
1186                out << '</script>';
1187
1188                out << '<input type="button" onClick="openPublicationDialog(\'' + attrs.name + '\' );" value="Add Publication">';
1189        }
1190
1191        def ContactSelectElement = { attrs, body ->
1192
1193                attrs.description = 'Contacts';
1194                // render list with publications currently available
1195                baseElement.call(
1196                        '_contactList',
1197                        attrs,
1198                        body
1199                )
1200
1201                attrs.description = '';
1202
1203                // render 'publications list'
1204                out << '<div id="' + attrs.name + '_dialog" class="contacts_dialog" style="display: none;">'
1205                baseElement.call(
1206                        '_personSelect',
1207                        attrs,
1208                        body
1209                )
1210                baseElement.call(
1211                        '_roleSelect',
1212                        attrs,
1213                        body
1214                )
1215                baseElement.call(
1216                        '_contactAddButtonAddition',
1217                        attrs,
1218                        body
1219                )
1220                out << '</div>';
1221
1222                // render 'Add contact button'
1223                baseElement.call(
1224                        '_contactAddDialogButton',
1225                        attrs,
1226                        body
1227                )
1228        }
1229
1230        def _contactList = { attrs, body ->
1231                def display_none = 'none';
1232                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1233                        display_none = 'inline';
1234                }
1235
1236                // Add a unordered list
1237                out << '<ul class="contact_list" id="' + attrs.name + '_list">';
1238
1239                out << '<li>';
1240                out << '<span class="contacts_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1241                out << 'No contacts selected';
1242                out << '</span>';
1243                out << '</li>';
1244
1245                out << '</ul>';
1246
1247                // Add the contacts using javascript
1248                out << '<script type="text/javascript">'
1249                if (attrs.get('value') && attrs.get('value').size() > 0) {
1250                        def i = 0;
1251                        attrs.get('value').each {
1252                                out << 'showContact( ';
1253                                out << '  "' + attrs.name + '",';
1254                                out << '  "' + it.person.id + '-' + it.role.id + '",';
1255                                out << '  "' + it.person.lastName + ', ' + it.person.firstName + (it.person.prefix ? ' ' + it.person.prefix : '') + '",';
1256                                out << '  "' + it.role.name + '",';
1257                                out << '  ' + i++;
1258                                out << ');';
1259                        }
1260                }
1261                out << '</script>';
1262
1263                def ids = '';
1264                if (attrs.get('value') && attrs.get('value').size() > 0) {
1265                        ids = attrs.get('value').collect { it.person.id + '-' + it.role.id }
1266                        ids = ids.join(',');
1267                }
1268                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1269        }
1270
1271        def _contactAddSelect = { attrs, body ->
1272                out << _personSelect(attrs) + _roleSelect(attrs);
1273        }
1274
1275        def _contactAddButtonAddition = { attrs, body ->
1276                out << '<input type="button" onClick="if( addContact ( \'' + attrs.name + '\' ) ) { $(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show(); }" value="Add">';
1277                out << '<input type="button" onClick="$(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Close">';
1278        }
1279
1280        def _contactAddDialogButton = { attrs, body ->
1281                out << '<input type="button" onClick="$( \'#' + attrs.name + '_dialog\' ).show(); $(this).hide();" id="' + attrs.name + '_dialogButton" value="Add Contact">';
1282        }
1283        /**
1284         * Person select element
1285         * @param Map attributes
1286         */
1287        def _personSelect = { attrs ->
1288                def selectAttrs = new LinkedHashMap();
1289
1290                // define 'from'
1291                def persons = Person.findAll().sort({ a, b -> a.lastName == b.lastName ? (a.firstName <=> b.firstName) : (a.lastName <=> b.lastName) } as Comparator);
1292                selectAttrs.from = persons.collect { it.lastName + ', ' + it.firstName + (it.prefix ? ' ' + it.prefix : '') }
1293                selectAttrs.keys = persons.id;
1294
1295                // add 'rel' attribute
1296                selectAttrs.rel = 'person'
1297                selectAttrs.name = attrs.name + '_person';
1298
1299                // add a dummy field
1300                selectAttrs.from.add(0,'')
1301                selectAttrs.keys.add(0,'')
1302
1303                out << "Person: " + select(selectAttrs)
1304        }
1305
1306        /**
1307         * Role select element
1308         * @param Map attributes
1309         */
1310        def _roleSelect = { attrs ->
1311                def selectAttrs = new LinkedHashMap();
1312
1313                // define 'from'
1314                def roles = PersonRole.findAll();
1315                selectAttrs.from = roles.collect { it.name };
1316                selectAttrs.keys = roles.id;
1317
1318                // add 'rel' attribute
1319                selectAttrs.rel = 'role'
1320                selectAttrs.name = attrs.name + '_role';
1321
1322                // add a dummy field
1323                selectAttrs.from.add(0,'')
1324                selectAttrs.keys.add(0,'')
1325
1326                out << "Role: " + select(selectAttrs)
1327        }
1328
1329
1330        def UserSelectElement = { attrs, body ->
1331                // render list with publications currently available
1332                baseElement.call(
1333                        '_userList',
1334                        attrs,
1335                        body
1336                )
1337
1338                attrs.description = '';
1339
1340                // render 'Add user button'
1341                baseElement.call(
1342                        '_userAddButton',
1343                        attrs,
1344                        body
1345                )
1346        }
1347
1348        /**
1349         * Renders an input box for publications
1350         */
1351        def userSelect = { attrs, body ->
1352                if (attrs.get('value') == null) {
1353                        attrs.value = [];
1354                }
1355                if (attrs.get('description') == null) {
1356                        attrs.description = '';
1357                }
1358               
1359                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1360                out << select(
1361                        name: attrs.get("name"),
1362                        value: '',
1363                        from: SecUser.list(),
1364                        optionValue: 'username',
1365                        optionKey: 'id',
1366                        style: 'width: 400px;'
1367                );
1368                out << '</form>';
1369        }
1370
1371        def _userList = { attrs, body ->
1372                def display_none = 'none';
1373                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1374                        display_none = 'inline';
1375                }
1376
1377                // Add a unordered list
1378                out << '<ul class="user_list" id="' + attrs.name + '_list">';
1379
1380                out << '<li>';
1381                out << '<span class="user_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1382                out << '-';
1383                out << '</span>';
1384                out << '</li>';
1385
1386                out << '</ul>';
1387
1388                // Add the publications using javascript
1389                out << '<script type="text/javascript">'
1390                if (attrs.get('value') && attrs.get('value').size() > 0) {
1391                        def i = 0;
1392                        attrs.get('value').each {
1393                                out << 'showUser( ';
1394                                out << '  "' + attrs.name + '",';
1395                                out << '  ' + it.id + ',';
1396                                out << '  "' + it.username + '",';
1397                                out << '  ' + i++;
1398                                out << ');';
1399                        }
1400                }
1401                out << '</script>';
1402
1403                def ids;
1404                if (attrs.get('value') && attrs.get('value').size() > 0) {
1405                        ids = attrs.get('value').id.join(',')
1406                } else {
1407                        ids = '';
1408                }
1409                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1410        }
1411
1412        def _userAddButton = { attrs, body ->
1413
1414                // Output the dialog for the publications
1415                out << '<div id="' + attrs.name + '_dialog">';
1416                out << '<p>Select a user from the database.</p>';
1417                out << userSelect(attrs, body);
1418                out << '</div>';
1419                out << '<script type="text/javascript">';
1420                out << '  createUserDialog( "' + attrs.name + '" );'
1421                out << '</script>';
1422
1423                out << '<input type="button" onClick="openUserDialog(\'' + attrs.name + '\' );" value="Add User">';
1424        }
1425
1426        def showTemplateField = { attrs, body ->
1427                def field = attrs.get( 'field' );
1428                def entity = attrs.get( 'entity' );
1429                def fieldName = '';
1430                def fieldType = '';
1431                def fieldUnit = '';
1432               
1433                if( entity ) {
1434                        if( field instanceof String ) {
1435                                fieldName = field;
1436                                fieldType = '';
1437                                fieldUnit = '';
1438                        } else if( field instanceof TemplateField ) {
1439                                fieldName = field.name
1440                                fieldType = field.type.toString();
1441                                fieldUnit = field.unit
1442                        } else {
1443                                return;
1444                        }
1445
1446                        def value = entity.getFieldValue( fieldName );
1447
1448                        // Show a link if the field is a FILE field
1449                        if( fieldType == 'FILE' && value != "" ) {
1450                            out << '<a href="' + g.createLink( controller: "file", action: "get",  id: value ) + '">' + value + '</a>';
1451                        } else {
1452                                out << value;
1453                        }
1454
1455                        // Show the unit (if a unit is present and a value was shown)
1456                        if( fieldUnit && value != null && value != "" )
1457                                out << " " + fieldUnit
1458
1459                }
1460        }
1461
1462}
Note: See TracBrowser for help on using the repository browser.