Naja – jak řídit forceRedirect true/false

m.brecher
Generous Backer | 759
+
0
-

Ahoj,

ve formuláři potřebuji po různých akcích různý typ přesměrování:

  • po vytvoření nového záznamu ajaxový redirect na novou url
  • po smazání záznamu neajaxový (hard) redirect na výpis zbylých záznamů

Formulář mám v UI\Control, používám knihovnu Naja. Použil jsem kód podle staršího příspěvku na fóru:

if($this->presenter->isAjax()){
    $this->redrawControl();
    $this->presenter->payload->forceRedirect = true;
    $this->presenter->redirect(':default');
}

payload:

{
    "forceRedirect": true,
    "redirect": "http://localhost/project/test?_fid=d0ij",
    // ...
}

Naja ignoruje forceRedirect = true

naja.redirectHandler.addEventListener('redirect', (event) => {
    console.log(event.detail.isHardRedirect)       // false
})

Podle dokumentace Naja lze hardRedirect provést takto:

naja.redirectHandler.addEventListener('redirect', (event) => {
    event.detail.setHardRedirect(true)
})

Toto funguje OK, ale hard redirect se nastaví pro VŠECHNY redirecty. V daném formuláři je ale potřeba použít v konkrétní situaci určitý typ redirectu. V redirectHandleru ale není k dispozici payload, aby bylo možno podle zaslaného pokynu ze serveru nastavit event.detail.setHardRedirect(true/false).

Řešením by mohlo být nějakým způsobem dostat payload do redirectHandleru, ale nevím jak a jestli to vůbec jde. Díky předem za jakékoliv tipy.

Editoval m.brecher (8. 11. 2023 4:23)

Pepino
Člen | 249
+
0
-

Můžeš si ten payload odchytit v jiném eventu a provést redirect.

jiri.pudil
Nette Blogger | 1028
+
+1
-

Od 2.5.0 je tam událost payload, která se přesně k tomuhle dá použít.

Popřípadě data-naja-force-redirect na formuláři nebo submit tlačítku.

m.brecher
Generous Backer | 759
+
0
-

@jiripudil

Od 2.5.0 je tam událost payload, která se přesně k tomuhle dá použít.

Díky za tip, vyzkoušel jsem a jde to.

ForcedRedirect

v Nette vložím do payload flag označující typ redirectu

$this->presenter->payload->forcedRedirect = true

ForcedRedirect vynutím javascriptem a ručně přidám do historie nové url, protože to prohlížeč automaticky nedělá. Funguje to OK.

naja.addEventListener('payload', (event) => {
    const payload = event.detail.payload
    if(payload.forcedRedirect === true){
        window.location.href = payload.redirect        // works ok
        window.history.pushState(payload.redirect)
    }
})

AjaxRedirect do jiné akce

Co se stále nedaří je ajaxový redirect formuláře po vytvoření nového záznamu, kdy potřebuji přesměrovat z akce /create na akci /update/id. V akci update se nastaví formuláři updateMode = true a vykreslí se formulář pro editaci záznamu, který se liší od formuláře pro nový záznam.

Pokus 1 – ajaxový redirect() presenteru zpracovaný Naja standardně

// po odeslání formuláře pro nový záznam z akce /create
if($this->presenter->isAjax()){
    $this->presenter->redirect(':update', ['id' => $row->id]);  // klasický ajax redirect
}

Po odeslání formuláře proběhnou dva ajaxové requesty, první na /create, druhý na /update/id, formulář se vykreslí správně z akce /update/id, ale NEZMĚNÍ se aktuální url v prohlížeči.

Pokus 2 – použití flagu postGet pro Naja

if($this->presenter->isAjax()){
    $this->presenter->payload->postGet = true;
    $this->presenter->payload->url = $this->presenter->link(':update', ['id' => $row->id]);
    $this->redrawControl();
}

Kód je dle dokumentace Naja https://naja.js.org/#…

Po odeslání formuláře proběhne jeden ajaxový request na akci /create a změní se aktuální url prohlížeče na /update/id, formulář se nevykreslí v updateMode, ale v createMode, protože se vykresluje ze staré akce. Potřebuji, aby to fungovalo jako „skutečný“ redirect, kdy se komponenta sosá z cílového url redirectu – zde sosá ze staré url.

Pokus 3 – vlastní řešení pomocí naja.makeRequest() v události Naja payload

if($this->presenter->isAjax()){
    $this->presenter->redirect(':update', ['id' => $row->id]);  // ajax redirect
}
naja.addEventListener('payload', (event) => {
   naja.makeRequest('GET', event.detail.payload.redirect, null)
       .then(response => {
           if(!response.ok){
               throw new Error('error in server response')
           }
       })
       .catch(error => console.log(error))
})

Proběhnou tři ajaxové requesty:

– první uložení data formuláře,
 – druhý vyvolaný naja.makeRequest(), skončí chybou response.ok = false,
 – třetí je defaultní ajax redirect Naja, ten vykreslí formulář z nového url, ale NEAKTUALIZUJE url v prohlížeči.

Odstraním naja.makeRequest(), k chybnému response nedojde, ale defaultní ajax request Naja funguje stejně – nezmění se url v prohlížeči

naja.addEventListener('payload', (event) => {
    //  default ajax redirect
})

Aktualizace historie prohlížeče nejde vynutit:

naja.addEventListener('payload', (event) => {
    window.history.pushState({}, "", payload.redirect)   // nefunguje
})

Otázky jsou dvě:

a) čím je způsobena chyba v response naja.makeRequest(..) – není to tím, že už se zpracovává redirect ??

b) proč nelze vynutit nastavení aktuálního url v historii prohlížeče.

Potřebuji, aby se po uložení dat formuláře poslal nový ajaxový request na cílové url, kde je také komponenta formuláře, z cílového url se nasosal snipet pro komponentu formuláře a aktualizovalo se url v prohlížeči.

Díky předem, kdyby Tě napadlo jak to vyřešit.

Editoval m.brecher (9. 11. 2023 10:59)

jiri.pudil
Nette Blogger | 1028
+
0
-

ručně přidám do historie nové url, protože to prohlížeč automaticky nedělá

To by ovšem udělat měl 🤔

Co se týká ajaxového redirectu, správně by měl být už pokus 1. Jen pozor na to, že ten druhý request využívá stejné options jako ten první, takže pokud tam (anebo globálně) nějak upravuješ nastavení historie, může to mít vliv. To je jediné, co mě k tomu napadá…

Ad pokus 3: response z makeRequest obsahuje payload, nikoliv Response, nejspíš proto je response.ok false

m.brecher
Generous Backer | 759
+
0
-

@jiripudil

ručně přidám do historie nové url, protože to prohlížeč automaticky nedělá … To by ovšem udělat měl

To je asi jádro celého problému, protože když přidání položky do historie url po redirectu bude v Naja fungovat, tak to je vyřešené.

// obsluha submitu formuláře
if($this->presenter->isAjax()){
    $this->presenter->redirect(':update', ['id' => $row->id]);  // klasický ajax redirect
}

Naja po serverovém redirectu (kód výše) pošle ajaxový GET request na url: /update/id, kde mám tuto obsluhu v presenteru:

public function actionUpdate(int $id): void
{
    if($this->isAjax()  && $this->getHttpRequest()->isMethod('GET')){
       $this['myForm']->redrawControl();
    }
}

V akci /update se obsluhuje editace formuláře a obsluhu submitu formuláře versus přesměrování (obojí ajaxové) odliším podle metody POST/GET. Tento kód asi nebude zdrojem chyby, to funguje.

S historií prohlížeče v Naja nikde nemanipuluji, nic tam nikde nenastavuji.

m.brecher
Generous Backer | 759
+
0
-

Nepodařilo se mě přesvědčit knihovnu Naja, aby po ajaxovém redirectu odpovídajícím způsobem upravila url. Nakonec bylo rychlejší si pro ajaxové formuláře napsat vlastní javascriptový ovladač. Po překreslení snippetů spouští automaticky překreslené vložené javascripty a má událost complete, ve které lze spustit nějaký externí skript. Po forcedRedirectu přidá nové url do historie, po ajaxRedirectu nahrazuje původní url novým. Konkrétní provedení redirectu lze snadno ovládat dvěma flagy v nette payloadu:

if($this->isAjax()){
   $this->presenter->payload->forcedRedirect = true
   $this->redirect($destination, $args)
}
if($this->isAjax()){
   $this->presenter->payload->pushState = true
   $this->redirect($destination, $args)
}
<script>
    class Bite extends EventTarget
    {
        #ajaxAttribute = 'data-ajax'

        handleSubmit(event)
        {
            event.preventDefault()
            const form = event.target
            if(form.hasAttribute(this.#ajaxAttribute)){
                const body = this.#getBody(form, event.submitter)
                this.#submitData(form, body)
            }
        }

        #getBody(form, submitter)
        {
            let body = form.enctype === 'application/x-www-form-urlencoded'
                ? new URLSearchParams(new FormData(form))
                : new FormData(form);
            body.append(submitter.name, submitter.value)
            return body
        }

        #submitData(form, body)
        {
            fetch(form.action, {
                method: form.method,
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': form.enctype,
                },
                body: body
            })
                .then(response => {
                    if(!response.ok){
                        throw new Error('server respond failed')
                    }
                    return response.json()
                })
                .then(payload => this.#processResponse(payload))
                .catch(error => console.error(error));
        }

        #processResponse(payload)
        {
            if(payload.snippets){
                const snippets = payload.snippets
                for(let id in snippets){
                    this.#redrawSnippet(id, snippets[id])
                    let snippet = document.getElementById(id)
                    this.#executeScripts(snippet)
                }
                let completeEvent = new Event('complete')
                this.dispatchEvent(completeEvent)
            }
            const redirectUrl = payload.redirect
            const pushState = payload.pushState
            if(redirectUrl){
                if(payload.forcedRedirect){
                    this.#forcedRedirect(redirectUrl)

                }else{
                    this.#redirect(redirectUrl, pushState)
                }
            }
        }

        #redrawSnippet(id, html)
        {
            let snippet = document.getElementById(id)
            if(snippet === null){
                throw new Error('snippet ' + id + ' not found')
            }
            snippet.innerHTML = html
        }

        #forcedRedirect(redirectUrl)
        {
            window.location.href = redirectUrl
            window.history.pushState({}, "", redirectUrl)
        }

        #redirect(redirectUrl, pushState)
        {
            fetch(redirectUrl, {
                method: 'GET',
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': 'application/json',
                },
            })
                .then(response => {
                    if(!response.ok){
                        throw new Error('server respond failed')
                    }
                    return response.json()
                })
                .then(payload => {
                    this.#processResponse(payload)
                    if(pushState){
                        window.history.pushState({}, "", redirectUrl)

                    }else{
                        window.history.replaceState(null, '', redirectUrl)
                    }
                })
                .catch(error => console.error(error));
        }

        #executeScripts(snippet)
        {
            let scripts = snippet.getElementsByTagName('script');
            for (let i = 0; i < scripts.length; i++) {
                eval(scripts[i].innerText);
            }
        }
    }
    const bite = new Bite()
    document.addEventListener('DOMContentLoaded', () => {
        bite.addEventListener('complete', () => {
            something.init()
        })

    })
    document.addEventListener("submit", (event) => bite.handleSubmit(event))
</script>

Editoval m.brecher (11. 11. 2023 14:52)

jiri.pudil
Nette Blogger | 1028
+
0
-

For the record, na slacku se objevil další případ s obdobným problémem. Trochu jsem se porozhlédnul v kódu a mám silné podezření, že se jedná o bug.

m.brecher
Generous Backer | 759
+
0
-

@jiripudil

Budu v sobotu v Brně, tak to můžeme probrat u piva po skončení oficiální části posoboty.