Özel Form Öğeleri
*****************
.[perex]
Nette geniş bir [yerleşik form öğesi |controls] paleti sunar. Ama aralarında bulunmayan bir gereksinimle karşılaştığınızda hiçbir şeyi dolambaçlı yollarla çözmeniz ya da bir şeyleri birbirine yapıştırmanız gerekmez: kendi öğenizi yazarsınız. O da yerleşik olanların yapabildiği her şeyi yapabilecek: doğrulama, kendini çevirme, render; ve tam olarak aynı biçimde kullanılacak.
Bunu pratik bir örnekle göstereceğiz: üç alan (gün, ay ve yıl) kullanarak tarih girmeye yarayan bir öğe. Yol boyunca, öğe yazmak hakkında bilmeniz gereken her şeyi öğreneceksiniz.
Ne Zaman Özel Öğe Yazmalı, Ne Zaman Yazmamalı
=============================================
Özel öğe, formların sunduğu en güçlü araçtır. Ve her güçlü araç gibi, ilk değil son seçenek olmalıdır. Pek çok durum daha basit yollarla çözülebilir:
- **Bir değeri değiştirmeyi** [addFilter() |validation#Girdi Değerlerini Değiştirme] üstlenir. Posta kodunda boşluklara ya da bir kodda küçük harflere göz yummak mı istiyorsunuz? Bir filtre birkaç satırdır.
- **Yinelenen yapılandırmayı** özel bir ekleme metodu sarmalar. On yerde aynı doğrulamayla posta kodu alanı mı ekliyorsunuz? Onlar için adlandırılmış bir kısayol oluşturun, [sonunda göstereceğiz |#Özel Ekleme Metodu].
- **Birbiriyle ilişkili bir alan grubuna** bir [container |controls#addContainer()] hizmet eder. Sokak, şehir ve posta kodundan oluşan bir adres için özel öğe gerekmez, üç metin alanlı bir container yeter.
- **Farklı bir görünüm** [setHtmlType() |controls#addText()] ve HTML nitelikleriyle ya da [prototiplerle |rendering#Prototipler] elde edilir.
Özel bir öğe, **özel bir değere** ihtiyaç duyduğunuz anda anlam kazanır: dışarıdan tek değerli tek bir alan gibi davranan, ama içeride birkaç input'tan oluşan ya da değeri gösterdiğinden farklı biçimde saklayan bir öğe. Üç alandan oluşan bir tarih. Haritaya tıklanarak seçilen koordinatlar. Otomatik tamamlamalı bir etiket girişi.
Bir Öğenin Anatomisi
====================
Her özel öğe, soyut [api:Nette\Forms\Controls\BaseControl] sınıfından türer. Ondan çok büyük miktarda hazır işlev miras alır: değer saklama, doğrulama kuralları ve koşulları, hata mesajları, çeviriler, HTML nitelikleri, etiket ve render bağlantısı. Siz yalnızca öğenizi farklı kılan şeyi yazarsınız.
Çalışan en küçük öğe şaşırtıcı derecede kısadır:
```php
use Nette\Forms\Form;
use Nette\Forms\Helpers;
use Nette\Utils\Html;
class SimpleInput extends Nette\Forms\Controls\BaseControl
{
public function loadHttpData(): void
{
$this->setValue($this->getHttpData(Form::DataLine));
}
public function getControl(): Html
{
return Html::el('input', [
'type' => 'text',
'name' => $this->getHtmlName(),
'id' => $this->getHtmlId(),
'value' => $this->getValue(),
'data-nette-rules' => Helpers::exportRules($this->getRules()) ?: null,
]);
}
}
```
İki metot: biri değerin gönderilen veriden nasıl alınacağını, öteki öğenin nasıl render edileceğini söyler. Birazdan ikisine de yakından bakacağız. Geri kalan her şey (`setRequired()`, `addRule()`, `setDefaultValue()`, çeviriler) kendiliğinden çalışır.
Öğeyi forma `addComponent()` metoduyla ya da daha kısaca köşeli parantezlerle eklersiniz:
```php
$form['nickname'] = new SimpleInput('Takma ad:');
```
Bir Öğenin Yaşam Döngüsü
========================
Daha ilginç bir öğeye geçmeden önce, bir öğeye ne zaman ne olduğunu bilmek iyidir. Form ve öğeleri, bir ağaç oluşturan [bileşenlerdir |component-model:]. Bunun hoş bir sonucu var: öğenin hiçbir şeyi kendi başına bulması gerekmez, önemli olan her şeyi framework doğru anda halleder:
1) Öğeyi gönderilmiş bir forma eklediğiniz anda, formun kendisi onda `loadHttpData()` metodunu çağırır. Öğe orada, birazdan göstereceğimiz gibi, gönderilen değerini okur. Asla doğrudan `$_POST` ile çalışmaz ve container'ların içinde iç içe olup olmadığını hiç dert etmez.
2) Form gönderildiğinde doğrulama gerçekleşir: `addRule()` ile eklenen kurallar, `getValue()` metodundan gelen değerle çalışarak değerlendirilir.
3) Sonra `$form->getValues()` ya da öğede `getValue()` çağıran kişi, temiz ve türlenmiş bir değer alır; örneğin formdan gelen üçlü dize değil, bir `DateTimeImmutable` nesnesi.
Render sırasında ise `getControl()`, etiket içinse `getLabel()` çağrılır.
Gönderilen Değeri Okuma
=======================
`loadHttpData()` metodunda öğe, gönderilen değerini `getHttpData()` metoduyla ister. Parametresi, değerin nasıl temizleneceğini belirleyen bir türdür:
| tür | anlamı
|-------
| `Form::DataLine` | tek satırlı metin: satır sonlarını boşlukla değiştirir, boşlukları kırpar
| `Form::DataText` | çok satırlı metin: satır sonlarını `\n` biçimine normalleştirir
| `Form::DataFile` | yükleme, bir `Nette\Http\FileUpload` örneği
Bir saldırgan ne kadar uğraşırsa uğraşsın, sonuç her zaman denetim karakterleri olmayan geçerli bir UTF-8 dizesidir (ya da bir yükleme nesnesi veya `null`). Değeri doğrudan `$_POST` içinden okumamamızın nedeni tam da budur; tüm bu güvenceleri yitirirdik.
Bizim tarihimiz gibi birkaç input'tan oluşan bir öğe, HTML adının bir parçasını ikinci parametre olarak verir ve tek tek alt değerlerini böyle okur. Onları kendi `$day`, `$month` ve `$year` dize özelliklerinde saklar:
```php
public function loadHttpData(): void
{
$this->day = $this->getHttpData(Form::DataLine, '[day]') ?? '';
$this->month = $this->getHttpData(Form::DataLine, '[month]') ?? '';
$this->year = $this->getHttpData(Form::DataLine, '[year]') ?? '';
}
```
HTML adı `[]` ile biterse bir değer dizisi döndürülür. `Form::DataKeys` türüyle birleştirerek (yani `Form::DataLine | Form::DataKeys`) anahtarlarını da korursunuz:
```php
$tags = $this->getHttpData(Form::DataLine, '[tags][]');
```
Eksik bir değer `null` olur (dizilerde boş dizi). İstek, öğenin verisini hiç içermek zorunda değildir; hiçbir şey bir saldırganın canının istediğini göndermesini engellemez. Örnekte `?? ''` eklememizin ve bu olasılığı her zaman hesaba katmanız gerektiğinin nedeni budur.
Öğenin Değeri
=============
Öğe değerini tutar ve onu, sözleşmesine uyulması gereken üç metotla dışa açar.
`setValue()` metodu programcıdan bir değer alır; `setDefaultValue()` ve `$form->setDefaults()` de bu yolu izler. Anlamlı olan her şeyi kabul etmeli, değeri iç biçimine dönüştürmeli ve anlamsız girdide istisna fırlatmalıdır; böylece hata, formun gizemli davranışlarıyla değil hemen ortaya çıkar. Bizim tarihimiz bir `DateTimeInterface`, bir dize, bir zaman damgası ya da `null` kabul eder ve bunları üç alana böler:
```php
public function setValue(mixed $value): static
{
if ($value === null) {
$this->day = $this->month = $this->year = '';
} else {
$date = Nette\Utils\DateTime::from($value); // saçmalık istisna fırlatır
$this->day = $date->format('j');
$this->month = $date->format('n');
$this->year = $date->format('Y');
}
return $this;
}
```
`getValue()` metodu ise temiz, türlenmiş bir değer kurar; öğenizin kullanıcısının göreceği tek şey budur. Değer geçerli değilse `null` döndürür. Statik `validateDate()` metodu yalnızca üç alanın var olan bir tarih oluşturup oluşturmadığını denetler:
```php
public function getValue(): ?DateTimeImmutable
{
return self::validateDate($this)
? (new DateTimeImmutable)->setDate((int) $this->year, (int) $this->month, (int) $this->day)->setTime(0, 0)
: null;
}
```
`isFilled()` metodu ise kullanıcının öğeyi doldurup doldurmadığını söyler; onu `setRequired()` kuralı kullanır. Varsayılan gerçekleştirim (boş olmayan bir değer) çoğu zaman yeter, ama bileşik bir öğede onu kendi mantığına göre geçersiz kılın:
```php
public function isFilled(): bool
{
return $this->day !== '' || $this->year !== '';
}
```
Render
======
`getControl()` metodu, öğenin HTML biçimini genellikle bir [Html |utils:html-elements] nesnesi olarak döndürür, ama düz bir dize de olur, fark etmez. Html nesnesine başlıca kodu kurarken başvururuz; çünkü ortaya çıkan işaretlemeyi güvenli ve keyifli bir API ile kurmamızı sağlar. Elinizin altında çeşitli yardımcılar var:
- `getHtmlName()`, container'lardaki olası iç içe geçme de dahil olmak üzere HTML `name` niteliğini döndürür (örneğin `invoice[date]`). Bileşik bir öğede tek tek input'ların ad parçalarını buna eklersiniz: `$name . '[day]'`.
- `getHtmlId()`, etiketle bağlanan `id` niteliğini döndürür.
- `Helpers::exportRules($this->getRules())`, doğrulama kurallarını `data-nette-rules` niteliği için dışa aktarır; bu sayede [JavaScript doğrulaması |validation#JavaScript Doğrulaması] sizin öğenizde de çalışır. Nitelik, öğenin ilk input'una konur.
- `Helpers::createSelectBox($items, $optionAttrs, $selected)`, bir öğe dizisinden `<select>` elemanı kurar (iç içe diziler `<optgroup>` olarak render edilir) ve onu `Html` olarak döndürür; tarihimizin ay alanı için kullanışlıdır.
- `Helpers::createInputList($items, $inputAttrs, $labelAttrs)`, `<label>` içine sarılmış `<input>` elemanlarından oluşan bir liste (radyo düğmeleri ya da onay kutuları) üretir ve onu dize olarak döndürür.
Tarihimizin ilk alanı böylece şöyle oluşturulur:
```php
public function getControl(): Html
{
$name = $this->getHtmlName();
return Html::el()
->addHtml(Html::el('input', [
'name' => $name . '[day]',
'id' => $this->getHtmlId(),
'value' => $this->day,
'type' => 'number',
'data-nette-rules' => Helpers::exportRules($this->getRules()) ?: null,
]))
->addHtml(/* ... ay için select ve yıl için input ... */);
}
```
Etiket `getLabel()` ile render edilir ve varsayılan gerçekleştirimi genellikle uygundur. Yalnızca dikkat: bileşik bir öğede onun `for` niteliği `getHtmlId()` değerine işaret eder, bu yüzden bu id'yi ilk input'a verin; tam da örnekteki gibi.
Bileşik öğenin şablonda parça parça render edilebilmesi için (örneğin `{input birthdate:day}`), ilgili parça için `Html` elemanını döndüren `getControlPart($key)` ve `getLabelPart($key)` metotlarını geçersiz kılın; `CheckboxList` ve `RadioList` de böyle yapar.
.[note]
`getControl()` metodunu geçersiz kılarsanız, `BaseControl::getControl()` metodunun aynı zamanda `setOption('rendered', true)` ile öğeyi render edilmiş olarak işaretlediğini unutmayın. Aynı formda elle ve otomatik render'ı birleştirdiğinizde onu da çağırın (ya da `parent::getControl()` çağırın); böylece öğe iki kez render edilmez. (Yukarıdaki `DateInput` örneği bunu kısalık için atlıyor.)
Eksiksiz Örnek: DateInput
=========================
Anlatılan tüm parçalar bir arada, ayın seçilmesi için bir select box'la da tamamlanmış hâlde, bitmiş `DateInput` öğesinde, [doğrudan depodaki örnekler |https://github.com/nette/forms/blob/master/examples/custom-control.php] arasında bulunabilir.
Öğenin yapıcıda kendisine, tarihin anlamlı olup olmadığını denetleyen bir doğrulama kuralı eklediğine dikkat edin. Böylece 31 Şubat gibi anlamsız bir girdi, sıradan bir form doğrulama hatası olarak ortaya çıkar:
```php
public function __construct($label = null)
{
parent::__construct($label);
$this->addRule(self::validateDate(...), 'Tarih geçersiz.');
}
```
Peki kullanımı? Tam olarak yerleşik öğelerdeki gibi:
```php
$form['birthdate'] = (new DateInput('Doğum tarihi:'))
->setDefaultValue(new DateTime('2000-01-01'))
->setRequired('Ne zaman doğdunuz?');
$date = $form->getValues()->birthdate; // ?DateTimeImmutable
```
Latte şablonunda onu, başka herhangi bir öğe gibi, alışıldık `{input birthdate}` ya da `{label birthdate /}` etiketiyle render edersiniz.
Doğrulama
=========
Yerleşik doğrulama kuralları özel bir öğeyle hemen çalışır; `getValue()` metodundan gelen değer üzerinde işlem yaparlar. Böylece `DateInput` öğemiz örneğin izin verilen en eski tarih için `Form::Min` kullanabilir. JavaScript karşılığı da dahil olmak üzere kendi kurallarınızı nasıl yazacağınız [Özel kurallar ve koşullar |validation#Özel Kurallar ve Koşullar] bölümünde anlatılıyor.
Özel Ekleme Metodu
==================
Yerleşik öğeleri `$form->addText()` ve benzeri elverişli metotlarla ekleriz. Özel bir öğenin böyle bir metodu yoktur, bu yüzden onu düz atamayla eklersiniz; hem formda hem container'da aynı şekilde çalışır ve düzenleyiciler ile statik çözümleme bunu anlar:
```php
$form['birthdate'] = new DateInput('Doğum tarihi:');
```
Otomatik tamamlamayı korurken eklemeyi kısaltmak isterseniz, doğrudan öğenin üzerindeki statik bir factory metodu işe yarar. `Form` sınıfının bir torunundaki metodun yapamayacağı şeyi, iç içe container'larda bile çalışmayı başarır; iç içe container'lar ondan habersizdir:
```php
class DateInput extends Nette\Forms\Controls\BaseControl
{
public static function addTo(
Nette\Forms\Container $container,
string $name,
?string $label = null,
): self {
return $container[$name] = new self($label);
}
}
// formda ve herhangi bir container'da çalışır:
DateInput::addTo($form, 'birthdate', 'Doğum tarihi:');
```
Aynı yaklaşım, yerleşik bir öğenin yinelenen yapılandırması için adlandırılmış bir kısayol olarak da işe yarar:
```php
final class ZipInput
{
public static function addTo(
Nette\Forms\Container $container,
string $name,
?string $label = null,
): Nette\Forms\Controls\TextInput {
return $container->addText($name, $label)
->addRule(Nette\Forms\Form::Pattern, 'Posta kodu tam olarak 5 rakam olmalıdır', '[0-9]{5}');
}
}
ZipInput::addTo($form, 'zip', 'Posta kodu:');
```
Nette geniş bir yerleşik form öğesi paleti sunar. Ama
aralarında bulunmayan bir gereksinimle karşılaştığınızda hiçbir şeyi dolambaçlı yollarla çözmeniz ya da bir şeyleri
birbirine yapıştırmanız gerekmez: kendi öğenizi yazarsınız. O da yerleşik olanların yapabildiği her şeyi yapabilecek:
doğrulama, kendini çevirme, render; ve tam olarak aynı biçimde kullanılacak.
Bunu pratik bir örnekle göstereceğiz: üç alan (gün, ay ve yıl) kullanarak tarih girmeye yarayan bir öğe. Yol boyunca,
öğe yazmak hakkında bilmeniz gereken her şeyi öğreneceksiniz.
Ne Zaman Özel Öğe Yazmalı, Ne Zaman Yazmamalı
Özel öğe, formların sunduğu en güçlü araçtır. Ve her güçlü araç gibi, ilk değil son seçenek olmalıdır. Pek
çok durum daha basit yollarla çözülebilir:
- Bir değeri değiştirmeyi addFilter() üstlenir. Posta kodunda
boşluklara ya da bir kodda küçük harflere göz yummak mı istiyorsunuz? Bir filtre birkaç satırdır.
- Yinelenen yapılandırmayı özel bir ekleme metodu sarmalar. On yerde aynı doğrulamayla posta kodu alanı mı
ekliyorsunuz? Onlar için adlandırılmış bir kısayol oluşturun, sonunda
göstereceğiz.
- Birbiriyle ilişkili bir alan grubuna bir container hizmet eder. Sokak, şehir ve posta kodundan oluşan
bir adres için özel öğe gerekmez, üç metin alanlı bir container yeter.
- Farklı bir görünüm setHtmlType() ve HTML
nitelikleriyle ya da prototiplerle elde edilir.
Özel bir öğe, özel bir değere ihtiyaç duyduğunuz anda anlam kazanır: dışarıdan tek değerli tek bir alan gibi
davranan, ama içeride birkaç input'tan oluşan ya da değeri gösterdiğinden farklı biçimde saklayan bir öğe. Üç alandan
oluşan bir tarih. Haritaya tıklanarak seçilen koordinatlar. Otomatik tamamlamalı bir etiket girişi.
Bir Öğenin Anatomisi
Her özel öğe, soyut Nette\Forms\Controls\BaseControl sınıfından
türer. Ondan çok büyük miktarda hazır işlev miras alır: değer saklama, doğrulama kuralları ve koşulları, hata
mesajları, çeviriler, HTML nitelikleri, etiket ve render bağlantısı. Siz yalnızca öğenizi farklı kılan şeyi
yazarsınız.
Çalışan en küçük öğe şaşırtıcı derecede kısadır:
use Nette\Forms\Form;
use Nette\Forms\Helpers;
use Nette\Utils\Html;
class SimpleInput extends Nette\Forms\Controls\BaseControl
{
public function loadHttpData(): void
{
$this->setValue($this->getHttpData(Form::DataLine));
}
public function getControl(): Html
{
return Html::el('input', [
'type' => 'text',
'name' => $this->getHtmlName(),
'id' => $this->getHtmlId(),
'value' => $this->getValue(),
'data-nette-rules' => Helpers::exportRules($this->getRules()) ?: null,
]);
}
}
İki metot: biri değerin gönderilen veriden nasıl alınacağını, öteki öğenin nasıl render edileceğini söyler.
Birazdan ikisine de yakından bakacağız. Geri kalan her şey (setRequired(), addRule(),
setDefaultValue(), çeviriler) kendiliğinden çalışır.
Öğeyi forma addComponent() metoduyla ya da daha kısaca köşeli parantezlerle eklersiniz:
$form['nickname'] = new SimpleInput('Takma ad:');
Bir Öğenin Yaşam Döngüsü
Daha ilginç bir öğeye geçmeden önce, bir öğeye ne zaman ne olduğunu bilmek iyidir. Form ve öğeleri, bir ağaç
oluşturan bileşenlerdir. Bunun hoş bir sonucu var: öğenin hiçbir
şeyi kendi başına bulması gerekmez, önemli olan her şeyi framework doğru anda halleder:
- Öğeyi gönderilmiş bir forma eklediğiniz anda, formun kendisi onda
loadHttpData() metodunu çağırır. Öğe
orada, birazdan göstereceğimiz gibi, gönderilen değerini okur. Asla doğrudan $_POST ile çalışmaz ve
container'ların içinde iç içe olup olmadığını hiç dert etmez.
- Form gönderildiğinde doğrulama gerçekleşir:
addRule() ile eklenen kurallar, getValue()
metodundan gelen değerle çalışarak değerlendirilir.
- Sonra
$form->getValues() ya da öğede getValue() çağıran kişi, temiz ve türlenmiş bir
değer alır; örneğin formdan gelen üçlü dize değil, bir DateTimeImmutable nesnesi.
Render sırasında ise getControl(), etiket içinse getLabel() çağrılır.
Gönderilen Değeri Okuma
loadHttpData() metodunda öğe, gönderilen değerini getHttpData() metoduyla ister. Parametresi,
değerin nasıl temizleneceğini belirleyen bir türdür:
| tür |
anlamı |
Form::DataLine |
tek satırlı metin: satır sonlarını boşlukla değiştirir, boşlukları kırpar |
Form::DataText |
çok satırlı metin: satır sonlarını \n biçimine normalleştirir |
Form::DataFile |
yükleme, bir Nette\Http\FileUpload örneği |
Bir saldırgan ne kadar uğraşırsa uğraşsın, sonuç her zaman denetim karakterleri olmayan geçerli bir UTF-8 dizesidir
(ya da bir yükleme nesnesi veya null). Değeri doğrudan $_POST içinden okumamamızın nedeni tam da
budur; tüm bu güvenceleri yitirirdik.
Bizim tarihimiz gibi birkaç input'tan oluşan bir öğe, HTML adının bir parçasını ikinci parametre olarak verir ve tek
tek alt değerlerini böyle okur. Onları kendi $day, $month ve $year dize özelliklerinde
saklar:
public function loadHttpData(): void
{
$this->day = $this->getHttpData(Form::DataLine, '[day]') ?? '';
$this->month = $this->getHttpData(Form::DataLine, '[month]') ?? '';
$this->year = $this->getHttpData(Form::DataLine, '[year]') ?? '';
}
HTML adı [] ile biterse bir değer dizisi döndürülür. Form::DataKeys türüyle birleştirerek
(yani Form::DataLine | Form::DataKeys) anahtarlarını da korursunuz:
$tags = $this->getHttpData(Form::DataLine, '[tags][]');
Eksik bir değer null olur (dizilerde boş dizi). İstek, öğenin verisini hiç içermek zorunda değildir;
hiçbir şey bir saldırganın canının istediğini göndermesini engellemez. Örnekte ?? '' eklememizin ve bu
olasılığı her zaman hesaba katmanız gerektiğinin nedeni budur.
Öğenin Değeri
Öğe değerini tutar ve onu, sözleşmesine uyulması gereken üç metotla dışa açar.
setValue() metodu programcıdan bir değer alır; setDefaultValue() ve
$form->setDefaults() de bu yolu izler. Anlamlı olan her şeyi kabul etmeli, değeri iç biçimine
dönüştürmeli ve anlamsız girdide istisna fırlatmalıdır; böylece hata, formun gizemli davranışlarıyla değil hemen
ortaya çıkar. Bizim tarihimiz bir DateTimeInterface, bir dize, bir zaman damgası ya da null kabul
eder ve bunları üç alana böler:
public function setValue(mixed $value): static
{
if ($value === null) {
$this->day = $this->month = $this->year = '';
} else {
$date = Nette\Utils\DateTime::from($value); // saçmalık istisna fırlatır
$this->day = $date->format('j');
$this->month = $date->format('n');
$this->year = $date->format('Y');
}
return $this;
}
getValue() metodu ise temiz, türlenmiş bir değer kurar; öğenizin kullanıcısının göreceği tek şey
budur. Değer geçerli değilse null döndürür. Statik validateDate() metodu yalnızca üç alanın
var olan bir tarih oluşturup oluşturmadığını denetler:
public function getValue(): ?DateTimeImmutable
{
return self::validateDate($this)
? (new DateTimeImmutable)->setDate((int) $this->year, (int) $this->month, (int) $this->day)->setTime(0, 0)
: null;
}
isFilled() metodu ise kullanıcının öğeyi doldurup doldurmadığını söyler; onu setRequired()
kuralı kullanır. Varsayılan gerçekleştirim (boş olmayan bir değer) çoğu zaman yeter, ama bileşik bir öğede onu kendi
mantığına göre geçersiz kılın:
public function isFilled(): bool
{
return $this->day !== '' || $this->year !== '';
}
Render
getControl() metodu, öğenin HTML biçimini genellikle bir Html nesnesi olarak döndürür, ama düz bir dize de olur, fark etmez.
Html nesnesine başlıca kodu kurarken başvururuz; çünkü ortaya çıkan işaretlemeyi güvenli ve keyifli bir API ile
kurmamızı sağlar. Elinizin altında çeşitli yardımcılar var:
getHtmlName(), container'lardaki olası iç içe geçme de dahil olmak üzere HTML name niteliğini
döndürür (örneğin invoice[date]). Bileşik bir öğede tek tek input'ların ad parçalarını buna eklersiniz:
$name . '[day]'.
getHtmlId(), etiketle bağlanan id niteliğini döndürür.
Helpers::exportRules($this->getRules()), doğrulama kurallarını data-nette-rules niteliği
için dışa aktarır; bu sayede JavaScript
doğrulaması sizin öğenizde de çalışır. Nitelik, öğenin ilk input'una konur.
Helpers::createSelectBox($items, $optionAttrs, $selected), bir öğe dizisinden <select>
elemanı kurar (iç içe diziler <optgroup> olarak render edilir) ve onu Html olarak döndürür;
tarihimizin ay alanı için kullanışlıdır.
Helpers::createInputList($items, $inputAttrs, $labelAttrs), <label> içine sarılmış
<input> elemanlarından oluşan bir liste (radyo düğmeleri ya da onay kutuları) üretir ve onu dize olarak
döndürür.
Tarihimizin ilk alanı böylece şöyle oluşturulur:
public function getControl(): Html
{
$name = $this->getHtmlName();
return Html::el()
->addHtml(Html::el('input', [
'name' => $name . '[day]',
'id' => $this->getHtmlId(),
'value' => $this->day,
'type' => 'number',
'data-nette-rules' => Helpers::exportRules($this->getRules()) ?: null,
]))
->addHtml(/* ... ay için select ve yıl için input ... */);
}
Etiket getLabel() ile render edilir ve varsayılan gerçekleştirimi genellikle uygundur. Yalnızca dikkat:
bileşik bir öğede onun for niteliği getHtmlId() değerine işaret eder, bu yüzden bu id'yi ilk
input'a verin; tam da örnekteki gibi.
Bileşik öğenin şablonda parça parça render edilebilmesi için (örneğin {input birthdate:day}), ilgili
parça için Html elemanını döndüren getControlPart($key) ve getLabelPart($key)
metotlarını geçersiz kılın; CheckboxList ve RadioList de böyle yapar.
getControl() metodunu geçersiz kılarsanız, BaseControl::getControl() metodunun aynı
zamanda setOption('rendered', true) ile öğeyi render edilmiş olarak işaretlediğini unutmayın. Aynı formda elle
ve otomatik render'ı birleştirdiğinizde onu da çağırın (ya da parent::getControl() çağırın); böylece
öğe iki kez render edilmez. (Yukarıdaki DateInput örneği bunu kısalık için atlıyor.)
Anlatılan tüm parçalar bir arada, ayın seçilmesi için bir select box'la da tamamlanmış hâlde, bitmiş
DateInput öğesinde, doğrudan
depodaki örnekler arasında bulunabilir.
Öğenin yapıcıda kendisine, tarihin anlamlı olup olmadığını denetleyen bir doğrulama kuralı eklediğine dikkat edin.
Böylece 31 Şubat gibi anlamsız bir girdi, sıradan bir form doğrulama hatası olarak ortaya çıkar:
public function __construct($label = null)
{
parent::__construct($label);
$this->addRule(self::validateDate(...), 'Tarih geçersiz.');
}
Peki kullanımı? Tam olarak yerleşik öğelerdeki gibi:
$form['birthdate'] = (new DateInput('Doğum tarihi:'))
->setDefaultValue(new DateTime('2000-01-01'))
->setRequired('Ne zaman doğdunuz?');
$date = $form->getValues()->birthdate; // ?DateTimeImmutable
Latte şablonunda onu, başka herhangi bir öğe gibi, alışıldık {input birthdate} ya da
{label birthdate /} etiketiyle render edersiniz.
Doğrulama
Yerleşik doğrulama kuralları özel bir öğeyle hemen çalışır; getValue() metodundan gelen değer üzerinde
işlem yaparlar. Böylece DateInput öğemiz örneğin izin verilen en eski tarih için Form::Min
kullanabilir. JavaScript karşılığı da dahil olmak üzere kendi kurallarınızı nasıl yazacağınız Özel kurallar ve koşullar bölümünde
anlatılıyor.
Özel Ekleme Metodu
Yerleşik öğeleri $form->addText() ve benzeri elverişli metotlarla ekleriz. Özel bir öğenin böyle bir
metodu yoktur, bu yüzden onu düz atamayla eklersiniz; hem formda hem container'da aynı şekilde çalışır ve düzenleyiciler
ile statik çözümleme bunu anlar:
$form['birthdate'] = new DateInput('Doğum tarihi:');
Otomatik tamamlamayı korurken eklemeyi kısaltmak isterseniz, doğrudan öğenin üzerindeki statik bir factory metodu işe
yarar. Form sınıfının bir torunundaki metodun yapamayacağı şeyi, iç içe container'larda bile çalışmayı
başarır; iç içe container'lar ondan habersizdir:
class DateInput extends Nette\Forms\Controls\BaseControl
{
public static function addTo(
Nette\Forms\Container $container,
string $name,
?string $label = null,
): self {
return $container[$name] = new self($label);
}
}
// formda ve herhangi bir container'da çalışır:
DateInput::addTo($form, 'birthdate', 'Doğum tarihi:');
Aynı yaklaşım, yerleşik bir öğenin yinelenen yapılandırması için adlandırılmış bir kısayol olarak da
işe yarar:
final class ZipInput
{
public static function addTo(
Nette\Forms\Container $container,
string $name,
?string $label = null,
): Nette\Forms\Controls\TextInput {
return $container->addText($name, $label)
->addRule(Nette\Forms\Form::Pattern, 'Posta kodu tam olarak 5 rakam olmalıdır', '[0-9]{5}');
}
}
ZipInput::addTo($form, 'zip', 'Posta kodu:');