Monday, December 19, 2011

A Javascript script with query string parameters?

TopUp is one of the more popular so-called "lightbox" libraries that allows you to create Web 2.0 popups for tasks such as viewing full resolution images and playing videos. It works like this: Instead of opening a new browser window, like traditional popups do, TopUp uses Javascript to modify the webpage's DOM and display a popup window inside the webpage. This creates a more fluent user experience because it doesn't push the user away from the webpage they're currently viewing, like traditional popups tend to do.

Lightboxes are popups that are part of the webpage itself.

I was reading the library's documentation and noticed that you can pass configuration settings via query string parameters in the URL to the Javascript file. For example, to enable "fast mode" (a setting that improves support for IE 6 and 7), you would import the TopUp script into your page like so:

<script
  type="text/javascript"
  src="path/to/top_up-min.js?fast_mode=1">
</script>

The question I asked myself was, "How are they passing query string parameters into a Javascript file?" If top_up-min.js was really a server-side script that returned Javascript code, then I could understand. But it's an ordinary, plain-text .js file! The file can't be aware of its own URL, so it can't see the parameters!

I opened up the source code to have a look. The author queries the DOM to get a reference to the <script> element that imported the file, and then parses the element's src attribute to get the query string parameters. Pretty clever!

But how does he get a reference to the <script> element? There's nothing unique about this element that you can use to pull it out from the DOM. No unique ID. No specially-named class attribute. How does he do it? What he does is he adds a dummy element to the DOM as soon as the script loads. This guarantees that the dummy element will be added immediately after the <script> element, so he can just get the dummy element's previous sibling and wham...you've got your <script> node.

var scriptElement = (function deriveScriptElement() {
  var id = "tu_dummy_script";
  document.write('<script id="' + id + '"></script>');

  var dummyScript = document.getElementById(id);
  var element = dummyScript.previousSibling;

  dummyScript.parentNode.removeChild(dummyScript);
  return element;
}());

Some people say that programming is not a skill that requires creativity. I would ask these people to look at this code sample and tell me that it's not creative!

7 comments:

Jason said...

I think a better approach is to accept those parameters in the url hash, rather than the query string. Query string parameters can affect caching, which means performance may be degraded. (I believe some proxies will not cache content with query strings at all. And additionally if any parameter values are dynamic, they obviously would affect caching).

Michael Angstadt said...

Hi Jason. Thanks for the comment I like that idea. Putting the parameters in the fragment identifier is probably better because it won't effect the caching of the JS file. Using query string parameters, a browser may assume that the URLs "file.js?one=value1" and "file.js?one=value2" contain different data and thus might cache them as two separate files.

Jason said...

Yep, exactly. Those are treated as different content by browsers, proxies, CDNs, ... anything that caches content. Additionally, there are some older proxies out there that will not cache content period if there is a query string present. Of course, if you actually do need to read the parameters server-side (perhaps maybe you serve the script dynamically), then the fragment identifier would not be an option.

Michael Angstadt said...

Yes the fragment identifier is not passed to the server, it's only read by the browser.

Santosh Kumar Gupta said...

You could pass a rel tag with the parameter... :)

Paul Engel said...

Thanks again for your post, Michael. I really really appreciate that someone noticed this handy trick. Cheers! :) :) :)

Michael Angstadt said...

You're welcome.