Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Jul. 2008) with: Simon Free and Peter Bell

My First jQuery Plugin

By Ben Nadel on

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.

See the jQuery Scared Plugin Demo Here

Here is the HTML for the page:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>My First jQuery Plugin</title>
  •  
  • <script
  • type="text/javascript"
  • src="jquery-latest.pack.js">
  • </script>
  • <script
  • type="text/javascript"
  • src="jquery.scared.js">
  • </script>
  • <script type="text/javascript">
  •  
  • // When the document is ready for interation,
  • // initialize the buttons to make them scared.
  • $(
  • function(){
  • // Apply the scared plugin to all the
  • // matching elements in the returned
  • // jQuery stack object.
  • $( "input[@type='button']" ).scared();
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • My First jQuery Plugin
  • </h1>
  •  
  • <p>
  • Hey, try to click on the following buttons (you know you want to):
  • </p>
  •  
  • <p>
  • <input type="button" value="Click Me!" />
  • <input type="button" value="Touch Me!" />
  • <input type="button" value="Grope Me!" />
  • <input type="button" value="Fondle Me!" />
  • <input type="button" value="Caress Me!" />
  • </p>
  •  
  • </body>
  • </html>

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.

Tweet This Great article by @BenNadel - My First jQuery Plugin Thanks my man — you rock the party that rocks the body!



Reader Comments

Ben, I think you can simplify a few little things.

Instead of using two statements, you can just use (except you did that for readability):

return this.each(...);

(You could even use chaining in here, like return this.css(...).each() )

More important: I think it is not necessary and even potentially dangerous to attach the OnMouseOver function to the jQuery object.

You can just use the power of closures. Say you define the function like:

var OnMouseOver = function( jNode ){ ... }

you can later on bind it like:

jNode.mouseover(function(){
OnMouseOver( jNode );
});

This also saves you from the need to define the "scared" variable.

Potentially dangerous, because you may find yourself overwriting an internal jQuery function on that specific object, which would give you a really hard time of debugging.

Apart from that, my first plugin wasn't looking that good :-)

Happy coding, Klaus

Reply to this Comment

@Klaus,

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??).

Anyway, this is really helpful. Thanks a lot.

Reply to this Comment

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.

Reply to this Comment

Nice :)

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."

regards
thomas

Reply to this Comment

@Thomas,

Awesome tip! Still learning all this jQuery stuff. Gotta learn more about these selectors.

Reply to this Comment

I am also interested in jquery. I try to use your but it is not working on my machine......

Reply to this Comment

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.

Reply to this Comment

@Andy,

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*.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.