Ajaxowe dodawanie nowego rekordu

W dzisiejszym artykule pokażemy jak łatwo stworzyć ajaxową alternatywę dodawania nowego rekordu.

W Railscaście 258 Ryan Beats zademonstrował użycie Token Field, pluginu napisanego w jquery, który umożliwia wygodne dodawanie rekordów dla assocjacji wiele i wiele do wielu. W poniższym artykule chciałbym pokazać w jaki sposób dodać nowe rekordy za pomocą ajax-a. Przed przeczytaniem tutoriala, polecam obejrzenie railscastu (trwa tylko 10 minut)

Aplikację stworzoną w poniższym artykule można ściągnąć z githuba lub zobaczyć jak działa na heroku. Do jej stworzenia wykorzystałem aplikację stworzoną w railscaście 258.

Aby dodać nowego autora możemy przejść pod adres /author/new, ale gdy dodajemy książkę i chcemy przypisać do niej jeszcze nie istniejącego autora, lepiej będzie wyświetlić formę dodawania autora używając ajax’a. Do wyświetlenia formy użyjemy faceboxa, napisanego w jquery przez Chris’a Wanstrath’a.

Zaczynamy – instalacja faceboxa!

Ściągnijmy faceboxa z githuba, rozpakujmy i umieśćmy odpowiednio pliki w naszej aplikacji: facebox.css w public/stylesheets, facebox.js w public/javascripts, loading.gif i closelabel.png w public/images.

Teraz wpiszemy ścieżki do obrazków faceboxa w pliku public/javascripts/facebox.js:

loadingImage: '/images/loading.gif',
closeImage: '/images/closelabel.png'

oraz umieścimy arkusz css i plik z kodem javascript w pliku layoutu app/views/layouts/application.html.erb:

<%= stylesheet_link_tag "application", "token-input-facebook","facebox" %>
<%= javascript_include_tag :defaults, "jquery.tokeninput","facebox" %>

Formularz na faceboxie – jakie to proste 🙂

Teraz przejdźmy do wyświetlenia formularza dodawania nowego autora za pomocą faceboxa.
Musimy dodać odpowiedni link w pliku app/views/books/_form.html.erb:

<%= f.label :author_tokens, "Authors" %>
<%= link_to 'Add new author', new_author_path, :remote => true %>

Powyższy link posiada opcję :remote, która spowoduje wywołanie akcji new za pomocą ajaxa. Aby to zadziałało, musimy lekko zmodyfikować akcję w kontrollerze AuthorsController.

Osługa naszego żądania js w kontrolerze dzieki Railsom jest bardzo prosta. Wystarczy dodać nowy widok app/views/authors/new.js.erb. Po kliknięciu linku, metoda rozpozna, że żądanie jest w formacie js i wyświetli widok formatu js. Teraz dodajmy do widok kod który wyrenderuje nową akcję za pomocą faceboxa:

$.facebox('<%= escape_javascript(render :template => 'books/new.html') %>');

Powyższy kod wygeneruje formę dodawania nowego autora na faceboxie (app/views/books/new.html.erb ). Podając argument dla opcji :template musimy wskazać html-owy widok. Tworząc aplikację Rails z rozbudowaną obsługą Ajaxa znacznie częściej używany partiali, jednak nasze zadanie można wykonać bez modyfikacji istniejącego kodu używając opcji template.

Jeśli chcecie się dowiedzie więcej na temat używania faceboxa, w pliku public/javascripts/facebox.js są bardzo dobrze opisane wszystkie możliwości użycia.

Dodawanie autora – wreszcie coś działa!

Teraz chcemy wysłać nasz formularz i stworzyć nowego autora. Normalnie użylibyśmy opcji

:remote => true

w definicji formularza, ale w naszym przypadku chcemy użyć ajax’a jedynie na faceboxie. Aby uczynić tylko formularz na faceboxie ajaxowym, dodajmy poniższą linijkę w pliku app/views/books/new.js.erb:

$('#facebox form').data('remote','true');

Kod doda htmlowy atrybut data-remote=’true’ w znaczniku form, dzięki czemu plik public/javascript/rails.js przechwyci wysłanie formularza i wykona żądanie ajaxowe. Formularz jest wysyłany do akcji create w AuthorsController i potrzebuje templata w formacie js. Zatem tworzymy plik app/views/authors/create.js.erb, za pomocą którego zamykamy faceboxa:

$(document).trigger('close.facebox');

Ponieważ akcja create inaczej zachowuje się przy żądaniu html i js, musimy użyć bloku respond_to. W przypadku żądania html przekierowujemy użytkownika na stronę autora, a przy żądaniu js wyświetlamy opisany wyżej widok js.

def create
  @author = Author.new(params[:author])
  if @author.save
    respond_to do |format|
      format.html { redirect_to @author, :notice => "Successfully created author." }
      format.js
    end
  else
    render :action => 'new'
  end
end

Gotowe? Jeszcze parę poprawek…

Osiągnęliśmy nasz cel, ale musimy jeszcze wprowadzić parę usprawnień. Kiedy obiekt @author nie może być zapisany, powinniśmy wyświetlić formularz dodawania ponownie na faceboxie. W app/models/author.rb dodajmy walidację:

validates :name, :presence => true

Od teraz nie możemy dodać autora bez nazwy. Kiedy spróbujemy, dostaniemy błąd walidacji, który chcemy wyświetlić na faceboxie.

Na początku zmieńmy akcję create aby użyć widoku js,  wprzypadku gdy nie uda nam się zapisać rekordu:

def create
  @author = Author.new(params[:author])
  if @author.save
    respond_to do |format|
      format.html { redirect_to @author, :notice => "Successfully created author." }
      format.js
    end
  else
    respond_to do |format|
      format.html { render :action => 'new' }
      format.js
    end
  end
end

I zmodyfikujmy plik app/views/books/create.js.erb aby wyświetlić formularz z błędami na facebox’ie przy nie udanym zapisie obiektu @author:

<% if @author.save %>
  $(document).trigger('close.facebox')
<% else %>
  $('#facebox .content').html('<%= escape_javascript(render :template => 'authors/new') %>')
  $('#facebox form').data('remote','true');
<% end %>

Wreszcie mamy działające ajaxowe dodawanie autorów, wprowadzając minimalne zmiany w kodzie.

Jeszcze jedno malutkie usprawnienie.

Wprowadźmy jeszcze jedną małą zmianę – usuńmy link ‘Back to list’ z formy na faceboxie. Na faceboxie ten link jest kompletnie bezużyteczny. Zrobimy to za pomocą poniższej linijki jQuery:

$('#facebox .content p:last').remove();

Po prostu usuwamy ostatni tag w formularzu generowanym na faceboxie.

Powyższy kod dodajemy na końcu pliku app/views/authors/new.js.erb i na końcu sekcji else w app/views/authors/create.js.erb. To już naprawdę koniec!

Na heroku możesz zobaczyc demo aplikacji, a jej kod na githubie.
Wszystkie zmiany znajdują się w commicie cf13adacdff595059dc6eba0fcb33c0204ad6714

Jeśli chcesz wiedzieć więcej o działaniu javascripta w Rails 3, odwiedź poniższe linki:
http://rubysfera.pl/2010/11/javascript-w-rails-3/
http://www.simonecarletti.com/blog/2010/06/unobtrusive-javascript-in-rails-3/
http://www.alfajango.com/blog/rails-jquery-ujs-now-interactive/



Komentarze

  1. Czak 27.05.2011

    Comment Arrow

    A ja i tak uważam, że powinno być respond_with 🙂


  2. florianki 5.07.2011

    Comment Arrow

    Hej

    Wykonujesz 2 razy @author.save w przypadku Ajaxa ?
    Najpierw w akcji kontrolera a potem 2 raz w template ?


  3. slawosz 6.07.2011

    Comment Arrow

    @florianki

    Nie, @author.save jest tylko w kontrolerze. Wywołanie tego w widoku jest railsowym antywzorcem.


  4. echo 29.09.2011

    Comment Arrow

    @slawosz
    Ale w kodzie jak byk stoi @author.save wiec WTF? 🙂


  5. echo 29.09.2011

    Comment Arrow

    ^W kodzie create.js


  6. tjeden 29.09.2011

    Comment Arrow

    Wygląda na to, że w ‚create.js’ powinno być @author.valid? zamiast @author.save.




O autorze

Sławosz Sławiński

Programista Ruby. Zawodowo i hobbystycznie. Fan Githuba. W chwilach wolnych od programowania zajmuje się najchętniej chodzeniem po górach i wspinaczką.