2010-05-10 11 views
7

Mój problem polega na tym: Chcę utworzyć instancję domeny grails, definiując "wiele" wystąpień innej domeny, która ma. Mam rzeczywiste źródło w Google Code Project, ale poniższe powinny zilustrować problem.Grails - Simple hasMany Problem - Używanie CheckBoxów zamiast HTML Wybierz w create.gsp

class Person { 
    String name 
    static hasMany[skills:Skill] 

    static constraints = { 
    id (visible:false) 
    skills (nullable:false, blank:false) 
    } 
} 

class Skill { 
    String name 
    String description 

    static constraints = { 
    id (visible:false) 
    name (nullable:false, blank:false) 
    description (nullable:false, blank:false) 
    } 
} 

Jeśli używasz tego modelu i def scaffold dla dwóch kontrolerów wtedy skończyć z postaci jak to, że nie działa;

Scaffolding

Moja własna próba uzyskania to do pracy wylicza umiejętności wyboru i wygląda następująco;

Custom Create.gsp

Ale kiedy zapisać wolontariusza umiejętności są nieważne!

Failed to save Skills

Jest to kod dla mojego ratowania metodą;

def save = { 
    log.info "Saving: " + params.toString() 
    def skills = params.skills 
    log.info "Skills: " + skills 
    def volunteerInstance = new Volunteer(params) 
    log.info volunteerInstance 
    if (volunteerInstance.save(flush: true)) { 
     flash.message = "${message(code: 'default.created.message', args: [message(code: 'volunteer.label', default: 'Volunteer'), volunteerInstance.id])}" 
     redirect(action: "show", id: volunteerInstance.id) 
     log.info volunteerInstance 
    } 
    else { 
     render(view: "create", model: [volunteerInstance: volunteerInstance]) 
    } 
} 

To jest moje wyjście dziennika (mam niestandardowe metody toString());

2010-05-10 21:06:41,494 [http-8080-3] INFO bumbumtrain.VolunteerController - Saving: ["skills":["1", "2"], "name":"Ian", "_skills":["", ""], "create":"Create", "action":"save", "controller":"volunteer"] 

2010-05-10 21:06:41,495 [http-8080-3] INFO bumbumtrain.VolunteerController - Skills: [1, 2] 

2010-05-10 21:06:41,508 [http-8080-3] INFO bumbumtrain.VolunteerController - Volunteer[ id: null | Name: Ian | Skills [Skill[ id: 1 | Name: Carpenter ] , Skill[ id: 2 | Name: Sound Engineer ] ]] 

Należy zauważyć, że w końcowej linii dziennika odpowiednie umiejętności zostały zebrane i są częścią instancji obiektu. Po zapisaniu wolontariusza "Umiejętności" są ignorowane i nie są przypisywane do bazy danych, mimo że w utworzonej pamięci wyraźnie widać elementy. Czy nie jest możliwe przekazanie Umiejętności w czasie budowy? Musi być na to sposób? Potrzebuję jednego formularza, aby umożliwić rejestrację, ale chcę znormalizować dane, aby móc później dodać więcej umiejętności.

Jeśli uważasz, że powinno to po prostu zadziałać, link do działającego przykładu byłby świetny.

Jeśli używam HTML Select, to działa dobrze! Takich jak poniżej, aby utworzyć stronę Utwórz;

<tr class="prop"> 
<td valign="top" class="name"> 
    <label for="skills"><g:message code="volunteer.skills.label" default="Skills" /></label> 
</td> 
<td valign="top" class="value ${hasErrors(bean: volunteerInstance, field: 'skills', 'errors')}"> 
    <g:select name="skills" from="${uk.co.bumbumtrain.Skill.list()}" multiple="yes" optionKey="id" size="5" value="${volunteerInstance?.skills}" /> 
</td> 
</tr> 

Ale potrzebuję go do pracy z wyboru, takie jak ten;

<tr class="prop"> 
<td valign="top" class="name"> 
    <label for="skills"><g:message code="volunteer.skills.label" default="Skills" /></label> 
</td> 
<td valign="top" class="value ${hasErrors(bean: volunteerInstance, field: 'skills', 'errors')}"> 
    <g:each in="${skillInstanceList}" status="i" var="skillInstance"> 
     <label for="${skillInstance?.name}"><g:message code="${skillInstance?.name}.label" default="${skillInstance?.name}" /></label> 
             <g:checkBox name="skills" value="${skillInstance?.id.toString()}"/> 
    </g:each> 
</td> 
</tr> 

Wyjście dziennika jest dokładnie takie samo! W obu wersjach formularza instancja wolontariusza jest tworzona razem z umiejętnościami prawidłowo wymienionymi w zmiennej "umiejętności". Podczas zapisywania ten ostatni kończy się niepowodzeniem z zerowym odwołaniem do odwołania, jak pokazano na górze tego pytania.

Mam nadzieję, że to ma sens, z góry dzięki!

Gav

Odpowiedz

5

zastąpić stworzyć.GSP Kod<g:checkbox...> przez:

<g:checkBox name="skill_${skillInstance.id}"/> 

Następnie wewnątrz działania kontrolera save wymienić def volunteerInstance = new Volunteer(params) przez:

def volunteerInstance = new Volunteer(name: params.name) 
params.each { 
    if (it.key.startsWith("skill_")) 
    volunteerInstance.skills << Skill.get((it.key - "skill_") as Integer) 
} 

powinno działać. (kod nie testowany)

+0

ratownik! To był taki ból głowy. – gav

+0

Witaj :-) – fabien7474

+0

Mała optymalizacja - it.key.startsSith jest lepszy niż it.key.contains – mmigdol

3

Chciałbym, aby czytelnik wysłał listę identyfikatorów twojego ma wiele elementów, ponieważ można to łatwo przypisać domyślnie w Grails. Twój .gsp powinna wyglądać następująco:

<g:each in="${skills}" var="skill"> 
      <input type="checkbox" 
        name="skills" 
        value="${skill?.id}" 
      </g:each> 

i kontrolera można po prostu przechowuje wartość tak:

person.properties = params 
person.validate() 
person.save() 

Jest to dość proste, prawda? :-)

+0

Jeśli Twój formularz zawiera błąd, Twoje rozwiązanie nie będzie działać. Musisz sprawdzić, czy twoje polecenie - osoba - zawiera każdy element kolekcji referencyjnej - umiejętności - i jeśli tak, zaznacz to pole wyboru jako zaznaczone. Ponadto wywołanie metody składowania na klasie domeny wywoła najpierw funkcję sprawdzania poprawności, więc możesz również użyć polecenia save, aby sprawdzić klasy domeny. –

3

Grails nie zapewnia obsługi powiązania danych, gdy używasz pola wyboru i chcesz powiązać ze sobą ToMany. Przynajmniej do wersji 2.2.0

Obejście problemu?

1º opcja - Napisz GSP kodu, który zachowuje się jak wybierz składnik

<g:each var="skillInstance" in="${skillInstanceList}"> 
    <div class="fieldcontain"> 
     <g:set var="checked" value=""/> 
     <g:if test="${volunteerInstance?.skills?.contains(skillInstance)}"> 
      <input type="hidden" name="_skills" value="${skillInstance?.id}"/> 
      <g:set var="checked" value="checked"/> 
     </g:if> 
     <label for="${skillInstance?.name}"> 
      <g:message code="${skillInstance?.name}.label" 
         default="${skillInstance?.name}" /> 
     </label> 
     <input type="checkbox" name="skills" value="${skillInstance?.id}" 
       ${checked} /> 
    </div> 
</g:each> 

Stwórz własną TagLib

/** 
    * Custom TagLib must end up with the TagLib suffix 
    * 
    * It should be placed in the grails-app/taglib directory 
    */ 
class BindingAwareCheckboxTagLib { 

    def bindingAwareCheckbox = { attrs, body -> 
     out << render(
        template: "/<TEMPLATE_DIR>/bindingAwareCheckboxTemplate.gsp", 
        model: [referenceColletion: attrs.referenceColletion, 
          value:attrs.value]) 
    } 

} 

Gdzie <TEMPLATE_DIR> powinny być w stosunku do katalogu /grails-app/views . Ponadto szablony powinny być poprzedzone prefiksem _.

Teraz można użyć niestandardowego TagLib następująco

<g:bindingAwareCheckbox 
     referenceCollection="${skillInstanceList}" 
     value="${volunteerInstance?.skills}"/> 

Gdy to zrobisz, proces wiązania nastąpi automatycznie. Brak dodatkowego kodu.

+0

To miłe obejście. Zwróć uwagę, że powiązanie działa automatycznie tylko wtedy, gdy relacja jest ustawiona za pomocą 'hasMany' - jeśli jest to prosta lista' List skills' wiązanie nie zostanie wykonane dla ciebie. – elias

+0

@elias Tak, możesz.Nie czekam na Grails, kiedy mogę stworzyć własne obejście. Użyj BindEventListener i obiektu funkcji z Google Guava, aby przekonwertować żądanie do listy. Zobacz http://stackoverflow.com/a/13538222 i http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Function.html –

+0

Przepraszam, ja oznaczało to, że nie zrobiono tego automatycznie dla ciebie, nie, że nie można tego zrobić. Ale fajnie jest zobaczyć, że można jeszcze bardziej dostosować oprawę, nie zdawałem sobie z tego sprawy - dzięki za wskazówki. =) – elias

0

GSP

<g:checkBox name="skills" value="${skillInstance.id}" checked="${skillInstance in volunteerInstance?.skills}"/> 

Groovy

def volunteerInstance = new Volunteer(params).save()  
def skills = Skill.getAll(params.list('skills')) 
    skills.each{ volunteerInstance.addToSkills(it).save() }