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.



Komentarze

  1. Seban 23.11.2010

    Comment Arrow

    Jakiś czas temu popełniłem tekst jak obsługiwać własne atrybuty ‚data-*’. http://blog.sebastiannowak.net/2010/09/20/ujs-atrytuby-data-w-rails-3/




O autorze

Łukasz Adamczak

Programuje w Rubym i dłubie w Rails od końca 2007. Przygody z Javą i PHP były krótkie, ale wystarczyły by z pasją i wzajemnością zakochać się w RoR. Wierny członek sekty nadgryzionego jabłka. Od niedawna adept tajemnej sztuki Objective-C.