1

I need to build a small website for my ops team that will have a series of iframes pointing to a couple of Kibana graphics. The website will have, lets say, 4 links, each pointing to a a specific website page with X iframes in each, with different iframe URLs.

The thing is, I don't want to statically type in the URLs and width/height of all the iframes, since the URL and dimensions have a strong potential for changes in the future.

What would be a good approach on organizing and dynamically inputting those values on the iframes? I was thinking about having a constants file to get those values from. Should I do it via JavaScript or maybe a template engine like Jinja2 would do it?

2

Overview

I would recommend the 'javascript' approach. If you have never used javascript before, then I would highly recommend that you take a break from this project for like a year to learn javascript (I have found W3Schools to be a really helpful place to start, and MDN to be a really helpful reference).


Auto generating the links

Now, as for the huge amount of different links. If you don't want to have to type them all in, but they follow a common pattern, then you can use javascript to auto-generate the path names. See the the example below.

<!doctype html><html><head><title>Iframe Auto-Generation</title></head><body>
<script>/*<![CDATA[*/ (function(){ // so that we don't pollute the global scope
    'use-strict'; // I always 'use-strict' for best practices
    /***************************************************************/
    var generatePathURL = function( i ){
            return '/path/to/file_' + i + '.html'; // example usage
        }, // you can process the value of the iterator here
        i=100; // number of pages you want to generate
    /***************************************************************/
    var HTMLcont = '',
        // live node list (auto-updated)
        autoIFrames = document.getElementsByName('autogenframe'),
        // set this to false to load them all at once
        synchronouslyLoadIFrames = true,
        loadNextIframeOptimized,
        loadNextIframe = function(){
            autoIFrames[i] && (autoIFrames[i].style.display = '');
            if (!autoIFrames[++i]) { 
               return; // code in here will run after all IFrames loaded
            } // loading the iframe:
            autoIFrames[i].src = autoIFrames[i].getAttribute('thesrc');
            autoIFrames[i].removeAttribute('thesrc');
            if (synchronouslyLoadIFrames){
               autoIFrames[i].addEventListener('load', loadNextIframeOptimized);
               autoIFrames[i].addEventListener('error', loadNextIframeOptimized);
            } else loadNextIframe();
        };
    while (i--)
        HTMLcont += '<iframe name="autogenframe" frameBorder="0" thesrc="' + 
            generatePathURL( i ) + '" style="display:none"></iframe>'
    document.body.insertAdjacentHTML('afterbegin', HTMLcont);
    if (window.requestAnimationFrame){ // helps avoid expensive thrashing
     loadNextIframeOptimized=requestAnimationFrame.bind(window,loadNextIframe);
    } else loadNextIframeOptimized = loadNextIframe;
    window.addEventListener('load', loadNextIframeOptimized);
})(); //]]></script></body></html>

Notice how in the example above, the iframes are loaded synchronously (one at a time). This is to prevent overloading your server with requests every time a client gets on it. Here is a more complex example of what you could do to generate a path name to help get you started.

var animalTypes = ['dogs', 'cats', 'rats', 'snakes', 'lizards'],
    animalTypesSize = animalTypes.length,
    animalAgeGroups = ['baby', 'child', 'teenager', 'adolescent', 'elder'],
    generatePathURL = function( i ){
        return animalTypes[i%animalTypesSize] + '/' + 
                animalAgeGroups[Math.floor(i/animalTypesSize)] + '/';
    },
    i = animalTypesSize * animalAgeGroups.length;


Auto Sizing The Iframes

Iframes do not automaticly size themselves for security concerns (which is actually a pretty good thing). There was a proposal for the seamless attribute which would change the behavior of the iframe to, among other things, auto-resize. But, it failed to gain traction. So, the easist cross-browser way would be to use javascript. I have created two seperate scripts that if inserted on the right page (as shown below), then it will automaticly resize the pages.

Insert the following script into the outer parent page that contains the iframe.

<script>/*<![CDATA[*/(function(){'use-strict';
    // if the iframe is on a different domain or protocal, then change
    // iframepageorigin to the domain and protocal of the iframe, an array
    // of domains and protocals, or "*" to allow all (which I don't recommend).
    // You can also use regexps (for case-insensitivity) in place of strings
    var iframepageorigin = location.origin,
    observedEles = [ ];
    function run_main_script(){
        var observeFunc,
            firstContactFunc = function(e){
                var msgcnt = 'parentiframeid' + observedEles.indexOf(e.target),
                    cntWin = e.target.contentWindow;
                if (iframepageorigin instanceof Array){
                    var i = iframepageorigin.length;
                    while(i--) cntWin.postMessage(msgcnt,
                            iframepageorigin[i].source||iframepageorigin[i]);
                } else cntWin.postMessage(msgcnt,
                        iframepageorigin.source||iframepageorigin);
            },
            eleObserver = new MutationObserver(observeFunc = function(MOlst){
                var iCur = MOlst.length;
                morep: while (iCur--){
                    target = MOlst[iCur].target || MOlst[iCur];
                    if (target.nodeName !== 'IFRAME' ||
                        ~observedEles.indexOf( target )) continue morep;
                    observedEles.push( target );
                    target.addEventListener('load', firstContactFunc);
                }
            });
        /*window.*/addEventListener('message', function(e){
            var srcOrig = e.target.origin.toLowerCase(),
                msgcnt = e.data.slice('resize'.length).split('X');
            if (msgcnt.length !== 3) return;
            if (iframepageorigin instanceof Array){
                var i = iframepageorigin.length;
                while (i--) if (e.target.origin.match( iframepageorigin[i] ))
                            observedEles[+msgcnt[0]].width = msgcnt[1] + 'px',
                            observedEles[+msgcnt[0]].height = msgcnt[2] + 'px';
            } else if (e.target.origin.match(iframepageorigin[i]) ||
                        iframepageorigin ==='*' )
                observedEles[+msgcnt[0]].width = msgcnt[1] + 'px',
                observedEles[+msgcnt[0]].height = msgcnt[2] + 'px';
        });
        eleObserver.observe( document.body,
          { subtree: true, attributes: true, attributeFilter: ['src'] } );
        addEventListener('load', 
            observeFunc.bind(0, document.getElementsByName('iframe')) );
    };
    if (!window.MutationObserver){ // polyfill for IE9 & IE10
        var script=document.createElement('script');
        script.onload=run_main_script, script.async=true, script.src= 
 'https://www.dropbox.com/s/ft95wfv4ahow63u/mutationobserverpolyfill.js?dl=1';
        document.head.appendChild(script);
    } else run_main_script();
})(); //]]></script>

Insert the following script into the inner iframe page. Please note that this script will, aside from auto-resizing, set the display of the HTML element to table. This is required for it to function correctly.

<script>/*<![CDATA[*/ (function(){'use-strict';
    // if the iframe is on a different domain or protocal, then 
    // change parentorigin to the domain and protocal of the iframe, an array
    // of domains and protocals, or "*" to allow all (which I don't recommend).
    // You can also use regexps (for case-insensitivity) in place of strings
    var parentorigin = location.origin,
        pariframeID,
        HTML = document.documentElement,
        prevSize = [],
        whenresize = function(){ try {
            (window.requestAnimationFrame||setTimeout)( whenresize );
            if (!((prevSize[0]!==(prevSize[0]=HTML.scrollWidth)) +
                (prevSize[1]!==(prevSize[1]=HTML.scrollHeight))))return;
            var curMes = 'resize'+pariframeID+'X'+prevSize[0]+'X'+prevSize[1];
            if (parentorigin instanceof Array){
                var i=parentorigin.length;
                while (i--) parent.postMessage(curMes,
                                    parentorigin[i].source||parentorigin[i])
            } else parent.postMessage(curMes,
                            parentorigin.source||parentorigin);
        } catch (e) {} };
    /*window.*/addEventListener('message', function(e){ try{
        var theData = e.data.slice('parentiframeid'.length);
        if (!e.data.startsWith('parentiframeid')) return;
        if (parentorigin instanceof Array){
            var i = parentorigin.length;
            while (i--) if (e.target.origin.match(parentorigin[i]))
                            pariframeID = theData, whenresize();
        }else if(parentorigin==='*'||e.target.origin.match(parentorigin))
            pariframeID = theData, whenresize();
    } catch(e) {} });
    HTML.style.overflow = 'hidden', HTML.style.display = 'table';
})();//]]></script>


Demo

A demo exemplifying all of the above content can be found on my website, jackgiffin.com by clicking on this link. Don't forget to inspect the page with dev tools so you can get a better sense of its workings.


Server-side solution

If you are not opposed to using a server-side solution, then you could always use PHP to inline your iframes content into the page before the data is sent to the client. The biggest advatage to this is that it will be much easier for serarch engines to parse your site because not all search engines fare the best with executing javascript. The disadvatage is that it will tax the server a little bit more than otherwise. And you won't be able to auto-generate the path names without knowing how to program PHP, meaning you will have to type in all of the pages by hand. Basicly, replace all your iframes with this:

<?php
   @readfile('/path/to/file/')
?>

If you do use this approach, then you should get rid of all of the stuff outside the body in the pages that you add in. This is because using the fast readfile function in PHP will just copy-n-paste in the content of the files, resulting in potentially unwanted side effects such as a doctype in the middle of your page if you don't get rid of it in the linked file. So basicly, just do this to your iframe would-be pages if you are using this server-side solution:

Before:

<!doctype HTML>
<html>
    <head>
        <title>Tab Title</title>
    </head>
    <body>
        <h1>BIG Title</h1>
        <p>Some example text</p>
    </body>
</html>

After:

<h1>BIG Title</h1>
<p>Some example text</p>


Final Statements

Happy coding :)! Hope you are successful in your endeavors.

2
  • 1
    Ty so much for all the info :) – user3682983 Jul 10 '17 at 23:17
  • @user3682983 Your welcome. Very glad I could help. Also, just as a suggestion, next time try to make your question appear more concise. Some moderators when visiting this legitimate question might closed it under the guise of 'too broad' or some senseless equivalent. Best regards, 3.1415926535897932384626433833. – 3.1415926535897932384626433833 Jul 21 '17 at 0:23

Not the answer you're looking for? Browse other questions tagged or ask your own question.