czwartek, 28 kwietnia 2011

Prosta konfiguracja autocomplete w Ruby on Rails

Znowu walczyłem z autocomplete w Rails. Walczyłem, bo inaczej tego nie można określić.

Twórcą tego pluginu jest osobnik o swojsko brzmiącym nicku dhh. Na stronie poświęconej temu pluginowi można nawet (sic!) natknąć się na przykład jego wykorzystania. Przeglądając tę stronkę trudno coś o niej powiedzieć innego niż "łomatulu!". Wyjaśniać to ona niczego nie wyjaśnia, a tylko denerwuje niedziałającymi linkami scriptacoulosa.

Aby zainstalować z konsoli zawołaj:
ruby script/plugin install auto_complete
oczywiście zazwyczaj nie działa :-(
Można więc ręcznie. Ściągnij zatem plugin i rozpakuj do
/vendor/plugins/auto_complete
zazwyczaj pomaga.

Aby użyć tej kontrolki w widoku wpisz:
<%= text_field_with_auto_complete :model_name, 
                                  :field_in_model_to_search, 
                                  {:value => ""}, {:min_chars => 3, :after_update_element => 'approveForm'} %>
gdzie approveForm jest nazwą funkcji JavaScript, która zostanie wywołana po wybraniu elementu z listy.

Aby rozwikłać pozostałe zawiłości złapałem się po raz kolejny za google i gugłam. Czytam, czytam... i nadziwić się nie mogę. Zerknij tylko (albo raczej nie zerkaj) na railsforum, albo nawet na railscasts. To jakaś masakra jest!

Chodziło mi o prostą rzecz. Chciałem sam zapanować nad zapytaniem do bazy i nad tym co ona zwróci do użytkownika.
Najbardziej zdziwił mnie fakt, że to poniżej działa!!!
class CustomersController < ApplicationController
  auto_complete_for :customer, :name, :conditions => ["gender = ?", "female"]
  .
  .
  .
Zadziałało! Uradowany, nigdzie tego nie wyczytałem, tylko wykombinowałem "na macanta".  Fajno jest. I tak zostało na jakiś czas.
Gdy w następnym miejscu aplikacji znów musiałem czegoś podobnego użyć, miałem prawie gotowe rozwiązanie:
class ProductsController < ApplicationController
  # Nie stosuj tego, to nie działa!
  auto_complete_for :product, :name, :conditions => ["group_id = ?", @group_id]
  .
  .
  .
ale to nie działa. Zmienna egzemplarza pojawia się za wcześnie. Trick z tą zmienną @group_id odpowiadał mi, więc nie chciałem stosować trzymania się prawą ręką za lewe ucho i zacząłem drążyć temat. Po jakimś czasie okazało się, że notacja:
auto_complete_for :product, :name, :conditions => ["group_id = ?", @group_id]
tworzy dynamicznie metodę:
def auto_complete_for_product_name
  .
  .
  .
def
Co wiec stało na przeszkodzie aby taka metodę samemu napisać? Nic, więc napisałem:
def auto_complete_for_product_name
  product_name=params[:product][:name]
  conditions=["name = ? AND group_id = ?", product_name, @group_id]
  @products=Product.all(:conditions => conditions)
end
Sprawdzam... prawie działa.
Prawie bo Rails krzyczy, że brak mu pliku widoku auto_complete_for_product_name.erb
Znów "na macanta" poleciałem. Utworzyłem plik widoku i... zadziałało:
<ul> 
  <% @products.each do |product| %>
    <li><%= h product.name %></li> 
  <% end %> 
</ul> 
Rozwiązanie proste, łatwe i przyjemne.
W kontrolerze panuje się nad każdym przejawem tworzonej listy. Można zrobić dosłownie wszystko czego się tylko zapragnie. Nie trzeba z niczym kombinować jak to pokazywali w sreencaście i proponowali na liście. Moje rozwiązanie jest całkowicie naturalne i w duchu Rails.

Pozostała jeszcze sprawa pozostałych opcji.
Opcje są, a jakże. Żywcem je tu przytaczam:
# Required +options+ are:
# :url:: URL to call for autocompletion results
# in url_for format.
#
# Addtional +options+ are:
# :update:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the autocomplete
# entries returned by the AJAX request.
# Defaults to field_id + '_auto_complete'
# :with:: A JavaScript expression specifying the
# parameters for the XMLHttpRequest. This defaults
# to 'fieldname=value'.
# :frequency:: Determines the time to wait after the last keystroke
# for the AJAX request to be initiated.
# :indicator:: Specifies the DOM ID of an element which will be
# displayed while autocomplete is running.
# :tokens:: A string or an array of strings containing
# separator tokens for tokenized incremental
# autocompletion. Example: :tokens => ',' would
# allow multiple autocompletion entries, separated
# by commas.
# :min_chars:: The minimum number of characters that should be
# in the input field before an Ajax call is made
# to the server.
# :on_hide:: A Javascript expression that is called when the
# autocompletion div is hidden. The expression
# should take two variables: element and update.
# Element is a DOM element for the field, update
# is a DOM element for the div from which the
# innerHTML is replaced.
# :on_show:: Like on_hide, only now the expression is called
# then the div is shown.
# :after_update_element:: A Javascript expression that is called when the
# user has selected one of the proposed values.
# The expression should take two variables: element and value.
# Element is a DOM element for the field, value
# is the value selected by the user.
# :select:: Pick the class of the element from which the value for
# insertion should be extracted. If this is not specified,
# the entire element is used.
# :method:: Specifies the HTTP verb to use when the autocompletion
# request is made. Defaults to POST.

Ogólna notacja kontrolki w widoku jest taka:
.
<%= text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})  %>
.

Aby na przykład zacząć wyszukiwanie od wpisanych trzech liter kontrolkę skomponuj tak:
.
<%= text_field_with_auto_complete :employee, :name, {}, {:min_chars => 3} %>
.

wtorek, 26 kwietnia 2011

Screencasty o Vim'ie

Wybaczcie, że nie będę oryginalny ale dzięki blipowi niezauważenie zacząłem oglądać arcyciekawe (dla mnie oczywiście) screencasty o vim'ie.
Dostępne to to jest pod adresem: http://vimcasts.org i cieszy, że jest taka rzesza entuzjastów.
Nie będę ukrywał, że piszący te słowa z chęcią do tych entuzjastów się przyznaje :-).