Prostsze formularze w Ruby on Rails
Zawsze irytowało mnie pisanie formularzy. Ciągłe powtarzanie kodu w stylu:
<p>
<%= f.label :field %>
<%= f.text_field :field %>
</p>
wydało mi się nieco bezsensowne. Na szczęście jest na to rozwiązanie. Helper form_for
posiada parametr :builder
który pozwala na ustawienie własnego FormBuildera - klasy obsługującej “budowanie” pól formularza.
Mój wymarzony formularz wygląda teraz mniej więcej tak:
<% standard_form_for @user do |f| -%>
<%= errors_for :user %>
<% f.fieldset do %>
<%= f.text_field :login, :info => "Only letters" %>
<%= f.text_field :email %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<% end %>
<% f.fieldset "Personal info" do %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= f.text_area :description %>
<% end %>
<% f.submit_tag 'Save' do %>
or <%= link_to "Back".t, user_path(@user) %>
<% end %>
<% end %>
Ale po kolei, co się tak właściwie tutaj dzieje?
Na samym początku jest helper standard_form_for
, który wygląda tak:
def standard_form_for(name, *args, &proc)
options = args.last.is_a?(Hash) ? args.pop : {}
options = options.merge(:builder => StandardBuilder)
args = (args << options)
form_for(name, *args, &proc)
end
Jest to tylko wygodniejszy sposób na zapisanie form_for
z naszym własnym builderem. Najlepiej umieścić tą metodę w application_helper.rb
Przejdźmy teraz do samej klasy StandardBuilder
. Najpierw sama definicja klasy.
# app/helpers/standard_builder.rb
class StandardBuilder < ActionView::Helpers::FormBuilder
include ActionView::Helpers::TextHelper
include ActionView::Helpers::FormTagHelper
end
Najpierw najprostsze - f.fieldset
.
def fieldset(legend = "", &proc)
p = legend.blank? ? "" : @template.content_tag("legend", legend)
concat("<fieldset>" + p, proc.binding)
proc.call
concat("</fieldset>", proc.binding)
end
Parametru legend
chyba nie trzeba objaśniać ;). Reszta to tylko “opakowanie” obiektu Proc w tag <fieldset>
. Można by tu dodać jeszcze więcej opcji typu id
czy class
jednak nigdy nie było mi to potrzebne.
W większości przypadków input[type="submit"]
jest wstawiany poprzez helper submit_tag
. Ja jednak postanowiłem dołączyć ją do buildera, będzie wygodniej i bardziej spójnie.
def submit_tag(label, &proc)
submit = @template.tag(:input, :type => "submit", :value => label, :class => "submit")
if proc
concat("<p class=\"actions\">" + submit, proc.binding)
proc.call
concat("</p>", proc.binding)
else
@template.content_tag(:p, submit, :class => "actions")
end
end
Wszystko co umieszczone w bloku podanym do tej metody razem z inputem zostanie opakowane w <p class="actions">...</p>
. Dołączenie bloku jest oczywiście opcjonalne.
Przejdźmy teraz do pomocniczej metody label
.
def label(field, label = nil, *args)
label ||= field.to_s.humanize
super(field, label + ":", *args)
end
Tutaj tylko dodane “:” na końcu. (W tym miejscu można również dodać metody obsługujące i18n).
Po tej krótkie umysłowej rozgrzewce czas na coś nieco bardziej skomplikowanego.
def self.create_p_field(method_name)
define_method(method_name) do |label, *args|
options = args.extract_options!
info = options.delete(:info)
clean = options.delete(:clean)
options[:class] ||= (method_name == "text_area" ? "" : method_name.split("_").first)
return super(label, options) if clean
info = info ? @template.content_tag(:span, info, :class => "info") : ""
@template.content_tag(:p, label(label, options[:label]) + super(label, options) + info)
end
end
W tym miejscu dzieje się cała magia ;). Jest to metoda klasy, która definiuje metodę egzemplarza za pomocą define_method
i podanych parametrów. Podobnie jak w powyższym przykładzie, metodę tę można dowolnie zmodyfikować w celu dopasowania do własnych potrzeb.
Na początku pobieramy i usuwamy kilka parametrów z hasha options
. Następnie ustawiamy klase na podstawie nazwy metody. Opcja clean
pozwala nam na wstawienie czystego pola w niestandardowej sytuacji. Potem dodajemy <span class="info">
jeśli został podany parametr info
. Na koniec pakujemy wszystko w <p>...</p>
dodając pole label
.
Teraz wystarczy tylko wygenerować metody dla wszystkich typów pól:
field_helpers.each do |name|
create_p_field(name) unless ["label"].include?(name)
end
I to by było na tyle. Jak już wspomniałem, możliwości dostosowania są nieograniczone. A wszystko to aby ułatwić sobie życie :)
standard_builder.rb do pobrania.
Looking for comments section?
Send me an email instead to [email protected]