Lista zadań w Grails
W poprzednim numerze JAVA exPress przedstawiłem sposób na rozpoczęcie pracy z Grails, jak zainstalować środowisko, jak utworzyć pierwszy projekt, pierwsze klasy domenowe z ograniczeniami oraz jak wykorzystać dynamiczny scaffolding. Teraz zajmiemy się utworzeniem prostej listy zadań w Grails od podstaw. Zobaczymy jak zbudować kontroler, wykorzystać podstawowe funkcjonalności GORM i zbudować własne widoki – w skrócie CRUD ręcznie, bez generowania.
Utworzenie projektu i klasy domenowej
Na początek tworzymy nowy projekt o nazwie ToDo. Jak pamiętamy z poprzedniego artykułu jest to wszystko co tak naprawdę trzeba zrobić, aby rozpocząć pracę z projektem Grails – HSQLDB oraz Jetty w paczce z Grails są już gotowe do pracy. Tworzymy naszą klasę domenową o nazwie Task
. Nasze zadanie będzie miało temat, priorytet, maksymalny termin wykonania oraz flagę oznaczającą, czy zadanie jest wykonane czy też nie.
class Task { String subject Date dueDate Boolean completed static constraints = { } }
Utworzenie kontrolera
W kolejnym kroku tworzymy kontroler Task
(kontroler, jak i klasę domenową, możemy utworzyć korzystając z menu kontekstowego projektu lub wywołując odpowiednią komendę z linii poleceń). Definiujemy w nim następujące metody: list
, edit
, save
, delete
. Bedą one odpowiednio odpowiedzialne za: wyświetlenie listy zadań, wyświetlenie formularza edycji/dodawania rekordu, zapisanie rekordu (nowego lub zmienionego) oraz usunięcie rekordu.
Pobranie rekordów
Na początku zajmijmy się akcją list. W Grails przekazanie danych z kontrolera do widoku odbywa się przez zwrócenie z akcji mapy. Klucze mapy będą nam odpowiednio reprezentowały zmienne dostępne w widoku. Nasza akcja może zatem wyglądać następująco:
def list = { [tasks:Task.findAll()] }
Nawiasy kwadratowe oznaczają tutaj utworzenie mapy. Pod kluczem tasks znajdzie się lista zadań pobranych z bazy. Jest tutaj widoczna jeszcze jedna cecha Groovy – brak słowa kluczowego return. Domyślnie zwracany jest wynik ostatnio wykonanej instrukcji, w naszym wypadku utworzona mapa.
Do pełni szczęścia potrzeba nam jeszcze pliku GSP, na którym wyświetlimy nasze zadania. W widoku projektu zaznaczamy Views and Layouts -> task i z menu kontekstowego wybieramy New -> GSP File. Jako nazwę podajmy list. Grails wyznaje zasadę konwencji ponad konfigurację, tak więc plik widoku nazywa się tak samo jak nazwa akcji. Oczywiście w miare potrzeb można wyrenderować dowolny inny widok, jednak nam na razie wystarczy to, co dostajemy z paczki.
Do naszego widoku dodajemy następujący fragment kodu:
<g:each in="${tasks}" var="task"> ${task} </g:each>
Wykorzystujemy tutaj tag dostarczany razem z Grails pozwalający na przeiterowanie po kolekcji elementów w widoku. Atrybut in
określa zmienną po jakiej będziemy iterować. Atrubut var
określa, jak w kolejnych iteracjach będzie nazywał się nasz element. Bez podania tego atrybutu będzie się on domyślnie nazywał it
, tak jak nazwa domyślnego parametru w domknięciach w Groovy. Można również podać atrybut status, który będzie określał nazwę zmiennej przechowującej licznik w kolejnych iteracjach (przydatne przy tworzeniu numerowanych list, itp.).
Na tym etapie można by się pokusić o uruchomienie projektu i sprawdzenie działania, jednak nie mamy jeszcze żadnych rekordów, ani widoku do ich dodawania.
BootStrap.groovy
Z pomocą przyjdzie nam tutaj plik BootStrap.groovy. Zawiera on sekcję init
, która jest wykonywana podczas startu aplikacji. Możemy w niej dodać następujący fragment kodu:
new Task(subject:"Pranie", priority:1, dueDate:new Date(), completed:false).save()
Utworzy on nam i zapisze w bazie jedno zadanie. Przykład ten pokazuje również w jaki sposób można tworzyć instancje obiektów przekazując wartości dla poszczególnych właściwości – to kolejna przyjazna cecha Groovy. Grails daje również możliwość utworzenia instancji klasy domenowej z automatycznym przypisaniem wartości parametrów żądania do właściwości obiektu. Wykorzystamy to później przy zapisywaniu nowego zadania.
Po takiej modyfikacji pliku BootStrap nasza aplikacja powinna już wyświetlać pierwsze zadanie na liście.
Dodawanie i edycja rekordu
Dodawanie i edycję rekordu wykonamy korzystając ze wspólnego widoku. Gdy nie będzie podanego identyfikatora, wyświetlimy pusty formularz w celu dodania nowego zadania. W przypadku, gdy identyfikator zostanie podany, przed wyświetleniem formularza wczytamy odpowiedni rekord w celu umożliwienia jego edycji. Zapis rekordów wykonamy we wspólnej metodzie save
.
Na początek przygotujemy sobie widok edit
(analogicznie do list
). Nie musimy na razie nic wpisywać w kontrolerze w akcji edit
, gdyż póki co chcemy tylko dodać nowy rekord, więc wystarczy nam wyświetlenie pustej formatki. Może ona wyglądać następująco:
<g:form name="addForm" action="save" id="${task?.id}"> Subject: <g:textField name="subject" value="${task?.subject}"/><br/> Due Date: <g:datePicker name="dueDate" value="${task?.dueDate?:(new Date())}"/><br/> Completed: <g:checkBox name="completed" value="${task?.completed}"/><br/> <g:submitButton name="save" value="Save" /> </g:form>
Pojawia się tutaj nowy tag OD. Służy on do budowania formularzy HTML, a wraz z nim widzimy również g:textField
, g:datePicker
, g:checkBox
oraz g:submitButton
. Zachęcam do zapoznania się z dokumentacją w zakresie możliwych atrybutów do skonfigurowania dla tych znaczników. W tym fragmencie kodu pojawiają się również dwie ciekawostki z Groovy: operator "OD" - safe navigation operator, który zapobiega wyjątkowi NullPointerException
(jeśli operand z lewej jest null
, to zamiast NullPointerException
zostanie po prostu zwrócony null
), oraz drugi operator "?:
", który jest małą odmianą swojego brata z Javy, operatora ternarnego (jeśli zmienna po lewej jest równa null
lub false
, to przypisywana jest do niej wartość z prawej, w przeciwnym wypadku wartość pozostaje bez zmian). Dzięki obsłużeniu wartości null
oraz wartości domyślnych nasz formularz może zarówno służyć do edycji, jak i dodawania nowych rekordów.
Nasz formularz wysyła dane do akcji save
, która zapisze nam nowy rekord:
def task = new Task(params) task.save() redirect(action:"list")
Akcja ta tworzy nowy obiekt klasy Task
na podstawie przekazanej mapy parametrów żądania (params
). Następnie zapisuje ten rekord i wykonuje przekierowanie na akcję wylistowującą nasze rekordy.
Dodajmy zatem odnośnik pozwalający na edycję rekordu oraz zmodyfikujmy akcję edit
tak, aby wczytywała rekord przed wyświetleniem formatki. Na początek widok list
:
<g:each in="${tasks}" var="task"> <g:link action="edit" id="${task.id}">${task}</g:link><br/> </g:each>
Wprowadziliśmy tutaj nowy tag g:link
pozwalający na tworzenie odnośników. Jak widać Grails w formularzach oraz odnośnikach domyślnie wspiera podawanie nazw kontrolerów, akcji oraz identyfikatorów. Trzymanie się tutaj konwencji narzuconej przez Grails bardzo ułatwia sprawę, jednak należy pamiętać, że w miarę chęci i potrzeb nad wszystkim mamy kontrolę.
Zmodyfikowana akcja edit wygląda w następujący sposób:
if (params.id) { [task:Task.get(params.id)] }
W momencie, gdy został podany parametr żądania o nazwie id
, to staramy się wczytać rekord task o właśnie takim identyfikatorze. Ciekawie wygląda tutaj instrukcja warunkowa. Zauważmy, że params to mapa zawierająca parametry żądania, które są typu String
. Jak w takim razie zadziałało to w warunku? To kolejna ciekawa cecha Groovy, tzw. Groovy Truth. W Groovy nie tylko typ boolean
może zostać użyty w warunkach. Wartość 0, pusty łańcuch, null
, pusta tablica lub mapa również traktowane są jak wartość negatywna w warunkach. Znacznie to upraszcza część operacji.
Pozostała nam do zmodyfikowania akcja save
. Dotychczas tworzyła ona nowy rekord na podstawie przekazanych parametrów, a teraz będzie musiała dodatkowo obsłużyć zmiany w istniejącym rekordzie:
def task if (params.id) { task = Task.get(params.id) task.properties = params } else { task = new Task(params) } task.save() redirect(action:"list")
Usuwanie rekordu
Pozostało nam usunięcie rekordu. W tym celu na widoku list
dodajemy nowy odnośnik przy każdym rekordzie przekierowujący na akcję delete
. Tym razem obejdziemy się bez pokazania kodu – każdy na pewno sobie poradzi.
A jak będzie wyglądała nasza akcja w kontrolerze? Zgodnie z oczekiwaniami, niezbyt skomplikowanie:
def task = Task.get(params.id) task.delete() redirect(action:"list")
Podobnie, jak w akcji save wczytujemy tutaj rekord na podstawie podanego parametru żądania. Następnie usuwamy go i wykonujemy przekierowanie na listę zadań.
Podsumowanie
W tym artykule przedstawiłem jak wykonać prostą aplikację Grails z wszystkimi operacjami CRUD. Tym razem nie korzystaliśmy ze scaffodingu i wszystko stworzyliśmy ręcznie. Nasza aplikacja nie zalizcza się do najbardziej rozbudowanych, ale jest już funkcjonalna, a co najważniejsze, jej wykonanie nie zajęło nam specjalnie dużo czasu. Poznaliśmy też podstawowe operacje GORM (Grails Object Relationship Mapping), które pozwoliły nam wczytać, zapisać i usuwać rekordy. Wiemy już również jak wygląda klasa domenowa, kontroler oraz widoki, czyli podstawowe składowe Grails. Jednocześnie poznaliśmy też parę ciekawostek Groovy, które może zachęcą niektórych do jego poznania (nie koniecznie w parze z Grails).
Co dalej
Powszechnie wiadomo, że samodzielne rozwiązywanie problemów poprawia zapamiętywanie. W związku z tym, w ramach samodzielnych ćwiczeń proponuję wykonanie następujących zadań:
- modyfikację listy zadań, tak aby była czytelniejsza i bardziej przyjazna dla oka;
- wprowadzenie walidacji danych (np. niepusty temat zadania). W ramach podpowiedzi: trzeba zadbać o sekcję
contraints
klasy domenowej oraz zapyrzjaźnić się z metodąvalidate()
. Dodatkowo przyjdzie nam z pomocą tagg:renderErrors
; - wprowadzić menu na stronie. W ramach podpowiedzi: można zmodyfikować układ strony, najlepiej przez dołączenie do niego oddzielnego elementu, którym będzie menu.
Kod źródłowy można znaleźć pod adresem: http://code.google.com/p/javaexpress-grails-todo/ . Być może ktoś z czytelników będzie chciał rozwinąć projekt. Powodzenia i miłej zabawy.
Odniosłem wrażenie, że artykuł napisany jest po łebkach :/ Wykonuje instrukcje krok po kroku i niestety pierwsze uruchomienie w moim przypadku kończy się błędem. I to jeszcze w NB w którym wszystko działa out of the box.
Udało mi się uruchomić przykład, ale dalej mam wrażenie, że niektóre punkty można napisać jaśniej (np. gdzie dokładnie utworzyć list.gsp).
Super, że się udało. W każdej chwili możesz skorzystać z grupy dyskusyjnej JAVA exPress. Na 100% znajdzie się ktoś, kto będzie Ci w stanie pomóc. Czasami ciężko jest trafić do każdego. Z jednej strony są ludzie tacy jak Ty, którzy potrzebują dokładnych instrukcji (co jest zrozumiałe w przypadku technologii której nie znasz), a z drugiej strony pojawiały się głosy (z tego co pamiętam to na dzone), że przykład jest zbyt uproszczony.
W przypadku dalszych problemów spróbuj napisać na naszą grupę dyskusyjną. Na 100% znajdą się osoby, co będą chciały Ci pomóc.
Spoko :) Dzięki za zainteresowanie. Jasne, że ciężko trafić do każdego, ale dobrze, że przynajmniej podejmuje się próbe. Fakt, że Grails nie znam zupełnie. No i wielkie szacun za JavaExpress!