Naja – jak řídit forceRedirect true/false
- m.brecher
- Generous Backer | 864
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)
- jiri.pudil
- Nette Blogger | 1029
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 | 864
@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 | 1029
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 | 864
@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 | 864
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 | 1029
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.