Home > Grails, Groovy > Grails i Ajax – pierwsze starcie

Grails i Ajax – pierwsze starcie

Marzec 25th, 2009

Naukę Grails prowadzę wielowątkowo. Lektura dokumentacji, przeglądanie wybranych rozdziałów książek, eksperymenty z poszczególnymi komponentami na testowym projekcie oraz próba sił w dwóch konkretnych projektach. Tym razem mała relacja z próby użycia Ajax'a z Grails.

W swojej aplikacji chciałem zrobić Ajax'owe zarządzanie kategoriami (lista, dodawanie i usuwanie). Na początek utworzyłem sobie klasę domenową:

 
class Category {
    String name
}
 

oraz kontroler z pustą akcją index. Do nagłówka strony dodałem:

 
<g:javascript library="prototype" />
 

Prototype i scriptaculous (bazujący na prototype) są domyślnie dołączone do Grails. Podobno korzystając z pluginów łatwo można wykorzystać dowolną inną bibliotekę - jeszcze nie sprawdzałem.

W pliku index.gsp wstawiłem następujący fragment kodu:

 
<div class="form">
  <g:javascript>
    function clearField(name) {
      document.getElementById(name).value = '';
    }
  </g:javascript>
  <g:formRemote name="addCategory" url="[controller:'categories', action:'add']" update="[success:'categories', error:'message']" onSuccess="clearField('name')">
<input type="text" name="name" id="name"><br/>
<input type="submit" value="Dodaj kategorię" action="add"/>
  </g:formRemote>
</div>
<div class="list" id="categories">
  <g:include controller="categories" action="list"/>
</div>
 

Można tu zauważyć trzy elementy. g:javascript, który zawiera prościutki kawałek JS do czyszczenia pola formularza. g:formRemote to specjalny formularz używany do wysyłania żądań korzystając z Ajax'a. Posiada artybuty określające jakie pole zaktualizować po odebraniu odpowiedzi oraz gdzie żądanie powinno być wysłane. g:inlude wstawia nam efekt działania akcji list kontrolera categories (chcemy inicjalnie tą listę wyświetlić).

Kontroler nie należy do zbytnio skomplikowanych:

 
import Category
 
class CategoriesController {
    def index = {
    }
 
    def add = {
        new Category(params).save()
        redirect(action:"list")
    }
 
    def delete = {
        Category.get(params.id).delete()
        redirect(action:"list")
    }
 
    def list = {
        render(template:"list", model:[categories:Category.list(sort:"name")])
    }
}
 

Akcja index po prostu wyświetla nasz widok. Akcja list renderuje nam szablon list z zapodanym modelem (do zmiennej categories przypisujemy listę kategorii posortowaną po nazwie - to znacie z serii postów o GORM). Akcja add tworzy i zapisuje nową kategorię na podstawie parametrów żądania (dzięki Jacek za wskazanie takiego sposobu tworzenia obiektów) i przekierowuje na wyświetlenie listy. Akcja delete usuwa kategorię i też przekierowuje na wyświetlenie aktualnej listy.

Brakuje nam tylko szablonu list (szablony tworzymy jako pliki GSP z nazwą zaczynającą się od podkreślenia, np. _list.gsp). Szablon wygląda tak:

 
<g:each in="${categories}" var="category">
    ${category.name}
    <g:remoteLink controller="categories" action="delete" id="${category.id}" update="categories">Usuń</g:remoteLink>
    <br/>
</g:each>
 

Na początku miałem małą zagwozdkę, jak inicjalnie wczytać listę i później ją aktualizować. Miałem akcję list w kontrolerze, a akcja index dodatkowo też wczytywała kategorię. Nie podobało mi się to podwojenie logiki, a tak g:render nie rozwiązywał mojego problemu. Jednak po chwili znalazłem tag g:include.

Pytanie za 100 pkt.: dlaczego w kontrolerze umieściłem linijkę "import Category", skoro i klasa domenowa i kontroler są w tym samym pakiecie? Jako podpowiedź polecam zajrzeć na blogi Jacka i Chlebika (nie napotkali tego problemu, ale pisali, co i dlaczego warto w projektach zawsze stosować).

Jeśli ktoś chce przyjrzeć się źródłom to dostępne są na GitHubie: http://github.com/matixo/wydatki/tree/master

Grails, Groovy

  1. No comments yet.
  1. No trackbacks yet.