Nette šablony samostatně – AJAX
- nanuqcz
- Člen | 822
Ahoj,
v jednom projektu používám samostatně Nette šablony, a teď jsem chtěl
jednu část stránky zAJAXovatět. Připojil jsem tedy jquery.js
,
jquery.nette.js
a do šablony jsem přidal
{snippet ajax_header}...{/snippet}
. Od této chvíle mi to
chlásí error:
Call to a member function getSnippetId() on a non-object
Ve zkompilované šabloně ukazuje laděnka na tento řádek:
<div id="<?php echo $control->getSnippetId('ajax_header') ?>"><?php call_user_func(reset($_l->blocks['_ajax_header']), $_l, $template->getParams()) ?></div>
Je mi jasné, že když nepoužívám MVC a tedy nemám žádný kontroler,
s proměnnou $control
v šabloně bude asi trochu problém. Jak
to ale řešit? Díky
(Nette 2.0alfa pro PHP5.3)
P.S. Zatím jsem AJAX vůbec v Nette nepoužíval, takže je možné, že dělám chybu někde jinde
Editoval xxxObiWan (28. 5. 2011 17:00)
- nanuqcz
- Člen | 822
22: Jj, jenže test.php
(využívající Nette
šablony) mi vrátí obsah celé stránky. Takže zatím to mám takto:
jQuery('a.ajax').click(function(){
jQuery.ajax({
url: jQuery(this).attr('href'),
success: function(cela_stranka){
jQuery('#ajax-content').html( jQuery('#ajax-content', cela_stranka).html() );
}
});
return false;
});
Já jsem se ptal na to, jak by měl soubor test.php
přibližně
vypadat, aby mi vrátil jen obsah divu #ajax-content
.
Mkoubik vymyslel v příspěvku nad tímto docela dobré řešení (díky ti :)), každopádně jsem ale doufal v něco tak hezkého, jako je používání snipettů :-)
Editoval xxxObiWan (29. 5. 2011 17:33)
- nanuqcz
- Člen | 822
Tak jsem trochu pokročil a podařilo se mi Nette šablony „ohnout“ tak,
aby do nich šla značka {snippet}
napsat. V principu
asi takto:
class ZeroPresenter extends \Nette\Application\Presenter {}
class MyFileTemplate extends \Nette\Templates\FileTemplate
{
public function __construct($file)
{
parent::__construct($file);
$this->control = new ZeroPresenter();
$this->registerFilter(new \Nette\Templates\LatteFilter);
}
}
To mi dovoluje mít pak např. takovouto šablonu:
{*file test.latte*}
<h1>{$title}</h1>
{snippet content}
<p>{$content}<p>
{/snippet}
V PHP bych pak chtěl použít něco takového:
//file ajax.php
/*
* Toto je soubor, který zpracovává AJAX požadavek. Z důvodu úspory přenášených dat atd. bych chtěl vygenerovat jen část šablony, označenou v {snippet content}{/snippet}
*/
include "libs/Nette/loader.php";
$template = MyFileTemplate('test.latte');
$template->content = "Hello world!";
$template->renderSnippet('content');
Metoda renderSnippet()
ale samozřejmě neexistuje, a já už
teď tuším, že jdu špatným směrem. Můžete mě pls někdo nasměrovat,
jak používat snippety v šablonách v ne-MVC architektuře? Děkuji :)
Editoval xxxObiWan (30. 5. 2011 1:11)
- nanuqcz
- Člen | 822
Tak nakonec na to jdu z úplně opačné strany. Chtěl bych z Nette šablon dostat při AJAXovém požadavku klasicky JSON strukturu, a z API jsem pochopil, že k tomu potřebuju presenter. Vyplodil jsem tedy něco takového:
Class FileTemplate extends \Nette\Application\Presenter
{
protected $template;
public function __construct($file)
{
$this->template = new \Nette\Templating\FileTemplate($file);
$this->template->control = $this;
$this->template->presenter = $this;
$this->template->registerFilter(new \Nette\Templates\LatteFilter);
}
public function __set($name, $value)
{
return $this->template->__set($name, $value);
}
public function __get($name)
{
return $this->template->__get($name, $value);
}
public function render()
{
return $this->template->render();
}
public function __toString()
{
return $this->template->__toString();
}
}
A používá se to takto:
$template = new FileTemplate("templates/homepage.latte");
$template->foo = "bar";
$template->invalidateControl("content");
$template->render();
Při AJAXovém požadavku jsem postupným debugováním zjistil, že kód
při renderování dojde uvnitř frameworku až k volání metody
LatteMacros::renderSnippets()
:
445: public static function renderSnippets($control, $local, $params)
446: {
447: $payload = $control->getPresenter()->getPayload();
448: if (isset($local->blocks)) {
449: foreach ($local->blocks as $name => $function) {
450: if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) {
451: continue;
452: }
453: ob_start();
454: $function = reset($function);
455: $function($local, $params);
456: $payload->snippets[$control->getSnippetId(substr($name, 1))] = ob_get_clean();
457: }
458: }
459: if ($control instanceof Nette\Application\UI\Control) {
460: foreach ($control->getComponents(FALSE, 'Nette\Application\UI\Control') as $child) {
461: if ($child->isControlInvalid()) {
462: $child->render();
463: }
464: }
465: }
466: }
Zde se na řádku 456
správně do proměnné
$payload->snippets[content]
uloží vyrenderovaný obsah
snippetu content
. Tady ale nastává problém –
toto je poslední výskyt proměnné $payload
v kódu, nikde se
nevrací příkazem return
, ani se neukládá zpět do presenteru
($control->getPresenter()->payload = $payload;
) a já tak
nevím, jak se k těmto datům ve svojem kódu dostat. Zdá se mi, že obsah
proměnné $payload
po skončení metody
renderSnippets()
prostě nevyužit zbytečně zanikne. Něco mi
určitě uniká. Nějaké nápady? Orientujete se vůbec v mojem problému
ještě někdo? :-)
Editoval xxxObiWan (1. 6. 2011 2:18)
- kravčo
- Člen | 721
$payload
je objekt – viď
#@@ namespace Nette\Application\UI; class Presenter; function run():
$this->payload = (object) NULL;
Takže riadok
#@@ namespace Nette\Latte\Macros; class UIMacros; function renderSnippets():
$payload = $control->getPresenter()->getPayload();
získa referenciu na objekt $presenter->payload
s ktorým
pracuje, pričom jeho obsah sa využije neskôr. Postupné debugovanie by ťa
malo priviesť až k metóde Presenter::sendPayload()
, kde sa
naplnený $presenter->payload
odosiela v
JsonResponse
.
- nanuqcz
- Člen | 822
kravčo: To jsem si původně myslel taky, jenže: (poslední dva řádky)
public static function renderSnippets($control, $local, $params)
{
$payload = $control->getPresenter()->getPayload();
if (isset($local->blocks)) {
foreach ($local->blocks as $name => $function) {
if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) continue;
ob_start();
$function = reset($function);
$function($local, $params);
$payload->snippets[$control->getSnippetId(substr($name, 1))] = ob_get_clean();
}
}
if ($control instanceof Nette\Application\Control) {
foreach ($control->getComponents(FALSE, 'Nette\Application\Control') as $child) {
if ($child->isControlInvalid()) {
$child->render();
}
}
}
\Nette\Debug::dump($payload); //vypíše správně pole s obsahem snippetu `content`
\Nette\Debug::dump($control->getPresenter()->getPayload()); //vypíše NULL
}
- nanuqcz
- Člen | 822
kravčo: tak jsi měl pravdu, $payload
je
objekt, takže se předával referencí. Problém byl v tom, že proměnná
$payload
je v Nette Presenteru private, takže v mojí
poděděné třídě jsem k ní neměl přístup. Díky za nakopnutí
správným směrem :-)
Současné (konečně) funkčí řešení tak, jak ho používám já: (odzkoušeno na Nette 5.0 alfa2)
<?php
use \Nette\Json,
\Nette\Application\Presenter,
\Nette\Templates\FileTemplate;
/**
* ZeroPresenter class
* Nulový / prázdný presenter, existuje jen proto, že Nette Presenter je abstract
*
* @author Michal Mikoláš <xxxObiWan@gmail.com>
*/
class ZeroPresenter extends Presenter {}
/**
* PresenterFileTemplate
* Mírně upravená verze Nette šablon pro samostatné používání bez MVC.
* PresenterFileTemplate je vlastně spojením presenteru a šablony :-)
*
* @author Michal Mikoláš <xxxObiWan@gmail.com>
*/
class PresenterFileTemplate
{
/** @var stdClass $payload úložiště pro obsah snippetů*/
protected $payload;
/** @var ZeroPresenter $presenter presenter, na který přes __call() přesměrovávám některé požadavky */
protected $presenter;
/** @var FileTemplate $template načtená šablona */
protected $template;
/**
* Konstruktor
* @var string $file cesta k šabloně
*/
public function __construct($file)
{
// Payload
$this->payload = new \stdClass();
// Presenter & Control
$this->presenter = new ZeroPresenter();
// Template
$this->template = new FileTemplate($file);
$this->template->control = $this; //'$this' sice není Presenter, ale pomocí __call() předává požadavky na $this->presenter
$this->template->presenter = $this;
$this->template->registerFilter(new \Nette\Templates\LatteFilter);
$this->template->registerHelper('escape', '\Nette\Templates\TemplateHelpers::escapeHtml');
$this->template->registerHelper('escapeJs', '\Nette\Templates\TemplateHelpers::escapeJs');
$this->template->registerHelper('escapeCss', '\Nette\Templates\TemplateHelpers::escapeCss');
$this->template->registerHelper('ucfirst', function($text){ return ucfirst($text); });
$this->template->registerHelper('lcfirst', function($text){ return lcfirst($text); });
}
/********************* Šablonové metody *********************/
/**
* Uložení hodnoty do šablony
* @param string $name
* @param mixed $value
* @return PresenterFileTemplate
*/
public function set($name, $value)
{
$this->template->__set($name, $value);
return $this;
}
/**
* Uložení hodnoty do šablony
* @param string $name
* @param mixed $value
* @return PresenterFileTemplate
*/
public function __set($name, $value)
{
$this->template->__set($name, $value);
return $this;
}
/**
* Získání hodnoty šablonové proměnné
* @param string $name
* @return mixed
*/
public function __get($name)
{
return $this->template->__get($name);
}
/**
* Vykreslí šablonu, při ajaxovém požadavku pošle na výstup JSON
* s obsahem invalidovaných snippetů
* @return void
*/
public function render()
{
$this->template->render();
if ($this->isAjax()) {
echo Json::encode($this->payload);
}
}
/**
* Vrací obsah šablony tak, jak by se vykreslila metodou render()
* @return string
*/
public function __toString()
{
ob_start();
$this->render();
$output = ob_get_clean();
return $output;
}
/********************* Upravené metody Presenteru *********************/
/**
* Přetížená metoda z Nette Presenteru
* @return PresenterFileTemplate
*/
public function getPresenter()
{
return $this;
}
/**
* Přetížená metoda z Nette Presenteru
* @return PresenterFileTemplate
*/
public function getControl()
{
return $this;
}
/**
* Přetížená metoda z Nette Presenteru
* @return stdClass
*/
public function getPayload()
{
return $this->payload;
}
/********************* Původní metody Presenteru *********************/
/**
* Volání metod Nette Presenteru
* @return mixed
*/
public function __call($name, $args)
{
return call_user_func_array(callback($this->presenter, $name), $args);
}
}
A používá se to pak pěkně takto :-)
$template = new PresenterFileTemplate('templates/message.latte');
$template->message = "Hello world";
$template->invalidateControl('content');
$template->render(); //anebo echo $template;, při ajaxovém požadavku pošle klasicky JSON
Napadlo mě, že bych to dal do „Tipy, triky, návody“, ale nevím, jak moc je to kvalitní řešení, a taky jestli to kromě mě vůbec někdo využije? Každopádně, kdo bude chtít používat AJAX a snippety spolu s novým super jednoduchým Nette :-) , narazí na stejný problém ;-)
Editoval xxxObiWan (2. 6. 2011 23:58)