Formların Render Edilmesi

Formların görünümü çok çeşitli olabilir. Pratikte iki uç durumla karşılaşabiliriz. Bir yanda, uygulamada görsel olarak birbirine çok benzeyen bir dizi formu render etme ihtiyacı vardır ve $form->render() kullanarak şablon olmadan kolay render etmeyi takdir ederiz. Bu genellikle yönetim arayüzleri durumudur.

Diğer yanda, her birinin orijinal olduğu çeşitli formlar vardır. Görünümleri en iyi HTML diliyle form şablonunda tanımlanır. Ve elbette, bahsedilen her iki uç durumun yanı sıra, arada bir yerde bulunan birçok formla karşılaşacağız.

Latte ile Render Etme

Latte şablonlama sistemi formların ve elemanlarının render edilmesini önemli ölçüde kolaylaştırır. Önce formları manuel olarak tek tek elemanlarla nasıl render edeceğimizi ve böylece kod üzerinde tam kontrol sahibi olacağımızı göstereceğiz. Daha sonra böyle bir render işlemini nasıl otomatikleştirebileceğinizi göstereceğiz.

Formun Latte şablonu tasarımını Nette\Forms\Blueprint::latte($form) metodunu kullanarak oluşturabilirsiniz, bu da onu tarayıcı sayfasına yazdırır. Kodu daha sonra tıklayarak işaretleyip projeye kopyalamak yeterlidir.

{control}

Formu render etmenin en basit yolu şablonda şunu yazmaktır:

{control signInForm}

Bu şekilde render edilen formun görünümünü Renderer ve bireysel elemanlar yapılandırarak etkileyebilirsiniz.

n:name

PHP kodundaki form tanımını HTML koduyla ilişkilendirmek son derece kolaydır. Sadece n:name niteliklerini eklemek yeterlidir. Bu kadar kolay!

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>Şifre: <input n:name=password></label>
	</div>
	<div>
		<input n:name=send class="btn btn-default">
	</div>
</form>

Sonuçtaki HTML kodunun görünümü tamamen sizin kontrolünüzdedir. n:name niteliğini <select>, <button> veya <textarea> elemanlarında kullanırsanız, iç içerikleri otomatik olarak tamamlanır. <form n:name> etiketi ayrıca render edilen formun nesnesiyle $form yerel değişkenini oluşturur ve kapanış </form> etiketi render edilmemiş tüm gizli elemanları render eder (aynısı {form} ... {/form} için de geçerlidir).

Ancak olası hata mesajlarının render edilmesini unutmamalıyız. Hem addError() metoduyla bireysel elemanlara eklenenler ( {inputError} kullanarak) hem de doğrudan forma eklenenler ( $form->getOwnErrors() tarafından döndürülenler):

<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>Şifre: <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 veya CheckboxList gibi daha karmaşık form elemanları bu şekilde tek tek öğelerle render edilebilir:

{foreach $form[gender]->getItems() as $key => $label}
	<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}

{label} {input}

Her eleman için şablonda hangi HTML elemanını kullanacağınızı düşünmek istemiyor musunuz, <input>, <textarea> vb. mi? Çö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}Şifre: {input password}{/label}
		{inputError password}
	</div>
	<div>
		{input send, class: "btn btn-default"}
	</div>
</form>

Form bir çevirmen kullanıyorsa, {label} etiketleri içindeki metin çevrilecektir.

Bu durumda bile, RadioList veya CheckboxList gibi daha karmaşık form elemanları tek tek öğelerle render edilebilir:

{foreach $form[gender]->items as $key => $label}
	{label gender:$key}{input gender:$key} {$label}{/label}
{/foreach}

Checkbox elemanındaki <input>'ın kendisini render etmek için {input myCheckbox:} kullanın. Bu durumda HTML niteliklerini her zaman virgülle ayırın {input myCheckbox:, class: required}.

{inputError}

Bir form elemanı için varsa hata mesajını yazdırır. Mesajı genellikle stil vermek için bir HTML elemanına sararız. Mesaj yoksa boş bir elemanın render edilmesini önlemek, n:ifcontent kullanarak zarif bir şekilde yapılabilir:

<span class=error n:ifcontent>{inputError $input}</span>

Bir hatanın varlığını hasErrors() metoduyla kontrol edebilir ve buna göre üst elemana bir sınıf atayabiliriz:

<div n:class="$form[username]->hasErrors() ? 'error'">
	{input username}
	{inputError username}
</div>

{form}

{form signInForm}...{/form} etiketleri <form n:name="signInForm">...</form>'a bir alternatiftir.

Otomatik Render Etme

{input} ve {label} etiketleri sayesinde, herhangi bir form için kolayca genel bir şablon oluşturabiliriz. Sırayla tüm elemanlarını yineleyecek ve render edecektir, formun </form> etiketiyle sonlandırıldığında otomatik olarak render edilen gizli elemanlar hariç. Render edilen formun adı $form değişkeninde beklenecektir.

<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>

Kullanılan kendi kendine kapanan çift etiketler {label .../} PHP kodundaki form tanımından gelen etiketleri görüntüler.

Bu genel şablonu örneğin basic-form.latte dosyasına kaydedin ve formu render etmek için onu dahil etmek ve formun adını (veya örneğini) $form parametresine iletmek yeterlidir:

{include basic-form.latte, form: signInForm}

Belirli bir formu render ederken görünümüne müdahale etmek ve örneğin bir elemanı farklı render etmek isterseniz, en kolay yol şablonda daha sonra üzerine yazılabilecek bloklar hazırlamaktır. Bloklar ayrıca dinamik isimler sahip olabilir, bu nedenle render edilen elemanın adını da içlerine ekleyebilirsiniz. Örneğin:

...
	{label $input /}
	{block "input-{$input->name}"}{input $input}{/block}
...

Örneğin username elemanı için, {embed} etiketi kullanılarak kolayca üzerine yazılabilecek input-username bloğu oluşturulur:

{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 parametresi dahil olmak üzere bir blok olarak tanımlanabilir:

{define basic-form, $form}
	<form n:name=$form class=form>
		...
	</form>
{/define}

Bu sayede çağrısı biraz daha basit olacaktır:

{embed basic-form, signInForm}
	...
{/embed}

Bloğu yalnızca tek bir yerde, layout şablonunun başında içe aktarmak yeterlidir:

{import basic-form.latte}

Özel Durumlar

Formun yalnızca iç kısmını HTML <form> etiketleri olmadan render etmeniz gerekiyorsa, örneğin snippet gönderirken, bunları 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>

Form konteyneri içindeki elemanların render edilmesine {formContainer} etiketi 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 Etme

Formu render etmenin en basit yolu çağırmaktır:

$form->render();

Bu şekilde render edilen formun görünümünü Renderer ve bireysel elemanlar yapılandırarak etkileyebilirsiniz.

Manuel Render Etme

Her form elemanı, form alanının ve etiketin HTML kodunu üreten metotlara sahiptir. Bunu ya bir dize olarak ya da bir Nette\Utils\Html nesnesi olarak döndürebilirler:

  • getControl(): Html|string elemanın HTML kodunu döndürür
  • getLabel($caption = null): Html|string|null varsa etiketin HTML kodunu döndürür

Form bu nedenle tek tek elemanlarla render edilebilir:

<?php $form->render('begin') ?>
<?php $form->render('errors') ?>

<div>
	<?= $form['name']->getLabel() ?>
	<?= $form['name']->getControl() ?>
	<span class=error><?= htmlspecialchars($form['name']->getError()) ?></span>
</div>

<div>
	<?= $form['age']->getLabel() ?>
	<?= $form['age']->getControl() ?>
	<span class=error><?= htmlspecialchars($form['age']->getError()) ?></span>
</div>

// ...

<?php $form->render('end') ?>

Bazı elemanlar için getControl() tek bir HTML elemanı (örneğin <input>, <select> vb.) döndürürken, diğerleri için tüm bir HTML kod parçasını (CheckboxList, RadioList) döndürür. Bu durumda, her öğe için ayrı ayrı tek tek girdileri ve etiketleri üreten metotları kullanabilirsiniz:

  • getControlPart($key = null): ?Html bir öğenin HTML kodunu döndürür
  • getLabelPart($key = null): ?Html bir öğenin etiketinin HTML kodunu döndürür

Bu metotların tarihsel nedenlerden dolayı get öneki vardır, ancak her çağrıda yeni bir Html elemanı oluşturup döndürdükleri için generate daha iyi olurdu.

Renderer

Formun render edilmesini sağlayan bir nesnedir. $form->setRenderer metoduyla ayarlanabilir. $form->render() metodu çağrıldığında kontrol ona devredilir.

Kendi renderer'ımızı ayarlamazsak, varsayılan Nette\Forms\Rendering\DefaultFormRenderer render edicisi kullanılacaktır. Bu, form elemanlarını bir HTML tablosu şeklinde render eder. Çıktı şöyle görünür:

<table>
<tr class="required">
	<th><label class="required" for="frm-name">İsim:</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>
	...

Form iskeleti için tablo kullanıp kullanmamak tartışmalıdır ve birçok web tasarımcısı farklı bir işaretleme tercih eder. Örneğin, bir tanım listesi. Bu nedenle DefaultFormRenderer'ı formu bir liste şeklinde render edecek şekilde yeniden yapılandıracağız. Yapılandırma $wrappers dizisini düzenleyerek yapılır. İlk dizin her zaman alanı ve ikincisi onun niteliğini temsil eder. Bireysel alanlar resimde gösterilmiştir:

Standart olarak, controls eleman grubu bir tablo <table> ile sarılır, her pair bir tablo satırını <tr> temsil eder ve label ve control çifti hücreler <th> ve <td>'dir. Şimdi saran elemanları değiştireceğiz. controls alanını <dl> konteynerine koyacağız, pair alanını konteynersiz bırakacağız, label<dt>'ye koyacağız ve son olarak control<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();

Sonuç bu HTML kodudur:

<dl>
	<dt><label class="required" for="frm-name">İsim:</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 dizisinde diğer birçok niteliği etkileyebilirsiniz:

  • bireysel form elemanı türlerine CSS sınıfları eklemek
  • tek ve çift satırları CSS sınıfıyla ayırt etmek
  • zorunlu ve isteğe bağlı öğeleri görsel olarak ayırt etmek
  • hata mesajlarının doğrudan elemanların yanında mı yoksa formun üzerinde mi görüntüleneceğini belirlemek

Options

Renderer'ın davranışı, bireysel form elemanlarında options ayarlayarak da kontrol edilebilir. Bu şekilde, giriş alanının yanında yazdırılacak açıklamayı ayarlayabilirsiniz:

$form->addText('phone', 'Numara:')
	->setOption('description', 'Bu numara gizli kalacaktır');

İçine HTML içeriği yerleştirmek istiyorsak, Html sınıfını kullanırız

use Nette\Utils\Html;

$form->addText('phone', 'Numara:')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Numaranızın saklanma koşulları</a>')
	);

Html elemanı etiket yerine de kullanılabilir: $form->addCheckbox('conditions', $label).

Elemanların Gruplandırılması

Renderer, elemanları görsel gruplara (fieldset) ayırmayı sağlar:

$form->addGroup('Kişisel Veriler');

Yeni bir grup oluşturulduktan sonra, bu grup aktif hale gelir ve yeni eklenen her eleman aynı zamanda ona eklenir. Yani formu bu şekilde oluşturabilirsiniz:

$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ı ve ancak ondan sonra hiçbir gruba ait olmayan elemanları render eder.

Bootstrap Desteği

Örneklerde Renderer'ı Twitter Bootstrap 2, Bootstrap 3 ve Bootstrap 4 için nasıl yapılandıracağınıza dair örnekler bulacaksınız.

HTML Nitelikleri

Form elemanlarının herhangi bir HTML niteliğini ayarlamak için setHtmlAttribute(string $name, $value = true) metodunu kullanırız:

$form->addInteger('number', 'Numara:')
	->setHtmlAttribute('class', 'big-number');

$form->addSelect('rank', 'Sıralama ölçütü:', ['fiyat', 'isim'])
	->setHtmlAttribute('onchange', 'submit()'); // değişiklikte gönder


// <form> öğesinin kendisinin niteliklerini ayarlamak için
$form->setHtmlAttribute('id', 'myForm');

Eleman türünün belirtilmesi:

$form->addText('tel', 'Telefonunuz:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'telefonu yazın');

Türün ve diğer niteliklerin ayarlanması yalnızca görsel amaçlıdır. Girdilerin doğruluğunun doğrulanması sunucu tarafında yapılmalıdır, bu da uygun bir form elemanı seçerek ve doğrulama kuralları belirterek sağlanır.

Radyo veya onay kutusu listelerindeki bireysel öğelere, her biri için farklı değerlere sahip HTML niteliği atayabiliriz. Anahtara göre değer seçimini sağlayan style: sonrasındaki iki noktaya dikkat edin:

$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);

Yazdırır:

<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 mantıksal nitelikleri ayarlamak için soru işaretiyle yazımı kullanabiliriz:

$form->addCheckboxList('colors', 'Renkler:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // daha fazla anahtar için bir dizi kullanın, örn. ['r', 'g']

Yazdırır:

<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çme kutuları (selectbox) durumunda, setHtmlAttribute() metodu <select> elemanının niteliklerini ayarlar. Bireysel <option>'ların niteliklerini ayarlamak istiyorsak, setOptionAttribute() metodunu kullanırız. Yukarıda belirtilen iki nokta ve soru işaretiyle yazımlar da çalışır:

$form->addSelect('colors', 'Renkler:', $colors)
	->setOptionAttribute('style:', $styles);

Yazdırır:

<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>

Prototypler

HTML niteliklerini ayarlamanın alternatif bir yolu, HTML elemanının üretildiği şablonu düzenlemektir. Şablon bir Html nesnesidir ve getControlPrototype() metodu tarafından döndürülür:

$input = $form->addInteger('number', 'Numara:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number');            // <input class="big-number">

Bu şekilde, getLabelPrototype() tarafından döndürülen etiket şablonunu da değiştirebilirsiniz:

$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive');         // <label class="distinctive">

Checkbox, CheckboxList ve RadioList elemanlarında, tüm elemanı saran elemanın şablonunu etkileyebilirsiniz. Bu, getContainerPrototype() tarafından döndürülür. Varsayılan durumda bu “boş” bir elemandır, bu yüzden hiçbir şey render edilmez, ancak ona bir ad atayarak render edilecektir:

$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 durumunda, getSeparatorPrototype() tarafından döndürülen bireysel öğelerin ayırıcısının şablonunu da etkileyebilirsiniz. Varsayılan durumda bu <br> elemanıdır. Onu çiftli bir elemana değiştirirseniz, bireysel öğeleri ayırmak yerine saracaktır. Ayrıca, getItemLabelPrototype() tarafından döndürülen bireysel öğelerdeki etiket HTML elemanının şablonunu da etkileyebilirsiniz.

Çeviri

Çok dilli bir uygulama programlıyorsanız, muhtemelen formu farklı dil mutasyonlarında render etmeniz gerekecektir. Nette Framework bu amaçla çeviri için Nette\Localization\Translator arayüzünü tanımlar. Nette'de varsayılan bir uygulama yoktur, ihtiyaçlarınıza göre Componette üzerinde bulabileceğiniz birkaç hazır çözüm arasından seçim yapabilirsiniz. Belgelerinde çevirmeni nasıl yapılandıracağınızı öğreneceksiniz.

Formlar, metinlerin bir çevirmen aracılığıyla yazdırılmasını destekler. Onu setTranslator() metoduyla iletiriz:

$form->setTranslator($translator);

Bu andan itibaren, yalnızca tüm etiketler değil, aynı zamanda tüm hata mesajları veya seçme kutusu (select box) öğeleri de başka bir dile çevrilecektir.

Bireysel form elemanları için farklı bir çevirmen ayarlamak veya null değeriyle çeviriyi tamamen kapatmak mümkündür:

$form->addSelect('carModel', 'Model:', $cars)
	->setTranslator(null);

Doğrulama kuralları için çevirmene özel parametreler de iletilir, örneğin kural için:

$form->addPassword('password', 'Şifre:')
	->addRule($form::MinLength, 'Şifre en az %d karakter olmalıdır', 8);

çevirmen şu parametrelerle çağrılır:

$translator->translate('Şifre en az %d karakter olmalıdır', 8);

ve dolayısıyla sayıya göre karakter kelimesinin doğru çoğul biçimini seçebilir.

onRender Olayı

Form render edilmeden hemen önce, kodumuzun çağrılmasını sağlayabiliriz. Bu kod, örneğin doğru görüntüleme için form elemanlarına HTML sınıfları ekleyebilir. Kodu onRender dizisine ekleriz:

$form->onRender[] = function ($form) {
	BootstrapCSS::initialize($form);
};
versiyon: 4.0