Nette šablony samostatně – AJAX

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
nanuqcz
Člen | 822
+
0
-

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)

22
Člen | 1478
+
0
-

použij klasický ajax z jQuery ne? :-)

nanuqcz
Člen | 822
+
0
-

Popravdě, to mě vůbec nenapadlo :-D A lze nějak donutit šablonu, aby mi vrátila jen část stránky, nebo musím přes jQuery načítat celou stránku včetně obsahu, který vůbec nepotřebuju? Něco jako:

$template->render('<div id="__ajax_header">');   // :-)
22
Člen | 1478
+
0
-

nevím, jestli tě chápu správně, ale:

$.ajax({
  url: "test.php",
  success: function(data){
    $('div.ajax').text(data); //do div ajax se vykreslí data, zbytek se nehne
  }
});

Editoval 22 (28. 5. 2011 17:31)

mkoubik
Člen | 728
+
0
-

Možná to dát do bloku a pak udělat šablonu template.ajax.phtml:

{extends template.phtml}
{include #ajax}
nanuqcz
Člen | 822
+
0
-

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

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

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

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

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

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)