Created: 01-01-2022
Last update: 15-01-2022

AniMisc



How I used JavaScript to manage links and iframes



This how-I assumes a familiarity with basic HTML. Very, very basic HTML.




Why my HTML code is old

My first introduction to the internet was on a 286 - an incredibly ancient computer by today's standards, although in the 1980s it was the hot new thing - receiving text-only email and exchanging messages on servers via Telnet, using a modem dialup connection made possible by pioneering internet provider XS4ALL. My second, in the mid-90s, was on a 486 running the latest 16-bit Windows and a very early, incredibly slow Mosaic browser on the brand new World Wide Web. I continued using dialup but bought faster modems, first 28.8Kbps and then 56Kbps, the fastest possible speed before dialup was dropped in favour of a network connection, and almost a fraction as fast as my current ADSL connection. Let's just say downloading a CD-sized Linux distro image took all night, if the connection didn't give out halfway through the download. It was around this time, when XS4ALL started giving its customers a modest slice of webspace for a homepage, that I began to code a very small website, intended to share images and video with online friends, as emailing such files was very much not possible then.

This is why my website, whose page filenames end on ".htm" because I started coding them under MS-DOS with its filename limits, is still programmed in the most archaic, basic, deprecated HTML code possible. Heck, it took some courage to use frames (for the BK Quiz) in one page, and simulate frames using a table (in order to move buttons to a sidebar) in others. Believe it or not, the first Netscape Navigator browser didn't even support background colour, let alone tables and frames. And while the second and third did, they still choked on JavaScript, the new emphatically-not-Java script language to make buttons change appearance when the mouse cursor hovered over them, open annoying popups, take over the browser window and cause errors that, if they made an error dialog appear under the browser window, essentially paralyzed the browser, which had to be killed and restarted. Oh boy, did I hate JavaScript.

Since then, I progressed from Netscape 4 to 4.76 to the totally rewritten Netscape 6, and from there to Netscape 7 and Mozilla Firefox 2, then 3, then a flurry of version numbers from 52 up to 95, with some Safari, Opera and a Chrome clone on the side. HTML4 happened, and CSS, and Adobe Flash, and alternatives to Flash, and HTML5, and it all just passed me by. I didn't use style sheets, everything was hard-coded, nothing was dynamic. I also didn't use "web builders" but wrote everything - and still do - in a text editor, with a HTML reference book within easy reach.

This works for me, except for one niggle. Links.

Back to index

The issue with links

"What am I going to do about these links???"

That was my thought at seeing the chaotic mess the Links page had become, and the clunky mechanism for linking to external sites.

Originally, this website was an anime series fansite. And like most other fansites at that time, it had a separate links page to connect to other fansites on the same subject. Sites come up, then go down as their maintainers die or lose interest, and "community" websites move to private domains; long story short, links need to be checked and updated every so often. Not a problem, when those links are all on the same page.

But the strength of the HTML format is the ability to hyperlink - use a clickable link to jump to another page - from anywhere in a text. Meaning, not just from a links page. So a website will be riddled with links on all its pages, and if those links are external - leading to another site - they too must be checked from time to time. But, being all over the place, they are easy to overlook. Not a problem: I just make these external links internal, by pointing the link in the text to the Links page, where the actual external link is, so the reader has to click twice to get to the page I'm referring to. Clunky? Yes, but on the Links page, the link comes with a description, so the reader can choose whether to visit the site, rather than being dumped straight into an Angelfire or GeoCities page full of ads and hinky scripts. (Yes, "GeoShitty" still existed in those days.) And I still have all external links in one place for easy maintenance.

So far, so good.

But in time it became a fansite of other things too, like KiSS (kisekae ningyou, "paper dolls for your computer"). So, on the Links page, I added a section of links to other KiSS sites, and on the KiSS page, the button "Links" jumped straight to the KiSS section, using an anchor. The anchor is what HTML uses to jump to a spot within a page. It's how I jumped from an internal link someplace in my website, to the corresponding external link on the Links page.

But the KiSS Page had a sub-page called "KiSS documents and tutorials on the web", which was basically a links page too, because the description for the link was on the KiSS docs&tuts page itself, so there was no excuse to route the link via the Links page, therefore, I put in the external link directly. Now I had two links pages to check! It was still manageable, though.

But then, the website morphed from "fandom" to "general interest", and I wrote some simple how-to's, of the kind found on blogs and forums. I called them "how-I's", because what works for me, might not work for everyone. To acknowledge the internet sources I used for these how-I's, and point the reader to more background information, I included external links. But where to put those links? Initially on the Links page, but the various links used to solve a Linux update problem, for instance, had nowhere near the cohesion of a set of links all dedicated to the same fandom or hobby. In short, the how-I link sections were a mess.

Due to increased screen resolutions and bigger windows, another problem came up regarding anchors, those "bookmarks" that links can jump to within a page. If I click on a link with an anchor, the page it links to will load from the top and then scroll up to the line where the anchor is. On the Links page, that puts the external link referred to at the top of the browser window. But a webpage can scroll up only as far as the height of the unshown space at its bottom end. If the page fits completely in the window, without scrollbars, then it will not scroll up, and since the anchor is invisible, there's no way of knowing which external link the reader is supposed to click.

So far, not so good any more.

To forestall any mention of "asp" or dynamically created webpages: I started writing webpages for Netscape 1.0 under Windows 3.11, stuck with Netscape 2 for a long time because Netscape 3 froze so often due to scripts, jumped to Netscape 4, followed all its non-W3C-compliant versions until the great rewrite of Netscape 6, and ended up a FireFox user; due to the problems I've had in the past with viewing anything other than plain HTML, I kept my code simple and script-free, avoiding even CSS.

Getting back to the how-I links: they're more like footnotes. I saw three options to deal with these links:

  1. I could send the how-I links straight to the external link plus description on the Links page, forcing the reader to interrupt the how-I in mid-read by jumping to a different page. Clunky.

  2. I could point the how-I links in the text to actual footnotes, plus descriptions, at the bottom of the how-I page, and link those footnotes to the external links on the Links page, so the reader would have to click not two but three times to see what the how-I link referred to. Clunkity clunk.

  3. I could point the how-I links in the text to a block of external links, in the form of footnotes plus descriptions, at the bottom of the how-I page; aesthetically the best solution, but now every how-I page would become a links page that needed periodic checks. Or I'd have to duplicate the how-I's footnotes to the Links page, check them there, then copy them back. Clunky for me.

And in all three cases, I'd still have the problem of maybe not being able to scroll to a given anchor.

Back to index

The rough outline of a solution

A different solution presented itself, for which I would have to use both scripts and CSS, but which would be backward compatible to some degree. Any time I had a collection of links, whether "other sites of this fandom" or "all footnotes for a how-I", I would put them on their own HTML page. I would incorporate this block-of-links HTML page, using a script or an iframe (inline frame) in the page relevant to it, and also in the Links page, so the links would appear twice, but still be in only one document. The Links page itself would be an empty framework for a collection of incorporated links blocks.

To sum up the different ways in which the link block would be used:

i. The link block of, for instance, KiSS links is in a HTML page with a header consisting of the title ("KiSS Links"), a button linking back to the main KiSS page, and a "last updated" line at the top.

ii. The links of this page, without header, are incorporated into the Links page using code tricks. Above the block-of-links incorporated in the Links page is a title (again, "KiSS Links") that leads to this page, in case the browser doesn't support these code tricks. So a websurfer sees either the block of links, or one title/link that opens this block of links as a separate page.

iii. The main KiSS page has a simple link to "KiSS links" which also loads this KiSS links page as a separate page, as well as a sidebar button to load the old Links page, without anchor (ie. if the button is clicked, the Links page will not jump to the KiSS links section).

iv. One of the KiSS Page's subpages is the KiSS documents and tutorials page, another block-of-links page. It is loaded from the main KiSS Page as a normal page, but its links are incorporated in the Links page as in (ii).

v. Any how-I page: unlike (iv), this page has a separate links page for its "footnotes", that is incorporated in both the how-I page and the Links page. The "footnotes" page is not meant to be opened as a new page, but is still linked to through the "Links" titles on both the how-I page and the Links page, again, in case the code tricks don't work. As the reader is supposed to be able to jump from the text to the footnotes and back, each link will need an anchor.

v. The old Links page becomes basically an empty skeleton with a list of titles/hyperlinks. It needs no anchors, as no page refers to it any more except through the "Links" sidebar button. I'll give it anchors and a little index at the top, though, so I can quickly jump to a particular link block instead of having to scroll all the way down.

The anchor problem would be partially solved by numbering the external links in a link block, and adding the number in brackets to the corresponding internal link, ie. text referring to "Abandonia", the 13th external link in the links block, would be written as "the Abandonia website(3)".

That was the rough outline: now, how to implement it.

Back to index

A links block in an iframe

Having never coded dynamic HTML before, I had to start from scratch. A static, non-script solution would be frames (deprecated and also extra work, because each text/links combination would need a "skeleton" frame to be loaded into), or iframes (inline frames; theoretically perfect, but a later addition and not supported very well). A dynamic, scripted solution (meaning, I would change the HTML content loaded into memory) would be to copy the links block to the page that needs it using JavaScript, which comes in various versions which, I know from experience, are also not supported very well in all browsers, so I would need to test if it worked, and have both an iframe and a link as fallback solutions.

(Note: while JavaScript would add the links to the page that's stored in memory, it would not of course add them to the physical HTML file. Save the loaded page to disk in the browser, and the saved file will not contain those links. "Only change what's in memory" is how dynamic HTML works. All the more reason to also have the page retain a hyperlink to the actual links document.)

A browser that supports the IFRAME tag will also support the DIV tag, a jack-of-all-trades tag that can be used to "select" part of a webpage, in order to do things to it, like changing its background colour or hiding it from sight. So here's a code example of the page that will contain the links:

<HTML>
<HEAD><TITLE>Main page</TITLE></HEAD>

<BODY>
<H3>Main page</H3>
<P>Bla bla bla text</P>

<!-- To go to the links block page itself: -->
<P><A HREF="lifothpa.htm">Links for this page</A></P>

<!-- This is the important bit! -->
<DIV ID=dumplinkshere>

<IFRAME ID=if_content SRC="lifothpa.htm#content" WIDTH=100% 
        TITLE="Links for this page">
<!-- Text below shown if IFRAME not supported: -->
<P>Links can't be loaded here because iframes are not supported.
Click on the title above to see them.</P>
</IFRAME>

</DIV>

<P>Maybe a footer or something</P>

</BODY>
</HTML>

(Yes, I know HTML tags aren't supposed to be capitalized any more. I'm stuck in the 20th century. Bite me.)

To unpack this sample: the document identifies itself as HTML, consisting of a HEAD and BODY. The TITLE in the head, which is what's shown in the browser's title bar, should be the same as the H3 header at the top of the BODY section, which will be shown as a title in the browser window. Text is enclosed in P tags, standing for "paragraph"; each opening tag has a closing tag with a backslash. So far, very basic.

Opening tags can be followed by attributes. The opening DIV tag has an attribute: "ID", followed by a string that uniquely identifies this tag. No other tag on this page, or in any HTML code dynamically loaded into this page, should use that same string with an ID tag, or even, as I found out, with a NAME tag. The NAME tag is not the same as the ID tag, but they overlap enough that they should not have the same string. Not even if they are used with the same tag. Trust me.

The identifying string is more useful if it means something, which is why this one is "dumplinkshere". (The iframe doesn't need this div; the div is just there to dynamically overwrite the iframe if the browser supports that, as explained in section 6.)

Inside the DIV tags are IFRAME tags.

The iframe ID attribute holds its unique identifier, "if_content". Its SRC attribute is the links block page I want to load into the iframe, starting at the content, so the link blocks title is above the frame's upper edge and out of sight; this only works if the iframe is smaller than the links block page, in which case a scrollbar automatically appears. The hash in "lifothpa.htm#content" points to the anchor "content" defined in "lifothpa.htm". (I'm sticking to DOS-compatible short filenames, or I would write out the name of the links block page as "LinksForThisPage.html".) The iframe WIDTH is 100%, so it will stretch across the main page instead of being an unsightly little box. It has the same TITLE as "lifothpa.htm" will have; although the title of an iframe is invisible in most browsers, it may be used in text-only browsers and special browsers for the visually impaired to indicate what the iframe contains. (I've read this online and have no experience with such browsers myself, but include the title as a courtesy.) If I wanted to load new pages into the iframe using hyperlinks, I would also give it a NAME attribute to refer to, although in modern browsers, ID should also work for that.

The iframe will have a 1-pixel border. There are two ways of getting rid of this border, one of which is deprecated; I'll get back to that at the end of this section.

By default, you can't set an iframe to the right height to show the whole page contained in it, without scrolling; that has to be done via JavaScript. Setting the iframe's HEIGHT attribute to 100% means making it as high as its container, which is either the whole page or, as in this case, the div enclosing the iframe; results will vary, but will always be unsatisfactory, and all will be for nought when you resize the browser window. For now, give the height an absolute value, something like "300", or settle for the default iframe height of 150 pixels. (In the oldest browsers, the choice was between an absolute number and a percentage. CSS-capable browsers would like an absolute number in pixels to be followed by "px", because there are other units, but just the number is fine.) I won't use JavaScript to adjust the iframe, because if the browser understands JavaScript, the iframe will be overwritten.

Between the opening and closing IFRAME is a message that refers the reader to the title above the iframe, which is also a direct hyperlink to the links block page. If the browser supports iframes, anything between the iframe tags is ignored, so this message doesn't appear. If not, the iframe tags themselves are ignored, so anything between them is processed, and the message is displayed. Sometimes, the iframe is displayed (so you don't see the message) but empty (because its SRC page couldn't be loaded; browsers may refuse to load a page into an iframe for security reasons). That's why the direct link to the links page is above the iframe, not enclosed in its tags; that direct link should be accessible no matter what. For browsers that support it, you might put a message in the iframe itself using SRCDOC, which should always be displayed as it doesn't involve loading a different page, but SRCDOC defeats the iframe's purpose by overriding SRC, so the links block is not loaded. You can put a hyperlink in the SRCDOC message:

<IFRAME ID=if_content SRC="lifothpa.htm#content" WIDTH=100% 
        TITLE="Links for this page">
        SRCDOC="Click <A HREF='lifothpa.htm#content'>HERE</A>
                if you want to see links in the iframe!">

<P>Links can't be loaded here because etc.</P>
</IFRAME>

if you want to annoy readers.

The only modifications required in "lifothpa.htm", the links block page, are adding the anchor "content" below the header at the top of the content; adding the tag "<BASE TARGET=_top>" inside the HEAD tags, to make sure that if the reader clicks on a link, the new page will open in the browser window, not the iframe; and numbering the links. Also, I'm enclosing the anchor "content" and all links below it in a div called "linkblock" for the sake of the dynamic HTML trick explained in section 6.

<HTML>
<HEAD><TITLE>Links for this page</TITLE>
<!-- This tag sets the default for loading pages you link to -->
<BASE TARGET=_top>
</HEAD>

<BODY>
<H3>Links for this page</H3>
<P>Last updated: whenever</P>

<DIV ID=linkblock> <!-- remember this div! -->

<!-- ANCHOR!! -->
<A NAME=content></A>

<P>Little intro</P>

<!-- Automatically sorted links list: -->
<OL> <!-- This means Ordered List, numbers from 1 by default. -->
<LI><A HREF="external link">Link 1</A>,
bla bla description</LI>
<LI><A HREF="external link">Link 2</A>,
bla bla description</LI>
</OL>

<P>Little intro for subsection</P>

<!-- Links list using START to continue from last number: -->
<OL START=3>
<LI><A HREF="external link">Link 3</A>,
bla bla description</LI>
<LI><A HREF="external link">Link 4</A>,
bla bla description</LI>
</OL>

<DIV>

</BODY>
</HTML>

More unpacking: the OL tags enclose an "ordered" list, ie. the "list items" (enclosed in LI tags) are automatically numbered. The OL attribute START, which restarts the numbering from whatever number follows it (in the example, "3"), is now deprecated, which means older, pre-CSS browsers will recognize it, but the very oldest browsers may not, so for absolute backwards compatibility, avoid START; if absolutely necessary, make an Unsorted List (tag: UL) and number the list by hand.

The TARGET attribute specifies how to load a webpage as a result of clicking on a link: in the same frame as the webpage or iframe that contains the clicked-on link ("_self", the default); replacing the page containing the frame that contains the link ("_parent"); in the browser window ("_top"); or in a new window or tab ("_blank"). All links in the links block iframe should load "on top", either by adding "TARGET=_top" to every "A HREF" tag, or by inserting a BASE tag between the HEAD tags as explained above.

The A in the tag stands for "anchor", but it's called a link when its attribute is HREF and an anchor when its attribute is NAME. Instead of "A NAME", I could use "A ID" - in fact I should, because "A NAME" is deprecated - but the attribute NAME is older and more well-established, meaning, even the oldest browser should handle it.

The ideal solution would be using both NAME and ID and giving them the same string, but due to programmer rivalry during the browser wars, a NAME and ID attribute that have the same string, even if they belong to the same tag, will conflict with each other and cause problems. Using NAME and ID with two different strings would be pointless, as a hyperlink with an anchor will accept only one string, meaning, if I put an anchor in the page as "A NAME=one ID=uno", I can't refer to it as "A HREF=page.htm#one#uno". So I'm forced to choose the deprecated but more backwards-compatible attribute NAME.

I can use both the ID and NAME attribute with the same A tag (so long as they have different values), but not NAME and HREF. A piece of text that is both anchor and hyperlink, becomes:

<LI><A NAME="lnk1"><A HREF="link">Link 1</A></A>,
bla bla description</LI>

It's ugly. To avoid using A twice like this, I could also add the NAME attribute to an element nearby, in this case the LI tag:

<LI NAME="lnk1"><A HREF="link">Link 1</A></A>,
bla bla description</LI>
But then I run into backwards compatibility again, because while attribute NAME could be used with tag A from the start, tag LI did not initially support NAME.

To end this section: the border around the iframe can be made to disappear with the deprecated attribute FRAMEBORDER, or using CSS code with the attribute STYLE, or, to make absolutely sure, both. Two deprecated attributes of an iframe are FRAMEBORDER, whose default value is "1", and SCROLLING (show a scrollbar if the content is bigger than the frame) whose default value is "yes". That the value is default, means the iframe will show the feature unless told not to. So, to the opening IFRAME tag, add FRAMEBORDER with its only other value: 0, and/or STYLE with "border:none;".

<IFRAME ID=if_content SRC="lifothpa.htm#content" WIDTH=100% 
        TITLE="Links for this page"
        FRAMEBORDER=0 STYLE="border:none;">

An iframe attribute called SEAMLESS, that would automatically resize an iframe to display all of its content without borders and scrollbars, was introduced, supported to some extent by some browsers, then dropped again. The only way to make iframe content act like an integral part of its parent page is through dynamic HTML using JavaScript, as explained in section 6.

Back to index

Footnotes in an iframe

In the previous section, I showed how to display a links block page inside an iframe in another page. This links block page is not part of the "parent" page - the page that has the iframe in it - and I can't make it scroll up to just an anchor. What do I mean by this? In the previous section, I mentioned that I would explain something in section 6, linking within this same page to text under the anchor "sec6" by using "<A HREF=#sec6>" (note the hash). Clicking on the link will make the page jump to section 6 (if it can scroll up that far). By omitting the filename before the anchor, I ensure that the link works even if I rename the file to something like "nevermind.htm".

Obviously, this only works for links to anchors within the same page, not for links to an anchor of a link on a page within an iframe.

It would work if the footnote links were in the parent page itself, instead of in an iframe: I link to footnote (rather than section) 6; click on the link, the page jumps to footnote 6; click on the browser's Back button, the page goes back to where it was before. If I link to footnote 6 in the page inside the iframe, using that page's anchor, the parent page, not finding anchor "footnote6" within itself, will not even jump to the iframe, let alone the anchor in the iframe's content.

To load a new page, then jump to an anchor within it, the link code is:

<A HREF=newpage.html#anchorname>
To load that same page in an iframe, positioned at its anchor (if it can scroll up that far), give the iframe a NAME attribute, and use the above with TARGET:
<A HREF=newpage.html#anchorname TARGET=iframename>
Since browsers are supposed to ignore whatever code they can't understand, the second example should scroll the iframe page to its footnote in the iframe if iframes are supported, and if they are not, open the iframe page as a new page instead, then jump to its footnote.

Even assuming this works, though, there are two problems, one a niggle, one a serious obstacle.

The niggle is that each jump to an anchor in an iframe counts as a jump to a new page, even though the browser still displays the same iframe page in the same parent page. So if I jumped to footnote 6 in the iframe, then clicked the browser's Back button to return to a previous page, I would just see the same page; to get to that previous page, I would have to click the Back button twice.

The obstacle is that the iframe's page would reposition itself WITHIN the iframe, but the parent page would not reposition itself TO the iframe, as I can't make the same "A HREF" do the double duty of taking me to the iframe, then jumping to an anchor within it. Assuming the footnotes' iframe, at the bottom of the page, is out of sight, this is how it will work out: I click on "footnote 6", nothing seems to happen, I click on the Back button, nothing seems to happen.

Clearly, jumping to the iframe matters more than jumping to the footnote's anchor.

(Note that some browsers automatically jump to an iframe when a page loads in it, even if they do so as part of loading the parent page, ie. the reader wants to open a page and is instantly whizzed to somewhere halfway down the page. This is undesirable behaviour, and I consider it a bug.)

So, every footnote is pointed at "A HREF=#name" where "name" is the NAME attribute of the iframe. Solved? No, because non-iframe browsers won't be able to find "name" in the page. Okay, I'll add the same "name" anchor between the opening and closing IFRAME tags. But that means all footnote links point to the same anchor. Wouldn't it be better to list the footnotes, each with their own anchor, and have each footnote point to its own external link in the links list? I'd be back at making people click three times (from "see footnote", to the footnote that opens the links page, to the external link on the links page) but only for footnote-type links, and only if they used a really old browser. But if I have six footnotes and the browser does support iframes, all anchors must point at the iframe, so this iframe needs six NAME attributes, and it can't have more than one.

Or I can add six dummy iframes whose ID attributes correspond to the six footnotes. (An iframe's NAME attribute is used to load a page into that iframe, but in my version of FireFox, I had to use ID and not NAME as an anchor. Then again, any browser that knows IFRAME will know ID.) The only purpose of these iframes will be to make the page jump to the big iframe containing the links; the reader then has to scroll down this iframe to find the footnote with the right number.

I'll put number images (to avoid font size issues when sizing the iframes) in the dummy iframes, which act as clickable links that reposition the main iframe's content to the right footnote (which will cause aforementioned Back button issue!) using SRC and SRCDOC, to keep both bases covered. In fact, given that browsers may very occasionally display empty iframes because they can process SRCDOC but not SRC (which I've seen in the legacy browser emulators on dejavu.org), I'll even add an extra warning iframe whose SRCDOC tells the reader to go to the links block page if the links iframe is empty; that iframe has no borders and, if the browser doesn't support SRCDOC, will just be white space. Using four footnotes, the whole collection of iframes will look like this:

"Did you know that Google's(1) motto is "don't be evil"? Suspicious, right? I'd rather use DuckDuckGo(2). Or the WayBack Machine(3) if I wanted to keep tabs on what's been edited out on Wikipedia(4)." (Note that I'm using these sites in the example because they seem like World Wide Web fixtures, and I never want to have to check or change this example page again.)




I've enclosed the above example in a div with a light blue background colour. Any iframe text content that comes from SRCDOC will have the same background colour, while text from a page loaded in the iframe using SRC will be that page's own background colour, ie. white. The numbered buttons will be white because they're GIF images that have their own colour, irrespective of HTML code. The buttons are 32x32 pixels, so I thought they would fit neatly in iframes with a height and width of 32, but the iframe clearly causes some padding around the images, as they would only fully show up in iframes of 39x39; any larger, and in FireFox, a vertical scrollbar appears. In Falkon, vertical and horizontal scrollbars appear regardless, so I've added "SCROLLING=no" to the button-iframes.

This is how the example looks in code; note the line breaks ("BR") between the warning iframe, the row of "anchor" iframes, and the link block iframe. (And between the closing P and DIV tags, for a blue line under the link block iframe.)

<DIV BGCOLOR=aliceblue STYLE="background-color:aliceblue;">
<P>"Did you know that <A HREF=#howifscr-ex1>Google's(1)</A>
motto is "don't be evil"? Suspicious, right? I'd rather use <A
HREF=#howifscr-ex2>DuckDuckGo(2)</A>. Or the <A
HREF=#howifscr-ex3>WayBack Machine(3)</A> if I wanted to keep tabs on
what's been edited out on <A HREF=#howifscr-ex4>Wikipedia(4)</A>."
(Note that I'm using these sites in the example because they seem like World
Wide Web fixtures, and I never want to have to check or change this example page
again.)</P>

<IFRAME HEIGHT=50 WIDTH=100% 
TITLE="Link to the example links page"
FRAMEBORDER=0 STYLE="border:none;"
SRCDOC="<P>(If you can't see any links in the frame, click <A
HREF='howifscr/howifscx.htm' TARGET=_top>here</A> to go directly to the
example links page.)</P>">
</IFRAME>
<BR>
<IFRAME ID=howifscr-ex1 HEIGHT=39 WIDTH=39 TITLE="footnote 1"
FRAMEBORDER=0 STYLE="border:none;" SCROLLING=no SRC="howifscr/1.htm"
SRCDOC="<A HREF=howifscr/howifscx.htm#howifscr-ex1 TARGET=exiframe><IMG
SRC=howifscr/1.gif></A>">
</IFRAME>
<IFRAME ID=howifscr-ex2 HEIGHT=39 WIDTH=39 TITLE="footnote 2"
FRAMEBORDER=0 STYLE="border:none;" SCROLLING=no SRC="howifscr/2.htm"
SRCDOC="<A HREF=howifscr/howifscx.htm#howifscr-ex2 TARGET=exiframe><IMG
SRC=howifscr/2.gif></A>">
</IFRAME>
<IFRAME ID=howifscr-ex3 HEIGHT=39 WIDTH=39 TITLE="footnote 3"
FRAMEBORDER=0 STYLE="border:none;" SCROLLING=no SRC="howifscr/3.htm"
SRCDOC="<A HREF=howifscr/howifscx.htm#howifscr-ex3 TARGET=exiframe><IMG
SRC=howifscr/3.gif></A>">
</IFRAME>
<IFRAME ID=howifscr-ex4 HEIGHT=39 SCROLLING=no WIDTH=39 TITLE="footnote 4"
FRAMEBORDER=0 STYLE="border:none;" SRC="howifscr/4.htm"
SRCDOC="<A HREF=howifscr/howifscx.htm#howifscr-ex4 TARGET=exiframe><IMG
SRC=howifscr/4.gif></A>">
</IFRAME>
<BR>
<IFRAME NAME=exiframe HEIGHT=150 WIDTH=100% TITLE="Example links page"
FRAMEBORDER=0 STYLE="border:none;" SRC="howifscr/howifscx.htm#howifscx-top">
Your browser does not support iframes. Click on the footnote you were looking
for:
<UL>
<LI><A NAME=howifscr-ex1></A>
<A HREF=howifscr/howifscx.htm#howifscr-ex1>footnote 1</A></LI>
<LI><A NAME=howifscr-ex2></A>
<A HREF=howifscr/howifscx.htm#howifscr-ex2>footnote 2</A></LI>
<LI><A NAME=howifscr-ex3></A>
<A HREF=howifscr/howifscx.htm#howifscr-ex3>footnote 3</A></LI>
<LI><A NAME=howifscr-ex4></A>
<A HREF=howifscr/howifscx.htm#howifscr-ex4>footnote 4</A></LI>
</UL>
</IFRAME>
</P>
<BR>
</DIV>

The perceptive reader may notice two apparent mistakes in the above jumble of code. First, the footnotes between the last iframe's tags have anchor names "howifscr-ex[1-4]", but also link to anchors "howifscr-ex[1-4]". That's fine, because the anchors linked to are in a different page. Also, contradictory to my earlier warning about not using the same string with NAME and ID, I use "howifscr-ex[1-4]" as ID strings with iframes, and as NAME strings for the non-iframe anchors. Again, that's fine, because the browser should see either the iframe code, or the code between the iframe tags; it shouldn't parse both. Besides, a link looking for an anchor string will always go to the topmost attribute with that string, and ignore any attributes with the same string below it. The iframe and non-iframe anchor strings are so close together that it doesn't really matter which one gets jumped to.

Since I want to keep things simple, the extra gif buttons and tiny pages to add hyperlink code to them are overkill, so I'll only use SRCDOC in the "anchor" iframes to display footnote numbers as clickable text, if the browser supports that. I can't make the appropriate footnote scroll into view and/or light up, because that falls under dynamic HTML, which is the subject of the sections below; and with dynamic HTML, I'll simply replace the iframe.

To be sure, this solution is clunky. However, iframes and their non-iframe alternatives are just fallback solutions for when the JavaScript solution doesn't work.

Back to index

Using JavaScript to put a links block into a page

The iframe is contained within a div. I said that DIV tags are used to "select" part of a HTML page. Think of it as selecting a block of text, then pressing Delete or Paste. The div encloses the iframe, and, using JavaScript, I will "delete" the iframe by "pasting" the links from the links block page over it. In this way, I don't have to worry about the iframe, and what size it will be, and how its contents will appear.

(Remember the link page's div called "linkblock", enclosing all the links? That's how the links are "selected" for copying.)

And the whole anchor mess when using links like footnotes? Irrelevant, because once the links are "pasted" into the page, a simple "A HREF=#name" will find them no matter what other page they originally came from.

But if I'm just going to paste part of the links block page over the iframe in the main page, why bother defining the iframe in the first place? Because in order to copy and paste from the links block page, that page must be loaded, and it's loaded along with the main page via the iframe. When the whole main page has finished loading, including external files like images and iframe contents, this triggers a "load event".

Since browsers supported (an early version of) JavaScript before they supported iframes, this may mean that in an older browser, even if JavaScript is enabled, the copy-paste trick will not work, and nothing will be shown except a message and the link to the links block page. On the other hand, in a newer browser with JavaScript disabled, at least the reader sees the links in the iframe.

Back to the load event. If a user does something like pressing a key, or the computer does something like starting to hibernate, software considers this an "event". A computer program will wait for an event to happen, and then carry out something it has connected with that event, like inserting a line break if the user presses the Enter key. The connection between an event and its effect is the "event handler". To make the computer program notice that a particular event is happening at all, it needs an "event listener". These literal terms are used in JavaScript.

Apparently, the code to make JavaScript react to a webpage being fully loaded is:

window.addEventListener("load", function() { [code for what to do] });

Happily, there is a much shorter and less confusing way to write this:

window.onload = function() { [code for what to do] };

This is also much more likely to be understood by older browsers, which apparently don't support "addEventListener". Now, how do I splice this event listener into the main page'? There are three possibilities:

  1. using the script tag, via an event listener for "load", which I already said I wasn't going to do.
    <script type="text/javascript">
    window.addEventListener("load", function() {
      // code goes here
    });
    </script>
    

  2. still using the script tag, via window.onload.
    <script type="text/javascript">
    window.onload = function() {
      // code goes here
    };
    </script>
    

  3. via BODY tag, ONLOAD attribute - yes, an event listener can be a HTML tag's attribute!
    <BODY ONLOAD="code goes here">
    webcontent goes here
    </BODY>
    

(NB. I will not capitalize the script tags, in order to set them apart from what I consider the normal HTML syntax. I'll still capitalize interlopers like event listeners disguised as attributes, though. Also, the type attribute of the script tag harks back to the days when web coders used more languages than just JavaScript. Nowadays, JavaScript is the default, and the type attribute can be omitted.)

If I use script tags, I can run an external script instead with "src=scriptname". A script tag pair can either contain code or have a script name in the SRC attribute, but not both. Something to remember, if the script code proves reusable. If I put the code into a BODY tag attribute, it had better be very compact, or the HTML code will be a mess.

What do I want the code to do? First, look at the iframe's content, which is the links block page. Next, put the div section of the links block page in a variable. (Yes, I can squeeze all of that text into a JavaScript variable.) Then, fill the main page div with that variable. If this doesn't work, which means iframes are not supported or the iframe content didn't load, do nothing.

JavaScript was meant to make people think of Java, an object-oriented programming language, and likewise works with objects and object properties. Its main object, which is always there and doesn't need to be declared in any script, is called "window"; this refers to the active browser window and whatever HTML or other content it's displaying at any given moment. An object has properties, referred to in the format "object.property". A loaded HTML webpage, for instance, is the property "document" of object "window", as well as an object in its own right:

window.document

The HTML page being an object called "document", its BODY tag is one of that object's properties:

document.body

A HTML page has only one BODY tag. (To be clear, I refer to the opening half of what is generally a pair of tags.) But it can have several IFRAME tags. By which I mean: two or more iframes. So whereas "body" is an unambiguous element of "document", "iframe" is not. So when referring to a iframe via JavaScript, I must specify which one by using its ID string. I will be referring to iframe "if_content", in order to hoover out its links. Say hello to "getElementById()", searching a document object for any ID attribute whose value matches the string between quotes:
document.getElementById('if_content')

Due to the unique identifier, I don't even have to specify what kind of HTML element I'm looking for. (Simplified: HTML elements are pairs of HTML tags and what's between them, or single tags, like <HR>.) The HTML element summoned this way can be put in a variable, out of which further HTML elements can be retrieved, and also put into variables; for instance, linkIFrame is the iframe whose content is the links block page, and linkContent is the div called "linkblock" inside linkIFrame. Each line of code ends on a semicolon, and both variables are filled with a vallue in the same line as they are defined:

var linkIFrame = document.getElementById('if_content');
var linkContent = linkIFrame.contentWindow.document.getElementById('linkblock');

If it sounds as though I know what I'm doing, it's because I'm adapting code from an online example. Having captured the link block in variable linkContent, I must put the "innerHTML" (all code between the tags) of this variable into HTML element "dumplinkshere", the div in the main page. I do this by putting the main page's div in another variable, linkDump, and setting the property "innerHTML" of linkDump to that of linkContent:

var linkDump = document.getElementById('dumplinkshere');
linkDump.innerHTML = linkContent.innerHTML;

These are the four lines of code to insert in the "page loaded" event listener in the main page. It's a lot of code to stuff into the ONLOAD attribute - not that that's never been done, mind you - so, they will be inside script tags, as per solution 2. Traditionally, script tags are put between the HEAD tags, but there's nothing to stop me from putting them at the bottom of the BODY section, especially since they are not run until all the HTML has been processed. In the example below, those lines have been sandwiched in a "try{}" block (every block goes between curly brackets) followed by a "catch(err){}" block which, if there is an error, "catches" it so it won't just crash the browser tab (or, in pre-tab days, the browser) and, using alert(), prints an error message.

<script>
window.onload = function() {
  try {
    var linkIFrame = document.getElementById('if_content');
    var linkContent = linkIFrame.contentWindow.document.getElementById('linkblock');
    var linkDump = document.getElementById('dumplinkshere');
    linkDump.innerHTML = linkContent;
  }
  catch(err) {
    alert('Iframe overwrite error: ' + err);
    return;
  }
};
</script>

In testing this code, two hiccups arise: the purely theoretical one that this code might not be compatible with older JavaScript-enabled browsers, and the entirely practical one that it doesn't work. To be precise, if I test it offline, with FireFox, it doesn't work, the error text being:

Iframe overwrite error: SecurityError: Permission denied to access property "document" on cross-origin object
A browser called Midori has the same problem. With a browser called Falkon, it works like a charm.

I did a websearch on "cross-origin", and it appears that FireFox has a same-origin policy for the kind of script above, and doesn't allow a HTML page from one domain to simply extract bits from a page on another. And from Firefox 68 onwards, every "file:///" URL is seen as having a unique origin. That means that two HTML files on harddisk, in the same directory, are still treated as if they were part of two entirely disconnected websites. If I upload them and access them from my website, the script works.

To make the script work offline, I opened the FireFox settings by typing "about:config" in the browser. When I started on this how-I - using a FireFox version seventy-something, I dimly recall - what worked was changing the setting "privacy.file_unique_origin" to "false", and restarting FireFox. But a few months later, FireFox had updated itself to version 95, after which it was the setting "security.fileuri.strict_origin_policy" that needed to be set to "false". As these are set to "true" for security reasons, I set both back to their original value after I was done testing.

During testing, FireFox remained stroppy, usually doing what was required, but sometimes showing the following:

Iframe overwrite error: TypeError: linkIFrame is null
which implied that the iframe content hadn't finished loading yet, despite the event "window.onload" which is only supposed to happen when the whole page is good and ready. I've found that reloading a page with an iframe in FireFox dosn't always properly reload the iframe. When the script fails with this error, the browser still shows either the iframe plus content or the non-iframe code, so I shrugged and left it at that.

Below, you should see the four links from section 5, either dynamically embedded in this page (yay, it worked!) or in an iframe, which means it's the same links page loaded in a different iframe.


If all you see is this barebones iframe, it didn't work, for whatever reason.

Back to index

Using postMessage() to put a links block into a page

First things first: postMessage() requires addEventListener() and both are newer JavaScript methods, not supported by Internet Explorer 7 and earlier. Who uses Internet Explorer 7 any more, right? I should hope nobody uses Internet Explorer at all, but backwards compatibility matters to me, so I must say that if the previous section's code didn't work in a particular older browser, postMessage() won't work either, and the browser will show the iframe instead. Second, as opposed to just a script using "window.onload" in the parent window, this method requires scripts in both parent and child page (the child page, in this case, being the page loaded into the iframe). The only reason to use postMessage() at all is to get around the cross-origin policy on modern browsers. So I won't try to make this particular solution backward compatible, but will mention that two libraries have been developed to use postMessage() on older browsers.

Thirdly, as an aside: where normal letters are called "lowercase" and capital letters are called "uppercase", using capital letters in strings to separate the words, as in "postMessage", is called "camelcase". One lives and learns.

The format for sending a message is:

targetWindowObject.postMessage(data, targetOrigin)

To explain the parts: "target window object" is where the message needs to be sent: it's a window object, or, for an iframe, the contentWindow object. The "data" is the message posted, ie. what's being sent to the window object. The target origin is the URI - the Uniform Resource Identifier - of the window object; think of it as a complete URL. This parameter can be left as the wildcard "*", but that does mean the sender will be happily sending the message to any webpage on the internet that has an event listener for messages. That's not a problem in my case - I'm using this for local files - but it is technically, spamming. And if the message being sent is meant to be confidential, it's a security hole.

The format for the event listener that receives the message is:

window.addEventListener("message", function() {
// code goes here
});

The outer layer of the code is "window.addEventListener(...);", so don't forget the closing bracket followed by a semicolon. The intermediate layer, represented by the three dots, is "'message', function() { xxx }" which is to say that if the event detected is "message", a nameless function must be carried out, the code of which is between the curly brackets; the event is implicitly used as the function's first parameter, as if writing "function(event)". If there is a separately defined named function that can handle the event, the "function()" and the curly brackets, including contents, can be replaced by "MyNamedFunction()", or whatever the function's name is.

Unless replaced by a named function, the inner layer, represented by the "code goes here" comment and the three x's between the curly brackets, is the event-handling code, meaning, the receiver's part of the message. This code can:

From a security standpoint, checking the domain is absolutely necessary, as, while the message-sending code simply checks who and where the recipient should be, the message-receiving code should know who the sender of the message is, as a webpage's event listener can accept messages from anywhere else on the internet, meaning, literally any other webpage can trigger its event-handling code. It's comparable to every other computer being able to make your computer reboot.

What I need for this experiment: a page with links; an iframe that loads this page; and three bits of JavaScript. Time to sit down, have a cup of tea, and plan this out.

I must define an iframe for the child page. To keep it simple, there will be no "footnotes" fallback solutions, just an iframe called "if_PostMsg". It will be inside a div called "div_Postmsg".

<DIV ID="div_PostMsg">
<P>If all you see is this barebones iframe, postMessage() didn't work, for
whatever reason.</P>
<IFRAME ID="if_PostMsg" SRC="howifscr/howifscp.htm#howifscp-top">
Your browser doesn't support iframes.
</IFRAME>
</DIV>

In the parent page, which should receive a reply bearing a bunch of links from the child page, I will put an event listener using a script. I will put this script at the top. It should be ready from the start, just as my phone should be charged or hooked up and ready if I expect a call.

In the iframe page, I will also put an event listener, but in a script using the window.load event, because I don't want it to start listening until the page is loaded into the iframe. I need the page to be loaded first, to test if it is loaded in an iframe, or directly into the browser window (in which case there is no parent page, and no need for a listener). The event listener will receive a message from the parent, and send a message straight back.

(Seasoned coders will tell me I should use an event listener to detect if the page is loaded, but 1. that is less backward compatible and 2. it's good for constantly interacting webpages, but not needed for my one simple trick.)

In the parent page, in a script using the window.load event, I will message the iframe page to send me its links. The window.load event of the parent page always comes after the window.load event of any of its child pages, so by the time the parent sends this message, the iframe page will be ready to receive it.

Ready? Here goes:

<script>
// This is the event listener in the parent page

var w = window;      // put active browser window in variable
var msgString = '';  // incoming message variable, initially empty
var putHere = document.getElementById('div_PostMsg'); // div to overwrite

// Test if addEventListener is supported
if (w.addEventListener) {

  // If so, it is set up to receive.
  w.addEventListener('message', function(event) {

    // It only accepts messages from the same domain!
    // Which is null if both files are local.
    if (event.origin === window.location.origin) {

      // The message (event.data) is put in msgString.
      msgString = event.data;

      // The value of msgString is put in the div.
      putHere.innerHTML = msgString;

    }
  }); // end of event listener
}
</script>

Next snippet, in the iframe page, where the links are inside a div called "links": the event listener is set after the page loads, so it can check whether it loaded into an iframe, or as a new page. If it's not in an iframe, then it doesn't need the event listener.

<script>
// This is the event listener in the iframe page

var wif = window; // put self in variable

// Wait for own object to load.
window.onload = function() {

  // If top window is own window, not in iframe, abort script!
  if(wif.self === wif.top) { return; }

  // If own window is in iframe, put div content in variable.
  var linkblock = wif.document.getElementById('links').innerHTML

  // Test if addEventListener is supported
  if (wif.addEventListener) {

    // If so, it is set up to receive.
    wif.addEventListener('message', function(event) {

      // This listener, too, only accepts messages from the same domain!
      if (event.origin === window.location.origin) {

        // Is it the expected message?
        if(event.data == 'PlzSendLinks') {

          // Send linkblock to parent.
          wif.parent.postMessage(linkblock, '*');
        }
      }
    }); // end of event listener
  }
} // end of window.load</script>

Have we got all that? The iframe page accepts only a message from the same origin, which also has to be the right string ("PlzSendLinks"). Knowing who its parent is, it just has to send a message back to that parent, consisting of a variable containing the code of its links block.

Lastly, the parent, once loaded, needs to send the message that will set this chain of events in motion. Out of caution, I'm putting the code in a "try" block. The "alert" notification is only used for testing, after which it is commented out with two backslashes.

<script>
// The parent page sends a message once it's completely finished loading.

// Only once loaded does the parent know its iframes.
window.onload = function() {

  try {
    // Put iframe object in variable.
    var if_page = document.getElementById('if_PostMsg').contentWindow;

    // Post message to iframe object.
    if_page.postMessage('PlzSendLinks', '*');

  }
  catch(err) {
//    alert('Example iframe postMessage overwrite error: ' + err);
    return;
  }

} // end of window.load
</script>

Does it work? Look below: if you see links but no iframe, it worked.

To catch a red-bellied sapsucker, a description of which is found here(1), the best method is to get a tree octopus(2) to help you. Those tree octopuses sure know how to grab a bird off a branch!

One thing, though: tree octopuses are always hungry, so unless you tape its beak shut, just say goodbye to that bird. Yes, octopuses have beaks, just like parrots! How else do you think they crack peanuts?

Disclaimer: this text is utterly ding-ding and purely for testing purposes. Do not approach either red-bellied sapsuckers or tree octopuses without the assistance of the fire department. Peace out.



If all you see is this barebones iframe, postMessage() didn't work, for whatever reason.

Back to index

Still not finished. Next update: refinements of the postMessage solution.