30 marca 2009

Rozdział 8. o wsparciu dla Ajax w Grails z DGG2

Rozdział 8. w książce "The Definitive Guide to Grails, Second Edition" przedstawia wsparcie dla technologii Ajax w Grails.

Działanie technologii Ajax opiera się na wykorzystaniu klasy XMLHttpRequest jako kontrolki ActiveX lub bezpośrednio wspierany obiekt JavaScript. Ukrycie różnego wsparcia przez przeglądarki to zadanie dla szkieletów ajaksowych, np. Prototype czy Dojo Toolkit. Podobno pracuje się nad standardem, który ujednoliciłby pracę z różnymi szkieletami ajaksowymi (podobnie do JPA czy JDBC), ale póki co prace trwają. W Grails domyślnie dostarczanymi szkieletami ajaksowymi są Prototype oraz Scriptaculous, a za pomocą mechanizmu wtyczek można dodać inne, np. Yahoo UI (yui lub yui3), Dojo (dojo), Ext-JS (ext) i jQuery (jquery).

Grails ma dedykowane znaczniki ajaksowe w GSP do tworzenia odnośników, formularzy i pól tekstowych, np. g:remoteLink (wykonanie akcji synchronicznie).

Pracę z biblioteką ajaksową rozpoczynamy od jej wskazania przez <g:javascript library="prototype" /> w sekcji <head>. W wyniku zależności biblioteki ajaksowej zostaną dołączone do strony.

Konstrukcja (Listing 8-4):
 <g:remoteLink controller="timer" action="showTime" update="time">
Aktualny czas
</g:remoteLink>
<div id="time"></div>
spowoduje, że każdorazowe wciśnięcie łącza "Aktualny czas" spowoduje wykonanie akcji showTime (aktualnego) kontrolera (atrybut action), którego wynik zostanie wyświetlony w elemencie DOM o identyfikatorze time (atrybut update) - w tym przypadku będzie to obszar div.

Zmiana biblioteki to instalacja jej wtyczki grailsowej (polecenie grails install-plugin) oraz jej wskazanie przez g:javascript. Nie trzeba nawet zatrzymywać aplikacji!

Znacznik GSP - g:formRemote - to udoskonalona wersja g:form do zatwierdzenia formularza jako żądanie ajaksowe. Przy migracji g:form na f:formRemote należy pamiętać, aby odpowiedź z kontrolera nie była pełną odpowiedzią (pełną stroną), a jedynie jej wycinkiem (szablonem w nomenklaturze Grails), który wpasujemy w resztę bieżącej strony. Główną różnicą między stronami a szablonami to nazwa pliku rozpoczynająca się od podkreślnika.

Dla przypomnienia: jeśli podzielimy części strony na szablony to złożenie ich w całość w postaci pojedynczej strony jest możliwe z wykorzystaniem znacznika g:render z atrybutem template, np. (Listing 8-6):
 <div id="loginBox">
<g:render template="/user/loginForm" />
</div>
Tak przygotowany szablon możemy wykorzystać jako odpowiedź w zapytaniu ajaksowym.

Odpowiedź na zatwierdzenie formularza z wykorzystaniem Ajax trafia do elementu DOM (strony) wskazanego przez atrybut update znacznika g:formRemote, np. (Listing 8-7):
 <g:formRemote name="loginForm" url="[controller:'user','action:'login']" 
update="loginBox">
...
</g:formRemote>
Wyświetlenie wycinka strony (szablonu) to wskazanie go w akcji kontrolera przez metodę render z atrybutem template (zamiast dotychczasowego view), np. (Listing 8-9):
 def login = {
...
render(template:'welcomeMessage')
}
Każdy ze znaczników ajaksowych w Grails wspiera dwa atrybuty: before i after, za pomocą których możliwe jest umieszczenie dowolnego kodu JavaScript, który będzie wykonany, odpowiednio, przed i po żądaniu ajaksowym. Kod JavaScript w atrybucie after zostanie wykonany bez względu na to, czy żądanie zakończyło się pomyślnie, czy nie, a zaraz po wysłaniu żądania (innymi słowy: nie mylić z onComplete!).

Do obsługi zdarzeń ajaksowych wykorzystujemy atrybuty onSuccess, onFailure, onLoaded, onComplete oraz on<KOD_BŁĘDU> znaczników ajaksowych w GSP, np. (Listing 8-12):
 <g:formRemote ... onLoading="showProgress();" 
onComplete="hideProgress();">
...
</g:formRemote>
Podczas prezentacji obsugi Ajax w Grails autorzy wykorzystują Amazon Associates Web Services do zdalnego pobrania prezentowanych w aplikacji okładek płyt. Ponownie prezentowane są te cechy klas usługowych w Grails, które powodują, że są one idealne do realizacji tego zadania. Nie obsługują interakcji przeglądarka-aplikacja, a jedynie zawierają pewną funkcjonalność (logikę) biznesową systemu, którą można współdzielić w wielu innych miejscach przez wykorzystanie mechanizmu wstrzeliwania zależności (ang. dependency injection). Domyślnie klasy usługowe są transakcyjne, tj. wszystkie publiczne metody są opakowane w transakcję zarządzaną przez Spring Framework, co sprawia, że operacje bazodanowe są właściwie izolowane i gwarantuje się ich atomowość. Wyłączenie transakcji w klasie usługowej to ustawienie statycznego atrybutu transactional na false, tj.
 static transactional = false
Ciekawostką, o której nie pamiętałem w związku z klasami usługowymi, jest możliwość konfiguracji ich atrybutów za pomocą konfiguracji w pliku grails-app/conf/Config.groovy. Technika nosi nazwę konfiguracji nadpisywania atrybutów (ang. property override configuration), a jest grailsową realizacją funkcjonalności znanej z Spring Framework. Każda klasa usługowa w Grails jest ziarnem springowym typu Singleton. Nazwa ziarna wyliczana jest na podstawie nazwy klasy. Blok beans w Config.groovy służy do ich konfiguracji, np. (Listing 8-18):
 beans {
albumArtService {
accessKeyId = "ciąg znaków będący kluczem dostępowym do serwisu Amazon"
}
}
Możliwe jest określenie wartości poszczególnych klas usługowych per środowisko, jak to ma miejsce w innych plikach konfiguracyjnych Grails, które są niczym innym jak skryptami Groovy.

Przy prezentacji rozwiązania, pojawia się również wzmianka o metaprogramowaniu w Grails, a w zasadzie Groovy, gdzie poznajemy sposób przełonięcia wykonania metody udostępnianej przez klasę dostarczaną w bibliotece Amazonu. Wystarczy skorzystać z atrybutu metaClass i przypisać domknięcie, aby dotychczasowa implementacja została zastąpiona naszą. Bardzo użyteczny mechanizm podczas testowania, kiedy klasa uczestnicząca w testach kontaktuje się ze światem zewnętrznym, a chcielibyśmy tego uniknąć.

Istnieje jeszcze jeden plik konfiguracyjny, w którym definiujemy ziarna springowe do użycia w naszej aplikacji grailsowej - grails-app/conf/spring/resources.groovy. Wszystkie zdefiniowane ziarna springowe w tym pliku mogą być wstrzeliwane do klas obsługiwanych przez mechanizm wstrzeliwania zależności w Grails - kontrolery, klasy usługowe i klasy znaczników GSP. Wystarczy, więc następująca konstrukcja, aby stworzyć ziarno albumArtCache, które reprezentuje definicję ziarna EhCache (Listing 8-21):
 beans = {
albumArtCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
timeToLive = 300
}
}
Funkcjonalność uzupełniania wprowadzanego tekstu lub automatycznego wyszukiwania można zrealizować w Grails przy pomocy <g:remoteField>, który przesyła swoją wartość przy każdorazowej zmianie. Atrybut paramName określa nazwę parametru żądania, jaki będzie wysłany do akcji kontrolera (atrybut url z parametrami controller i action), np. (Listing 8-30):
 <g:remoteField name="searchBox" update="musicPanel" paramName="q" 
url="[controller:'store',action:'search']" />
Domyślna nazwa parametru, w którym jest wprowadzana wartość, to value.

Wspomina się jeszcze o funkcjonalności wyszukiwania z pomocą wtyczki Searchable opartej na projektach Compass oraz Apache Lucene, aby na zakończenie zaprezentować, jak to nazwali autorzy, "a compelling use of Groovy closures to deal with exceptions" (Listing 8-32):
 def search = {
...
def searchResults = [albumResults: trySearch { Album.search(q, [max:10]) } ]
}

def trySearch(Closure callable) {
try {
return callable.call()
} catch (Exception e) {
log.debug "Search Error: ${e.message}", e
return []
}
}
Niezłe, co?! Kolejny rozdział niemniej interesujący i niezwykle obszerny "Creating Web Flows", który dotyczy integracji Spring Web Flow z Grails. Prawie 50 stron czystej wiedzy.