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

Revision 976, 38.4 KB (checked in by robert@…, 3 years ago)

Authentication and authorization for studies is added, according to ticket 118

  • 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
23        def AuthenticationService
24       
25        // define the tag namespace (e.g.: <wizard:action ... />
26        static namespace = "wizard"
27
28        // define the AJAX provider to use
29        static ajaxProvider = "jquery"
30
31        // define default text field width
32        static defaultTextFieldSize = 25;
33
34        /**
35         * ajaxButton tag, this is a modified version of the default
36         * grails submitToRemote tag to work with grails webflows.
37         * Usage is identical to submitToRemote with the only exception
38         * that a 'name' form element attribute is required. E.g.
39         * <wizard:ajaxButton name="myAction" value="myButton ... />
40         *
41         * you can also provide a javascript function to execute after
42         * success. This behaviour differs from the default 'after'
43         * action which always fires after a button press...
44         *
45         * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
46         * @see http://www.grails.org/WebFlow
47         * @see http://www.grails.org/Tag+-+submitToRemote
48         * @todo perhaps some methods should be moved to a more generic
49         *        'webflow' taglib or plugin
50         * @param Map attributes
51         * @param Closure body
52         */
53        def ajaxButton = { attrs, body ->
54                // get the jQuery version
55                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
56
57                // fetch the element name from the attributes
58                def elementName = attrs['name'].replaceAll(/ /, "_")
59
60                // javascript function to call after success
61                def afterSuccess = attrs['afterSuccess']
62
63                // src parameter?
64                def src = attrs['src']
65                def alt = attrs['alt']
66
67                // generate a normal submitToRemote button
68                def button = submitToRemote(attrs, body)
69
70                /**
71                 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
72                 * not properly work with AJAX as the submitToRemote button does not
73                 * handle and submit the form properly. In order to support webflows
74                 * this method modifies two parts of a 'normal' submitToRemote button:
75                 *
76                 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
77                 *    action refers to the button and / or the action upon that button.
78                 *    However, it should point to the form the button is part of as the
79                 *    the button should submit the form data.
80                 * 2) prepend the button name to the serialized data. The default behaviour
81                 *    of submitToRemote is to remove the element name altogether, while
82                 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
83                 *    the appropriate webflow action. Hence, we are going to prepend the
84                 *    serialized formdata with an _eventId_BUTTONNAME parameter.
85                 */
86                if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
87                        // fix for older jQuery plugin versions
88                        button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
89                } else {
90                        // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
91                        // this.form part has been fixed. Consequently, our wrapper has changed as well...
92                        button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
93                }
94
95                // add an after success function call?
96                // usefull for performing actions on success data (hence on refreshed
97                // wizard pages, such as attaching tooltips)
98                if (afterSuccess) {
99                        button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
100                }
101
102                // got an src parameter?
103                if (src) {
104                        def replace = 'type="image" src="' + src + '"'
105
106                        if (alt) replace = replace + ' alt="' + alt + '"'
107
108                        button = button.replaceFirst(/type="button"/, replace)
109                }
110
111                // replace double semi colons
112                button = button.replaceAll(/;{2,}/, ';')
113
114                // render button
115                out << button
116        }
117
118        /**
119         * generate a ajax submit JavaScript
120         * @see WizardTagLib::ajaxFlowRedirect
121         * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
122         */
123        def ajaxSubmitJs = { attrs, body ->
124                // define AJAX provider
125                setProvider([library: ajaxProvider])
126
127                // got a function name?
128                def functionName = attrs.remove('functionName')
129                if (functionName && !attrs.get('name')) {
130                        attrs.name = functionName
131                }
132
133                // generate an ajax button
134                def button = this.ajaxButton(attrs, body)
135
136                // strip the button part to only leave the Ajax call
137                button = button.replaceFirst(/<[^\"]*onclick=\"/, '')
138                button = button.replaceFirst(/return false.*/, '')
139
140                // change form if a form attribute is present
141                if (attrs.get('form')) {
142            button = button.replace(
143                                "jQuery(this).parents('form:first')",
144                                "\$('" + attrs.get('form') + "')"
145                        )
146                }
147
148                // change 'this' if a this attribute is preset
149                if (attrs.get('this')) {
150                        button = button.replace('this', attrs.get('this'))
151                }
152
153                out << button
154        }
155
156        /**
157         * generate ajax webflow redirect javascript
158         *
159         * As we have an Ajaxified webflow, the initial wizard page
160         * cannot contain a wizard form, as upon a failing submit
161         * (e.g. the form data does not validate) the form should be
162         * shown again. However, the Grails webflow then renders the
163         * complete initial wizard page into the success div. As this
164         * ruins the page layout (a page within a page) we want the
165         * initial page to redirect to the first wizard form to enter
166         * the webflow correctly. We do this by emulating an ajax post
167         * call which updates the wizard content with the first wizard
168         * form.
169         *
170         * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
171         * form = the form identifier
172         * name = the action to execute in the webflow
173         * update = the divs to update upon success or error
174         *
175         * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
176         *
177         * Example initial webflow action to work with this javascript:
178         * ...
179         * mainPage {
180         *      render(view: "/wizard/index")
181         *      onRender {
182         *              flow.page = 1
183         *  }
184         *      on("next").to "pageOne"
185         * }
186         * ...
187         *
188         * @param Map attributes
189         * @param Closure body
190         */
191        def ajaxFlowRedirect = { attrs, body ->
192                // generate javascript
193                out << '<script type="text/javascript">'
194                out << '$(document).ready(function() {'
195                out << ajaxSubmitJs(attrs, body)
196                out << '});'
197                out << '</script>'
198        }
199
200        /**
201         * render the content of a particular wizard page
202         * @param Map attrs
203         * @param Closure body  (help text)
204         */
205        def pageContent = { attrs, body ->
206                // define AJAX provider
207                setProvider([library: ajaxProvider])
208
209                // render new body content
210                //      - this JavaScript variable is used by baseElement to workaround an IE
211                //        specific issue (double submit on onchange events). The hell with IE!
212                //        @see baseElement
213                out << '<script type="text/javascript">var lastRequestTime = 0;</script>'
214                out << render(template: "/wizard/common/tabs")
215                out << '<div class="content">'
216                out << body()
217                out << '</div>'
218                out << render(template: "/wizard/common/navigation")
219                out << render(template: "/wizard/common/error")
220        }
221
222        /**
223         * generate a base form element
224         * @param String inputElement name
225         * @param Map attributes
226         * @param Closure help content
227         */
228        def baseElement = { inputElement, attrs, help ->
229println ".rendering [" + inputElement + "] with name [" + attrs.get('name') + "] and value [" + ((attrs.value) ? attrs.get('value').toString() : "-") + "]"
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 << '<script type="text/javascript">';
796                out << '  $(document).ready( function() { ';
797                out << '    var filename = "' + attrs.value + '";';
798                out << '    fileUploadField( "' + attrs.name + '" );';
799                out << '    if( filename != "" ) {';
800                out << '      $("#' + attrs.name + 'Example").html("Current file: " + createFileHTML( filename ) )';
801                out << '    }';
802                out << '  } );';
803                out << "</script>\n";
804        }
805
806        /**
807         * Protocol form element
808         * @param Map attributes
809         * @param Closure help content
810         */
811        def protocolElement = { attrs, body ->
812                // render protocol element
813                baseElement.call(
814                        'protocolSelect',
815                        attrs,
816                        body
817                )
818        }
819
820        /**
821         * render a protocol select element
822         * @param Map attrs
823         */
824        def protocolSelect = { attrs ->
825                // fetch all protocold
826                attrs.from = Protocol.findAll() // for now, all protocols
827
828                // got a name?
829                if (!attrs.name) {
830                        attrs.name = 'protocol'
831                }
832
833                out << select(attrs)
834        }
835
836        def show = { attrs ->
837                // is object parameter set?
838                def o = attrs.object
839
840                println o.getProperties();
841                o.getProperties().each {
842                        println it
843                }
844
845                out << "!! test version of 'show' tag !!"
846        }
847
848        /**
849         * render table headers for all subjectFields in a template
850         * @param Map attributes
851         */
852        def templateColumnHeaders = { attrs ->
853                def entity              = (attrs.get('entity'))
854                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
855
856                // got a template?
857                if (template) {
858                        // render template fields
859                        entity.giveFields().each() {
860                                // Format the column name by:
861                                // - separating combined names (SampleName --> Sample Name)
862                                // - capitalizing every seperate word
863                                def ucName = it.name.replaceAll(/[a-z][A-Z][a-z]/) {
864                                        it[0] + ' ' + it[1..2]
865                                }.replaceAll(/\w+/) {
866                                        it[0].toUpperCase() + ((it.size() > 1) ? it[1..-1] : '')
867                                }
868
869                                out << '<div class="' + attrs.get('class') + '">' + ucName + (it.unit ? " (${it.unit})" : '')
870                                if (it.comment) {
871                                        out << '<div class="helpIcon"></div>'
872                                        out << '<div class="helpContent">' + it.comment + '</div>'
873                                }
874                                out << '</div>'
875                        }
876                }
877        }
878
879        def templateColumns = { attrs ->
880                // render template fields as columns
881                attrs.renderType = 'column'
882                out << renderTemplateFields(attrs)
883        }
884
885        def templateElements = { attrs ->
886                // render template fields as form elements
887                attrs.renderType = 'element'
888                out << renderTemplateFields(attrs)
889        }
890
891        /**
892         * render form elements based on an entity's template
893         * @param Map attributes
894         * @param String body
895         */
896        def renderTemplateFields = { attrs ->
897                def renderType  = attrs.remove('renderType')
898                def entity              = (attrs.get('entity'))
899                def prependName = (attrs.get('name')) ? attrs.remove('name')+'_' : ''
900                def template    = (entity && entity instanceof TemplateEntity) ? entity.template : null
901                def inputElement= null
902                def addDummy    = (attrs.get('addDummy')) ? true : false
903
904                // got a template?
905                if (template) {
906                        // render template fields
907                        entity.giveFields().each() {
908                                def fieldValue  = entity.getFieldValue(it.name)
909                                def helpText    = (it.comment && renderType == 'element') ? it.comment : ''
910                                def ucName              = it.name[0].toUpperCase() + it.name.substring(1)
911
912                                // output column opening element?
913                                if (renderType == 'column') {
914                                        out << '<div class="' + attrs.get('class') + '">'
915                                }
916
917                                switch (it.type.toString()) {
918                                        case ['STRING', 'INTEGER', 'FLOAT', 'DOUBLE', 'LONG']:
919                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
920                                                out << "$inputElement"(
921                                                        description     : ucName,
922                                                        name            : prependName + it.escapedName(),
923                                                        value           : fieldValue,
924                                                        required        : it.isRequired()
925                                                ){helpText}
926                                                break
927                                        case 'TEXT':
928                                                inputElement = (renderType == 'element') ? 'textAreaElement' : 'textField'
929                                                out << "$inputElement"(
930                                                        description     : ucName,
931                                                        name            : prependName + it.escapedName(),
932                                                        value           : fieldValue,
933                                                        required        : it.isRequired()
934                                                ){helpText}
935                                                break
936                                        case 'STRINGLIST':
937                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
938                                                if (!it.listEntries.isEmpty()) {
939                                                        out << "$inputElement"(
940                                                                description     : ucName,
941                                                                name            : prependName + it.escapedName(),
942                                                                from            : it.listEntries,
943                                                                value           : fieldValue,
944                                                                required        : it.isRequired()
945                                                        ){helpText}
946                                                } else {
947                                                        out << '<span class="warning">no values!!</span>'
948                                                }
949                                                break
950                                        case 'ONTOLOGYTERM':
951                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
952                                                // @see ontology-chooser.js
953                                                inputElement = (renderType == 'element') ? 'termElement' : 'termSelect'
954
955                                                // override addDummy to always add the dummy...
956                                                addDummy = true
957
958                                                if (it.ontologies) {
959                                                        out << "$inputElement"(
960                                                                description     : ucName,
961                                                                name            : prependName + it.escapedName(),
962                                                                value           : fieldValue.toString(),
963                                                                ontologies      : it.ontologies,
964                                                                addDummy        : addDummy,
965                                                                required        : it.isRequired()
966                                                        ){helpText}
967                                                } else {
968                                                        out << "$inputElement"(
969                                                                description     : ucName,
970                                                                name            : prependName + it.escapedName(),
971                                                                value           : fieldValue.toString(),
972                                                                addDummy        : addDummy,
973                                                                required        : it.isRequired()
974                                                        ){helpText}
975                                                }
976                                                break
977                                        case 'ONTOLOGYTERM-old':
978                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
979                                                // @see ontology-chooser.js
980                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
981                                                out << "$inputElement"(
982                                                        name    : prependName + it.escapedName(),
983                                                        value   : fieldValue,
984                                                        rel             : 'ontology-all',
985                                                        size    : 100,
986                                                        required: it.isRequired()
987                                                )
988                                                out << hiddenField(
989                                                        name: prependName + it.name + '-concept_id',
990                                                        value: fieldValue
991                                                )
992                                                out << hiddenField(
993                                                        name: prependName + it.escapedName() + '-ontology_id',
994                                                        value: fieldValue
995                                                )
996                                                out << hiddenField(
997                                                        name: prependName + it.escapedName() + '-full_id',
998                                                        value: fieldValue
999                                                )
1000                                                break
1001                                        case 'DATE':
1002                                                inputElement = (renderType == 'element') ? 'dateElement' : 'textField'
1003
1004                                                // transform value?
1005                                                if (fieldValue instanceof Date) {
1006                                                        if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
1007                                                                // transform date instance to formatted string (dd/mm/yyyy)
1008                                                                fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
1009                                                        } else {
1010                                                                // transform to date + time
1011                                                                fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
1012                                                        }
1013                                                }
1014
1015                                                // render element
1016                                                out << "$inputElement"(
1017                                                        description     : ucName,
1018                                                        name            : prependName + it.escapedName(),
1019                                                        value           : fieldValue,
1020                                                        rel                     : 'date',
1021                                                        required        : it.isRequired()
1022                                                ){helpText}
1023                                                break
1024                                        case ['RELTIME']:
1025                                                inputElement = (renderType == 'element') ? 'textFieldElement' : 'textField'
1026                                                out << "$inputElement"(
1027                                                        description                     : ucName,
1028                                                        name                            : prependName + it.escapedName(),
1029                                                        value                           : new RelTime( fieldValue ).toString(),
1030                            addExampleElement   : true,
1031                            onBlur                              : 'showExampleReltime(this)',
1032                                                        required                        : it.isRequired()
1033                                                ){helpText}
1034                                                break
1035                                        case ['FILE']:
1036                                                inputElement = (renderType == 'element') ? 'fileFieldElement' : 'fileField'
1037                                                out << "$inputElement"(
1038                                                        description                     : ucName,
1039                                                        name                            : prependName + it.escapedName(),
1040                                                        value                           : fieldValue ? fieldValue : "",
1041                            addExampleElement   : true,
1042                                                        required                        : it.isRequired()
1043                                                ){helpText}
1044                                                break
1045                                        case ['BOOLEAN']:
1046                                                inputElement = (renderType == 'element') ? 'checkBoxElement' : 'checkBox'
1047                                                out << "$inputElement"(
1048                                                        description     : ucName,
1049                                                        name            : prependName + it.escapedName(),
1050                                                        value           : fieldValue,
1051                                                        required        : it.isRequired()
1052                                                ){helpText}
1053                                                break
1054                                        case ['TEMPLATE']:
1055                                                inputElement = (renderType == 'element') ? 'templateElement' : 'templateSelect'
1056                                                out << "$inputElement"(
1057                                                        description     : ucName,
1058                                                        name            : prependName + it.escapedName(),
1059                                                        addDummy        : true,
1060                                                        entity          : it.entity,
1061                                                        value           : fieldValue,
1062                                                        required        : it.isRequired()
1063                                                ){helpText}
1064                                                break
1065                                        case ['MODULE']:
1066                                                inputElement = (renderType == 'element') ? 'selectElement' : 'select'
1067                                                out << "$inputElement"(
1068                                                        description     : ucName,
1069                                                        name            : prependName + it.escapedName(),
1070                                                        from            : AssayModule.findAll(),
1071                                                        value           : fieldValue,
1072                                                        required        : it.isRequired()
1073                                                ){helpText}
1074                                        break
1075                                                break
1076                                        default:
1077                                                // unsupported field type
1078                                                out << '<span class="warning">!' + it.type + '</span>'
1079                                                break
1080                                }
1081
1082                                // output column closing element?
1083                                if (renderType == 'column') {
1084                                        out << '</div>'
1085                                }
1086                        }
1087                }
1088        }
1089
1090        def PublicationSelectElement = { attrs, body ->
1091                attrs.description = 'Publications';
1092                // render list with publications currently available
1093                baseElement.call(
1094                        '_publicationList',
1095                        attrs,
1096                        body
1097                )
1098
1099                attrs.description = '';
1100
1101                // render 'Add publication button'
1102                baseElement.call(
1103                        '_publicationAddButton',
1104                        attrs,
1105                        body
1106                )
1107        }
1108
1109        /**
1110         * Renders a input box for publications
1111         */
1112        def publicationSelect = { attrs, body ->
1113                if (attrs.get('value') == null) {
1114                        attrs.value = [];
1115                }
1116                if (attrs.get('description') == null) {
1117                        attrs.description = '';
1118                }
1119                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1120                out << textField(
1121                        name: attrs.get("name"),
1122                        value: '',
1123                        rel: 'publication-pubmed',
1124                        style: 'width: 400px;'
1125                );
1126                out << '</form>';
1127                out << '<script type="text/javascript">';
1128                out << '  var onSelect = function( chooserObject, inputElement, event, ui ) { selectPubMedAdd( chooserObject, inputElement, event, ui ); enableButton( ".' + attrs.name + '_publication_dialog", "Add", true ); };'
1129                out << '  iField = $( "#' + attrs.get('name') + '" );';
1130                out << '  new PublicationChooser().initAutocomplete( iField, { "select" : onSelect } );';
1131                out << '</script>';
1132        }
1133
1134        def _publicationList = { attrs, body ->
1135                def display_none = 'none';
1136                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1137                        display_none = 'inline';
1138                }
1139
1140                // Add a unordered list
1141                out << '<ul class="publication_list" id="' + attrs.name + '_list">';
1142
1143                out << '<li>';
1144                out << '<span class="publication_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1145                out << 'No publications selected';
1146                out << '</span>';
1147                out << '</li>';
1148
1149                out << '</ul>';
1150
1151                // Add the publications using javascript
1152                out << '<script type="text/javascript">'
1153                if (attrs.get('value') && attrs.get('value').size() > 0) {
1154                        def i = 0;
1155                        attrs.get('value').each {
1156                                out << 'showPublication( ';
1157                                out << '  "' + attrs.name + '",';
1158                                out << '  ' + it.id + ',';
1159                                out << '  "' + it.title + '",';
1160                                out << '  "' + it.authorsList + '",';
1161                                out << '  ' + i++;
1162                                out << ');';
1163                        }
1164                }
1165                out << '</script>';
1166
1167                def ids;
1168                if (attrs.get('value') && attrs.get('value').size() > 0) {
1169                        ids = attrs.get('value').id.join(',')
1170                } else {
1171                        ids = '';
1172                }
1173                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1174        }
1175
1176        def _publicationAddButton = { attrs, body ->
1177
1178                // Output the dialog for the publications
1179                out << '<div id="' + attrs.name + '_dialog">';
1180                out << '<p>Search for a publication on pubmed. You can search on a part of the title or authors. </p>';
1181                out << publicationSelect(attrs, body);
1182                out << '</div>';
1183                out << '<script type="text/javascript">';
1184                out << '  createPublicationDialog( "' + attrs.name + '" );'
1185                out << '</script>';
1186
1187                out << '<input type="button" onClick="openPublicationDialog(\'' + attrs.name + '\' );" value="Add Publication">';
1188        }
1189
1190        def ContactSelectElement = { attrs, body ->
1191
1192                attrs.description = 'Contacts';
1193                // render list with publications currently available
1194                baseElement.call(
1195                        '_contactList',
1196                        attrs,
1197                        body
1198                )
1199
1200                attrs.description = '';
1201
1202                // render 'publications list'
1203                out << '<div id="' + attrs.name + '_dialog" class="contacts_dialog" style="display: none;">'
1204                baseElement.call(
1205                        '_personSelect',
1206                        attrs,
1207                        body
1208                )
1209                baseElement.call(
1210                        '_roleSelect',
1211                        attrs,
1212                        body
1213                )
1214                baseElement.call(
1215                        '_contactAddButtonAddition',
1216                        attrs,
1217                        body
1218                )
1219                out << '</div>';
1220
1221                // render 'Add contact button'
1222                baseElement.call(
1223                        '_contactAddDialogButton',
1224                        attrs,
1225                        body
1226                )
1227        }
1228
1229        def _contactList = { attrs, body ->
1230                def display_none = 'none';
1231                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1232                        display_none = 'inline';
1233                }
1234
1235                // Add a unordered list
1236                out << '<ul class="contact_list" id="' + attrs.name + '_list">';
1237
1238                out << '<li>';
1239                out << '<span class="contacts_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1240                out << 'No contacts selected';
1241                out << '</span>';
1242                out << '</li>';
1243
1244                out << '</ul>';
1245
1246                // Add the contacts using javascript
1247                out << '<script type="text/javascript">'
1248                if (attrs.get('value') && attrs.get('value').size() > 0) {
1249                        def i = 0;
1250                        attrs.get('value').each {
1251                                out << 'showContact( ';
1252                                out << '  "' + attrs.name + '",';
1253                                out << '  "' + it.person.id + '-' + it.role.id + '",';
1254                                out << '  "' + it.person.lastName + ', ' + it.person.firstName + (it.person.prefix ? ' ' + it.person.prefix : '') + '",';
1255                                out << '  "' + it.role.name + '",';
1256                                out << '  ' + i++;
1257                                out << ');';
1258                        }
1259                }
1260                out << '</script>';
1261
1262                def ids = '';
1263                if (attrs.get('value') && attrs.get('value').size() > 0) {
1264                        ids = attrs.get('value').collect { it.person.id + '-' + it.role.id }
1265                        ids = ids.join(',');
1266                }
1267                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1268        }
1269
1270        def _contactAddSelect = { attrs, body ->
1271                out << _personSelect(attrs) + _roleSelect(attrs);
1272        }
1273
1274        def _contactAddButtonAddition = { attrs, body ->
1275                out << '<input type="button" onClick="addContact ( \'' + attrs.name + '\' ); $(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Add">';
1276                out << '<input type="button" onClick="$(\'#' + attrs.name + '_dialog\').hide(); $( \'#' + attrs.name + '_dialogButton\' ).show();" value="Close">';
1277        }
1278
1279        def _contactAddDialogButton = { attrs, body ->
1280                out << '<input type="button" onClick="$( \'#' + attrs.name + '_dialog\' ).show(); $(this).hide();" id="' + attrs.name + '_dialogButton" value="Add Contact">';
1281        }
1282        /**
1283         * Person select element
1284         * @param Map attributes
1285         */
1286        def _personSelect = { attrs ->
1287                def selectAttrs = new LinkedHashMap();
1288
1289                // define 'from'
1290                def persons = Person.findAll().sort({ a, b -> a.lastName == b.lastName ? (a.firstName <=> b.firstName) : (a.lastName <=> b.lastName) } as Comparator);
1291                selectAttrs.from = persons.collect { it.lastName + ', ' + it.firstName + (it.prefix ? ' ' + it.prefix : '') }
1292                selectAttrs.keys = persons.id;
1293
1294                // add 'rel' attribute
1295                selectAttrs.rel = 'person'
1296                selectAttrs.name = attrs.name + '_person';
1297
1298                // add a dummy field
1299                selectAttrs.from.add(0,'')
1300                selectAttrs.keys.add(0,'')
1301
1302                out << "Person: " + select(selectAttrs)
1303        }
1304
1305        /**
1306         * Role select element
1307         * @param Map attributes
1308         */
1309        def _roleSelect = { attrs ->
1310                def selectAttrs = new LinkedHashMap();
1311
1312                // define 'from'
1313                def roles = PersonRole.findAll();
1314                selectAttrs.from = roles.collect { it.name };
1315                selectAttrs.keys = roles.id;
1316
1317                // add 'rel' attribute
1318                selectAttrs.rel = 'role'
1319                selectAttrs.name = attrs.name + '_role';
1320
1321                // add a dummy field
1322                selectAttrs.from.add(0,'')
1323                selectAttrs.keys.add(0,'')
1324
1325                out << "Role: " + select(selectAttrs)
1326        }
1327
1328
1329        def UserSelectElement = { attrs, body ->
1330                // render list with publications currently available
1331                baseElement.call(
1332                        '_userList',
1333                        attrs,
1334                        body
1335                )
1336
1337                attrs.description = '';
1338
1339                // render 'Add user button'
1340                baseElement.call(
1341                        '_userAddButton',
1342                        attrs,
1343                        body
1344                )
1345        }
1346
1347        /**
1348         * Renders an input box for publications
1349         */
1350        def userSelect = { attrs, body ->
1351                if (attrs.get('value') == null) {
1352                        attrs.value = [];
1353                }
1354                if (attrs.get('description') == null) {
1355                        attrs.description = '';
1356                }
1357               
1358                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">';
1359                out << select(
1360                        name: attrs.get("name"),
1361                        value: '',
1362                        from: SecUser.list(),
1363                        optionValue: 'username',
1364                        optionKey: 'id',
1365                        style: 'width: 400px;'
1366                );
1367                out << '</form>';
1368        }
1369
1370        def _userList = { attrs, body ->
1371                def display_none = 'none';
1372                if (!attrs.get('value') || attrs.get('value').size() == 0) {
1373                        display_none = 'inline';
1374                }
1375
1376                // Add a unordered list
1377                out << '<ul class="user_list" id="' + attrs.name + '_list">';
1378
1379                out << '<li>';
1380                out << '<span class="user_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">';
1381                out << '-';
1382                out << '</span>';
1383                out << '</li>';
1384
1385                out << '</ul>';
1386
1387                // Add the publications using javascript
1388                out << '<script type="text/javascript">'
1389                if (attrs.get('value') && attrs.get('value').size() > 0) {
1390                        def i = 0;
1391                        attrs.get('value').each {
1392                                out << 'showUser( ';
1393                                out << '  "' + attrs.name + '",';
1394                                out << '  ' + it.id + ',';
1395                                out << '  "' + it.username + '",';
1396                                out << '  ' + i++;
1397                                out << ');';
1398                        }
1399                }
1400                out << '</script>';
1401
1402                def ids;
1403                if (attrs.get('value') && attrs.get('value').size() > 0) {
1404                        ids = attrs.get('value').id.join(',')
1405                } else {
1406                        ids = '';
1407                }
1408                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">';
1409        }
1410
1411        def _userAddButton = { attrs, body ->
1412
1413                // Output the dialog for the publications
1414                out << '<div id="' + attrs.name + '_dialog">';
1415                out << '<p>Select a user from the database.</p>';
1416                out << userSelect(attrs, body);
1417                out << '</div>';
1418                out << '<script type="text/javascript">';
1419                out << '  createUserDialog( "' + attrs.name + '" );'
1420                out << '</script>';
1421
1422                out << '<input type="button" onClick="openUserDialog(\'' + attrs.name + '\' );" value="Add User">';
1423        }
1424
1425}
Note: See TracBrowser for help on using the browser.