Ignore:
Timestamp:
Mar 26, 2010, 1:02:39 PM (11 years ago)
Author:
ademcan
Message:

queryController changes

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/taglib/dbnp/query/QueryTagLib.groovy

    r315 r316  
    1313 *
    1414 * Revision information:
    15  * $Rev$
    16  * $Author$
    17  * $Date$
     15 * $Rev: 299 $
     16 * $Author: duh $
     17 * $Date: 2010-03-22 14:40:35 +0100 (Mon, 22 Mar 2010) $
    1818 */
    19 
    20 class QueryTagLib extends JavascriptTagLib {
     19class WizardTagLib extends JavascriptTagLib {
     20        // define the tag namespace (e.g.: <wizard:action ... />
     21        static namespace = "wizard"
     22
     23        // define the AJAX provider to use
     24        static ajaxProvider = "jquery"
     25
     26        // define default text field width
     27        static defaultTextFieldSize = 25;
     28
     29        /**
     30         * ajaxButton tag, this is a modified version of the default
     31         * grails submitToRemote tag to work with grails webflows.
     32         * Usage is identical to submitToRemote with the only exception
     33         * that a 'name' form element attribute is required. E.g.
     34         * <wizard:ajaxButton name="myAction" value="myButton ... />
     35         *
     36         * you can also provide a javascript function to execute after
     37         * success. This behaviour differs from the default 'after'
     38         * action which always fires after a button press...
     39         *
     40         * @see http://blog.osx.eu/2010/01/18/ajaxifying-a-grails-webflow/
     41         * @see http://www.grails.org/WebFlow
     42         * @see http://www.grails.org/Tag+-+submitToRemote
     43         * @todo perhaps some methods should be moved to a more generic
     44         *        'webflow' taglib or plugin
     45         * @param Map attributes
     46         * @param Closure body
     47         */
     48        def ajaxButton = {attrs, body ->
     49                // get the jQuery version
     50                def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']
     51
     52                // fetch the element name from the attributes
     53                def elementName = attrs['name'].replaceAll(/ /, "_")
     54
     55                // javascript function to call after success
     56                def afterSuccess = attrs['afterSuccess']
     57
     58                // src parameter?
     59                def src = attrs['src']
     60                def alt = attrs['alt']
     61
     62                // generate a normal submitToRemote button
     63                def button = submitToRemote(attrs, body)
     64
     65                /**
     66                 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
     67                 * not properly work with AJAX as the submitToRemote button does not
     68                 * handle and submit the form properly. In order to support webflows
     69                 * this method modifies two parts of a 'normal' submitToRemote button:
     70                 *
     71                 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
     72                 *    action refers to the button and / or the action upon that button.
     73                 *    However, it should point to the form the button is part of as the
     74                 *    the button should submit the form data.
     75                 * 2) prepend the button name to the serialized data. The default behaviour
     76                 *    of submitToRemote is to remove the element name altogether, while
     77                 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
     78                 *    the appropriate webflow action. Hence, we are going to prepend the
     79                 *    serialized formdata with an _eventId_BUTTONNAME parameter.
     80                 */
     81                if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
     82                        // fix for older jQuery plugin versions
     83                        button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
     84                } else {
     85                        // as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
     86                        // this.form part has been fixed. Consequently, our wrapper has changed as well...
     87                        button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
     88                }
     89
     90                // add an after success function call?
     91                // usefull for performing actions on success data (hence on refreshed
     92                // wizard pages, such as attaching tooltips)
     93                if (afterSuccess) {
     94                        button = button.replaceFirst(/\.html\(data\)\;/, '.html(data);' + afterSuccess + ';')
     95                }
     96
     97                // got an src parameter?
     98                if (src) {
     99                        def replace = 'type="image" src="' + src + '"'
     100
     101                        if (alt) replace = replace + ' alt="' + alt + '"'
     102
     103                        button = button.replaceFirst(/type="button"/, replace)
     104                }
     105
     106                // replace double semi colons
     107                button = button.replaceAll(/;{2,}/, ';')
     108
     109                // render button
     110                out << button
     111        }
     112
     113        /**
     114         * generate a ajax submit JavaScript
     115         * @see WizardTagLib::ajaxFlowRedirect
     116         * @see WizardTagLib::baseElement (ajaxSubmitOnChange)
     117         */
     118        def ajaxSubmitJs = {attrs, body ->
     119                // define AJAX provider
     120                setProvider([library: ajaxProvider])
     121
     122                // got a function name?
     123                def functionName = attrs.remove('functionName')
     124                if (functionName && !attrs.get('name')) {
     125                        attrs.name = functionName
     126                }
     127
     128                // generate an ajax button
     129                def button = this.ajaxButton(attrs, body)
     130
     131                // strip the button part to only leave the Ajax call
     132                button = button.replaceFirst(/<[^\"]*\"jQuery.ajax/, 'jQuery.ajax')
     133                button = button.replaceFirst(/return false.*/, '')
     134
     135                // change form if a form attribute is present
     136                if (attrs.get('form')) {
     137                        button = button.replaceFirst(/this\.form/,
     138                                "\\\$('" + attrs.get('form') + "')"
     139                        )
     140                }
     141
     142                out << button
     143        }
     144
     145        /**
     146         * generate ajax webflow redirect javascript
     147         *
     148         * As we have an Ajaxified webflow, the initial wizard page
     149         * cannot contain a wizard form, as upon a failing submit
     150         * (e.g. the form data does not validate) the form should be
     151         * shown again. However, the Grails webflow then renders the
     152         * complete initial wizard page into the success div. As this
     153         * ruins the page layout (a page within a page) we want the
     154         * initial page to redirect to the first wizard form to enter
     155         * the webflow correctly. We do this by emulating an ajax post
     156         * call which updates the wizard content with the first wizard
     157         * form.
     158         *
     159         * Usage: <wizard:ajaxFlowRedirect form="form#wizardForm" name="next" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" />
     160         * form = the form identifier
     161         * name = the action to execute in the webflow
     162         * update = the divs to update upon success or error
     163         *
     164         * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name'
     165         *
     166         * Example initial webflow action to work with this javascript:
     167         * ...
     168         * mainPage {*  render(view: "/wizard/index")
     169         *      onRender {*             flow.page = 1
     170         *}*    on("next").to "pageOne"
     171         *}* ...
     172         *
     173         * @param Map attributes
     174         * @param Closure body
     175         */
     176        def ajaxFlowRedirect = {attrs, body ->
     177                // generate javascript
     178                out << '<script type="text/javascript">'
     179                out << '$(document).ready(function() {'
     180                out << ajaxSubmitJs(attrs, body)
     181                out << '});'
     182                out << '</script>'
     183        }
    21184
    22185        /**
     
    25188         * @param Closure body  (help text)
    26189         */
    27 
    28190        def pageContent = {attrs, body ->
    29191                // define AJAX provider
     
    31193
    32194                // render new body content
    33                
    34195                out << render(template: "/query/common/tabs")
    35196                out << '<div class="content">'
     
    38199                out << render(template: "/query/common/navigation")
    39200                out << render(template: "/query/common/error")
    40                
    41         }
    42 
     201        }
     202
     203        /**
     204         * generate a base form element
     205         * @param String inputElement name
     206         * @param Map attributes
     207         * @param Closure help content
     208         */
     209        def baseElement = {inputElement, attrs, help ->
     210                // work variables
     211                def description = attrs.remove('description')
     212                def addExampleElement = attrs.remove('addExampleElement')
     213                def addExample2Element = attrs.remove('addExample2Element')
     214
     215                // got an ajax onchange action?
     216                def ajaxOnChange = attrs.remove('ajaxOnChange')
     217                if (ajaxOnChange) {
     218                        if (!attrs.onChange) attrs.onChange = ''
     219
     220                        // add onChange AjaxSubmit javascript
     221                        attrs.onChange += ajaxSubmitJs(
     222                                [
     223                                        functionName: ajaxOnChange,
     224                                        url: attrs.get('url'),
     225                                        update: attrs.get('update'),
     226                                        afterSuccess: attrs.get('afterSuccess')
     227                                ],
     228                                ''
     229                        )
     230                }
     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">'
     240                out << ' <div class="description">'
     241                out << description
     242                out << ' </div>'
     243                out << ' <div class="input">'
     244                out << renderedElement
     245                if (help()) {
     246                        out << '        <div class="helpIcon"></div>'
     247                }
     248
     249                // add an disabled input box for feedback purposes
     250                // @see dateElement(...)
     251                if (addExampleElement) {
     252                        def exampleAttrs = new LinkedHashMap()
     253                        exampleAttrs.name = attrs.get('name') + 'Example'
     254                        exampleAttrs.class = 'isExample'
     255                        exampleAttrs.disabled = 'disabled'
     256                        exampleAttrs.size = 30
     257                        out << textField(exampleAttrs)
     258                }
     259
     260                // add an disabled input box for feedback purposes
     261                // @see dateElement(...)
     262                if (addExample2Element) {
     263                        def exampleAttrs = new LinkedHashMap()
     264                        exampleAttrs.name = attrs.get('name') + 'Example2'
     265                        exampleAttrs.class = 'isExample'
     266                        exampleAttrs.disabled = 'disabled'
     267                        exampleAttrs.size = 30
     268                        out << textField(exampleAttrs)
     269                }
     270
     271                out << ' </div>'
     272
     273                // add help content if it is available
     274                if (help()) {
     275                        out << '  <div class="helpContent">'
     276                        out << '    ' + help()
     277                        out << '  </div>'
     278                }
     279
     280                out << '</div>'
     281        }
     282
     283        /**
     284         * render a textFieldElement
     285         * @param Map attrs
     286         * @param Closure body  (help text)
     287         */
     288        def textFieldElement = {attrs, body ->
     289                // set default size, or scale to max length if it is less than the default size
     290                if (!attrs.get("size")) {
     291                        if (attrs.get("maxlength")) {
     292                                attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength")
     293                        } else {
     294                                attrs.size = defaultTextFieldSize
     295                        }
     296                }
     297
     298                // render template element
     299                baseElement.call(
     300                        'textField',
     301                        attrs,
     302                        body
     303                )
     304        }
     305
     306        /**
     307         * render a select form element
     308         * @param Map attrs
     309         * @param Closure body  (help text)
     310         */
     311        def selectElement = {attrs, body ->
     312                baseElement.call(
     313                        'select',
     314                        attrs,
     315                        body
     316                )
     317        }
     318
     319        /**
     320         * render a checkBox form element
     321         * @param Map attrs
     322         * @param Closure body  (help text)
     323         */
     324        def checkBoxElement = {attrs, body ->
     325                baseElement.call(
     326                        'checkBox',
     327                        attrs,
     328                        body
     329                )
     330        }
     331
     332        /**
     333         * render a dateElement
     334         * NOTE: datepicker is attached through wizard.js!
     335         * @param Map attrs
     336         * @param Closure body  (help text)
     337         */
     338        def dateElement = {attrs, body ->
     339                // transform value?
     340                if (attrs.value instanceof Date) {
     341                        // transform date instance to formatted string (dd/mm/yyyy)
     342                        attrs.value = String.format('%td/%<tm/%<tY', attrs.value)
     343                }
     344
     345                // add 'rel' field to identity the datefield using javascript
     346                attrs.rel = 'date'
     347
     348                // set some textfield values
     349                attrs.maxlength = (attrs.maxlength) ? attrs.maxlength : 10
     350                attrs.addExampleElement = true
     351
     352                // render a normal text field
     353                //out << textFieldElement(attrs,body)
     354                textFieldElement.call(
     355                        attrs,
     356                        body
     357                )
     358        }
     359
     360        /**
     361         * render a dateElement
     362         * NOTE: datepicker is attached through wizard.js!
     363         * @param Map attrs
     364         * @param Closure body  (help text)
     365         */
     366        def timeElement = {attrs, body ->
     367                // transform value?
     368                if (attrs.value instanceof Date) {
     369                        // transform date instance to formatted string (dd/mm/yyyy)
     370                        attrs.value = String.format('%td/%<tm/%<tY %<tH:%<tM', attrs.value)
     371                }
     372
     373                // add 'rel' field to identity the field using javascript
     374                attrs.rel = 'datetime'
     375
     376                attrs.addExampleElement = true
     377                attrs.addExample2Element = true
     378                attrs.maxlength = 16
     379
     380                // render a normal text field
     381                //out << textFieldElement(attrs,body)
     382                textFieldElement.call(
     383                        attrs,
     384                        body
     385                )
     386        }
     387
     388        /**
     389         * Template form element
     390         * @param Map attributes
     391         * @param Closure help content
     392         */
     393        def speciesElement = {attrs, body ->
     394                // render template element
     395                baseElement.call(
     396                        'speciesSelect',
     397                        attrs,
     398                        body
     399                )
     400        }
     401
     402        /**
     403         * Button form element
     404         * @param Map attributes
     405         * @param Closure help content
     406         */
     407        def buttonElement = {attrs, body ->
     408                // render template element
     409                baseElement.call(
     410                        'ajaxButton',
     411                        attrs,
     412                        body
     413                )
     414        }
     415
     416        /**
     417         * render a species select element
     418         * @param Map attrs
     419         */
     420        def speciesSelect = {attrs ->
     421                // fetch the speciesOntology
     422                // note that this is a bit nasty, probably the ontologyName should
     423                // be configured in a configuration file... --> TODO: centralize species configuration
     424                def speciesOntology = Ontology.findByName('NCBI Taxonomy')
     425
     426                // fetch all species
     427                attrs.from = Term.findAllByOntology(speciesOntology)
     428
     429                // got a name?
     430                if (!attrs.name) {
     431                        // nope, use a default name
     432                        attrs.name = 'species'
     433                }
     434
     435                out << select(attrs)
     436        }
     437
     438        /**
     439         * Template form element
     440         * @param Map attributes
     441         * @param Closure help content
     442         */
     443        def templateElement = {attrs, body ->
     444                // render template element
     445                baseElement.call(
     446                        'templateSelect',
     447                        attrs,
     448                        body
     449                )
     450        }
     451
     452        /**
     453         * render a template select element
     454         * @param Map attrs
     455         */
     456        def templateSelect = {attrs ->
     457                def entity = attrs.remove('entity')
     458
     459                // fetch templates
     460                if (attrs.remove('addDummy')) {
     461                        attrs.from = ['']
     462                        if (entity && entity instanceof Class) {
     463                                Template.findAllByEntity(entity).each() {
     464                                        attrs.from[attrs.from.size()] = it
     465                                }
     466                        }
     467                } else {
     468                        attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll()
     469                }
     470
     471                // got a name?
     472                if (!attrs.name) {
     473                        attrs.name = 'template'
     474                }
     475
     476                // got result?
     477                if (attrs.from.size() > 0) {
     478                        out << select(attrs)
     479                } else {
     480                        // no, return false to make sure this element
     481                        // is not rendered in the template
     482                        return false
     483                }
     484        }
     485
     486        /**
     487         * Term form element
     488         * @param Map attributes
     489         * @param Closure help content
     490         */
     491        def termElement = {attrs, body ->
     492                // render term element
     493                baseElement.call(
     494                        'termSelect',
     495                        attrs,
     496                        body
     497                )
     498        }
     499
     500        /**
     501         * render a term select element
     502         * @param Map attrs
     503         */
     504        def termSelect = {attrs ->
     505                // fetch all terms
     506                attrs.from = Term.findAll()     // for now, all terms as we cannot identify terms as being treatment terms...
     507
     508                // got a name?
     509                if (!attrs.name) {
     510                        attrs.name = 'term'
     511                }
     512
     513                out << select(attrs)
     514        }
     515
     516        /**
     517         * Protocol form element
     518         * @param Map attributes
     519         * @param Closure help content
     520         */
     521        def protocolElement = {attrs, body ->
     522                // render protocol element
     523                baseElement.call(
     524                        'protocolSelect',
     525                        attrs,
     526                        body
     527                )
     528        }
     529
     530        /**
     531         * render a protocol select element
     532         * @param Map attrs
     533         */
     534        def protocolSelect = {attrs ->
     535                // fetch all protocold
     536                attrs.from = Protocol.findAll() // for now, all protocols
     537
     538                // got a name?
     539                if (!attrs.name) {
     540                        attrs.name = 'protocol'
     541                }
     542
     543                out << select(attrs)
     544        }
     545
     546        def show = {attrs ->
     547                // is object parameter set?
     548                def o = attrs.object
     549
     550                println o.getProperties();
     551                o.getProperties().each {
     552                        println it
     553                }
     554
     555                out << "!! test version of 'show' tag !!"
     556        }
     557
     558        /**
     559         * render table headers for all subjectFields in a template
     560         * @param Map attributes
     561         */
     562        def templateColumnHeaders = {attrs ->
     563                def template = attrs.remove('template')
     564
     565                // output table headers for template fields
     566                template.fields.each() {
     567                        out << '<div class="' + attrs.get('class') + '">' + it + '</div>'
     568                }
     569        }
     570
     571        /**
     572         * render table input elements for all subjectFields in a template
     573         * @param Map attributes
     574         */
     575        def templateColumns = {attrs, body ->
     576                def subject = attrs.remove('subject')
     577                def subjectId = attrs.remove('id')
     578                def template = attrs.remove('template')
     579                def intFields = subject.templateIntegerFields
     580                def stringFields = subject.templateStringFields
     581                def floatFields = subject.templateFloatFields
     582                def termFields = subject.templateTermFields
     583
     584                // output columns for these subjectFields
     585                template.fields.each() {
     586                        def fieldValue = subject.getFieldValue(it.name)
     587
     588                        // output div
     589                        out << '<div class="' + attrs.get('class') + '">'
     590
     591                        // handle field types
     592                        switch (it.type.toString()) {
     593                                case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']:
     594                                        out << textField(
     595                                                name: attrs.name + '_' + it.escapedName(),
     596                                                value: fieldValue
     597                                        )
     598                                        break
     599                                case 'STRINGLIST':
     600                                        // render stringlist subjectfield
     601                                        if (!it.listEntries.isEmpty()) {
     602                                                out << select(
     603                                                        name: attrs.name + '_' + it.escapedName(),
     604                                                        from: it.listEntries,
     605                                                        value: fieldValue
     606                                                )
     607                                        } else {
     608                                                out << '<span class="warning">no values!!</span>'
     609                                        }
     610                                        break
     611                                case 'DATE':
     612                                        // transform value?
     613                                        if (fieldValue instanceof Date) {
     614                                                if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) {
     615                                                        // transform date instance to formatted string (dd/mm/yyyy)
     616                                                        fieldValue = String.format('%td/%<tm/%<tY', fieldValue)
     617                                                } else {
     618                                                        // transform to date + time
     619                                                        fieldValue = String.format('%td/%<tm/%<tY %<tH:%<tM', fieldValue)
     620                                                }
     621                                        }
     622
     623                                        // output a date field (not the 'rel' which makes the
     624                                        // javascript front-end bind the jquery-ui datepicker)
     625                                        out << textField(
     626                                                name: attrs.name + '_' + it.escapedName(),
     627                                                value: fieldValue,
     628                                                rel: 'date'
     629                                        )
     630                                        break
     631                                case 'ONTOLOGYTERM':
     632                                        // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
     633                                        // @see ontology-chooser.js, table-editor.js
     634                                        //out << it.getClass()
     635                                        out << textField(
     636                                                name: attrs.name + '_' + it.escapedName(),
     637                                                value: fieldValue,
     638                                                rel: 'ontology-all-name',
     639                                                size: 100
     640                                        )
     641                                        out << hiddenField(
     642                                                name: attrs.name + '_' + it.escapedName() + '-concept_id'
     643                                        )
     644                                        out << hiddenField(
     645                                                name: attrs.name + '_' + it.escapedName() + '-ontology_id'
     646                                        )
     647                                        out << hiddenField(
     648                                                name: attrs.name + '_' + it.escapedName() + '-full_id'
     649                                        )
     650                                        break
     651                                default:
     652                                        // unsupported field type
     653                                        out << '<span class="warning">!' + it.type + '</span>'
     654                                        break
     655                        }
     656
     657                        out << '</div>'
     658                }
     659        }
     660
     661        /**
     662         * render form elements based on an entity's template
     663         * @param Map attributes
     664         * @param String body
     665         */
     666        def templateElements = {attrs ->
     667                def entity = (attrs.get('entity'))
     668                def template = (entity && entity instanceof TemplateEntity) ? entity.template : null
     669
     670                // got a template?
     671                if (template) {
     672                        // render template fields
     673                        template.fields.each() {
     674                                def fieldValue = entity.getFieldValue(it.name)
     675
     676                                switch (it.type.toString()) {
     677                                        case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']:
     678                                                out << textFieldElement(
     679                                                        description: it.name,
     680                                                        name: it.escapedName(),
     681                                                        value: fieldValue
     682                                                )
     683                                                break
     684                                        case 'STRINGLIST':
     685                                                if (!it.listEntries.isEmpty()) {
     686                                                        out << selectElement(
     687                                                                description: it.name,
     688                                                                name: it.escapedName(),
     689                                                                from: it.listEntries,
     690                                                                value: fieldValue
     691                                                        )
     692                                                } else {
     693                                                        out << '<span class="warning">no values!!</span>'
     694                                                }
     695                                                break
     696                                        case 'ONTOLOGYTERM':
     697                                                // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form
     698                                                // @see ontology-chooser.js
     699                                                out << textFieldElement(
     700                                                        name: it.escapedName(),
     701                                                        value: fieldValue,
     702                                                        rel: 'ontology-all-name',
     703                                                        size: 100
     704                                                )
     705                                                out << hiddenField(
     706                                                        name: it.name + '-concept_id',
     707                                                        value: fieldValue
     708                                                )
     709                                                out << hiddenField(
     710                                                        name: it.escapedName() + '-ontology_id',
     711                                                        value: fieldValue
     712                                                )
     713                                                out << hiddenField(
     714                                                        name: it.escapedName() + '-full_id',
     715                                                        value: fieldValue
     716                                                )
     717                                                break
     718                                        case 'DATE':
     719                                                out << dateElement(
     720                                                        description: it.name,
     721                                                        name: it.escapedName(),
     722                                                        value: fieldValue
     723                                                )
     724                                                break
     725                                        default:
     726                                                out << "unkown field type '" + it.type + "'<br/>"
     727                                                break
     728                                }
     729                        }
     730                }
     731        }
    43732}
Note: See TracChangeset for help on using the changeset viewer.