substr() with negative value not working in IE
Asked Answered
B

3

6

EDIT:I've changed the title, because the issue had nothing to do with IE image.load() firing - my substr() wasn't working (see accepted answer).


There's a ton of posts about making sure that you define your onload handler prior to assigning img.src in order to guarantee that the onload handler is in place in case the image is loaded from cache first.

This does not appear to the be issue in my code, since that's precisely what I have done.

Note that this script works across all other browsers, but IE 8 and lower does not trigger the onload inline function.

var i = lastSlideIndex || 1;

while(imagesQueued < MAX_IMAGES){
    var preloadImage = new Image();
    preloadImage.arrayIndex = i;
    preloadImage.onload = function(eventObj){
        debug('Image ' + this.src + ' loaded.');
        slideshowImages[this.arrayIndex] = this;
        if (this.arrayIndex > lastImageIndex) lastImageIndex = this.arrayIndex;
        triggerSlideshow(this.arrayIndex);
    }

    // add leading zeros
    var leadingZeros = "0000000".substr(-(String(MAX_IMAGES).length));
    imageNumber = leadingZeros.substr(String(i).length) + i;

    debug('preloading Image #' + imageNumber);
    preloadImage.src = fullImagePrefix + imageNumber + "." + IMAGES_TLA;

    if (++i > MAX_IMAGES) i = 1;
    imagesQueued++;
}

Any other suggestions would be deeply appreciated!

Bot answered 2/8, 2011 at 21:34 Comment(0)
F
9

Update: As commenters have pointed out (and I can't prove them otherwise for now), I removed the first suggestion.

Couple of more things to note:

onload event will not fire if the image is being loaded from cache. Try clearing your cache and retry.

Another problem is that IE doesn't like negative in substr. Use slice instead:

"0000000".slice(-(String(MAX_IMAGES).length));
Filipino answered 2/8, 2011 at 21:38 Comment(9)
No, this is simply not true. IE definitely does support "load" events on Image object instances. If it did not, after all, your suggestion wouldn't work either. Here is the jsfiddle.Bulk
I agree with Pointy. This is not true. IE (all versions) support the load handler for images. Another JSFiddle showing that to be true: jsfiddle.net/jfriend00/D59SD.Undersea
Again, you are wrong. onload will fire when the image comes from the cache as long as you have set the load handler before setting the .src property.Undersea
@Undersea now about that, I'm not so sure. See my answer.Bulk
@Pointy, not so sure about what? I use onload handlers in IE all the time on versions from IE6 to IE9 and they absolutely work if you do them right - whether the image is cached or not.Undersea
@Undersea I'll do a fiddle variation - IE (maybe just IE8) doesn't call the load handler when "src" is set in the same event loop as the handler is established, but it will call it if it's either bypassing the cache or if the "src" is set in a separate event loop.Bulk
@Mrchief: thank you for the substr hint. In fact that's what was causing the problem. Replacing with slice() did the trick and if I had only been paying attention to my debug I would have caught that it was trying to load images that didn't exist! Great catch, man. ThanksBot
@Tom: Glad it helped! There was a recent issue with someone else. HE thought adding class was not working in IE but it was due to the same issue that his code was not getting called.Filipino
"IE doesn't like negative in substr. Use slice instead:" You just solved a head-melter for me with that. Thanks :) +1Ali
T
10

Now that this question is retitled to be about substr() in IE 8, here's the quick fix to the negative index problem. Paste the following code from Mozilla Developer Network:

// only run when the substr() function is broken
if ('ab'.substr(-1) != 'b') {
  /**
   *  Get the substring of a string
   *  @param  {integer}  start   where to start the substring
   *  @param  {integer}  length  how many characters to return
   *  @return {string}
   */
  String.prototype.substr = function(substr) {
    return function(start, length) {
      // call the original method
      return substr.call(this,
        // did we get a negative start, calculate how much it is from the beginning of the string
        // adjust the start parameter for negative value
        start < 0 ? this.length + start : start,
        length);
    }
  }(String.prototype.substr);
};

This code detects the broken implementation of substr and replaces it with a compliant one.

Turgid answered 13/2, 2013 at 3:22 Comment(7)
Wicked. Thanks Josh for picking up this old thread and posting something really useful!Bot
Another option is to just replace substr with slice.Bidarka
Will it work if this.length + start < 0? It will result in calling substr with negative start argument, which seems to give wrong result.Gilchrist
@user502144 looks like MDN subsequently updated their polyfill to address that bug... just updated my answer to have their latest code.Turgid
@Turgid But what will happen if this.length === 5 and start === -7? There is only check start < 0, but it is not checked if this.length + start < 0Gilchrist
@user502144 you tell me :-) don't have ie 8 installed right now. what should happen is it starts from 0 -- it should be equivalent to .substr(0, length). Not sure what ie 8 does in practice... if it's something else, we should let the MDN guys know...Turgid
thanks man, you helped me to fix a gov.uk project's bug; I've exchanged the favour with a small code edit (jslint flagged a couple of missing semicolons)Raneeraney
F
9

Update: As commenters have pointed out (and I can't prove them otherwise for now), I removed the first suggestion.

Couple of more things to note:

onload event will not fire if the image is being loaded from cache. Try clearing your cache and retry.

Another problem is that IE doesn't like negative in substr. Use slice instead:

"0000000".slice(-(String(MAX_IMAGES).length));
Filipino answered 2/8, 2011 at 21:38 Comment(9)
No, this is simply not true. IE definitely does support "load" events on Image object instances. If it did not, after all, your suggestion wouldn't work either. Here is the jsfiddle.Bulk
I agree with Pointy. This is not true. IE (all versions) support the load handler for images. Another JSFiddle showing that to be true: jsfiddle.net/jfriend00/D59SD.Undersea
Again, you are wrong. onload will fire when the image comes from the cache as long as you have set the load handler before setting the .src property.Undersea
@Undersea now about that, I'm not so sure. See my answer.Bulk
@Pointy, not so sure about what? I use onload handlers in IE all the time on versions from IE6 to IE9 and they absolutely work if you do them right - whether the image is cached or not.Undersea
@Undersea I'll do a fiddle variation - IE (maybe just IE8) doesn't call the load handler when "src" is set in the same event loop as the handler is established, but it will call it if it's either bypassing the cache or if the "src" is set in a separate event loop.Bulk
@Mrchief: thank you for the substr hint. In fact that's what was causing the problem. Replacing with slice() did the trick and if I had only been paying attention to my debug I would have caught that it was trying to load images that didn't exist! Great catch, man. ThanksBot
@Tom: Glad it helped! There was a recent issue with someone else. HE thought adding class was not working in IE but it was due to the same issue that his code was not getting called.Filipino
"IE doesn't like negative in substr. Use slice instead:" You just solved a head-melter for me with that. Thanks :) +1Ali
B
1

Two ways of dealing with this:

  1. Add a nonce parameter to your image URLs.

    var nonce = new Date().getTime();
    
    // ...
    
    preloadImage.src = fullImagePrefix + imageNumber + "." + IMAGES_TLA + ('?_=' + nonce++);
    
  2. Set the "src" property in a different event loop.

    setTimeout(function(img, src) {
      img.src = src;
    }(preloadImage, fullImagePrefix + imageNumber + "." + IMAGES_TLA), 1);
    

By using a nonce parameter each time you fetch an image, you bypass the cache. Now, that's probably not such a great idea, so the second option gets around the problem by making sure that the "src" property is set in a separate event loop. The "load" will trigger then.

Here is an example. The code uses nonces on some images but sets the "src" for all of them in a timeout handler. As you can see, they all load (turn red).

I don't know why IE doesn't fire the "load" handler when the image is in cache, but it does when the "src" is set in a different event loop from where the image object is (otherwise) initialized.

editHere is that same fiddle, but modified to skip the timeout. In IE8, you should notice that the even-numbered images often don't get a call made to the "load" handlers.

Bulk answered 2/8, 2011 at 22:21 Comment(4)
I'm really unsure what you're seeing where you think IE8 fails to call the load handler without using the timeout and without nonce. In your jsFiddle without the nonce and without the timeout, I always see the load handler called in my copy of IE8. I have a javascript slideshow that relies on the load handler (it won't display an image if the load handler doesn't fire) and it's in use on hundreds of web sites (including my own) and I've never had a report of a problem with images loading on IE8. Something else must be going on when you see it fail.Undersea
FYI, this would be a little simpler to get to the root issue if you didn't use jQuery for the load handler portion of your examples. Since we're talking about the naked load handler, it would be nice if that's all that was involved.Undersea
Here's my pure JS fiddle: jsfiddle.net/jfriend00/YU56G that loads 6 images with onload handlers and no nonces and no use of setTimeout. I cannot get it to miss an onload handler in IE8. @Bulk - do you see failures in it in IE8?Undersea
Thanks for the suggestions @Pointy. Just a note that a delay of 1 is not recommended by Mozilla, so watch out for that (The minimum delay, DOM_MIN_TIMEOUT_VALUE, is 4 ms (stored in a preference in Firefox: dom.min_timeout_value), with a DOM_CLAMP_TIMEOUT_NESTING_LEVEL of 5ms.) Also, did you mean to use a closure as the first argument in setTimeout()?Bot

© 2022 - 2024 — McMap. All rights reserved.