1

This is related to my XYZ app to write comments.

I found a much neater way to submit the request using forms:

function post() {

se_url='https://api.stackexchange.com/2.3/posts/'+se_postId.value+'/comments/add';
se_form.action=se_url;
se_form.submit();
}
<form id="se_form" onsubmit="post();" method="post">
    <input type="text" id="se_access_token" name="access_token" value="">
    <label for="se_access_token">Access token</label>
    <br>
    <input type="text" id="se_postId" name="id" value="51812">
    <label for="se_postId">Post Id</label>
    <br>
    <input type="text" maxlength=100 id="se_body" name="body" value="Test comment sent from App xyz">
    <label for="se_body">Comment</label>    
    <input type="hidden" id="se_preview" name="preview" value="false">
    <br>
    <input type="text" id="se_key" name="key" value="3SgaKR3yq2M1tzN3dNu*Cw((">
    <label for="se_preview">App key</label>
    <br>
    <input type="text" id="se_site" name="site" value="meta">
    <label for="se_preview">Site</label>    
    <input type="hidden" id="se_filter" name="filter" value="default">
    <br>
    <input type="submit" value="post">    
</form>

where the three lines in the post() function replace this:

inps=se_form.getElementsByTagName('input');
se_postBody='';
for (i in inps)
if (inps[i].id) se_postBody+=inps[i].name+'='+inps[i].value+'&';
se_postBody=se_postBody.slice(0,-1);

xh = new XMLHttpRequest();
xh.open("POST", se_url);
xh.onload=function() {out.textContent=xh.responseText;}
xh.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xh.send(se_postBody);

Unfortunately, I get whizzed off to

https://api.stackexchange.com/2.3/posts/51812/comments/add

but then I have no way to access the response text.

Is there a way around this?

5
  • Tangential question: is there any reason why you do not want to use the Fetch API in late 2021 and opted for XMLHttpRequest instead? Nov 6, 2021 at 16:48
  • @OlegValter; I'm used to the simple syntax of XMLHttpRequest and the similar MSXML2.serverXMLHTTP in MS365, all my software uses it, and Fetch doesn't look at all easy, for example see developer.mozilla.org/en-US/docs/Web/API/Fetch_API/…
    – JMP
    Nov 6, 2021 at 17:04
  • As for your specific problem, it is the way HTML forms work by default. If you want it to stop from reloading, you need to add a submit listener and prevent the default event action. Unfortunately, that will bring you back to square one of using either XMLHttpRequest or fetch. P.s. Sorry, forgot to send the draft Nov 6, 2021 at 17:51
  • Fetch API actually is much easier to use and is a way to go forward with asynchronous requests in JS, but the XMLHttpRequest is still useful when maintaining legacy code or writing one that is compatible with old browsers, so I am mostly asking that part out of curiosity. Fetch equivalent would be something like: fetch(url, { method: "POST", body: <body here, can be an instance of FormData in your case> }) Nov 6, 2021 at 17:53
  • I am mentioning Fetch API as it at least might ease the pain of having to resort to a submit event listener (as it is slightly less verbose than XMLHttpRequest) either way to be able to both see the response and not reload the page. Unfortunately, there is little you can do if you want both being able to submit the form and see the response. Nov 6, 2021 at 17:57

1 Answer 1

2

The problem you are experiencing is a standardized default behavior of <form> elements. When a form is submitted, under normal circumstances, navigation happens as one of the last steps of processing the submission. The exact scenario that plays out depends on the values of method and action attribute values.

Step 23 of the DOM spec's form submission algorithm defines what happens after the form is validated, and attribute values are successfully parsed. Your scenario falls under the https scheme and POST method case "Submit as entity body".

After building the request body, this is what user agent is instructed to perform, explaining why you get whizzed away to the submission URL:

Plan to navigate to a new request whose URL is parsed action, method is method, header list is « (Content-Type, mimeType) », and body is body.

It does not mean you lose the response text as it should be accessible from devtools in the Network tab if you had it open during submission, but it does mean that unless your app can run in the context of the API subdomain api.stackexchange.com, you will not be able to process it.

There are several ways of dealing with it:

Running in the context of the API subdomain

If your app can run in the context of the page you are being navigated to (for example, if it is a userscript, you can modify the @match/@include headers to allow it to be run on those pages), you can process the response there but that would likely be a jarring UX.

Using the good old AJAX

All other solutions involve preventing default form action and submitting it manually with JavaScript similarly to how you did it before. Here are a few tips, though, on how to optimize what you had before (since that is what led you on the path of trying to submit the form directly in the first place):

  1. Avoid using inline event listeners like onsubmit - they are a legacy way of adding listeners and have some major drawbacks, use addEventListener instead.
  2. Call preventDefault() method to cancel the default action (in the case of a form - submitting and navigating to the target) in the submit event listener and then send the data with AJAX.
  3. Use the modern Fetch API - XMLHttpRequest is verbose, considered legacy, and cannot work with promises which significantly simplify handling of asynchronous requests.
  4. Use FormData constructor to build up the form data to send - there is no need to manually construct the payload these days.

With all the above addressed, you code should become nice, clean, and able to process the response from the API easily:

form.addEventListener("submit", async (event) => {
    event.preventDefault(); // stop form from submitting

    const body = new FormData(form); // construct payload

    const response = await fetch(
        `https://api.stackexchange.com/2.3/posts/${body.get("se_postId")}/comments/add`, 
        { method: "POST", body }
    );

    console.log(response); // process the API response somehow
});
5
  • From this post: stackoverflow.com/questions/37799258/…, I found: w3c.github.io/webappsec-csp/#directive-frame-ancestors ,with the line: "Resources can use this directive to avoid many UI Redressing [UISECURITY] attacks, by avoiding the risk of being embedded into potentially hostile contexts.". I wouldn't have thought a text response to an API call falls into this category, and it makes me think that the SE directive for this is placed arbitrarily high on your server, so that these pages are accidentally affected.
    – JMP
    Nov 7, 2021 at 2:50
  • and therefore could be removed for API calls. Does this make any sense? (I could then load them into an iframe and read the document from there.
    – JMP
    Nov 7, 2021 at 2:51
  • you mean you want to place the form in an <iframe>, listen for the load event, and read the response text from the parent frame? Seems like a possibility (but you are mentioning the CSP directive frame-ancestors which is set to self for the SE API, which is a problem), just not sure why would you want to do that when you have access to all the power of AJAX? Nov 9, 2021 at 0:34
  • 1
    The FORM equivalent to AJAX would be something like response=form.submit();, and so the code would be a lot neater. I think it is just API calls that issue responses that need the directive altering - for example, the documentation is in the API subdomain, and this could be hijacked without it.
    – JMP
    Nov 9, 2021 at 3:37
  • @JMP eh, yeah, that would be neat - however all the submit() does (as I think you know) is launch the same form submission algorithm always ending in navigation. I suppose you can try making a feature request post for allowing the response pages to be embedded on origins other than api.stackexchange.com (from our conversation I assume your app is not a userscript and is hosted somewhere on a domain you own) but... just don't expect anything to be done any time soon. As I am sure you noticed, the API isn't the highest priority for the company, which is a shame Nov 9, 2021 at 19:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .