by Elijah Manor / @elijahmanor
{
"name": "Elijah Manor",
"priorities" : [ "Christian", "Family", "Work" ],
"employer" : "Manorism, Inc.",
"title" : [ "Developer", "Trainer", "Pluralsight Author" ],
"tech" : [ "JavaScript", "jQuery", "HTML5", "CSS3" ],
"awards" : [
"Microsoft Regional Director",
"Microsoft ASP.NET MVP",
"ASPInsider",
"IE userAgent"
],
"blog": "http://elijahmanor.com",
"email": "elijah.manor@gmail.com",
"social": [ "@elijahmanor" ]
}
→
→
Attacks with the force of its trusty IIFE, the basic block of all privacy
Triggers events and messages that scatter to infiltrate the pig's castle
Comes with a RequireJS speed booster and dynamically injects scripts against those pesky swine
Proves to be a much more organized approach to fighting these porkers and introduces the Backbone.js bomb to their dismay
Appears to be seemingly harmless, but when it pulls out it's strict coding style and bursts of quality checks the hogs are sure to squeal
Can reach all of those hard to reach places and will mock and spy those stealing swine right where it hurts!
Starts out small with a simple template, but then expands itself into a DOM blast that will surely send the message that the birds mean business
Pulls out the big guns with his finite state machine and other proven design patterns of destruction
Uses the most superior weapon of them all, a suite of tools that can organize and deploy all the other birds into battle against their soon to be vanquished foe
→
Attacks with the force of its trusty IIFE, the basic block of all privacy
For ages the birds used to litter the global namespace with their custom objects and functions. Over time the birds slowly learned techniques to protect their objects from the global namespace.
1. Declaring an Object in the Window Scope
var type = "Red",
attack = function() {
console.log( type + " Bird Attacks!" );
};
console.log( window.type ); // I'm a global variable
console.log( window.attack ); // I'm a global function
2. Not Declaring an Object in Any Scope
var type = "Red",
attack = function() {
typeOfBird = type + " Bird";
console.log( birdType + " Bird Attacks!" );
};
console.log( window.type ); // I'm a global variable
console.log( window.attack ); // I'm a global function
console.log( window.typeOfBird ); // I'm a global variable too :(
3. Specifically Adding an Object to the Window
var type = "Red",
attack = function() {
typeOfBird = type + " Bird";
window.message = typeOfBird + " Attacks!";
console.log( birdType + " Bird Attacks!" );
};
console.log( window.type ); // I'm a global variable
console.log( window.attack ); // I'm a global function
console.log( window.typeOfBird ); // I'm a global variable too :(
console.log( window.message ); // I'm a global variable too :|
// Gather type & attack and make properties off higher level object
var bird = {
type: "Red",
attack: function() {
console.log( this.type + " Bird Attacks!" );
}
};
console.log( window.bird ); // Only 1 global object!
console.log( window.bird.type ); // Red
console.log( window.bird.attack() ); // Red Bird Attacks!
// JavaScript can parse this just fine now, yay!
(function() {
// All memory is contained within this scope
}()); // <-- Immediately Invoked
// Revealing Module Pattern
var bird = (function() {
var type = "Red",
power = "IIFE", // This is private
attack = function() {
console.log(type + " Bird Attacks with " + power + "!");
};
// Only the items returned are public
return { type: type, attack: attack };
}());
console.log( window.bird ); // Only 1 global object!
console.log( window.bird.type ); // Public property
console.log( window.bird.attack() ); // Public method access private
console.log( window.bird.power ); // Private var, can't access
(function( bird ) {
var power = "IIFE"; // This is private
bird.type = "Red";
bird.attack = function() {
console.log( bird.type + " Bird Attacks with " + power + "!" );
};
}( window.bird = window.bird || {} ));
console.log( window.bird ); // Only 1 global object!
console.log( window.bird.type ); // Public property
console.log( window.bird.attack() ); // Public method accessing private
console.log( window.bird.power ); // Private variable, can't access
Triggers events and messages that scatter to infiltrate the pig's castle
The birds used to build their web applications with components having hard dependencies on each-other. They eventually started to learn to reduce tight coupling by introducing events and messages.
$( document ).on( "click", ".term", function( e ) {
$( "input" ).val( $( this ).text() );
$( "button" ).trigger( "click" );
});
$( "button" ).on( "click", function( e ) {
var searchTerm = $( "input" ).val(),
url = "http://odata.netflix.com/Catalog/Titles?$filter=substringof('" +
escape( searchTerm ) + "',Name)&$callback=callback&$format=json";
$( ".help-block" ).html( function( index, html ) {
return e.originalEvent ?
html + ", " + "<a href='#' class='term'>" + searchTerm + "</a>" : html;
});
$.ajax({
dataType: "jsonp",
url: url,
jsonpCallback: "callback",
success: function( data ) {
var rows = [];
$.each( data.d.results, function( index, result ) {
var row = "";
if ( result.Rating && result.ReleaseYear ) {
row += "" + result.Name + " ";
row += "" + result.Rating + " ";
row += "" + result.ReleaseYear + " ";
row = "" + row + " ";
rows.push( row );
}
});
$( "table" ).show().find( "tbody" ).html( rows.join( "" ) );
}
});
});
document.getElementById( "bird" )
// Native addEventListener attaches observer to the DOM element
.addEventListener( "click", function() { console.log( "Catapult!" ); }, false );
$( "#bird" )
// Old school event helpers attaches observer to the DOM element
.click( function() { console.log( "Flying through the air..." ); } )
// Old school bind method attaches observer to the DOM element
.bind( "click", function() { console.log( "COWABUNGA!" ); } )
// New school 2 parameter on method attaches observer to the DOM element
.on( "click", function() { console.log( "Destroy those pesky pigs!" ); } );
// Event is triggered and the list of observers are notified
$( "#bird" ).trigger( "click" );
var channel = postal.channel(),
$lastUpdated = $( "#lastUpdated" );
// Subscribe to all bird.launch messages
channel.subscribe( "bird.launch", function( data ) {
console.log( "Launch the blue birds at a " + data.angle + " angle!" );
});
// Subscribe to all bird.reset messages
channel.subscribe( "bird.reset", function( data ) {
console.log( "Resetting blue birds to the catapult." );
});
// Subscribe to all messages that match the bird.* wildcard!
channel.subscribe( "bird.*", function( data ) {
$lastUpdated.text( moment().format( "MMMM Do YYYY, h:mm:ss a" ) );
});
// Publish some messages with optional data
channel.publish( "bird.launch", { angle: 45 } );
channel.publish( "bird.reset" );
// Observer is attached to the #pigs element where impact events are delegated
$( "#pigs" ).on( "impact", ".pig", function( e ) {
console.log( "I know which pig was impacted: " + e.target.innerHTML );
console.log( "I know where the subscribers are listed: " + e.delegateTarget.id );
console.log( "I can invoke another subscriber if I want!" );
$._data( e.delegateTarget, "events" ).secret[ 0 ].handler( e );
$( this ).text( "Bacon" );
});
$( "#pigs" ).on( "secret", ".pig", function( e ) {
console.log( "Shh, I'm hiding. Don't tell anyone..." );
});
// Event is triggers on the .pig element and bubble up to the #pigs element
$( ".pig:first" ).trigger( "impact" );
"...use observer 'locally', inside a component, mediator 'remotely' between components. No matter what, be ready to use both in tandem.' --Jim Cowart
var channel = postal.channel();
$( "button" ).on( "click", function() {
channel.publish( "searchTerm.changed", { term: $( "input" ).val() } );
});
channel.subscribe( "searchTerm.changed", function( data ) {
netflix.getTitles( data.term, function( titles ) {
channel.publish( "netflix.titles.updated", titles );
});
});
channel.subscribe( "searchTerm.changed", function( data ) {
$( ".help-block" ).html( function( index, html ) {
return ~html.indexOf( data.term ) ? html :
html + ", " + "<a href='#' class='term'>" + data.term + "</a>";
});
});
channel.subscribe( "netflix.titles.updated", function( titles ) {
var rows = [];
$.each( titles, function( index, result ) {
var row = "";
if ( result.Rating && result.ReleaseYear ) {
row += "<td>" + result.Name + "</td>";
row += "<td>" + result.Rating + "</td>";
row += "<td>" + result.ReleaseYear + "</td>";
row = "<tr>" + row + "</tr>";
rows.push( row );
}
});
$( "table" ).show().find( "tbody" ).html( rows.join( "" ) );
});
$( document ).on( "click", ".term", function( e ) {
var term = $( this ).text();
e.preventDefault();
$( "input" ).val( term );
channel.publish( "searchTerm.changed", { term: term } );
});
window.netflix = {
getTitles: function( term, callback ) {
var url = "http://odata.netflix.com/Catalog/Titles?$filter=substringof('" +
escape( term ) + "',Name)&$callback=callback&$format=json";
$.ajax({
dataType: "jsonp",
url: url,
jsonpCallback: "callback",
success: function( data ) { callback( data.d.results ); }
});
}
};
→
Comes with a RequireJS speed booster and dynamically injects scripts against those pesky swine
The birds used to manually add script tags to their HTML files. They were introduced to RequireJS which provided them a way to...
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Angry Birds</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<script src="./libs/jquery.min.js"></script>
<script src="./libs/postal.min.js"></script>
<script src="./libs/underscore.min.js"></script>
<script>
var channel = postal.channel();
channel.subscribe( "pig.collide", function() {
console.log( "Pig Down!" );
});
channel.publish( "pig.collide" );
</script>
</body>
</html>
WHAT!?! I don't see any each method anywhere. What's up with that? Ohh man, it looks like the exception occurred in postal.min.js somewhere. FOUND A BUG... see if I use that library again. But, wait!?! Ohh, maybe something else is going on here. --Ficticious Internal Dialog
Asynchronous Module Loader and the API it provides allows us to define and require modules.
// File Name: my-first-module.js
// The name of the module defaults to the name of the file by convention
define(
[ "underscore", "jquery" ], // Array of dependencies needed for this module
function( _, $ ) { // Callback with parameters matching dependencies requested
// Underscore and jQuery have both been loaded and are available for use with
// the `_` & `$` variables
return { // This will be available in callback of whoever requires this module
message: "Hello World!"
};
}
);
require(
[ "my-first-module" ], // Array of dependencies that are needed
function( firstModule ) { // Callback with parameters matching dependencies
console.log( firstModule.message );
}
);
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Angry Birds</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<script src="./libs/require.min.js" data-main="./js/main"></script>
</body>
</html>
/* main.js */
// Let RequireJS know where all the scripts are
require.config({
paths: {
"jquery": "../libs/jquery.min",
"underscore": "../libs/underscore.min",
"postal": "../libs/postal.min"
},
shim: {
// Underscore.js doesn't know about AMD, so you have to shim it
underscore: {
exports: "_"
}
}
});
// The postal.js library internally defined that it needs the underscore library
// so RequireJS will load postal, which in turn will load its underscore
require([ "postal" ], function( postal ) {
var channel = postal.channel();
channel.subscribe( "pig.collide", function() {
console.log( "Pig Down!" );
});
channel.publish( "pig.collide" );
});
({
appDir: ".", // The main root URL
dir: "../dist", // Directory that we build to
mainConfigFile: "main.js", // Location of main.js
name: "main", // Name of the module that we are loading
optimizeCss: "standard", // Standard optimization for CSS
removeCombined: true, // Temporary combined files will be removed
paths : {
"jquery": "libs/jquery.min",
"underscore": "libs/underscore.min",
"postal": "libs/postal.min"
}
})
$ r.js -o build.js
Optimizing (standard) CSS file: C:/Users/Elijah/Desktop/demo/dist/css/style.css
Tracing dependencies for: main
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/build.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/libs/jquery.min.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/libs/postal.min.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/libs/require.min.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/libs/underscore.min.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/main.js
Uglifying file: C:/Users/Elijah/Desktop/demo/dist/r.js
css/style.css
----------------
css/style.css
C:/Users/Elijah/Desktop/demo/src/main.js
----------------
C:/Users/Elijah/Desktop/demo/src/main.js
→
Proves to be a much more organized approach to fighting these porkers and introduces the Backbone.js bomb to their dismay
The birds used to write their jQuery code like it was a tangled smorgasbord of worms. A Black Bird introduced the Backbone.js library and showed them a different way to think about developing front-end web applications
$( document ).on( "click", ".term", function( e ) {
e.preventDefault();
$( "input" ).val( $( this ).text() );
$( "button" ).trigger( "click" );
});
$( document ).ready( function() {
$( "button" ).on( "click", function( e ) {
var searchTerm = $( "input" ).val();
var url = "http://odata.netflix.com/Catalog/Titles?$filter=substringof('" +
escape( searchTerm ) + "',Name)&$callback=callback&$format=json";
$( ".help-block" ).html( function( index, html ) {
return e.originalEvent ? html + ", " + "<a href='#' class='term'>" +
searchTerm + "</a>" : html;
});
$.ajax({
dataType: "jsonp",
url: url,
jsonpCallback: "callback",
success: function( data ) {
var rows = [];
$.each( data.d.results, function( index, result ) {
var row = "";
if ( result.Rating && result.ReleaseYear ) {
row += "<td>" + result.Name + "</td>";
row += "<td>" + result.Rating + "</td>";
row += "<td>" + result.ReleaseYear + "</td>";
row = "<tr>" + row + "</tr>";
rows.push( row );
}
});
$( "table" ).show().find( "tbody" ).html( rows.join( "" ) );
}
});
e.preventDefault();
});
});
Let's take a stab at refactoring the above jQuery mess and use Backbone.js to split out some of the various concerns.
require.config({
paths: {
jquery: "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min",
underscore: "http://underscorejs.org/underscore",
backbone: "http://backbonejs.org/backbone",
postal: "http://cdnjs.cloudflare.com/ajax/libs/postal.js/0.8.2/postal.min",
bootstrap: "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.0/js/bootstrap.min"
},
shim: {
underscore: {
exports: "_"
},
backbone: {
deps: [ "jquery", "underscore" ],
exports: "Backbone"
},
bootstrap: {
"deps" : [ "jquery" ]
}
}
});
require( [ "jquery", "search-view", "search", "movie-view", "movies" ],
function( $, SearchView, Search, MovieView, Movies ) {
$( document ).ready( function() {
var searchView = new SearchView({
el: $( "#search" ),
model: new Search()
});
var movieView = new MovieView({
el: $( "#output" ),
collection: new Movies()
});
});
});
define( [ "backbone", "channels" ], function( Backbone, channels ) {
var Model = Backbone.Model.extend({
initialize: function() {
this.on( "change:term", function( model, value ) {
channels.bus.publish( "search.term.changed", { term: value } );
});
}
});
return Model;
});
define( [ "backbone" ], function( Backbone ) {
var Model = Backbone.Model.extend({
defaults: { term: "" },
parse: function( data, xhr ) {
return {
releaseYear: data.ReleaseYear,
rating: data.Rating,
name: data.Name
};
}
});
return Model;
});
define( [ "backbone", "movie" ], function( Backbone, Movie ) {
var Collection = Backbone.Collection.extend({
model: Movie,
url: function() {
return "http://odata.netflix.com/Catalog/Titles?$filter=substringof('" +
escape( this.term ) + "',Name)&$callback=callback&$format=json";
},
parse : function( data, xhr ) {
return data.d.results;
},
sync: function( method, model, options ) {
options.dataType = "jsonp";
options.jsonpCallback = "callback";
return Backbone.sync( method, model, options );
}
});
return Collection;
});
define( [ "jquery", "backbone", "underscore", "channels" ],
function( $, Backbone ) {
var View = Backbone.View.extend({
events: {
"click button": "searchByInput",
"click .term": "searchByHistory"
},
initialize: function() {
this.model.on( "change", this.updateHistory, this );
this.model.on( "change", this.updateInput, this );
},
searchByInput: function( e ) {
e.preventDefault();
this.model.set( "term", this.$( "input" ).val() );
},
searchByHistory: function( e ) {
var text = $( e.target ).text();
this.model.set( "term", text );
},
updateHistory: function() {
var that = this;
this.$el.find( ".help-block" ).html( function(index, html) {
var term = that.model.get( "term" );
return ~html.indexOf( term ) ? html :
html + ", " + "" + term + "";
});
},
updateInput: function() {
this.$el.find( "input" ).val( this.model.get("term") );
}
});
return View;
});
define( [ "jquery", "backbone", "underscore", "channels", "text!movie-template.html" ],
function( $, Backbone, _, channels, template ) {
var View = Backbone.View.extend({
template: _.template( template ),
initialize: function() {
var that = this;
_.bindAll( this, "render" );
channels.bus.subscribe( "search.term.changed", function( data ) {
that.collection.term = data.term;
that.collection.fetch({
success: that.render
});
});
},
render: function() {
var html = this.template({ movies: this.collection.toJSON() });
this.$el.show().find( "tbody" ).html( html );
}
});
return View;
});
<% _.each( movies, function( movie ) { %>
<tr>
<td><%= movie.name %></td>
<td><%= movie.rating %></td>
<td><%= movie.releaseYear %></td>
</tr>
<% }); %>
→
Appears to be seemingly harmless, but when it pulls out it's strict coding style and bursts of quality checks the hogs are sure to squeal
Each bird thought their coding standard was the "right way" and it started to become an issue. One day a wise White Bird came along and suggested that they come up with a common set of coding practices & tools to use.
/*jshint maxparams:3, maxdepth:2, maxstatements:5, maxcomplexity:3, maxlen:80 */
/*global console:false */
(function( undefined ) {
"use strict";
function test1( arg1, arg2, arg3, arg4 ) {
console.log( "too many parameters!" );
if ( arg1 === 1 ) {
console.log( arg1 );
if ( arg2 === 2 ) {
console.log( arg2 );
if( arg3 === 3 ) {
console.log( "too much nesting!" ); console.log( arg3 ); console.log( arg4 );
}
}
}
console.log( "too many statements!" );
}
test1( 1, 2, 3, 4 );
}());
Plato that will analyse your code and provide a visual report where you can view the complexity of your application.
→
Can reach all of those hard to reach places and will mock and spy those stealing swine right where it hurts!
The birds have a symbiotic relationship with Water Buffalo (birds write front-end & buffalo writes back-end). A Green Bird proposed the idea of mocking.
(function( twitter, $, undefined ) {
var channel = twitter.channel = postal.channel(),
URL_TEMPLATE = "https://api.twitter.com/1/statuses/user_timeline/" +
"%(userName)s.json?count=%(count)s&include_rts=1",
$selection = null;
twitter.selector = null;
twitter.init = function( selector ) {
twitter.selector = selector;
channel.subscribe( "tweets.available", twitter.displayTweets );
};
twitter.displayTweets = function( tweets ) {
var $list = $( "<ul/ >" ),
$location = $selection || $( twitter.selector );
// This would be better suited for a templating engine,
// but that's for another Angry Bird ;)
$.each( tweets || {}, function( index, tweet ) {
var html = "<i>" + moment( tweet.created_at ).fromNow() + "</i>: ";
html += "<b>" + tweet.user.name + "</b> - ";
html += tweet.text;
html += tweet.retweeted ? " <i class='icon-repeat'></i>" : "";
html += tweet.favorited ? " <i class='icon-star'></i>" : "";
$( "<li />", { html: html }).appendTo( $list );
});
$location.append( $list.children() );
};
twitter.getTweets = function( userName, count ) {
var url = _.string.sprintf( URL_TEMPLATE, {
userName: userName,
count: count || 5
});
$.ajax({
url: url,
dataType: "jsonp",
success: function( tweets ) {
channel.publish( "tweets.available", tweets );
}
});
};
}( window.twitter = window.twitter || {}, jQuery ));
twitter.init( ".tweets" );
$( document ).on( "click", "button", function( e ) {
var $input = $( this ).closest( "form" ).find( "input" );
e.preventDefault();
twitter.getTweets( $input.val() || "elijahmanor" );
});
A library to mock jQuery Ajax requests
$.mockjax({
url: "https://api.twitter.com/1/statuses/user_timeline/*",
responseTime: 750,
responseText: [
{ id: 0, created_at: "Mon Apr 11 8:00:00 +0000 2012", text: "Test Tweet 1",
favorited: false, retweeted: false, user: { name: "User 1" } },
{ id: 1, created_at: "Mon Apr 11 9:00:00 +0000 2012", text: "Test Tweet 2",
favorited: true, retweeted: true, user: { name: "User 2" } },
{ id: 2, created_at: "Mon Apr 11 10:00:00 +0000 2012", text: "Test Tweet 3",
favorited: false, retweeted: true, user: { name: "User 3" } },
{ id: 3, created_at: "Mon Apr 11 11:00:00 +0000 2012", text: "Test Tweet 4",
favorited: true, retweeted: false, user: { name: "User 4" } },
{ id: 4, created_at: "Mon Apr 11 12:00:00 +0000 2012", text: "Test Tweet 5",
favorited: true, retweeted: true, user: { name: "User 5" } }
]
});
A library to generated random data based on a template
$.mockjax({
url: "https://api.twitter.com/1/statuses/user_timeline/*",
responseTime: 750,
response: function() {
var data = $.mockJSON.generateFromTemplate({
"tweets|5-10": [{
"id|+1": 0,
"created_at": "Mon Apr 11 @TIME_HH:@TIME_MM:@TIME_SS +0000 2012",
"text": "@LOREM_IPSUM",
"favorited|0-1": false,
"retweeted|0-1": false,
"user": { "name": "@MALE_FIRST_NAME @LAST_NAME" }
}]
});
this.responseText = data.tweets;
}
});
→
Starts out small with a simple template, but then expands itself into a DOM blast that will surely send the message that the birds mean business
The birds found themselves using string concatenation to build up rich user interfaces, which resulted in a lot of code that was boring and also prone for errors. Thankfully an Orange Bird came along and introduced templating libraries such as Underscore.js and Handlebar.js.
(function( twitter, $, undefined ) {
var _selection;
twitter.init = function( $selection ) {
_selection = $selection;
};
twitter.displayTweets = function( tweets ) {
var $list = $( "<ul/ >" );
$.each( tweets || {}, function( index, tweet ) {
var html = "<i>" + moment( tweet.created_at ).fromNow() + "</i>: ";
html += "<b>" + tweet.user.name + "</b> - ";
html += "<span>" + tweet.text + "</span>";
html += tweet.retweeted ? " <i class='icon-repeat'></i>" : "";
html += tweet.favorited ? " <i class='icon-star'></i>" : "";
$( "<li />", { html: html }).appendTo( $list );
});
_selection.empty().append( $list.children() );
};
}( window.twitter = window.twitter || {}, jQuery ));
twitter.displayTweets = function( tweets ) {
var templateString = $( "#tweets-underscore" ).html(),
template = _.template( templateString );
_selection.empty().append( template( { tweets: tweets } ) );
};
<script id="tweets-underscore" type="text/template"> <ul> <% _.each( tweets, function( tweet ) { %> <li> <i><%= moment( tweet.created_at ).fromNow() %></i>: <b><%= tweet.user.name %></b> - <span><%= tweet.text %></span> <% if ( tweet.retweeted ) { %><i class="icon-repeat"></i><% } %> <% if ( tweet.favorited ) { %><i class="icon-star"></i><% } %> </li> <% }); %> </ul> </script>
twitter.displayTweets = function( tweets ) {
var templateString = $( "#tweets-underscore" ).html(),
template = _.template( templateString );
tweets = _.map( tweets, function( tweet ) {
tweet.created_at = moment( tweet.created_at ).fromNow();
return tweet;
});
_selection.empty().append( template( { tweets: tweets } ) );
};
<script id="tweets-underscore" type="text/template"> <ul> <% _.each( tweets, function( tweet ) { %> <li> <i><%= tweet.created_at %></i>: <b><%= tweet.user.name %></b> - <span><%= tweet.text %></span> <% if ( tweet.retweeted ) { %><i class="icon-repeat"></i><% } %> <% if ( tweet.favorited ) { %><i class="icon-star"></i><% } %> </li> <% }); %> </ul> </script>
twitter.init = function( $selection ) {
_selection = $selection;
Handlebars.registerHelper( "fromNow", function( time ) {
return moment( time ).fromNow();
});
};
twitter.displayTweets = function( tweets ) {
var templateString = $( "#tweets-handlebars" ).html(),
template = Handlebars.compile( templateString );
_selection.empty().append( template( tweets ) );
};
<script id="tweets-handlebars" type="text/x-handlebars-template"> <ul> {{#each this}} <li> <i>{{fromNow this.created_at}}</i>: <b>{{this.user.name}}</b> - <span>{{this.text}}</span> {{#if this.retweeted}}<i class="icon-repeat"></i>{{/if}} {{#if this.favorited}}<i class="icon-star"></i>{{/if}} </li> {{/each}} </ul> </script>
→
Pulls out the big guns with his finite state machine and other proven design patterns of destruction
The birds knew how to program, but they never had a common nomenclature that they all understood. Big Brother Bird came along and documented a set of common Design Patterns in a book lovingly known as the Gang of ./img/.
There can be only one!
var bird = {
type: "Red",
fly: function() {
console.log( "Weeeee!" );
},
destroy: function() {
console.log( "Hasta la vista, baby!" );
}
};
A factory is a way to create new objects without actually using the new keyword. The idea is that there is something abstracted away from you in the factory method.
var Bird = function() {};
Bird.factory = function( type ) {
var bird;
if ( typeof Bird[ type ] === "function" ) {
bird = new Bird[ type ]();
}
return bird;
};
Bird.Red = function() {};
Bird.Blue = function() {};
var redBird = Bird.factory( "Red" );
var blueBird = Bird.factor( "Blue" );
decouple an abstraction from its implementation so that the two can vary independently --http://en.wikipedia.org/wiki/Bridge_pattern
// Not Bridged
var getUrl = function() {
var url = $( this ).attr( "href" );
$.ajax({
url: url,
success: function( data ) {
console.log( data );
}
});
};
$( "a.ajax" ).on( "click", getUrl );
// Bridged
var getUrl = function( url, callback ) {
$.ajax({
url: url,
success: function( data ) {
if ( callback ) { callback( data ); }
}
});
};
var getUrlBridge = function() {
var url = $( this ).attr( "href" );
getUrl( url, function( data ) {
console.log( data );
});
}
$( "a.ajax" ).on( "click", getUrlBridge );
A facade is common place in front-end web development since there is so much cross-browser inconsistencies. A facade brings a common API to something that could vary under the covers.
// Facade
var addEvent = function( element, type, eventHandler ) {
if ( element.addEventListener ) {
element.addEventListener( type, eventHandler, false );
} else if ( elemement.attachEvent ) {
element.attachEvent( "on" + type, eventHandler );
}
};
An adapter is a nice way to massage one piece of code to work with another piece of code. This can be useful when you need to switch to another library, but can't afford to rewrite much of your code.
/*!
* jquery-win8-deferred - jQuery $.when that understands WinJS.promise
* version: 0.1
* author: appendTo, LLC
* copyright: 2012
* license: MIT (http://www.opensource.org/licenses/mit-license)
* date: Thu, 01 Nov 2012 07:38:13 GMT
*/
(function () {
var $when = $.when;
$.when = function () {
var args = Array.prototype.slice.call(arguments);
args = $.map(args, function (arg) {
if (arg instanceof WinJS.Promise) {
arg = $.Deferred(function (dfd) {
arg.then(
function complete() {
dfd.resolveWith(this, arguments);
}, function error() {
dfd.rejectWith(this, arguments);
}, function progress() {
dfd.notifyWith(this, arguments);
}
);
}).promise();
}
return arg;
});
return $when.apply(this, args);
};
}());
We've covered the Observer pattern already in the Blue Bird past a while back in this series, but it is a powerful pattern that can help decouple various components. My recommendation is to use the postal.js library.
var channel = postal.channel( "game" );
channel.subscribe( "bird.attack", function( data ) {
console.log( "Geronimo!" );
});
channel.subscribe( "pig.collide", function( impact ) {
if ( impact > 100 ) {
console.log( "AHHHHHHH!" );
}
});
channel.publish( "bird.attack", { angle: 45 } );
There are several ways to implement inheritance in JavaScript. It is good to know some of these patterns as you create new objects in your application.
var bird = {
name: "Red Bird",
power: "",
getName: function() {
return this.name;
},
catapult: function() {
return this.name + " is catapulting with " + this.power;
}
};
var yellowBird = Object.create( bird );
yellowBird.name = "Yellow Bird";
yellowBird.power = "Speed";
console.log( yellowBird.catapult() ); //Yellow Bird is catapulting with Speed
var Bird = function( name, power ) {
this.name = name + " Bird";
this.power = power || "";
};
Bird.prototype.getName = function() {
return this.name;
};
Bird.prototype.catapult = function() {
return this.getName() + " is catapulting with " + this.power;
};
var YellowBird = function() {
this.constructor.apply( this, arguments );
};
YellowBird.prototype = new Bird();
var yellowBird = new YellowBird( "Yellow", "Speed" );
yellowBird.getName = function() {
return "Super Awesome " + this.name;
};
console.log( yellowBird.catapult() ); //Super Awesome Yellow Bird is catapulting with Speed
var bird = {
catapult: function() {
console.log( "Yippeeeeee!" );
return this;
},
destroy: function() {
console.log( "That'll teach you... you dirty pig!" );
return this;
}
};
bird.catapult().destroy();
// IIFE
var yellowBird = (function() {
var superSecret = {
power: "Speed"
};
return {
type: "Red",
mood: "Angry",
goal: "Vengence"
}
}());
var attackFsm = new machina.Fsm({
initialState: "idle",
states : {
"idle" : {
_onEnter: function() {
this.handle( "Zzzzzz" );
},
"bird.launch" : function( data ) {
console.log( "Weeeeee at " + data.angle + " degrees!" );
this.transition( "attacking" );
}
},
"attacking" : {
_onEnter: function() {
console.log( "Yay, hear me tweet!" );
},
"pig.destroyed" : function() {
this.transition( "victorious" );
},
"pig.alive" : function() {
this.transition( "defeated" );
}
},
"victorious": {
_onEnter: function() {
console.log( "Yay, we are victorious!" );
},
"game.restart": function() {
this.transition( "idle" );
},
"game.next": function() {
// Goto next level
this.transition( "idle" );
}
},
"defeated": {
_onEnter: function() {
console.log( "You may have won this time, but I'll be back!" );
},
"gmae.restart": function() {
this.transition( "idle" );
}
}
}
});
attackFsm.handle( "bird.launch", { angle: 45 } );
attackFsm.handle( "pig.destroyed" );
// Weeeeee at 45 degrees!
// Yay, hear me tweet!
// Yay, we are victorious!
In addition to learning these patterns I would recommend that you pick one of your favorite libraries and start to source dive into their repository.
→
Uses the most superior weapon of them all, a suite of tools that can organize and deploy all the other birds into battle against their soon to be vanquished foe
Over time the birds picked up RequireJS (Yellow Bird), JSHint (White Bird), Plato, Mustache (Orange Bird), and a bunch of other great tools, but all of them required a command line task to complete. The Mighty Eagle introduced some tools to make things a little bit easier.
Once you've installed grunt you'll need 2 main things for each of your projects
{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.1.1",
"grunt-contrib-nodeunit": "~0.1.2"
}
}
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task(s).
grunt.registerTask('default', ['uglify']);
};
→
DISCLAIMER: No birds were hurt during this presentation...