My First jQuery Plugin

Last night, I was trying to help Glen Lipka write a jQuery plugin. I love love love jQuery, but I have never written a jQuery plugin, so at first, I was very confused as to what was going on. After a while, I started to get my bearings, figuring out what THIS pointed to, and what I had access to, and all that jazz. Still, I am not sure that I am on the right path, so I thought I would take some time this morning to write my very first jQuery plugin (to completion) and post it up so that people can basically pick it apart and tell me how much I suck so that I can get better at doing this :) I am used to being in objects in Javascript, but never really at this level of complexity.

The Scared jQuery Plugin

My first jQuery plugin is called Scared. It takes all elements defined by your jQuery selector and makes them scared to be touched. To do this, I am binding the mouse over and mouse move events in such a way that when you try to click on the elements or move over them, they freak out and move away. Now, I am not sure how this interacts with different styles of positioning and all that, but for this demo, I am just going to keep it simple.

As you can see, the power of jQuery comes from its insanely simple syntax. To initialize the document and apply the Scared plugin to the elements, all I have to do is run this:

$( "input[@type='button']" ).scared();

This finds all input elements that are of type Button and then applies the Scared plugin behavior. jQuery is so freakin' cool.

Now, on to the complicated stuff - the actually jQuery plugin:

// Define the jQuery-scared plugin.

jQuery.fn.scared = function(){

// Create a pointer to the curren scared object

// such that we can refer to this base instance

// without having to worry about the dynamic

// run-time linking of THIS.

var scared = this;

// This is the mouse over/move handler.

this.OnMouseOver = function( jNode ){

// Pick a random direction to move in. Each number,

// 0-3 will be N, E, W, or S.

var intDirection = Math.round( Math.random() * 3 );

// Pick a random distance to travel.

var intDistance = Math.round( Math.random() * 30 );

// Get the current position of the node.

var intLeft = parseInt( jNode.css( "left" ) );

var intTop = parseInt( jNode.css( "top" ) );

// Check to see which direction we are moving in.

switch( intDirection ){

// Move node North.

case 0:

jNode.css(

"top",

(intTop - intDistance)

);

break;

// Move node East.

case 1:

jNode.css(

"left",

(intLeft + intDistance)

);

break;

// Move node South.

case 2:

jNode.css(

"top",

(intTop + intDistance)

);

break;

// Move node West.

case 3:

jNode.css(

"left",

(intLeft - intDistance)

);

break;

}

}

// ASSERT: Now that our event handlers are defined,

// we need to initialize the DOM nodes an actually

// bind node-level events to our event handlers.

// Right now, THIS points to the jQuery stack object

// that contains all the target elements to which we

// are going to apply the scared plugin. Let's loop

// over each element and initialize it.

this.each(

function(){

// For each DOM node that we loop over, let's

// create a jQuery object for it.

var jNode = $( this );

// When making these elements scared, we want

// to make sure the current top/left positions

// are not auto.

if (isNaN( parseInt( jNode.css( "top" ) ) )){

// The top value is NOT numeric. Therefore,

// we need to set it to zero.

jNode.css( "top", 0 );

}

if (isNaN( parseInt( jNode.css( "left" ) ) )){

// The left value is NOT numeric. Therefore,

// we need to set it to zero.

jNode.css( "left", 0 );

}

// If the position is not explicitly set, we

// are going to set it to relative.

if (jNode.css( "position") == "static"){

// Set to relative so that we can actually

// move stuff around.

jNode.css( "position", "relative" );

}

// NOTE: When it comes to binding the functions

// below, notice that we are no longer referring

// to the THIS pointer - we are referring to the

// "scared" pointer. This is because at runtime,

// in that function, THIS will be dynamically

// linked to point to something else. Therefore,

// we refer to "scared" which, at run time will

// bind itself via the scope-chain to the current

// THIS which is the jQuery stack for scared.

// Bind the mouse over event to the element.

// The method that will be called will fire

// the event handler and will pass in the

// source jQuery object.

jNode.mouseover(

function(){

scared.OnMouseOver( jNode );

}

);

// Just incase the user is really fast, we

// want to bind the mouse move event to the

// same handler.

jNode.mousemove(

function(){

scared.OnMouseOver( jNode );

}

);

}

);

// Return the THIS pointer such that the jQuery chain

// is not broken.

return( this );

}

The jQuery plugin works by binding the onmouseover and onmousemove events to event handlers defined within the Scared function. Once the event handler fire, they pick a random direction and random distance and move the target node in such a way that it travels away from the cursor. Now, here's where I get a bit fuzzy - are these handlers now part of the jQuery object instance (which is what it seems like to me)? I am used to having objects with prototypes and shared functions, but something about this just confuses me at a core level. I feel like I am very close to getting it, I just need to bridge a small gap.

So that's my first jQuery plugin, any and all feedback is appreciated. I would love to really wrap my head around this process and be able to make some cool plugins.

Thank you so much for the feedback. I really like the idea of storing the event handlers as VAR'd variables (var OnMouseOver = ...). I totally didn't want to store anything in the THIS scope, so that would take care of the problem quite nicely. Sometimes I get so involved in the details, the little solutions like that don't occur to me.

Also, I didn't realize that you could just return the result of Each(). However, like you say, I do opt for a little more readability.

And, as far as the "scared" variable, this shouldn't be a problem because, like the event handlers you were suggesting, it is VAR'd to the function, not to the jQuery object itself (right??).

Sure, the scared variable isn't much of a problem, it is just obsolete, if you bind the handlers like I proposed. The only reason I see you created it, is because you needed something other then "this" to refer to, which is not required any longer.

You can do return this.each(), because this.each() also returns the this, it is the same chaining principle as in $('div').show().each(...) just from a different perspective (from the inside) so to say maybe.

Just a short note, nothing special, instead of $( "input[@type='button']" ) you can use the shorter and faster selector $("input:button") as described here: http://docs.jquery.com/Selectors#Form_Selectors

Quote: "Using :radio is mostly the same as [@type=radio], but should be slightly faster. Good to know when working with large forms."

I also agree with... Klaus Hartl that You can do return this.each(), because this.each() also returns the this, it is the same chaining principle as in $('div').show().each(...) just from a different perspective (from the inside) so to say maybe.

This is definitely true; but, at some point, we just have to balance readability and efficiency. I have found that returning (this) as a separate action makes things just a bit more understandable, at least for *me*.

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.