Problém s dynamickými snippety

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

Pracuji na aplikaci, která umožňuje vyhledávat zboží. Rozhodl jsem se, že přidám ke každému nalezenému zboží hvězdičku pro přidání mezi oblíbené zboží uživatele. Vše by bylo v pohodě, kdybych se to nesnažil udělat pomocí AJAXu.

Používám tento návod: https://doc.nette.org/…mic-snippets

Pro vykreslování nalezeného zboží používám vlastní komponentu, která přebírá pole nalezených položek $items, ty následně zobrazí.

1) Jako první řešení jsem zkusil toto: https://doc.nette.org/…mic-snippets#…
Bohužel jakmile jsem invalidoval celý blok, nastala tato chyba:

<?php
Nette\InvalidArgumentException: Invalid argument passed to foreach resp. Nette\Iterators\CachingIterator; array or Traversable expected, NULL given.
?>

Myslím si, že se do komponenty znovu nepředává pole $items a nevím, jak to mám udělat.

<?php
class SearchResultControl extends Nette\Application\UI\Control
{
    private $items;

    public function __construct($items)
    {
        parent::__construct();
        $this->items = $items;
    }

    public function handleAddFavorite($idItem)
    {
        if ($this->presenter->isAjax()) {
            $itemRepository = $this->presenter->context->itemRepository;
            $itemRepository->addToFavorite($this->presenter->user->id, $idItem);
            $this->invalidateControl('itemsContainer');
        }
    }

    public function handleRemoveFavorite($idItem)
    {
        if ($this->presenter->isAjax()) {
            $itemRepository = $this->presenter->context->itemRepository;
            $itemRepository->removeFromFavorite($this->presenter->user->id, $idItem);
            $this->invalidateControl('itemsContainer');
        }
    }

    public function render()
    {
        $this->template->setFile(__DIR__ . '/SearchResult.latte');
        $this->template->items = $this->items;
        $this->template->render();
    }
}
?>

SearchResult.latte

<?php
    {snippet itemsContainer}
    {foreach $items as $item}
    <tr n:class="$iterator->isOdd() ? odd : even">
        <td>
                {snippet item-$item->id_item}
                {ifset $item->favorite}
                    <a n:href="removeFavorite! $item->id_item" class="ajax" title="Odebrat z oblíbených"><img src="{$basePath}/images/star_full.gif" border="0" /></a>
                {else}
                    <a n:href="addFavorite! $item->id_item" class="ajax" title="Přidat mezi oblíbené"><img src="{$basePath}/images/star_empty.gif" border="0" /></a>
                {/ifset}
                {/snippet}

        </td>
        <td>{$item->number}</td>
        <td>{$item->description}</td>
    </tr>
    {/foreach}
    {/snippet}
?>

2) Druhý pokus jsem zkusil toto: https://doc.nette.org/…mic-snippets#…
Vytvořil jsem si komponentu pro „favoritování“ položek.

Problém nastal s vkládáním komponenty pomocí Multiplier. Jelikož mám pole, které je číslováno od nuly a ne podle ID jako v příkladu vytvořil jsem tento hnus.

<?php
    protected function createComponentFavoriteControl()
    {
        $items = $this->items;
        return new Nette\Application\UI\Multiplier(function ($itemId) use ($items) {
            foreach($items as $key => $item) { // zde to hlásí chybu
                if ($item->id_item == $itemId) {
                    return new FavoriteControl($item);
                }
            }
        });
    }
?>

Po kliknutí na hvězdičku se nic nestane a logu se objeví tato chyba:

<?php
Fatal error: Invalid argument supplied for foreach()
?>

Říkám si, jestli to nemá spojitost s chybou v prvním pokusu, protože komponenta FavoriteControl se vytváří v komponentě SearchResultControl. Myslím si, že nedochází k dosazení pole $items v komponentě.
Díky moc za každou radu co s tím!

David Matějka
Moderator | 6445
+
0
-

Asi spatne inicializujes tu komponentu – nepredavas $items, ukaz presenter, kde ji vytvaris.

dr3ex
Člen | 8
+
0
-

Presenter vypadá následovně:

<?php

use Nette\Application\UI\Form;
use Nette\Diagnostics\Debugger;

class SearchPresenter extends BasePresenter {

    private $items;

    protected function startup()
    {
        parent::startup();
        if (!$this->getUser()->isLoggedIn()) {
            $this->redirect('Sign:in');
        }
    }

    protected function createComponentSearchForm()
    {
        $sfc = new SearchFormControl();
        $sfc['form']->onSuccess[] = $this->searchFormSucceeded;
        return $sfc;
    }

    public function searchFormSucceeded($form)
    {
        $values = $form->getValues();

        $params = array(
            "item" => $values->item,
            "maxResults" => $values->maxResults
        );
        $settings = new SearchSettings($params);

        $this->items = $this->itemRepository->search($settings);
    }

    protected function createComponentSearchResult()
    {
        return new SearchResultControl($this->items);
    }

    public function renderDefault() {
        $userData = $this->getUser()->getIdentity()->getData();
        $this->template->userName = $userData['login'];

        if ($this->getUser()->isInRole('admin')) {
            $this->template->menu = "menuLoggedInAdmin";
        } else {
            $this->template->menu = "menuLoggedIn";
        }
    }

}
?>

Nemůže být problém s tím, že vyhledávací formulář je odesílán POSTem?

duke
Člen | 650
+
0
-

Problém je v tom, že property $items SearchPresenteru plníš jen v případě, kdy se zpracovává formulář, tedy nikoli v případě zpracování signálů removeFavorite a addFavorite.

To plnění property $items je třeba přesunout jinam (buď do render metody nebo do komponenty). Ve zpracování formuláře si jen ulož potřebné údaje (nejlépe asi do persistentních proměnných presenteru).

Snad jsem to řekl dobře…

dr3ex
Člen | 8
+
0
-

duke: Po tvé radě jsem myslel, že už to nemůže nefungovat. Tak jsem se spletl.

Upravil jsem SearchPresenter následovně:

<?php

use Nette\Application\UI\Form;
use Nette\Diagnostics\Debugger;

class SearchPresenter extends BasePresenter {

    private $itemRepository;
    private $items;
    private static $parameters;

    protected function startup()
    {
        parent::startup();
        if (!$this->getUser()->isLoggedIn()) {
            $this->redirect('Sign:in');
        }
        $this->itemRepository = $this->context->itemRepository;
    }

    protected function createComponentSearchForm()
    {
        $sfc = new SearchFormControl();
        $sfc['form']->onSuccess[] = $this->searchFormSucceeded;
        return $sfc;
    }

    public function searchFormSucceeded($form)
    {
        $values = $form->getValues();

        self::$parameters= array(
            "item" => $values->item,
            "maxResults" => $values->maxResults
        );
    }

    protected function createComponentSearchResult()
    {
        return new SearchResultControl($this->items);
    }

    public function renderDefault() {
        if (isset(self::$parameters)) {
           $settings = new SearchSettings(self::$parameters);
           $this->items = $this->itemRepository->search($settings);
        }

        $userData = $this->getUser()->getIdentity()->getData();
        $this->template->userName = $userData['login'];

        if ($this->getUser()->isInRole('admin')) {
            $this->template->menu = "menuLoggedInAdmin";
        } else {
            $this->template->menu = "menuLoggedIn";
        }
    }

}
?>

Vyhledávání funguje jako předtím, jakmile odešlu ajaxový požadavek na server. Firebug hlásí chybu (500) a v logu se objeví:

<?php
Nette\InvalidArgumentException: Invalid argument passed to foreach resp. Nette\Iterators\CachingIterator; array or Traversable expected, NULL given.
?>

Připadá mi, jako by se statická proměnná $parameters ztratila a podmínku isset v render to přeskočí. Jak to mám správně ukládat persistentně? Už mě napadá jedině to ukládat do session.

David Matějka
Moderator | 6445
+
0
-
  1. vyhod tu statickou promennou
  2. opravdu si myslis, ze pri novem requestu zustane v te promenne nejaka hodnota?
  3. pouzij persistentni parametr, jak ti radil duke
  4. (tohle spatne poradil duke) – v render metode uz je pozde, ty polozky musis vybirat uz v action (pripadne az v komponente)
duke
Člen | 650
+
0
-

Pro úplnost ohledně té render metody… Šlo by to také, ale jen s dalšími úpravami. Jde o to, v jakém pořadí se co volá:

  1. action metoda
  2. zpracování signálů (handleAddFavorite, handleRemoveFavorite)

    2.1 v rámci tohoto (před voláním handle metod) se volá i továrnička SearchPresenter::createComponentSearchResults (pokud se již nevolala dříve z action metody – volá se automaticky při prvním sáhnutí na $presenter['searchResults'])

  3. render metoda presenteru
  4. render metody vykreslovaných komponent v šabloně (tj. v případně ajaxu jen v invalidovaných snippetech)

Takže pokud bys načítal $items až v render metodě presenteru, musel bys komponentě tyto předat až poté, tj. např.:

$this['searchResults']->setItems($this->items);

… a nikoli již v továrničce přes konstruktor, jak to děláš teď.

Načítat to v action metodě lze také, ale pokud bys měl na stránce např. více komponent, z nichž některá by mohla vynucovat redirekci, tak bys to načítal v takovém případě zbytečně. Doporučuji co nejlínější načítání zdrojů a proto bych načítal až z komponenty. Co vidím, tak s $items pracuješ stejně pouze v té komponentě, tak mi není jasné, proč to vůbec řešíš v presenteru. Jinak persistentní parametry může mít i ta komponenta (nemusí to být přímo presenter). Takže téměř celou logiku toho, co děláš, můžeš mít v té komponentě.

Editoval duke (11. 3. 2014 14:18)