Editace položky / Zobrazení formuláře pomocí AJAXu
- jarks
- Člen | 94
Dobrý den, mohl bych prosím dostat radu?
Mám funkční editaci jednoho políčka šablony takto:
Šablona detailu:
<p>Název: {$p->nazev}> <a href="{link edit, $p->id}">Editovat název</a></p>
V presenteru k šabloně mám formulář pro editaci:
public function renderEdit($id = 0) {
$form = new Form;
$form ->addText('nazev', 'Název:');
$form->addSubmit('ulozit', 'Uložit');
$this->template->form = $form;
// Naplníme hodnoty polí
if (!$form->isSubmitted()) {
$detail = new Detail; //model
$row = $detail->findDetail($id)->fetch();
if (!$row) {throw new /*Nette\Application\*/BadRequestException('Záznam nenalezen.');}
$form->setDefaults($row);
} else { // jestliže je odesláno:
$detail = new Detail;
$detail->update($id, $form->getValues());
$this->flashMessage('Změna byla uložena.');
$this->redirect('Detail:sablona',$id);
}
}
Šablona pro editaci obsahuje jen {!$form}
.
Všechno to funguje správně. Co bych chtěl udělat: Po
kliknutí na Editovat název
ze na tom místě pomocí AJAXu
zobrazí ten malý formulářík, vyplním, potvrdím a opět na stejném
místě se objeví aktualizovaná položka.
Zkoušel jsem to všelijak podle různých informací tady, ale vůbec se mi nedaří. Můžete mě prosím nasměrovat? Díky.
- jarks
- Člen | 94
Jod napsal(a):
Ak dobre rozumiem, tak ti to ajaxom nefunguje…
Bohužel mi to nefunguje vůbec. Nedaří se mi ani, aby se formulář vůbec
objevil ve stránce. Javascripty mám viditelně nalinkované správně,
protože Datagrid jde krásně. Ale když zkouším ajaxový odkaz
(class="ajax"
) nebo různě operovat se snippety, nedělá to buď
nic, nebo chyby parse error
na šabloně. Potřeboval bych prostě
ukázat cestu, jak na to. Teprve začínám a trochu mi z toho jde
hlava kolem.
Jeditable – In Place Editor plugin se mi moc použít nechce, protože bych rád, aby v nepřítomnosti javascriptu proběhlo normální zavolání presenteru na jeho stránce.
Editoval jarks (16. 7. 2009 14:26)
- Panda
- Člen | 569
Hm, kód zkusím napsat z fleku, bez testování, takže se prosím nehněvejte, když tam bude nějaká bota, sada bot, nebo dokonce celá obuv. Je to koncipované kousek jinak, než kód nahoře, ale vyjít by z toho jít mělo. Kód také využívá některé vlatnosti více či méně aktuálního Nette.
Template:
<!--
Pokud je tento kód zanořen do podmínky či cyklu,
musí být prefixován zavináčem:
@{if ...}
@{/if}
Pokud používáš nové šablony, mělo by být prefixováno i
@{include #content} (v layoutu)
@{block #content}
@{/block} (pravděpodobně může být vynecháno,
pokud jde o poslední blok šablony)
-->
<p>
{snippet title$p->id}
Název: {$p->nazev}
{if $editTitle != $p->id}
<a href="{link editTitle!, 'editId' => $p->id}" class="ajax">Editovat název</a>
{else}
{control EditTitleForm}
{/if}
{/snippet}
</p>
Presenter:
<?php
class SomePresenter
{
// ...
// Následující konstrukci využívám k líné inicializaci modelu
// a udržování jen jedné instance ve všech koutech Presenteru
private $detailModel;
/**
* @return Detail
*/
public function getDetailModel()
{
if ($this->detailModel === NULL)
$this->detailModel = new Detail;
return $this->detailModel;
}
// Proměnná, která určuje, zda zobrazíme formulář,
// případně pro kterou položku
protected $editTitle = FALSE;
public function renderAction(/* ... */)
{
// Zde proběhne získání záznamu, popř. záznamů,
// pokud jich je na stránce víc
$this->template->editTitle = $this->editTitle;
}
public function createComponentEditTitleForm()
{
$form = new AppForm();
// Formuláři nastavíme třídu ajax
$form->getElementPrototype()->class('ajax');
$form->addText('nazev', 'Název:');
$form->addHidden('id');
$form->addSubmit('ulozit', 'Uložit');
$form->addSubmit('zrusit', 'Zrušit');
// Funkce, která se zavolá po odeslání formuláře
$form->onSubmit[] = array($this, 'EditTitleForm_Submit');
/* Pokud používáš AJAXové odesílání formuláře, zkontroluj,
zda je v JavaScriptu nastavené odesílání formuláře i na submit:
$("form.ajax :submit").livequery("click", function () {
$(this).ajaxSubmit();
return false;
});
(vyžaduje plugin livequery, ale pokud používáš DataGrid,
měl by být k dispozici)
*/
return $form;
}
public function EditTitleForm_Submit(Form $form)
{
$values = $form->getValues();
if ($form['ulozit']->isSubmittedBy()) {
$this->getDetailModel()->update(
$values['id'],
array(
'nazev' => $values['nazev']
)
);
/* Pokud používáš DibiTableX, můžeš použít variantu
$this->getDetailModel()->update(
NULL,
$form->getValues()
);
*/
}
// Při ajaxu nepřesměrováváme, ale invalidujeme
if (!$this->isAjax())
$this->redirect('this');
else {
// Překreslíme jen jeden snippet, ne všechny
$this->validateControl();
$this->invalidateControl('title' . $values['id']);
}
}
public function handleEditTitle($editId)
{
$this->editTitle = $editId;
$row = $this->getDetailModel()->findDetail($id)->fetch();
if ($row === FALSE)
throw new BadRequestException('Záznam nebyl nalezen.');
$this->getComponent('EditTitleForm')
->setDefaults(array(
'id' => $row->id,
'nazev' => $row->nazev
));
// Překreslíme jen jeden snippet, ne všechny
$this->validateControl();
$this->invalidateControl('title' . $editId);
}
// ...
}
?>
Doufám, že kód bude alespoň k něčemu dobrý a že jsem tam těch bot neudělal mnoho.
//Úprava: opraven kód.
Editoval Panda (16. 7. 2009 19:02)
- jarks
- Člen | 94
Díky moc!
Napoprvé to nechodí. Toto: {snippet title$p->id}
vygeneruje
<div id="__title$p->id">
, tzn. nepřeloží se to. Dál se
vypíše jen název, žádný odkaz ani konec snippetu
</div>. A vyhodí chybu: Undefined variable: editTitle
. Budu
se tomu snažit porozumět, snad na to nějak příjdu.
Taky vidím, že jsem předtím dělal chybu v tom, že jsem nepoužil
zavináče. Mám předtím podínku {if $p}
a pořád jsem
dostával parse error s výpisem podobným tomuto, a nechápal jsem
o co jde:
<?php } if ($_cb->foo = SnippetHelper::create($control, 'title$p->id')) { $_cb->snippets[] = $_cb->foo; ?>
Název: <?php echo TemplateHelpers::escapeHtml($p->nazev) ?>
- Panda
- Člen | 569
Tak jsem si to zkusil naroubovat do jedné mojí existující aplikace a
chodilo mi to, až na drobnost – podmínku v šabloně jsem měl špatně,
zapomněl jsem na negaci. Také jsem tam ještě přihodil přiřazení třídy
ajax
formuláři a pár komentářů.
Pokud se Ti nepřekládá název snippetu, zkusil bych nejnovější Nette.
Nedávno CurlyBracketsFilter
prodělal nějaké větší úpravy a
refactoringy, může to být tím. Mám revizi 422 a ten překlad mi jel,
dokonce to byla jediná věc, kterou jsem si ověřil, než jsem to
začal psát.
Pokud to hlásí Undefined variable: editTitle
, zkontroluj,
jestli máš v metodě render toto přiřazení:
<?php
$this->template->editTitle = $this->editTitle;
?>
//Doplnění: nebo pokud máš jen jeden prvek na stránce, tak ten snippet
klidně můžeš pojmenovat jen title
a do proměnné
SomePresenter::$editTitle
ukládat jen
TRUE
/FALSE
.
Editoval Panda (17. 7. 2009 10:02)
- jarks
- Člen | 94
Funguje, díky! Musel jsem ale přejít na novější Nette. Proměnnou jsem skutečně zapomněl přiřadit.
Jeden problém ale zůstává: Jakmile kódu v šabloně
předřadím podmínku, dostanu parse error
, ať dávám zavináče
nebo ne.
{if $p}
@{snippet title$p->id} //<<< parse error
Název: {$p->nazev}
@{if $editTitle != $p->id}
<a href="{link editTitle!, 'editId' => $p->id}" class="ajax">Editovat název</a>
@{else}
@{control EditTitleForm}
@{/if}
@{/snippet}
Přitom tato šablona je vkládána do layoutu také se zavináčem:
@{include $content}
. Zkusil jsem Nette rev422 a rev425.
Bohužel příliš nerozumím té zavináčové magii. Ani ona, ani značka
{widget}
není v popisu zatím
vysvětlena.
- Panda
- Člen | 569
Makro widget skutečně vysvětleno není, je to relativně nová věc a celá komunita čeká na stabilizaci nových šablon a první článeček o změnách. Pak se to jistě dostane i do dokumentace, zatím jsme odkázání na zdroják. :)
Jinak u šablony zkus toto:
@{if $p}
{snippet title$p->id}
Název: {$p->nazev}
{if $editTitle != $p->id}
<a href="{link editTitle!, 'editId' => $p->id}" class="ajax">Editovat název</a>
{else}
{control EditTitleForm}
{/if}
{/snippet}
@{/if}
Zavináčovou magii se Ti pokusím trochu objasnit.
Zavináčová magie
Zavináčová magie není zas tak těžká. Je potřeba si uvědomit, co se u AJAXu děje – renderují se jen ty části, které Ty jako programátor chceš, aby se renderovaly. Zavináč šablonám řekne, že následující příkaz se má vykonat vždy, bez ohledu na AJAX. Pokud by u předchozí podmínky nebyl, podmínka by se vůbec nevykonala a s ní i celý obsah – šablona by tedy vůbec nevěděla, že uvnitř podmínky je nějaký snippet. Pokud tam ale zavináč dá, podmínka se vykoná, a uvnitř bloku opět jede selektivní vykreslování. Pokud se tedy narazí na snippet, zkontroluje se, zda je invalidní nebo validní. V případě, že je invalidní, vyrenderuje se a přidá se do AJAXové odpovědi. Snippety si svůj stav zjišťují samy, takže se před ně zavináče vůbec dávat nemusí.
Parse errory souvisí se současnou implementací snippetu. Nyní je v podstatě celá přeložená šablona (tzn. šablona, která prošla všemi filtry a vznikl PHP kód) uzavřena do podmínky:
<?php
if (SnippetHelper::$outputAllowed) {
// Zde je přeložený kód šablony
}
?>
Zavináč nedělá nic jiného, než že tuto podmínku uzavře a pak znovu otevře:
<p>Odstavec 1</p>
@{control Form}
<p>Odstavec 2</p>
Se přeloží na (zformátováno pro větší čitelnost):
<?php
// U AJAXového požadavku SnippetHelper::$outputAllowed = FALSE
if (SnippetHelper::$outputAllowed) {
// kód pro <p>Odstavec 1</p>
}
$control->getWidget("Form")->render();
/* Takto se mimo jiné překládá makro widget. Jde o alternativu k
$control->getComponent("Form")->render();
*/
if (SnippetHelper::$outputAllowed) {
// kód pro <p>Odstavec 2</p>
}
?>
Kód snippetu si vždy při překladu podmínku uzavře a po svém skončení jí opět otevře:
{* kód *}
{snippet main}
{* kód *}
{/snippet}
{* kód *}
<?php
if (SnippetHelper::$outputAllowed) {
// kód
}
if ($_cb->foo = SnippetHelper::create($control, "main")) {
$_cb->snippets[] = $_cb->foo;
// kód
}
if (SnippetHelper::$outputAllowed) {
// kód
}
?>
Podmínky a cykly jsou velmi prakticky překládány do své alternativní syntaxe. Pokud totiž člověk pak někde zapomene zavináč, zahlásí parser chybu, protože mu nesedí závorky:
{* kód *}
{if ...}
{* kód v podmínce *}
{snippet main}
{* kód snippetu *}
{/snippet}
{* kód v podmínce *}
{/if}
{* kód *}
<?php
if (SnippetHelper::$outputAllowed) {
// kód
if (...):
// kód v podmínce
}
/* Na tomto místě uzavíráme blok podmínky, která však
byla vytvořena s alternativní syntaxí
-> parser error
*/
/* K tomuto snippetu by se při renderování šablona vůbec nedostala -
máme špatně zavináče a snippet je natvrdo uzavřen v bloku hlavní podmínky */
if ($_cb->foo = SnippetHelper::create($control, "main")) {
$_cb->snippets[] = $_cb->foo;
// kód snippetu
}
if (SnippetHelper::$outputAllowed) {
// kód v podmínce
endif;
/* Zde naopak zavíráme alternativní syntaxí podmínku,
která byla vytvořena s normální syntaxí
-> kdyby se sem parser dostal, tak parser error #2
*/
// kód
}
?>
Jen pro zajímavost kód, kde by alternativní syntaxe u podmínky použita nebyla – přeložit by šel, ale znamenal by úplně něco jiného!
<?php
if (SnippetHelper::$outputAllowed) {
// kód
if (...) {
// kód v podmínce
}
// Konec podmínky!
// K snippetu se opět šablona nedostane
if ($_cb->foo = SnippetHelper::create($control, "main")) {
$_cb->snippets[] = $_cb->foo;
// kód snippetu
}
/* Tento "kód v podmínce" už ale s první podmínkou nemá co dělat -
u normánlího požadavku by se vykonal vždy, u AJAXu nikdy */
if (SnippetHelper::$outputAllowed) {
// kód v podmínce
}
// kód
}
?>
Doufám, že jsem to napsal srozumitelně a že to alespoň někomu bude trochu užitečné. Zavináčová magie se však chystá Nette opustit, takže si o využitelnosti a přínosu nedělám moc velké iluze.
- jarks
- Člen | 94
Díky. Původně jsem to pochopil úplně obráceně.
Zachází se v té logice nějak jinak s {else}
? Na něm to
opět zhavaruje.
@{if $p}
{snippet title$p->id}
Název: {$p->nazev}
{if $editTitle != $p->id}
<a href="{link editTitle!, 'editId' => $p->id}" class="ajax">Editovat název</a>
{else}
{control EditTitleForm}
{/if}
{/snippet}
@{else} <p>Nic nebylo nalezeno.</p> //<< parse error
@{/if}
- Panda
- Člen | 569
Ve vygenerovaném kódu by měl být za závorkou před else
středník. Může tam být vždy, středník navíc by nic poškodit neměl,
takže jednoduchá oprava spočívá v úpravě proměnné
CurlyBracketsFilter::$defaultMacros
před vytvořením šablony,
např. v metodě startup()
u BasePresenter
u nebo
jako event do Application::$onStartup
. Pokud to chceš mít
opravené rychle a jednoduše, doporučuji tu metodu
BasePresenter::startup()
.
CurlyBracketsFilter::$defaultMacros['else'] = '<?php ; else: ?>';
Po úpravě musíš promazat temp.
Otázkou zůstává, zda to má cenu reportovat jako bug, když nám zavináče snad již brzy pojdou…
//Doplnění: tak to nedělá paseku jen před zavináčem, takže to jdu reportovat jako bug.
//Doplnění 2: celá oprava vypadá nyní takto:
CurlyBracketsMacros::$defaultMacros['else'] = '<?php ; else: ?>';
CurlyBracketsMacros::$defaultMacros['elseif'] = '<?php ; elseif (%%): ?>';
Editoval Panda (29. 7. 2009 9:55)
- Klokan
- Člen | 47
Chtel bych se optat, jaké je nejlepsi reseni tohoto problemu v sablone. Zde je reseni na zaklade atributu tridy, ktery se prepina a v sablone se formular zobrazi na zaklade podminky.
<?php
Presenter:
$this->template->editTitle = $this->editTitle;
Template:
{if $editTitle != $p->id}
<a href="{link editTitle!, 'editId' => $p->id}" class="ajax">Editovat název</a>
{else}
{control EditTitleForm}
{/if}
?>
Je možné to řešit ještě jiným způsobem, např. zjištěním z URL ?