jQuery in 20 minutes (ish)

by Chip Brown

jQuery Essentials

"Write Less, Do More"

What is jQuery?

$ shortcut

$ = jQuery = window.jQuery = window.$

There are other libraries that use $ as a global designator, and jQuery can release it for the global scope.

The $ shortcut is convent, but you must be careful how you use it, especially when writing a plugin.

Include the jQuery library

For the basic jQuery

	<script type="text/javascript" src="/libs/jquery-1.7.1.min.js"></script>

Or hotlink to one of the many CDN hosts, like google

	<script type="text/javascript" 
	src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
	</script>

Launching code

Back in the old days, before jQuery, the only way to run code once the page was fully loaded was with onload

<script type="text/javascript">
	window.onload = function(){ 
		alert("Hello World!");
	}
</script>

But this isn't called until the entire page has downloaded, including images and the page has rendered for the first time.

Launching code

jQuery gives us the document ready function, which is called once the main content has been downloaded but before images have finished or the initial page render.

<script type="text/javascript">$(document).ready(function(){
	$(document).ready(function(){
alert("Hello World!"); }); </script>

Selectors

Selectors are the core of jQuery's coolness.

Use standard CSS style selectors to easily filter content to just what you need.

Additional selectors for even more power.

jQuery wrapper

All jQuery selectors return a wrapper, an instance of the jQuery object that contains a reference to all the native elements found by the selection.

HTML Snippet

	<div>
<h1>Headline</h1>
<p>Paragraph 1</p>
<p>Paragraph 1</p>
</div>

JavaScript

	$('p');

Selects all of the <p> tags (in this case two items)

Implicit iteration

The wrapper object automatically applies commands to everything in the set.

HTML Snippet

	<div class="inactive">
<h1>Headline</h1>
<p>Paragraph 1</p>
<p>Paragraph 1</p>
</div>

jQuery

	$('.inactive p').addClass('dim');

Adds the class dim to all of the paragraphs

The jQuery Chain

Almost all jQuery functions return either

a) the original set of elements or

b) a modified set of elements

within a jQuery wrapper unless explicitly documented otherwise.

Save space and time by chaining one function onto another.

The jQuery Chain

HTML Snippet

	<ul class="first">
<li class="foo">list item 1</li>
<li>list item 2</li>
<li class="bar">list item 3</li>
</ul>
<ul class="second">
<li class="foo">list item 1</li>
<li>list item 2</li>
<li class="bar">list item 3</li>
</ul>

jQuery

	$('ul.first').find('.foo').css('background-color', 'red')
.end().find('.bar').css('background-color', 'green');

The jQuery Chain

jQuery

	$('ul.first').find('.foo').css('background-color', 'red')
.end().find('.bar').css('background-color', 'green');

jQuery

	$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();

Special Effects

jQuery has many easy ways to add simple animations.

example

	$("#target").fadeOut();
	$("#target").fadeIn()
	$("#target").slideDown();

etc...

Call backs

A callback is a function that is called when something is finished.

For instance, here is a callback from the fadeout command.

	$("#prev").fadeOut( 1000, function(){
$("#next").fadeIn( 1000 ); });

JavaScript Refresher

Important points for jQuery

Anonymous functions

JavaScript can create functions in three ways:

Standard function definition:

	function myFunction ( argument1 ){ ... }

Assign the function to a variable

	var myFunction = function ( argument1 ){ ... }

Anonymous function

	function ( argument1 ){ ... }

Scope

Just remember, curly brackets { } define one block of scope.

	function scope1 () {
var outer = 'outer';
var scope2 = function () {
var inner = 'inner'
// outer == 'outer'
}
//inner == undefined
}

And 'this' has a different meaning inside each scope, even if they're right next to each other.

In normal JavaScript this isn't too confusing because there is usually a lot a space around functions blocks, but jQuery's heavy reliance on anonymous functions can lead to confusing situations.

As long as you're aware of waits happening, it's usually pretty easy to figure out what's going on.

The problem with this

We could talk a long time about scope and how this works. But for our purposes, just realize that:

JSON (JavaScript Object Notation)

A shorthand way of declaring objects and arrays.

[ ] designates an array (or a list of items stored by number 0 to X.)

	[ 'one', 'two', 'three' ]

{ } designates an object with name/values pares separated by a colon.

	{ name : 'value', second : 2 }

jQuery makes heavy use of JSON as a means of condensing the overall amount of code.

jQuery Plugins

In (20 - currentTime) minutes

Purpose of plugins

Goals for creating a plugin

jQuery Plugin at it's most basic

At it's core, a jQuery plugin is a function added to the jQuery.fn object

	jQuery.fn.myPlugin = function() {
	
		// Your plugin code goes here
	
	};

$, Get some closure

To avoid issues with conflicting use of the $ shortcut, it's best practice to create a "closure function". This is simply an anonymous function that calls itself and passes a reference to the jQuery object as it's only parameter.

	(function( $ ) {
		$.fn.myPlugin = function() {
		
			// Your plugin code goes here
	
		};
	})( jQuery );

It is then safe to use the $, and also creates a private namespace where you can create private functions hidden from the world.

The confusion of this

Inside of a jQuery plugin, sometimes this refers to the HTML object, sometimes the jQuery wrapper.

Simple rule of thumb:

Implicit Iteration

One of the core aspects of jQuery, so wouldn't it be nice to add it to your plugin?
Just target each from your main plugin function (the one defined on jQuery.fn)

	(function( $ ) {
		$.fn.fadedBox = function() {
			this.each(function() {
				$(this).css('opacity', .5 )
					   .css('boarder', '5px' );
			});
		};
	})( jQuery );

to process all elements of the wrapper set.

Don't be the weakest link

Maintain chainability by returning a reference to the jQuery object.

Simply return this unless you need to specifically return another value.

	(function( $ ) {
		$.fn.fadedBox = function() {
			return this.each(function() {
				$(this).css('opacity', .5 )
					   .css('boarder', '5px' );
			});
		};
	})( jQuery );

Defaults and Options the $.extend function

A handy way to combine two or more objects.

Best way to handle default values.

	var objectA = { name:'chip', height:6.5, favorateColor:'red' };
	var objectB = { quest:'explain jQuery', favorateColor:'blue' };
	var objectC = { favorateColor:'green' };
	var result = $.extend( {}, objectA, objectB, objectC );

After using $.extend, the result is now a combination of objectA, objectB & objectC, which is equal to:

	result = {
		name			: 'chip',
		height			: 6.5,
		favorateColor	: 'green',
		quest			: 'explain jQuery'
		}

Namespacing

Wrap it up and keep it safe.

It's best practice to wrap all of your functions inside your plugin namespace (the name of your plugin). That way you minimize conflicts with other plugins. To do this, first we move where we define our functionality.

	$.extend({
slidePresenter :{
defaults : {
// record default properties
},
init : function( options ) {

return this.each(function(){
// init code goes here
});
},
next : function () {
// code to go to the next goes here
}
}
});

Namespacing

So now our plugin function adds a bit of code that checks for the correct function, and calls it for us.

	$.fn.slidePresenter = function( method ) {
var sp = $.slidePresenter;

// Method calling logic
if ( sp[method] ) {
return sp[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return sp.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.slidePresenter' );
}

};

Namespacing

Now we can call our function one of two ways.

As a string to the main plugin:

	$('#target').slidePresenter('next'); 

Or as a method of the plugin:

	$('#target').slidePresenter.next();

Notice that we still give a target for the plugin.

Don't conflict with yourself

Use the .data() method to ensure that you don't create a problem.

This will allow multiple instances of the same plugin on the same page without overriding each other's settings, or creating circular references.


init : function( options ) {

return this.each(function(){

var $slideshow = $(this);
var data = $slideshow.data('slidePresenter');

// If the plugin hasn't been initialized yet
if ( ! data ) {

var settings = $.extend( true, {}, $.slidePresenter.defaults, options); // Init code goes here

// Store Data for this instance of the slideshow
$(this).data('slidePresenter', {
settings : settings
});

}
});
}

slidePresenter Plugin

This is the plugin that is running this presentation!

(Yes I wrote it just for this presentation.)

slidePresenter Plugin

Overview of plugin

;( function( $ ){

  // this is where most of the info goes
  $.extend({
    slidePresenter :{
  		...
    }
  });
  
  // Extend jQuery with our function
  $.fn.slidePresenter = function( method ) {
    ...
  };
  
  // Private functions
  function transition ( $slideshow, newSlide ){ ... }
  function initHotKeys ( $slideshow ){ ... }
  function initNavigation ( $slideshow ){ ... }
  function selectCurrentCrumb ( $slideshow ){ ... }
	
})( jQuery );

slidePresenter Plugin

Main plugin function

 
  // Extend jQuery with our function
  $.fn.slidePresenter = function( method ) {
    var sp = $.slidePresenter;
    
    // Method calling logic
    if ( sp[method] ) {
      return sp[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return sp.init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.slidePresenter' );
    }    
  
  };
  

slidePresenter Plugin

Extension object to apply namespace

  $.extend({
    slidePresenter :{
      defaults : {
        transition    : 'none',
        transitionSpeed  : 0,
        navID      : 'slidePresenterNav',
        showNavigation  : true,
        showBreadcrumbs  : true,
        loop      : false,
        hotkeys : {
          next   : [39,40,32],  // right & down arrow, spacebar
          previous : [37,38]  // up & left arrow
        }
      },
      init : function( options ) { 
        
        return this.each(function(){
        
          var $slideshow = $(this);
          var data = $slideshow.data('slidePresenter');
          
          // If the plugin hasn't been initialized yet
          if ( ! data ) {
            
            var settings = $.extend( true, {}, 
                $.slidePresenter.defaults, 
                options);
            var slides = [];
            var current = 0;
            var total = 0;
            
            // Init Slides
            $slideshow.children().hide().each(function(){
              slides.push( $(this) );
            });
            slides[current].show();
            $slideshow.addClass('active');
            
            // Store Data for this instance of the slideshow
            $(this).data('slidePresenter', {
              settings  : settings,
              slides    : slides,
              current    : current,
              animating  : false
            });
            
            // Init Nav elements
            initNavigation( $slideshow );
            initHotKeys( $slideshow );
            
          }
        });
      },
      next : function () {
        var data = this.data('slidePresenter');
        return this.slidePresenter('goTo', data.current+1 );
      },
      previous : function () {
        var data = this.data('slidePresenter');
        return this.slidePresenter('goTo', data.current-1 );
      },
      goTo : function ( number ) {
        var data = this.data('slidePresenter');
        var settings = data.settings;
        var slides = data.slides;
        
        // Constrain to valid slides
        if ( number<0 ){
          number = ( settings.loop ? slides.length-1 : 0 );
        } else if ( number>=data.slides.length ) {
          number = ( settings.loop ? 0 : slides.length-1 );
        }
        if ( data.current!=number && data.animating==false ){
          transition(this, number);
          selectCurrentCrumb(this);
        }
        return this;
      }
    }
  });

slidePresenter Plugin

Transition function


function transition ( $slideshow, newSlide ){
var data = $slideshow.data('slidePresenter');

var prev = data.slides[data.current];
var next = data.slides[newSlide];
data.current = newSlide;

var duration = data.settings.transitionSpeed/2;
var simultaneous = false;
var nextCSS = {};
var prevCSS = {};
switch ( data.settings.transition ){
case 'crossfade':
duration *= 2;
simultaneous = true;
case 'fade':
// prep next slide for animation
next.show();
next.css('opacity',0);
// CSS values to transition to
nextCSS = {
opacity: 1
};
prevCSS = {
opacity: 0
};
break;
case 'none':
default:
prev.hide();
next.show();
return $slideshow;
}

data.animating = true;

// define the animation for the next slide
var animateNext = function (){
next.animate(
nextCSS,
duration,
function (){
data.animating = false;
}
);

}

// Transition out the previous slide
prev.animate(
prevCSS,
duration,
function (){
prev.hide();
if (!simultaneous) animateNext();
}
);
if ( simultaneous ) animateNext();

return $slideshow;
}

slidePresenter Plugin

Hotkey navigation


// setup hotkey functionality for keyboard navigation
function initHotKeys ( $slideshow ){
var data = $slideshow.data('slidePresenter');
var settings = data.settings.hotkeys;
// make a map of keyCodes and actions
var hotKeys = [];
$.each(settings, function( action, keys ){
if ( $.slidePresenter[action] ){
$.each( keys, function( index, keynum ){
hotKeys[keynum] = action;
});
}
});
// register key event for the browser window
$(window).keydown(function (e){
if ( hotKeys[e.keyCode] ){
$slideshow.slidePresenter( hotKeys[e.keyCode] );
e.preventDefault();
}
});
}

slidePresenter Plugin

Build the navigation

// Create the navigation section
function initNavigation ( $slideshow )
{
var data = $slideshow.data('slidePresenter');
var settings = data.settings;

if ( settings.showNavigation ){

var makeBtn = function ( action, num ){
return $('&lt;div /&gt;').attr( 'id' , ( num ? action+'_'+num : action ) )
.append( num ? num : action )
.wrapInner('&lt;a href=&quot;#&quot;&gt;&lt;span /&gt;&lt;/a&gt;')
.click( function( e ){
$slideshow.slidePresenter( action, num-1 );
e.preventDefault();
});
}

var $nav = $('&lt;div id=&quot;'+settings.navID+'&quot;&gt;');
if ( settings.showBreadcrumbs ){
$.each( data.slides, function ( index, value ){
$nav.append(makeBtn('goTo',index+1));
});
}
$nav.prepend(makeBtn('previous'))
.append(makeBtn('next'));

$('body').append( $nav );

selectCurrentCrumb($slideshow);
}
}

slidePresenter Plugin

Select the breadcrumb

 function selectCurrentCrumb ( $slideshow )
{
var data = $slideshow.data('slidePresenter');
$('#'+data.settings.navID)
.children()
.removeClass('selected')
.end()
.children('#goTo_'+(data.current+1))
.addClass('selected');
}

Summary

Always seek closure, wrap it up (function ( $) { //stuff })(jQuery);

Return 'this' to maintain the chain if at all possible.

Pass a single argument object for ease of extending

Only one jQuery.fn namespace per plugin

Add namespace to methods, events & data for added protection.

Extras

Minification

Google CDN (Content Delivery Network)

Firebug and Safari developer Console