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

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