slayeroffice - web experiments gone horribly awry

09.26.2005 - Safari & createElement("script")

I was browsing through Safari's bugzilla and found this extremely exciting bit of information:

"Here's a very simple workaround. Set the onload and src attributes of the script *before* inserting it into the document:"

That's right - we can now get Safari to source scripts created with createElement! I have verified this to work in Safari 2.0.1 with MODIv2 and the original MODI.

The favelets themselves dont work terribly well (yet -- which is why I haven't updated the links), but that's beside the point. This is wonderful news - and not just for favelets either. This is gonna be a great boon for web development as a whole.

Just think - no longer do you have to trudge in a slew of scripts that you might not even need -- now you can dynamically pull in scripts when the functionality they provide is requested without having to worry about Safari!. Oh, happy day.

For those interested, the actual change for MODIv1 is as follows:

The Old Way:

javascript:void(z=document.body.appendChild(document.createElement('script'))); 
void(z.language='javascript');
void(z.type='text/javascript');
void(z.src='http://slayeroffice.com/tools/modi/modi.js');void(z.id='modi');
The New Way:
javascript:void(z=document.createElement('script'));
void(z.language='javascript');
void(z.type='text/javascript');
void(z.src='http://slayeroffice.com/tools/modi/modi.js');
void(z.id='modi');
void(document.getElementsByTagName('head')[0].appendChild(z));

Time permitting, a progression of updates will start coming out for the favelets over the coming weeks.

Nice spotting :).

Just one trick I thought I'd suggest -- I picked this up myself recently. For favelets, try instantiating and running a new anonymous function like this:

javascript:(function(){ var abc = 123; foobar(abc); whatever(); })();

That removes the need for ensuring that the last statement is void and also allows you to use "var" with impunity, as it's locally scoped.
Posted by Angus Turnbull on September 27, 2005 @ 12:14 am


Just curious, is the language property actually needed? AFAIK it doesn't mean anything to modern browsers.
Posted by Stephen Clay on September 27, 2005 @ 12:50 am
@Angus: I'd seen that done in GM scripts - it hadnt occured to me to use that method on a favelet. Nice.

@Stephen: Nah, its not. Just one of those legacy things from years ago that proliferated through my lazy copy/pasting :)
Posted by Steve on September 27, 2005 @ 3:52 am


Setting attributes before inserting the node into the document tree is faster than setting the attributes afterwards. So even if it works the other way round, one should do it in this order. I don't know how much faster it is, though.
Posted by Martin on October 12, 2005 @ 10:14 pm
What about Omniweb and Konqueror? Will this apply to them too since these browsers cannot append Js as well?
Posted by Jo on October 16, 2005 @ 10:28 am
Ohh, nice!

I was gonna get to work figuring out why my script wasn't working in Safari tomorrow, so you've saved me some time. :) Thanks alot. :D I'm terrible at bugtesting on Mac's, just can't comprehend em, you know.
Posted by Jonas Pihlström on November 10, 2005 @ 9:39 pm


That's good. But i have a question, if the src script had a "document.write" open a new window on firefox, but IE, Opera and others works fine. Do you know why?

For example, i try load this http://app.feeddigest.com/digest3/YCJVBTOK96.js , on IE, Opera works fine, but Firefox open a new window.

Best regards

Mario
Posted by Mario Brhemenz on November 16, 2005 @ 11:46 pm


Did you actually test "the workaround"? I tried it and it doesn't fire onload event. (Safari 2.0.2) :-(

index.html

----- cut here ----

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html

PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>

<head>

<title>Hello World from External Javascript</title>

<script type="text/javascript">

var

oScript = document.createElement('script');

oScript.type = 'text/javascript';

oScript.src = 'test.js';

oScript.onload = oScript.onreadystatechange = scriptLoaded;

document.getElementsByTagName('head')[0].appendChild(oScript);

function scriptLoaded() {

helloWorld();

}

</script>

</head>

<body>

<h1>Hello World Example</h1>

Did we get "Hello world" alert?

</body>

</html>

----- cut here ----

test.js

----- cut here ----

function helloWorld() {

alert("Hello World!");

}

----- cut here ----
Posted by Boris on November 17, 2005 @ 11:39 am


Of course I "actually tested it" - I wouldn't have posted about it if I hadn't.

Take the "new way" code snippet in the example and use that. I cant see why you would need to fire a method from the onload event of a script object anyway (i half suspect thats the issue as a few quick experiments shows Safari to not fire that event) - just call your init method inline from the bottom of the script if you need a function executed onload.

Go here: http://slayeroffice.com/tools/js_eval

And paste this in:

x = document.createElement("script");

x.type = "text/javascript";

x.src = "http://slayeroffice.com/scripts/page_methods_v4.js";

x.onload = function() { alert("woot!"); }

document.getElementsByTagName("head")[0].appendChild(x);

j = setOldTitles;

Firefox will alert, but Safari won't. Both source the script and write out the function from page_methods_v4.js - so, it works.
Posted by Steve on November 17, 2005 @ 1:34 pm


Yes, it is about onLoad. It is not triggered after script is loaded.

And no, you can't call it manually ("inline" as you say) at the bottom of the script, because loading of the external script is asynchronous operation. If, in my example, I would add

helloWorld();

after

document.getElementsByTagName('head')[0].appendChild(oScript);

helloWorld function would not be called because it is still not existing (error would be reported in Javascript console)

Cheers!
Posted by Boris on November 18, 2005 @ 11:39 am


I didnt mean call it from the first script, I meant call it from the bottom of the script you are loading like i do in nearly all of my favelets.

Have a look at any of the source of any of them that have an init() method and it'll be called as the last line in the script. No need to attach it to an event.
Posted by Steve on November 18, 2005 @ 2:11 pm


I see now what do you mean. Seems fine, though I would be nice if onload would work.

Nice tool this MODI bookmarklet, great idea, fine job!

Regards...
Posted by Boris on November 18, 2005 @ 4:41 pm


I've just compiled it, end current development ("nightly build") version of Safari does trigger onload event. Nice!
Posted by Boris on November 21, 2005 @ 10:05 am
Ah, very cool!
Posted by Steve on November 21, 2005 @ 1:36 pm
Here's my take on making a compact favelet/bookmarklet with an external source...

javascript:

(function(){

with(j=(d=document).createElement(s='script')){

type='text/'+(language='java'+s);

src='http://slayeroffice.com/tools/modi/modi.js';

}

d.body.appendChild(j);

})()

:ca_redwards.
Posted by ca_redwards on November 23, 2005 @ 10:50 pm


Comments have been closed for this post.