IEemul
by David Blackledge

A suite of JavaScript "libraries" that emulate certain functions of IE (Internet Explorer) for use with Netscape, allowing you to easily create cross-browser compatible code by just writing a restricted set of IE-specific code, and including this library. This is mostly thanks to Netscape's oh-so-powerful Object.watch method.
These libraries support the "all" collection, parts of the "style" object, and the "window.event" method of accessing event information, among other things.
History
8/2001
  • Broke IEemul.js into several modules to reduce load time when you don't need all of the functionality. Now it is a suite of libraries: IEemulCore.js, IEemulEvents.js, IEemulEventsUtil.js, IEemulStyleSheet.js
  • Added the new library IEemulCursor.js for pseudo-support of the "cursor" stylesheet attribute.


Amiga users should appreciate the name, by the way.

NO WARRANTIES. USE AT YOUR OWN RISK. IF ANYTHING GOES WRONG BECAUSE OF YOUR USE OF THIS CODE, IT'S YOUR OWN FAULT FOR THINKING IT WAS RELIABLE WHEN YOU DIDN'T EVENT PAY FOR IT. NETSCAPE HAS BEEN KNOWN TO *CRASH* UNDER CERTAIN CIRCUMSTANCES WITH REPEATED USE OF COMPLEX FUNCTIONS LIKE THOSE USED BY THIS LIBRARY.

Some example stuff using the library and only ONE set of code, but works in BOTH browsers. A 4-layer box outline that's resized by dragging the bottom blue border[size,clips,positions,events], and an image that gets re-written with the new size as well[innerHTML]. A cursor-following layer[events,position], a random status message for each mousemove [attach/detachEvent], and two empty boxes that can be dragged around the screen with a small box staying halfway in-between them[events,size,clips,positions]. Several boxes have different cursors set [cursors].

IEemul also has a "competitor" called dhtmllib which, though it uses the same concept, the philosophy is very different. IEemul tries to do everything real-time in case anything has changed or the work is being done during layout, and specifically supports the document.all("id") syntax. Dhtmllib initializes at the onload event and actively builds an all "collection" by flattening out the layers hierarchy and thus specifically uses the document.all["id"] (or document.all.id) syntax. Dhtmllib also supports the onscroll event and does a better job of event bubbling and might support one or two other things, while IEemul supports a wider range of things, but doesn't support the onscroll event (yet) and doesn't do as good a job of event bubbling (yet). Note also that dhtmllib has apparently not been updated since December 1999.

What you must do:

Include the following lines as needed in your HEAD section of your documents:
<SCRIPT language="JavaScript" SRC="IEemulCore.js"></SCRIPT>
<SCRIPT language="JavaScript" SRC="IEemulEvents.js"></SCRIPT>
<SCRIPT language="JavaScript" SRC="IEemulEventsUtil.js"></SCRIPT>
<SCRIPT language="JavaScript" SRC="IEemulStyleSheet.js"></SCRIPT>
<SCRIPT language="JavaScript" SRC="IEemulCursor.js"></SCRIPT>

The code includes the IE-only "conditional compilation" junk that makes sure IE never tries to run anything in the file.

You may also want to include a bit of browser-sniffing script and only document.write the library inclusion when you have Netscape 4. This will reduce download time for other browsers.

IEemulCore contains the basic DHTML functionality like the "all" collection and the "style" object.
IEemulEvents sets up the window.event object.
IEemulEventsUtil provides a few other event-related functions that are not as commonly used.
IEemulStyleSheet provides the script-driven stylesheet definition API.
IEemulCursor creates a floating image next to the Netscape cursor that attempts to follow any "cursor" entries in a stylesheet.

For any non-layer tag to show up as a layer (and thus be available from the IEemul .all collection) its style must have the "position" attribute explicitly set to either "relative" or "absolute". An easy way to do this is to have a first style element of
DIV {position:relative}
and perhaps one for SPAN as well, or any other element you will be referencing by ID. This will work for ANYTHING with an ID, IF you set the position attribute. You could, in fact, have
A {position:relative}
and suddenly be able to dynamically move and change the background color of all your Anchors that have IDs using IE-style code, or whatever else you want to do with them.
Note also that depending on the effect you want, you may need to use Netscape's layer-background-color property to have the background-color set correctly in both browsers. With the right combination of sizing and clipping you can get IE to show background without any real content, but currently it doesn't cause netscape to do so.

In general, you need to restrict your usage of IE-specific code outside of what is listed below. For instance, any setting of the style object for things not supported won't HURT anything, but it won't have any effect in Netscape. Using special IE functions, however, will break. Usually, most anything implemented specially by IE can be implemented manually in Netscape (as this library proves).

What works:

IEemulCore.js:

document.all("id")
reference.all("id")
Gets you an object reference for that ID. This is the most critical function and must be used for almost anything else to work right as it sets up all additional properties on the object you request. Starts at the document/reference used (either a document or a Layer in Netscape), first looks for a property of that object with that name (usually a fast way to get layers immediately beneath it, but could cause problems with oddly named ids), then checks for layers below that object with that name, and then recurses on each of those layers.
reference.style.backgroundColor
.height
.width
.pixelHeight
.pixelWidth
not on document:
.clip
.left
.top
.pixelLeft
.pixelTop
.visibility
.zIndex
Tries to do the equivalent change. Usually by just passing the value through to the layer's equivalent property. .clip accepts "rect(top,right,bottom,left)" where any of the four can be the word "auto", or you can do .clip="auto"; "pixel" versions of values do the exact same thing as non-pixel versions. .visibility translates from IE/Standard wording to Netscape wording on "visible" and "hidden", and passes all other words on through.
reference.innerHTML
reference.outerHTML
Actually, both do the exact same thing since, when using a DIV or SPAN, writing the layer's document is like innerHTML, but for anything else that gets "turned into" a layer like an A tag, writing the document is equivalent to outerHTML.
USE THIS CAREFULLY, as it re-writes the document of the layer in Netscape which has some complex results sometimes. One notable thing is that some aspects of your reference will stop working after you set the innerHTML, so if you still need to use the reference again, you should re-get it using document.all() again. This won't hurt anything in IE, and it will make things work better in Netscape.
reference.offsetLeft
.offsetTop
.offsetWidth
.offsetHeight
.clientWidth
.clientHeight
.offsetParent
read-only in IE, so don't write them and expect it to do anything good. These are the values you need to use to find out something's position, as IE does NOT initialize the style equivalents of them in certain (all?) cases.
window.execScript(scriptstring)
just runs eval
window.navigate(url)
just sets window.location.href=url

IEemulEvents.js:

window.event.altKey
.button
.clientX
.clientY
.ctrlKey
.keyCode
.offsetX
.offsetY
.screenX
.screenY
.shiftKey
.srcElement
.type
.x
.y
Each one is just a direct translation or use of the Netscape's Event argument's equivalent value. The window.event object is set up by capturing events at the window level, preparing the object, then routing the events down to the real targets. Event bubbling is only barely supported (nested layers go to all enclosing layers & documents, and finally window; document goes to window) and not really tested, so you should use each target individually. Now tries to do a better job and uses event.cancelBubble and tries to use event.returnValue. Netscape event capturing is turned on automatically when you set the event in JavaScript, though the most important events (mainly those available inline on an A tag) are captured (on) by default. So, inline events will typically work (with window.event available) for anything that Netscape supports them on (primarily just the A tag). Everything else, you'll have to get a reference to (using "all") and set the event in JavaScript.
You MAY set the document's events, and your function will be called AFTER the initial document event that sets up the IE event object.

IEemulEventsUtil.js

reference.fireEvent(eventname[,event object]);
just uses Netscape's handleEvent and makes sure the window.event is more-or-less set up correctly (according to MS docs).
reference.attachEvent(eventname,handler);
reference.detachEvent(eventname,handler);
Adds more event handlers. Regular assignment still applies to the regular eventname and won't affect attached ones. Handlers added this way are fired "in random order" after the regular event handler. Works Beautifully. MS docs don't say how return-values work here, so currently it always runs all functions, but returns true only if all of them return true (!== false, actually). MS docs also mention some weird "ondetach" event that is supposed to let you know when to call .detachEvent... I dunno what that's all about.

IEemulStyleSheet.js:

document.styleSheets(0).addRule(context,rule); (or document.styleSheets[0]....)
Gives you the ability to create Stylesheets through JavaScript. Makes use of Netscape's document.ids, document.classes, and document.tags type stuff, but only works BEFORE layout. Changes after layout will have no effect. Translates property names from "blah-blah-blah" to "blahBlahBlah" and just tries it, so could blow up with more obscure properties not supported by Netscape.

IEemulCursor.js:

Stylesheet: #id or BODY or A or INPUT or SELECT or TEXTAREA cursor:hand or crosshair, or anything else in the older standard for IE.
Uses BUILT IN xbm images floating NEXT to the real cursor to indicate the desired cursor in Netscape.
some examples

What already works on both browsers:

What doesn't work:

What may work someday:

NOTES:

IEEmulLite - if you don't want the whole (around 25k last I looked) library to have to load:
if(document.layers) {
Document.prototype.all = new Function("id",
"var l=this.layers[id]; if(l)l.style=l; return l;");
Layer.prototype.all = Document.prototype.all;
}
This will make ref=document.all(id), ref.all(id2), ref.style.left, ref.style.top, ref.style.visibility, and ref.style.zIndex all work.
Here's a short example using it (with the small hack of doing parseInt(ref.style.left) to get a number in IE instead of the number followed by "px"): IEEmulLite Example... works in IE and Netscape with no lines changed ... just the above few lines inserted.
Eventinitialized to work in-line if supported?window.event.srcElement contains
onmouseoveryesLAYER
onmouseoutyesLAYER
onmousemoveNOdocument
onmousedownyesdocument
onmouseupyesdocument
onclickyesdocument
ondblclickyesdocument
onkeydownNOdocument
onkeyupNOdocument
onkeypressNOdocument
the following items I realized I could catch, so they try to work, but haven't really been tested well, and some may not even fire in Netscape (notably onselect and onmove), though the Event.TYPE does have a value. I could actually add more, but many of the constants are not actually supported by Netscape at all, as far as I know.
They're enabled by default, though maybe they shouldn't be, especially blur and focus.
onresizeyes
onselectyes
onchangeyes
onmoveyes
onfocusyes
onbluryes

Reading the value of window.onevent won't get you the same thing you wrote it with (it'll get you the Netscape top level event handler that sets up the window.event object). So don't do that. (Looks like IE doesn't support window events anyhow, though)
Same applies to any object if you use attachEvent on it... you'll get the attachedEventHandler back.

The target of an event is the DOCUMENT contained in a layer for all events going to a layer, except onmouseover and onmouseout where the target is the LAYER itself. That's just how Netscape does it.

RELATIVELY positioned (position:relative) content doesn't handle events properly in Netscape. It never fires the event unless something INSIDE the object fires it (e.g. an anchor inside a span or div)... then the event handler will fire, but only when the other object is the one targeted. ABSOLUTELY positioned content is the only reliable method aside from using things that can do inline events.

Everybody says you can't do stuff with table cells in Netscape. Not true, but if you want it to still show up as a table, you have to use something like TD {position:relative} which runs into the problem mentioned above for events. You also have to have an explicit ID for every cell you want to mess with. If you can get around that with an Anchor or something, you can certainly do anything else you want to the cell like change its background color... even re-position it, although then you get really weird results (the display gets moved, but as far as events and the pointer are concerned, it's still in its original location).

window.event is ONLY set up for the supported events. Some other events may not be able to use window.event. HOWEVER, window.event never gets CLEARED after an event, so if you are capturing key events and/or mousemove events, the window.event from the last of those to be fired will be available and might contain what you need/want.

Do NOT re-define any of the events on the window object itself until after the include of IEemulEvents.js. IEemulEvents.js makes no attempt to check if an event is already present before redefining them. If this bugs too many people, this could easily be changed. Note again, however, I'm pretty sure IE doesn't support window events anyhow... just document and below.

document.all.id (and document.all["id"]) doesn't work until after the first time you reference the id with document.all("id"). Might not work at all if I've got it turned off at the moment (since it's kind of impractical given this limitation).

I've made an attempt to make document.body work, but I'm not certain it does.

Some random notes I've made about future possibilities:

Style:
Can view unsupported style in Netscape, e.g. document.ids.blah.cursor
cursor, overflow[-x][-y], filters would be best candidates... some others. (scrollbar-blah-color,
Support for ids, at least... maybe document.tags.DIV
maybe someday find out how to get pseudotags, then support A:hover

Events:
See what order onblah fires versus .watch("blah")
make event handler system do:
- capture at window level.  
do toplevelcapture thing (ie5.5?)
make sure window, document, and target all use IEemulEventHandler
add window/document/target to bubble hierarchy
check parentLayers from target for more bubble hierarchy.
from target: do work, bubble to next in hierarchy

when routed to, and not yet bubbling: do toplevelcapture thing, add self to bubble hierarchy, route down.
when run and bubbling, do work, do attached events, bubble up.

Could make it look kind of like original event with:
onblah = eval(onblah.toSource().replaceFirst("{","{return IEemulEventHandler(this);"));

Maybe: just catch at window, do entirely-own event system calling items' regular on funcs, and return false so Netscape event system doesn't fire at all. (can't use routeEvent then, though)


IE4.0:
window.onscroll (just watch window.pageX, window.pageY) [Nope... checked into that.. apparently not all Object.watch really works.  would have to be a setInterval]

document.clear() (open and close)


document.activeElement: IE4.0
IE5.5:
Object.onactivate
Object.onbeforedeactivate (cancellable)
Object.ondeactivate
Object.setActive (doesn't focus) (same as window.onfocus, but uses "this" and does NO focusing)
window.onfocus: (nothing if target==activeElement) activeElement.onbeforedeactivate (activeElement.focus if false) ondeactivate, activeElement = target, activeElement.onactivate

IE5.0:
window.onbefore/afterprint: window.print = onbefore, oldprint(), onafter

showModalDialog: open window and set up properly(window.open properties, window.dialogArguments/Height/Left/Top/Width); alert in parent window saying "click when done with popup" (maybe make a loop while popup remains open); return popup's return value.
window is dependent, menuless(both menubar and right-click), and retains "focus" while open.
showModelessDialog: open window same as modal, but then just pass back the window reference.... window is still dependent, menuless(menubar/right-click) and always on top (relative to opener), but NOT always focused.

IE5.5:
createPopup... use Layers, maybe.