CheckBox/CheckBoxList, input mimo createComponent

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Bernard Williams
Člen | 207
+
0
-

Nazdárek,

narazil jsem na několik problému, které se mi nedaří vyřešit.

  1. Nechápu, proč u Checkboxu není metoda setValue(). Absence nastavení hodnoty Checkboxu snižuje použitelnost tohoto prvku o několik desítek procent. Např. v admin sekcí se bez tohoto prvku prakticky neobejdete – zaškrtnutí položek, které uživatel chce změnit/vymazat atd. Jak jinak to řešit?
  2. Na fóru jsem nalezl, že problém 1) řeší tzv CheckBoxList. Bohužel s tímto novým prvkem jsem narazil na další problém. Jak je můžu ručně vykreslit jednotlivé zaškrtávací políčka? Jak můžu umístit přesně tam, kam chci? Řešení přes getControls() se mi na CheckBoxList zatím nepodařilo napasovat.
  3. Poslední problém nastal v případě, kdy nějaký prvek formuláře nevytvořím hned v createComponent, ale až třeba v pohledu. Obsah těchto prvků se vůbec neodešle – odešlou se jen obsahy prvků, které byly vytvořeny v createComponent. Př:
protected function createComponent($name) {
    switch ($name) {
        case 'smazatFotkyForm':
        $form = new AppForm($this, $name);
        $form->addSubmit('smazat', 'Smazat vybrané');
        $form->addText('text', 'neco');
        $form->onSubmit[] = array($this, 'smazatFotkyFormSubmitted');
            return;
        default:
            parent::createComponent($name);
    }
}

Z takové formuláře se prvek text odešle – to je v pořádku. Když ale v pohledu přidám k formuláři další prvek – např. text2, tak už se jeho obsah neodešle.

$form = $this->getComponent('smazatFotkyForm');
$form->addText('text2', 'neco2');

Je to chyba nebo je to snad záměr?

Děkuji
Bernard

Honza Kuchař
Člen | 1662
+
0
-
  1. Je to záměr. Form není hloupý generátor html kódu, proto musí vědět už při vytváření, které políčka má ve formuláři.
Bernard Williams
Člen | 207
+
0
-

honzakuchar: Jde to nějak obejít? Když potřebuji vytvářet prvky formuláře až v pohledu..

Panda
Člen | 569
+
0
-

2. bod by mělo jít vyřešit nějak takto (kód pro šablonu):

{foreach $form['checkboxlist']->getItems() as $key => $label}
	{$form['checkboxlist']->getControl($key)} {$label}
{/foreach}

3. bod: formulář samozřejmě upravit lze, ale musíš to udělat před zpracováním signálů, tedy ne v metodě render<action>(), ale v metodě action<action>().

Bernard Williams
Člen | 207
+
0
-

Myslím, že v té třídě CheckboxList je asi chybka. S definicí:

$items = array(
    1 => 'item1',
    2 => 'item2',
);

$form->addCheckboxList('demo', 'Choices', $items)
    ->addRule('CheckboxList::validateChecked', 'Check something!');

Mi kód:

foreach ($form['demo']->getItems() as $key => $label) {
    echo $form['demo']->getControl($key).' ['.$label.']';
}

Vrátí tohle:

<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-0" value="1" /><label for="frmsmazatFotkyForm-demo-0">item1</label> [item1]
<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-1" value="2" /><label for="frmsmazatFotkyForm-demo-1">item2</label> [item2]

Místo předpokládaného výstupu:

<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-0" value="1" /> [<label for="frmsmazatFotkyForm-demo-0">item1</label>]
<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-1" value="2" /> [<label for="frmsmazatFotkyForm-demo-1">item2</label>]

Kdyby nebyl na první pohled patrný rozdíl, tak $label z foreach cyklu obsahuje item2 místo <label for="frmsmazatFotkyForm-demo-1">item2</label> a control obsahuje taky label místo samotného controlu.

Panda
Člen | 569
+
0
-

Bernard Williams napsal(a):

Myslím, že v té třídě CheckboxList je asi chybka. S definicí:

$items = array(
    1 => 'item1',
    2 => 'item2',
);

$form->addCheckboxList('demo', 'Choices', $items)
    ->addRule('CheckboxList::validateChecked', 'Check something!');

Mi kód:

foreach ($form['demo']->getItems() as $key => $label) {
    echo $form['demo']->getControl($key).' ['.$label.']';
}

Vrátí tohle:

<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-0" value="1" /><label for="frmsmazatFotkyForm-demo-0">item1</label> [item1]
<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-1" value="2" /><label for="frmsmazatFotkyForm-demo-1">item2</label> [item2]

Místo předpokládaného výstupu:

<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-0" value="1" /> [<label for="frmsmazatFotkyForm-demo-0">item1</label>]
<input type="checkbox" name="demo[]" id="frmsmazatFotkyForm-demo-1" value="2" /> [<label for="frmsmazatFotkyForm-demo-1">item2</label>]

Kdyby nebyl na první pohled patrný rozdíl, tak $label z foreach cyklu obsahuje item2 místo <label for="frmsmazatFotkyForm-demo-1">item2</label> a control obsahuje taky label místo samotného controlu.

Pardon, ve zdrojáku jsem si nevšiml, že metoda CheckboxList::getControl($key) vrací checkbox i s labelem. Pokud to chceš rozdělit, tak vytvoř zhruba následujícího potomka třídy CheckboxList:

<?php
class MyCheckboxList extends CheckboxList
{
	private $baseLabel;

	protected function getBaseLabel()
	{
		if ($this->baseLabel === NULL)
			$this->baseLabel = /*Nette\Web\*/Html::el('label');
		return $this->baseLabel;
	}

	public function getControl($key = NULL)
	{
		if ($key === NULL) {
			$container = clone $this->container;
			$separator = (string) $this->separator;

		} elseif (!isset($this->items[$key])) {
			return NULL;
		}

		$control = parent::getControl();
		$control->name .= '[]';
		$id = $control->id;
		$counter = -1;
		$values = $this->value === NULL ? NULL : (array) $this->getValue();
		$label = $this->getBaseLabel();

		foreach ($this->items as $k => $val) {
			$counter++;
			if ($key !== NULL && $key != $k) continue; // intentionally ==

			$control->id = $label->for = $id . '-' . $counter;
			$control->checked = (count($values) > 0) ? in_array($k, $values) : false;
			$control->value = $k;

			if ($val instanceof /*Nette\Web\*/Html) {
				$label->setHtml($val);
			} else {
				$label->setText($this->translate($val));
			}

			if ($key !== NULL) {
				return (string) $control;
			}

			$container->add((string) $control . (string) $label . $separator);
		}

		return $container;
	}

	public function getLabel($key = NULL)
	{
		if ($key === NULL)
			return parent::getLabel();

		$id = $this->getHtmlId();
		$counter = -1;
		$label = $this->getBaseLabel();

		foreach ($this->items as $k => $val) {
			$counter++;
			if ($key != $k) continue;
			$label->for = $id . '-' . $counter;

			if ($val instanceof /*Nette\Web\*/Html) {
				$label->setHtml($val);
			} else {
				$label->setText($this->translate($val));
			}

			return (string) $label;
		}
	}
}
?>

Kód pak bude následující:

foreach ($form['demo']->getItems() as $key => $label) {
    echo $form['demo']->getControl($key).' ['. $form['demo']->getLabel($key) .']';
}

PS: Poslední dobou mi to nějak nepálí, takže pokud tam zas bude nějaká bota, tak prosím moc nekamenovat.

Editoval Panda (26. 8. 2009 14:10)

Bernard Williams
Člen | 207
+
0
-

Panda: Děkuju, getControl jsem si dokázal upravit, ale s tím getLabel jsem měl problém – ještě jednou díky.. ale! Máš tam chybku :-) Takto upravený getLabel hází chyby. Chybí ti tam $control = parent::getControl(); takže výsledná metoda třídy MyCheckboxList by měla vypadat takto:

	public function getLabel($key = NULL)
	{
		$control = parent::getControl();

		$id = $control->id;
		$counter = -1;
		$label = /*Nette\Web\*/Html::el('label');

		foreach ($this->items as $k => $val) {
			$counter++;
			if ($key != $k) continue;
			$label->for = $id . '-' . $counter;

			if ($val instanceof /*Nette\Web\*/Html) {
				$label->setHtml($val);
			} else {
				$label->setText($this->translate($val));
			}

			return (string) $label;
		}
	}

Ale to už jsou detaily.. Takže do třetice: Moc děkuju za pomoc.

Panda
Člen | 569
+
0
-

Bernard Williams napsal(a):

Panda: Děkuju, getControl jsem si dokázal upravit, ale s tím getLabel jsem měl problém – ještě jednou díky.. ale! Máš tam chybku :-) Takto upravený getLabel hází chyby. Chybí ti tam $control = parent::getControl();

Děkuji za upozornění, ještě jsem to ve svém původním příspěvku kousek upravil – ten $control tam v podstatě vůbec není potřeba, volání $control->id lze nahradit za $this->getHtmlId(), což bude z hlediska výkonu mnohem lepší (nedochází k zbytečnému klonování objektů, viz FormControl::getControl()). Navíc jsem umožnil volání getLabel bez parametru $key (vrátí popisek celé sady checkboxů) a přidal kód, aby se základní objekt pro label vytvořil jen při prvním použití, což by mělo mít lehký pozitivní dopad na výkon při vykreslování většího počtu prvků.

//Doplnění: tak jsem ještě řešení kousek poupravil.

Editoval Panda (26. 8. 2009 14:15)