[ajax, render, partial] dynamiczne wypełnianie opcji w selectboxie

Witam,
dalej walczę ze swoim prostym systemem wprowadzania danych. Jak zwykle powiem najpierw jaki efekt chcę osiagnąć - przy okazji sądzę, że z tym drobnym problemem spotykacie się wiele razy, ale jakoś nie mogę go przeskoczyć od wczoraj, mimo że posiłkuję sie wieloma tutorialami i przykładami.

Otóż: moja baza danych ma zawierać najróżniejsze pojazdy. Każdy pojazd ma główny typ (pojazd drogowy, pociag, samolot, statek), a każdy typ powinien mieć odpowiadające mu kategorie (pojazdy drogowe to np. samochody osobowe, cieżarówki, czołgi, a wśród statków mogą być promy i łodzie podwodne). Więc jeżeli wprowadzamy do bazy danych nowy pojazd (klasa vehicle) musimy zdefiniować jego typ (klasa vtype) i jego kategorię (vclass).

W widoku vehicles/new utworzonym automatycznie ze scaffoldu zamieniłem text_field na collections_select, żeby dodawany obiekt miał parametry wybierane z innych tabel, a nie wklepywane z palca (to był poprzedni problem, który tu zgłaszałem i rozwiązałem). Teraz oczywiście chciałbym, żeby zawartość drugiego selectboxa (tego z kategoriami) byłą wypełniana (czy jak to się mówi - populowana) opcjami zgodnymi tylko i wyłącznie z głównym typem, tak, aby nie tworzyć pojazdów drogowych, które są łodziami podwodnymi :slight_smile:

Poczytałem trochę i zauważyłem, że najlepszym rozwiazaniem (o ile nie jedynym) jest budowanie drugiego selectboxa za pomocą partiala - i tu jest pies pogrzebany.

Pliki biorące udział w tej operacji:

vehicles/new.html.erb

[code=ruby]

New vehicle

<% form_for(@vehicle) do |f| %>
<%= f.error_messages %>

<%= f.label :name %>
<%= f.text_field :name %>

<%= f.label :designation %>
<%= f.text_field :designation %>

<%= f.label :vtype_id %>
<%= f.collection_select :vtype_id, Vtype.find(:all), :id, :name, {:prompt => '-pick one-'}, {:id => 'select_vtype'} %> <%= observe_field 'select_vtype', :update => 'vclassdiv', :url => {:remote_action => :update_vclasses}, :with => 'select_vtype' %>

<%= f.label :vclass_id %>

<%= f.label :description %>
<%= f.text_area :description %>

<%= f.submit 'Create' %>

<% end %>

<%= link_to ‘Back’, vehicles_path %>[/code]
vehicle_controller

code=ruby
def update_vclasses
@vclasses = Vclass.all.select {|x| x.vtype_id == params[:select_vtype]}

render :update do |page|
	page.replace_html 'vclass', :partial => 'vclasspart', :object => @vclasses
end  	

end

(…)
def new
@vehicle = Vehicle.new
@vtypes = Vtype.find_all_for_select
@vclasses = Vclass.find_all_for_select

respond_to do |format|
  format.html # new.html.erb
  format.xml  { render :xml => @vehicle }
end

end[/code]
Na końcu partial views/_vclasspart.html.erb

<%= select (:vehicle, :vclass_id, @vclasses) %>

Po uporaniu sie z wieloma błędami wcześniej zostaję z takim problemem, że wybranie czegokolwiek z pierwszego selecta skutkuje nie wyświetleniem partiala, ale wyświetleniem kopii całego widoku vehicles/new.html.erb , zagnieżdżonej w DIV’ie o nazwie vclassdiv.

Potrzebuję pomocy :slight_smile:

Rozwiązałem problem dziwnego zagnieżdżania, po pierwsze nie mogę mieć w observe_field ajaksowego wywołania remote_action, tylko zwykłe action, po drugie cały formularz powinien być zajaksowany przedrostkiem remote_.

Do pierwszego wniosku doszedłem sam, metodą dziwnych prób i dziwniejszych błędów :slight_smile: drugi warunek wyczytałem gdzieś na blogach. Nadal jednak nie mogę osiągnąć zamierzonego efektu. Nie mam bladego pojęcia jak powinien wyglądać mój partial.

Jeżeli zrobię go takviews/_vclasspart.html.erb

<%= select (:vehicle, :vclass_id, @vclasses) %>

dostaję w miejscu DIVa linijkę typu (część wyciąłem, pokazuję tylko typ błędu, mogę mu zrobić screenshota)

try { Element.update("classdiv", "\n"); } catch (e) { alert(...); alert (...); throw e}

Jeżeli próbuję zrobić to w jakikolwiek inny sposób, np:

<%= form_for(@vehicle) do |f| %> <%= f.select (:vclass_id, @vclasses) %> <% end %>
albo

<%= remote_form_for(@vehicle) do |f| %> <%= f.select (:vclass_id, @vclasses) %> <% end %>
Wszystko wygląda jeszcze gorzej.
Jak to do cholery zrobić? Jak dołączyć partiala wewnątrz formularza html?

No odbra, usunąłem parę kolejnych przyczyn błędnego zachowania i teaz wszystkie moje obserwacje wskazują, że ostatnia rzecz do zrobienia, to poprawne przesłanie przefiltrowanej kolekcji do partiala, bo póki co robię to źle. Moja kolekcja jest najprawdopodobniej całkowicie pusta, niezależnie od tego jaki typ pojazdu wybierzemy w pierwszym select-boxie.

Postanowiłem metodę która pozwala na wybranie przefiltrowanych rekordów przenieść z kontrolera do modelu (tam gdzie jest również metoda mapująca wszystkie, niefiltrowane opcje), ale to nic nie dało. Obecnie nie mam żadnych komunikatów o błędach, ale drugi select box w ogóle nie jest tworozony, mimo że wykonanie tych samych czynności w konsoli, daje na koniec poprawne kolekcje:

models/vclass.rb

[code=ruby]class Vclass < ActiveRecord::Base
has_many :vtype

def self.find_all_for_select
	self.all.map {|x| [x.name, x.id]}
end 

def self.find_all_by_vtype(identifier)
	temp = self.all.select {|x| x.vtype_id == identifier}
	temp.map {|x| [x.name, x.id]}
end

end[/code]
controllers/vehicle_controller.rb

[code=ruby]def update_vclasses
@vcl = Vclass.find_all_by_vtype(params[:value])

render :partial => 'vclasspart', :collection => @vcl, :as => :vcl

end[/code]

naprawdę nikt nie wie jak mi pomóc? żadnych pomysłów?

Ja mam 2 pomysły:

  1. Nie wiem dokładnie jak wyglądają relacje w twojej bazie ale zapis:

has_many :vtype

w Vclass nie jest raczej tym o co Ci chodzi. Vclass ma pole vtype_id więc powinno być belongs_to :vtype

  1. Pisanie self.all.select to zabójstwo.
    self.all pobiera wszystkie rekordy, które filtrujesz za pomocą Rubiego (select). Do filtrowania rekordów użwaj bazy danych

Vclass.all.select {|x| x.vtype_id == params[:select_vtype]}

powinno wyglądać np. tak:

Vclass.all(:conditions => [‘vtype_id = ?’, params[:select_vtype]])

Z pytań które zadajesz wnioskuję, że próbujesz pisać trochę na oślep czytając różne blogi i tutoriale i dojść jak najszybciej do celu nie rozumiejąc podstaw.

Polecam zapoznanie się z ActiveRecord Associations oraz Active Record Query Interface (wersja po polsku tutaj)

Dopóki tego dobrze nie opanujesz wstrzymaj się z korzystaniem z Ajaxa, dynamicznych formularzy etc. poniewaź to wywoła tylko większy zamęt w Twojej głowie.

Powodzenia