Pisząc aplikacje w Rails 2 po wywołaniu zdalnej metody łatwo było
zaktualizować fragment strony. Wszystkie ajaxowe zapytania miały
zdefiniowane kilka callbacków: update, success, failure… Można było
napisać odpowiednie zdarzenia dla każdego z nich.
Gdy Rails 3 uwolniło się od tak pisanego zagnieżdżonego javascriptu
callbacki zniknęły. Cały kod javascriptowy został przeniesiony do
zewnętrznych plików, a informacja że dany link jest AJAXowy umieszczana
jest za pomocą tagów HTML5. Jak zatem aktualnie wygląda aktualizacja
fragmentu strony za pomocą AJAXA?
Zobrazujmy hipotetyczną aplikację, gdzie jest model Państwa i model
Miasta. Każde Państwo posiada kilka miast.
W naszym formularzu użytkownik musi wybrać z rozwijanych list oba te
elemnty. Dla ułatwienia chcielibyśmy aby lista miasta aktualizowała się
po wybraniu państwa w taki sposób aby na tej liście znalazły się miasta
tego państwa (ale zakręciłem :))
Mamy zatem modele:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Country has_many :cities end class City belongs_to :country end |
W formularzy powinniśmy teraz umieścić dwie listy rozwijane. Jedna dla
państwa druga dla miasta, jednak aby łatwiej później zaktualizować ten
fragment powinniśmy drugą listę umieścić w jakimś partialu.
1 2 3 4 5 |
<%= collection_select :country, Country.all, :id, :name, :include_blank => true %> <span id="city_select"> <%= render "city_select", :cities => [] %> </span> |
Jak widać lista rozwijana z miastami została przeniesiona do partiala city_select:
Do partiala jest także dostarczana lista cities która w początkowym
stadium jest pusta.
1 2 3 4 |
<% unless cities.empty? %> <%= collection_select :city, cities, :id, :name %> <% end %> |
Widać, że lista jest wyświetlana tylko wtedy gdy lista miast nie jest
pusta. Jak widać w kodzie podanym troche wyżej, lista miast była
dostarczona pusta, zatem po załadowaniu się formularza lista miast nie
będzie widoczna. Teraz powinniśmy zrobić coś aby była widoczna.
Jak to zrobić? Po pierwsze, musimy napisać kawałek javascriptu, który
po zmianie wartości listy rozwijanej z państwami podmieni fragment z
miastami. Po drugie musimy napisać akcję w kontrolerze która
zaktualizuje listę wg id wybranego państwa. Trzecie musimy w routingach
powiedzieć, że taka akcja w kontrolerze istnieje.
Zacznijmy od javascriptu. Ja wykorzystuję w swojej pracy jQuery i tą
biblioteką posłużę się w przykładzie. W pliku aplication.js umieszczam
swój własny kawałek kodu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$(document).ready(function(){ $(function($){ # sprawdzamy czy select sie zmienia $("#item_country_id").change(function(){ var country = $("select#item_country_id :selected").val(); #zmienna country pobrana z listy Państw jako zaznaczony element if(country == "") country = "0" $.get('/cities/update_cities_list/'+country, function(data){ $("city_select").html(data); }) }); }); }); |
Kto chociaż trochę liznął jQuery wie co powyżej jest napisane. Dla tych
co nie wiedzą wyjaśniam.
jQuery nasłuchuje elementu o ID item_country_id
i gdy wartość jego
się zmieni to wywołuje funkcję, która: pobiera zaznaczony element i
przypisuje go do zmiennej country
. Gdy country
jest puste to
oznaczane jest jako 0. Następnie wywoływana jest akcja
/cities/update_cities_list/
z id wybranego państwa na końcu(zmienna country).
Po otrzymaniu wyniku jest on umieszczany wewnątrz elementu o ID
city_select
Pozostaje nam napisać metodę w kontrolerze cities i uzupełnić wpisu w
routes.rb
:
1 2 3 4 5 6 7 8 9 10 |
def update_cities_list cities = City.where(country_id => params[:id].order(:name)) unless params[:id].blank? render :partial => "items/city_select", :locals => {:cities => cities } end # routes: match 'cities/update_cities_list/:id', :controller => :cities, :action => :update_cities_list |
Podsumowanie
Po raz pierwszy na mur natrafiłem gdy użyłem observe_field
znany z
rails 2. Potem już poszło lawinowo – właściwie wszystko co wiemy o AJAX
w rails możemy wrzucić do kosza. No może nie wszystko, ale zdecydowaną
większość. Czy zmiany w rails 3 wyszły na lepsze? Pomimo początkowych
utrudnień wydaje mi się że tak. Cały javascript jest w jednym miejscu.
Kod dostarczany przez rails też jest w jednym miejscu i możemy sobie
wygodnie zmienić bibliotekę.
Trochę treningu i człowiek się przyzwyczai jakoś.
Mam nadzieję, że przedstawiony problem jest zrozumiały i prosty do
powielenia.
.