skip to Main Content

For an existing webapp1, i am using a jQuery instance across iframes which causes me headaches.

jQuery is loaded (and aliased to $jq) in a Document which contains an iFrame(for a sidebar). In the iFrame document, I reuse the jQuery from the parent document by creating a new alias in the iFrame like $jq = window.parent.$jq.

The problem is now, that queries made in the iFrame refer to the Parent-Document, i.e. $jq("body").attr("id") yields the id of the body of the parent document not the id of the body of the iframe-document, so I would have to query with the context parameter like $jq("body", document).attr("id") to get the id of the document jQuery is currently being executed in.

My Question is: can I do anything, so I would not have to add the context parameter to every single query in order to scope the queries to the current document? For example, can I set the context globally for jQuery or such?

Parent Document:

<!DOCTYPE html>
<html>
<head>
    <title>Parent</title>
    <style>body{ margin: 0; padding: 0; font-family: sans-serif;} #page{ display: flex; } #main { padding: 10px; flex: 1 0 auto; } #sidebar{ width: 360px; background-color: #fafafa; border: 0; padding: 10px; height: calc(100vh - 20px); } </style>
    <script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
    <script>
        $jq = jQuery;
    </script>
</head>
<body id="Parent">
    <div id="page">
        <iframe id="sidebar" src="sidebar.html"></iframe>
    
        <div id="main">
            <h2>I am the parent</h2>
            <code class="result"></code>
        </main>
    </div>
    
    <script>
        $('.result').html('$jq("body").attr("id"): <b>' + $jq("body").attr("id") + '</b>');
    </script>
</body>
</html>

iFrame Document (sidebar.html):

<!DOCTYPE html>
<html>
<head>
    <title>Sidebar</title>
    <style>body{font-family: sans-serif;}</style>
</head>
<body id="Sidebar">

    <h2>I am the sidebar iFrame</h2>
    <code class="result"></code>
    
    <script>
        var $jq = window.parent.$jq;
        var theId = $jq("body").attr("id");
        var theIdithContext = $jq("body", document).attr("id");
        var el = document.querySelector('code');
        el.innerHTML +=('$jq("body").attr("id"): <b>' + theId + '</b><br><br>');
        el.innerHTML +=('$jq("body", document).attr("id"): <b>' + theIdithContext + '</b>');
        
    </script>
    
</body>
</html>

And here a testcase outlining my problem.


1 which happens to use jQuery and replacing that is out of scope for now

3

Answers


  1. Just create a function in sidebar.html that will work in the context you need.
    And you can also not assign $jq = jQuery;.

    In the code below, jQuery will have a Parent Document context and $ will have an iFrame Document context. (You can assign any names to the variables).

    const jQuery = window.parent.$;
    const $ = (selector) => jQuery(selector, document);
    Object.setPrototypeOf( $, jQuery );
    

    Example


    Parent Document:

    <!DOCTYPE html>
    <html>
    <head>
      <title>Parent</title>
      <style>body{ margin: 0; padding: 0; font-family: sans-serif;} #page{ display: flex; }</style>
      <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
    </head>
    <body id="Parent">
      <div id="page">
        <iframe id="sidebar" src="sidebar.html"></iframe>
        <div id="main">
          <h2>I am the parent</h2>
          <code class="result"></code>
        </div>
      </div>
      <script>$('.result').html(`bodyID: <b>${$('body').attr('id')}</b>`);</script>
    </body>
    </html>

    iFrame Document:

    <!DOCTYPE html>
    <html>
    <head>
      <title>Sidebar</title>
      <style>body{font-family: sans-serif;}</style>
    </head>
    <body id="Sidebar">
      <h2>I am the sidebar iFrame</h2>
      <code class="result"></code>
      <script>
        const jQuery = window.parent.$;
        const $ = (selector) => jQuery(selector, document);
        Object.setPrototypeOf( $, jQuery );
        const bodyID = $('body').attr('id');
        const parentBodyID = jQuery('body').attr('id');
        const el = document.querySelector('code');
        $('code').html(`bodyID: <b>${bodyID}</b><br>parentBodyID: <b>${parentBodyID}</b>`);
      </script>
    </body>
    </html>

    Login or Signup to reply.
  2. A short answer: it’s impossible, a longer answer: it’s not needed.

    I think the whole problem is overengineered, too much perfection & DRY. you can easily reuse the parent’s jQuery object to get your sidebar’s body and work with it:

    const $body = window.parent.$(document.body);
    

    But why you need to include your script in the iframe? Also seems unnecessary. You can easily manipulate your iframe from the parent:

    const $iframeBody = $($('iframe')[0].contentDocument.body);
    

    Also I consider using $(selector) everywhere as low quality jQuery spaghetti code since it’s lacking any container context.

    Back to the problem.

    First if you look at jQuery’s minified code you’ll find it’s a hard object which means the window & document become private scope variables defined at the script execution:

    /*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
    !function(e, t) {
        "use strict";
        "object" == typeof module && "object" == typeof module.exports ? module.exports = e.document ? t(e, !0) : function(e) {
            if (!e.document)
                throw new Error("jQuery requires a window with a document");
            return t(e)
        }
        : t(e)
    }("undefined" != typeof window ? window : this, function(e, t) {
        "use strict";
        var n = []
          , r = e.document
          , i = Object.getPrototypeOf
          , o = n.slice
          , a = n.concat
          , s = n.push
          , u = n.indexOf
          , l = {}
          , c = l.toString
          , f = l.hasOwnProperty
          , p = f.toString
          , d = p.call(Object)
          , h = {}
    

    So you can’t change something like prototype to change window & document.

    Second to create a jQuery object for your iframe you just need to execute this script again in your iframe. So the easiest why to put jQuery into your frame:

    <script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
    

    Modern browsers are smart enough not to download the script twice.
    So you have your jQuery in the parent page and in the iframe with one source code file. Basically the browser download the jQuery’s source once than creates individual jQuery objects for your parent and iframe pages.
    I think it’s really easy and doesn’t require any smarter solution.

    The gist could be seen here:
    https://codesandbox.io/s/silly-ardinghelli-v7774q?file=/src/frame.js

    In the iframe’s JS I just easily can use both the parent’s jQuery and the iframe’s one:

    $jp = window.parent.jQuery;
    $(() => {
      $("body").append("<div>test</div>");
      $jp("body").append("<div>test</div>");
    });
    

    The result:
    enter image description here

    Login or Signup to reply.
  3. You can define $jq as being a function that takes a selector as an attribute and calls window.parent.$jq(selector, document):

            var $jq = function(selector) {
                return window.parent.$jq(selector, document);
            };
    

    Full iframe code:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Sidebar</title>
        <style>body{font-family: sans-serif;}</style>
    </head>
    <body id="Sidebar">
    
        <h2>I am the sidebar iFrame</h2>
        <code class="result"></code>
        
        <script>
            var $jq = function(selector) {
                return window.parent.$jq(selector, document);
            };
            var theId = $jq("body").attr("id");
            var theIdithContext = $jq("body", document).attr("id");
            var el = document.querySelector('code');
            el.innerHTML +=('$jq("body").attr("id"): <b>' + theId + '</b><br><br>');
            el.innerHTML +=('$jq("body", document).attr("id"): <b>' + theIdithContext + '</b>');
            
        </script>
        
    </body>
    </html>
    

    Successful test:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search