Taky vás nebaví stále dokola psát chyby validací v NETTE FORMS?

Jistě to všichni známe. Stále dokola se opakující zprávy: „Pole „Jméno“ musí být vyplněno!“, „Pole „Příjmení“ musí být vyplněno!“, … A co když najednou se nám tato zpráva přestane líbit a chceme ji nahradit? Nezbývá nic jiného než všechny hlášky projít a přepsat je.

Ale ono to jde i jednodušeji. Stačí třeba v bootstrapu si připravit masku těchto zpráv a je vystaráno.

Rules::$defaultMessages = array(
	Form::FILLED => 'Položka „%label“ musí být vyplněna.',
	Form::EMAIL => 'Položka „%label“ nemá správný formát.',
	Form::URL => 'Položka „%label“ nemá správný formát.',
	Form::MAX_LENGTH => 'Položka „%label“ je příliš dlouhá.',
	Form::INTEGER => 'Položka „%label“ musí být celé číslo.',
	Form::REGEXP => 'Neplatný formát položky „%label“.',
);

V presenteru pak stačí…

...
$form->addText("name", "Jméno:")
	->addRule(Form::FILLED);
...

Generování vícejazyčného formuláře v NETTE

Občas je potřeba mít vícejazyčnou strukturu menu (kategorií, …) s tím, že předem nevím kolik jazyků budu potřebovat, ale zároveň chci aby administrace s tím počítala.
Jak takový automaticky generovaný formulář napsat v NETTE?

Struktura tabulek databáze:

Presenter (továrnička na generování formuláře):

public function createComponentCat() {
	// vytáhneme si všechny jazyky
	$langs = dibi::fetchAll('SELECT [id], [code] FROM [languages] ORDER BY [code]');
 
	$form = new AppForm;
	// * (budu se na toto místo dále odkazovat v textu)
	foreach ($langs as $lang) {
		$form->addText("name_{$lang['id']}", "Jméno kategorie [{$lang['code']}]:")
		    ->addRule(Form::FILLED);
	}
	// *
 
	// tyto skryté pole se budou hodit pro editaci
	$form->addHidden('id');
	$form->addHidden('parent_id');
	$form->addSubmit('send', ' Uložit ');
 
	$form->onSubmit[] = array($this, 'formCatSubmitted');
 
	return $form;
}

* – tady mohou pro více polí nastat dvě situace:
1/ chceme jazykově oddělené atributy mít u sebe
Pak musíme pro každý atribut vložit tento cyklus. Takže pět atributů znamená pět pod sebou těchto cyklů.
2/ chceme atributy seskupovat podle jazyků
V tomto případě nám stačí jeden cyklus a v něm nasázené všechny atributy.

O výkonu zde nemá cenu se dohadovat. Jedná se o administraci a tam nikdy nebude velký nával.

Presenter (zpracování formuláře):

public function formCatSubmitted($form) {
	$val = $form->getValues();
	# TABLE: categories
	$categories['id'] = $val['id'];
	$categories['parent_id'] = $val['parent_id'];
 
	if ($categories['id'] == 0) { // jedná se o nový záznam
		// vytáhneme si level vkládaného záznamu
		// level je hloubka zanoření ve stromové struktuře
		$categories['level'] = ($categories['parent_id'] == 0) ? 1 : dibi::fetchSingle('SELECT [level]+1 FROM [categories] WHERE [id]=%i LIMIT 1', $categories['parent_id']);
		// vytáhneme si position vkládaného záznamu (u nového záznamu budeme vkládat novou kategorii až na konec)
		// position je pořadí v dané kategorii
		$categories['position'] = dibi::fetchSingle('SELECT [position]+1 FROM [categories] WHERE [parent_id]=%i ORDER BY [position] DESC LIMIT 1', $categories['parent_id']);
		// oprava při nule pokud vkládáme první záznam
		$categories['position'] = ($categories['position'] == 0) ? 1 : $categories['position'];
		dibi::query('INSERT INTO [categories]', $categories);
		$categories_translations['categories_id'] = dibi::insertId();
	} else {
		dibi::query('UPDATE [categories] SET', $categories, 'WHERE [id]=%i LIMIT 1', $categories['id']);
		$categories_translations['categories_id'] = $categories['id'];
	}
 
	# TABLE: categories_translations
	$langs = dibi::fetchAll('SELECT [id] FROM [languages]');
 
	if ($categories['id'] == 0) { // jedná se o nový záznam
		foreach ($langs as $lang) {
			$categories_translations['language_id'] = $lang['id'];
			$categories_translations['name'] = $val["name_{$lang['id']}"];
			dibi::query('INSERT INTO [categories_translations]', $categories_translations);
		}
	} else {
		$categories_id = $categories_translations['categories_id'];
		unset($categories_translations['categories_id']);
		foreach ($langs as $lang) {
			$categories_translations['name'] = $val["name_{$lang['id']}"];
	    		dibi::query('UPDATE [categories_translations] SET', $categories_translations, 'WHERE [language_id] = %i AND [categories_id] = %i LIMIT 1', $lang['id'], $categories_id);
 
			// tady je ještě třeba vyřešit situaci, kdy již máme naplněnou databázi kategoriemi a přidáme nový záznam do tabulky jazyků. Update by nebylo nad čím provést, proto musíme nové záznamy pro nové jazyky INSERTnout do tabulky překladů.
			// zjistíme si, zda nám update něco ovlivnil (ANO - záznam existuje, NE - jedná se o nový záznam)
			$info = dibi::getConnection()->driver->getInfo();
			if ($info['Rows matched'] === 0) {
				$categories_translations['language_id'] = $lang['id'];
				$categories_translations['categories_id'] = $categories_id;
				dibi::query('INSERT INTO [categories_translations]', $categories_translations);
			}
		}
	}
 
	$this->flashMessage('Data uložena.', 'info');
	$this->redirect('Category:default', $this->getParam('parent_id'));
}

A teď už jen pro pořádek zbývá uvést metody presenteru Add a Edit:

public function renderAdd($parent_id) {
	$this['cat']->setDefaults(array('id' => 0, 'parent_id' => $parent_id));
	$this->template->form = $this['cat'];
}
 
public function renderEdit($id, $parent_id) {
	$this->setView('add');
 
	// vytáhneme si data
	$rows = dibi::fetchAll('SELECT *, [b].[id] AS [bid] FROM [categories_translations] [a] JOIN [categories] [b] ON [a].[categories_id] = [b].[id] WHERE [b].[id] = %i ORDER BY [language_id]', $id);
 
	// začneme generovat pole pro naplnění formuláře pro editaci
	$arr = array();
	foreach ($rows as $row) {
		$arr["name_{$row['language_id']}"] = $row['name'];
	}
	$arr['id'] = $row['bid'];
	$arr['parent_id'] = $row['parent_id'];
 
	$this['cat']->setDefaults($arr);
	$this->template->form = $this['cat'];
}

Výsledek takového formuláře pak vypadá pro tři jazyky a více polí třeba takto:

Generování barevného modelu RGB pomocí PHP

Kamarád chtěl poradit jak toto vyřešit. Chvilku jsem dumal a nakonec jsem vyplodil následující kód.

/* 2D plochy 3D RGB modelu */ 
echo "<table>"; 
for ($l = 0; $l < = 250; $l=$l+10) { 
    echo "<tr><td style='background: rgb(" . $l . ","; 
    for ($j = 0; $j <= 250; $j=$j+10) { 
        if ($j != 0) { 
            echo "<td style='background: rgb(" . $l . ","; 
        } 
        echo $j . ","; 
        for ($k = 0; $k <= 250; $k=$k+10) { 
            if ($k != 0) { 
                echo "<td style='background: rgb(" . $l . "," . $j . ","; 
            } 
            echo $k . ")'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>"; 
        } 
        echo ""; // nevím proč se to tu nezobrazuje, ale tu má být ukončení TR
    } 
} 
echo "</table>"; 
 
/* odstiny sede */ 
echo "<table><tr>"; 
for ($l = 0; $l < = 250; $l=$l+10) { 
    echo "<td style='background: rgb(" . $l . "," . $l . "," . $l . ")'> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"; // nevím proč se to tu nezobrazuje, ale tu má být ukončení TD
} 
echo "</tr></table>";

Validace uživatelského jména v NETTE

Většinou chceme aby uživatelské jméno bylo složeno jen z písmen, čísel a maximálně dovolíme znaky „-“ a „_“. V NETTE takový prvek formuláře můžeme definovat následovně:

$form->addText('username', 'Uživatelské jméno')
	->addRule(Form::REGEXP, '„Uživatelské jméno“ musí začínat písmenem a může obsahovat jen písmena, čísla a znaky "-", "_".', '/^[a-z][a-z0-9_-]+$/i');

Vysvětlení regulárního výrazu:
/^ – značí začátek
[a-z] – očekáváme znak a-z či A-Z
[a-z0-9_-] – očekáváme znaky a-z, A-Z, 0-9, znaky „-“ a „_“
+ – váže se k předchozí definici a říká nám, že znak definovaný pomocí [a-z0-9_-] se musí vyskytovat 1 až n-krát (znak * by nám naznačoval že výskyt může být i 0)
$/ – značí konec řetězce
i – značí, že celý výraz je CASE INSENSITIVE neboli že nezáleží na velikosti písmen. Tudíž nemusíme psát [a-zA-Z] ale stačí nám jednoduché [a-z]

Tento výraz nám v tomto zápisu ještě zajistí, že Uživatelské jméno bude dlouhé minimálně 2 znaky. Samozřejmě se dá regulárním výrazem řešit i povinná délka, ale dle mě už pak nejsme schopni předat uživateli přesnou odpověď kde v zadání udělal chybu.

iPhone 4

Na sklonku minulého roku jsem se stal hrdým majitelem tohoto telefonu. Jsem s ním spokojený až na drobné problémy s některými operacemi, které je třeba díky malému počtu ovládacích prvků dělat neintuitivně.

V drobných příspěvcích, které tu budu zveřejňovat se podělím o nesnáze, kterými jsme sám procházel.

Doufám, že se k tomu budu dostávat častěji než jednou za měsíc.

Captcha pro NETTE 2.0 (PHP 5.2)

Dneska jsem narazil na problém s přidáním Captchy do NETTE. Plugin z Nette Addons je nefunkční a dle vlákna na NETTE fóru se již delší dobu o to nikdo nestará. Pročítáním vlákna dále jsem narazil na aktualizovanou verzi od Pandy, ale ten to má zase optimalizované pro PHP 5.3. Takže jsem dále jel chybu po chybě vesměs dle fóra a nakonec jsem odstranil poslední vadu a zde přikládám výsledek.

NETTE 2.0, Captcha for PHP 5.2