JavaScript Basics

Falsiness

''        // Empty string
0         // Number
undefined // Tricky
null      // Obvious
false     // Boolean
NaN       // Weirdness
var name; // Undefined
if (name) { ... } // Never runs
name = 'Big Mike';
if (name) { ... } // Runs
NaN !== NaN // This is the world we live in
isNaN('37') // False: can be parsed to a number
isNaN('asdfasdf') // True: cannot be parsed to a number
isNaN('') or isNaN(' ') // False: can be parsed as a number?
var secretDigit; // Undefined
if (secretDigit === undefined) { ... } // Maybe do some init of 'secretDigit'
undefined = 'I am no longer empty' // Bad, bad man
if (typeof secretDigit === 'undefined') { ... } // Much better

Truthiness

Pretty much everything else

Interesting Truthinesses

' '         // True: non-empty string
'null'      // True: non-empty string
'0'         // True: non-empty string
-1          // True: non-zero number
'undefined' // True: non-empty string
'false'     // True: non-empty string
[]          // True: Empty Array (use length property)
{}          // True: Empty Object (more complicated)
function isEmpty(obj) {
  for(var prop in obj) {
    if(obj.hasOwnProperty(prop)) return false;
  }
  return true;
}

var obj = {};
isEmpty(obj) // True

Comparison

===

0 === '0' // False

==

0 == '0' // True

Bang that (non)Boolean

  1. determine truthiness/falsiness
  2. flip the boolean value
if (0)  // Falsy
if (!0) // True
if (!1) // False

Coerce booleans

var label = ''; // Falsy
var opposite = !label; // true
var bool = !!label; // false

Conditionals

something || somethingElse || somethingElseElse // Evals left to right until something is true
something && somethingElse && somethingElseElse // Evals left to right until something is false

(isTrueOrFalse) ? something : somethingElse                   // Basic ternary
(ifTrueOrFalse) ? (something) ? minor : major : somethingElse // Nested
(ifTrueOrFalse)                                               // More legible?
  ? (something)
    ? minor
    : major
  :somethingElse

if (varName === 'foo' || varName === 'bar') {...} // Old and busted

switch (varName) {                                // More or less the same as the if method
  case 'foo':
  case 'bar':
    ...
    break;
}

if (/^(foo|bar)$/.test(varName)) {...}          // Better

if (({ foo: 1, bar: 1 })[varName]) {...}        // Mo Betta, Truthy
if (({ foo: 0, bar: 0 })[varName]) {...}        // Mo Betta, Falsy

if ((function(varName){...}())) {...}     // Questionable

if (someFunction(varName)) {...}          // Better

var cached = someFunction(varName);
if (cached) {...}                         // Better still (if applicable)

var cached;
if (cached = someFunction(varName)) {...} // Fun for the whole family

Functions

// Bad
function doinSumpin() {
  $('.someClass').each(function(){
    $(this).addClass('holas');
    $(this).bind('mouseover', function() {
      $(this).find('.child').bind('mouseover', function() {
        $(this).bind('click', function() {
          doSumpin(); // Incites rage
        });
      });
    });
  });
}

// Better
function doinSumpin() {
  $('.someClass')
    .addClass('holas')
    .bind('mouseover', onHover);
  });
}

function onHover(e) {
  $(e.target).find('.child').bind('mouseover', onChildHover);
}

function onChildHover(e) {
  $(e.target).bind('click', onClick);
}

function onClick(e) {
  doSumpin(); // Much less anger
}

Var Scope

Variable scope is defined at function level

Scopes are inherited

function() {
  var i = 1;

  function() {
    var j = 2;

    return i + j; // returns 3
  }
}

Cache vars to local scope

// array lengths
var l = arr.length;

// properties
var sumpin = object.subObject.sumpin;

// responses from functions
var isSomething = testSomething();

Var/Function Hoisting

function() {
  if (false) var x = 0;
  return;
  var y = 0;
}

might as well be:

function() {
  var x, y;
  if (false) x = 0;
  return;
  y = 0;
}

Closures

functions remember their environment

// Might be confusing if done accidentally
function counter() {
  var i = 0,
      addMe = function() {
        return i + 1;
      };
  i++;
  return addMe;
}

var countMe = counter();

countMe(); // returns 2
// Mini-module pattern
(function(){
  var foo = 'bar',
      showMe = function() {
        return foo;
      };

  window['showMe'] = showMe;
}());

showMe(); // returns 'bar'

iife

// feature detection
var canSupport = (function(d){
  if (!d['querySelectorAll']) return false; // supports qsa
  if (!d['addEventListener']) return false; // support w3c style event listeners
  if (!d['isSameNode']) return false; // supports isSameNode method
  if (!'onhashchange' in window) return false; // supports hash change event

  return true;
}(document));

if (canSupport) {...}

Global namespace

Avoid the global namespace

Object literal

// Define
var plugin = {
  variableName: '',
  init: function() {...},
  doSomething: function() {...}
};

// Usage
plugin.init();
plugin.doSomething();

Module pattern

// Define
(function(context) {
  var privateVar = 'keep it on the down low',
      privateMethod = function() {...},
      plugin = {
        publicVar: 'whip it out in public',
        publicMethod: function() {
          return privateVar;
        }
      };

  context['plugin'] = plugin;
}(namespace));

// Usage
plugin.publicVar // 'whip it out in public'
plugin.privateVar // undefined
plugin.privateMethod() // undefined
plugin.publicMethod() // returns 'keep it on the down low'

jQuery Basics

DOM

<ul id="parent">
  <li>List item 1</li>
  ...
</ul>
var list = document.getElementById('parent') // sets node to element with id of 'parent' (ul)
list.nodeType === 1 // it's an element (ul)
list.childNodes // array of immediate descendent nodes (all types)
list.childNodes[0].nodeType === 3 //the line break and tabs/spaces before the first list item

var listElems = [];
for (var i = 0; i < list.childNodes.length; i++) {
  if (list.childNodes[i].nodeType === 1) listElems.push(list.childNodes[i]);
}
// jQuery Style
$('#parent').children('li')

DOM Ready

// Old school method
if (addEventListener && document.addEventListener('DOMContentLoaded')) {
	... // listen for that event
} else if (attachEvent && document.attachEvent('onreadystatechange')) {
	... // listen for event and test if it starts with a 'c' (for complete)
} else {
	... // try to scroll the page to see if it will let you. if it will, then DOM is ready
}
// DOM ready: jQuery style
$(document).ready(function() {
  ... // Do something with the DOM
});
// better yet
var plugin = {
  init: function() {...},
  somethingElse: function() {...}
};

$(document).ready(plugin.init)

All jQuery events (including DOMReady) queue up, first in, first out.

jQuery Collections

var $links = $('a') // jQuery Collection
var link = $('a')[0] // DOM Element

// jQuery method
$links.addClass('some') // adds class to all the links in the collection
link.addClass('some') // fails, not a jQuery collection

// DOM property
$links.href // fails, not a DOM node
link.href // returns the href attribute value
$links.eq(0).attr('href') // way to get it with jQuery (obviously slower)

Chaining

<div class="some"></div>

$('.some').addClass('test').text('Test text').append('<p>Child</p>');

<div class="some test">
  Test text
  <p>Child</p>
</div>

Selectors

// sample selectors
$('#myId') // get the node with that id
$('.className') // get any node that has that class
$('div.className') // get any div that has that class. more efficient
$('ul li') // get the lis that are under uls, including nested ones
$('ul > li') // get the lis that are immediate children of uls
$(':input') // gets all inputs, textareas, selects & buttons
$('[type="email"]') // get all nodes with a type of email (new HTML form field type)
// selectors with context (& cached)
$('#myForm > :input') // slow

var $form = $('#myForm');
$form.find('> :input') // gets there, could be faster
$form.children(':input') // super fast

$(':input', $form) === $form.find(':input')

$('.data td.domain') // faster
$('table.data .domain') // slower

XHR (AJAX)

// convenience methods
$.get('/url', { requestParam: value }, function(d) {
  ... // success callback, d = response
});

$.getJSON('/url', { requestParam: value }, function(d) {
  ... // success callback, d = parsed json
});

$.post('/url', $form.serialize(), function(d) {
  ... // success callback, d = response
});
// Notable methods/properties
$.ajax({
  url: '/url',
  type: 'get', // you know as method, head and preflight work as well
  data: { request: param }, // or post payload, also, request=param as a string works
  dataType: 'json', // type of response data. Tries to parse based on type
  success: function(){}, // do something with the response
  error: function() {}, // This is an http error, not a data error (think 404)
  beforeSend: function() {
    $throbber.show();
  },
  complete: function() { // fires on success and error
    $throbber.hide();
  }
});

Cross Domain

// Applies to EVERY ajax request
$.ajaxPrefilter('json', function(options, orig, jqXHR) {
  if (options.crossDomain && !$.support.cors) return 'jsonp'
});

// fetch the data
$.ajax({
  url: 'http://somewhere.else',
  dataType: 'json',
  success: doSumpin
});

function doSumpin(d) {
  ... // Do something with that ajaxy goodness
}
// Use auth
$.ajax({
  ...
  xhrFields: {
    withCredentials: true // Sends cookies, auth & ssl certs
  }
})

Deferreds/Promises

// XHR
var get = $.get('/url');
get.done(function(d) { /* Do something with d */ });
...
get.done(function(d) { /* Do something else */ });
// Roll your own
var dfd = $.Deferred(); // setup deferred obj

function doSumpin(data) {...} // Do something with the data that was resolved

dfd.done(doSumpin) // setup doSumpin to fire once the deferred is resolved

...

dfd.resolve('value'); // resolve the deferred, triggering the done
// Chaining deferreds
var get1 = $.ajax( '/url1', { dataType: 'json' } ),
    get2 = function(data) {
      	return $.ajax( '/url2', { data: { user: data.userId } } );
    },
    chained = get1.pipe(get2);

chained.done(function( data ) { /* data retrieved from url2 as provided by the first request */ });

Events

$('a').bind('click.namespace', function(){ // namespace is an additional hook to get at event listener
  ...
});

$('form').bind('submit', function() {
  ... // do some of that fun filled form validation we all know and love
});

$('.nav-item').hover(function() {
  ... // mouseenter function
}, function() {
  ... // mouseleave function
});

$('.nav-item').hover(function() {
  ... // one function to rule them all (and handle both events)
});

$('button').toggle(function() { // alternate clicks between the callbacks
  ... // handle first click
}, function() {
  ... // handle second click
});

Triggering Events

// Simulate an event
$('.some').click();
$('.some').trigger('click');

// call the event handler
$('.some').triggerHandler('click');

Event Bubbling

<ul>        ↑
  <li>      ↑
    <a>  (click)
$('a').bind('click', function() { /* fires FIRST! */ });

$('li').bind('click', function() { /* fires second */ });

$('ul').bind('click', function() { /* fires third */ });

Event Stoppage

// Canceling behaviors
$('form').bind('submit', function(e) {
  e.preventDefault(); // stop form from submitting
});

$('a').bind('click', function(e) {
  e.stopPropagation(); // Stop click event from bubbling (rarely wanted)
});

$('a').bind('click', function(e) {
  return false; // stops bubbling and prevents default (usually not a good idea)
});

Event Delegation

// Non-delegated event listeners
$('a').each(function() {
  $(this).bind('click', function(){...}); // Each one of these has a memory footprint, 500 links = 500 event listeners
});
$('a').bind('click', function() {...}); // Same thing
// Delegated event listeners
$('a').live('click', function() {...}); // Delegated, 1 event listener
$(document).delegate('a', 'click', function() {...}); // Same thing

<document>      ↑
  <body>        ↑
    <a>      (click)
// Undelegate
$(document).undelegate('a', 'click');

Event Data

// Busted
for (var i = 0; i < 5; i++) {
  $('.some').bind('click', function() {
    alert(i); // 5 alerts, all of them say 5
  });
}
// Works
for (var i = 0; i < 5; i++) {
  $('.some').bind('click', { i: i }, function(e) {
    alert(e.data.i); // 5 alerts, in counting order from 0 to 4
  });
}

Custom Events

$('.some').bind('dinner.bell', function() {
  alert('yay!');
});

$('.some').triggerHandler('dinner');

inArray

var arr = [2, 3, 4, 5];
$.inArray(2, arr) // returns 0
$.inArray(7, arr) // returns -1
~$.inArray(7, arr) // return 0 (if you don't need index, just testing if is in array)

Extend

// Merge objects
$.extend(origObj, newObj) // merge newObj into origObj
var newObj = $.extend({}, origObj) // newObj is a copy of origObj
$.extend(true, origObj, newObj) // recursively merges newObj into origObj

jQuery Data

// Usage
$('#some').data('key', 'value'); // Setter
$('#some').data('key') // Getter: returns 'value'

var pluginData = {
  foo: 42,
  bar: 'holas'
};
$('.some').data('pluginName', pluginData);
$('.some').data('pluginName').bar // returns 'holas'

Check Length

Check the lengths of jQuery collections before running 3rd party plugins

$('.some').pluginName() // Can be expensive

// Plugin code
(function($) {
  $.fn.pluginName = function(options) {
    if (!this.length) return this; // this is the magic line
    ...
  };
  ...
})(jQuery);
// Safer usage
var $somes = $('.some');
if ($somes.length) $somes.pluginName();

jQuery plugin

Use when working with DOM elements/events, especially collections

// Define
(function($) {
  $.fn.pluginName = function(options) {
    if (!this.length) return this;

    var opts = $.extend({}, $.fn.pluginName.defaults, options);

    // Global init stuff goes here

    this.each(function() {
      var $this = $(this);
      // Per node code goes here
    });

    return this;
  };

  function privateFunction() {...}

  $.fn.pluginName.publicFunction = function() {...};

  $.fn.pluginName.defaults = {...};
})(jQuery);

// Usage
$('.someClass').pluginName(optsObj);

jQuery Widget

Use when state is needed

// Define
(function($){
  $.widget('wsm.pluginName', {
    options: {...},

    _create: function () {
      this; // plugin
      this.element; // jQuery collection of the single element it was called on
    },

    _private: function () {...},

    public: function () {...},

    destroy: function () {
      // Take out what you brought in
      $.Widget.prototype.destroy.call(this);
    }
  });
})(jQuery);

// Usage
$('.someClass').pluginName(optsObj);

When to use jQuery vs regular js

What I said to a bunch of .Net developers I work with:

TL;DR: Use jQuery when available

Unless you know the feature is supported by all the browsers we have to support, use jQuery. It ensures compatible fallback to all the browsers it supports (which supercedes our own support matrix).

What I say to you:

Find out if what you are doing can be done with straight JavaScript consistently, cross browser and with minimal additional abstractions/forking. Only then can you make the educated decision to use it or not.

Resources