Friday, February 15, 2008 #

JQuery IntelliSense in Visual Studio 2008

UPDATED July 5 2008: The intellisense file now supports JQuery 1.2.6. More Information

ScottGu recently announced that VS2008 JavaScript IntelliSense no longer fails completely when it runs into JQuery, which is fantastic news - and an impressive testament to the flexibility of VS2008's JavaScript parsing abilities. Previously, merely referencing JQuery was enough to disable all JavaScript IntelliSense in VS2008, which meant that JQuery addicts like me had simply never seen how impressive this system really can be.

But even though they've got over the crashing problem, the basic IntelliSense experience is still a little sparse, especially since JQuery employs minimally descriptive one-or-two character parameter names in an effort to keep source size down. JQuery also frequently employs method chaining (for more on which, see blogs passim) but VS IntelliSense is unable - against raw JQuery - to determine the return type and generate chained method and parameter hints.

So visual Studio IntelliSense starts off promisingly:

plain.1

but then falls flat as soon as you've hit the dot:

plain.2

This has led to a spate of efforts to annotate JQuery with IntelliSense-friendly documentation comments. Unfortunately, there's a limit to how much you can do with adding annotations directly to the JQuery code, and it's a time-consuming - and manually intensive - operation that will be blown away by the next release of JQuery.

One of the key problems is that JQuery - in classic JavaScript space-conscious style - creates a number of its prototype functions by repeatedly using the same function declaration in a loop, relying on JavaScript closures to make each function behave differently. Here's a sample where JQuery sets up a series of event hook methods:

jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
	"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + 
	"submit,keydown,keypress,keyup,error").split(","), function(i, name){
	
	// Handle event binding
	jQuery.fn[name] = function(fn){
		return fn ? this.bind(name, fn) : this.trigger(name);
	};
});

Stick a documentation comment in the top of that function, and you'll end up with the same IntelliSense documentation on 20 JQuery methods. Hardly ideal.

There has to be a better way; we use JQuery intensively at work, and so, with the encouragement of my colleague Duncan Smart, I've set out to produce better JQuery IntelliSense, using an automated process rather than manual annotation.

Duncan also pointed me towards the JQuery machine-readable documentation at http://jquery.com/api/. Sadly it's a little out of date, but hopefully it will be updated at some point, and when it is, the automated process should be able to employ the updated data and rebuild the annotated JQuery.

Visual Studio figures out external script references, such as to JQuery, by following special reference comments it finds at the top of .js files:

/// <reference path="jquery-1.2.3.js" />

In practice, this means that you can reference any JavaScript file through this mechanism, not just those that you're actually going to run at runtime. Most sites deploy a minified version of JQuery to the browser, but you could reference instead an annotated JQuery file here to get IntelliSense support.

There is a different resolution employed to provide IntelliSense from within <script> tags, where VS attempts to follow <script src=""> links to external includes, and even traces includes it finds in the master page. But we can fool the design-time engine by including <script> tags that won't be output to the browser - for example wrapping the tag inside an <asp:PlaceHolder> with its Visible property set permanently to false. If it overrides the definitions that will really be imported at runtime with its own design-time definitions, we get IntelliSense - in ASPX pages, HTML pages, and so on.

In which case, the question arises, why does the file you're referencing here need to contain anything other than the annotations to drive IntelliSense? Why make VS2008 work so hard to understand JQuery in the first place, when you're already lying to it about which JavaScript source file you're using?

So the approach I've ended up with is not to annotate the JQuery source code, which has the limitations mentioned before, but instead to generate completely different JavaScript that defines a stub that looks exactly the same as JQuery from the point of view of IntelliSense, but which contains no actual functionality. It's a design-time 'header file' that contains all the documentation and structure of JQuery but none of the code. The result is fully-functional method and parameter IntelliSense. Where previously VS drew a blank, now it can continue to help.

intel.1

 intel.2

 intel.3

intel.4 

Employing the script-hiding technique mentioned above, here it is functioning in an ASPX page:

aspx

To produce the script, I turned, naturally, to JavaScript. Using JavaScript, it's possible to examine the actual JQuery API and object model as it exists in the web browser, and construct a script that outputs the same structure. Along the way, I can also generate the appropriate documentation comments from the XML source data at http://jquery.com/api/data/jquery-docs-xml.xml, and include IntelliSense comments. Here's a sample of the output it ends up producing:

jQuery = $ = function (expr, context) {
	/// <summary>
	/// 1: $(expr, context) - This function accepts a string containing a CSS or basic XPath selector which is then used to match a set of elements.
	/// 2: $(html) - Create DOM elements on-the-fly from the provided String of raw HTML.
	/// 3: $(elems) - Wrap jQuery functionality around a single or multiple DOM Element(s).
	/// 4: $(fn) - A shorthand for $(document).
	/// </summary>
	/// <returns type="jQuery"></returns>
	/// <param name="expr" />
	/// 1: expr - An expression to search with
	/// 2: html - A string of HTML to create on the fly.
	/// 3: elems - DOM element(s) to be encapsulated by a jQuery object.
	/// 4: fn - The function to execute when the DOM is ready.
	/// </param>
	/// <param name="context" optional="true" />
	/// 1: context - (optional) A DOM Element, Document or jQuery to use as context
	/// </param>
	/// <field type="String" name="jquery">The current version of jQuery.</field>
	/// <field type="Number" name="length">The number of elements currently matched.</field>
};

You can run the JQuery IntelliSense Header Generator in your browser, copy and paste the resulting script into a .js file, and then you can use it as an IntelliSense source.

We've posted the header generator up here. Or, you can download a copy of the header file from here. Let me know in the comments if you find it useful, or if there's anything you think needs changing about it.

UPDATED: I just noticed that Brennan Stehling has produced something similar to this stub file - apparently manually. Hopefully he won't be too annoyed I've made a tool that does the same thing.

posted @ Friday, February 15, 2008 2:59 PM | Feedback (31)