ConvetionalRenderer – Latte pomocník pro ruční renderování formuláře + Tw Bootstrap
- Filip Procházka
- Moderator | 4668
Není to 20 řádků kódu, ale funguje to obstojně a
je je pěkně rozšiřitelné. https://gist.github.com/2029739
A na co je to vlastně dobré? No to si takhle chcete vykreslit jeden
jediný prvek formuláře jinak a musíte kvůli tomu ručně renderovat celou
šablonu. Nahouby, že?
Použití:
Pokud máte návrh na zlepšení, tak fork, upravit a
upozornit ;)
Nyní i v podobě rendereru do formuláře:
use Kdyby\BootstrapFormRenderer\BootstrapRenderer;
$form->setRenderer(new BootstrapRenderer);
Více informací na stránce addonu
Editoval HosipLan (31. 8. 2012 17:54)
- ptacek.pavel
- Člen | 27
Tak, původně jsem chtěl kód uveřejnit až po refaktoringu, nicméně alespoň získáme čas na diskuzi o tom, jak to nakonec ideálně udělat. Rád bych předeslal, že se Twitter Bootstrap formulářům budu věnovat do neděle 22. 4., čili rád implementuju cokoliv, na čem se zde domluvíme :-)
Současný kód je zde . Jedná se o prasárnu, napsanou za včerejší odpoledne! (a už jsem si dal facku za uveřejnění)
V podstatě to skládá .latte soubory za sebe stejně, jako to dělal původní ConventionalRenderer. Přemýšlel jsem, které řešení je lepší:
- rvát vše do jednoho master.latte souboru je wtf
- přetěžovat renderování za pomocí
{include}
je znásilnění - skládat stuby za sebe, tak jak to dělám já, je taky wtf (a hlavně princip, který se používal 7 let zpátky.)
Navrhuji (a budu na tom teď dělat) to, aby byl jeden
master.latte
soubor, který by vypadal nějak takto
a takto
. Jsou to kódy použité v test suite JSONRpcv2, nicméně stejný princip
bych zachoval i v refaktoringu.
Řešení, které mi osobně přijde nejlepší, se vyznačuje následujícím:
- možnost přetížit hlavní master.latte, který includuje svoje stuby,
například
$form->setRenderer(new TemplateFormRenderer(APP_DIR . '/templates/myforms.latte'));
- možnost přetížit jednotlivé fieldy:
$form->addText(..)->setOption('templateOverride', APP_DIR . '/templates/mytexbox.latte')
- vlastní renderer – tzn. když chci udělat bootstrap-enabled formy,
prostě
přidám
$form->setRenderer(new TemplateFormRenderer());
Důvod, proč chci přetěžovat jednotlivé fieldy na úrovni Options je
ten, že spoustu věcí (jako například latitude/lognitude control) můžu
udělat za pomocí .latte a JS – nemusím tedy dělat tenhle
a tenhle
bordel. Navíc nemusím vyrábět z Nette\Forms
→
Moje\forms
. To samé platí pro různé WYSIWYG editory atp.
V podstatě bych pro gmapu mohl udělat:
$form->addHidden('map')->setOption('templateOverride', 'templates/forms/googlemap.latte');
–
stejně si obslužné javascripty tak jako tak musíme napsat sami, nevidím
jediný důvod, proč to jinak nedělat.
Ad vlastní renderer: osobně bych byl pro to, aby se z toho bordelu, co tam teď mám, stalo něco, co předžvejká form pro šablonu tak, aby se v tom v šablonách dobře pracovalo. Jde například o placeholders, helpy, serializování checkboxů, různé options etc. Renderer by tedy vygeneroval stdclasses s předem danou strukturou, ze kterých by se vytvořil formulář. Výhoda je navíc ta, že jenom do components/ dropnu celou složku, u formu přidám jeden řádek, a mám hotovo.
A teď co to umí:
- helpy u jednotlivých fieldů
- warningy u jednotlivých fieldů ve chvíli, kdy jsou špatná data (zde, a po submitu zde)
- chyby, které jsou u fieldu, se nezobrazují nahoře ve formu (u složitých formů výhoda)
- díky rendereru umí vytvářet formy za pomocí
{control myForm}
- následující:
<?php
$field->setOption('help', 'help text');
$field->setOption('status', 'warning|error|success'); // the style of the field
$field->setOption('class', 'someclasses');
$field->setOption('prepend', 'prepend text within input');
$field->setOption('append', 'append text within input');
$field->setOption('prepend-button', 'buttonid');
$field->setOption('append-button', 'buttonid');
$field->setOption('placeholder', 'this is some placeholder text');
?>
Jdu refaktorovat, nahoďte mi Váš názor. Až bude updatnutý github, dám vědět :-)
- Filip Procházka
- Moderator | 4668
Co se týče Rendereru tak ho považuji za nejlepší možné řešení, ale lenost vyhrála a udělat jednu šablonu je prostě jednodušší.
S následujícím si dovolím nesouhlasit, protože moje (Davidovo) řešení ti umožní použít plnou sílu Latte.
- rvát vše do jednoho master.latte souboru je wtf
- přetěžovat renderování za pomocí
{include}
je znásilnění
A já ti to jdu s dovolením forknout, protože je to pěkný základ, který by šel naroubovat na moji šablonu :)
Editoval HosipLan (19. 4. 2012 12:19)
- ptacek.pavel
- Člen | 27
forkuj, uvidíme co z toho vyleze :-) Právě pracuju na tom, co jsem naznačil, uvidíme, čí řešení se ujme víc :-)
Nicméně si stále stojím za tímto:
možnost přetížit jednotlivé fieldy: $form->addText(..)->setOption(‚templateOverride‘, APP_DIR . ‚/templates/mytexbox.latte‘)
- Filip Procházka
- Moderator | 4668
Jop, s tím souhlasím, jenom bych to zkrátil na prosté
template
, nebo klidně i latte
.
- ptacek.pavel
- Člen | 27
Tak, nahodil jsem na GH novou verzi (a zapomenul branchovat.) Každopádně, co jsem měl na mysli já je teď vidět zde a hlavně v master.latte souboru
Teď bych už jenom předělal jednotlivé .latte files tak, aby to vypadalo
lépe – skládat html dohromady z pěti variables je nechutné, použití
{input}
je krásné.
@hosiplan: osobně bych zkombinoval moje phpko a tvoji šablonu, dej vědět až budeš mít pushnuto do forku.
- Filip Procházka
- Moderator | 4668
Tak já mám poladěno. https://github.com/HosipLan/nette-forms-renderer
(Dlouho to tu nezůstane, začlením to do Kdyby a pak udělám
subdir-split) https://github.com/…rerExtension
Takže, na konec formuláře z prvního příspěvku si přidáme
$renderer = new \Renderer\BootstrapRenderer($this->context->nette->createTemplate());
// $renderer->errorsAtInputs = FALSE;
// $renderer->priorGroups[] = 'Other';
$form->setRenderer($renderer);
A vesele renderujeme :) Výsledek vypadá takto.
Editoval HosipLan (24. 4. 2012 14:22)
- Patrik Votoček
- Člen | 2221
Kluci nechcete udělat DefaultTemplateFormRedener? Který by vyplivnul stejný výsledek jako aktualní DefaultFormRenderer? A poslat to jako pull do nette? :-)
- potapnik
- Člen | 127
Ähoj, tak jsem to zkusil a vyhazuje to následující noticku:
Notice
Undefined variable: _control
…
…
BootStrapRenderer.php
…
110: $this->template->render();
Nějak jsem se svépomocí nedopracoval k tomu, kde by mohl být problém…(PHP 5.3.10, Nette 2.0.1, Apache 2.2.21 – vše x64)
- Filip Procházka
- Moderator | 4668
Absolutně nedostačující report. Pošli klikací laděnku, nebo alespoň screen.
- Šaman
- Člen | 2658
potapnik napsal(a):
Undefined variable: _controlNějak jsem se svépomocí nedopracoval k tomu, kde by mohl být problém…(PHP 5.3.10, Nette 2.0.1, Apache 2.2.21 – vše x64)
Aktualizuj na Nette 2.02 nebo vyšší.
Myslím, že od té doby už není nutné mít $_control
, pokud
$form
je instance Form
. Dříve se to snažilo najít
komponentu s názvem $form
v kontejneru
$_control
.
Jinak pokud bys předával do šablony $form
jako string
obsahující název komponenty, tak je stále nutné předat i
$_control
, ve kterém bude nadřazený kontejner (typicky
presenter).
Editoval Šaman (24. 4. 2012 0:08)
- potapnik
- Člen | 127
Šaman: díky, v sandboxu 2.0.3 to chodí, akorát jsem měl rozjetý projekt na 2.0.1, kde by se mi tohle konkrétně hodilo, tak jsem to chtěl otestovat na týhle verzi…
Hosiplan: sorry, než jsem sem stačil poslat klikací laděnku, tak řešení poslal Šaman, každopádně díky moc za hezkou utilitku.
- ptacek.pavel
- Člen | 27
Tak já mám také hotovo :) zde
Proč: Protože jsem nerozumněl hosiplanově šabloně.
Proč 2: Protože renderer by imho neměl manipulovat s Html:: →
nechť se toto děje v šablonách
Každopádně jsem si pár věcí od Hosiplana zkopnul, není to špatně napsané ;)
edit: Ad vlastní fields: (psáno specielně pro vrtáka ;))
- Vyrobte si vlastní šablonu v projektu / nelle / kdyby – třeba "@form.latte" a naplňte ji takto:
{layout '../components/Nestraps/bootstrap.latte'}
{define #WYSIWYG}<nejakej markup>{/define}
- Vyrobte field
class WYSIWYG extends ... {}
- ???
$form->addWYSIWYG(...)
$form->setRenderer(new foglcz\NestrapRenderer(__DIR__ . '/../templates/@form.latte', $this->context->cacheStorage))
Editoval ptacek.pavel (24. 4. 2012 7:43)
- Patrik Votoček
- Člen | 2221
ptacek.pavel napsal(a):
edit: Ad vlastní fields: (psáno specielně pro vrtáka ;))
wut?
- arron
- Člen | 464
Chlapi, jak jste na tom s rychlostí renderování? Když jsem si tohle
napsal asi před dvěma třemi roky (někde tady na fóru to určitě
ještě bude), tak jsem narazil na to, že to bylo řádově (cca 10x)
pomalejší než ConventionalRenderer. Bylo to tím, že to zpracování šablon
v takovém množství prostě chvíli trvá. Dělali jste teď nějaké
porovnání podle rychlosti?
Editoval arron (24. 4. 2012 13:42)
- Filip Procházka
- Moderator | 4668
Můj renderer je v jedné šabloně. Rychlost tedy bude srovnatelná a možná to bude i rychlejší.
- LeonardoCA
- Člen | 296
Chci vyzkoušet HosipLanovo řešení a nějak mi to dnes nemyslí. Jak nastavím Kdyby\Extension\Forms\BootstrapRenderer jako výchozí renderer přes compilerExtension?
něco jako
class BootstrapCompilerExtension extends \Nette\Config\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->getDefinition('nette.latte')->addSetup('Lxo\Bootstrap\BootstrapMacros::setup', array('@self'));
}
//register Kdyby\Extension\Forms\BootstrapRenderer
$builder->getDefinition('nette.basicForm')->addSetup('tady nevim jak to šikovně zaregistrovat', array('@self'));
}
}
nebo se musí Nette\Forms\Form->SetRenderer() provést až někdy později?
Editoval LeonardoCA (12. 6. 2012 22:15)
- LeonardoCA
- Člen | 296
hmmm, chtěl bych nějak docílit toho, aby se bootstrap renderer použil automaticky pro všechny formuláře v projektu i ty vytvářené přes
new Nette\Application\UI\Form
ale to asi nelze nijak docílit :-(
Takže asi nejlepší bude vytvořit potomka BootstrapForm extends Nette\Application\UI\Form
Editoval LeonardoCA (13. 6. 2012 0:25)
- Mas3r
- Člen | 116
Menší cyhbička:
<?php
--- b6527da68175b7847b1c8896ea79e58143827769
+++ e0be77dfd10404b993bf2b853438c99d8bda880a
@@ -90,7 +90,7 @@
}
$formEl = $form->getElementPrototype();
- if (stripos('form-', $formEl->class) === FALSE) {
+ if (stripos($formEl->class, 'form-') === FALSE) {
$formEl->addClass('form-horizontal');
}
}
?>
- ptacek.pavel
- Člen | 27
Takže asi nejlepší bude vytvořit potomka BootstrapForm extends Nette\Application\UI\Form
Osobně to řeším v config.neon takto:
factories:
form:
parameters: [parent, name]
class: \Nette\Application\UI\Form(%parent%, %name%)
setup:
- setRenderer(\foglcz\NestrapRenderer(null, @cacheStorage))
v presenteru pak jenom zavolám:
<?php
/**
* Create category form
* @param $name
* @return \Nette\Application\UI\Form
*/
public function createComponentCategoryForm($name) {
$form = $this->context->createForm($this, $name);
}
?>
- raketoplan2005
- Člen | 147
Můžu prosím nějak i při použití tohoto rozšíření nastavit wrappers?
Jako dříve např:
$renderer->wrappers['pair']['container'] = 'p';
- Filip Procházka
- Moderator | 4668
Ne. Wrappery ty vůbec nepodporuje. Jestli máš zájem, můžeš to zkusit dodělat ;)
Editoval HosipLan (13. 8. 2012 12:20)
- Filip Procházka
- Moderator | 4668
To vůbec řešit nemusíš, stačí ti tento řádek :)
$form->setRenderer(new Kdyby\Extension\Forms\BootstrapRenderer\BootstrapRenderer);
Editoval HosipLan (29. 8. 2012 16:05)
- LeonardoCA
- Člen | 296
Hosiplan:
je nějaký důvod proč se pro checkbox label renderuje
$control->label->getText()
a ne
$control->label
?
potřeboval jsem dát do popisky odkaz …
Editoval LeonardoCA (30. 8. 2012 14:47)
- Filip Procházka
- Moderator | 4668
Protože Twitter Bootstrap renderuje checkboxy v labelu. Otevři issue a něco vymyslíme.
Editoval HosipLan (30. 8. 2012 18:17)
- LeonardoCA
- Člen | 296
Protože Twitter Bootstrap renderuje checkboxy v labelu.
To jsem si právě díky toho všiml. Moc to neřeším – pro svou potřebu použiju „$control->label“. Inline element může obsahovat inline elementy a nic jiného než <a> mu tam cpát nebudu.
- deadly_hawk
- Člen | 2
Dobry den
pouzil jsem renderer dle instrukci, ale ladenka me psala toto:
Recoverable Error
Argument 1 passed to Forms\BootstrapRenderer\BootstrapRenderer::Forms\BootstrapRenderer\{closure}() must be an instance of Nette\Forms\Controls\BaseControl, instance of Nette\Iterators\Filter given
264: public function findControls(Nette\Forms\Container $container = NULL)
265: {
266: $container = $container ?: $this->form;
267:
268: return new Filter($container->getControls(), function (Controls\BaseControl $control) {
269: return !$control->getOption('rendered');
270: });
tak jsem chvili hledal a narazil jsem na toto forum https://forum.nette.org/…traprenderer
a po te i tento fix s kterym to beha jak ma https://github.com/…1dc9e3bf3409
bohuzel za nim je hned dalsi fix ktery to zas maze https://github.com/…4d5ca4d6c374
nevim tedy jak mam docilit toho aby to behalo a nemusel jsem Rederer
upravovat rucne
Dekuji
- Elijen
- Člen | 171
Je potreba nova verze
tridy Nette\Iterators\Filter
. Doporucuju updatnout tridy
Filter
, Mapper
i RecursiveFilter
.
Editoval Elijen (9. 9. 2012 21:23)
- Filip Procházka
- Moderator | 4668
Nechce se někomu otestovat mi nová formulářová makra? :)
Editoval HosipLan (12. 9. 2012 1:44)
- pidiclovek
- Člen | 91
Vypadá to skvěle! Díky :) Jen tag 0.9.2 neobsahuje složky DI a Latte, takže s composer instalací je člověk nedostane, musí na github… Stáhnu manuálně a jdu testovat :)
- Filip Procházka
- Moderator | 4668
Protože je to experimentální feature. Použij jako referenci
@dev
a nainstaluje ti to aktuální master.
- pidiclovek
- Člen | 91
Jasné, hodil bych ale o tom zatím zmínku do doplňků , tam to vypadá jako aktuální feature.
- LeonardoCA
- Člen | 296
Zkoušel jsem renderovat formulář do modal dialogu:
<div id="modalForm" class="modal hide" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
{snippet modalForm}
{if $showForm}
{$form->render('begin')}
<div class="modal-header">
<a class="close" data-dismiss="modal" aria-hidden="false">×</a>
<h3 id="myModalLabel">{$formLabel}</h3>
</div>
<div class="modal-body">
{$form->render('errors')}
{$form->render($form->getGroup('body'))}
</div>
<div class="modal-footer">
{$form->render($form->getGroup('controls'))}
</div>
{$form->render('end')}
{/if}
{/snippet}
</div>
Přidal jsem si pro ten účel ve formuláři 2 Groups – funguje to, jen uzavírací tagy pro fieldset a form to hodí do html později než by mělo. Např:
<div class="modal-footer">
<fieldset>
<input type="submit" class="btn-primary button btn" name="save" id="frmredirectsForm-save" value="Save" />
</div>
</fieldset>
Nápad: bylo by fajn, kdyby renderer podporoval modals nativně, zkoušel jsem to implementovat na starší verzi doplňku a povedlo se akorát to nebylo moc pěkné. Časem převedu do nové verze a doladím. (U modals mi jde o to, aby se tlačítka renderovaly do divu „modal-footer“, jinak to vytváří jakoby dvě patičky formuláře)
Editoval LeonardoCA (17. 9. 2012 13:46)
- Filip Procházka
- Moderator | 4668
@LeonardoCA: On se tam ten
</fieldset>
nerenderuje pozdě, ale spíš se tam renderuje
navíc ten </div>
, protože ten se má vypisovat za
odesílacími tlačítky. Spíš se divím, že se ti tam nerenderuje další
ještě před tím.
Prozatím, než vymyslíme něco hezčího, bych to renderoval takto:
<div id="modalForm" class="modal hide" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
{snippet modalForm}{if $showForm}
{$form->render('begin')}
<div class="modal-header">
<a class="close" data-dismiss="modal" aria-hidden="false">×</a>
<h3 id="myModalLabel">{$formLabel}</h3>
</div>
<div class="modal-body">
{$form->render('errors')}
{$form->render($form->getGroup('body'))}
</div>
<div class="modal-footer">
{foreach $form->getGroup('controls')->controls as $control}
{input $control->lookupPath('Nette\Forms\Form')}
{/foreach}
</div>
{$form->render('end')}
{/if}{/snippet}
</div>
Asi zkusím nějaký ModalControl
napsat a přihodit ho
k tomu :)
- LeonardoCA
- Člen | 296
@Hosiplan: Máš pravdu ten <div> to tam kazí. Díky, tvoje řešení funguje, akorát jsem tam musel z nějakého důvodu nastavit $_form.
{var $_form => $form}
{foreach $form->getGroup('controls')->controls as $control}
{input $control->lookupPath('Nette\Forms\Form')}
{/foreach}