JavaScript scope issue in Opera?
Asked Answered
D

2

4

I have a widget-like block of HTML+JavaScript that people can copy/paste into their HTML-page one or multiple times. That block checks if an external JavaScript file is already in the DOM, and loads it if not, something like this:

(function(){
  d = document;
  if (!d.getElementById('ex-scr')) {
    scr = d.createElement('script');
    scr.async = true;
    scr.id = 'ex-scr';
    scr.src = 'external.js';
    d.getElementsByTagName('head')[0].appendChild(scr)
  }
})();

The external JavaScript-file checks the HTML-page for instances of the widget (using getElementsByClassName) and does stuff with those instances, kind of like this;

for (var i=0;i<document.getElementsByClassName('target').length;i++) {
 document.getElementsByClassName('target')[i].style.borderStyle="solid";
}

A working example of this can be found on http://futtta.be/opera_enigma.html.

This works perfectly in Firefox (3.6 & 4b), Chrome (5 & 6) and Safari, but does not work as expected in Opera (tested with most recent version, 10.61): no matter how many 'widgets' (divs with class='target') are present, Opera only acts on the first one because apparently the nodeList only contains 1 entry (length is 1 instead of 2 or 3 or ...).

The problem goes away if in the widget's javascript I call the function to load the external script with window.onload, but I'd like my widget to activate as soon as possible (without interfering with the rest of the page, hence the asynchronous stuff).

So my questions; is there a bug in my code which Firefox, Safari and Chrome ignore? Is this a bug in Opera? How can I get Opera to behave?

Dateless answered 29/8, 2010 at 8:33 Comment(2)
Well, for starters, it should be var d = document! Less global namespace pollution, please! :) Also, remember that every time you access document.getElementsByClassName("target") it has to regenerate that list. So you should do something like for (var i = 0, a = Array.prototype.slice.call(document.getElementsByClassName("target")), len = a.length; i < len; i++) { a[i].style.borderStyle = "solid"; }Sob
thanks, solid advice, i'll keep that in mind.Dateless
T
5

The problem goes away if in the widget's javascript I call the function to load the external script with window.onload

I think that's your problem right there. When the getElementsByClassName is executed in your script file, the DOM is not yet guaranteed to be completely present. You won't be able to create reliable behaviour here without waiting until the DOMready or loaded event.

Tax answered 29/8, 2010 at 9:21 Comment(4)
that would make sense, in that sense it's even weird that ff/chrome/safari don't mind me doing that :)Dateless
@Dateless that may be down to different DOM handling / rendering / prioritization /optimization methods.Tax
yeah, i'll try to do something (opera specific) with domready, thanks!Dateless
@Dateless You're welcome, but I wouldn't trust this in any browser. You will never know for sure what results you get, e.g. on slower machines. I would use DOMReady in all browsers.Tax
A
1

You are probably running into a small timing difference between browsers: if you through DOM add a SCRIPT to the head of a document, Opera will currently wait until the script is executed before parsing the rest of the document. This is the third issue I wrote about here: http://my.opera.com/hallvors/blog/2009/03/07/websites-playing-timing-roulette

The other major browsers will keep parsing while waiting for the external script. They will run the external script when it comes in, which randomly (depending on caching, size of document, speed of connection etc.) might make it run when all the elements you want to act on are in the DOM. However, I can pretty much promise that trying to look up all elements of a certain type in the DOM before you know the full markup is parsed will cause problems for some users in any browser - what you are doing is way too sensitive to small network hiccups, connection speed differences, CPU power and all the other minor differences that affect timing while loading and parsing a web page.

Browsers give you two signals the page is ready for scripting: DOMContentLoaded event and onload. If you need to read dimensions from the document or its elements, you probably need to wait until all images and CSS are loaded (i.e. wait for the onload event), if not you can use DOMContentLoaded. jQuery gives you $(document).ready() to abstract away the differences in what "DOM is ready now" signals browsers support, other frameworks probably have equivalents.

Arraignment answered 3/9, 2010 at 3:5 Comment(3)
(BTW Opera will try to align timing on this point with the other browsers, with the caveat that "when the external script loads" is and will remain a random factor.)Arraignment
based on this and pekka's comments i'm already re-rewriting my script to wait for domcontentloaded. concerning the way opera treats dynamically added scripts; if it waits for the script load, parse & execute, that means that it is currently impossible to do asynchronous script loading in opera (as e.g. google does with their analytics code)?Dateless
It's not at all impossible, works fine as long as you keep in mind that while parsing, the asynchronous script may not see the full DOM. If you add those scripts to the DOM from or after the DOMContentLoaded event, you'll have no problems reading from the DOM.Arraignment

© 2022 - 2024 — McMap. All rights reserved.