All significant code in this article is licensed under the LGPL (significant meaning longer than a few lines and exhibiting some complexity), and is available under other licenses per request.
One of the greatest strengths of Firefox when it comes to DOM scripting is that it exposes all of its interfaces to Javascript, allowing you to enhance and extend. Making Firefox support IE's method of event handling is as easy as:
HTMLElement.prototype.attachEvent = function(type, listener) { return this.addEventListener(type.substr(2), listener, false); }
While using this as an excuse to write non-forward compatible code is a bad idea, the ability remains as immensely useful.
As it turns out, Opera 8 exposed constructors in the same fashion as Firefox, leaving us only Safari and Internet Explorer to contend with. You may download a zip file of all the source code involved, or view it below.
/*
HTMLElement Prototyping in KHTML and WebCore
Copyright (C) 2005 Jason Davis, http://www.browserland.org
Additional thanks to Brothercake, http://www.brothercake.com
This code is licensed under the LGPL:
http://www.gnu.org/licenses/lgpl.html
*/
if (navigator.vendor == "Apple Computer, Inc." || navigator.vendor == "KDE") { // WebCore/KHTML
(function(c) {
for (var i in c)
window["HTML" + i + "Element"] = document.createElement(c[ i ]).constructor;
})({
Html: "html", Head: "head", Link: "link", Title: "title", Meta: "meta",
Base: "base", IsIndex: "isindex", Style: "style", Body: "body", Form: "form",
Select: "select", OptGroup: "optgroup", Option: "option", Input: "input",
TextArea: "textarea", Button: "button", Label: "label", FieldSet: "fieldset",
Legend: "legend", UList: "ul", OList: "ol", DList: "dl", Directory: "dir",
Menu: "menu", LI: "li", Div: "div", Paragraph: "p", Heading: "h1", Quote: "q",
Pre: "pre", BR: "br", BaseFont: "basefont", Font: "font", HR: "hr", Mod: "ins",
Anchor: "a", Image: "img", Object: "object", Param: "param", Applet: "applet",
Map: "map", Area: "area", Script: "script", Table: "table", TableCaption: "caption",
TableCol: "col", TableSection: "tbody", TableRow: "tr", TableCell: "td",
FrameSet: "frameset", Frame: "frame", IFrame: "iframe"
});
function HTMLElement() {}
HTMLElement.prototype = HTMLHtmlElement.__proto__.__proto__;
var HTMLDocument = document.constructor;
var HTMLCollection = document.links.constructor;
var HTMLOptionsCollection = document.createElement("select").options.constructor;
var Text = document.createTextNode("").constructor;
var Node = Text;
}
That can be included into your pages to enable Safari support for HTMLElement prototyping. Alternatively, it seems that in Safari 2, a lot of these are exposed directly:
// More efficient for Safari 2
function Document() {}
function Event() {}
function HTMLCollection() {}
function HTMLElement() {}
function Node() {}
Document.prototype = window["[[DOMDocument]]"];
Event.prototype = window["[[DOMEvent]]"];
HTMLCollection.prototype = window["[[HTMLCollection.prototype]]"];
HTMLElement.prototype = window["[[DOMElement.prototype]]"];
Node.prototype = window["[[DOMNode.prototype]]"];
Save the following as HTMLElement.css (and include it on your pages):
* { behavior: url(HTMLElement.htc); }
And save the following as HTMLElement.htc:
<!--
Crossbrowser HTMLElement Prototyping
Copyright (C) 2005 Jason Davis, http://www.browserland.org
Additional thanks to Brothercake, http://www.brothercake.com
This code is licensed under the LGPL:
http://www.gnu.org/licenses/lgpl.html
-->
<PUBLIC:COMPONENT lightWeight="true">
<SCRIPT>
var constructors = {
html: "Html", head: "Head", link: "Link", title: "Title", meta: "Meta",
base: "Base", isindex: "IsIndex", style: "Style", body: "Body",
form: "Form", select: "Select", optgroup: "OptGroup", option: "Option",
input: "Input", textarea: "TextArea", button: "Button", label: "Label",
fieldset: "FieldSet", legend: "Legend", ul: "UList", ol: "OList",
dl: "DList", dir: "Directory", menu: "Menu", li: "LI", div: "Div",
p: "Paragraph", h1: "Heading", q: "Quote", pre: "Pre", br: "BR",
basefont: "BaseFont", font: "Font", hr: "HR", ins: "Mod", a: "Anchor",
img: "Image", object: "Object", param: "Param", applet: "Applet",
map: "Map", area: "Area", script: "Script", table: "Table",
caption: "TableCaption", col: "TableCol", tbody: "TableSection",
thead: "TableSection", tfoot: "TableSection", tr: "TableRow",
td: "TableCell", frameset: "FrameSet", frame: "Frame", iframe: "IFrame"
};
// Default initialization
if (!("HTMLElement" in window)) {
// Master element
var control = element.document.createElement("object");
// Setup HTMLElement
control.appendChild(element.document.createElement("param"));
var dummy = window.HTMLElement = function() {};
dummy.prototype = control.lastChild;
// Initialize other constructors
for (var el in constructors) {
control.appendChild(element.document.createElement("param"));
dummy = window["HTML" + constructors[el] + "Element"] = function() {}
dummy.prototype = control.lastChild;
}
// Make live
element.document.all.tags("head")[0].appendChild(control);
}
// Constructor name
var name = "HTML" + constructors[element.tagName.toLowerCase()] + "Element";
// Force inheritance
for (var prop in window[name].prototype) {
if (typeof element[prop] != "undefined" && prop != "dataFormatAs" && prop != "dataSrc" && prop != "dataFld")
element[prop] = window[name].prototype[prop];
}
for (var prop in HTMLElement.prototype) {
if (typeof element[prop] != "undefined" && prop != "dataFormatAs" && prop != "dataSrc" && prop != "dataFld")
element[prop] = HTMLElement.prototype[prop];
}
// Map expandos to inherited
HTMLElement.prototype.attachEvent("onpropertychange", function() {
if (typeof window[name].prototype[event.propertyName] == "undefined")
element[event.propertyName] = HTMLElement.prototype[event.propertyName];
});
window[name].prototype.attachEvent("onpropertychange", function() {
element[event.propertyName] = window[name].prototype[event.propertyName];
});
</SCRIPT>
</PUBLIC:COMPONENT>
This will create the constructors for all of the HTML elements (but not collections or more general interfaces such as Node or Element), and you can use them normally.