Archive

Posts Tagged ‘jQuery’

Creating Application-specific jQuery plugins

August 16th, 2010

When building an application with jQuery, it’s very likely that you will end up writing some code that would be easiest to use directly in a jQuery chain. When the functions you’re writing solve general problems that don’t have a dependency on your application, it’s usually obvious that you should write the function as a jQuery plugin. However, when you’re writing code that is very specific to your application and you want to use it in a chain, it’s not always clear whether writing the function as a jQuery plugin is a good idea.

I’ve come across three main categories of application-specific plugins since I started using jQuery. The first category contains plugins that are useful throughout most of your application, but useless outside of your application. These plugins usually deal with traversing and manipulating common components based on specific markup in your application. This set of plugins can be used on many elements across many pages and therefore get the most reuse. The second category contains plugins that are tied to a specific element that persists across most, if not all, of your application. These are different from the first category because the plugins are never called on more than one element for any page load. You can think of these as Singleton DOM components, such as help dialogs. The final category consists of plugins that are only used on a single page. In many cases these plugins will be used on multiple elements within the page, but the plugin is never loaded on any other page.

One pattern for creating application-specific plugins is to prefix your method names to prevent name collisions. For example, let’s say we have lots of widgets with common markup structures. If we want to be able to navigate from any given element on the page to the widget the element is contained in, we might create a method called nmkWidget.

Let’s assume the widgets have the following shell:

<div class="nmk-widget">
	<div class="nmk-widget-header">
		<!-- widget-specific header -->
	</div>
	<div class="nmk-widget-content">
		<!-- widget-specific content -->
	</div>
</div>

We can define some helper functions for navigating the DOM:

$.fn.nmkWidget = function() {
	return this.closest( ".nmk-widget" );
};
$.fn.nmkWidgetHeader = function() {
	return this.nmkWidget().children( ".nmk-widget-header" );
};
$.fn.nmkWidgetContent = function() {
	return this.nmkWidget().children( ".nmk-widget-content" );
};

This works well for simple plugins like these that don’t have any dependencies or state management beyond the DOM. But what happens if you have a lot of inter-related functionality that you want to organize into a module? jQuery doesn’t have support for namespaces in jQuery.fn, but there is a simple pattern that can help. We can define a method that acts as a pass-thru from jQuery to any object:

var slice = Array.prototype.slice;
$.fn.exec = function( method ) {
	return method.apply( this, slice.call( arguments, 1 ) );
};

Then we can create objects, nested as deeply as we want, and define methods on them as if they were standard jQuery methods:

var nmk = {
	widget: {
		open: function( duration ) {
			return this.nmkWidget().slideDown( duration );
		},
		close: function( duration ) {
			return this.nmkWidget().slideUp( duration );
		}
	}
};

We can then execute them using .exec() in any jQuery chain:

$( elem ).exec( nmk.widget.close );

We can even pass parameters to these methods:

$( elem ).exec( nmk.widget.close, 1000 );

Now we’ve got reusable, modular, namespaced code that can be used directly in jQuery chains. We can use this to define multiple ways to close our widgets without duplicating any logic and without worrying about where we are in the DOM.

// allow any widget to be closed by a close button located in the header
$( ".nmk-widget-close-button" ).live( "click", function() {
	$( this ).exec( nmk.widget.close );
});

// allow adding a new widget, with undo functionality
$( "#add-widget" ).click(function() {
	// generate a new widget and add to the page
	// returns a jQuery object containing the new widget
	var widget = nmk.widget.create();
	$( "<p>Your new widget has been added. Click here to undo.</p>" )
		.click(function() {
			widget.exec( nmk.widget.close );
		});
});

jQuery , ,

Using multiple versions of jQuery

October 3rd, 2009

Whenever a new version of jQuery comes out, there are several reasons to upgrade; new features, bug fixes and performance are generally the most common. However, it’s not always possible to upgrade as soon as a new version comes out. The new version may contain an API change that would break some of your functionality or you may be using a plugin that isn’t compatible with the new version. This is where jQuery.noConflict() comes in.

History of jQuery.noConflict()

jQuery.noConflict() was added in v1.1 to support loading jQuery on a page with other code that uses the global $ variable. In this version, the only thing that jQuery.noConflict() did was revert the $ variable to what it was before jQuery was loaded. In v1.1.1, this function was modified to also return the jQuery object so that you could assign jQuery to another variable (though it was still also assigned to the global jQuery variable). v1.1.4 introduced a boolean parameter for jQuery.noConflict() to indicate whether the global jQuery variable should be reverted; this option was added to make it easy to load multiple versions of jQuery on the same page.

Using jQuery.noConflict()

Using jQuery.noConflict() to load multiple versions of jQuery is actually pretty simple. There’s essentially just one rule: load your plugins in the correct order. In order for this to work, you have to include one version of jQuery and all of the plugins for that version before including the next version and all of its plugins. You can call jQuery.noConflict() immediately before including the second version or after you have included all the plugins for the second version. Both methods will have the same final outcome, but it may be easier to keep track of what’s going on if you call jQuery.noConflict() immediately before loading the next version in the unlikely event that you need to include more than two versions of jQuery on the same page. Because this method is slightly easier to follow, we’ll use it in our example.

First we need to load both versions of jQuery:

<!-- load jQuery 1.1.3 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.1.3.js"></script>
<script type="text/javascript" src="jquery.dimensions.min.js"></script>

<!-- revert global jQuery and $ variables and store jQuery in a new variable -->
<script type="text/javascript">
var jQuery_1_1_3 = $.noConflict(true);
</script>

<!-- load jQuery 1.3.2 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.3.2.js"></script>

<!-- revert global jQuery and $ variables and store jQuery in a new variable -->
<script type="text/javascript">
var jQuery_1_3_2 = $.noConflict(true);
</script>

Now we can use either version of jQuery by using the two new variables we’ve created:

jQuery_1_1_3('<button>Use jQuery 1.1.3</button>')
	.click(function() {
		alert('Top: ' + jQuery_1_1_3(this).offset().top + '\n' +
			'jQuery: ' + jQuery_1_1_3.fn.jquery);
	})
	.appendTo('body');

This is great, but you’re probably looking at that code and thinking “I wish I could still use the $ variable.” Well, with a little help from a self-executing anonymous function, you can!

(function($) {
	$('<button>Use jQuery 1.1.3</button>')
		.click(function() {
			alert('Top: ' + $(this).offset().top + '\n' +
				'jQuery: ' + $.fn.jquery);
		})
		.appendTo('body');
})(jQuery_1_1_3);

This is a pattern you’ll see in a lot of plugins (in fact it’s part of what makes this whole thing work). We’ve created a function that accepts a single parameter, $, and we call that function as soon as we define it, passing our jQuery object to it. Now we can reference our jQuery object as $ inside this function. This makes it very easy to rename your global jQuery object while keeping your existing code working with minimal modifications.

View this example in a new window

jQuery , , ,