The startup time of Java applets isn't all that great. If you place an applet directly on your front page, some users may not like it. Caspian from puppygames.net removed the (awesome) Puppy Invaders game for this very reason. It's a shame, really. So, what can be done about that?
The issue is the direct start of the Java VM without any user interaction or any indication of what will happen. Apparently there are two options:
The former is pretty straightforward: display some fancy button and open the game page in the browser or in a popup. If you use a popup be sure that the link still works if JS is disabled.
<a href="nojs.html" onclick="yourPopupFunction();return false;">...</a>
If JavaScript is enabled the "return false" bit, will eat the event, which means that the usual link isn't activated. And if JavaScript is disabled href stuff works as usual. Also the link can be still opened in a new tab either way.
However, adding the applet later on isn't that easy. I came up with three different methods to achieve the desired effect. If you aren't really interested in the details jump to the last preferred method, which offers the most robust behavior.
My first idea was to toggle the display mode of the placeholder (inline->none) and the applet (none->block). That can be done with this little JavaScript function:
function showhide(show,hide){
document.getElementById(hide).style.display='none';
document.getElementById(show).style.display='block';
}And a link like:
<a id="placeholder" href="nojs.html" onclick="showhide('applet','placeholder');return false;">...</a>All would be great now, if IE6 wouldn't start the Java VM right away - ignoring the fact that the applet isn't displayed initially. It does work fine with Firefox and Opera tho.
Toggle Demonstration: applet_toggle.html
You can inject an applet into the DOM with a few lines of JavaScript:
function swap(code,codebase,width,height,alt){
document.getElementById('gamebox').removeChild(document.getElementById('placeholder'));
var a=document.createElement('applet');
a.setAttribute('code',code);
a.setAttribute('codebase',codebase);
a.setAttribute('width',width);
a.setAttribute('height',height);
a.setAttribute('alt',alt);
document.getElementById('gamebox').appendChild(a);
}If the user now clicks a link like:
<a id="placeholder" href="nojs.html" onclick="swap('TestApplet.class','.','200','300','demo applet');return false;">...</a>The element with the id 'placeholder' is removed and a new applet element is added. Should work fine, right? Well, Opera 9.23 won't load the applet for some reason. All you get is some "Loading Applet" message and that's it. If anyone finds out the reason behind it, please let me know.
As workaround you can detect Opera and open the "nojs" page (the page which is opened if the link is opened in a new tab or if JavaScript is disabled).
function swap(code,codebase,width,height,alt){
if(window.opera){
var a=document.getElementById('placeholder');
window.location=a.getAttribute('href');
}else{
document.getElementById('gamebox').removeChild(document.getElementById('placeholder'));
var a=document.createElement('applet');
a.setAttribute('code',code);
a.setAttribute('codebase',codebase);
a.setAttribute('width',width);
a.setAttribute('height',height);
a.setAttribute('alt',alt);
document.getElementById('gamebox').appendChild(a);
}
}DOM Injection Demonstration: applet_dom_inject.html
Well, that works, but I wasn't completely satisfied. Which leads us to option number 3.
Update:
As one reader pointed out: Opera will happily load the applet if you add it through innerHTML:
function swap(code,codebase,width,height,alt){
if(window.opera){
var gamebox=document.getElementById('gamebox');
gamebox.innerHTML='<applet code="'+code+'" codebase="'+codebase+'" width="'+width+'" height="'+height+'" alt="'+alt+'"></applet>';
}else{
document.getElementById('gamebox').removeChild(document.getElementById('placeholder'));
var a=document.createElement('applet');
a.setAttribute('code',code);
a.setAttribute('codebase',codebase);
a.setAttribute('width',width);
a.setAttribute('height',height);
a.setAttribute('alt',alt);
document.getElementById('gamebox').appendChild(a);
}
}With this new workaround it's the new preferred method. It behaves very well and you only need one extra document, whereas the last method required two. Additionally there is less fiddling with CSS involved.
DOM Injection Demonstration with better Opera workaround: applet_dom_inject2.html
This method is similar to the previous one. However, instead of injecting an applet element we'll use an iframe, which in turn references the applet.
The injection method now looks like:
function swap(src,width,height){
document.getElementById('gamebox').removeChild(document.getElementById('placeholder'));
var a=document.createElement('iframe');
a.setAttribute('src',src);
a.setAttribute('width',width);
a.setAttribute('height',height);
a.style.border='0';
a.style.overflow='hidden';
document.getElementById('gamebox').appendChild(a);
}And the link looks like:
<a id="placeholder" href="nojs.html" onclick="swap('applet_iframe_applet.html','200','300');return false;">...</a>The only drawback of this method is the requirement of yet another (X)HTML document. Giving us a total of three different documents for a single applet. Those are: the page itself, the content of the iframe and the alternative document for open in new tab/no JavaScript. Well, in theory the "nojs" and the page of the iframe can be identical. However, usually that's not feasible, because the document shown in the iframe needs to be very minimalistic. There is no margin or padding, which means it would look pretty dull when shown in a full browser window. Additionally iframes are a pretty dated technique.
On the flip side those extra documents are brain dead enough for automatic generation via PHP, other server-sided stuff or your build process.
Note: The styling for the last method requires some attention. Refer to the test package if you have problems with the border or scrollbars of the iframe.
IFrame Injection Demonstration: applet_iframe.html
The Complete Test Package: delayed_applet_loading.zip (56kb)
Update 07/08/25: The links to "nojs.html" were wrong (should be "applet_nojs.html"). I updated the demos and the test package. Sorry for any inconvenience.
Update 07/08/26: I changed the structure of the test package. Every demo is now in it's own directory, which contains all required files. There is also an index page now. Additionally it also contains the improved injection method with innerHTML fallback for Opera.
Comments
Great article!
Great article!
king - Noble Master Games
Opera
It seems to work in Opera if you use innerHTML:
var gamebox = document.getElementById('gamebox');
gamebox.innerHTML = '<applet code="TestApplet.class" codebase="." width="200" height="300"></applet>';
Tested with Opera/9.23 (X11; Linux i686; U; en)
Interesting
Thanks for the pointer. :)
Well, innerHTML isn't part of the specs, but using it as an Opera specific workaround should be fine.
I'll update the article later.Updated.
Nice!
Thanks for the article, that's really great. BTW, what will happen with applet if you'll try to remove a parent DOM element?
Juriy
Huh?
Nothing will happen to the applet if it isn't a child of that parent. If it is it will be removed from the DOM, but I'm not sure if its stop and destroy methods will be triggered.
If you're interested in this aspect you can just modify the example. The test applet already prints debug messages for all 4 default hooks. Enable the Java console (via the Java control panel) if you want to see em.
wait and Prototype
I use javascript to wait a little before loading the applet (if you don't want user input like pressing a button)
Something like this:
<script type="text/javascript">
//<![CDATA[
try {
setTimeout(function() {
Element.update("applet", "<applet width=\"670\" height=\"480\" code=\"...\"></applet>");
}, 1000);
} catch (e) { alert('Error loading applet:\n\n' + e.toString()); throw e }
//]]>
</script>
Element.update is a Prototype code but is easily replacable in case you don't want to use that library
Post new comment