Forgotten JavaScript: Object.handleEvent()
And other Notable Netscape Constructs
Tales of event-handling in JavaScript and some historic oddities of Netscape Navigator 4.
An interesting aspect of web technology is that solutions to specific problems tend to be invented more than once. For some reason or the other, a given solution doesn’t catch on and a new generation of developers will eventually address the wheel by the help of a new proposal. In this case it’s about object isolation and event handling.
In essence, it’s about the “this
” keyword, and what the object represented by it is pointing to. Is it the listener object or the originating HTML-element? This will be always somewhat ambiguous and there’s no obvious solution to this. And for the most of the history of JavaScript developers have been trapped in this ambiguity. Nowadays, there’s “let
” and arrow functions to address this, but there has been and still is a much older solution to this very problem, namely “Object.handleEvent
”. To understand the issue properly, we may want to go back in time and follow the developments.
JavaScript Events, From the Beginning
To start from the beginning, right before there was even the concept of a DOM (Document Object Model) representing all elements there are in a document or webpage, there were JavaScript handlers, which could be attributed to an element. However, this was limited to interactive elements, like form elements and links. Event-handlers could be either included as an attribute to a given HTML-Element or could be attached in JavaScript by the means of any of the pre-populated arrays (“document.forms
” and “document.links
”):
<a href="#someTarget" onmouseover="window.status='Jump to some target...'; return true;" onmouseout="window.status=''; return false;" >Some link</a>
Note the use of the now defunct property “window.status
” for setting the display text in the browser’s status bar.
or,
<html> <head> <script language="JavaScript"> function init() { var myLink = document.links["theLink"]; myLink.onclick = function() { alert('You clicked me!'); return false; } } </script> </head> <body onload="init()"> <a name="theLink" href="#">Click Me</a> </body> </html>
Note that at this time reference was only by the “name
” attribute as IDs hadn’t been introduced yet. Also, the body would have been present only with all arrays populated on a page’s “onload
” event. Moreover, note the “language
” attribute, now superseded by the type-attribute. (And, of course, all tags would have been then in upper-case proper.)
At the time, there hadn’t been an event-object, for which there had to be a convention regarding the “this
”-object. Moreover, in the absence of an event-object, the simple, Boolean return value addressed any further event delegation and event processing by the browser, true
meaning the browser was to continue with defaults, false
for preventing defaults. (Also, as for event propagation, we may note that just a single handler could be attached to any element, as there was just a single property or slot for any event for a given element.) Provided these limitations, it made perfect sense to refere to the target element or originating HTML-source element by the “this
”-object.
This way, we could generalize our status-message as in:
<html> <head> <script language="JavaScript"> function setStatus(link) { // @text contains a link's inner text content window.status = 'Go to "' + link.text + '"...'; } function resetStatus() { window.status = ''; } </script> </head> <body> <a href="#someTarget" onmouseover="setStatus(this); return true;" onmouseout="resetStatus(); return false;">Some Link</a> and <a href="#someOtherTarget" onmouseover="setStatus(this); return true;" onmouseout="resetStatus(); return false;">Some Other Link</a> </body> </html>
Note how the reference to the link-element is passed using the “this
”-object inside the listener-attributes of the HTML-tags. At the time, it made perfect sense, to follow this mechanism for any handlers declared purely in JavaScript, as well. What else would you expect for this
?
<html> <head> <script language="JavaScript"> function showStatus() { window.status = 'Visit the ' + this.text; return true; } function clearStatus() { window.status = ''; return false; } function initLinkStatusDisplays() { for (var i = 0, l = document.links.length; i < l; i++) { var link = document.links[i]; link.onmouseover = showStatus; link.onmouseout = clearStatus; } } </script> </head> <body onload="initLinkStatusDisplays()"> <p> A <a href="#target1">first link</a> and a <a href="#target2">second link</a>. </p> </body> </html>
So, with what was called in hindsight “DOM 0.1” everything was well and nice, and making perfectly sense.
Objects, Listeners and Encapsulation
Fast-forward a few iterations of browsers and JavaScript-versions, object-oriented programming had eventually caught on. Also, there were Event
-objects passing messages to and fro, and a facility for attaching multiple handlers by means of Element.addEventListener()
as introduced by Netscape Navigator 4 (and the incompatible Element.attachEvent()
on the MSIE side of things).
As scripts became more complex, the want arose to bind controller objects incorporating some state and event handling more closely. Which is exactly, where ambiguity was introduced. Inside an object, “this
” usually referred to the very instance (and up its prototpe chain), while inside an event-handler the standard behaviour somewhat enigmatically changed. Confusion arose about the semantics of “this
”. Also, how do we get back to an encapsulating object reference inside such a handler? (Closures to the rescue?)
However, there was — and still is — a perfect mechanism to address the issue, Object.handleEvent
. Sadly, this didn’t see much use, mainly because of lacking support by Microsoft’s Internet Explorer and the hassles of implementing a suitable abstraction for this.
If we looked up the documentation for EventListener at MDN just a few days ago, there was still a tiny, but sadly incomprehensible, note on this, which gave the inspiration for this article. However, in the past few days, there was documentation added on the behaviour and purpose (both for EventListener and addEventListener), reading like this,
Note: Due to the need for compatibility with legacy content, EventListener accepts both a function and an object with a handleEvent() property function. This is shown in the example below.
A few days before, there was just a basic example, which didn't illustrate much, like
element.addEventListener('click', { handleEvent: function() { alert('click'); }, false);
Semantics & Pragmatics
So, how does it work?
Quite simple. If we pass an object instead of a callback function to “addEventListener()
”, any method “.handleEvent()
” of this object will be called as the effective handler, with the this
-object set to the object itself (rather than the originating element). Voilà, issue solved.
Let’s assume there is some controller object encapsulating state and handling events, as well. If we pass this as the callback to “addEventListener()
”, events will be routed to its method “handleEvent
”, just like this:
function MyObject() { this.clicks = 0; this.dblclicks = 0; this.rollovers = 0; this.handleEvent = function(event) { switch(event.type) { case 'click': this.clicks++; break; case 'dblclick': this.dblclicks++; break; case 'mouseover': this.rollovers++; break; } } } var listener = new MyObject(), element = document.getElementById('myElement'); element.addEventListener('click', listener, false); element.addEventListener('dblclick', listener, false); element.addEventListener('mouseover', listener, false);
Mind how “this
” refers to the object instance and not to the event-target.
Therefore, if we wanted to implement OO-components like it were still the 1990s and we were using Netscape Navigator 4, we could (still) do something like the following. (No need for classes!) Mind how we also may use “this
” inside the object to refer to itself as the listener, as the listener-object is resolved early.
<html> <head> <title>A Simple Component</title> <script> function Counter( buttonText, className, language ) { this.clicks = 0; this.button = null; this.label = null; this.lang = this.locales[language]? language : 'en'; var element = this.createElements( buttonText ); if (className) element.className = className; this.updateLabel(); this.button.addEventListener('click', this, false); return element; } Counter.prototype = { 'createElements': function( buttonText ) { var button = document.createElement('button'); button.innerHTML = buttonText; var label = document.createElement('span'); var wrapper = document.createElement('div'); wrapper.appendChild(button); wrapper.appendChild(label); this.button = button; this.label = label; return wrapper; }, 'updateLabel': function() { this.label.innerHTML = this.clicks + ' ' + (this.clicks === 1? this.locales[this.lang][0] : this.locales[this.lang][1] ); }, 'handleEvent': function( event ) { // ta-taa, here it is... switch(event.type) { case 'click': this.clicks++; this.updateLabel(); break; } }, 'locales': { 'en': ['click','clicks'], 'fr': ['clic','clics'], 'es': ['clic','clics'], 'de': ['Klick','Klicks'], 'de-comic': ['Klickerling','Klickerlingens'] // :-) } }; document.addEventListener('DOMContentLoaded', function() { document.body.appendChild( new Counter( 'Click Me', 'counter', 'en' ) ); }, false); </script> <style> .counter { display: inline-block; font-family: sans-serif; } .counter span { display: inline-block; margin: 0 0.5em; white-space: nowrap; } </style> </head> <body> <h1>A Simple Component with Object.handleEvent</h1> </body> </html>
A Simple Component with Object.handleEvent
Note: Obviously, we could and probably would want to move the code in the constructor to an init-method of the prototype, as well, and just call this init-method in the constructor to avoid code duplication in the runtime. Here, we keep it in the constructor, just to point out the relations of the instance and the event mechanism.
Some More Netscape 4 Fun & Oddities
When Netscape Navigator 4 saw the light of CRT displays in August 1997, it came with some odd features, which were also in hindsight rather futuristic. (To be precise, Netscape Navigator 4 was the browser, which came in an entire suite of applications, “Netscape Communicator”, including the browser, email and messaging, groupware, basic HTML editing capabilities, and at times even video conferencing.)
JavaScript Styles
For example, there’s a trend in modern frameworks to decribe any elements and aspects of a webpage in JavaScript. Which is exactly, what was Netscape’s attitude to styles. Styles where in JavaScript, which is also still the style interface, we use today. Unlike with CSS, rules were defined and/or accessed by the collections “document.tags
”, “document.ids
” and “document.classes
” and the function “document.contextual()
”. And, unlike with CSS, rules could be procedural, since this was still JavaScript. (No need for Sass and the like.)
When Microsoft’s CSS was adopted as a standard shortly thereafter, Netscape just wired CSS definitions to a JS-styles translator, for which CSS-support remained always a bit cluncky with this browser.
Example of a JS-style sheet:
<style type="text/javascript"> // Some sample JS style declarations for Netscape 4 // (no need for prefix "document." inside a style sheet) // define some rules for H1 and H2 tags.H1.color = tags.H2.color = 'black'; tags.H1.fontStyle = tags.H2.fontStyle = 'italic'; // define some rules for all members of class "WARNING" // ("all" serves the same purpose as * in CSS) classes.WARNING.all.backgroundColor = 'yellow'; classes.WARNING.all.color = 'red'; classes.WARNING.all.fontWeight = 'bold'; // contextual() for combined rules for any number of selectors // here, center H2 with class WARNING contextual(tags.H2, class.WARNING.all).textAlign = 'center'; // paragraphs with class quote classes.quote.P.fontStyle = 'italic'; classes.quote.P.marginLeft = '16px'; // define by id, here id="test1" ids.test1.color = 'blue'; // we may use variables as well var baseSize = 12; tags.P.fontSize = baseSize + 'px'; tags.BIG.fontSize = 1.25 * baseSize + 'px'; tags.SMALL.fontSize = 0.75 * baseSize + 'px'; tags.H1.fontSize = 2.00 * baseSize + 'px'; tags.H2.fontSize = 1.75 * baseSize + 'px'; tags.H3.fontSize = 1.50 * baseSize + 'px'; // or get it from a function tags.P.fontSize = getPreferredStyle('P', 'fontSize'); </style>
And a script to access style rules:
// query effective rules for a given class-name (NS4 only)
function readRulesForClassName( className ) {
var style = document.classes[className].all,
s = '';
for (var p in style)
s += p + ': ' + style[p] + '\n';
alert(s);
}
JavaScript Entities
Another interesting feature of Netscape 4 were JavaScript-entities: Much like normal HTML-entities, these were implemented ontop of the HTML-escape mechanism, but, instead of some predefined characters, they displayed a string returned by any JavaScript expression enclosed in a construct “&{...};
”:
<p>As always, 2 + 2 = &{ 2 + 2 }; is &{ (2 + 2 == 4) };.</p>
You could also do things like:
<script>
function sum(a, b) {
return a + b;
}
</script>
...
<p>The sum of 2 and 3 is &{sum(2, 3)};.</p>
Just like normal HTML-entities, JS-entities could be used in attributes as well:
<body bgcolor="&{selectedBgColor};"> <input type="text" name="animal" value="&{favoriteAnimal};"> ... </body>
So, HTML came with a JS-driven template syntax in 1997, out of the box. However, this code ran only once, when the page was rendered and the document-stream was still open.
A special use case were conditional comments. If a JS-entity immediately following to the opening part of a comment tag evaluated to true
, the comment-body would be included, else it was treated like any other, normal comment:
<!--&{navigator.platform == 'win95'}; <script> // this script will execute on Windows 95 clients only ... </script> -->
Note: MS came up with a different mechanism for conditional comments in Internet Explorer about the same time. There is no comparable mechanism in web-standards nowadays.
Layers and Templates
Netscape’s approach to dynamic HTML wasn’t exactly the DOM we know today, where you may interface with any given element. Instead, Netscape Navigator 4 had layers (and ilayers for inline-elements). Layers weren’t just some other kind of element, but full-fledged elements HTML documents (including a full HTML-tree starting from a “html
”-node). They were more like chromeless windows of their own, which populated the HTML-layout of the parent page, but populating an array of their own (“document.layers
”). As a side-effect, they initially also allowed the use of an external source, by this also providing the capabilities of an iFrame. Their key feature: As opposed to normal HTML-element, their content (body) and properties (styles) could be rewritten on the fly, you could even create a layer in JavaScript and add it to the document. Unlike the DOM-approach, they provided some kind of encapsulation, much like we see it in the shadow-DOM today. As a major drawback, this also meant that in order to use any of the styles of the parent page, you had to redefine (or re-embed) all the required style definitions for the layer’s document. (Which may have been a bit of a hassle, when you were just dynamically changing some display value, but now had to output the source of an entire HTML-document for the purpose.)
However, this would have facilitated components defined in an external source files quite the way, especially, when combined with local JavaScript code and the “Object.handleEvent()
”-mechanism, we inspected above.
Some of this did see a short-lived renaissance with the HTML “object
”-tag later, but (again) this didn’t catch on with developers.
JavaScript before ECMA Script
While Netscape had actually proposed ECMA Script and was still onboard the process during the development and roll-out of Netscape Navigator 4, for some reason, Netscape 4 wasn’t compatible to the ECMA-262 standard initially. As a result, after the first couple of revisions, Netscape Navigator 4 came in two parallel strains with parallel feature sets, a 4.0x
strain with the traditional JavaScript1.2 syntax and a 4.x
strain adopting the ECMA standard. (So there was Netscape Navigator 4.04 and Netscape Navigator 4.4 available at the same time and the same capabilities, but different JS syntax.)
So, you may ask, what’s the difference of (traditional) JavaScript and ECMA Script? What’s the deal? Isn’t ECMA Script JavaScript?
Actually, there are differences to JavaScript according to its licenser, Netscape, and the ECMA standard. And, while the differences are subtle, they are still potential deal-breakers. Take for instance the semicolon.
Semicolons — Separator or Delimiter?
Initially, semicolons where in JavaScript statement separators rather than delimiters. This went with the then common fashion of “easy” scripting languages (much like the colon was used in BASIC). However, just like with BASIC, it was quite commonly regarded as the stigma of a “friendly”, but not fully grown up language. A serious programming language had delimiters, much like Algol and C, and no excuses! (Which was, as we may assume, why it was changed in ECMA Script.)
// JavaScript1.0 .. JavaScript1.2 function fooFunc(bar) { square = bar * bar; foobar = foo + bar; //<-- this semicolon introduces an empty statement }
However, thanks to the automatic semicolon-insertion mechanism (another of these “easy” features), this wasn’t much of an issue. Normally. However, when it came to simple statements in clauses, this became a crucial discriminator:
if (foo) alert('foo') else alert('bar'); // However (ECMA-262-style) if (foo) alert('foo'); else alert('bar'); // --> Syntax Error: Else without if at line 7.
So simple if-else
clauses where incompatible. Oops!
(Which is also, why putting even simple statements in blocks for the use in compound clauses is still the prefered way for most style guides.)
Return Objects — Or Lack Thereof
Probably the single major improvement of ECMA Script was the introduction of a return object, specifying a return value (default undefined/empty) for any function call. This meant that any exit of a function could set the return value by means of this return object to its liking without changing the function signature (as in procedure versus function). Again, you may ask, what’s the deal?
Now, this is a big deal, a game changer even. Before this, if any of the exits of your function returned a value, each exit had to do so as well.
function foobar(element) { if (!element) return; var bar = parseInt(element.value, 10); if (isNaN(bar)) return; foo += bar } // However... function foobar2(element) { if (!element) return; var bar = parseInt(element.value, 10); if (isNaN(bar)) return; foo += bar; return foo } // --> Error: Function "foobar2" does not always return a value. // Instead... function foobar3(element) { if (!element) return false; var bar = parseInt(element.value, 10); if (isNaN(bar)) return false; foo += bar; return foo }
Now commemorate all those futile and perfectly useless return values…
(Also, consider a rather complex function with any number of exits, which wasn’t drafted to return a value, but at some point, you had to eventually return some. Now you had to go all over the code and look for all those exits and come up with a suitable convention for return values. Mind that this was a runtime error and not a syntax error and that this was well before unit tests. Adding a simple functionality to an application could become a major issue due to this.)
Ancient JavaScript and LiveScript
If you fail to envision the horrors of return values in early JavaScript by now, consider that one of the major introductions of Netscape Navigator 4 was the console, spefically, any errors being routed to the console and being logged there. Before this, any errors were alerted to the user in a popup window! Meaning, not only the very first one, but every consecuitive runtime error resulting from this, as well. The rule of JavaScript failing as early as possible helped a bit, but just so much…
However, there were more oddities about early JavaScript…
Arrays?
Right from the introduction of JavaScript, or LiveScript as it was originally dubbed before the marketing partnership with Sun (actually, browsers accepted LiveScript as a synonym for JavaScript for quite a while — which is, BTW, why you do not name your fancy preprocessor-flavor like this), the language featured arrays. From the very beginning, they have been the means to access properties of a document and its collections of interactive elements, as in “document.forms
” and “document.links
”.
alert(typeof document.forms) // "array" alert(document.forms.length) // 0
However, there was no array constructor (Array object class) and no array methods at all!
So, how would you deal with this? Mind that functions were first class members right from the beginning! Also, mind some oddities with enumerable properties (which were eventually ironed out in ECMA Script), but were of considerable use here, as we had to implement arrays on our own:
// JavaScript1.0 AKA LiveScript function push(value) { return this[this.length++] = value } function pop() { if (!this.length) return null; return this[--this.length] } function MyArray() { this.length = 0; this.push = push; this.pop = pop } var a = new MyArray(); a.push(1); a.push(2); a.push('C'); for (var i = 0; i < a.length; i++) alert(a[i]); // 1, 2, "C" for (var p in a) alert(p + ': ' + (typeof a[p])); // "length: number", "push: function", "pop: function" // (Not necessarily in this order.) var b = a; alert( b[0] ); // 1 alert( b.pop() ); // "C" alert( a.length ); // 2
Which changed in the second half of 1996 with Netscape Navigator 3 (or JavaScript1.1), which finally brought the array constructor. Now, we could simply state “var a = new Array()
”, just like real grown-ups. And, even more, we got swappable images with the “Image
”-object and “document.images
”, as well! Now, the sky was the limit, at least, we could implement a graphical Pac-Man game (instead of rendering it in ASCII inside a textarea)! — However, sky level was still low, since screen real estate to be accounted for was still 12" at 72dpi.
And, how would we address the language differences? By a switch in the language
attribute of the script
-tag. For some time, we would implement a simple script using the attribute “JavaScript
” and then a second script with a language attribute “JavaScript1.1
”, which would be recognized just by capable browsers, owerwriting any definitions made in the first, basic script. The same technique would be employed for dynamic HTML, AKA “JavaScript1.2
”, the next year. (This switch also regulated some other behavior of the JS-engine. E.g., version 1.2 — and only 1.2 that is, as this proved to be a rather odd and unpopular feature — featured negative zero.)
Mind the gray default background, displaying text black on gray (#cacaca), unless you defined your own background-color. Which was Netscape’s approach to medium contrast.
Norbert Landsteiner,
Vienna, 2019-02-01