IFormRenderer beforeRender
- Pipaslot
- Člen | 19
Snažím se aplikovat renderování šablony, například pro Bootstrap do formulářů ale narazil jsem na problém při manuálním vykreslování. Původní myšlenku renderování šablon například pro bootstrap jsem zapsal do třídy rendereru dědícího od DefaultFormRenderer
class Bootstrap3Renderer extends DefaultFormRenderer
{
/**
* Bootstrap3Renderer constructor.
*/
public function __construct()
{
$this->wrappers['controls']['container'] = NULL;
$this->wrappers['pair']['container'] = 'div class=form-group';
$this->wrappers['pair']['.error'] = 'has-error';
$this->wrappers['control']['container'] = 'div class=col-sm-9';
$this->wrappers['label']['container'] = 'div class="col-sm-3 control-label"';
$this->wrappers['control']['description'] = 'span class=help-block';
$this->wrappers['control']['errorcontainer'] = 'span class=help-block';
$this->wrappers['error']['container'] = 'div class="col-sm-9 col-sm-offset-3"';
$this->wrappers['error']['item'] = 'div class="alert alert-danger" role="alert"';
}
/**
* Provides complete form rendering.
* @param Form $form
* @return string
*/
function render(Form $form)
{
$usedPrimary = FALSE;
foreach ($form->getControls() as $control) {
if ($control instanceof Controls\Button) {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn btn-default');
$usedPrimary = TRUE;
} elseif ($control instanceof Controls\TextBase || $control instanceof Controls\SelectBox || $control instanceof Controls\MultiSelectBox) {
$control->getControlPrototype()->addClass('form-control');
} elseif ($control instanceof Controls\Checkbox || $control instanceof Controls\CheckboxList || $control instanceof Controls\RadioList) {
$control->getSeparatorPrototype()->setName('div')->addClass($control->getControlPrototype()->type);
}
}
return parent::render($form);
}
}
Toto využívám spolu s továrnou na formuláře, která mi vrátí
předchystaný formulář i s nasaveným rendererem. Zatím je vše
v pohodě.
Když poté v šabloně zavolám {control myForm}, tak se formulář vykreslí
se vší parádou, jelikož se zavolalo na rendereru render(Form $form) , kde se
projdou všechny prvky a doupraví se k obrazu šablony.
Komplikovat se to začíná, když formulář chci podrobit manuálnímu vykreslování, takže zavolám:
{form myForm}
{foreach $form->getComponents() ...}
... vykreslím formulářová pole ...
{/foreach}
{/form}
Bohužel při manuální vykreslování se mi žádným způsobem nezkontaktuje renderer, který by mi právě prvky přidané do formuláře až po nastavení rendereru dodatečně upravil.
Napadlo mě tedy, přidal do rozhranní IFormRenderer metodu
beforeRender(Form $form), která by se poté volala jak v metodě
Nette\Forms\Form::render, tak
v Nette\Bridges\FormsLatte\Runtime::renderFormBegin ,která se spouští při
vyžádání vykreslení elementu formuláře.
Tím bycho mohli přesunout kód z metody render u rendereru, který se stará
o vykreslení celého formuláře do metody beforeRender.No a výchozí
implementace u třídy DefaultFormRenderer by byla prázdná, tak aby to bylo
kompatibilní.
Co vy na to? Je v tom nějaká záludnost kterou nevidím?
- F.Vesely
- Člen | 369
Pouzij https://github.com/…Renderer.php, stejnak uz to mas az nenapadne podobne. :)
- Pipaslot
- Člen | 19
Pokud chápu tvé řešení správně, tak bych poté v šabloně vždy
musel volat místo makra {input …} a {label …} něco jako:
$form->getRenderer()->renderControl($control) .
Tomu jsem se ale chtěl vyhnout.
Nicméně stále tam vidím záludnost a to že při inicializaci prvků
potřebuješ předat formulář, který se předá až metodou render, jenže ta
se při manuálním vykreslení nespustí.
Napadá me ještě přepsání maker pro formuláře, tak aby se spustila metoda
render ale druhým parametrem bych řekl že se má jen zinicializovat a nikoli
vykreslit. Nicméně mi to připadá moc krkolomné.
- David Matějka
- Moderator | 6445
@F.Vesely macro input se o to nepostara, to nezavola nic z rendereru: https://github.com/…rmMacros.php#…
- Pipaslot
- Člen | 19
Teď jsem zkoušel na sandboxu příklad:
class HomepagePresenter extends BasePresenter
{
public function createComponentForm1()
{
$form = new Nette\Application\UI\Form();
$form->addText("name");
$form->addSubmit("submit");
$form->setRenderer(new Bs3FormRenderer());
return $form;
}
public function createComponentForm2()
{
$form = new Nette\Application\UI\Form();
$form->addText("name");
$form->addSubmit("submit");
$form->setRenderer(new Bs3FormRenderer());
return $form;
}
}
S šablonou:
{* Funguje*}
{control form1}
{* Nefunguje*}
{form form2}
{input name}
{input submit}
{/form}
a první formulář se vykreslí podle rendereru, zatím co druhý nikoli. Platí přesně poznámka Davida Matějky.
Nicméně jsem zksil nalinkovat makra, které uvádíš ve svém repozitáři
a poté se oba formuláře začaly vykreslovat podobně.
Vidím v tom ale jednu neduhu, a to že kromě rendereru si musí človek
ještě hlídta , zda má nalinkované správné makra. Bohužel využívám
vícero různých rendererů v jedné aplikaci, takže to takto nemohu
použít.
- F.Vesely
- Člen | 369
No jo, mas pravdu, ono to i dava smysl, aby se nevolalo nic z rendereru,
kdyz mas manualni render.
Ty budes muset nejak zaradit, aby kdyz se nastavi formulari Renderer, tak se
vsem inputum pridaly ty tridy, ktere potrebujes. Jo a nove pridanym inputum po
nastaveni Rendereru taky.
- Pipaslot
- Člen | 19
Tak jako nejschůdnější řešení se mi zdá definovat si rozhranní, kde udělám metodu before render a překriju makro {form}, kde zjistím zda renderer má definované rozhranní a případně spustím before render metodu. Tím změny renderování zustanou stále jen v rendereru a mělo by pak fungovat snad vše normálně :)
- Pipaslot
- Člen | 19
Tak nakonec jsem si to vymyslel takto:
interface IManualRenderer extends IFormRenderer
{
function renderFormBegin(Form $form);
}
class Bootstrap3Renderer extends DefaultFormRenderer implements IManualRenderer
{
public function __construct()
{
$this->wrappers['controls']['container'] = NULL;
$this->wrappers['pair']['container'] = 'div class=form-group';
$this->wrappers['pair']['.error'] = 'has-error';
$this->wrappers['control']['container'] = 'div class=col-sm-9';
$this->wrappers['label']['container'] = 'div class="col-sm-3 control-label"';
$this->wrappers['control']['description'] = 'span class=help-block';
$this->wrappers['control']['errorcontainer'] = 'span class=help-block';
$this->wrappers['error']['container'] = 'div class="col-sm-9 col-sm-offset-3"';
$this->wrappers['error']['item'] = 'div class="alert alert-danger" role="alert"';
}
private function beforeRender(Form $form)
{
$form->getElementPrototype()->class[] = 'form-horizontal';
BootstrapHelper::ApplyBootstrapToControls($form);
}
function render(Form $form, $mode = NULL)
{
$this->beforeRender($form);
return parent::render($form);
}
function renderFormBegin(Form $form)
{
$this->beforeRender($form);
}
}
class BootstrapHelper
{
public static function ApplyBootstrapToControls(Form $form)
{
$usedPrimary = FALSE;
foreach ($form->getControls() as $control) {
if ($control instanceof Controls\Button) {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn btn-default');
$usedPrimary = TRUE;
} elseif ($control instanceof Controls\TextBase || $control instanceof Controls\SelectBox || $control instanceof Controls\MultiSelectBox) {
$control->getControlPrototype()->addClass('form-control');
} elseif ($control instanceof Controls\Checkbox || $control instanceof Controls\CheckboxList || $control instanceof Controls\RadioList) {
$control->getSeparatorPrototype()->setName('div')->addClass($control->getControlPrototype()->type);
}
}
}
}
Pak jsem překryl původní makra
class FormMacros extends FormsLatte\FormMacros
{
public static function install(Compiler $compiler)
{
$me = new static($compiler);
$me->addMacro('form', array($me, 'macroForm'), 'echo Pipas\Forms\Latte\Runtime::renderFormEnd($_form)');
$me->addMacro('formContainer', array($me, 'macroFormContainer'), '$formContainer = $_form = array_pop($_formStack)');
$me->addMacro('label', array($me, 'macroLabel'), array($me, 'macroLabelEnd'));
$me->addMacro('input', array($me, 'macroInput'), NULL, array($me, 'macroInputAttr'));
$me->addMacro('name', array($me, 'macroName'), array($me, 'macroNameEnd'), array($me, 'macroNameAttr'));
$me->addMacro('inputError', array($me, 'macroInputError'));
}
public function macroForm(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
trigger_error('Modifiers are not allowed here.', E_USER_WARNING);
}
if ($node->prefix) {
throw new CompileException('Did you mean <form n:name=...> ?');
}
$name = $node->tokenizer->fetchWord();
if ($name === FALSE) {
throw new CompileException("Missing form name in {{$node->name}}.");
}
$node->tokenizer->reset();
return $writer->write(
'echo Pipas\Forms\Latte\Runtime::renderFormBegin($form = $_form = '
. ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '')
. '$_control[%node.word], %node.array)'
);
}
}
class Runtime extends FormsLatte\Runtime
{
public static function renderFormBegin(Form $form, array $attrs, $withTags = TRUE)
{
$renderer = $form->getRenderer();
if ($renderer instanceof IManualRenderer) {
$renderer->renderFormBegin($form);
}
return parent::renderFormBegin($form, $attrs, $withTags);
}
}
Editoval Pipaslot (10. 1. 2016 0:55)