You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi. Thanks for this contribution and proposal. I'd like this see something like this built into browsers/HTML.
While reading the code and playing around with it, I noticed what could be a bug. There's a replacePage function, which should replace the full document. This function is supposedly used when there's no target in the element, as seen in the ajax function and in the README:
When targets are not supplied on buttons and forms, Triptych simulates the full-page behavior to the best of its ability. It replaces the entire document and uses the history API to update the browser URL and history. Clicking "back" should basically work as expected.
However, the previous condition that checks that the target exists, seems to invalidate this, and logs an error then returns null. Which makes it impossible to implement a behaviour to replace the full page.
Here's an example self-contained HTML document to showcase the bug. I copy-pasted the source code as of commit 70803de and replaced the fetch() call with a sample text to make it easier to run.
<!DOCTYPE html><html><head><script>// These are vars so that they just get redeclared if the script is re-executedvarADDITIONAL_FORM_METHODS=["PUT","PATCH","DELETE"];varEXISTING_TARGET_KEYWORDS=["_self","_blank","_parent","_top","_unfencedTop"];/** * Mimic a full-page navigattion with the history API and a full-document replacement. * * @param {string} html * @param {string} url * @param {boolean} addHistory */functionreplacePage(html,url,addHistory){if(!history.state){conststate={html: document.documentElement.innerHTML,url: window.location.href};history.replaceState(state,"");}// Replace the page's HTML and, if applicable, add it to the history statedocument.querySelector("html").innerHTML=html;if(addHistory)history.pushState({ html, url },"",url);// We have to manually execute all the scripts that we inserted into the pagedocument.querySelectorAll("script").forEach((oldScript)=>{constnewScript=document.createElement("script");Array.from(oldScript.attributes).forEach((attr)=>{newScript.setAttribute(attr.name,attr.value);});constscriptText=document.createTextNode(oldScript.innerHTML);newScript.appendChild(scriptText);oldScript.replaceWith(newScript);});}/** * Return the function that performs the request and replacement when fired * * @param {string} rawUrl * @param {string} method * @param {FormData} formData * @param {string} target */functionajax(rawUrl,method,formData,target){// Remove the query string form the URL, if it's presentconstqueryIndex=rawUrl.indexOf("?");leturl=queryIndex>-1 ? rawUrl.substring(0,queryIndex) : rawUrl;/** @param {Event} e */returnasync(e)=>{// End immediately if there's an iFrame with the target nameif(document.querySelector(`iframe[name="${target}"]`))returnnull;// This comes after the iFrame check so that normal iFrame targeting is preservede.preventDefault();// Replace self if the target is `_this`, otherwise replace the target from a querySelectorlettargetElement;if(target==="_this"){targetElement=e.target;}else{targetElement=document.querySelector(target);}if(!targetElement){console.error(`no element found for target ${target} - ignorning`);returnnull;}constopts={ method };// If the methods allow for request bodies, add the data to the request bodyif(method!=="GET"&&method!=="DELETE"){opts.body=formData;// Otherwise, if there is formData at all, add it to the URL params}elseif(formData!=null){// != null checks for both null and undefinedconstqueryParams=newURLSearchParams(formData).toString();url+="?"+queryParams;}// const res = await fetch(url, opts);constresponseText='<div data-target="1"><p>test text</p></div>';if(targetElement){consttemplate=document.createElement("template");template.innerHTML=responseText;processNode(template);// @ts-ignore - all the targets are going to be ElementstargetElement.replaceWith(template.content);}else{replacePage(responseText,res.url);}};}/** * Add Triptych functionality to this DOM node and all its children * * @param {Document | Element} node */functionprocessNode(node){// Find all the formsconstforms=node.querySelectorAll("form");for(constformofforms){constmethod=form.getAttribute("method");consttarget=form.getAttribute("target")||undefined;// Only process forms that a) have subtree targets or b) have new methodsif(target||ADDITIONAL_FORM_METHODS.includes(method)){consturl=form.getAttribute("action");constformData=newFormData(form);form.addEventListener("submit",ajax(url,method,formData,target));}}// Find the buttons with an action attributeconstbuttons=node.querySelectorAll("button[action]");for(constbuttonofbuttons){consturl=button.getAttribute("action");consttarget=button.getAttribute("target");constmethod=button.getAttribute("method")||"GET";letformData;if(button.getAttribute("name")){formData=newFormData();formData.append(button.getAttribute("name"),button.getAttribute("value"));}button.addEventListener("click",ajax(url,method,formData,target));}// Find the links with a target attributeconstlinks=node.querySelectorAll("a[target]");for(constlinkoflinks){consttarget=link.getAttribute("target");consturl=link.getAttribute("href");if(url&&!EXISTING_TARGET_KEYWORDS.includes(target)){link.addEventListener("click",ajax(url,"GET",undefined,target));}}}// Process all the nodes once when the DOM is readyif(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",()=>{processNode(document);});}else{processNode(document);}// Handle forward/back buttonswindow.addEventListener("popstate",(event)=>{if(event.state)replacePage(event.state.html,event.state.url,true);},{once: true});</script></head><body><divdata-target="1"></div><buttonaction="./something" target="[data-target='1']">Btn Target 1</button><buttonaction="./something">Btn No Target</button></body></html>
The action="./something" can be ignored, the response is always overridden by:
Hi. Thanks for this contribution and proposal. I'd like this see something like this built into browsers/HTML.
While reading the code and playing around with it, I noticed what could be a bug. There's a
replacePage
function, which should replace the full document. This function is supposedly used when there's no target in the element, as seen in theajax
function and in the README:However, the previous condition that checks that the
target
exists, seems to invalidate this, and logs an error then returnsnull
. Which makes it impossible to implement a behaviour to replace the full page.Here's an example self-contained HTML document to showcase the bug. I copy-pasted the source code as of commit
70803de
and replaced thefetch()
call with a sample text to make it easier to run.The
action="./something"
can be ignored, the response is always overridden by:When I click:
The behaviour is as expected. But when I click:
I would expect a full replacement of the
document
element, but that's not the case.The text was updated successfully, but these errors were encountered: