PDA

View Full Version : IE 8 RC1 hopeless?



jscheuer1
02-25-2009, 07:45 AM
I've been working on a modular jQuery marquee for text and/or images. Just about everything went along fine until I decided to test it in IE 8 RC1. I wanted to make sure that images were loaded before trying to determine the length of a marquee that contained one or more images. I devised a simple and fairly standard construct (which works in all other modern browsers, including IE 7):


function finaltouch(i){
var ims = m[i].find('img'), c = 0; if(ims[0]){
ims.each(function(){
if(this.complete) c++;
});
if(c < ims.length){
setTimeout(function(){finaltouch(i);}, 300);
return;
}
}
w[i] = m[i].width(); t[i] = Math.floor((m[i].parent().height() - m[i].height()) / 2);
m[i].css('top', t[i]).next('div').css({top: t[i], left: w[i]}).parent().css('visibility', 'visible');
while(m[i].parent().width() > w[i] - mq[i].inc)
m[i].parent().css('width', (isNaN(cw[i][0])? w[i] - mq[i].inc : --cw[i][0] + cw[i][1]));
runit(i);
}

The construct is highlighted, and the line that I think IE 8 is barfing on is in red. In that line, IE 8 just doesn't seem to grasp what i is.

codeexploiter
02-25-2009, 08:07 AM
Hi john is there any link to view this issue?

jscheuer1
02-25-2009, 09:55 AM
Well, right now I'm using the emulate IE 7 meta tag to deal with the problem, here:

http://home.comcast.net/~jscheuer1/side/jq_marquee/

You can see the exact same page without the meta tag misbehaving (at least in my IE 8), here:

http://home.comcast.net/~jscheuer1/side/jq_marquee/index_bad.htm

I did a test, and it appears that this is some wierd interplay between the script and the browser, as IE 8 can handle this:


function test(i){
i++;
if (i < 10){
alert(i);
setTimeout(function(){test(i);}, 300);
}
}
test(0);

just fine. But I also found that putting an alert in the other code likewise 'resolved' the problem.

Another screwy thing about IE 8, this:


<div style="position:relative;overflow:hidden;border: 2px solid black;height:2em;">
<div style="position:absolute;top:0;left:0;width:2000px;border: 2px solid red;">
Hi
</div>
</div>

causes an empty horizontal scroll bar at the bottom of the page. That one's easier to fix, just wrap it in another div with overflow:hidden. But the first wrapper should make that unnecessary, and does so in all other modern browsers. If it is a matter of IE 8 following a stricter standard, then I would think that the additional wrapper shouldn't fix it, and that the scroll bar shouldn't be empty.

jscheuer1
02-25-2009, 05:25 PM
After more testing I've found that IE 8 just seems to have intermittent problems with that construct (it also does this elsewhere in the script if it runs long enough), forgetting initialized values, sometimes of the function name or of the variable argument passed with it. Even with IE 7 emulation, it happened, though much less often. I thought this might be due to some interplay with jQuery though, as I've not been able to get it to barf on this in a very simple example. I'm also thinking that naming the passing function which can remain anonymous may be a factor, but it is valid and supported by all other modern browsers. I did port this to a script using only ordinary javascript, and I'm not sure if IE 8 barfed on this there. It did barf on the script assigned styles though, and in a different way than with overflow (though perhaps related) like I mentioned before and that I haven't been able to definitively track down yet, causing all but the first marquee to hop up and down rapidly. Once again IE 7 emulation or any other modern browser and this was a non-issue.

I'm quite frustrated at all of this but will test more when I have the time. In the mean time I am tending toward a diagnosis of IE 8 (the RC1 version of it anyway) as hopeless.

Twey
02-25-2009, 06:10 PM
Well, it is still in early development. Still, that's a pretty serious bug to let in at any stage.

jscheuer1
02-26-2009, 01:30 AM
Well, it is still in early development.

Knowing your general attitude to most things MS, that's pretty charitable. I see where you are coming from. But - MS announced an anticipated official roll-out earlier than now, and have been pseudo promoting the various beta and this RC version as 'official', not going so far as to state as much, but by virtue of how they are presented for download, misleading many, many people. I've even just recently heard that Vista users that install 8 are finding it very hard to find a rollback installer for 7.

I was sick and tired of hearing about 8, so I devised a plan to create a testing environment. Things didn't work out exactly as I had planned, but at least my primary machine was spared and I have a testing environment on an old LAN node over which I have complete control.

I put up with all this crap when 7 came out, but this time I think MS is trying to have it both ways even more than usual - leading people to believe that 8 is a fait accompli, while at the same time not supplying a stable official release, all the while dicking around with development that should have been conducted in private, only because they feel threatened by other superior existing products.

jscheuer1
02-26-2009, 08:39 AM
I'm still exasperated with 8, but I have found a workable solution, and it's kind of funny how it happened. I was playing around with my pure javascript version, and noticed that 8 had no problem with the aforementioned construct as used in it, just that jumpiness business, which was absent from the jQuery version. So I decided to port my port back to jQuery one line at a time to see where the jumpiness was coming from, it was here:


tag.innerHTML = '';

This is used after the innerHTML is saved, trimmed of leading and trailing whitespace and prepended with a single nbsp entity, and before the markup for the marquee is built up inside the existing div tag, to clear that tag out. In jQuery, this is done:


$(tag).html('');

and I have no idea precisely what code they use for that. But when I switched to that, the jumpiness went away. I then undid all the other modifications, and it was still good. So I figured, how about:


while(tag.lastChild) tag.removeChild(tag.lastChild);

instead? That worked fine, so I now have a completely non jQuery version that works in all modern browsers, including IE 8 RC1 in strict mode.

I've learned a few things here, one about the overflow issue in IE 8, also that the coding style I was using with jQuery was a bit sloppy even though it worked except in IE 8*. And that IE 8 does something differently than all other modern browsers when you set an element's innerHTML to an empty string, I'm not sure what though or how that could result in the jumpiness I observed.

I can now probably port back to jQuery if I like. But without all that extra code, the plain javascript version is leaner and faster in totality, if a bit longer than the version that uses jQuery.


*I think that jQuery to a degree encourages a style of coding that is sloppy because of its innate nature of turning almost everything into an array while co-opting the this keyword. But hopefully I've learned my lesson on that score. To get around that, code for jQuery (if it is at all complex) should probably be at least mapped out (outlined) first with function groups that would work with ordinary javascript, and then be spiced up with jQuery's more powerful and succinct methods.

Twey
02-26-2009, 03:54 PM
It does use innerHTML, but it does an awful lot more, too. It's not possible to tell by looking at it what triggers the correct behaviour.

Nobody knows what will happen when you use innerHTML — it's non-standard, as bad as using document.all or <layer> in your HTML.
So I figured, how about:
while(tag.lastChild) tag.removeChild(tag.lastChild);That should have been your first resort, not your last. The idiom goes:
while (tag.hasChildNodes())
tag.removeChild(tag.firstChild);
*I think that jQuery to a degree encourages a style of coding that is sloppy because of its innate nature of turning almost everything into an array while co-opting the this keyword. But hopefully I've learned my lesson on that score. To get around that, code for jQuery (if it is at all complex) should probably be at least mapped out (outlined) first with function groups that would work with ordinary javascript, and then be spiced up with jQuery's more powerful and succinct methods.Why do you consider that sloppy? I think the repeated uses of innerHTML without any fallback are far sloppier. The use of the context argument to pass values is really perfectly acceptable — it's just another argument, after all.

jscheuer1
02-27-2009, 12:36 AM
I have no last resort. It's like, when you lose your keys (to your house or car, for example), you always find them in the last place that you look.

As for the other issue, why do I consider what sloppy? Have you written much code for jQuery? In any case, I may not have been clear about that, consider:


$('.marquee').each(function(i){
do something(s) here one at a time with each element that has a class name of marquee,
while you are at it, the index of that element will be i, and the element
itself will be this, extensible as another array object with one member but many
properties by doing $(this).
});

Now the this keyword cannot be easily used to refer to an object other than the element, and i is reserved for the index of that element in an array, so you will be tempted to use it (i) to differentiate processes which can be better done with a constructor function or object of your own devising.


* javascript:alert($(document).html)

outputs:


function (E) {
return E === g ? this[0] ? this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : null : this.empty().append(E);
}

Twey
02-27-2009, 01:28 AM
Now the this keyword cannot be easily used to refer to an object other than the elementIt can't anyway, inside a nested function. It's one of the flaws of JS' OO model, but in this case it's that or nothing (or, more likely, window). There's an idiom that goes jQuery('.marquee').each((function(that) { return function(element) { /* ... do stuff ... */ }; })(this)); and refer to the old this as that (or self; personally I like to use me). You don't have to call the element i: you can call it element, mother, or piggywinkle, it doesn't care.

Note that this[0].innerHTML.replace won't always be defined; on some browsers innerHTML isn't a string, and requires a .toString() before using any string methods.

jscheuer1
02-27-2009, 04:56 AM
This (pun intended) is easy:


function Something(el){
this.el = el;
this.somethingelse();
}

Something.prototype.somethingelse = function(){
alert(this.el.id);
}

new Something(document.getElementById('bob'));

It also helps tremendously in organizing code, and can be achieved in other ways, none of which work all that well when this is already being used. In jQuery you can name the index anything you like, but the element itself is always this, whereas in the above this refers to the instance of Something().

Twey
02-27-2009, 07:48 PM
All that you say is true. But there is still no issue, since we can rebind a this.

The above code does not have anything to do with jQuery; am I missing something?

jscheuer1
02-28-2009, 12:08 AM
Maybe, maybe not. It could depend upon your point of view, or other factors. I'm not trying to convince you to code properly or to follow best practices. I more or less trust you on that. What I'm trying to say is that the structure of jQuery (and other libs), in the hands of a novice (and even occasionally a journeyman or expert) encourages sloppy coding.

I've seen how one can break it out, using jQuery (at least) in a prototypical (not prototype.js) framework. It limits (at least without some workaround(s)) some of the more advanced features of jQuery though, so one might be tempted to find another way.

I know from experience that you often reply that it should be done this way or that way, generally a more standard or extensible way. But I'm not talking about you. I'm talking about how folks who are just plodding along, perhaps even at a fairly advanced level, will try to adapt to a given library.

I'm frequently amazed at both your facility for and understanding of language (human and computer) coupled with your apparent lack of understanding of how special that is.

jscheuer1
03-03-2009, 04:44 AM
Got another buggy thing about IE 8. It will automatically switch to compatibility mode and reload the page if it sees certain things in the code. These things as far as I know, whatever they are, are undocumented and there is currently no way I know of to debug a page for this behavior, as a valid HTML 4.01 Strict page can exhibit this behavior. There is a tag to force IE 8 standards, that will prevent it, but I'd prefer knowing what the cause is, and at least have the option of changing the markup to prevent it. But, if anyone wants to know the tag:


<meta http-equiv="X-UA-Compatible" content="IE=8">

At the moment, if you know your page will work in IE 8 Standards mode, it might just be prudent to use this tag, as the server, as well as MS's 'blacklist' can still throw your page into compatibility mode if this tag is not specified.

Twey
03-03-2009, 05:10 AM
No, I'm sorry, I'm still not understanding what 'sloppy' methods jQuery encourages :-\ Could you perhaps give a 'before and after' for comparison, with your preferred 'neat' mode, and then what jQuery encourages you to do instead?


I know from experience that you often reply that it should be done this way or that way, generally a more standard or extensible way.
Yes... I feel somehow that it's wrong, though. I fear I'm becoming too pedantic, and rejecting code styles not my own. I occasionally have to stop myself hyper-correcting things down to tiny variations in spacing. This is particularly ironic as I've recently discovered the most readable formatting for advanced ECMAScript to be a combination of Java-style and Lisp pretty-printing...


I'm frequently amazed at both your facility for and understanding of language (human and computer) coupled with your apparent lack of understanding of how special that is.
Ah, it comes from not having a life :p Truly, though, it's just like Forth: it's all very simple if you just follow it through steadily.


the server, as well as MS's 'blacklist' can still throw your page into compatibility mode if this tag is not specified.
Yes, it's quite ridiculous. I've never before heard of 'non-standard by default'.

jscheuer1
03-03-2009, 05:24 AM
Well, I've just submitted a non jQuery script that I think is well organised:

http://home.comcast.net/~jscheuer1/side/thecrawl/

You can view the script source:

http://home.comcast.net/~jscheuer1/side/thecrawl/thecrawl.js

Here's an earlier version using jQuery (I discarded it for several reasons, not just the jQuery 'sloppiness'):



/* jQuery Marquee Script (c)2009 John Davenport Scheuer
as first seen in http://www.dynamicdrive.com/forums/
username: jscheuer1 - This Notice Must Remain for Legal Use
*/

///////////////// DO NOT EDIT /////////////////

function marqueeInit(config){
marqueeInit.ar.push(config);
}
marqueeInit.ar = [];
(function marqueewrapper($){
var dopreload = true;
/*@cc_on @*/
/*@if(@_jscript_version >= 5)
dopreload = false;
@end @*/
$('head').append('<style type="text/css">.marquee{visibility:hidden;}<\/style>');
var mq, cw = [], m = [], w = [], s = [], t = [], req1 = {position: 'relative', overflow: 'hidden'},
req2 = {position: 'absolute', left: 0, whiteSpace: 'nowrap'}, defaultconfig = {
style: { //style object for this marquee container (use quotes on both sides of the : as shown)
'height': '1.6em',
'font-family': 'sans-serif',
'margin': '0 auto'
},
inc: 2, //speed - pixel increment for each iteration of this marquee's movement
pause: true //pause onmouseover (unquoted true or false)
};
$(marqinit);
function marqinit(){mq = marqueeInit.ar; $('.marquee').each(marq);}
function marq(i){
if(!mq[i]){
mq[i] = {}; $.extend(true, mq[i], (i? mq[0] : defaultconfig));
}
var u = mq[i].style.width? mq[i].style.width.split(/\d/) : ['']; m.push($(this));
cw[i] = mq[i].style.width? [parseInt(mq[i].style.width), u[u.length - 1]] : ['a']; s[i] = mq[i].noAddedSpace? '' : '&nbsp;';
m[i].html($.trim(m[i].html())).prepend(s[i]).wrap(document.createElement('div')).parent().css(mq[i].style).css(req1).hover(
function hoverover(){
mq[i].stopped = mq[i].pause;
},
function hoverout(){
mq[i].stopped = false;
}
).addClass('marquee' + i).wrap(document.createElement('div')).parent().css('overflow', 'hidden')
.find('img').css({display: 'inline', verticalAlign: 'middle'});
m[i].removeClass().attr('id', '').css(req2).clone(true).insertAfter(m[i]);
var ims = m[i].find('img'); if (ims.length && dopreload)
ims.each(function preload(i){
var im = new Image(); preload.c = preload.c || 0;
if((typeof this.complete != 'boolean' || !this.complete) || (i == ims.length - 1 && preload.c < ims.length - 1)){
im.onload = function incc(){
if (++preload.c == ims.length)
setup();
};
im.src = this.src;
}
else preload.c++;
if (preload.c == ims.length)
setup();
}
); else setup();
function setup(){
if(m[i].data('setup')) return;
m[i].data('setup', true);
w[i] = m[i].width(); t[i] = Math.floor((m[i].parent().height() - m[i].height()) / 2);
m[i].css('top', t[i]).next('div').css({top: t[i], left: w[i]});
while(m[i].parent().width() > w[i] - mq[i].inc)
m[i].parent().css('width', (isNaN(cw[i][0])? w[i] - mq[i].inc : --cw[i][0] + cw[i][1]));
runit(i);
}
}
function runit(i){
if(mq[i].stopped || mq[i].stopMarquee){
setTimeout(function retry(){runit(i);}, 300);
return;
}
if(-parseInt(m[i].css('left')) >= w[i])
m[i].css('left', parseInt(m[i].next('div').css('left')) + w[i]);
if(-parseInt(m[i].next('div').css('left')) >= w[i])
m[i].next('div').css('left', parseInt(m[i].css('left')) + w[i]);
m[i].css('left', parseInt(m[i].css('left')) - mq[i].inc).next('div').css('left', parseInt(m[i].next('div').css('left')) - mq[i].inc);
setTimeout(function goagain(){runit(i);}, 30 + (mq[i].addDelay? mq[i].addDelay : 0));
}
})(jQuery);

Twey
03-03-2009, 10:10 AM
Sorry, I was kind of hoping for a more specific case (and to be honest, the lack of formatting renders that very difficult to read for me; perhaps it's just because I'm tired at the moment).

jscheuer1
03-03-2009, 12:13 PM
OK, let's go back to my simple example but iterate a set of classed elements:


function Something(el){
this.el = el;
this.somethingelse();
}

Something.prototype.somethingelse = function(){
alert(this.el.id);
}

var b = document.getElementsByClassName('bob');
for(var i = 0; i < b.length; ++i)
new Something(b);

In jQuery, the more tempting syntax I'm referring to could be something like:


function something(){
somethingelse(this);
}

function somethingelse(el){
alert(el.id);
}

$('.bob').each(something);

But we have lost a prototypical instance, and its meaning of this. We will have the index if we catch it in something(i) and pass it, and that can make up for a lot, but doesn't quite equal the prototypical instance we've lost.

Now I know it can be written:


function Something(el){
this.el = el;
this.somethingelse();
}

Something.prototype.somethingelse = function(){
alert(this.el.id);
}

$('.bob').each(function(){new Something(this);});

But it's so tempting to do it the other way.

Twey
03-03-2009, 10:20 PM
I'm not sure I'd really say that it's tempting to do that... removing the 'new' operator makes quite a big difference to the code. It's similar to saying that it's tempting to write jQuery(".bob").each(function() { foo(-this); }) as jQuery(".bob").each(foo). They're just clearly different — there's no temptation to completely change the code.

We can wrap that, though:

Function.curry = function(f, l, a) {
l = (typeof l === "number" ? l : (f.length || 0));
a = a || [];

return function() {
var m = a.concat(Array.prototype.slice.call(arguments));
return m.length < l ? Functional.curry(f, l, m) : f.apply(this, m);
};
};

var Operator = {
'new' : function(ctor) {
var a = Array.prototype.slice.call(arguments, 1);

switch (a.length) {
case 0: return new ctor();
case 1: return new ctor(a[0]);
case 2: return new ctor(a[0], a[1]);
case 3: return new ctor(a[0], a[1], a[2]);
case 4: return new ctor(a[0], a[1], a[2], a[3]);
case 5: return new ctor(a[0], a[1], a[2], a[3], a[4]);
default: throw new Error("Too many arguments; add more.");
}
}
};

// ...

jQuery(".bob").each(Function.curry(Operator['new'], 2, [Something]));Yes, that Operator['new'] code is possibly the most hideous thing since the swamp monster. It's like that because there is no standard way of applying 'new' to an arbitrary function application, so we can't use call() and apply() as we usually would. In SpiderMonkey and KJS, we can do this:
var Operator = {
'new' : function(ctor) {
var r = { __proto__ : ctor.prototype };
ctor.apply(r, Array.prototype.slice.call(arguments, 1));
return r;
}
};
... which is particularly awesome, since it fixes just about all the problems with Javascript's OO system in one fell swoop by eliminating the magic of 'new' and allowing us to use prototypical OO as prototypical OO was meant to be used. However, this isn't supported in Opera, and I'd be surprised if IE liked it, too. :( Maybe some day.