Javascript w Rails 3
Jako technologii server-side Railsów specjalnie zachwalać nie trzeba. Szczególnie wśród czytelników Rubysfery. Aktualny trend w webdevelopmencie przenosi jednak coraz większy ciężar na front-end. Budujemy coraz bardziej wymyślne GUI i piszemy coraz więcej Javaskryptu. AJAX przestał być modnym słowem kluczowym, a stał się standardem. Aby uprościć budowę aplikacji opartych na Ajaksie, w Railsach od dawna istniały helpery link_to_remote
czy remote_form_for
, a bliska integracja frameworku z biblioteką Prototype.js sprawiała, że próg wejścia w ten świat był wyjątkowo niski.
Sporo jednak się zmieniło. Rozwiązania oparte o onclick
czy generowanie wymyślnych formularzy są wyjątkowo passé, a na domiar złego faworytem wielu webdeveloperów stało się w ostatnich latach jQuery. Team Rails zdawał się te drobiazgi mieć w głębokim poważaniu. Aż nadeszła wersja 3.
Wreszcie
Najważniejszą nowością w tym temacie jest uniezależnienie Railsów od Prototype.js. Jeśli chcemy korzystać z jQuery, nic nie stoi nam na przeszkodzie i wcale nie musimy rezygnować z railsowych helperów. Co więcej, odpowiedni moduł rails.js w wersji dla jQuery jest rozwijany przez Rails Team równolegle z wersją dla Prototype. Użytkownicy innych bibliotek znajdą również - już nieoficjalne acz w pełni sprawne - wersje dla MooTools, Ext.js czy YUI.
Warto też zwrócić uwagę na gem jquery-rails, który upraszcza migrację Prototype → jQuery do wywołania generatora:
rails generate jquery:install
Komenda usunie pozostałości po Prototypie, pobierze świeże jQuery i odpowiedni rails.js i zadba by javascript_include_tag :defaults
właśnie te dwa pliki wczytywało.
Jak to działa?
Po pierwsze, zapominamy o link_to_remote
i jego pobratymcach. W 3.x mamy tylko opcję :remote => true
. Dodajemy ją do link_to
, button_to
lub form_for
i tym prostym sposobem otrzymujemy ajaksowy - miast zwykłego - link, przycisk lub formularz.
Powyższa opcja zdaje się robić bardzo niewiele. Do wygenerowanego linku lub formularza dodaje jedynie atrybut data-remote="true"
. Co to za cudo i gdzie podział się mój onclick
?!
Atrybuty data-*
to wprowadzony w HTML5 standard wiązania dowolnych danych z elementami HTML. Całkowicie poprawny (w rozumieniu poprawności HTML5) jest przykładowy element:
<li data-villain="Kapitan Wyspa">Wilq</li>
...w którym to przebiegle powiązałem superbohatera z jego adwersarzem. Railsy zaadoptowały ten mechanizm m.in. do oznaczenia linków lub formularzy jako ajaksowe (remote).
Nie byłoby jednak magii Ajaksu bez wspomnianego wcześniej pliku rails.js. W nim zaimplementowano mechanizmy wysyłania zapytań i submitowania formularzy w tle. Tutaj odpowiednie handlery podpinane są do linków i formularzy z data-remote="true"
. I każda z rozwijanych oddzielnie wersji robi dokładnie to samo we własnym dialekcie Javaskryptu.
remote
to nie wszystko
Oprócz dodania :remote
, face-lifting przeszły opcje :method
i :confirm
. Najbardziej wypasiony link_to
z potwierdzeniem i usuwaniem przez Ajax:
link_to "Usuń", @product, :method => :delete, :confirm => "Czy aby na pewno?", :remote => true
generuje w Rails 3 całkiem zgrabne:
<a href="/products/7" data-confirm="Czy aby na pewno?" data-method="delete" ↵ data-remote="true" rel="nofollow">Usuń</a>
Analogicznie jak wcześniej, cała logika wywołania confirm("Czy aby na pewno?");
i ustawienia metody zapytania znajduje się w pliku rails.js.
I co dalej?
A dalej jest oczywiście kontroler. I akcja, która musi dany request - ajaksowy czy nie - obsłużyć. Tu warto wspomnieć o jeszcze jednej perełce w Rails 3. Dla mnie osobiście to odkrycie stulecia. Para respond_to
i respond_with
pozwoli skrócić kod każdego Twojego kontrolera o połowę. Nie ściemniam. Weźmy dość standardowy przykład z Rails 2.x:
def index @users = User.all respond_to do |format| format.html format.xml { render :xml => @users } format.json { render :json => @users } end end
Jedna akcja renderuje standardowy widok HTML lub, na życzenie, zwraca reprezentację JSON lub XML. Żadna fanaberia. W Rails 3 może to wyglądać tak:
respond_to :html, :xml, :json def index @users = User.all respond_with @users end
respond_to
służy do zadeklarowania akceptowanych formatów, a respond_with
na podstawie przekazanego obiektu i żądanego formatu wykona odpowiednią operację. A jak się to ma do Javaskryptu? Weźmy inny przykład:
respond_to :html, :js def create @article = Article.create(params[:article]) respond_with @article end
Długo by wymieniać ile magii znajduje się w jednej linijce respond_with
. Zaczynając od wariantu :html
, metoda ta inaczej zachowa się jeśli @article.valid?
(przekieruje na article_path(@article)
), a inaczej jeśli nie (wyrenderuje akcję new
, a jakże). Jeśli zapytanie przyszło z Javascriptu, choćby z formularza z ustawionym :remote => true
, akcja wyrenderuje natomiast create.js.erb
, w którym mamy dostęp do @article i możemy w UI natychmiast odzwierciedlić zmianę.
Dziękuję, dobranoc. Kwiaty można przysyłać pocztą na adres redakcji ;) Więcej info o respond_with
w artykule Ryana Daigle.