Archive for April, 2005
Well, it’s that time of the year again, at least for me. I have two holidays planned for the next two months, one is for certain, one highly likely.
The Vesges
On Sunday I’ll be leaving for a one-week holiday in the Vesges, France, with the family. Going to do some nice walks, read some books, and revise for my economics and Dutch exams I’ll have at the end of May. I’ll take some pictures to share when I get back.
Reboot7
I just found out about this conference in Copenhagen, Denmark. The description on the site reads:
reboot is the european meetup for the practical visionaries who are building tomorrow one little step at a time, using new models for creation and organization – in a world where the only entry barrier is passion. reboot is two days in june filled with inspiration, perspective, good conversations and interesting people.
Damn right that’s going to be cool! Some of the cool folks who are going to speak / host conversations are Douglas Bowman, Cory Doctorow, Jason Fried, David Heinemeier Hansson, Matt Webb and David Weinberger. To cut to the chase: I’m thinking of going.
I already managed to get Anne to tag along, but we are hoping somebody is willing to put us up for a few days. We still are students, and speaking for myself I’ll be facing some hefty expenses in the coming months (laptop, but also I’m going to move to uni). Anyway, just asking. We’ll be in Copenhagen from June 9 to June 12. Let us know!
Actually, since I’ll be away next week, let Anne know. Thanks!
sIFR 2.0 is out! This has been my biggest project in the past months. I’ve spent hours upon hours tweaking and tuning the code to make it as easy to use as possible and to make it work in virtually all browsers out there. It’s been a great experience, and I’ve learned a lot more about providing support, promoting an open source project and (unfortunately) cross-browser issues. Heck, we even got Slashdotted!
During the development I worked closely together with Mike, who is a great guy to work with, he’s smart, pragmatic and he’s showed a lot of patience when we were debugging sIFR via IM. As far as I can tell there’s only one bad thing about him, which is that he’s a bit groggy in the mornings. Since I’m nine hours ahead of him, you can guess what time of the day we usually discussed things…
I’d like to thank everyone who I recruted to test various incarnations of the code, in various browsers. I don’t remember all your names, but you know who you are! In special, though, I’d like to thank Joen Asmussen for his article on sIFR, Andrew Hume for How and when to use sIFR and Stephanie Sullivan, who has been a great help improving the documentation.
And of course a huge thanks to everyone else who used and/or promoted sIFR. Thanks!
Now that we’ve reached the end of the 2.0 cycle, perhaps it’s a good idea to look into the future. I expect sIFR to become a bigger influence in webdesign in the coming months and I hope it’ll accelerate the design of a system which will allow designers to load fonts via CSS. In any case, we’ll make sure sIFR continues to work in future browser versions.
Again, thanks for this wonderful experience.
Mark, I’ve got a challenge for you! Try to make some JavaScript that emulates the behavior of the :target psuedo-class in IE.
No, I’m not trying to be rude, this would honestly be something very cool, and to be honest (still), my JavaScript skills are not that good.
That’s the challenge I recieved this morning from The Wolf (not The Real Wolf though). Well, I thought it was doable, so I accepted:
That should be possible… challenge accepted!
And indeed, after about 30 minutes of hacking it’s done: Emulate the CSS :target Pseudo-Class in IE. The demo only works in Internet Explorer, you can view source there to see the code.
I hope you can use it!
Oh, one more thing:
Great to hear it! If you can do it, everyone owes you a beer or something…
![]()
You all owe me one!
When I wrote my short tutorial on how to use the DOM for :hover I tried to point out something else. It was between the lines, and the post was written in a hurry, so I suppose it didn’t caught much attention.
Basically what the JavaScript did was look for events so it could set a different class on the elements. Effectively, mouseover set the state of the element to “hover”, while mouseout set it to “none”. In other words: I wrote code to manage the state of the element, and the style of the element in that specific state is controlled via it’s class.
Thus, a.hover becomes the equivalent of a:hover.
This turns out to be key in understanding why CSS doesn’t define behaviour. The pseudo-classes are a special kind of classes, which aren’t set by some JavaScript code, but by hidden, low-level code in the browser. The browser manages state, detects you have placed the mouse pointer over the element, and styles the element following the rules in the applied pseudo-class [1].
Unfortunately Internet Explorer doesn’t support this behaviour for elements other than a. We need to write high level JavaScript code to manage the state. And since we can’t use :hover to style these elements, we need to use our own class.
This doesn’t mean, however, that CSS is behavioural. Nor does it mean that we should shy away from using :hover, because there are browsers out there which aren’t capable of managing the state of elements internally [2].
Footnotes
[1]: Of course this not only applies to :hover, but also to :active, :link, :visited and :focus.
[2]: Or, more likely, aren’t capable of exposing this state.
Yup, Jeremy Keith is completely right when he says that :hover is behavioural. That’s why I’m going to explain here how you should do :hover via the DOM. That’s right… I’m going to tell you!
Okay, so first we grab all the links on the page. We’ll do this on onload. I’m actually going to cheat a bit with the events, but I hope you’ll forgive me:
window.onload = function(){
var links = document.getElementsByTagName("a");
for(var i = 0; i < links.length; i++){
links[i].onmouseover = function(){
this.className += "hover"; // sorry, no support for multiple classNames here
};
links[i].onmouseout = function(){
this.className = this.className.replace(/\bhover\b/, "");
};
};
links = null; // look how I evaded memory leaks here!
};
And the CSS:
a.hover {
text-decoration: none;
font-weight: bold;
}
Ahhh.. that’s so much better. Now we have seperated style and behaviour completely. Time for some well deserved rest now.
You didn’t think I was serious about this, did you?
Via Jeffry Zeldman’s alter ego Apartness’ blog come the following practices from Tanya Rabourn regarding web standards (emphasize mine):
In order to anticipate future browser compatibility we require conformance to the following W3C standards:
HTML
Validate to either the W3C’s XHTML 1.0 transitional or strict doctype http://www.w3.org/TR/xhtml1/
CSS
Validate to the W3C’s CSS 2.1 or 1.0 http://www.w3.org/TR/CSS21/ http://www.w3.org/TR/CSS1
Javascript
Javascript will never use browser detect but instead object detection to test for browser support of properties, arrays and methods
I think we all know that things aren’t black and white, just how it is in this case. Object detection surely is a good idea: if you want to fetch an element which has a certain ID, you need document.getElementById, so you might as well want to make sure that method is supported before you run your script. Unfortunately browsers have other bugs than “simply” not supporting DOM methods.
Take Safari, for instance. It won’t repaint the document if you append a new node to it. To work around this you need to use a hack which adds an empty string to the innerHTML of the node you just appended to. Quite obviously you don’t want to use this hack in other browsers: you need to use browser detection to determine if the hack is necessary. And to add to the confusion: this bug has been solved in Safari 1.3, so you might as well want to make sure not to use this hack in that browser.
By the way, did I mention innerHTML is not supposed to work in XHTML documents? You need to find a way to detect that as well…
To conclude: it’s a good idea to use object detection, but sometimes it just won’t cut it. And you don’t want to leave out awesome scripts because it uses browser detection, do you?
This is an introductory tutorial to the sIFR Explained series, covering basic JavaScript (and programming) concepts — some knowledge of JavaScript, like knowing how to define functions, is required though. It’ll be updated from time to time when more background information is necessary. Here we go.
Anonymous Functions
An anonymous function is a function without a name. This is an anonymous function [1]:
function(msg){ alert(msg); }("hello world");
What is happening here is that the function is defined: function(msg){ alert(msg); } and executed immediately: ("hello world").
This isn’t an anonymous function:
function foo(msg){ alert(msg); };
foo("hello world");
Nor is this:
var foo = function(msg){ alert(msg); };
foo("hello world");
In general anonymous functions are used to execute code which only needs to be executed once. This is the way it is used in sIFR. A side effect which occurs in the sIFR code is the creation of closures.
Scope, the scope chain and closures
But before we get into closures, something about scope. Every JavaScript expression is surrounded in a scope. The scope contains what is available to the expression. When you define a new function, the body of this function exists in a new scope. For example:
function foo(){
var msg = "hello world";
alert(msg);
};
foo();
Here alert(msg); works, because msg is defined in the same scope as in which alert is called.
Deep down in the JavaScript interpreter the scope is represented as a call object. This object contains all the variables which have been defined in the scope that the call object represents. When a new scope is created, it’s call object is linked to the call object of the scope it was created in. This creates a scope chain.
This phenomenon is best explained through an example. In JavaScript the window object is the global scope. This is the highest scope in the scope chain. If we create a new function in the global scope the scope of this function will be chained to the window object:
var msg = "hello world";
function foo(){
alert(msg);
};
foo();
Now, if you call foo, it’ll alert hello world
! But how does it know the value of msg, which wasn’t defined in the function body foo? It turns out that if the JavaScript interpreter can’t find a variable in it’s current scope, it’ll go up the scope chain and searches the parent scope until it finds the variable. In our case it finds msg in global scope.
You can also create a scope chain by nesting functions:
function foo(){
var msg = "hello world";
function bar(){
alert(msg);
};
bar();
};
foo();
Now, if you call foo, it’ll define bar and execute it immediately afterwards. And as expected it’ll alert hello world
.
When a nested function has access to the scope of it’s parent function, it is called a closure. sIFR relies quite heavily on closures.
At ths point you might want to read Variable scoping gotchas from Scott Andrew for more information about defining variables in JavaScript.
Footnotes
[1]: I’m not really sure why you’d want to write an anonymous function to alert `”hello world”`, but it’s just an example!
This is a series about how the JavaScript code in sIFR works. What tricks were used, what are the powerful concepts, and what are the browser hacks. It’s an interesting read, in which you’ll dive into some complicated JavaScript code.
Before we get started though, you might want to read the terminology tutorial which will give you insight in the wonders of closures, scopes and all that fancy stuff.
Enjoy!
Event Cache is a small script which can store the events you have set on a page and remove them on unload. This way it can prevent memory leakage. Please read DHTML leaks like a sieve by Joel Webber for more information. Also, here’s a quote Jim Ley, who explains:
The Internet Explorer web browser [...] has a fault in its garbage collection system that prevents it from garbage collecting ECMAScript and some host objects if those host objects form part of a “circular” reference. The host objects in question are any DOM Nodes (including the document object and its descendants) and ActiveX objects. If a circular reference is formed including one or more of them, then none of the objects involved will be freed until the browser is closed down, and the memory that they consume will be unavailable to the system until that happens.
So, how can we solve this? If we make sure no references are left when the page unloads, there won’t be a problem. In the comments of Joel Webber’s article Dylan Schiemann mentioned that Alex Russell of NetWindows had written a solution for this problem. So I dived into the NetWindows code to look for it.
It turns out that what the NetWindows code does is caching all events set for the affected browsers and detaching the events on unload. Based on this principle I wrote EventCache.
Please also read the follow-up article.
Usage
The idea is that you hook EventCache into your normal event code. You simply add the information you need to set the event to the EventCache using the add method.
EventCache has the following properties and methods:
listEvents-
An
arraywhose items are arrays which contain the information in the following order:node,sEventName,fHandler,bCapture. Seeaddfor more information. add(node, sEventName, fHandler, bCapture)-
Add new information to
listEvents.node- A reference to the node on which the event has been set.
sEventName-
The name of the event.
Please note: this name must not be prefixed with
on
for any browser but Internet Explorer. The actual event you use must be prefixed withon
. For example, if your event isonclick,sEventNamemust beclick. If you use a custom event you can’t usefoo, but you have to useonfoo. Of coursesEventNamemust befoo. fHandler- A reference to the
functionwhich handles the event. bCapture- A
booleanwhich determines whether the event is triggered in capture mode or not. Does not apply to Internet Explorer.
flush()- Remove all cached events.
Please note: You have to make sure that EventCache.flush is fired on unload!
Examples
I’ve made two testcases. One with memory leaking, and one which uses Event Cache. You can also try the Event Cache testcase with feedback
As you can see these examples don’t use browser detection to see if cleaning the references is really necessary. That’s something for your own implementation.
Other Methods
There are some other ways to solve this problem. One would be to never keep references at all, but closures are too handy to do this. Another solution is to iterate over all elements in a document and remove specific events. This is the solution that Aaron Boodman proposed.
The downside with this method though is that it only works for events which use the so-called DOM 0 event model: element.onclick = function(){};. If you use element.attachEvent() or element.addEventListener() you need to remember the specific values you used to set the event, which means you have to cache it.
Considerations
The cache keeps references to the element on which you’ve set an event and the event handler. This means that it consumes memory itself. It also takes time to remove the events on unload.
Download and Licensing
EventCache is released under the CC-GNU LGPL. You can download version 1.0.
And Finally
If you have any questions about this article or the code, please contact me.
