reptile7's JavaScript blog
Wednesday, May 24, 2017
 
Document Son of Iframe
Blog Entry #374

Bringing back my <iframe src="some_file.html"> demos was in general a bit of a chore but not difficult. Some cases were tricky, however, in particular those that themselves make use of an external text resource, for example:
(1) the showModalDialog( ) demo of Blog Entry #184, which now calls on a document fragment appearing later in the entry source;
(2) the Ajax demo of Blog Entry #217, which now accesses an XML element planted in a preceding entry.

The Nikki's Diner Example discussed in Blog Entries #261 and #262 loads <select>-ed .htm files into a common space on the page. In the Calling Dr. Iframe section of Blog Entry #262 I talk up the iframe as the obvious choice of elements to replace the layer placeholder in Netscape's original code. Prior to restoring my lost dinerdemo.html demo for the example I mused, "If I'm going to spend all this time talking about iframes, it would be kind of dishonest on my part to trade in the demo's iframe for a div, wouldn't it? Is there some way I can hold onto the iframe container?"

An iframe is a type of frame, and a frame is a type of window. I knew that the frames[ ] collection of the window object accesses <iframe>s as well as <frame>s. I thought back to HTML Goodies' JavaScript Primers #12 and its code for building a document from scratch in a var OpenWindow = window.open("", "newwin", "height=300,width=300"); window - I ought to be able to do that with an iframe, right?

I picked up the dinerdemo.html <iframe> and
(a) set its src attribute to an empty string and
(b) changed its id="menu" attribute to a name="menu" attribute because window.frames["iframeName"] returns an iframe as an [object Window] whereas window.frames["iframeID"] returns an iframe as an [object HTMLIFrameElement] (vide infra).

<iframe name="menu" width="410" height="425" style="position:relative;left:50px;" src="" frameborder="0">It seems that your browser does not support iframes. Please call Nikki's Diner at 123-4567 for today's specials.</iframe>

I then crafted a new showSpecials( ) function that writes out an iframe document template to which pre-arrayed day-specific data is added via an n index.

var bgcolorArray = ["#bbee99", "#6699ff", "#ee99ee", "#22bbcc", "#ffbb00", "#66ffff", "#ee7777"];
var dayArray2 = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
var entreeArray = ["Curried Tofu with BeanSprouts and Raisins", "Tofu and Mushroom Pie", "Tofu, Artichoke, and Asparagus Surprise", "Tofu and Leek Tart", "Tofu and Mandarin Torte", "Tofu Burgers with Endive Salad", "Tofu and Parsnip Casserole"];
...

function showSpecials(n) {
    window.frames["menu"].document.open( );
    window.frames["menu"].document.write("<html><head><title>Specials<\/title>");
    window.frames["menu"].document.write("<style type='text/css'>body { background-color:" + bgcolorArray[n] + "; } h1, h2 { text-align:center; }<\/style>");
    window.frames["menu"].document.write("<\/head><body><hr />");
    window.frames["menu"].document.write("<h1>" + dayArray2[n] + "<\/h1><hr />");
    window.frames["menu"].document.write("<h2>Entrees<\/h2>");
    window.frames["menu"].document.write("<p>" + entreeArray[n] + "<\/p>");
    ...
    window.frames["menu"].document.close( ); }
showSpecials(0);


XHTML 1.0 deprecates the name attribute of the iframe element. The contentWindow attribute of the HTMLIFrameElement interface returns the [object Window] associated with an iframe so we can revert to the id="menu" identifier if we recast the window.frames["menu"] references as window.frames["menu"].contentWindow references.

The HTML Goodies JavaScript Primers #12 Script does not include document.open( ) and document.close( ) operations.
• An explicit document.open( ) call can be used to clear an already-open document stream with some browsers but as far as I know is unnecessary under other circumstances. Joe isn't clearing anything and I'm not either, and therefore we don't need to open( ) our respective documents, but doing so strikes me as 'good form', so I put a window.frames["menu"].document.open( ); command in there.
• An open document stream is a work in progress in that you can render what you've got so far but the loading process isn't finished until the document is close( )d. It's not really necessary for Joe and me to close( ) our respective documents, but in my case the main page's loading progress indicator spins indefinitely when using Firefox or Google Chrome if I don't do so, so I do so.
(For more on this topic, see Stack Overflow's "Are document.open and document.close necessary?" page.)

How does my plan work in practice? Check it yourself.

An alternative, more efficient approach is possible. With an eye on getting rid of those document.write( ) commands, I discovered that, its readonly status notwithstanding, the HTMLIFrameElement contentDocument attribute can be used to append freshly created elements to the head and body of an iframe document.

window.frames["menu"].onload = function ( ) { /* This listener is necessary when using Firefox. */
    iframeDoc = window.frames["menu"].contentDocument;
    sRules = document.createElement("style");
    sRules.type = "text/css";
    sRules.textContent = "h1, h2 { text-align:center; }";
    iframeDoc.getElementsByTagName("head")[0].appendChild(sRules);
    iframeDoc.body.appendChild(document.createElement("hr"));
    heading1 = document.createElement("h1");
    iframeDoc.body.appendChild(heading1);
    ... }


With a new document template in place, we can shrink the showSpecials( ) function to only those operations that set values that change from day to day.

function showSpecials(n) {
    iframeDoc.body.style.backgroundColor = bgcolorArray[n];
    heading1.textContent = dayArray2[n];
    ... }


Other related cases

(1) As part of a window.opener demo in Blog Entry #26, a remote control window loads http://www.htmlgoodies.com/ via an object element into a div on the main page, as though the div has a src capability.

zork.document.write("<input type='button' value='Click here to load HTML Goodies into the opener pane' onclick='window.opener.document.getElementById(\"openerDiv\").innerHTML = \"<object width=100% height=193 data=http://www.htmlgoodies.com/>Oops, data loading did not occur.<\/object>\";'>");

(2) The fram1.html-fram5.html frameset/frames demo in the Referencing a Window section of Blog Entry #18 would certainly seem to require the use of external files but I figured out a way to code the whole shebang in the main document. An outer iframe serves as the fram1.html top window:

<iframe name="fram1.html" width="100%" height="480" src="" frameborder="1"></iframe>

The fram2.html and fram3.html frame windows are created via:

window.frames["fram1.html"].document.write("<frameset cols='70%,30%'>");
window.frames["fram1.html"].document.write("<frame name='fram2.html' src=''>");
window.frames["fram1.html"].document.write("<frame name='fram3.html' src=''>");
window.frames["fram1.html"].document.write("<\/frameset>");


The fram2.html and fram3.html windows are then respectively accessed with:

window.frames["fram1.html"].window.frames["fram2.html"] // and
window.frames["fram1.html"].window.frames["fram3.html"]
/* window.frames["fram1.html"] is the parent of window.frames["fram2.html"] and window.frames["fram3.html"]. */


The fram4.html and fram5.html frame windows are created

window.frames["fram1.html"].window.frames["fram2.html"].document.write("<frameset rows='30%,70%'>");
window.frames["fram1.html"].window.frames["fram2.html"].document.write("<frame name='fram4.html' src=''>");
window.frames["fram1.html"].window.frames["fram2.html"].document.write("<frame name='fram5.html' src=''>");
window.frames["fram1.html"].window.frames["fram2.html"].document.write("<\/frameset>");


and accessed

window.frames["fram1.html"].window.frames["fram2.html"].window.frames["fram4.html"] // and
window.frames["fram1.html"].window.frames["fram2.html"].window.frames["fram5.html"]
/* window.frames["fram2.html"] is the parent of window.frames["fram4.html"] and window.frames["fram5.html"]. */


in an analogous manner.
I'll briefly discuss the restoration of code samples and dead links in my posts in the next entry.

Thursday, May 11, 2017
 
The Art of Blog Pothole Repair, Part 2
Blog Entry #373

Bringing back my images via Blogger's file-upload mechanism and a smattering of basic HTML was low-hanging fruit. Restoring my demos was a whole 'nother kettle of fish.

About a year and a half into my blogging career I hit on the idea that I could use iframes to display externally hosted demos. I would form a demo as a complete .html document: the renderable stuff would go in the <body>; the JavaScript would usually go in the <head>, on occasion the <body>; if present, a style sheet would go in the <head>. Next, I uploaded the document to my home.earthlink.net/~reptile7jr/ EarthLink server space with the Fetch application. Lastly, I loaded the document into an iframe in a Blogger post by assigning the document's URL to the value of the iframe's src attribute.

<iframe width="50%" height="300" src="http://home.earthlink.net/~reptile7jr/animation4.html" frameborder="1" scrolling="no">Your browser does not support iframes - the demo and the script it imports can be accessed via the links below.</iframe>

As shown, I further kitted out the iframe with width and height settings* and sometimes frameborder and scrolling settings as appropriate. My first iframe-framed demo appeared in Blog Entry #59.
(*On my computer, the content+padding area of an iframe without width and height attributes measures 300px by 150px.)

These demos were replaced by a Forbidden message when I lost them in the wake of parting ways with EarthLink, e.g.:

The 'Forbidden' message for the lost demo of Blog Entry #210

At least my lost images left behind some alt text:

HTML Goodies banner

As you can imagine, folks, I got sick and tired of looking at those Forbiddens, so I decided to do something about it. Blogger will host for its bloggers image and video files but shamefully not .html documents (it won't host .js scripts or .css style sheets either, in case you were wondering). Fortunately, recasting my demos as endogenous blocks of code - the obvious remedy - proved straightforward most of the time.

The obvious choice of 'canvas' for an intrapost demo is a div element, which
(a) is not only a suitable frame replacement for an iframe but also
(b) serves ably as a proxy for the <body> of the document we would have loaded into the iframe.

We can give a div a custom width, height, and border; we can give it scrollbars if necessary via a suitable overflow setting. Per its starting i, an iframe has an inline rendering; a div normally has a block-level rendering, but we can give it an inline rendering and still hold onto its other block-level capabilities by giving it an inline-block display.

In HTML5 the body and div elements both have a flow content content model, i.e., their immediate children can be just about anything - <p>s and <h#>s and all other textual elements, <img>s and other <object>s, <form>s and their controls, <tables>s, <ul>s and <ol>s - only a small handful of non-renderable elements are off-limits. (The situation is slightly more restrictive in HTML 4, I'll spare you the details.)

The body and div elements can parent the script element, so we can put the <script> for an intrapost demo inside the div container, but we can put it outside the div too à la an external script: its location doesn't matter as long as it doesn't hold any top-level code that references one or more later elements in the source - if it does hold such code, we'll need to place it after the referred-to element(s), of course.

The body and div elements can't contain the style element, however. The easiest way to re-effect the <style> rules of my demos was to recast them as inline styles, so that's what I did - I wasn't happy about doing this, but a blogger's gotta do what a blogger's gotta do. N.B. Styles for pseudo-elements and pseudo-classes can't be set via the inline style attribute mechanism although they can be set JavaScriptically via the insertRule( ) method of the Style DOM's CSSStyleSheet interface, e.g.:

var last_styleSheet = document.styleSheets[document.styleSheets.length - 1];
last_styleSheet.insertRule(":active { color: lime; }", last_styleSheet.cssRules.length);


If a demo features absolutely positioned elements, then the div container must be position:relative;-ed in order to make it the containing block for those elements.

The Options panel and its Line breaks settingsPasting a retooled demo into an existing post sometimes caused the after-the-demo end-of-line white space to collapse: as far as I can tell, this is an artifact of the
Press "Enter" for line breaks
Line breaks Options setting. I had to reach into my bag of spacing tricks to regenerate the original formatting, specifically:
(1) I placed a <div>&nbsp;</div> between the demo div and the following text so as to recreate the empty line box that originally separated them;
(2) I wrapped the subsequent inline content in one or more <span style="white-space:pre-line;">s to bring back the remaining newlines.
Serves me right for letting Blogger space my posts for me, eh? Lesson learned: From here on out I'll be coding my own <br />s per the
Use <br> tag
mode of operation.

Moreover, Blogger has an annoying practice of inserting newlines immediately after the start tags and end tags of block-level elements, which can kill a demo script if that script writes out block-level elements with document.write( ) commands. (SyntaxError! Unterminated string literal!) Converting such commands to corresponding createElement( )/appendChild( ) DOM operations will avoid this problem.

One more point: As Firefox, Google Chrome, Opera, and Safari all apply an 11px margin-top and an 8px margin-left to the <body>, I often apply an 8px padding to the demo div.
I've actually got a bit more to say about demos and iframes - let me save it for the next entry.


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)