A Rails sok automatizmust ad a kezünkbe, ezeket legtöbbször azonban felül kell bírálnunk. Viszont a Rubynak és a Rails logikus tervezésének köszönhetően könnyű dolgunk van.
Tegyük fel, hogy a következőre van szükségünk:
Az első két pontot akár le is kódolhatnánk minden form esetében, vagy használhatnánk partialt, esetleg helpert, a megoldás majdnem nyert ugyanis szükségünk lesz egy egyedi FormBuilder-re.
Hozzunk létre egy új osztályt a helpereink közé (újabb rails konvenció: app/helpers mappában a form builder-ünk nevével hozzunk létre egy fájlt, pl my_form_builder.rb), így automatikusan be fog töltődni.
Ahelyett, hogy megírnánk minden form helper metódust származtassuk le az osztályt a Rails alapértelmezett builder-jéből - ActionView::Helpers::FormBuilder.
Kezdjük a text_field metódus felülírásával
...
def text_field(field, *args)
label = label(field,options[:label])1
2@template.content_tag(:div,label+"<br />"+super3,:class=>"input")
end
...
A label metódus1 egy sima label tag-et hoz létre, ne feledjük ez az osztályunk metódusa, egyenlő a template-ben meghivott ActionView helperrel. A 2 pedig a szülő builder osztályunkból érjük el, ez megintcsak egyenlő a template-ben meghivott content_tag ActionView helperrel. A super-rel3 pedig egyszerűen meghívjuk az ősosztály hasonló metódusát, hogy ne kelljen megírnunk a működését.
Az osztály használatához adjuk meg a formok esetében a builder opciót:
form_for ... :builder=>MyFormBuilder ... do |f|
A text típusú űrlap beviteli(form input) tagokat tehát sikerült div-be csomagolni, mostmár csak a többi helpert kell megírnunk, amelyek gyakorlatilag megegyeznek a fentivel. A DRY elveit követve azonban nem akarunk kódismétlésbe kezdeni. Vessük be a Ruby dinamizmusát: a define_method a segítségünkre van!
...
helpers = field_helpers - %w(hidden_field label fields_for)1
helpers.each do |name|
define_method(name) do |field,*args|
label = label(field,options[:label])
@template.content_tag(:div,label+"<br />"+super :class=>"input")
end
...
1 A szülő osztály majd elbánik ezekkel, nekünk nincs szükségünk rájuk.
Márcsak a hibás mezők kezelését kell megoldanunk.
Hibás adat esetén a div-hez könnyen hozzáadhatjuk a kívánt osztálynevet(field-with-error), mindösszesen a felülbírált formbuilderünkben kell ellenőrizni, hogy tartozik-e a mezőhöz(field) hibaüzenetet.
...
wrapper_div_classes << INPUT_CLASS_NAME
wrapper_div_classes << " #{ERROR_CLASS_NAME}" unless 1@object.errors.on(field).nil?
@template.content_tag(:div,label+"<br />"+super,:class=>wrapper_div_classes2)
...
Mindössze annyit kell tudnunk, hogy a leszármaztatás miatt az @object osztályváltozóból1 elérhetjük a mezőhöz tartozó ActiveRecord tipusú objektumot, ahonnan megtudhatjuk tartozik-e az adott mezőhöz hibaüzenet. A 2-es pontban pedig hozzáadjuk a div osztályait.
Frissítés után láthatjuk, hogy a hibás mezők esetében valóban hozzáadódott az osztálynév, de hogyan iktassuk ki a Rails alapértelmezett hibakezelését, ami div-vel veszi körül nemcsak a hibás mezőt, de a hozzá tartozó címkét(labelt) is.
Nos, kevéssé dokumentált és nem is szép megoldás, de működik: felül kell írni az ActionView::Base.field_error_proc-t.
ActionView::Base.field_error_proc = Proc.new do |html_tag,instance_tag| html_tag end
Helyezzük el a fenti kódot a config/initializers könyvtárban pl. field_error_proc.rb néven. Szerver újraindítás után(!) a probléma megoldódik.
A forrás elérhető GitHub-ról, így:
git clone git://github.com/csiszarattila/myformbuilder.git