Fixing These jQuery!
A Guide to Debugging

adam j. sontag

Thanks for coming!

Fixing These jQuery is an HTML5 presentation designed to familiarise developers with basic approaches to debugging jQuery and JavaScript code. It also introduces many of the common pitfalls most people encounter at some point on their jQuery journey. It is always a work in progress!

Use the left and right arrow keys or your mouse wheel to navigate.

Who dat?

  • yayQuery Cohost!
  • Bocoup Dev!
  • I've debugged a lot of my own jQuery problems
  • I've debugged a lot of other people's jQuery problems
  • I've seen it all a lot

jQuery is JavaScript

You will have to debug it.

window.alert("Is Not A Debugger")

Why?

JavaScript Debuggers Exist

And if you aren't using them, you are insane.

Debuggers

Firefox uses Firebug
Chrome and Safari use WebKit Inspector
Opera uses Dragonfly
IE8 and IE9 have a built-in debugger
IE6 and IE7 require you to use a host app to debug. Lame.

All Look Same

Firebug
Webkit Inspector
Dragonfly
Internet Explorer 8

ZOMG!

The Debugger SHOWS YOU THE ERRORS!!!
  • A debugger will show you syntax errors and runtime exceptions
  • Save time: Use your IDE or text editor to catch syntax errors (JSLint)

Really Basic Errors

Let's get these out of the way

jQuery is not defined
You have not loaded jQuery correctly
Is the script included?
Is the path correct?
$ is not defined
jQuery is loaded, but the $ alias is not assigned to it.
Was jQuery.noConflict() run? (Wordpress does this by default.)
$.fn is not defined
Are you using another library that is taking the $ alias?
jQuery.fn.somePlugin is not defined
You have not loaded the plugin correctly
Is the script included?
Is the path correct?
Did you load the plugin after loading jQuery?

Debuggin' ain't is easy

Instead of this ugly, useless ugly alert...

$("ul li span").hover(function(e) {
  var $t = $(this),
      w = $t.width(),
      o = $t.offset();
  o.left = o.left + w;
  alert(o);
  span.text($t.text())
  tip.offset(o).show();
});
            

...Set a Breakpoint!

  1. Select the script you want to debug
  2. Click on the line number where you want to inspect further
  3. An arrow or dot thingy will show up. That's a breakpoint!

The debugger; statement

Don't want to wait to set a breakpoint?

$("ul li span").hover(function(e) {
  var $t = $(this),
      w = $t.width(),
      o = $t.offset();
  o.left = o.left + w;
  debugger;
  span.text($t.text())
  tip.offset(o).show();
});
            

The debugger; statement lets you set a breakpoint programmatically.

In either case

When the breakpoint is reached, the code will stop executing and the debugger will light up like a Christmas tree.

And just like a Christmas tree, you can find all sorts of awesome stuff underneath it.

Watch out! We're going ¡Local!

The Watch/Locals windows

Chrome/Safari's Scope Variables; Firebug's Watch window
  • Determine the current value of variables.
  • Drilldown through objects - no more [Object object]!
  • Watch: Keep track of arbitrary expressions

R U Steppin To Me Bro?

Continue - Step Over - Step Into - Step Out

When execution is paused in a debugger, you can control it - just like a VCR!

Continue
Resume execution. Go to the next breakpoint, if it exists.
Step Over
Call the next function, pause again after it finishes. (Go to the next line of code.)
Step Into
Calls the next function on the line, pausing execution on its first line.
Step Out
Finish the current function call, pause on the line that invoked it.

console.log & friends

Pullin' out all the stops!

  • Breakpoints are great, but are similar to alerts. How?
  • Wait for something to happen, and pause when it does
  • The Console and its API let you debug arbitrary code and values
$('#testElement')
.click({"foo":"bar"},function(e) {
  console.info("The button has been pressed");
  console.dir(e.data);
  // You can pass an unlimited list of arguments
  console.log(this,e);
  $(this).append('
Hello
'); });

Console loggin'

Something like you'll see in your console when you click the element. (Firebug)
The previous example, in action on jsFiddle (more on that later).

The Consolation Prize

You don't have to modify your code at all to debug using the Console

  • You can execute any arbitrary code just by entering it into the console.
  • Code is executed in the context of the window object (this === window)
  • Unless the debugger is paused, in which case code is executed in the context of the paused function
  • Embiggen your console (if possible)

A Whole Lotta API

Shamelessly capped from Firebug Wiki
  • There are a lot of methods in the Console API
  • To get started, we only really need log, debug, and dir
  • Unfortunately, most of these suck in IE
  • Our arsenal is almost complete!

He's On Fire!

The FireQuery add-on for Firebug makes debugging jQuery even easier
  1. FireQuery showing jQuery.data() properties in HTML View
  2. Pretty-printing jQuery objects in console

Answer Your Own FAQs

AKA: "Why isn't it working?"

Q: Why aren't my events happening?
Do the elements you're attaching them to even exist? Type this in the console, and run it.
// Replace ".theElements" with your actual selector
console.log($(".theElements").length);
If the result is 0, the elements are not in the DOM
Checking the length property of jQuery object is the easiest & fastest way to check if a selector matched any elements. Don't use .size(), it's a waste of time.
A: A selector you are using is not correct.
A: A traversal failed to match any elements.
Don't Quote Me On That
If you are referring to DOM Elements like window or this, don't treat them like strings
$('this') // Wrong
$(this)   // Right
Q: But the result was > 0!
A: Then we forge on!

"The selection contains elements!"

By and large, jQuery works with elements that are in the DOM.

Q: Were the the elements there when the code was actually run?
Just because the elements are in the DOM now, doesn't mean they were when the code executed.
A1: jQuery(document).ready(function($){});
Make sure your code was inside of a document-ready block, or otherwise executed after the DOM has loaded.
This is just one reason to put your scripts at the bottom of the body.
A2: Debug!
Set a breakpoint at the line of code that is misbehaving. When the code pauses, console.log($("yourSelector").length)
Add $("yourSelector").length as a Watch Expression, wait for the breakpoint.
Place console.log($("yourSelector").length) in your script, right before the code executes.

Log Freely!

Don't just log .length!
You can log any type of variable.
If you log the entire jQuery object, you can hover over the elements, if any, to see them highlighted in the page!*

* IE doesn't support any of this awesome stuff.
In fact, you can't interact with the page at all when you've paused execution in IE.

I Added The Elements to Page After I Tried To Actually Use Them

  • One of the most FA of all Q's
  • Could be as simple as .html() or the result of inserting AJAX-fetched content.
  •   var foo = $("div.foo").click(function() {
        $(this).text("I was clicked!");
      });
      // foo.length == 1;
    
      // nothing will happen when these divs are clicked.
      $("<div>",{"class":"foo"}).appendTo(document.body);
      $("div.bar").addClass("foo");
      
  • Handlers bound with .bind() (and shortcuts) are only attached to elements that are in the jQuery object when .bind() is executed.

Hence, .live() and .delegate

  • Event delegation uses event bubbling to catch events at a higher level in the DOM than where they occurred.
  • Now, you can catch events that happen on newly added elements
// All three examples will catch a click on any anchor that is ever in the page
$(document).bind("click",function(e) {
  if ($(e.target).is("a") {
    // this === document
  }
});

$(document).delegate("a","click",function(e) {
  //this === clicked anchor
});

$("a").live("click",function(e) {
  //this === clicked anchor
});

bind() is not live()

They do similar things. How are they different?

<div id="teddy" class="foo">
<div id="roosevelt" class="bar">
var log = function() { console.log(this.id); }
$(".foo").click(log);
$(".bar").live("click",log);

$(".foo").addClass("bar").removeClass("foo").trigger("click");
// logs 'teddy' twice
// 1) Because there is still a click handler attached to <div id='teddy'></div>
//    even though it no longer has the class 'foo'
// 2) Because a "click" on an element with class "bar" bubbled to the document
  • .bind() creates a 1:1 association between element and handler
  • .live() and .delegate() check to see if the target element of a bubbled event matches certain criteria

jQuery Objects are not live

New elements that match a selector do not automagically appear in existing collections

  • Abita Purple Haze
  • Hoegaarden
  • UFO Hefeweizen
var li = $("#wheatBeers li");
// li.length == 3;
$("<li>",{text:"Weihenstephaner"})
.appendTo("#wheatBeers");
// guess what? li.length == 3

jQuery objects are transient

  • Every call to jQuery's traversal/selection methods returns a new jQuery object
  • That's why you can't compare jQuery objects for equality
  • $("#foo") === $("#foo"); // false
    $("#foo")[0] === $("#foo")[0] // true
    
  • DON'T store arbitrary data on random jQuery objects! Hint: Use .data()
  • var $myCoolWidget = $("#cycleMcParallax");
    $myCoolWidget.cycleCount = 0;
    
  • Beware of add()
  • var red = $(".red"), green = $(".green");
    // red.length == 5; green.length == 5;
    green.add(red);
    // green.length == 5;
    green = green.add(red);
    // green.length == 10;
    
    
    

What is this? I don't even...

  • You pass a lot of functions to various jQuery methods
  • Event Handlers, AJAX callbacks, Utility methods, Plugin callbacks
  • jQuery and plugins will change the 'this' (the scope) of these functions
  • Just being aware of this fact is half the battle
$("#foo").click(function(e) {
  $.getJSON("/ajax/page/",function(data) {
    $(this).text(data.status); // IT'S NOT WORK
  });
});
This this example gives new jQuery users fits.
  • Be very aware of this when writing object-oriented code

this is out of control!

Event handlers, jQuery.fn.each, jQuery UI
Explicitly set this to be a DOM element
AJAX callbacks
Explicitly set this to be the options object
jQuery.each
Explicitly sets this to be the value being iterated, but weird.
$.each(["foo","bar"],function(i,value) {
  // this == ["f","o","o"]
  // value == "foo"
});
jQuery.each: Don't use this here, use the second argument
Random Plugins With Callbacks
Might set this, might not. Be on the lookout.

Don't drop the scope

If this isn't the this you think this is, you can use the debugger and/or the console to confirm your suspicion.

Hmm, this doesn't look like a DOM element from here!

Let's get this straight

Use a variable to alias this
$("#foo").click(function(e) {
  var elem = this;
  $.getJSON("/ajax/page/",function(data) {
    $(elem).text(data.status); // ITS WORK NOW!
  });
});
jQuery.proxy
Returns a reference to a function that will run with a particular context
var person = {
  name:"Ace S. McCloud",
  init:function() {
    $("#sayHi").click($.proxy(function(e) {
       // Now 'this' refers to the person object!
       // We can still use e.target to reference the element
       $(e.target).text(this.name);
    },this));
  }
};

this shall not pass!

Referencing vs. executing a function

// A simple function
function log(str) {
  console.log(str);
};

// Logs an event object, because jQuery always passes the event object as the first argument to handlers
$("#foo").click(log);

You cannot override supplied function arguments by "passing arguments" to a named function: You'll end up executing the function!

// WRONG
$("#foo").click(log("I clicked it"));
// You just executed 'log', and failed to pass a function as a handler. Huh?
// It's the same as doing the following, and just as broken.
var l = log("I clicked it"); // l === undefined
$("#foo").click(l);

// RIGHT
$("#foo").click(function(e) {
  log("I clicked it");
});

Question #1

Was there an actual request?

The Network/Resources Tab

Investigate the existence and results of AJAX calls

Firebug shows XHR activity in the Console and the Net tabs.
Newest versions of WebKit Inspector use the Network tab.
Current releases use the Resources tab

Did the request even happen? IE edition

IE's debugging tools do not allow you to interrogate XHRs
You have to use a third-party HTTP Proxy tool like Charles or Fiddler

Charles, shown here, pairs with IE (and other browsers) to allow you to explore XHR calls, similar to the Net tab in other debuggers

If You Don't See an XHR

Likely Explanation
The request was never made. Something else in your code broke/misbehaved before the AJAX call was even fired.
Protip: Add a beforeSend callback to check if the $.ajax() function is being reached.
$.ajax({
  url:"foo.php",
  data:{hello:"there"},
  success:function(data) {},
  beforeSend:function(xhr) {
    console.debug("The XHR object has been prepared ",xhr);
  }
});
Considerably Less Likely Explanation
The request does not use an XMLHttpRequest, and is not logged by these tools.
Certain AJAX-y interactions don't actually use an XHR to communicate.
  • JSONP: Creates a new script node and appends it to the head.
  • 'AJAX' Upload: Uses an iframe or Flash as a proxy to do the upload.

Haven't Had Much success

If you are able to confirm that an XHR has indeed taken place, but your success callback isn't firing:

A1: Server Error
The resource you are requesting is returning some sort of error. Use the Network tab of your debugger to see the StatusCode and physical response from the server.
A2: Invalid JSON
If you are using jQuery.getJSON or otherwise specifying a dataType of JSON, jQuery will not fire the success callback if it cannot parse the response as valid JSON.
Valid JSON has double quotes around all strings, including keys, and is typically generated by a JSON encoder.
A3: Malformed HTML
If you are using jQuery.fn.load or otherwise appending HTML that you fetched via AJAX, make sure the HTML is structurally valid and does not contain errors.

Not this again!

If your success callback is firing, but not working right

A1: Verify the scope
Remember, jQuery manipulates the scope of AJAX callbacks.
In addition to the earlier techniques, jQuery.ajax() has a context option that sets the scope for all callbacks associated with that call.
var widget = {
  elem:$("#scores"),
  init:function() {
    this.ajaxOptions = {
      // The 'widget' object is 'this' when .init() runs,
      // Preserve it as the context for all callbacks
      context:this,
      url:"scores.php"
    };
    return this;
  },
  getData(function() {
    $.ajax($.extend({
      success:function(data) {
        // I can safely use 'this' to refer to the 'widget' object here
        this.elem.html(data);
      }
    },this.ajaxOptions));
  }
};

widget.init().getData();

The A in AJAX

Doesn't stand for "Awesome", it stands for "Asynchronous"

Beware of Race Conditions

It's A Race! It's A Race!

You cannot reliably return the result of an AJAX callback back into the function that triggered it

// newContent === XMLHttpRequest
// NOT the raw server response
var newContent = $.get("remote.php",function(data) {
  return data;
}); 
data is returned from the anonymous function, but not into newContent
var newContent;
$.get("remote.php",function(data) {
  newContent = data;
});
$("#container").html(newContent); // Nothing is appended
Race Condition: newContent will contain the server response at some point in the future, but not immediately after $.get() runs.

"Two Wrongs" != "A Right"

You cannot combine the previous two non-working techniques to create something that works

Don't feel bad, everybody tries this once

function getContent() {
  var stuff;
  $.get("remote.php",function(data) {
    stuff = data;
  });
  return stuff;
}
var newContent = getContent(); // newContent === undefined
This is a very bad idea. Just like $.get() does not wait for the callback to finish, neither will the containing getContent function

Whither async:false?

It's not like killing a kitten, but it is like locking one in a dark room and choosing not to feed it.

Random SO Quote

"It is much better to use callbacks and start thinking asynchronously, rather than procedurally. It's a bit of a struggle when you first start (since your code isn't necessarily run in line-by-line order), but it is very powerful and flexible once you get the hang of it."
nickf, 10 Nov 2009

Cross-domain AJAX: "It's Not Possible"

  • Same Origin Policy
  • You simply cannot make a basic AJAX request of any arbitrary resource
  $.get("http://www.google.com/search?q=javascript",function(data) {
    $("#searchResults").html(data);
  });
A cross-domain request. Will not work.

Yes, there are workarounds:

  • JSONP
  • CORS,
  • YQL
  • Server-side proxy: use cURL, etc, on the server

<script> tags in AJAX Content

  • No, it's not just as good as a real callback
  • Scripts won't always be executed
  • Not fun to debug
  • No separation of concerns

"I Found a jQuery Bug"

Just because you get an error that points to a line number in jQuery
does not mean you have found a jQuery bug
Ask Yourself This
"If [insert your issue] was REALLY a bug, wouldn't someone else besides me have noticed?"
Garbage In, Garbage Out
Somewhere along the line, you are probably passing something incorrect to jQuery.
To The Debugger!
But make sure you're using the unminified source to debug!
"Is anyone familiar with the error 'c.x is undefined' on line 1 of the jQuery file?" No.

Stack Trace or GTFO

Double handler is undefined! What does it mean?
  • Click the line number to find where jQuery is "breaking"
  • Set a breakpoint
  • Execute the code again (refresh)
  • Click through the call stack/stack trace until you find something familiar

Retracing your steps

Use the call stack to navigate the function invocation chain.

This doesn't look familiar...

Home is where the #fail is

Hey, I wrote this code!

When you encounter some of your own code, debug everything you're passing into jQuery.
In this case, I'm passing a nonexistent method as a handler!

jQuery is bugging

Have you recently upgraded, and your code has stopped working?
It might be an intentional change. Check release notes, blogs, commits, etc.
If you really think it's a bug
  • Read the bug reporting guidelines
  • Check to see if someone else reported it
  • Make a solid test case, illustrating ONLY that issue on jsFiddle.
  • Protip: You might figure out what the real bug is
  • File a ticket
For more on diagnosing and reporting jQuery bugs
Mike Taylor's "Is these a bug?"

IE will straight up kill you if...

  • You leave a trailing comma in an object literal
  • var get = {
      ready: "for",
      your: "funeral",
    }; // BOOM
    
  • You use reserved words you didn't know were reserved
  • var class = "boom"; // BOOM
    var foo = {class: "bar"}; // BOOM
    var foo = {"class": "bar"}; // Actually OK. Quoted keynames ftw!
    
  • You try to inject malformed HTML
  • $("#pick-a-nick-basket")
    .append("<div class='food'><h1>Roast Beef</a></div><a href='http://www.roastbeef.info'></h1>"); //sputter
    

event.stopPropagation()

"It stops events from propagating!"

  • Sometimes, you attach a handler to an element, and it works great.
  • $("form").focus(function(e) {
     $(e.target).addClass("active").siblings(":input").removeClass("active");
    });
    
  • Then you add some cool plugin, and the first handler. Stops. Happening.
  • // jQuery.fn.electricPiano is a fictional plugin that
    // turns an input into a keyboard that plays synthesized sounds.
    $("input.musical").electricPiano();
    
  • That fancy plugin (or your own code) may be stopping the event's bubbling.
  • Check to see if there are other handlers which call event.stopPropagation() or return false;

for pete's sake!

When you use a for loop, the iterator variable persists after the loop.

// OMG I heard jQuery.fn.each was like a HUGE perf hit!
// So I'm going to use a native for loop instead!
var lis = $("li"),
    l = lis.length, // 5
    li;
for (var i=0;i<l;i++) {
    li = lis.eq(i);
    li.click(function() {
       alert("You clicked the "+ i +"th item");
       // No matter which item is clicked, it always alerts 5!
    });
}
http://jsfiddle.net/ajpiano/wq376/

The variable i is local to the function in which it's defined, not the loop

Wrapping it up

// Make your own closure
var lis = $("li"), l = lis.length, i = 0, li;
for (i=0;i<l;i++) {
    (function(i) {
        // i is now local to this IIFE (immediately-invoked function expression)
        li = lis.eq(i);
        li.click(function() {
           alert("You clicked the "+ i +"th item");
        });
    })(i);
}

// Just use jQuery's .each, it's not actually a serious performance concern
lis.each(function(i,elem) {
   $(this).click(function(event) {
    alert("jQuery's each says you clicked the "+ i +"th item");
  });
});
http://jsfiddle.net/ajpiano/uNHYM/

Avast! Hoist the sails!

Variable declarations and function declarations are hoisted to the top of the function in which they're declared.

var foo = true;
if (foo) {
    function checkFoo() {
        document.write("Foo is right and good, it is true"); 
    }  
} else {
    function checkFoo() {
        document.write("Foo is nefarious and evil, it is false"); 
    }      
}
checkFoo();
// checkFoo() always writes that foo is false, 
// Even though you set it true "before" defining the function!
http://jsfiddle.net/ajpiano/ST5w5/

Quote Before Unquote

The code on the previous slide is actually interpreted like this!

var foo;
function checkFoo() {
    document.write("Foo is right and good, it is true"); 
}  
function checkFoo() {
    document.write("Foo is nefarious and evil, it is false"); 
}      
foo = true;
if (foo) { } else { }
checkFoo();
// Pretty crazy, amirite?

Throwing In The Towel

If you can't de- the bugs:
  • Get a cup of coffee/do something else! Breakpoints are good things.
  • Read the docs (again)
  • Get support on IRC (freenode - #jquery)
  • Get support on the forums or StackOverflow
  • Check for plugin-specific resources
  • Twitter
  • Ask your friends/colleagues/coworkers
  • Don't be a help vampire!
  • Get a new job.

Don't be this that guy!

Wrong on so many levels

Something clever