For an internal project I've been working on I have to provide embeddable versions of internal tools for deployment on remote partner sites.

I achieved this in my framework through, what I call, output format filters. Let's say you have an action "cloud" in a controller "markets". The URL to access this resource in its full glory would be:

http://www.domainname.com/markets/cloud

Pretty standard stuff. Let's say we want to offer an embeddable version of this resource. I can append an "output format filter" on this resource:

http://www.domainname.com/markets/cloud/embed

Alright, again, pretty standard stuff. How on earth does this relate to dynamic javascript SCRIPT insertion?

On the remote partner site, I ask them to place the following HTML fragment in the desired location they want the resource to appear:

<script language="javascript" type="text/javscript" src="http://www.domainname.com/markets/cloud/embed"></script>

One line of HTML.

How does this work under the hood? It simply takes the view's content and prepares it as a valid javascript document.write. It also specifies certain HTML headers, notably:

Content-Type: application/x-javascript

function prepJS($js)
{
	// prepare the output as valid javascript
	$js = str_replace("\\", "\\\\", $js);
	$js = preg_replace("/[\r\n]+/", '\n', $js);
	$js = str_replace('"', '\"', $js);
	$js = preg_replace("/<script/i", '<scr"+"ipt', $js);
	$js = preg_replace("/<\/script/i", '</scr"+"ipt', $js);

	return $js;
}
document.write("<?php echo prepJS($viewContent); ?>");

It should be fairly obvious what the function does. It escapes double quotes, adds slashes, and replaces newlines and carriage returns with their inline equivalent. Additionally and VERY important, it splits SCRIPT tag elements (if your view had inline SCRIPT's) into SCR"+"IPT so that the browser treats these as pure strings and doesn't cause JavaScript parsing errors.

What if the resource you are embedding depends upon the availability of certain JavaScript libraries or external source files? The first response usually is, myself included, to simply document.write more SCRIPT tags!

document.write("
<?php prepJS('<script type="text/javascript" language="javascript" src="http://www.domainname.com/path/to/jquery.js"></script>'); ?>
");
document.write("
<?php prepJS('<script type="text/javascript" language="javascript" src="http://www.domainname.com/path/to/morejs.js"></script>'); ?>
");
document.write("<?php echo prepJS($viewContent); ?>");

The problem is of course that in IE, there's no telling what order those SCRIPT tags will get executed. Furthermore, if your resource depends on them, they need to be executed before you document.write the content.

My solution to this problem was two-fold. For other reasons not worth going into here, it's generally a good idea to concatenate all your JavaScript source files into one larger file, minify (if desired), cache, and serve the result. Taking advantage of this we can reduce the above to a single additional SCRIPT insertion. Tack this snippet onto the end of that file:

if(typeof(embedReady) != 'undefined') {
	embedReady();
}

And we come up with this:

document.write("
<?php prepJS('<script type="text/javascript" language="javascript" src="http://www.domainname.com/path/to/cached_combined_js.js"></script>'); ?>
");
var embedReady = function() {
    document.write("<?php echo prepJS($viewContent); ?>");
}

Now we're only concerned with a single SCRIPT load. We've safely placed our document.write of content inside a function that's called at the end of our dependency SCRIPT. In an elegant, browser agnostic fashion our resource will have it's dependencies loaded before it executes.

Yay?

Related posts:

  1. PHP jQuery AJAX Javascript Long Polling
  2. Real-time “AJAX” JavaScript Progress Bar
  3. JavaScript roundTo Nearest Thousandth, Hundredth, Tenth, *
  4. 37Signals and PHP?
  5. PHP Sessions on localhost