Volání callbacku komponenty ze šablony

Fires
Člen | 97
+
0
-

Zdravím, chci poprosit o radu.
Problém: Presenter zobrazuje nějaká data, v rámci toho vytváři komponentu ItemTable, ta zobrazí tabulku s daty. Potřebuji do komponenty nějak předat url(“generator”) na které se má přesměrovat v případě kliku/dvojkliku. Jedná se vetšinou o jinou action u stejného presenteru která získá itemId jako parametr url, nastaví jej do druhé komponenty ItemModal pro popup modalu a pomocí $this->forward(“default”) vykresí zase default šablonu. Chtěl jsem nějak oddělit logiku komponenty a presenteru a udělat to nějak modulární. Aby komponenta nemusela mít nic napevno a presenter ji nastavil parametry, resp ji předal něco do čeho si vloží id a vypadne ji URL které má nastavit.

Vytvoření komponenty v presenteru:

    public function createComponentItemTable(){
        $itemTable =  $this->itemTableFactory->create();
        $itemTable->onDoubleClickUrlRequire[] = function($itemId){
            $link = $this->link('item',$itemId);
            return $link;
        };
	return $itemTable;
}

V šabloně komponenty jsem pak volal

data-handle-doubleclick-url="{$control->onDoubleClickUrlRequire($item->id)}"

pokud si vypíšu link v presenteru je validní url, ale v komponentě vždy vráti null a netuším proč :) AI v tom má ještě větší chaos jak já.

Asi to není ani nejlepší způsob jak tohle řešit ale nic lepší mě nenapadlo. Pokud máte lepší řešení beru. Napadl mě ještě opačný postup do komponenty nastavit cestu/target k action “Presenter:action” a komponenta by si pak volala $link->($target, $itemId);.

Snad to dává alespoň trochu smysl. Předem díky všem za komenty.

nightfish
Člen | 518
+
+2
-

@Fires IMHO je problém v $itemTable->onDoubleClickUrlRequire[] – prakticky to říká, že přidáváš handler “Nette eventu”, zatímco chceš spíš volat metodu.

Osobně bych to řešil předáním funkce, která má na starosti vygenerovat odkaz, z presenteru do komponenty, a následně jejím použitím jako Latte funkce.

Pro PHP 8.1+ by to mohlo vypadat +/- takto:

// Presenter
public function createComponentItemTable() {
    return $this->itemTableFactory->create(static fn(int $itemId): string => $this->link('item', $itemId));
}

// ItemTableFactory
interface ItemTableFactory {
	public function create(\Closure $linkCallback): ItemTable;
}

// komponenta ItemTable
class ItemTable extends AbstractControl {
	public function __construct(
		private \Closure $linkCallback,
	) {
	}

	public function render(): void {
		$this->getTemplate()->addFunction('link', ($this->linkCallback)(...));
	}
}

ItemTable.latte

<div data-handle-doubleclick-url="{link($item->id)}">...</div>
Fires
Člen | 97
+
0
-

nightfish napsal(a):

@Fires IMHO je problém v $itemTable->onDoubleClickUrlRequire[] – prakticky to říká, že přidáváš handler „Nette eventu“, zatímco chceš spíš volat metodu.

Osobně bych to řešil předáním funkce, která má na starosti vygenerovat odkaz, z presenteru do komponenty, a následně jejím použitím jako Latte funkce.

Pro PHP 8.1+ by to mohlo vypadat +/- takto:

// Presenter
public function createComponentItemTable() {
    return $this->itemTableFactory->create(static fn(int $itemId): string => $this->link('item', $itemId));
}

// ItemTableFactory
interface ItemTableFactory {
	public function create(\Closure $linkCallback): ItemTable;
}

// komponenta ItemTable
class ItemTable extends AbstractControl {
	public function __construct(
		private \Closure $linkCallback,
	) {
	}

	public function render(): void {
		$this->getTemplate()->addFunction('link', ($this->linkCallback)(...));
	}
}

ItemTable.latte

<div data-handle-doubleclick-url="{link($item->id)}">...</div>

Díky moc..

m.brecher
Generous Backer | 871
+
+1
-

@Fires

Asi řešíš něco podobného jako já. Já používám v tabulce v šabloně akce presenteru javascriptový odkaz na tagu <tr> tak, aby byl klikací celý řádek tabulky takto:

javascript TableLink.js – soubor vložím do šablony latte:

class TableLink
{
    #ajaxAttribute = 'data-ajax'
    #linkAttribute = 'data-link'

    constructor()
    {
        document.addEventListener('click', (event) => this.#linkClick(event))
    }

    #linkClick(event)
    {
        let elem = event.target
        while(elem && elem.nodeName !== 'BODY' && elem.nodeName !== 'A'){
            if(elem.hasAttribute(this.#linkAttribute)){
                if(!elem.hasAttribute(this.#ajaxAttribute)){
                    location.href = elem.dataset.link
                }
                break;
            }
            elem = elem.parentNode
        }
    }
}
new TableLink()

default.latte

            {foreach $clients as $id => $client}
                {var $href = href(':view', $id)}
                <tr data-link="{$href}" class="body">
                    <td><a href="{$href}" class="text-link">{$clientModel->getClientTitle($client)}</a></td>
                </tr>
            {/foreach}

url je v proměnné $href, která se použije dvakrát – v tagu <tr> a v tagu <a>, $href se získá pomocí latte funkce href(), kterou definuji zde:

class Filters
{
    public static function href(Presenter $presenter, string $destination, int|array|null  $args = null): string
    {
        $args = match(true){
            is_int($args) => ['id' => $args],
            is_array($args) => $args,
            default => [],
        };
        return $presenter->link($destination, $args);
    }

}

Funkci filtru zaregistruji traitou:

trait FiltersTrait
{
    public function injectFilter(): void
    {
        $this->onRender[] = function(){
 			// .....
            $template->addFunction('href', fn(string $destination, int|array|null $args = null) => Filters::href($this, $destination, $args));
        };
    }
}

traitu vložím do BasePresenteru

abstract class BasePresenter extends UI\Presenter
{
    use FiltersTrait;
}

Poznámka:

a) Pokud odkaz posílá ajaxový request, který indikuji atributem data-ajax (rozdíl oproti Naja, která vyžaduje označení linku class=„ajax“) tak javascript chování modifikuje.

b) Nemám FiltersTrait vyzkoušenou v UI\Control, pro použití jak v presenteru, tak v UI\Control asi bude potřeba v traitě injektnout LinkGenerator, který má také metodu link() do traity a použít místo $this (presenter), nějak takto:

trait FiltersTrait
{
    public function injectFilter(Nette\Application\LinkGenerator $linkGenerator): void
    {
        $this->onRender[] = function() use($linkGenerator){		// edited
 			// .....
            $template->addFunction('href', fn(string $destination, int|array|null $args = null) => Filters::href($linkGenerator, $destination, $args));
        };
    }
}
  • upravit typehint v metodě href(LinkGenerator $linkGenerator, .....)

V podstatě jsem problém vyřešil v latte, filtru latte a javascriptu, komponenta samotná o ničem neví – což je ideální.

Editoval m.brecher (4. 9. 16:18)

Fires
Člen | 97
+
0
-

m.brecher napsal(a):

@Fires

Asi řešíš něco podobného jako já. Já používám v tabulce v šabloně akce presenteru javascriptový odkaz na tagu <tr> tak, aby byl klikací celý řádek tabulky takto:

javascript TableLink.js – soubor vložím do šablony latte:

class TableLink
{
    #ajaxAttribute = 'data-ajax'
    #linkAttribute = 'data-link'

    constructor()
    {
        document.addEventListener('click', (event) => this.#linkClick(event))
    }

    #linkClick(event)
    {
        let elem = event.target
        while(elem && elem.nodeName !== 'BODY' && elem.nodeName !== 'A'){
            if(elem.hasAttribute(this.#linkAttribute)){
                if(!elem.hasAttribute(this.#ajaxAttribute)){
                    location.href = elem.dataset.link
                }
                break;
            }
            elem = elem.parentNode
        }
    }
}
new TableLink()

default.latte

            {foreach $clients as $id => $client}
                {var $href = href(':view', $id)}
                <tr data-link="{$href}" class="body">
                    <td><a href="{$href}" class="text-link">{$clientModel->getClientTitle($client)}</a></td>
                </tr>
            {/foreach}

url je v proměnné $href, která se použije dvakrát – v tagu <tr> a v tagu <a>, $href se získá pomocí latte funkce href(), kterou definuji zde:

class Filters
{
    public static function href(Presenter $presenter, string $destination, int|array|null  $args = null): string
    {
        $args = match(true){
            is_int($args) => ['id' => $args],
            is_array($args) => $args,
            default => [],
        };
        return $presenter->link($destination, $args);
    }

}

Funkci filtru zaregistruji traitou:

trait FiltersTrait
{
    public function injectFilter(): void
    {
        $this->onRender[] = function(){
 			// .....
            $template->addFunction('href', fn(string $destination, int|array|null $args = null) => Filters::href($this, $destination, $args));
        };
    }
}

traitu vložím do BasePresenteru

abstract class BasePresenter extends UI\Presenter
{
    use FiltersTrait;
}

Poznámka:

a) Pokud odkaz posílá ajaxový request, který indikuji atributem data-ajax (rozdíl oproti Naja, která vyžaduje označení linku class=„ajax“) tak javascript chování modifikuje.

b) Nemám FiltersTrait vyzkoušenou v UI\Control, pro použití jak v presenteru, tak v UI\Control asi bude potřeba v traitě injektnout LinkGenerator, který má také metodu link() do traity a použít místo $this (presenter), nějak takto:

trait FiltersTrait
{
    public function injectFilter(Nette\Application\LinkGenerator $linkGenerator): void
    {
        $this->onRender[] = function() use($linkGenerator){		// edited
 			// .....
            $template->addFunction('href', fn(string $destination, int|array|null $args = null) => Filters::href($linkGenerator, $destination, $args));
        };
    }
}
  • upravit typehint v metodě href(LinkGenerator $linkGenerator, .....)

V podstatě jsem problém vyřešil v latte, filtru latte a javascriptu, komponenta samotná o ničem neví – což je ideální.

Díky, mám ještě hodně co studovat :)