OOP Javascript & JS Architecture

By Oren Farhi

Oren Farhi

Front End Architect & JS Engineer @Tikal

Tikal

Tikal is a leading provider of open source solutions

Nowdays: Single page app is the thing!

The Base Structure For A JS Application

The Agenda For Today

Lets Play!

Step 1: The Red Bird

RedAngryBird.js

var color = "red";
var birdName = "redHat";
var powers = ["emptyHands"];
var isAttacked = false;
function attack($bird, animationConfig) {
	isAttacked = true;
	arm($bird);
	$bird.animate(animationConfig);
}
function removeBird($bird, removeConfig) {
	if (isAttacked)
		$bird.animate(removeConfig);
}
function getBirdHitScore ($bird) {
	return calculateScore($bird.position() + $nowPig.position());
}
var redBird = $("#redBird");
redBird.on("click", function(this){
	attack($(this), {left: 1000, top: 250});
});

Step 1: The Red Bird - Revisited

RedAngryBird.js

function RedAngryBird(htmlId, config) {
	this.color = "red";
	this.powers = config.powers;
	this.isAttacked = false;
	this.htmlId = htmlId;
	this.$bird = $("#" + htmlId);
}
RedAngryBird.prototype = {
	attack: function () {
		this.isAttacked = true;
		this.arm();
		this.$bird.animate(this.animate.attack);
	},
	
	remove: function () {
		if (this.isAttacked) {
			this.$bird.animate(this.animate.destroy);
		}
	},

	arm: function() { /*...*/}
};

Step 1: The Red Bird - Usage

Creating Birds (instances)


			var redHat = new RedAngryBird("jim", {name: "jim"});
			var redNosed = new RedAngryBird("red", {name: "red"});
			
		

Use the birds:

			redHat.attack();
			redNosed.attack();
			redHat.remove();
		

Demo

Step 2: Enter The Blue Bird

Step 2: Enter The Blue Bird

AngryBird.js

function AngryBird(htmlId, config) {
	this.color = config.color; // <- NEW Color property
	this.powers = config.powers;
	this.isAttacked = false;
	this.$bird = $(htmlId);
}
AngryBird.prototype = {
	attack: function () {
		this.isAttacked = true;
		this.arm();
		this.$bird.animate(this.animate.attack);
	},
	
	remove: function () {
		if (this.isAttacked) {
			this.$bird.animate(this.animate.destroy);
		}
	},
	arm: function () { /*...*/ }
};

Step 2: The Blue Bird - Usage

mainAppSomewhere.js

var twitter = new AngryBird("#twitter", {color: "blue", name: "jim"});
var redHat = new AngryBird("#red", {color: "red", name: "red"});

twitter.attack();

redHat.attack();

redHat.remove(); //- removes only "redHat" from the DOM

Demo

Step 2: Blue Bird - New Powers

BlueAngryBird.js

//- 1. define a class
function BlueAngryBird () { this.color = 'blue' }
//- 2. define the inheritance
BlueAngryBird.prototype = new AngryBird;
//- 3. fix the constructor
BlueAngryBird.prototype.constructor = BlueAngryBird;
//- 4.1 overide the arm method with a special arm
BlueAngryBird.prototype.arm = function() {
	this.powers.push(new AngryBird(/*..*/));
	this.powers.push(new AngryBird(/*..*/));
	this.powers.push(new AngryBird(/*..*/));
	this.$bird.on("click", this.split.bind(this));
}
//- 4.2 adding NEW method - split birds
BlueAngryBird.prototype.split = function() {
	for(var i = 0; i < this.powers.length; i++) {
		this.usePower(this.power[i]);
	}
}
//- 5. create the bird
var twitter = new BlueAngryBird("#twitter", {name: "twitter"});
twitter.color;	// outputs "blue"
twitter.arm();	// invokes the new method

Step 3: The Red Bird - with Backbone.js

Backbone.js - MVC* for JS

This is a backbone.

When used properly

It keeps one's head

Out of one's butt

Rand Macivor

Step 3: The Red Bird Model

RedBirdModel.js

var RedBirdModel = Backbone.Model.extend({
	isAttacked: function() {
		return this.get("isAttacked");
	},

	poof: function() {
		//- fires change events when data change 
		this.set({isDismissed: true});
	}
});
var redModel = new RedBirdModel( birdProperties );

redModel.get("isAttacked") // returns "false"

var redhat = new RedBirdView({ model: redModel });

Step 3: The Red Bird View

RedBirdView.js

var RedBirdView = Backbone.View.extend({

	events: {
		"click": "attack"
	},

	initialize: function() {
		this.model.on('change', this.render, this);
	},

	render: function() { ... },

	attack: function() {
		if (this.model.isAttacked()) {
			this.model.poof();
		} else {
			this.model.set('isAttacked', true);
		}
	}

});

Step 4: Yellow Bird - Dependancy & Extending

Demo

Step 4: Rundown

Step 4: Yellow Bird - Dependancy & Template (underscore.js)

bird.html

<%= birdHead %>
<%= birdBody %>
<%= birdLegs %>

use it:

var birdData = {
	birdColor: "yellow",
	birdHead: {/*...*/},
	birdBody: {/*...*/},
	birdLegs: {/*...*/}
}
var birdHtml = _.template( birdTemplateString, birdData );
		

Step 4: Yellow Bird - Require.js

require(
	
	["js/AngryBird", "js/RedAngryBird", "libs/jquery", "libs/backbone"], 
	
	function(angryBird, redAngryBird, $, Backbone){
	//- statements
});

define a single module:

define(
	
	["js/AngryBird", "libs\jquery", "libs\backbone"], 
	
	function(angryBird, $, Backbone){
		return {/* ... */};
});

Step 4: Yellow Bird - Dependancy

AngryBirdView.js

define([
	"libs/jquery", "libs/underscore", "libs/backbone",
	"text!templates/bird.html"],
	function($, _, Backbone, birdTemplate) {
		
		var bird = Backbone.View.extend({
			attack: function() {...},
			arm: function() {...}
		})
		
		return bird;
	}
)

Step 4: Yellow Bird

YellowBirdView.js

define([
"lib/jquery", "libs/underscore", "libs/backbone", 
"js/AngryBirdView",
"text!templates/bird.html"],

function($, _, B, AngryBird, birdTemplate) {
			//- private variable
			var birdSpeed = 2;
	//- YELLOW BIRD
	var yellowBird = AngryBird.extend({
	arm: function() {...},
	
	flyFast: function () {
		birdSpeed *= 2; //- reference to private
		this.fly();
	}
})

return yellowBird;
})

Step 4: Yellow Bird - Dependancy Extending & Privacy

mainAppSomewhere.js

require("path/to/YellowBirdView", 

function(yellowBird) {
	//- create new 
	var yellowChello = new yellowBird();
	
	yellowChello.flyFast(); 
	yellowChello.birdSpeed *= 5; //- JS Error - birdSpeed not defined

});

Step 5: The Level Module

Step 5: Rundown

Step 5: The Level Module

Navigation in URL

GameRouter.js

var GameRouter = Backbone.Router.extend({
	routes: {
		"help":                 "showHelp",
		"levels/:level":        "goTolevel",
		"levels/:level/:scene": "goToScene"
	},

	//- 2. callbacks to handle entry points
	showHelp: function() { /*...*/ },

	goToLevel: function(level) { /* */ }

	goToScene: function(level, scene) { /* */ }
});

Step 5: The Level Module - Replaying levels

GameMenuManager.js

//- called from Levels Menu Module
var GameMenuManager = Backbone.View.extend({

	//- 1. define dom events
	events: {
		"click .scene": "startScene"
	},

	//- 2. start to play a scene
	startScene: function(ev) {
		GameRouter.navigate("levels/1/2", {trigger: true});
}})

Step 5: HTML5's Storing Data with Backbone.Safe

GameStorage.js

//- Game Storage Module
define([ "libs/jquery", "libs/underscore", "libs/backbone",
	"libs/backbone/safe"
	function($, _, Backbone, Safe) {
		var GameStorage = Backbone.Model.extend({
// define Safe
		
		safe: "angry-birds-levels",
		
				setLevelSuccess: function(level, isPassed, score) { 
					this.set({
						level: level,
						isPassed: isPassed,
						score: score
					});
				}
			})
			
			return GameStorage;
		})

Step 6: The Game Controller

Step 6: Game Controller

Step 6: The Game Controller - Usage

BlueAngryBird.js

define([ "some-depended-files "], 
	function($, _, B, AngryBird, birdTemplate) {
	
	var blueBird = AngryBird.extend({
		
		initialize: function(gameController) {
			//- 1. listen to Game Events
			this.gameController = gameController;
			gameController.on("bird-success-hit", this.hooray, this);
		},

		hooray: function() { /* happy happy joy joy */ },
		attack: function() {
			//- 2. notify the game
			if (this.model.get('isSuccessAttack')) {
				this.gameController.trigger("bird-success-hit", this.model.birdId);
			}
		},
		
		
		})
		return blueBird;
	})

Step 6: The Game Controller (Core)

GameController.js

	var GameController = Backbone.View.extend({
		//- 1. notify/publish events to the core game controller
		trigger: function (eventName, eventData) { /**/ },
		
		//- 2. allow modules to listen/subscribe to other events
		on: function (eventName, handler, scope) {...},
		//- 3. constructor
			initialize: function() {
				this.modules = {
					gameManager: new GameManager( this ),
					gameStorage: new GameStorage( this ),
					gameRouter: new GameRouter( this ),
					levelsManager: new LevelsManager( this )
				};
			},
//- possible modules operations
	startAll: function(modules) { /**/ },
	
	stopAll: function() { /**/ },
	
	disableModule: function(moduleId) { /**/ },
	
	enableModule: function(moduleId) { /**/ }
})

On The Long Term...

Concern for the future:

Q & A
Thank you!

Credits: Tikal Techs, mr doob for chrome experiments

Inspiration: Nicholas Zakas, Addy Osmani

/

#