javascripthtmljquerydomjquery-events

How to stop an empty iframe triggering a load event on injection?


I'm using an empty frameto handle pseudo-asynchronous form submission. For those that aren't familiar with the technique, the idea is to reference the frame's name attribute in the form's target attribute, such that the URI of the form's action resolves in the frame and the user experience isn't interrupted.

To feedback to the user I need to use script to listen for the load or error events on the form, and because this effectively makes the whole UX script-dependent, I'm only injecting the frame and adding the form's target reference via script.

The problem is that the frame will trigger a load event as soon as it's injected into the page: default behaviour.

I can mitigate this either with jQuery's one method and e.stopImmediatePropagation() (here), or I can inject the frame and then bind the events (here). But both of these options are really counter-intuitive — the code certainly needs commenting to avoid looking like a mistake.

TL;DR: Is there any way an iframe can be modified or qualified to stop it firing an erroneous load event on injection in the first place?

What I've tried

  1. No src attribute
  2. src set to about:blank (which the above is implicit for)
  3. src set to #
  4. src set to javascript:void 0
  5. src set to empty data-URI data:text/plain;base64,;
  6. srcdoc set to empty or near-empty string

Solution

  • This isn't possible, as you stated, the default src is set to about:blank and any other value will fire either the load event or the errorone.

    So the cleanest solution might be to start listening to these events only when the submit event of the form is fired:

    const frame = document.createElement("iframe");
    frame.name = "frame";
    document.body.append(frame);
    
    const form = document.querySelector("form");
    form.addEventListener("submit", (evt) => {
      const controller = new AbortController();
      const { signal } = controller;
      const handler = (evt) => {
        console.log("Request", evt.type === "error" ? "failure" : "success");
        controller.abort(); // kill us and the other listener
      };
      frame.addEventListener("load", handler, { signal });
      frame.addEventListener("error", handler, { signal });
    });
    <form
      target=frame
      method=post
      action="https://stacksnippets.net/js">
    <button>post it</button>
    <textarea name=html>Some content</textarea>
    </form>

    But it should probably be noted that for the error event to fire you'd need to have a response that the browser can't open in an iframe, and browsers can open a lot of stuff in an iframe, and moreover, most servers do send proper HTML pages when they face an error. So in most cases this won't let you know that the request failed, even a 404 error will fire the iframe's load event.

    So the best, if you're in a same-origin situation, might be to use AJAX to make the request. From there you can check the response's status code, and even make the server give you useful information e.g as a JSON response. You can then populate the srcdoc of your iframe with the response consumed as text. (External fiddle since StackSnippet's null-origined frames can't make same-origin requests).