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)