Formların Render Edilmesi
Formların görünümü çok çeşitli olabilir. Pratikte iki uçla karşılaşabiliriz. Bir yanda, bir uygulamada görsel
olarak birbirinin aynısı çok sayıda formu render etme ihtiyacı vardır ve $form->render() ile şablonsuz
kolay render'ı takdir ederiz. Yönetim arayüzlerinde durum tipik olarak böyledir.
Öte yanda, her biri kendine özgü olan çeşitli formlar vardır. Görünümleri en iyi, form şablonunda HTML kullanılarak anlatılır. Ve elbette bu iki uç dışında, arada bir yere düşen pek çok formla karşılaşırız.
Latte ile Render
Latte şablon sistemi, formların ve öğelerinin render edilmesini belirgin biçimde kolaylaştırır. Önce, kod üzerinde tam denetim kazanmak için formları elle, öğe öğe nasıl render edeceğimizi göstereceğiz. Sonra böyle bir render'ın nasıl otomatikleştirilebileceğini göstereceğiz.
Form için Latte şablonunu, onu tarayıcı sayfasına çıktılayan
Nette\Forms\Blueprint::latte($form) metoduyla üretebilirsiniz. Sonra yalnızca tıklayarak kodu seçin ve projenize
kopyalayın.
{control}
Bir formu render etmenin en basit yolu şablona şunu yazmaktır:
{control signInForm}
Render edilen formun görünümü, Renderer ve tek tek öğeler yapılandırılarak etkilenebilir.
n:name
Formun PHP kodundaki tanımını HTML koduyla bağlamak son derece kolaydır. Yalnızca n:name niteliklerini
ekleyin. Bu kadar basit!
protected function createComponentSignInForm(): Form
{
$form = new Form;
$form->addText('username')->setRequired();
$form->addPassword('password')->setRequired();
$form->addSubmit('send');
return $form;
}
<form n:name=signInForm class=form>
<div>
<label n:name=username>Kullanıcı adı: <input n:name=username size=20 autofocus></label>
</div>
<div>
<label n:name=password>Parola: <input n:name=password></label>
</div>
<div>
<input n:name=send class="btn btn-default">
</div>
</form>
Ortaya çıkan HTML kodunun görünümü üzerinde tam denetiminiz olur. n:name niteliğini
<select>, <button> ya da <textarea> elemanlarıyla kullanırsanız, iç
içerikleri otomatik doldurulur. Ayrıca <form n:name> etiketi, render edilen form nesnesini içeren yerel bir
$form değişkeni oluşturur ve kapanış </form> etiketi, render edilmemiş gizli öğeleri
render eder (aynısı {form} ... {/form} için de geçerlidir).
Ancak olası hata mesajlarını render etmeyi unutmamalıyız. Buna, addError() metoduyla tek tek öğelere
eklenen hatalar ({inputError} ile render edilir) ve doğrudan forma eklenen hatalar
($form->getOwnErrors() ile döndürülür) dahildir:
<form n:name=signInForm class=form>
<ul class="errors" n:ifcontent>
<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
</ul>
<div>
<label n:name=username>Kullanıcı adı: <input n:name=username size=20 autofocus></label>
<span class=error n:ifcontent>{inputError username}</span>
</div>
<div>
<label n:name=password>Parola: <input n:name=password></label>
<span class=error n:ifcontent>{inputError password}</span>
</div>
<div>
<input n:name=send class="btn btn-default">
</div>
</form>
RadioList ya da CheckboxList gibi daha karmaşık form öğeleri, öğe öğe şöyle render edilebilir:
{foreach $form[gender]->getItems() as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
{label} {input}
Şablonda her öğe için hangi HTML elemanının kullanılacağını (<input> mu,
<textarea> mı vb.) düşünmemeyi mi yeğlersiniz? Çözüm, evrensel {input} etiketidir:
<form n:name=signInForm class=form>
<ul class="errors" n:ifcontent>
<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
</ul>
<div>
{label username}Kullanıcı adı: {input username, size: 20, autofocus: true}{/label}
{inputError username}
</div>
<div>
{label password}Parola: {input password}{/label}
{inputError password}
</div>
<div>
{input send, class: "btn btn-default"}
</div>
</form>
Form bir çevirmen kullanıyorsa, form tanımından render edilen etiketler (örneğin {label username /})
çevrilir. Doğrudan {label} ve {/label} etiketleri arasına yazılan metin çevrilmez.
Yine, RadioList ya da CheckboxList gibi daha karmaşık form öğeleri öğe öğe render edilebilir:
{foreach $form[gender]->items as $key => $label}
{label gender:$key}{input gender:$key} {$label}{/label}
{/foreach}
Bir Checkbox öğesinin yalnızca <input> kısmını render etmek için {input myCheckbox:}
kullanın. Bu durumda HTML niteliklerini her zaman virgülle ayırın: {input myCheckbox:, class: required}.
{inputError}
Varsa bir form öğesinin hata mesajını görüntüler. Mesaj genellikle biçimlendirme için bir HTML elemanının içine
alınır. Mesaj yokken boş bir elemanın render edilmesini n:ifcontent ile şık biçimde önleyebilirsiniz:
<span class=error n:ifcontent>{inputError $input}</span>
Bir hata olup olmadığını hasErrors() metoduyla denetleyebilir ve üst elemanın sınıfını buna göre
ayarlayabiliriz:
<div n:class="$form[username]->hasErrors() ? 'error'">
{input username}
{inputError username}
</div>
{form}
{form signInForm}...{/form} etiketleri, <form n:name="signInForm">...</form> yazımının
bir alternatifidir. Argümanları addan virgülle ayırın: {form signInForm, class: foo}.
Adın önüne konan scope anahtar sözcüğü, formu yalnızca yığına iter (böylece
{input}, {label} vb. ona bağlanır) ama <form> etiketini render etmez. Bir formun
bir parçasını, örneğin bir snippet içinde render etmek için kullanışlıdır. Zaten etkin bir form varsa, ad ona göre
çözülür; dolayısıyla {form scope} aynı zamanda {formContainer} yerine de geçer:
{form scope signInForm}
{input username}
{/form}
detached anahtar sözcüğü boş bir <form></form> render eder ve
her öğeyi ona HTML form niteliğiyle bağlar. Bu, HTML'in başka türlü yasakladığı bir şeyi, bir formu başka
bir formun içine koymanızı sağlar. Ayrılmış formun bir HTML id değeri olmalıdır; ona bir ad verdiğinizde
(aşağıdaki outerForm gibi) bu otomatik üretilir:
{form detached outerForm}
...
{/form}
Otomatik Render
{input} ve {label} etiketleri sayesinde, herhangi bir form için genel bir şablonu kolayca
oluşturabiliriz. Bu şablon, form </form> etiketiyle kapatıldığında otomatik render edilen gizli öğeler
dışında tüm öğeleri dolaşıp render eder. Render edilecek formun adını $form değişkeninde bekler.
<form n:name=$form class=form>
<ul class="errors" n:ifcontent>
<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
</ul>
<div n:foreach="$form->getControls() as $input"
n:if="$input->getOption(type) !== hidden">
{label $input /}
{input $input}
{inputError $input}
</div>
</form>
Burada kullanılan, kendiliğinden kapanan çiftli {label .../} etiketleri, PHP kodundaki form tanımından gelen
etiketleri görüntüler.
Bu genel şablonu örneğin basic-form.latte dosyasına kaydedin. Formu render etmek için onu yalnızca dahil
edin ve form adını (ya da örneğini) $form parametresine verin:
{include basic-form.latte, form: signInForm}
Belirli bir formun görünümünü render sırasında değiştirmek, belki bir öğeyi farklı render etmek istiyorsanız, en kolay yol şablonda sonradan geçersiz kılınabilecek bloklar hazırlamaktır. Blokların dinamik adları da olabilir; bu da render edilen öğenin adını eklemenizi sağlar. Örneğin:
...
{label $input /}
{block "input-{$input->name}"}{input $input}{/block}
...
Örneğin username adlı bir öğe için bu, {embed} etiketiyle kolayca geçersiz kılınabilen
input-username bloğunu oluşturur:
{embed basic-form.latte, form: signInForm}
{block input-username}
<span class=important>
{include parent}
</span>
{/block}
{/embed}
Alternatif olarak basic-form.latte şablonunun tüm içeriği, $form parametresiyle birlikte bir blok
olarak tanımlanabilir:
{define basic-form, $form}
<form n:name=$form class=form>
...
</form>
{/define}
Bu, çağrıyı biraz daha basitleştirir:
{embed basic-form, signInForm}
...
{/embed}
Bloğun yalnızca tek bir yerde, yerleşim şablonunun başında içe aktarılması gerekir:
{import basic-form.latte}
Özel Durumlar
Formun yalnızca iç kısmını <form> HTML etiketleri olmadan render etmeniz gerekiyorsa, örneğin snippet
gönderirken, onları n:tag-if niteliğiyle gizleyin:
<form n:name=signInForm n:tag-if=false>
<div>
<label n:name=username>Kullanıcı adı: <input n:name=username></label>
{inputError username}
</div>
</form>
Bir form container'ının içindeki öğeleri render etmede {formContainer} etiketi ya da daha yeni {form scope} yardımcı olur.
<p>Hangi haberleri almak istersiniz:</p>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
Latte Olmadan Render
Bir formu render etmenin en kolay yolu şunu çağırmaktır:
$form->render();
Render edilen formun görünümü, Renderer ve tek tek öğeler yapılandırılarak etkilenebilir.
Elle Render
Her form öğesinin, form alanının ve etiketinin HTML kodunu üreten metotları vardır. Bunları ya dize olarak ya da bir Nette\Utils\Html nesnesi olarak döndürebilirler:
getControl(): Html|stringöğenin HTML kodunu döndürürgetLabel($caption = null): Html|string|nullvarsa etiketin HTML kodunu döndürür
Bu, formun öğe öğe render edilmesine olanak tanır:
<?php $form->render('begin') ?>
<?php $form->render('ownerrors') ?>
<div>
<?= $form['name']->getLabel() ?>
<?= $form['name']->getControl() ?>
<span class=error><?= htmlspecialchars((string) $form['name']->getError()) ?></span>
</div>
<div>
<?= $form['age']->getLabel() ?>
<?= $form['age']->getControl() ?>
<span class=error><?= htmlspecialchars((string) $form['age']->getError()) ?></span>
</div>
// ...
<?php $form->render('end') ?>
Bazı öğelerde getControl() tek bir HTML elemanı döndürürken (örneğin <input>,
<select> vb.), bazılarında eksiksiz bir HTML kodu parçası döndürür (CheckboxList, RadioList). Böyle
durumlarda, her öğe için ayrı ayrı input ve etiket üreten metotları kullanabilirsiniz:
getControlPart($key = null): Htmltek bir öğenin HTML kodunu döndürürgetLabelPart($key = null): Htmltek bir öğenin etiketinin HTML kodunu döndürür
Bu metotlar tarihsel nedenlerle get öneki taşır, ama generate daha uygun olurdu;
çünkü her çağrıda yeni bir Html elemanı oluşturup döndürürler.
Renderer
Bu, formu render etmekten sorumlu bir nesnedir. $form->setRenderer() metoduyla ayarlanabilir.
$form->render() metodu çağrıldığında denetim ona geçer.
Özel bir renderer ayarlamazsak, varsayılan renderer Nette\Forms\Rendering\DefaultFormRenderer kullanılır. Bu, form öğelerini bir HTML tablosuna render eder. Çıktı şöyle görünür:
<table>
<tr class="required">
<th><label class="required" for="frm-name">Ad:</label></th>
<td><input type="text" class="text" name="name" id="frm-name" required value=""></td>
</tr>
<tr class="required">
<th><label class="required" for="frm-age">Yaş:</label></th>
<td><input type="text" class="text" name="age" id="frm-age" required value=""></td>
</tr>
<tr>
<th><label>Cinsiyet:</label></th>
...
Formun yapısı için tablo kullanılıp kullanılmayacağı tartışmalıdır ve pek çok web tasarımcısı tanım listesi
gibi farklı bir işaretlemeyi yeğler. Bu yüzden DefaultFormRenderer sınıfını, formu liste olarak render edecek
şekilde yeniden yapılandıracağız. Yapılandırma, $wrappers dizisi
düzenlenerek yapılır. İlk indeks her zaman bir alanı, ikincisi ise onun niteliğini temsil eder. Tek tek alanlar resimde
gösteriliyor:

Varsayılan olarak controls grubu <table> içine sarılır, her pair bir tablo
satırını <tr> temsil eder, label ile control çifti ise <th> ve
<td> hücreleridir. Şimdi sarmalayan elemanları değiştireceğiz. controls alanını bir
<dl> container'ına koyacak, pair alanını container'sız bırakacak, label
alanını <dt> içine koyacak ve en sonunda control alanını <dd>
etiketleriyle saracağız:
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = null;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';
$form->render();
Bu, şu HTML kodunu verir:
<dl>
<dt><label class="required" for="frm-name">Ad:</label></dt>
<dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd>
<dt><label class="required" for="frm-age">Yaş:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd>
<dt><label>Cinsiyet:</label></dt>
...
</dl>
wrappers dizisi başka pek çok niteliği de etkilemeye olanak tanır:
- tek tek form öğesi türlerine CSS sınıfları ekleme
- tek ve çift satırları CSS sınıflarıyla ayırt etme
- zorunlu ve isteğe bağlı öğeleri görsel olarak ayırt etme
- hata mesajlarının doğrudan öğelerin yanında mı yoksa formun üstünde mi görüntüleneceğini belirleme
Seçenekler
Renderer'ın davranışı, tek tek form öğelerinde seçenekler ayarlanarak da denetlenebilir. Böylece girdi alanının yanında görünen bir açıklama koyabilirsiniz:
$form->addText('phone', 'Numara:')
->setOption('description', 'Bu numara gizli kalacak');
İçine HTML içerik koymak istersek Html sınıfını kullanırız:
use Nette\Utils\Html;
$form->addText('phone', 'Telefon:')
->setOption('description', Html::el('p')
->setHtml('<a href="...">Hizmet koşulları.</a>')
);
Etiket yerine de bir Html elemanı kullanılabilir: $form->addCheckbox('conditions', $label).
Girdileri Gruplama
Renderer, öğeleri görsel gruplara (fieldset) ayırmaya olanak tanır:
$form->addGroup('Kişisel veriler');
Yeni bir grup oluşturulduktan sonra etkin hâle gelir ve yeni eklenen her öğe ona da eklenir. Böylece form şöyle kurulabilir:
$form = new Form;
$form->addGroup('Kişisel veriler');
$form->addText('name', 'Adınız:');
$form->addInteger('age', 'Yaşınız:');
$form->addEmail('email', 'E-posta:');
$form->addGroup('Teslimat adresi');
$form->addCheckbox('send', 'Adrese gönder');
$form->addText('street', 'Sokak:');
$form->addText('city', 'Şehir:');
$form->addSelect('country', 'Ülke:', $countries);
Renderer önce grupları, ardından hiçbir gruba ait olmayan öğeleri çizer.
Bootstrap Desteği
Örnekler dizininde, Renderer'ın Twitter Bootstrap 2, Bootstrap 3 ve Bootstrap 4 için nasıl yapılandırılacağını gösteren örnekler bulabilirsiniz.
HTML Nitelikleri
Form öğelerine herhangi bir HTML niteliği koymak için setHtmlAttribute(string $name, $value = true) metodunu
kullanın:
$form->addInteger('number', 'Numara:')
->setHtmlAttribute('class', 'big-number');
$form->addSelect('rank', 'Sıralama ölçütü:', ['fiyat', 'ad'])
->setHtmlAttribute('onchange', 'submit()'); // değişince formu gönder
// <form> elemanının kendi niteliklerini ayarlamak için
$form->setHtmlAttribute('id', 'myForm');
Öğenin türünü belirtme:
$form->addText('tel', 'Telefonunuz:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', 'Lütfen telefonunuzu girin');
Türü ve diğer nitelikleri ayarlamak yalnızca görsel amaçlıdır. Girdinin doğruluğunun doğrulanması sunucu tarafında yapılmalıdır; bunu uygun bir form öğesi seçerek ve doğrulama kuralları belirterek sağlarsınız.
Radyo ya da onay kutusu listelerindeki tek tek öğelerde, her biri için farklı değerlere sahip bir HTML niteliği
ayarlayabiliriz. style: sonrasındaki iki nokta üst üste işaretine dikkat edin; bu, değerin anahtara göre
seçilmesini sağlar:
$colors = ['r' => 'kırmızı', 'g' => 'yeşil', 'b' => 'mavi'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Renkler:', $colors)
->setHtmlAttribute('style:', $styles);
Şunu render eder:
<label><input type="checkbox" name="colors[]" style="background:red" value="r">kırmızı</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">yeşil</label>
<label><input type="checkbox" name="colors[]" value="b">mavi</label>
readonly gibi boolean nitelikleri ayarlamak için soru işaretli yazımı kullanabiliriz:
$form->addCheckboxList('colors', 'Renkler:', $colors)
->setHtmlAttribute('readonly?', 'r'); // birden çok anahtar için dizi kullanın, örneğin ['r', 'g']
Şunu render eder:
<label><input type="checkbox" name="colors[]" readonly value="r">kırmızı</label>
<label><input type="checkbox" name="colors[]" value="g">yeşil</label>
<label><input type="checkbox" name="colors[]" value="b">mavi</label>
Seçim kutularında setHtmlAttribute() metodu <select> elemanının niteliklerini ayarlar. Tek
tek <option> elemanlarına nitelik koymak istersek setOptionAttribute() metodunu kullanırız.
Yukarıda sözü edilen iki nokta ve soru işareti yazımları burada da çalışır:
$form->addSelect('colors', 'Renkler:', $colors)
->setOptionAttribute('style:', $styles);
Şunu render eder:
<select name="colors">
<option value="r" style="background:red">kırmızı</option>
<option value="g" style="background:green">yeşil</option>
<option value="b">mavi</option>
</select>
Prototipler
HTML nitelikleri ayarlamanın bir başka yolu, HTML elemanının üretildiği şablonu değiştirmektir. Şablon bir
Html nesnesidir ve getControlPrototype() metoduyla döndürülür:
$input = $form->addInteger('number', 'Numara:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number'); // <input class="big-number">
getLabelPrototype() ile döndürülen etiket şablonu da bu şekilde değiştirilebilir:
$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive'); // <label class="distinctive">
Checkbox, CheckboxList ve RadioList öğelerinde, öğenin tamamını saran elemanın şablonunu etkileyebilirsiniz. O da
getContainerPrototype() ile döndürülür. Varsayılan olarak “boş” bir elemandır, dolayısıyla hiçbir şey
render edilmez; ama ona bir ad verirseniz render edilir:
$input = $form->addCheckbox('send');
$html = $input->getContainerPrototype();
$html->setName('div'); // <div>
$html->class('check'); // <div class="check">
echo $input->getControl();
// <div class="check"><label><input type="checkbox" name="send"></label></div>
CheckboxList ve RadioList söz konusu olduğunda, getSeparatorPrototype() metoduyla döndürülen, tek tek
öğeler arasındaki ayırıcının şablonunu da etkileyebilirsiniz. Varsayılan olarak <br> elemanıdır. Onu
çiftli bir elemana çevirirseniz, öğeleri ayırmak yerine onları saracaktır. Ayrıca getItemLabelPrototype() ile
döndürülen, tek tek öğelerin etiketlerine ait HTML eleman şablonunu da etkileyebilirsiniz.
Çeviri
Çok dilli bir uygulama geliştiriyorsanız, formu büyük olasılıkla farklı dil sürümlerinde render etmeniz gerekecek. Nette Framework bunun için bir çeviri arayüzü tanımlar: Nette\Localization\Translator. Nette'in varsayılan bir gerçekleştirimi yoktur; ihtiyacınıza göre Componette üzerinde bulunan hazır çözümlerden seçebilirsiniz. Çevirmenin nasıl yapılandırılacağını belgeleri anlatır.
Formlar, metinlerin çevirmen üzerinden çıktılanmasını destekler. Onu setTranslator() metoduyla veririz:
$form->setTranslator($translator);
Bu andan itibaren yalnızca tüm etiketler değil, tüm hata mesajları, seçim kutularındaki öğeler ve girdi placeholder'ları da hedef dile çevrilir.
Tek tek form öğeleri için farklı bir çevirmen ayarlamak ya da değeri null yaparak çeviriyi tümüyle
kapatmak olanaklıdır:
$form->addSelect('carModel', 'Model:', $cars)
->setTranslator(null);
Doğrulama kurallarında çevirmene belirli parametreler de aktarılır. Örneğin şu kural için:
$form->addPassword('password', 'Parola:')
->addRule($form::MinLength, 'Parola en az %d karakter uzunluğunda olmalıdır', 8);
çevirmen şu parametrelerle çağrılır:
$translator->translate('Parola en az %d karakter uzunluğunda olmalıdır', 8);
ve böylece karakter sözcüğü için sayıya göre doğru çoğul biçimi seçebilir.
onRender Olayı
Form render edilmeden hemen önce kendi kodumuzun çağrılmasını sağlayabiliriz. Bu kod örneğin doğru görüntüleme
için form öğelerine HTML sınıfları ekleyebilir. Kodu onRender dizisine ekleriz:
$form->onRender[] = function ($form) {
BootstrapCSS::initialize($form);
};