ConvetionalRenderer – Latte pomocník pro ruční renderování formuláře + Tw Bootstrap

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Filip Procházka
Moderator | 4668
+
0
-

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)

Jan Tvrdík
Nette guru | 2595
+
0
-

Super, konečně to někdo napsal :)

Ot@s
Backer | 476
+
0
-

Kdy to bude ve stabilní verzi? :-)
Dalo by se to použít i pro „zajaxování“ elementárních částí formuláře?

norbe
Backer | 405
+
0
-

Vypadá to hezky, ale často potřebuju spíš vykreslit jinak celý Forms\Container. Předpokládám že to asi vyřešené nemáš? Kdysi jsem se o něco takového pokoušel, ale uspal jsem to společně s projektem, ve kterém jsem to vyvíjel…

Editoval norbe (14. 3. 2012 11:03)

Filip Procházka
Moderator | 4668
+
0
-

To celou věc značně zkomplikuje, ale popřemýšlím.

ptacek.pavel
Člen | 27
+
0
-

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\FormsMoje\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
+
0
-

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
+
0
-

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
+
0
-

Jop, s tím souhlasím, jenom bych to zkrátil na prosté template, nebo klidně i latte.

ptacek.pavel
Člen | 27
+
0
-

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
+
0
-

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
+
0
-

Kluci nechcete udělat DefaultTemplateFormRedener? Který by vyplivnul stejný výsledek jako aktualní DefaultFormRenderer? A poslat to jako pull do nette? :-)

redhead
Člen | 1313
+
0
-

Takhle jsem si to přesně představoval. Paráda, pánové!

Jenom technická: renderují se do inputů i validační pravidla?

Filip Procházka
Moderator | 4668
+
0
-

Samozřejmě :)

awsickness
Člen | 98
+
0
-

tak tohle se mi libi

potapnik
Člen | 127
+
0
-

Ä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
+
0
-

Absolutně nedostačující report. Pošli klikací laděnku, nebo alespoň screen.

Šaman
Člen | 2658
+
0
-

potapnik napsal(a):
Undefined variable: _control

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)

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
+
0
-

Š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
+
0
-

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 ;))

  1. 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}

  1. Vyrobte field class WYSIWYG extends ... {}
  2. ???
  3. $form->addWYSIWYG(...)
  4. $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
+
0
-

ptacek.pavel napsal(a):

edit: Ad vlastní fields: (psáno specielně pro vrtáka ;))

wut?

arron
Člen | 464
+
0
-

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
+
0
-

Můj renderer je v jedné šabloně. Rychlost tedy bude srovnatelná a možná to bude i rychlejší.

arron
Člen | 464
+
0
-

Jo, nakonec jsem dospěl k tomu samému :-)

LeonardoCA
Člen | 296
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

Ne. Wrappery ty vůbec nepodporuje. Jestli máš zájem, můžeš to zkusit dodělat ;)

Editoval HosipLan (13. 8. 2012 12:20)

KrawN
Člen | 17
+
0
-

Ahoj, chtěl bych se zeptat, kde bych mohl sehnat nějaký příklad použití? Vůbec mě nenapadá, jak má vypadat ta hierarchie šablon a presenter..
Díky moc

Filip Procházka
Moderator | 4668
+
0
-

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
+
0
-

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
+
0
-

Protože Twitter Bootstrap renderuje checkboxy v labelu. Otevři issue a něco vymyslíme.

Editoval HosipLan (30. 8. 2012 18:17)

rixi
Člen | 109
+
0
-

dik za renderer, kresli fajn :) daju sa ale prepisovat inputy tak ze jeden z nich rucne vykreslim a zvysok formu sa vykresli samo? makro {define} uz je opravene, akurat podla toho prikladu mi to nechce fungovat.

Editoval rixi (31. 8. 2012 10:35)

LeonardoCA
Člen | 296
+
0
-

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.

Filip Procházka
Moderator | 4668
+
0
-

Vydána verze 0.9.1, která opravuje několik chyb.

deadly_hawk
Člen | 2
+
0
-

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
+
0
-

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
+
0
-

Přidal jsem tam zpětnou kompatibilitu. Můžete to otestovat?

deadly_hawk
Člen | 2
+
0
-

Dobry den

ano ted to jiz funguje bezvadne

dekuji

Filip Procházka
Moderator | 4668
+
0
-

Nechce se někomu otestovat mi nová formulářová makra? :)

Editoval HosipLan (12. 9. 2012 1:44)

pidiclovek
Člen | 91
+
0
-

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
+
0
-

Protože je to experimentální feature. Použij jako referenci @dev a nainstaluje ti to aktuální master.

pidiclovek
Člen | 91
+
0
-

Jasné, hodil bych ale o tom zatím zmínku do doplňků , tam to vypadá jako aktuální feature.

LeonardoCA
Člen | 296
+
0
-

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">&times;</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)

rixi
Člen | 109
+
0
-

implemenovat by sa dalo kadeco, pripadne nastavit standardne aby sa vsetkym input/select prvkom nastavovala nejaka class span*, potom tak funguju formularove prvky dobre aj v responsive bootstrapu.

Filip Procházka
Moderator | 4668
+
0
-

@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">&times;</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
+
0
-

@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}