seanmonstar

Apr 12 2010

IE Opacity Ignores Positioned Children

The CSS2 Opacity property doesn’t work in the current versions of Internet Explorer - through IE8. However, it has provided a way to achieve similar results with a different method, using IE’s filter property. Javascript frameworks usually work this in for you when you try to set an elements opacity in a cross-browser fashion.

It should come as no surprise that there are bugs that arise from this filter property. One is that the filter doesn’t cascade to children that have their position as anything besides static.

This has been talked about in several places all over the internets, and even recently in the MooTools User Group, but it seemed worthy of documenting if only for my own purposes. You can see for yourself with this demo at jsFiddle.

A way to fix this has been suggested multiple times as well: You can set the positioned element to inherit the filter. Unfortunately, the instance where I ran into this bug proved unsolvable, because I actually needed that filter property to do something besides inherit. Nonetheless, if you’re simply trying to fade out and find that part of it isn’t fading in IE, this will solve it. This, and apparently some sort of change to the layout of the element is also required. You could change its display to inline-block, but some felt that setting contentEditable to true to be the least intrusive on changing the layout. I’m not so sure I agree, but here is it anyways.

#relative {    
	position: relative;    
	filter: inherit;
}

Feb 24 2010

Iterating Global Variables in Internet Explorer

There’s a couple ways you can define globally accessible variables in Javascript. And it turns out that in JScript, they actually mean different things (as opposed to all other implementations, where they’re the same). This meant my GetClass implementation just plain wouldn’t work for Internet Explorer. Well, that’s no good, since that’s a basic building block of my MooTools MVC framework. Now, I could require all classes be created explicitly, like window.Task, but that makes for a very inflexible pattern. And there’s no reasonable way to explain to users why I’m requiring that.

So instead, I delved into JScript to find a way to let me iterate all global variables. But first, here’s a few ways you could declare a global variable.

var a = 1;
window.b = 2;
c = 3;
(function(global) {    
	this.d = 4;    
	global.e = 5;
})(window)

Only if you specifically set the property of the window object does it get attached. The cases that are declaring variables instead of properties get added to a Variables Host object instead. Frankly, this is awful. But we come to expect this kind of shenanigans from IE.

This causes this iteration bug:

for(var i in window) {
	i; 
	//besides the usual, we'll only get
	//b, d, and e.
  }

Thankfully, some other people have noticed this before me. Eric Lippert did much more extensive research than I needed to fix the issue. But his research assisted Thomas Frank in a way that solved my problem as well.

This should get all our global variables, but doesn’t in JScript/IE.

var x=[];
for (var i in windows){
	x.push(i)
};
alert(x.join("\"));

Well, it turns out this doesn’t work in JScript/IE at all, because non-native variables are not found when we try to enumerate the window object.

—Find your global namespace

This might not necessarily be the best solution, but using a cache, I only have to eat the execution cost once. Thomas’ solution was to search all script tags for all variable declarations. This works because even though a in my first example doesn’t get iterated through, it can still be found through a property lookup on the window global object (window['a']).

The first thing I had to do was to write a workaround for IE browsers. In IE it is possible to get the complete script source for a page by looking at the document.scripts array.

If an element has no innerHTML value, then it is an external script and we can load the source with a simple AJAX request. We then use a little bit a regular expression magic to extract every “word” from the source(s) and check if these words exists as global variables. This works suprisingly well.

Using his approach, modified to store all the “word” possibilities in a cache so I don’t have do more ajax requests, I’ve cooked up the following bit to implement a sort of indexOf (or keyOf) for the window object.

var keyOf = (Browser.Engine.trident) ? (function() {
	var xhr = function(path) {
		var request = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
		request.open('GET', path, false); //asynchronous = false
		try {
		request.send();
		} catch(e) { return null; }
		if (request.status == 500 || 
		request.status == 404 || 
		request.status == 2 ||
		(request.status == 0 && request.responseText == '')) 
		return null;
		return request.responseText;
	}
	var cash = {};
	var ie_search = function(obj) {
		var key = search(obj) || search(obj, cash); //search does a for..in loop on window.
		if(!key) {
			//IE doesn't enumerate `var a = 'x';`, so we need to explicitly
			//declare those vars on the window object
			// credit: http://www.thomasfrank.se/downloadableJS/globalVars.js
			var scripts = document.scripts,
			src = '';
			for(var s = 0, len = scripts.length; s < len; s++) {
				if(!scripts[s]._lookedUp) {
					if(scripts[s].innerHTML) {
						src += scripts[s].innerHTML;
					} else if (scripts[s].src) {
						src += xhr(scripts[s].src);
					}
					scripts[s]._lookedUp = true;
				}
			}
			var idents = src.replace(/\\W/g, ' ').replace(/(function|if|for|while|true|false|null|typeof|var|new|try|catch|return|prototype|this)/g, '').split(' ');
			for(var i = 0; i < idents.length; i++) {
				var iden = idents[i].trim();
				cash[iden] = true;
				if(!key && (iden in window) && window[iden] == obj) {
					key = idents[i];
				}
			}
		}
		return key;
	}
	return ie_search;
})() : search;

The first time you call keyOf, it will collect all the Javascript source for the page, and then store every possible identifier that was declared. On subsequent calls, only new scripts will be gotten and parsed. Since the values of the global variables could change at any time, I could only store in the cache their names. The search function tries all those names on the window object.

A definite downside I see to this method is since it uses XHR to collect the source, it wouldn’t work on scripts that aren’t from the same domain. But for my framework, since it uses a loader anyways, it’s sufficient for me.


Oct 29 2009

Select Tags in IE: innerHTML

I just wanted to document this rather frustrating bug here, so I can look it up later, and hopefully help anyone else who is running into something similar. This bug involves select tags, specifically setting their innerHTMLproperty.

I had a list of options to give to the user, and a select box would work perfectly. Since DOM methods have burned me in the past, I felt innerHTML was the safer route. It appears, that they both have safety curves like a sine and cosine graph.

The Buggery

Here’s what I thought would work of stupendously!

var options = '<option>' + item.options.join('</option><option>') + '</option>';selectEl.innerHTML = options;

It works in Firefox, Safari, Chrome, etc. Except Internet Explorer. I first noticed in IE7, and so when I went to debug (using IE8’s sweeter debugger), I noticed it even breaks in IE8.

It disregards completely the first <option> in the string. Go ahead and try it yourself. Open IE8, press F12, go to Script, and try this super simple task:

var selectEl = document.createElement('select');
selectEl.innerHTML = '<option>this one breaks</option><option>we dont work because the first one is broken</option>';
document.body.appendChild(selectEl);

Solution

It breaks even that. So I resorted to DOM methods to build my select field.

for(var i = 0; i < item.options.length; i++) {    
	var option = document.createElement('option');    
	option.innerHTML = item.options[i];    
	select.appendChild(option); 
}

Apr 21 2009

Don’t Use the DOM to Insert Flash

I didn’t have the priviledge of using SWFObject or the likes. I just needed to create a movie in a modal-like window that depended on the page you were on. So, taking a list of properties and params, I proceed to use the DOM methods to build my Object and Param tags. Well, Prototype ‘s Element methods, actually. It worked in Firefox, and partially in Safari / Chrome, and none at all in Internet Explorer 7.

So then, a little research shows that all the Javascript solutions for inserting a video object doesn’t use Element at all. It would seem that extends the HTMLObjectElement cause problems. The solution: simply write the HTML into a string, and set the innerHTML of an Element to your object.

Here’s how Mootools handles it in its Swiff class :

var build = '<object id="' + id + '"';for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';build += '>';
for (var param in params){    
	if (params[param]) 
		build += '<param name="' + param + '" value="' + params[param] + '" />';
}
build += '</object>';

Nov 19 2008

ParentNode Misbehavior in IE

I wrote a simple script to pop-up a modal-like element when clicking on an “Add” button on the page. I wanted to insert the element into the DOM on first click, and on subsequent clicks, simply reset the form. A bug popped up in IE7 (surprise) that prevented me from checking if I had inserted the element already.

What I tried to do

I performed the case check by simply checking the parentNode of the element, which obviously shouldn’t exist until it’s inserted into the DOM.

if(!this.form.parentNode)
	$('Admin').insert(this.form);
else
	this.form.reset();

As I expected, worked perfectly. Until I popped open IE7.

IE Ignores Specs

Apparently, IE7 assigns a parentNode to nodes upon creation, before it has been inserted into the DOM. Well that’s silly, because the specifications say nothing about setting the parentNode. In fact, they state the opposite:

However, if a node has just been created and not yet added to the tree, or if it has been removed from the tree, this is null .

That’s fine. Doing some debugging, I find there is indeed a parentNode. And that parentNode has a nodeType of 9, or DOCUMENT_NODE. So it makes a new, off-screen document to hold onto created Elements. Whatever.

Excusing IE

A simple modification to my if statement fixed everything, once I finally knew IE gives it a parentNode. The good part (there’s good here? ), is that it assigns the new Element a parent that only the HTML node should have, a document. So here it is modified:

if(!this.form.parentNode || this.form.parentNode.nodeType == 9) // 9 == DOCUMENT_NODE, but IE is freaking retarded. I'm ganna kill Microsoft 100 times over.
	$('Admin').insert(this.form);
else
	this.form.reset();