Wrapping The Window Object In A jQuery Wrapper

Posted February 2, 2010 at 8:21 PM

Tags: Javascript / DHTML

This morning, when I was building my Flickr-style photo tagging demo using jQuery, I got totally stumped trying to debug a variable problem. From what it looked like, the Window object appeared to be undefined within the context of my document-ready event handler. After picking apart the code and commenting out bits of it for like 10-15 minutes, I finally figured out what the heck was going on. It boiled down to me simply forgetting how variables get "compiled" in Javascript. I think it's something that's very easy to forget, so I figured I would share my error.

Let's walk through a really simple scenario. In the following demo, I try to create a function-local jQuery-ized variable to override the raw window object reference:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • var window = $( window );
  •  
  • // Log window.
  • console.log( window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see, I am creating a local variable, window, which is a jQuery wrapper containing the global window object. The intent here was to create a locally-scoped window variable that would give me immediate access to jQuery methods like width(), height(), scrollLeft(), and scrollTop(). At first glance, this seems reasonable; but, when we run the above code, this the console output that we get:

[ ]

It's a jQuery wrapper alright, but the collection is empty.

When I saw this, my first thought was that the global window object was undefined in the context of the document-ready event handler. To test this, I tried putting a console log() method call before and after the variable assignment:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  •  
  • // Log the window.
  • console.log( "Before: ", window );
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • var window = $( window );
  •  
  • // Log window.
  • console.log( "After: ", window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

Here, you can see that I am logging the global window object, then creating the local window reference (using the window object), then logging the newly created window reference. When we run the above code, we get the following console output:

Before: undefined
After: [ ]

Ah-ha!! The window object is undefined in the context of the document-ready event handler! It doesn't even exist before I've tried to do anything with it. When I saw this, I figured something really crazy must be going on.

After staring at this code for like 5 minutes, I had another "ah-ha" moment. What I realized was that the above behavior was exactly what was to be expected. The problem was that I had become so accustomed to thinking about Javascript in a top-down manner, that I forgot that it actually does "compile", at least in a very lose sense, for optimization. In particular, one thing it does is move all of your variable declarations to the top of the current function. To give you a basic idea of what I'm talking about, here is my interpretation of the "compiled" version of the previous code sample:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  • // -------------------------------------------- //
  • // -------------------------------------------- //
  •  
  • var window;
  •  
  • // -------------------------------------------- //
  • // -------------------------------------------- //
  •  
  • // Log the window.
  • console.log( "Before: ", window );
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • window = $( window );
  •  
  • // Log window.
  • console.log( "After: ", window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see here, the Javascript interpreter (Me) has moved the "var" Window variable declaration up to top of the function and removed the "var" keyword form the actual variable assignment. The Javascript interpretor does this to allow our var usage to be easily sprinkled throughout our code without complicating scope-chain-crawl. Once it does this, however, you can quickly see that my local window reference is going to try to reference itself, rather than the global window object, in some sort of ill-fated mobius strip approach.

So now that we see why this is breaking, what can we do to create a local, jQuery-ized window object? The simplest way would be to create a reference to the window object outside of the document-ready event handler:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a window-reference outside of the event handler.
  • // The event handler will create a closure to this scope,
  • // which will maintain this variable reference.
  • var thisWindow = window;
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  • // Overwrite the window object, using the thisWindow
  • // proxy variable.
  • var window = $( thisWindow );
  •  
  • // Log window.
  • console.log( "After: ", window );
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see here, we've created a variable, thisWindow, which points to the global window object. When we then create the document-ready event binding, we pass an anonymous function off to jQuery. This anonymous function creates a closure to the global scope, granting the event handler access to the thisWindow variable during its execution. Now, since the variable assignment doesn't rely on a reference to "window", the locally-scoped window variable has no problems wrapping the thisWindow reference in a jQuery collection. And, when we run the above code, we get the following output:

After: [ Window window_in_ready.htm ]

Another solution to this problem would be to leverage the power of self-executing functions. In a self-executing function situation, we can create locally-scoped variables through input-argument translation. To see what I mean, take a look at this code:

 Launch code in new window » Download code as text file »

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a self-executing function. The parameters to
  • // this function - window, $ - will create locally-scoped
  • // versions of the arguments being passed in.
  • (function( window, $ ){
  •  
  •  
  • // When the DOM is ready ......
  • $(function(){
  •  
  • // Log window.
  • console.log( "Local: ", window );
  •  
  • });
  •  
  •  
  • })(
  • jQuery( window ),
  • jQuery
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

Here, we define the self-executing function's parameters as "window" and "$". These parameters then become our locally-scoped versions of whatever is being passed in during the self-executing method invocation. In our code, the following variable translation is taking place during said invocation:

jQuery( window ) .... becomes .... window

jQuery .... becomes .... $

As you can see, when we invoke the method, we use the global window object to create a nameless jQuery collection. This jQuery collection then gets assigned to the locally-scoped "window" parameter. I happen to really like this approach as it creates a very sexy and elegant encapsulation.

So much of Javascript is written in a top-down manner that it's very easy to forget that the code does get "compiled" to some degree (this may be even more true with the most recent browsers). When you're trying to do something tricky, like create a local reference of a globally available object, forgetting this fact can cause some serious head banging and hair pulling!

NOTE: I use the term "compiled" in the loosest possible manner.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Print Page





Reader Comments

Feb 2, 2010 at 8:43 PM // reply »
2 Comments

Very elegant solution... and good reminder. It's always the little stuff that leaves you wanting to throw your monitor through the window !


Feb 2, 2010 at 8:46 PM // reply »
7,486 Comments

@Ryan,

Right?? Luckily they strap my wrists to the chair, so the monitors are safer :)


Jim
Feb 3, 2010 at 1:36 PM // reply »
4 Comments

I use the self executing lambda with params a LOT, but with window using self might be the simplest:

jQuery( function( $ ){ var window = $( self ); console.log( window ); } );


Feb 3, 2010 at 3:49 PM // reply »
7,486 Comments

@Jim,

Oh wow, I forgot that window had a "self" property (it's been forever since I cared about framesets). Dynamite drop-in.


Feb 4, 2010 at 8:58 PM // reply »
1 Comments

Nice solution, but you could simply use other identifier than 'window' for the wrapped window object and the problem would be solved. Since javascript has function scope, the local variable window overrides the global window object as soon as the function get executed.

jQuery(function($) {
console.log('before: %o', window);
var wrappedWindow = $(window);
console.log('after: %o', wrappedWindow);
});


Feb 4, 2010 at 9:05 PM // reply »
7,486 Comments

@Elvis,

Yes, very true; I just liked the idea of using the "window" name. Since it's used so often, it just felt natural. But again, that's just a personal preference issue - your approach is good as well.


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 11, 2010 at 3:24 PM
Ask Ben: Using jQuery To Act On A Click Event Based On The Target Element
@TripeL, Awesome :) Glad it was helpful. ... read »
Mar 11, 2010 at 3:23 PM
Ask Ben: Using jQuery To Act On A Click Event Based On The Target Element
WOW...that's what I'm looking for. The code examples are very helpful. Thanks ... read »
Mar 11, 2010 at 1:20 PM
What Is The Best Time Of Day To Workout?
Well I am glad I stick to mid afternoon / evening work outs. Interesting find! ... read »
Mar 11, 2010 at 1:13 PM
CFHTTPSession.cfc For Multi-CFHttp Requests With Maintained Session
It worked for what I needed perfectly the first try... this is huge, you have made my week! ... read »
Mar 11, 2010 at 12:54 PM
Using Appropriate Status Codes With Each API Response
I forgot to mention that using this application stack allows me to separate as much of the core/business logic into the API Library which leaves the web applications just to handle presentation layer ... read »
Mar 11, 2010 at 12:47 PM
Using Appropriate Status Codes With Each API Response
@Ben Yep, we look a lot at the available http status codes to try and find the best match to what the error is. Between the status code, headers, and/or sending back what the error was via json or x ... read »
Mar 11, 2010 at 12:40 PM
Creating An Image Zoom And Clip Effect With jQuery And ColdFusion
@Ben Nadel, Hi, thanks for answering that fast, i did a little debug... Image data: ----------- Dibujo_uno_small.jpg : 474px × 570px 72dpi Dibujo_uno_big.jpg : 1947px × 2337px 72dpi Code source ... read »
Mar 11, 2010 at 12:13 PM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Ben We, finally, have been able to convert all URL and FORM variables inside the framework itself (via getHttpRequestData()). The hardest part was parsing the bytearray sent when a file is uploaded ... read »