Je nějaký důvod proč komponenty nemají metodu forward?

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

Ahoj,

Komponenty v základu nemají pohledy, to ví každý. Nejspíš je to částečně tím, že komponenty nemají životní cyklus, tedy ani action. Zjistil jsem ale, že signály jsou celkem dostačující. Můžu udělat třeba tohle:

public function handleEdit($id) {
    $this->edit = $id;
}

A v render si pošéfovat šablony. Klidně bych se vsadil, že spousta lidí (nebo aspoň začátečníků zhruba na mé úrovni) to nějak podobně používá, protože v základu to prostě jinak (asi) nejde. Problém přijde ve chvíli, kdy budu chtít udělat něco jako $this->forward(‚edit!‘, array(‚id‘=>$id));

Jasně, můžu jednoduše zavolat $this->handleEdit($id), ale co kdybych chtěl zároveň změnit nějaké persistentní parametry, třeba i v subkomponentě? Při redirectu to jde úplně v poho $this->redirect(‚edit!‘, array(‚subkomponenta-dalsisubkomponenta-parametr‘ ⇒ $hodnota)). Jistě, asi můžu ručně zavolat $this[‚subkomponenta-dalsisubkomponenta‘]->parametr = $hodnota, ale pak mám všude v komponentách jen samé:

if($this->presenter->isAjax()) {
    $this['subkomponenta-dalsisubkomponenta']->parametr = $hodnota;
    $this->handleEdit($id);
} else {
    $this->redirect('edit!', array(
        'id' => $id,
        'subkomponenta-dalsisubkomponenta-parametr' => $hodnota)
    );
}

Nevím jak vám, ale mně tohle připadá dost divný (jako kdyby if ajax udělej něco úplně odlišnýho, co ani nevypadá jako forward) a hlavně fakt otravný to psát pořád dokola. Lepší by bylo prostě a jednoduše tohle:

$params = array('id' => $id, 'subkomponenta-dalsisubkomponenta-parametr' => $hodnota);
if($this->presenter->isAjax()) {
    $this->forward('edit!', $params);
} else {
    $this->redirect('edit!', $params);
}

Neboli přibližně stejně, jako to jde v presenterech :-)

Myslím, že metoda forward by měla být součástí komponent. Napsal jsem si na to vlastní metodu, ale nejsem si až tak jistý tím, jak Nette uvnitř funguje a stopro to jde napsat líp (a neřeší to třeba parametry bez názvu). Zatím jsem však nenarazil na žádné problémy. Ideálně bych asi použil metodu $presenter->createRequest(), jenže ta metoda není public, proto přichází na scénu neobratné berličky.

// BaseControl.php

    protected function hasPersistentProperty($property) {
        $ref = $this->getReflection();
        if($ref->hasProperty($property)) {
            $refp = $ref->getProperty($property);
            return $refp->isPublic() && $refp->hasAnnotation('persistent');
        }
        return FALSE;
    }

    /** Forward to signal */
    protected function forward($destination, $args = array()) {

        // Clean up destination
        $destination = str_replace('!', '', $destination);
        $anchor = strpos($destination, '#');
        if(is_int($anchor)) {
            $destination = substr($destination, 0, $anchor);
        }

        $params = array();
        foreach($args as $key =>$param) {

            if(strpos($key, self::NAME_SEPARATOR) > 0) {
                // Subcomponent persistent param
                $names = explode(self::NAME_SEPARATOR, $key);
                $property = array_pop($names);
                $control = $this;
                foreach($names as $name) {
                    $control = $control->getComponent($name);
                }
                if($control->hasPersistentProperty($property)) {
                    $control->$property = $param;
                }
            }else if($this->hasPersistentProperty($key)) {
                // Component persistent param
                $this->$key = $param;
            } else {
                // Signal argument
                $params[$key] = $param;
            }

        }

        if($destination == 'this')
            return;

        $this->params = $params;
        $this->signalReceived($destination);
    }

Jak vidíte, je to celkem dost s*aní když chce člověk často používat obyčejný forward na signály uvnitř komponent (typicky kvůli Ajaxu) a pořád to není úplně OK. Připadám si, jako kdybych znovu vynalézal kolo (a dělal ho přitom trochu hranaté). Nějaké čisté řešení přímo v základu by dost bodlo. Ale možná je tu nějaký zjevný důvod, který nevidím, proč v základu metoda forward není?

Editoval Zax (23. 5. 2014 14:26)

Jan Endel
Člen | 1016
+
0
-

Nevím jak, ale pravděpodobně používáš komponenty dosti obskurním způsobem ke kterému nejsou určeny. Můžeš jsem hodit svůj use case, určitě přijdeme společně na nějaké víc nette way řešení.

Zax
Člen | 370
+
0
-

Jednoduše programuji v komponentách vše. Mám například nějaký seznam pomocí komponenty (řekněme seznam uživatelů – usersList) a ta komponenta v sobě obsahuje komponentu na stránkovadlo (usersList-paginator) a ta v sobě má persistentní parametr $page. Komponenta usersList si sama řeší třeba zobrazování přidávacího formuláře (včetně oprávnění zda aktuální uživatel smí do seznamu přidávat nové položky) a po uložení nového uživatele přes formulář chci udělat redirect(‚this‘, array(‚paginator-page‘ ⇒ $this[‚paginator‘]->lastPage)), ale i ajaxově. Zrovna mě nenapadá příklad kdy redirectuju na signál a k tomu ještě měním parametry v subkomponentách, ale prostě může se stát… A taky mi jde tak nějak i o to API, prostě zapsat forward stejným způsobem jako redirect (nad tím pak mám vlastní metodu redirectOrForward(…); která mi řeší if is ajax a volitelně i snippety).

Jan Endel
Člen | 1016
+
0
-

Ono každá komponenta by si signály měla řešit dost sama, navíc volání někde $this->handleXYZ je dost ugly. Každopádně ten redirect jak jsi napsal můžeš použít i bez těch overkillu co popisuješ v původním postu. Co tam dál vyrábíš?

Zax
Člen | 370
+
0
-

Redirect použít můžu, ale co Ajax? Když použiju samotný redirect, stránka se mi obnoví celá. To nechci. Chci logicky aby to směřovalo na stejné místo (chci aby aplikace fungovala stejně ať už s ajaxem nebo bez něj) a pokud možno to chci zapsat nějak jednoduše. V presenteru bych použil forward, jenže já jsem v komponentě. A nevím, jak zavolat $presenter->forward aby zohledňoval aktuální komponentu (jde to vůbec?).

Jan Endel
Člen | 1016
+
0
-

To přidávání uživatele máš v modalu?

Zax
Člen | 370
+
0
-

Ne, přímo v té komponentě se zobrazuje nad seznamem. Nechce se mi to popisovat celý, je to celkem na dlouho, ale obecně mi jde prostě o to, abych na všech místech, kde volám $this->redirect() v komponentách, měl možnost udělat to samé pro Ajax (tedy forward s tím, že navíc akorát zavolám redrawControl). Myslím, že je to celkem validní požadavek a v presenterech toto vyřešené je, protože tam je metoda forward. V komponentách není.

Editoval Zax (23. 5. 2014 16:48)

Jan Endel
Člen | 1016
+
0
-

forward se ale pro ajax nepoužívá nák echt, znáš invalidateControl()? :-)

Zax
Člen | 370
+
0
-

invalidateControl === redrawControl a ten už jsem zmiňoval. Použití forward v presenteru v případě ajaxu je legitimní záležitost, pokud vím. Ne vždy stačí if($isAjax) $this->redrawControl(); else $this->redirect(‚this‘);

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Pokud potřebuješ provést forward v komponentě, lze to snadno:

$this->getPresenter()->forward('...');

Není to ale moc pěkné. Lepší je, aby ta komponenta poskytovala událost, do které se může zaregistrovat presenter se svým $this->forward('...') voláním.

Zax
Člen | 370
+
0
-

To je ale forward v rámci presenteru, nemám pravdu? Když zavolám

$this->getPresenter()->forward('edit!', array('id'=>$id));

tak se hledá handleEdit($id) v presenteru, nikoliv v komponentě.

Hmm… momentík, právě mi asi něco došlo O_o

Fungovalo by tohle?

$fullName = $this->getUniqueId();
$this->getPresenter()->forward($fullName . '-edit!', array($fullName . '-id' => $id));

To musím vyzkoušet…

Zax
Člen | 370
+
0
-

Hmm náhodou to tak fakt funguje! Pěkný!

// BaseControl.php

    protected function forward($destination, $args = array()) {
        $name = $this->getUniqueId();
        if($destination != 'this')
            $destination =  "$name-$destination";
        $params = array();
        foreach($args as $key => $val) {
            $params["$name-$key"] = $val;
        }
        $this->getPresenter()->forward($destination, $params);
    }

EDIT: Akorát když udělám redrawControl před forward, tak se to ignoruje, musím to psát přímo v signálu. No asi nevadí…

Editoval Zax (23. 5. 2014 21:31)