当前位置: 动力学知识库 > 问答 > 编程问答 >

Autogenerate Jquery Autocomplete in CRUD grails scaffolding (one-to-many relationships)

问题描述:

I have altered the src/templates/scaffolding/renderEditor.template file in a grails project, in order to insert, the necessary html field boxes (and javascript code) to configure and use jquery autocomplete, in all the relationships "many-to-one". (The code is shown below)

The autogenerated autocomplete (_form.gsp) works correctly ... but I need to show the correct values (code and description) into the autocomplete textbox when a user edit a record using the scaffolding.

To do that, I need to identify two fields inside the domain: one for code and the other for the description.

To face this problem, I have tried to create two dummies constraints, using the plugin "constraints", the first one used like code, the second one used like a description. I don't like this solution, because the contrainsts could be used several times into the domain.

The code altered into the src/templates/scaffolding/renderEditor.template file is the following: (Note the two input boxes and Javascript code used for autocomplete):

private renderManyToOne(domainClass, property) {

if (property.association) {

/* ORIGINAL CODE inside comments

def sb = new StringBuilder()

sb << '<g:select'

// id is "x" and name is "x.id" as the label will have for="x" and "." in an id will confuse CSS

sb << ' id="' << property.name << '"'

sb << ' name="' << property.name << '.id"'

sb << ' from="${' << property.type.name << '.list()}"'

sb << ' optionKey="id"'

if (isRequired()) sb << ' required=""'

sb << ' value="${' << "${domainInstance}?.${property.name}" << '?.id}"'

sb << ' class="many-to-one"'

sb << renderNoSelection(property)

sb << '/>'

sb as String

*/

def sb = new StringBuilder()

// hidden field for domain.id

sb << '<input type=\"hidden\" '

sb << ' id="' << property.name << '.id"'

sb << ' name="' << property.name << '.id"'

sb << ' value="${' << "${domainInstance}" << '?.id}" '

sb << '/>\n'

// Text field to show the description generated by autocomplete

sb << '\t<input type=\"text\" '

sb << ' id="' << property.name << '"'

sb << ' name="' << property.name << '"'

if (isRequired()) sb << ' required="" '

sb << 'style=\"width: 600px;\" '

sb << ' value="${' << "${domainInstance}?.${property.name}" << '?.id}"'

// sb << '${' << "${property.name}" << '"'

sb << '/>'

def js = new StringBuilder()

js << '''

<script type="text/javascript">

/*

* Remember include jquery and jquery-ui libraries into head section of edit.gsp file

* < g:javascript library="jquery"/>

* < g:javascript library="jquery-ui"/>

*/

\$(document).ready(function() {

'''

js << '\t\$("#' << property.name << '").focus(function(){this.select(); });\n'

js << '\t\t$("#' << property.name << '").autocomplete({\n'

js << '''

source: function(request, response){

\$.ajax({

// Define Remote datasource into the controller

'''

js << ' \t\t url: "'

js << '/' << grails.util.Metadata.current.'app.name' << '/' << property.name << '/' << 'autoCompleteList",'

js << '''

data: request,

success: function(data){

// Get the response (JSON format)

response(data);

},

error: function(){

// Handle server errors

response("Error after search records. Try Again.")

}

});

},

// General options: Triggered only after minimum 2 characters have been entered and others

minLength: 2,

delay: 1,

autoFocus: true,

// Event handler when user selects a Loinc from the list.

select: function(event, ui) {

// update the hidden field.

'''

js << '\t\t\t\t \$("#' << property.name << '\\.id").val(ui.item.id);'

js << '''

}

});

});

</script>

'''

sb << js

sb as String

}

The domain using the dummy contraints (autoid and autodesc):

class LOINC {

static searchable = {

only = ["code", "shortName", "longName", "property", "system", "scale", "method", "time"]

}

String code // LOINC_NUM * 0

String shortName // SHORTNAME * 29

String longName // LONG_COMMON_NAME * 35

String name // BASE_NAME * 21

String component // COMPONENT * 1

String property // PROPERTY * 2

String time // TIME_ASPCT * 3

String system // SYSTEM * 4

String scale // SCALE_TYP * 5

String method // METHOD_TYP * 6

static constraints = {

code(nullable: false, unique: true, blank: false, maxSize: 100, autoid: true)

shortName(nullable: false)

longName(nullable: false, autodesc: true)

name(nullable: false, maxSize: 100)

component(nullable: false)

property(nullable: false)

time(nullable: false)

system(nullable: false)

scale(nullable: false)

method(nullable: false)

}

String toString(){

"${code} ${longName}"

}

}

The code inside the controler:

 def autoCompleteList = {

def loincAutoCompleteService = new LOINCAutoCompleteService()

render loincAutoCompleteService.loincList(params) as JSON

}

The service:

class LOINCAutoCompleteService {

def loincList(params) {

// Creates a new query Object

def query = {

or {

like("code", "${params.term}%") // term is the parameter send by jQuery autocomplete

like("longName", "${params.term}%")

like("shortName", "${params.term}%")

}

projections { // good to select only the required columns.

property("id")

property("code")

property("longName")

}

}

def loincSelectList = [] // aArray List to add each Loinc details

def clist = LOINC.createCriteria().list(query)

clist.each {

// Add to map. jQuery autocomplete expects the JSON object to be with id/label/value

def loincMap = [:]

loincMap.put("id", it[0])

// Label is text showed int he drop-down list

loincMap.put("label", it[1] + " : " + it[2])

// Values is the code to be returned when the user select an item from drop-down list

loincMap.put("value", it[1] + " : " + it[2])

// Add the row to the array list

loincSelectList.add(loincMap)

}

return loincSelectList

}

}

I want something like that inside the domain class:

<code>

static autocompleteAble = {

fields = ["code", "longName"]

}

</code>

Then access this array from the src/templates/scaffolding/renderEditor.template in order to get the field names (code and longName) and generate the correct html code in _forms.gsp and fix the problem.

Other solution? Any Ideas?

Many Thanks in Advance. ... and excuse my bad English.

网友答案:

Reading and testing and testing again ... I have found the answer, follow the steps:

  1. Add the following code to the Domain class:

    class DomainClass { String codeField; String descriptionField; static autoCompleteConfig = ["codeField", "descriptionField"] }

  2. Change the src/templates/scaffolding/renderEditor.template (only the renderManyToOne method):

    private renderManyToOne(domainClass, property) {

        def AUTOCOMPLETE_PROPERTY = "autoCompleteConfig"
        def className = property.type.name
    
        def autoCompleteProperty = org.codehaus.groovy.grails.commons.GrailsClassUtils.getStaticPropertyValue(property.referencedDomainClass.clazz, AUTOCOMPLETE_PROPERTY)
        def sb = new StringBuilder()
        // sb << "\n<!-- getFullName(): " <<   domainClass.getFullName() << " property.type.name: " << property.type.name << " property.referencedDomainClass.propertyName: " << property.referencedDomainClass.propertyName <<  "     property.referencedDomainClass: " << property.referencedDomainClass <<  " -->\n"
        if (autoCompleteProperty != null) {
    
            if (autoCompleteProperty[0] ) {
                if (property.association) {
    
                    // hidden field for domain.id
    
                    sb << '<input type=\"hidden\" '
                    sb << ' id=  "' << property.name << '.id"'
                    sb << ' name="' << property.name << '.id"'
                    sb << ' value="${' << "${domainInstance}" << '?.id}" '
                    sb << '/>\n'
    
                    // Text field to show the description generated by autocomplete
                    sb << '\t<input type=\"text\" '
                    sb << ' id=  "' << property.name  << '_' << (autoCompleteProperty[1]? autoCompleteProperty[1]:'Description')  << '\" '
                    sb << ' name="' << property.name  << '_' << (autoCompleteProperty[1]? autoCompleteProperty[1]:'Description')  << '\" '
                    if (isRequired()) sb << ' required="" '
                    sb << 'style=\"width: 600px;\" '
    
                    sb << ' value="${'
                    sb << "${domainInstance}?.${property.name}" << '?.' << autoCompleteProperty[0] << '}' << (autoCompleteProperty[1]? '' : '"' )
    
                    if (autoCompleteProperty[1]) {
                        sb << ': ${' << "${domainInstance}?.${property.name}" << '?.' << autoCompleteProperty[1] << (autoCompleteProperty[2]? '}' : '}"' )
                    }
    
                    if (autoCompleteProperty[2]) {
                        sb << ': ${' << "${domainInstance}?.${property.name}" << '?.' << autoCompleteProperty[2] << '}"'
                    }
    
    
                    sb << ' />'
    
                    def js = new StringBuilder()
                    js << '''
                    <script type="text/javascript">
    
                        /*
                         * Remember include jquery and jquery-ui libraries into head section of edit.gsp file
                         *   < g:javascript library="jquery"/>
                         *   < g:javascript library="jquery-ui"/>
                         *
                         */
    
                        \$(document).ready(function() {
                    '''
                       js << '\t\$("#' << property.name << '").focus(function(){this.select(); });\n'
    
                       js << '\t\t\t\t\t\t' // Tabs to sort the output
                       js << '\$("#' << property.name  << '_' << (autoCompleteProperty[1]? autoCompleteProperty[1]:'Description')  << '").autocomplete({\n'
    
                       js << '''
                                  source: function(request, response){
                                      \$.ajax({
                                          // Define Remote datasource into the controller
                       '''
                       js << '            \t\t url: "'
                       js << '/' << grails.util.Metadata.current.'app.name' << '/' << property.name << '/' << 'autoCompleteList",'
    
                       js << '''
                                          data: request,
                                          success: function(data){
                                              // Get the response (JSON format)
                                              response(data);
                                          },
                                          error: function(){
                                              // Handle server errors
                                              response("Error after search records. Try Again.")
                                          }
                                      });
                                  },
                                  // General options: Triggered only after minimum 2 characters have been entered and others
                                  minLength: 2,
                                  delay: 1,
                                  autoFocus: true,
                                  // Event handler when user choose un item from the list.
                                  select: function(event, ui) {
                                      // update the hidden field.
                       '''
                       js <<  '\t\t\t\t  '
                       js << '\$("#' << property.name << '\\\\.id").val(ui.item.id);'
    
                       js << '''
    
                                  }
                            });
                       });
                    </script>
                    '''
                    sb << js
                    sb as String
    
    
                }
            }
        } else {
    
    
            sb << '<g:select'
            // id is "x" and name is "x.id" as the label will have for="x" and "." in an id will confuse CSS
            sb << ' id="' << property.name << '"'
            sb << ' name="' << property.name << '.id"'
            sb << ' from="${' << property.type.name << '.list()}"'
            sb << ' optionKey="id"'
            if (isRequired()) sb << ' required=""'
            sb << ' value="${' << "${domainInstance}?.${property.name}" << '?.id}"'
            sb << ' class="many-to-one"'
            sb << renderNoSelection(property)
            sb << '/>'
            sb as String
    
    
        }
    
    }
    
  3. Add the jquery libraries to src/templates/scaffolding/edit.gsp. Remember to install the jquery plugin:

  4. Write your own autoCompleteRoutine inside the Domain controller, something like:

    def autoCompleteList = { def domainAutoCompleteService = new DomainAutoCompleteService() render domainAutoCompleteService.domainList(params) as JSON }

  5. Write your own domainAutoCompleteService, something like:

    package packageName

    // Change the words "Domain" and "domain" with your own Domain class name
    class DomainAutoCompleteService {
    
        def domainList(params) {
    
            // Creates a new query Object
            def query = {
                or {
                    // term is the parameter send by jQuery autocomplete
                    like("codeField", "${params.term}%") 
                    like("descriptionField", "${params.term}%")
                    like("otherField", "${params.term}%")
                }
                projections { // good to select only the required columns.
                    property("id")
                    property("codeField")
                    property("descriptionField")
                }
            }
    
            def domainSelectList = [] 
            // Replace the word "Domain" by your own domain Name
            def clist = Domain.createCriteria().list(query)
    
    
            clist.each {
                // Add to map. jQuery autocomplete expects the JSON object to be with id/label/value
                def map = [:]
    
                map.put("id", it[0])
    
                // Label is text showed int he drop-down list
                map.put("label", it[1] + " : " + it[2])
    
                // Values is the code to be returned when the user select an item from drop-down list
                map.put("value", it[1] + " : " + it[2])
    
                // Add the row to the array list
                domainSelectList.add(map)
            }
    
    
            return domainSelectList
    
        }
    }
    
  6. Generate the views .... and voila! All is working.

Any Comments? I think can be more elegant but is the first step ...

分享给朋友:
您可能感兴趣的文章:
随机阅读: