<commit_before>// GLOBAL VARIABLES
var GRID_SIZE = 5;
var SOLUTION_LENGTH = 17;
var DIR_PROB = 0.25;

var startingGrid;
var solution;
var grid;

// Define some objects
function Tile(x_, y_) {
	var tile = {
		pos: {
			x: x_,
			y: y_
		},
		on: true,
		up: false,
		right: false,
		down: false,
		left: false,
	};

	return tile;
}

// Generate a random grid
function Grid(size) {
	// Initialize a random directed graph
	var tiles = [];

	for(var x = 0; x < size; x++) {
		tiles[x] = [];

		for(var y = 0; y < size; y++) {
			var tile = Tile(x, y);

			var tileUp = y > 0;
			var tileDown = y < size - 1;
			var tileLeft = x > 0;
			var tileRight = x < size - 1;

			// Randomize directions
			// Tiles can only control the lights on the left/right OR up/down
			// This is exclusively for design reasons. Too many different directions
			// looks cluttered.
			if(Math.random() < 0.5) {
				if(tileLeft && Math.random() < DIR_PROB)
					tile.left = true;

				if(tileRight && Math.random() < DIR_PROB)
					tile.right = true;
			} else {
				if(tileUp && Math.random() < DIR_PROB)
					tile.up = true;

				if(tileDown && Math.random() < DIR_PROB)
					tile.down = true;
			}

			// If we didn't assign any directions, then it's a normal lights and
			// controls all 4 adjacent tiles
			if(!tile.up && !tile.down && !tile.left && !tile.right) {
				tile.up = tileUp;
				tile.right = tileRight;
				tile.down = tileDown;
				tile.left = tileLeft;
			}

			tiles[x][y] = tile;
		}
	}

	var grid = {
		tiles: tiles,
		size: size
	};

	return grid;
}

// Utility functions
function isSolution(solution) {
	var gridCopy = copyGrid(grid);

	for(var s = 0; s < solution.length; s++) {
		var x = solution[s].x;
		var y = solution[s].y;
		toggle(gridCopy, x, y);
	}
	return isSolved(gridCopy);
}

function isSolved(grid) {
	for(var x = 0; x < grid.size; x++) {
		for(var y = 0; y < grid.size; y++) {
			if(!grid.tiles[x][y].on) return false;
		}
	}

	return true;
}

function isDirected(tile, size) {
	var tileUp = tile.pos.y > 0;
	var tileDown = tile.pos.y < size - 1;
	var tileLeft = tile.pos.x > 0;
	var tileRight = tile.pos.x < size - 1;

	var undirected = (tileUp) ? tile.up : true;
	undirected = undirected && ((tileRight) ? tile.right : true);
	undirected = undirected && ((tileDown) ? tile.down : true);
	undirected = undirected && ((tileLeft) ? tile.left : true);

	return !undirected;
}

function getBestScore() {
	var bestScore = document.getElementById("best-score");
	var score = parseInt(bestScore.innerHTML.substr(18));
	return score;

}

function setBestScore(score) {
	var bestScore = document.getElementById("best-score");
	bestScore.innerHTML = "Best:" + score;
}

function getNowScore() {
	var nowScore = document.getElementById("now-score");
	var score = parseInt(nowScore.innerHTML.substr(17));
	return score;
}

function setNowScore(score) {
	var nowScore = document.getElementById("now-score");
	nowScore.innerHTML = "Now:" + score;
}

function copyGrid(grid) {
	// This is dirty
	return JSON.parse(JSON.stringify(grid));
}

// COOOOOOOOOKIE crisp
function getBestScoreCookie() {
	// Assuming we only store one cookie
	if(document.cookie != "") {
		var bestScoreStr = "bestScore=";
		var scoreStr = document.cookie.substr(bestScoreStr.length);
		var score = parseInt(scoreStr);
		return score;
	}

	return Math.NaN;
}

function setBestScoreCookie(bestScore) {
	var d = new Date();
	d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
	document.cookie = "bestScore=" + bestScore + "; expires=" + d.toUTCString();
}

// Implementation
var handleClickWrapper = function(grid) {
	return function(evt) {
		handleClick(evt, grid);
	}
}
function listenForClicks(grid) {
	var tiles = document.getElementsByClassName("tile");
	for(var t = 0; t < tiles.length; t++) {
		var tileDiv = tiles[t];
		tileDiv.addEventListener("click", handleClick, false);
	}
}

function bindUtilityButtons() {
	var resetBtn = document.getElementById("reset");
	resetBtn.addEventListener("click", function(evt) {
		grid = reset(grid, startingGrid);
	}, false);

	var newGridBtn = document.getElementById("new");
	newGridBtn.addEventListener("click", function(evt) {
		grid = reset(grid);
	}, false);
}

function handleClick(evt) {
	var tileDiv = evt.target;
	var id = tileDiv.id;
	var index = parseInt(id.substr(1));

	var x = index % grid.size;
	var y = (index - x) / grid.size;
	toggle(grid, x, y);
	displayGrid(grid);

	// Update score
	setNowScore(getNowScore() + 1);

	if(isSolved(grid)) {
		// Delayed to let animations finish
		setTimeout(function() {
			handleWin();
		}, 200);
	}
}

function handleWin() {
	var tileDivs = document.getElementsByClassName("tile");
	for(var t = 0; t < tileDivs.length; t++) {
		tileDivs[t].className = "tile on";
		tileDivs[t].removeEventListener("click", handleClick, false);
	}

	fancyFlashEffect();
	setTimeout(displayWinTiles, 1200);

	var bestScore = getBestScoreCookie();
	var nowScore = getNowScore();
	if (isNaN(bestScore) || nowScore < bestScore) {
		setBestScore(nowScore);
		setBestScoreCookie(nowScore);
	}
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

function facebookListener () {
	var url = "https://facebook.com/dialog/feed?";
	var appID = "774394885948709";
	var link = "http://williamg.me/lights";
	var picture = "http://williamg.github.io/lights/style/lights.png"
	var name = "Play lights!";
	var description = "I turned on all the lights in only " + getNowScore() + " moves! Can you beat me?";
	url += "app_id=" + appID;
	url += "&redirect_uri=" + link;
	url += "&description=" + description;
	url += "&link=" + link;
	url += "&picture=" + picture;
	url += "&name=" + name;

	window.open(encodeURI(url));
}

function twitterListener() {
	var url = "https://twitter.com/share?"
	var link = "http://williamg.me/lights"
	var text = "I turned on all the lights in only " + getNowScore() + " moves! Can you beat me?";
	url += "url=" + link;
	url += "&text=" + text;

	window.open(encodeURI(url));
}

function displayWinTiles() {
	var tileDivs = document.getElementsByClassName("tile");
	tileDivs[6].innerHTML = "Y";
	tileDivs[7].innerHTML = "O";
	tileDivs[8].innerHTML = "U";
	tileDivs[11].innerHTML = "W";
	tileDivs[12].innerHTML = "I";
	tileDivs[13].innerHTML = "N";
	tileDivs[16].className += " twitter";
	tileDivs[18].className += " facebook";

	var facebook = tileDivs[18];
	facebook.addEventListener("click", facebookListener, false);

	var twitter = tileDivs[16];
	twitter.addEventListener("click", twitterListener, false);
}

function toggle(grid, x, y) {
	if(x < 0 || x >= grid.size || y < 0 || y >= grid.size) {
		console.log("ERROR: Coordinates (" + x + ", " + y + ") out of bounds!");
		return;
	}

	var tile = grid.tiles[x][y];
	tile.on = !tile.on;

	if(tile.left) {
		var left = grid.tiles[x-1][y];
		left.on = !left.on;
	}

	if(tile.right) {
		var right = grid.tiles[x+1][y];
		right.on = !right.on;
	}

	if(tile.up) {
		var up = grid.tiles[x][y-1];
		up.on = !up.on;
	}

	if(tile.down) {
		var down = grid.tiles[x][y+1];
		down.on = !down.on;
	}
}

function unSolve(numMoves) {
	var moves = [];
	var choices = [];

	for(var c = 0; c < GRID_SIZE * GRID_SIZE; c++)
		choices.push(c);

	for(var m = 0; m < numMoves; m++) {
		// Generate random place
		var index = Math.floor(Math.random() * (GRID_SIZE * GRID_SIZE - m));
		var tile = choices.splice(index, 1);
		var x = tile % GRID_SIZE;
		var y = (tile - x) / GRID_SIZE;
		toggle(grid, x, y);

		moves[numMoves - m - 1] = {x: x, y: y};
	}

	return moves;
}

function displayGrid() {
	var tiles = document.getElementsByClassName('tile');

	for(var t = 0; t < tiles.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		var tile = grid.tiles[x][y];
		var div = tiles[t];
		div.className = "tile";
		div.innerHTML = "";

		if(tile.on)
			div.className += " on";

		// Determine if this is a directed tile or not (edges make this tricky)
		if(!isDirected(tile, grid.size)) continue;

		if(tile.left)
			div.className += " left";
		if(tile.right)
			div.className += " right";
		if(tile.up)
			div.className += " up";
		if(tile.down)
			div.className += " down";
	}
}

function reset(oldGrid, newGrid) {
	grid = newGrid;

	if(grid == undefined) {
		grid = Grid(GRID_SIZE);
		solution = unSolve(SOLUTION_LENGTH);

		// Sanity check
		if(!isSolution(solution)) console.log("Error! Bad solution!");
	}

	listenForClicks();

	var tileDivs = document.getElementsByClassName('tile');

	for(var t = 0; t < tileDivs.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		if(oldGrid == undefined || !oldGrid.tiles[x][y].on) {
			tileDivs[t].className += " on";
		}
	}

	var facebook = tileDivs[18];
	facebook.removeEventListener("click", facebookListener, false);

	var twitter = tileDivs[16];
	twitter.removeEventListener("click", twitterListener, false);

	setNowScore(0);
	setTimeout(displayGrid.bind(this, grid), 200);
	startingGrid = copyGrid(grid);
	return grid;
}

// Update cookie
var bestScore = getBestScoreCookie();
setBestScoreCookie(bestScore);

// Set best score box
if(isNaN(bestScore)) setBestScore("--");
else setBestScore(bestScore);

// Set up "Reset" and "New Grid" buttons
bindUtilityButtons();

// Generate and display the grid
grid = reset();
<commit_msg>Corrected social media links<commit_after>// GLOBAL VARIABLES
var GRID_SIZE = 5;
var SOLUTION_LENGTH = 17;
var DIR_PROB = 0.25;

var startingGrid;
var solution;
var grid;

// Define some objects
function Tile(x_, y_) {
	var tile = {
		pos: {
			x: x_,
			y: y_
		},
		on: true,
		up: false,
		right: false,
		down: false,
		left: false,
	};

	return tile;
}

// Generate a random grid
function Grid(size) {
	// Initialize a random directed graph
	var tiles = [];

	for(var x = 0; x < size; x++) {
		tiles[x] = [];

		for(var y = 0; y < size; y++) {
			var tile = Tile(x, y);

			var tileUp = y > 0;
			var tileDown = y < size - 1;
			var tileLeft = x > 0;
			var tileRight = x < size - 1;

			// Randomize directions
			// Tiles can only control the lights on the left/right OR up/down
			// This is exclusively for design reasons. Too many different directions
			// looks cluttered.
			if(Math.random() < 0.5) {
				if(tileLeft && Math.random() < DIR_PROB)
					tile.left = true;

				if(tileRight && Math.random() < DIR_PROB)
					tile.right = true;
			} else {
				if(tileUp && Math.random() < DIR_PROB)
					tile.up = true;

				if(tileDown && Math.random() < DIR_PROB)
					tile.down = true;
			}

			// If we didn't assign any directions, then it's a normal lights and
			// controls all 4 adjacent tiles
			if(!tile.up && !tile.down && !tile.left && !tile.right) {
				tile.up = tileUp;
				tile.right = tileRight;
				tile.down = tileDown;
				tile.left = tileLeft;
			}

			tiles[x][y] = tile;
		}
	}

	var grid = {
		tiles: tiles,
		size: size
	};

	return grid;
}

// Utility functions
function isSolution(solution) {
	var gridCopy = copyGrid(grid);

	for(var s = 0; s < solution.length; s++) {
		var x = solution[s].x;
		var y = solution[s].y;
		toggle(gridCopy, x, y);
	}
	return isSolved(gridCopy);
}

function isSolved(grid) {
	for(var x = 0; x < grid.size; x++) {
		for(var y = 0; y < grid.size; y++) {
			if(!grid.tiles[x][y].on) return false;
		}
	}

	return true;
}

function isDirected(tile, size) {
	var tileUp = tile.pos.y > 0;
	var tileDown = tile.pos.y < size - 1;
	var tileLeft = tile.pos.x > 0;
	var tileRight = tile.pos.x < size - 1;

	var undirected = (tileUp) ? tile.up : true;
	undirected = undirected && ((tileRight) ? tile.right : true);
	undirected = undirected && ((tileDown) ? tile.down : true);
	undirected = undirected && ((tileLeft) ? tile.left : true);

	return !undirected;
}

function getBestScore() {
	var bestScore = document.getElementById("best-score");
	var score = parseInt(bestScore.innerHTML.substr(18));
	return score;

}

function setBestScore(score) {
	var bestScore = document.getElementById("best-score");
	bestScore.innerHTML = "Best:" + score;
}

function getNowScore() {
	var nowScore = document.getElementById("now-score");
	var score = parseInt(nowScore.innerHTML.substr(17));
	return score;
}

function setNowScore(score) {
	var nowScore = document.getElementById("now-score");
	nowScore.innerHTML = "Now:" + score;
}

function copyGrid(grid) {
	// This is dirty
	return JSON.parse(JSON.stringify(grid));
}

// COOOOOOOOOKIE crisp
function getBestScoreCookie() {
	// Assuming we only store one cookie
	if(document.cookie != "") {
		var bestScoreStr = "bestScore=";
		var scoreStr = document.cookie.substr(bestScoreStr.length);
		var score = parseInt(scoreStr);
		return score;
	}

	return Math.NaN;
}

function setBestScoreCookie(bestScore) {
	var d = new Date();
	d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
	document.cookie = "bestScore=" + bestScore + "; expires=" + d.toUTCString();
}

// Implementation
var handleClickWrapper = function(grid) {
	return function(evt) {
		handleClick(evt, grid);
	}
}
function listenForClicks(grid) {
	var tiles = document.getElementsByClassName("tile");
	for(var t = 0; t < tiles.length; t++) {
		var tileDiv = tiles[t];
		tileDiv.addEventListener("click", handleClick, false);
	}
}

function bindUtilityButtons() {
	var resetBtn = document.getElementById("reset");
	resetBtn.addEventListener("click", function(evt) {
		grid = reset(grid, startingGrid);
	}, false);

	var newGridBtn = document.getElementById("new");
	newGridBtn.addEventListener("click", function(evt) {
		grid = reset(grid);
	}, false);
}

function handleClick(evt) {
	var tileDiv = evt.target;
	var id = tileDiv.id;
	var index = parseInt(id.substr(1));

	var x = index % grid.size;
	var y = (index - x) / grid.size;
	toggle(grid, x, y);
	displayGrid(grid);

	// Update score
	setNowScore(getNowScore() + 1);

	if(isSolved(grid)) {
		// Delayed to let animations finish
		setTimeout(function() {
			handleWin();
		}, 200);
	}
}

function handleWin() {
	var tileDivs = document.getElementsByClassName("tile");
	for(var t = 0; t < tileDivs.length; t++) {
		tileDivs[t].className = "tile on";
		tileDivs[t].removeEventListener("click", handleClick, false);
	}

	fancyFlashEffect();
	setTimeout(displayWinTiles, 1200);

	var bestScore = getBestScoreCookie();
	var nowScore = getNowScore();
	if (isNaN(bestScore) || nowScore < bestScore) {
		setBestScore(nowScore);
		setBestScoreCookie(nowScore);
	}
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

function facebookListener () {
	var url = "https://facebook.com/dialog/feed?";
	var appID = "774394885948709";
	var link = "lights.williamg.me";
	var picture = "http://williamg.github.io/lights/style/lights.png"
	var name = "Play lights!";
	var description = "I turned on all the lights in only " + getNowScore() + " moves! Can you beat me?";
	url += "app_id=" + appID;
	url += "&redirect_uri=" + link;
	url += "&description=" + description;
	url += "&link=" + link;
	url += "&picture=" + picture;
	url += "&name=" + name;

	window.open(encodeURI(url));
}

function twitterListener() {
	var url = "https://twitter.com/share?"
	var link = "lights.williamg.me";
	var text = "I turned on all the lights in only " + getNowScore() + " moves! Can you beat me?";
	url += "url=" + link;
	url += "&text=" + text;

	window.open(encodeURI(url));
}

function displayWinTiles() {
	var tileDivs = document.getElementsByClassName("tile");
	tileDivs[6].innerHTML = "Y";
	tileDivs[7].innerHTML = "O";
	tileDivs[8].innerHTML = "U";
	tileDivs[11].innerHTML = "W";
	tileDivs[12].innerHTML = "I";
	tileDivs[13].innerHTML = "N";
	tileDivs[16].className += " twitter";
	tileDivs[18].className += " facebook";

	var facebook = tileDivs[18];
	facebook.addEventListener("click", facebookListener, false);

	var twitter = tileDivs[16];
	twitter.addEventListener("click", twitterListener, false);
}

function toggle(grid, x, y) {
	if(x < 0 || x >= grid.size || y < 0 || y >= grid.size) {
		console.log("ERROR: Coordinates (" + x + ", " + y + ") out of bounds!");
		return;
	}

	var tile = grid.tiles[x][y];
	tile.on = !tile.on;

	if(tile.left) {
		var left = grid.tiles[x-1][y];
		left.on = !left.on;
	}

	if(tile.right) {
		var right = grid.tiles[x+1][y];
		right.on = !right.on;
	}

	if(tile.up) {
		var up = grid.tiles[x][y-1];
		up.on = !up.on;
	}

	if(tile.down) {
		var down = grid.tiles[x][y+1];
		down.on = !down.on;
	}
}

function unSolve(numMoves) {
	var moves = [];
	var choices = [];

	for(var c = 0; c < GRID_SIZE * GRID_SIZE; c++)
		choices.push(c);

	for(var m = 0; m < numMoves; m++) {
		// Generate random place
		var index = Math.floor(Math.random() * (GRID_SIZE * GRID_SIZE - m));
		var tile = choices.splice(index, 1);
		var x = tile % GRID_SIZE;
		var y = (tile - x) / GRID_SIZE;
		toggle(grid, x, y);

		moves[numMoves - m - 1] = {x: x, y: y};
	}

	return moves;
}

function displayGrid() {
	var tiles = document.getElementsByClassName('tile');

	for(var t = 0; t < tiles.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		var tile = grid.tiles[x][y];
		var div = tiles[t];
		div.className = "tile";
		div.innerHTML = "";

		if(tile.on)
			div.className += " on";

		// Determine if this is a directed tile or not (edges make this tricky)
		if(!isDirected(tile, grid.size)) continue;

		if(tile.left)
			div.className += " left";
		if(tile.right)
			div.className += " right";
		if(tile.up)
			div.className += " up";
		if(tile.down)
			div.className += " down";
	}
}

function reset(oldGrid, newGrid) {
	grid = newGrid;

	if(grid == undefined) {
		grid = Grid(GRID_SIZE);
		solution = unSolve(SOLUTION_LENGTH);

		// Sanity check
		if(!isSolution(solution)) console.log("Error! Bad solution!");
	}

	listenForClicks();

	var tileDivs = document.getElementsByClassName('tile');

	for(var t = 0; t < tileDivs.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		if(oldGrid == undefined || !oldGrid.tiles[x][y].on) {
			tileDivs[t].className += " on";
		}
	}

	var facebook = tileDivs[18];
	facebook.removeEventListener("click", facebookListener, false);

	var twitter = tileDivs[16];
	twitter.removeEventListener("click", twitterListener, false);

	setNowScore(0);
	setTimeout(displayGrid.bind(this, grid), 200);
	startingGrid = copyGrid(grid);
	return grid;
}

// Update cookie
var bestScore = getBestScoreCookie();
setBestScoreCookie(bestScore);

// Set best score box
if(isNaN(bestScore)) setBestScore("--");
else setBestScore(bestScore);

// Set up "Reset" and "New Grid" buttons
bindUtilityButtons();

// Generate and display the grid
grid = reset();
<|endoftext|>


<commit_before>document.addEventListener("DOMContentLoaded", function() {
  var STATES = {
    "Alabama": "AL",
    "Alaska": "AK",
    "American Samoa": "AS",
    "Arizona": "AZ",
    "Arkansas": "AR",
    "California": "CA",
    "Colorado": "CO",
    "Connecticut": "CT",
    "Delaware": "DE",
    "District Of Columbia": "DC",
    "Federated States Of Micronesia": "FM",
    "Florida": "FL",
    "Georgia": "GA",
    "Guam": "GU",
    "Hawaii": "HI",
    "Idaho": "ID",
    "Illinois": "IL",
    "Indiana": "IN",
    "Iowa": "IA",
    "Kansas": "KS",
    "Kentucky": "KY",
    "Louisiana": "LA",
    "Maine": "ME",
    "Marshall Islands": "MH",
    "Maryland": "MD",
    "Massachusetts": "MA",
    "Michigan": "MI",
    "Minnesota": "MN",
    "Mississippi": "MS",
    "Missouri": "MO",
    "Montana": "MT",
    "Nebraska": "NE",
    "Nevada": "NV",
    "New Hampshire": "NH",
    "New Jersey": "NJ",
    "New Mexico": "NM",
    "New York": "NY",
    "North Carolina": "NC",
    "North Dakota": "ND",
    "Northern Mariana Islands": "MP",
    "Ohio": "OH",
    "Oklahoma": "OK",
    "Oregon": "OR",
    "Palau": "PW",
    "Pennsylvania": "PA",
    "Puerto Rico": "PR",
    "Rhode Island": "RI",
    "South Carolina": "SC",
    "South Dakota": "SD",
    "Tennessee": "TN",
    "Texas": "TX",
    "Utah": "UT",
    "Vermont": "VT",
    "Virgin Islands": "VI",
    "Virginia": "VA",
    "Washington": "WA",
    "West Virginia": "WV",
    "Wisconsin": "WI",
    "Wyoming": "WY"
  };

  var formatNumber = function(x) {
    return x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '';
  };

  var pluralize = function(number, singular, plural){
    if (parseFloat(number) === 1.0) {
      return number + ' ' + singular;
    }
    else {
      if (!plural) {
        plural = singular + 's';
      }

      return formatNumber(number) + ' ' + plural;
    }
  };

  var shuffle = function(array) {
    for (var i = array.length - 1; i > 0; i--) {
      var j = Math.floor(Math.random() * (i + 1));
      var temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
  };

  // send event to Google Analytics
  function trackEvent(category, action, label, value) {
    if (window.ga) {
      var params = {
        hitType: 'event',
        eventCategory: category,
        eventAction: action
      };

      if (label) {
        params.eventLabel = label;
      }

      if (value) {
        params.eventValue = value;
      }

      window.ga('send', params);
    }
  }

  Vue.filter('formatNumber', formatNumber);
  Vue.filter('pluralize', pluralize);

  var app = new Vue({
    el: '#app',

    data: function() {
      return {
        states: STATES,
        selectedState: null,
        isLoading: false,
        localBusinesses: [],
        priorityBusinesses: [],
        tweetCount: 0,
        name: null,
        email: null,
        zipCode: null,
        phone: null,
        hasLargeAudience: false,
        actionComment: null,
        isSubmittingForm: false,
        formMessage: null,
        modalVisible: false
      }
    },

    watch: {
      selectedState: function(newValue) {
        if (newValue) {
          this.fetchLocalBusinesses();
        }
        else {
          this.localBusinesses = [];
        }
      }
    },

    created: function() {
      this.geocodeSelectedState();
      this.fetchPriorityBusinesses();
    },

    methods: {
      geocodeSelectedState: function() {
        var self = this;
        self.$http.get('https://fftf-geocoder.herokuapp.com').then(function(response){
          if (response.ok) {
            var geo = response.body;

            if (
              geo.country.iso_code === 'US' &&
              geo.subdivisions &&
              geo.subdivisions[0] &&
              geo.subdivisions[0].names &&
              geo.subdivisions[0].names.en
            ) {
              self.selectedState = geo.subdivisions[0].iso_code;
            }
          }
        });
      },

      uniqueifyBusinesses: function(businesses) {
        var uids = [];
        var uniqueishBusinesses = [];

        for (var i = 0; i < businesses.length; i++) {
          var biz = businesses[i];
          var uid = biz.twitter + biz.state;

          if (uids.indexOf(uid) === -1) {
            uids.push(uid);
            uniqueishBusinesses.push(biz);
          }
        }

        return uniqueishBusinesses;
      },

      fetchLocalBusinesses: function() {
        var self = this;
        self.isLoading = true;
        self.$http.get('https://data.battleforthenet.com/businesses/' + self.selectedState.toLowerCase() + '.json').then(function(response){
          if (response.ok) {
            var businesses = self.uniqueifyBusinesses(response.body);
            shuffle(businesses);
            self.localBusinesses = businesses.slice(0, 20);
            self.isLoading = false;
          }
        });
      },

      fetchPriorityBusinesses: function() {
        var self = this;
        self.$http.get('https://data.battleforthenet.com/businesses/priority.json').then(function(response){
          if (response.ok) {
            var businesses = response.body;
            shuffle(businesses);
            self.priorityBusinesses = businesses.slice(0, 12);
          }
        });
      },

      showModal: function() {
        this.modalVisible = true;
        document.querySelector('body').classList.add('modal-open');
      },

      hideModal: function() {
        this.modalVisible = false;
        document.querySelector('body').classList.remove('modal-open');
      },

      getMetaContent: function(name) {
        var el = document.querySelector('meta[name="' + name + '"]') || document.querySelector('meta[property="' + name + '"]');

        if (el) {
          return el.getAttribute('content');
        }

        return null;
      },

      openPopup: function(url, title='popup', w=600, h=500) {
        // Fixes dual-screen position
        var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
        var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;

        var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
        var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;

        var left = ((width / 2) - (w / 2)) + dualScreenLeft;
        var top = ((height / 2) - (h / 2)) + dualScreenTop;
        var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);

        // Puts focus on the newWindow
        if (window.focus) {
          newWindow.focus();
        }
      },

      shareOnFacebook: function() {
        var url = this.getMetaContent('og:url');
        this.openPopup('https://www.facebook.com/sharer.php?u=' + encodeURIComponent(url), 'facebook');
      },

      shareOnTwitter: function() {
        var tweetText = this.getMetaContent('twitter:description') + ' ' + this.getMetaContent('twitter:url');
        this.openPopup('https://twitter.com/intent/tweet?text=' + encodeURIComponent(tweetText), 'twitter');
      },

      submitForm: function() {
        var self = this;
        self.isSubmittingForm = true;
        self.$http.post('https://queue.fightforthefuture.org/action', {
          member: {
            first_name: self.name,
            email: self.email,
            postcode: self.zipCode,
            phone_number: self.phone,
            country: 'US'
          },
          hp_enabled: 'true',
          guard: '',
          contact_congress: 0,
          org: 'fftf',
          an_tags: "[\"net-neutrality\"]",
          an_petition_id: '11f84b38-e65b-4259-b0ae-e879a4044ca9',
          volunteer: self.hasLargeAudience,
          action_comment: self.actionComment
        }, { emulateJSON: true })
        .then(function(response){
          self.isSubmittingForm = false;

          if (response.ok) {
            self.resetForm();
            self.showModal();
            trackEvent('signup', 'success', 'Step 4 - Sign Up');
          }
          else {
            self.formMessage = "That didn't work for some reason :(";
          }
        })
        .catch(function(error){
          self.isSubmittingForm = false;
          self.formMessage = "That didn't work for some reason :(";
        })
      },

      resetForm() {
        this.phone = null;
        this.name = null;
        this.email = null;
        this.zipCode = null;
        this.hasLargeAudience = false;
        this.actionComment = null;
        this.formMessage = null;
      },

      openSpreadsheet() {
        window.open('https://docs.google.com/spreadsheets/d/12MkAWz8VGyIIiCgVdGXWfLKROBegZNew8-xKyPVv6vU/edit#gid=0', '_blank');
        trackEvent('button', 'click', 'Step 5 - Get Started');
      }
    }
  });

  Vue.component('business-card', {
    template: '#business-card-template',

    props: {
      business: {
        type: Object
      },
      national: {
        type: Boolean,
        default: false
      }
    },

    computed: {
      tweetURL: function() {
        var tweetText = "Hi @" + this.business.twitter + ", I’m volunteering to save #NetNeutrality, especially because losing it will hurt businesses like you. Will you join other businesses in your area by signing this letter?";

        return 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(tweetText) + '&url=' + encodeURIComponent('https://www.businessesfornetneutrality.com');
      },

      locationString: function() {
        if (this.business.city && this.business.state) {
          return [this.business.city, this.business.state].join(', ');
        }
        else if (this.business.city) {
          return this.business.city;
        }
        else if (this.business.state) {
          return this.business.state;
        }
      },

      logo: function() {
        if (this.business.twitter) {
          return 'https://data.battleforthenet.com/images/twitter/' + this.business.twitter.toLowerCase() + '.jpg';
        }
      },

      domain: function() {
        if (this.business.website) {
          var a = document.createElement('a');
          a.href = this.business.website;
          return a.hostname.replace(/^www\./i, '');
        }
      }
    },

    methods: {
      openTweetURL: function() {
        this.$parent.tweetCount += 1;
        window.open(this.tweetURL, '_blank');

        var stepNumber = this.national ? 3 : 1;
        trackEvent('business', 'tweet', 'Step ' + stepNumber + ' - ' + this.business.twitter);
      }
    }
  });
});<commit_msg>/business: Save selectedState in localStorage<commit_after>document.addEventListener("DOMContentLoaded", function() {
  var STATES = {
    "Alabama": "AL",
    "Alaska": "AK",
    "American Samoa": "AS",
    "Arizona": "AZ",
    "Arkansas": "AR",
    "California": "CA",
    "Colorado": "CO",
    "Connecticut": "CT",
    "Delaware": "DE",
    "District Of Columbia": "DC",
    "Federated States Of Micronesia": "FM",
    "Florida": "FL",
    "Georgia": "GA",
    "Guam": "GU",
    "Hawaii": "HI",
    "Idaho": "ID",
    "Illinois": "IL",
    "Indiana": "IN",
    "Iowa": "IA",
    "Kansas": "KS",
    "Kentucky": "KY",
    "Louisiana": "LA",
    "Maine": "ME",
    "Marshall Islands": "MH",
    "Maryland": "MD",
    "Massachusetts": "MA",
    "Michigan": "MI",
    "Minnesota": "MN",
    "Mississippi": "MS",
    "Missouri": "MO",
    "Montana": "MT",
    "Nebraska": "NE",
    "Nevada": "NV",
    "New Hampshire": "NH",
    "New Jersey": "NJ",
    "New Mexico": "NM",
    "New York": "NY",
    "North Carolina": "NC",
    "North Dakota": "ND",
    "Northern Mariana Islands": "MP",
    "Ohio": "OH",
    "Oklahoma": "OK",
    "Oregon": "OR",
    "Palau": "PW",
    "Pennsylvania": "PA",
    "Puerto Rico": "PR",
    "Rhode Island": "RI",
    "South Carolina": "SC",
    "South Dakota": "SD",
    "Tennessee": "TN",
    "Texas": "TX",
    "Utah": "UT",
    "Vermont": "VT",
    "Virgin Islands": "VI",
    "Virginia": "VA",
    "Washington": "WA",
    "West Virginia": "WV",
    "Wisconsin": "WI",
    "Wyoming": "WY"
  };

  var formatNumber = function(x) {
    return x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '';
  };

  var pluralize = function(number, singular, plural){
    if (parseFloat(number) === 1.0) {
      return number + ' ' + singular;
    }
    else {
      if (!plural) {
        plural = singular + 's';
      }

      return formatNumber(number) + ' ' + plural;
    }
  };

  var shuffle = function(array) {
    for (var i = array.length - 1; i > 0; i--) {
      var j = Math.floor(Math.random() * (i + 1));
      var temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
  };

  // send event to Google Analytics
  function trackEvent(category, action, label, value) {
    if (window.ga) {
      var params = {
        hitType: 'event',
        eventCategory: category,
        eventAction: action
      };

      if (label) {
        params.eventLabel = label;
      }

      if (value) {
        params.eventValue = value;
      }

      window.ga('send', params);
    }
  }

  Vue.filter('formatNumber', formatNumber);
  Vue.filter('pluralize', pluralize);

  var app = new Vue({
    el: '#app',

    data: function() {
      return {
        states: STATES,
        selectedState: null,
        isLoading: false,
        localBusinesses: [],
        priorityBusinesses: [],
        tweetCount: 0,
        name: null,
        email: null,
        zipCode: null,
        phone: null,
        hasLargeAudience: false,
        actionComment: null,
        isSubmittingForm: false,
        formMessage: null,
        modalVisible: false
      }
    },

    watch: {
      selectedState: function(newValue) {
        if (newValue) {
          this.fetchLocalBusinesses();
          localStorage.setItem('selectedState', newValue);
        }
        else {
          this.localBusinesses = [];
          localStorage.removeItem('selectedState');
        }
      }
    },

    created: function() {
      this.fetchPriorityBusinesses();

      var savedState = localStorage.getItem('selectedState');

      if (savedState) {
        this.selectedState = savedState;
      }
      else {
        this.geocodeSelectedState();
      }
    },

    methods: {
      geocodeSelectedState: function() {
        var self = this;
        self.$http.get('https://fftf-geocoder.herokuapp.com').then(function(response){
          if (response.ok) {
            var geo = response.body;

            if (
              geo.country.iso_code === 'US' &&
              geo.subdivisions &&
              geo.subdivisions[0] &&
              geo.subdivisions[0].names &&
              geo.subdivisions[0].names.en
            ) {
              self.selectedState = geo.subdivisions[0].iso_code;
            }
          }
        });
      },

      uniqueifyBusinesses: function(businesses) {
        var uids = [];
        var uniqueishBusinesses = [];

        for (var i = 0; i < businesses.length; i++) {
          var biz = businesses[i];
          var uid = biz.twitter + biz.state;

          if (uids.indexOf(uid) === -1) {
            uids.push(uid);
            uniqueishBusinesses.push(biz);
          }
        }

        return uniqueishBusinesses;
      },

      fetchLocalBusinesses: function() {
        var self = this;
        self.isLoading = true;
        self.$http.get('https://data.battleforthenet.com/businesses/' + self.selectedState.toLowerCase() + '.json').then(function(response){
          if (response.ok) {
            var businesses = self.uniqueifyBusinesses(response.body);
            shuffle(businesses);
            self.localBusinesses = businesses.slice(0, 20);
            self.isLoading = false;
          }
        });
      },

      fetchPriorityBusinesses: function() {
        var self = this;
        self.$http.get('https://data.battleforthenet.com/businesses/priority.json').then(function(response){
          if (response.ok) {
            var businesses = response.body;
            shuffle(businesses);
            self.priorityBusinesses = businesses.slice(0, 12);
          }
        });
      },

      showModal: function() {
        this.modalVisible = true;
        document.querySelector('body').classList.add('modal-open');
      },

      hideModal: function() {
        this.modalVisible = false;
        document.querySelector('body').classList.remove('modal-open');
      },

      getMetaContent: function(name) {
        var el = document.querySelector('meta[name="' + name + '"]') || document.querySelector('meta[property="' + name + '"]');

        if (el) {
          return el.getAttribute('content');
        }

        return null;
      },

      openPopup: function(url, title='popup', w=600, h=500) {
        // Fixes dual-screen position
        var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
        var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;

        var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
        var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;

        var left = ((width / 2) - (w / 2)) + dualScreenLeft;
        var top = ((height / 2) - (h / 2)) + dualScreenTop;
        var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);

        // Puts focus on the newWindow
        if (window.focus) {
          newWindow.focus();
        }
      },

      shareOnFacebook: function() {
        var url = this.getMetaContent('og:url');
        this.openPopup('https://www.facebook.com/sharer.php?u=' + encodeURIComponent(url), 'facebook');
      },

      shareOnTwitter: function() {
        var tweetText = this.getMetaContent('twitter:description') + ' ' + this.getMetaContent('twitter:url');
        this.openPopup('https://twitter.com/intent/tweet?text=' + encodeURIComponent(tweetText), 'twitter');
      },

      submitForm: function() {
        var self = this;
        self.isSubmittingForm = true;
        self.$http.post('https://queue.fightforthefuture.org/action', {
          member: {
            first_name: self.name,
            email: self.email,
            postcode: self.zipCode,
            phone_number: self.phone,
            country: 'US'
          },
          hp_enabled: 'true',
          guard: '',
          contact_congress: 0,
          org: 'fftf',
          an_tags: "[\"net-neutrality\"]",
          an_petition_id: '11f84b38-e65b-4259-b0ae-e879a4044ca9',
          volunteer: self.hasLargeAudience,
          action_comment: self.actionComment
        }, { emulateJSON: true })
        .then(function(response){
          self.isSubmittingForm = false;

          if (response.ok) {
            self.resetForm();
            self.showModal();
            trackEvent('signup', 'success', 'Step 4 - Sign Up');
          }
          else {
            self.formMessage = "That didn't work for some reason :(";
          }
        })
        .catch(function(error){
          self.isSubmittingForm = false;
          self.formMessage = "That didn't work for some reason :(";
        })
      },

      resetForm() {
        this.phone = null;
        this.name = null;
        this.email = null;
        this.zipCode = null;
        this.hasLargeAudience = false;
        this.actionComment = null;
        this.formMessage = null;
      },

      openSpreadsheet() {
        window.open('https://docs.google.com/spreadsheets/d/12MkAWz8VGyIIiCgVdGXWfLKROBegZNew8-xKyPVv6vU/edit#gid=0', '_blank');
        trackEvent('button', 'click', 'Step 5 - Get Started');
      }
    }
  });

  Vue.component('business-card', {
    template: '#business-card-template',

    props: {
      business: {
        type: Object
      },
      national: {
        type: Boolean,
        default: false
      }
    },

    computed: {
      tweetURL: function() {
        var tweetText = "Hi @" + this.business.twitter + ", I’m volunteering to save #NetNeutrality, especially because losing it will hurt businesses like you. Will you join other businesses in your area by signing this letter?";

        return 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(tweetText) + '&url=' + encodeURIComponent('https://www.businessesfornetneutrality.com');
      },

      locationString: function() {
        if (this.business.city && this.business.state) {
          return [this.business.city, this.business.state].join(', ');
        }
        else if (this.business.city) {
          return this.business.city;
        }
        else if (this.business.state) {
          return this.business.state;
        }
      },

      logo: function() {
        if (this.business.twitter) {
          return 'https://data.battleforthenet.com/images/twitter/' + this.business.twitter.toLowerCase() + '.jpg';
        }
      },

      domain: function() {
        if (this.business.website) {
          var a = document.createElement('a');
          a.href = this.business.website;
          return a.hostname.replace(/^www\./i, '');
        }
      }
    },

    methods: {
      openTweetURL: function() {
        this.$parent.tweetCount += 1;
        window.open(this.tweetURL, '_blank');

        var stepNumber = this.national ? 3 : 1;
        trackEvent('business', 'tweet', 'Step ' + stepNumber + ' - ' + this.business.twitter);
      }
    }
  });
});<|endoftext|>


<commit_before>(function( $ ) {

    var months = new Array(
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
    );

    function dateFormat(date) {
        var m = date.getMonth()+1;
        var d = date.getDate();
        return date.getFullYear() + '-' + (m < 10 ? '0'+m : m) + '-' + (d < 10 ? '0'+d : d);
    }

    function getMinDate() {
        var now = new Date;
        var d = now.getUTCDate();
        var m = now.getUTCMonth();
        var y = now.getUTCFullYear();
        return new Date(y, m, d);
    }

    function getMinSlot(date) {
        var now = new Date();
        var d = now.getUTCDate();
        var m = now.getUTCMonth() + 1;
        var y = now.getUTCFullYear();

        var nowDate = y + '-' + (m<10 ? '0'+m : m) + '-' + (d<10 ? '0'+d : d);
        if (date != nowDate)
            return -1;

        var h = now.getUTCHours();
        var m = now.getUTCMinutes();
        return (h < 10 ? "0" + h : h) + ":" + (m <= 30 ? "00" : "30");
    }

    $.fn.calendar = function(timepicker, meetings, inputName) {
        var calendar = this;

        calendar.inputName = inputName;

        var requests = new Array();
        var inputs = calendar.parent();

        var prevBtn = $('<');
        var monthLbl = $('');
        var yearLbl = $('');
        var nextBtn = $('>');

        function showTimePicker(n, v) {
            if(!timepicker)
                addRequestDateTime(n, v, "00:00");

            var options = $("option", timepicker);
            options.prop('disabled', false);

            var minSlot = getMinSlot(v);
            var minSlotOptionIndex = -1;
            for (var i = 0; i < options.length; i++) {
                var option = $(options[i]);
                var t = option.val();
                if(minSlot != -1 && minSlotOptionIndex == -1) {
                    option.prop('disabled', true);
                    if (t == minSlot)
                        minSlotOptionIndex = i;
                }

                console.log(meetings[v], t);
                if(v in meetings && $.inArray(t, meetings[v]) != -1)
                    option.prop('disabled', true);

            }
            if (minSlotOptionIndex != -1)
                $(options[minSlotOptionIndex+1]).prop('selected', true);
            else
                $(options[0]).prop('selected', true);

            var offset = n.position();
            timepicker.css({
                display: 'block',
                position: 'absolute',
                zIndex: 9000,
                top: offset.top,
                left: offset.left,
                height: n.outerHeight()+6,
                width: n.outerWidth()+6
            });
            timepicker.focus();
            timepicker.data('n', n);
            timepicker.data('v', v);
        }

        $("input", timepicker).on('click', function() {
            var slot = $("select", timepicker).val();
            var n = timepicker.data('n');
            var v = timepicker.data('v');
            addRequestDateTime(n, v, slot);
            timepicker.css('display', 'none');
        });

        // play as modal
        if (timepicker) {
            $('html').on('click', function () {
                if (timepicker.css('display') == 'block')
                    timepicker.css('display', 'none');
            });
            $(timepicker).on('click', function (e) {
                e.stopPropagation();
            });
        }

        function refreshCount(n, count) {
            if (count == 0) {
                $(".calendar-date-count", n).remove();
                var date = n.data('date');
                delete meetings[date];
            } else {
                var c = $('.calendar-date-count', n);
                if (c.length == 0) {
                    var c = $('
');
                    c.addClass('calendar-date-count');
                    n.append(c);
                }
                c.text(count);
            }
        }

        function requestInputId(v, t) {
            return calendar.inputName + '-' + v + '_' + t.replace(':', '-');
        }

        function delRequestDateTime(n, v, t) {
            n.removeClass('calendar-date-selected');
            requests.splice($.inArray(v, requests), 1 );
            $('#' + requestInputId(v, t)).remove();

            refreshCount(n, meetings[v].length);
        }

        function addRequestDateTime(n, v, t) {
            n.addClass('calendar-date-selected');
            requests.push(v);

            if (!(v in meetings))
                meetings[v] = new Array();
            meetings[v].push(t);

            refreshCount(n, meetings[v].length);

            var input = $("<input type='text' name='"+ calendar.inputName +"[]' readonly />");
            input.addClass('calendar-input');
            input.attr('id', requestInputId(v, t));
            input.val(v + " " + t);

            inputs.append(input);
        }

        this.showMonth = function (year, month) {
            var minDate = getMinDate();
            var firstDay = new Date(year, month, 1);
            var monthOffset = firstDay.getDay() - 1;

            if (minDate < firstDay)
                prevBtn.removeClass('disabled');
            else
                prevBtn.addClass('disabled');

            firstDay = new Date(year, month, -monthOffset);
            var lastDay = new Date(year, month + 1, 0);

            calendar.days.empty();
            var week = $("
");
            var curr = firstDay;
            while (curr <= lastDay) {

                var d = $("");

                if (curr.getMonth() == month) {
                    var n = $("
");
                    var v = dateFormat(curr);
                    n.attr('id', 'calendar-date-' + v);
                    n.addClass('calendar-date');
                    n.addClass('calendar-month-' + (curr.getMonth()));
                    n.addClass('calendar-day-' + (curr.getDay()));
                    n.data('date', v);
                    n.text(curr.getDate());

                    if (curr == today)
                        n.addClass('calendar-date-today');

                    if (curr < minDate)
                        n.addClass('disabled');
                    else
                        n.addClass('enabled');

                    if (v in meetings) {
                        n.addClass('calendar-date-filled');
                        refreshCount(n, meetings[v].length);
                    }

                    if ($.inArray(v, requests) != -1)
                        n.addClass('calendar-date-selected');

                    n.on('click', function(e) {
                        e.preventDefault();
                        e.stopPropagation();

                        var n = $(this);
                        if (n.hasClass('enabled')) {
                            var v = $(this).data('date');
                            showTimePicker(n, v);
                        }
                    });

                    d.append(n);
                }

                week.append(d);
                if (curr.getDay() == 6) {
                    calendar.days.append(week);
                    week = $("
");
                }

                curr.setDate(curr.getDate() + 1);
            }
            calendar.days.append(week);
            monthLbl.text(months[month]);
            yearLbl.text(year);

            calendar.year = year;
            calendar.month = month;
        };

        prevBtn.on('click', function(e) {
            e.preventDefault();
            if (prevBtn.hasClass('disabled'))
                return;

            var month = calendar.month - 1;
            var year = calendar.year;
            if (month < 0) {
                month = 11;
                year = calendar.year - 1;
            }
            calendar.showMonth(year, month);
        });

        nextBtn.on('click', function(e) {
            e.preventDefault();
            if (nextBtn.hasClass('disabled'))
                return;

            var month = calendar.month + 1;
            var year = calendar.year;
            if (month > 11) {
                month = 0;
                year = calendar.year + 1;
            }
            calendar.showMonth(year, month);
        });

        this.days = $('

');

        var head = $('
');
        head.append(prevBtn);
        head.append(monthLbl);
        head.append(yearLbl);
        head.append(nextBtn);
        this.append(head);

        var table = $('

' +
            'Sun	Mon	Tue	' +
            'Wed	Thu	Fri	Sat	' +
            '

');
        table.append(this.days);
        this.append(table);

        var today = new Date;
        this.showMonth(today.getFullYear(), today.getMonth());
    }
}( jQuery ));
<commit_msg>Select first next slot when min slot is taken<commit_after>(function( $ ) {

    var months = new Array(
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
    );

    function dateFormat(date) {
        var m = date.getMonth()+1;
        var d = date.getDate();
        return date.getFullYear() + '-' + (m < 10 ? '0'+m : m) + '-' + (d < 10 ? '0'+d : d);
    }

    function getMinDate() {
        var now = new Date;
        var d = now.getUTCDate();
        var m = now.getUTCMonth();
        var y = now.getUTCFullYear();
        return new Date(y, m, d);
    }

    function getMinSlot(date) {
        var now = new Date();
        var d = now.getUTCDate();
        var m = now.getUTCMonth() + 1;
        var y = now.getUTCFullYear();

        var nowDate = y + '-' + (m<10 ? '0'+m : m) + '-' + (d<10 ? '0'+d : d);
        if (date != nowDate)
            return -1;

        var h = now.getUTCHours();
        var m = now.getUTCMinutes();
        return (h < 10 ? "0" + h : h) + ":" + (m <= 30 ? "00" : "30");
    }

    $.fn.calendar = function(timepicker, meetings, inputName) {
        var calendar = this;

        calendar.inputName = inputName;

        var requests = new Array();
        var inputs = calendar.parent();

        var prevBtn = $('<');
        var monthLbl = $('');
        var yearLbl = $('');
        var nextBtn = $('>');

        function showTimePicker(n, v) {
            if(!timepicker)
                addRequestDateTime(n, v, "00:00");

            var options = $("option", timepicker);
            options.prop('disabled', false);

            var minSlot = getMinSlot(v);
            var minSlotOptionIndex = -1;
            for (var i = 0; i < options.length; i++) {
                var option = $(options[i]);
                var t = option.val();
                if(minSlot != -1 && minSlotOptionIndex == -1) {
                    option.prop('disabled', true);
                    if (t == minSlot)
                        minSlotOptionIndex = i;
                }
                if(v in meetings && $.inArray(t, meetings[v]) != -1)
                    option.prop('disabled', true);
            }

            for (var i = 0; i < options.length; i++) {
                var option = $(options[i]);
                if (option.prop('disabled') == false) {
                    option.prop('selected', true);
                    break;
                }
            }

            var offset = n.position();
            timepicker.css({
                display: 'block',
                position: 'absolute',
                zIndex: 9000,
                top: offset.top,
                left: offset.left,
                height: n.outerHeight()+6,
                width: n.outerWidth()+6
            });
            timepicker.focus();
            timepicker.data('n', n);
            timepicker.data('v', v);
        }

        $("input", timepicker).on('click', function() {
            var slot = $("select", timepicker).val();
            var n = timepicker.data('n');
            var v = timepicker.data('v');
            addRequestDateTime(n, v, slot);
            timepicker.css('display', 'none');
        });

        // play as modal
        if (timepicker) {
            $('html').on('click', function () {
                if (timepicker.css('display') == 'block')
                    timepicker.css('display', 'none');
            });
            $(timepicker).on('click', function (e) {
                e.stopPropagation();
            });
        }

        function refreshCount(n, count) {
            if (count == 0) {
                $(".calendar-date-count", n).remove();
                var date = n.data('date');
                delete meetings[date];
            } else {
                var c = $('.calendar-date-count', n);
                if (c.length == 0) {
                    var c = $('
');
                    c.addClass('calendar-date-count');
                    n.append(c);
                }
                c.text(count);
            }
        }

        function requestInputId(v, t) {
            return calendar.inputName + '-' + v + '_' + t.replace(':', '-');
        }

        function delRequestDateTime(n, v, t) {
            n.removeClass('calendar-date-selected');
            requests.splice($.inArray(v, requests), 1 );
            $('#' + requestInputId(v, t)).remove();

            refreshCount(n, meetings[v].length);
        }

        function addRequestDateTime(n, v, t) {
            n.addClass('calendar-date-selected');
            requests.push(v);

            if (!(v in meetings))
                meetings[v] = new Array();
            meetings[v].push(t);

            refreshCount(n, meetings[v].length);

            var input = $("<input type='text' name='"+ calendar.inputName +"[]' readonly />");
            input.addClass('calendar-input');
            input.attr('id', requestInputId(v, t));
            input.val(v + " " + t);

            inputs.append(input);
        }

        this.showMonth = function (year, month) {
            var minDate = getMinDate();
            var firstDay = new Date(year, month, 1);
            var monthOffset = firstDay.getDay() - 1;

            if (minDate < firstDay)
                prevBtn.removeClass('disabled');
            else
                prevBtn.addClass('disabled');

            firstDay = new Date(year, month, -monthOffset);
            var lastDay = new Date(year, month + 1, 0);

            calendar.days.empty();
            var week = $("
");
            var curr = firstDay;
            while (curr <= lastDay) {

                var d = $("");

                if (curr.getMonth() == month) {
                    var n = $("
");
                    var v = dateFormat(curr);
                    n.attr('id', 'calendar-date-' + v);
                    n.addClass('calendar-date');
                    n.addClass('calendar-month-' + (curr.getMonth()));
                    n.addClass('calendar-day-' + (curr.getDay()));
                    n.data('date', v);
                    n.text(curr.getDate());

                    if (curr == today)
                        n.addClass('calendar-date-today');

                    if (curr < minDate)
                        n.addClass('disabled');
                    else
                        n.addClass('enabled');

                    if (v in meetings) {
                        n.addClass('calendar-date-filled');
                        refreshCount(n, meetings[v].length);
                    }

                    if ($.inArray(v, requests) != -1)
                        n.addClass('calendar-date-selected');

                    n.on('click', function(e) {
                        e.preventDefault();
                        e.stopPropagation();

                        var n = $(this);
                        if (n.hasClass('enabled')) {
                            var v = $(this).data('date');
                            showTimePicker(n, v);
                        }
                    });

                    d.append(n);
                }

                week.append(d);
                if (curr.getDay() == 6) {
                    calendar.days.append(week);
                    week = $("
");
                }

                curr.setDate(curr.getDate() + 1);
            }
            calendar.days.append(week);
            monthLbl.text(months[month]);
            yearLbl.text(year);

            calendar.year = year;
            calendar.month = month;
        };

        prevBtn.on('click', function(e) {
            e.preventDefault();
            if (prevBtn.hasClass('disabled'))
                return;

            var month = calendar.month - 1;
            var year = calendar.year;
            if (month < 0) {
                month = 11;
                year = calendar.year - 1;
            }
            calendar.showMonth(year, month);
        });

        nextBtn.on('click', function(e) {
            e.preventDefault();
            if (nextBtn.hasClass('disabled'))
                return;

            var month = calendar.month + 1;
            var year = calendar.year;
            if (month > 11) {
                month = 0;
                year = calendar.year + 1;
            }
            calendar.showMonth(year, month);
        });

        this.days = $('

');

        var head = $('
');
        head.append(prevBtn);
        head.append(monthLbl);
        head.append(yearLbl);
        head.append(nextBtn);
        this.append(head);

        var table = $('

' +
            'Sun	Mon	Tue	' +
            'Wed	Thu	Fri	Sat	' +
            '

');
        table.append(this.days);
        this.append(table);

        var today = new Date;
        this.showMonth(today.getFullYear(), today.getMonth());
    }
}( jQuery ));
<|endoftext|>


<commit_before>if( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function() {
  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
  try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
  throw new Error( "This browser does not support XMLHttpRequest." )
};
/**
 * @namespace Meteor js connection namespace.
 */
meteor = new Object();
/**
 * Limit for the incoming buffer. When the incoming XMLHttpRequest object buffer grows larger than this, the connection is reinitiated.
 */
meteor.BUFFER_MAX = 1 << 16; // limit for incoming buffer, exceeding this buffer triggers a reconnect
/**
 * Meteor connection class. This is usually used with Atom serialization on top. Use psyc.Client if unsure.
 * @param {String} url URL of the Meteor connection endpoint.
 * @param {Function} callback Function to be called when data has been received.
 * @param {Function} error Function to be called when a fatal error occures.
 * @constructor
 * @example
 * var connection;
 * var incoming = function(data) {
 * 	document.write("Received data: " + data);
 * 	connection.send("Hello World.\n");
 * }
 * connection = new meteor.Connection("http://example.org/meteor/", incoming, alert);
 * connection.init();
 */
meteor.Connection = function(url, callback, error) {
    this.url = url;
    this.buffer = "";
    this.callback = callback;
    this.error = error;
	this.client_id = 0;
	this.new_incoming = 0;
	this.incoming = 0;
	this.outgoing = 0;
	this.init_xhr = 0;
	this.reconnect = 1; // do a reconnect on close
};
meteor.Connection.prototype = {
	new_incoming_state_change : function() {
		var con = this.meteor;
		con.error("new_incoming state is " + this.readyState);
		// we should check here for buffer length. maybe set a max
		// amount to shut down the main one ungracefully
		if (this.readyState >= 3) {
			con.connect_incoming();
		} else if (window.opera) {
			var xhr = this;
			setTimeout((function() {
				meteor.Connection.prototype.new_incoming_state_change.call(xhr);
			}), 200);
		}
	},
	connect_new_incoming : function() {
		if (this.new_incoming) {
			// we already have one new incoming and are waiting for the
			// main one to shut down

			if (this.new_incoming.readyState == 4) {
			// someone is too fast for us.
			// TODO: we have to check for data in new_incoming,
			// not sure what to do with it. we can probably savely
			// parse it in case of atoms.
				if (meteor.debug) meteor.debug("New connection already finished.\n");
			} else return this.connect_incoming();
		}

		if (meteor.debug) meteor.debug("Connecting new incoming.\n");

		var xhr = new XMLHttpRequest();
		this.new_incoming = xhr;
		xhr.pos = 0;

		xhr.open("POST", this.url + "?" + this.client_id, true);
		//xhr.overrideMimeType("text/plain; charset=ISO-8859-1");
		if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
		xhr.onreadystatechange = this.new_incoming_state_change;
		xhr.meteor = this;
		xhr.send("");
	},
	incoming_state_change : function() {
		var con = this.meteor;
		var status;
		if (meteor.debug) meteor.debug("incoming in readyState "+this.readyState);
		if (this.readyState >= 3) {
			//this.readyState = 2;

			// stupid workaround for IE which was written by
			// stupid assholes
			try {
				status = this.status;
			} catch(e) {
				// this is ie country
				// this is ie
				if (meteor.debug) meteor.debug("fucking ie");
			}

			if (meteor.debug) meteor.debug("http status code: "+status);
			if (status == 200) {
				var length = this.responseBody ? this.responseBody.length : this.responseText.length;
				var response = this.responseBody ? this.reponseBody : this.responseText;

				if (length > this.pos) {
					var str;

					if (this.pos) {
						str = (response.slice(this.pos));
					} else {
						str = response;
					}

					// ifdef firefox or safari
					if (navigator.userAgent.indexOf("Firefox") != -1 || navigator.userAgent.indexOf("Safari") != -1) {
						var t = new Array;
						for (var i = 0; i < str.length; i++) {
							t.push(str.charCodeAt(i) & 0xff);
						}
						str = String.fromCharCode.apply(window, t);
					}
					// endif

					try {
						if (con.callback.obj) {
							con.callback.call(con.callback.obj, str);
						} else {
							con.callback(str);
						}
					} catch (error) {
						if (meteor.debug) meteor.debug("ERROR: "+error);
					}

					this.pos = length;
				}

				if (this.readyState == 4 || this.pos >= meteor.BUFFER_MAX) {
					if (con.reconnect) con.connect_new_incoming();
				}
			} else {
			// this throws an exception in firefox. brain
			//	con.error(this.statusText);
			}
		}
	},
	incoming_on_error : function() {
		this.meteor.connect_new_incoming();
	},
	connect_incoming : function(xhr) {
		if (!xhr) {
			if (this.new_incoming) {
				xhr = this.new_incoming;
			} else throw("you need to call new_incoming() first. no this.new_incoming.");
		}

		if (this.incoming) {
			if (this.incoming.readyState != 4) {
				return;
			}

			try { this.incoming.abort(); } catch (e) {}
		}

		if (this.operatimer) {
			clearTimeout(this.operatimer);
			this.operatimer = null;
		}

		xhr.onreadystatechange = this.incoming_state_change;
		xhr.onerror = this.incoming_on_error;
		this.error("moved new incoming to incoming.\n");
		this.new_incoming = 0;
		this.incoming = xhr;
		meteor.Connection.prototype.incoming_state_change.call(xhr);

		// This code polls the xhr for new data in case opera is used. its necessary
		// because opera does not trigger an event if new data is available in state 3.
		if (window.opera) {
			var fun = function() {
				meteor.Connection.prototype.incoming_state_change.call(xhr);
			};
			this.operatimer = setInterval(fun, 100);
			if (meteor.debug) meteor.debug("timer: "+this.operatimer);
		}
	},
	outgoing_state_change : function() {
		var con = this.meteor;

		if (this.readyState == 4) {

			if (this.status == 200) {

				con.connect_outgoing();
			} else {
				con.error(this.statusText);
			}
		}
	},
	connect_outgoing : function() {
		var xhr;

		if (this.outgoing) {
			this.outgoing.abort();
			xhr = this.outgoing;
		} else {
			xhr = new XMLHttpRequest();
			this.outgoing = xhr;
		}
		//this.error("outgoing state is " + xhr.readyState);

		xhr.open("POST", this.url + "?" + this.client_id, true);
		// we do this charset hackery because we have internal utf8 and plain ascii
		// for the rest of atom. this is supposed to be a binary transport
		xhr.setRequestHeader("Content-Type", "application/binary");
		xhr.onreadystatechange = this.outgoing_state_change;
		xhr.meteor = this;
		this.ready = 1;
	},
	init_state_change : function(change_event) { // fetch the client_id and go
		var con = this.meteor;
		if (this.readyState == 4) {
			if (this.status == 200) {
				con.client_id = this.responseText;
				if (meteor.debug) meteor.debug("got client ID " + con.client_id);
				con.init_xhr = null;
				con.connect_outgoing();
				con.connect_new_incoming();
			} else {
				con.error(this.statusText);
			}
			this.meteor = null;
			//console.debug("not yet in readyState 4\n");
		}
	},
	/**
	 * Initialize the connection. This needs to be called before any data can be sent or received.
	 */
	init : function() { // fetch the client_id and go
		var xhr = new XMLHttpRequest();
		xhr.meteor = this;
		this.reconnect = 1;
		this.init_xhr = xhr;
		//this.error("initialization state is " + xhr.readyState);

		xhr.onreadystatechange = this.init_state_change;
		xhr.meteor = this;
		xhr.open("GET", this.url, true);
		xhr.send("");
	},
	/**
	 * Close incoming connection and clean up cyclic references.
	 */
	destruct : function() {
		var list = [this.init_xhr, this.new_incoming, this.incoming, this.outgoing];
		for (var t in list) {
			t = list[t];
			try {
				if (t) {
					t.abort();
					t.meteor = null;
				}
			} catch(e) { }
		}
		this.init_xhr = null;
		this.incoming = null;
		this.new_incoming = null;
		this.outgoing = null;
		this.callback = null;
		this.error = null;
	},
	/**
	 * Send some data.
	 * @param {String} data String to be sent.
	 */
	send : function(data) {
		// check for status
		this.buffer += data;

		if (this.client_id && this.ready) {
			// ifdef firefox
			//this.outgoing.setRequestHeader("Content-Length", this.buffer.length);
			// endif
			this.outgoing.send(this.buffer);
			this.ready = 0;
			this.buffer = "";
		}
	}
};
// the params handed by the user could be prototyped with a
// msg and something more
meteor.CallbackWrapper = function(params, mapping) {
	this.mapping = mapping;
	this.params = params;
};
meteor.CallbackWrapper.prototype = {
	msg : function(message) {
		if (!this.params.source || this.params.source == message.vars.get("_source")) {
			if (this.params.object) {
				if (this.params.cb) {
					return this.params.cb.call(this.params.object, message, this);
				}

				return this.params.object.msg(message, this);
			} else {
				return this.params.cb(message, this);
			}
		}
	},
	active : function() {
		return this.mapping == 0 ? 0 : 1;
	},
	unregister : function() {
		if (this.mapping == 0) return;

		var list = this.mapping.get(this.params.method);

		for (var i = 0; i < list.length; i++) {
			if (list[i] == this) {
				list.splice(i, 1);
			}
		}

		this.mapping = 0;
	}
};
<commit_msg>write with call_out to bundle several packets in one request<commit_after>if( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function() {
  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
  try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
  try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
  throw new Error( "This browser does not support XMLHttpRequest." )
};
/**
 * @namespace Meteor js connection namespace.
 */
meteor = new Object();
/**
 * Limit for the incoming buffer. When the incoming XMLHttpRequest object buffer grows larger than this, the connection is reinitiated.
 */
meteor.BUFFER_MAX = 1 << 16; // limit for incoming buffer, exceeding this buffer triggers a reconnect
/**
 * Meteor connection class. This is usually used with Atom serialization on top. Use psyc.Client if unsure.
 * @param {String} url URL of the Meteor connection endpoint.
 * @param {Function} callback Function to be called when data has been received.
 * @param {Function} error Function to be called when a fatal error occures.
 * @constructor
 * @example
 * var connection;
 * var incoming = function(data) {
 * 	document.write("Received data: " + data);
 * 	connection.send("Hello World.\n");
 * }
 * connection = new meteor.Connection("http://example.org/meteor/", incoming, alert);
 * connection.init();
 */
meteor.Connection = function(url, callback, error) {
    this.url = url;
    this.buffer = "";
    this.callback = callback;
    this.error = error;
	this.client_id = 0;
	this.new_incoming = 0;
	this.incoming = 0;
	this.outgoing = 0;
	this.init_xhr = 0;
	this.reconnect = 1; // do a reconnect on close
};
meteor.Connection.prototype = {
	new_incoming_state_change : function() {
		var con = this.meteor;
		con.error("new_incoming state is " + this.readyState);
		// we should check here for buffer length. maybe set a max
		// amount to shut down the main one ungracefully
		if (this.readyState >= 3) {
			con.connect_incoming();
		} else if (window.opera) {
			var xhr = this;
			setTimeout((function() {
				meteor.Connection.prototype.new_incoming_state_change.call(xhr);
			}), 200);
		}
	},
	connect_new_incoming : function() {
		if (this.new_incoming) {
			// we already have one new incoming and are waiting for the
			// main one to shut down

			if (this.new_incoming.readyState == 4) {
			// someone is too fast for us.
			// TODO: we have to check for data in new_incoming,
			// not sure what to do with it. we can probably savely
			// parse it in case of atoms.
				if (meteor.debug) meteor.debug("New connection already finished.\n");
			} else return this.connect_incoming();
		}

		if (meteor.debug) meteor.debug("Connecting new incoming.\n");

		var xhr = new XMLHttpRequest();
		this.new_incoming = xhr;
		xhr.pos = 0;

		xhr.open("POST", this.url + "?" + this.client_id, true);
		//xhr.overrideMimeType("text/plain; charset=ISO-8859-1");
		if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
		xhr.onreadystatechange = this.new_incoming_state_change;
		xhr.meteor = this;
		xhr.send("");
	},
	incoming_state_change : function() {
		var con = this.meteor;
		var status;
		if (meteor.debug) meteor.debug("incoming in readyState "+this.readyState);
		if (this.readyState >= 3) {
			//this.readyState = 2;

			// stupid workaround for IE which was written by
			// stupid assholes
			try {
				status = this.status;
			} catch(e) {
				// this is ie country
				// this is ie
				if (meteor.debug) meteor.debug("fucking ie");
			}

			if (meteor.debug) meteor.debug("http status code: "+status);
			if (status == 200) {
				var length = this.responseBody ? this.responseBody.length : this.responseText.length;
				var response = this.responseBody ? this.reponseBody : this.responseText;

				if (length > this.pos) {
					var str;

					if (this.pos) {
						str = (response.slice(this.pos));
					} else {
						str = response;
					}

					// ifdef firefox or safari
					if (navigator.userAgent.indexOf("Firefox") != -1 || navigator.userAgent.indexOf("Safari") != -1) {
						var t = new Array;
						for (var i = 0; i < str.length; i++) {
							t.push(str.charCodeAt(i) & 0xff);
						}
						str = String.fromCharCode.apply(window, t);
					}
					// endif

					try {
						if (con.callback.obj) {
							con.callback.call(con.callback.obj, str);
						} else {
							con.callback(str);
						}
					} catch (error) {
						if (meteor.debug) meteor.debug("ERROR: "+error);
					}

					this.pos = length;
				}

				if (this.readyState == 4 || this.pos >= meteor.BUFFER_MAX) {
					if (con.reconnect) con.connect_new_incoming();
				}
			} else {
			// this throws an exception in firefox. brain
			//	con.error(this.statusText);
			}
		}
	},
	incoming_on_error : function() {
		this.meteor.connect_new_incoming();
	},
	connect_incoming : function(xhr) {
		if (!xhr) {
			if (this.new_incoming) {
				xhr = this.new_incoming;
			} else throw("you need to call new_incoming() first. no this.new_incoming.");
		}

		if (this.incoming) {
			if (this.incoming.readyState != 4) {
				return;
			}

			try { this.incoming.abort(); } catch (e) {}
		}

		if (this.operatimer) {
			clearTimeout(this.operatimer);
			this.operatimer = null;
		}

		xhr.onreadystatechange = this.incoming_state_change;
		xhr.onerror = this.incoming_on_error;
		this.error("moved new incoming to incoming.\n");
		this.new_incoming = 0;
		this.incoming = xhr;
		meteor.Connection.prototype.incoming_state_change.call(xhr);

		// This code polls the xhr for new data in case opera is used. its necessary
		// because opera does not trigger an event if new data is available in state 3.
		if (window.opera) {
			var fun = function() {
				meteor.Connection.prototype.incoming_state_change.call(xhr);
			};
			this.operatimer = setInterval(fun, 100);
			if (meteor.debug) meteor.debug("timer: "+this.operatimer);
		}
	},
	outgoing_state_change : function() {
		var con = this.meteor;

		if (this.readyState == 4) {

			if (this.status == 200) {

				con.connect_outgoing();
			} else {
				con.error(this.statusText);
			}
		}
	},
	connect_outgoing : function() {
		var xhr;

		if (this.outgoing) {
			this.outgoing.abort();
			xhr = this.outgoing;
		} else {
			xhr = new XMLHttpRequest();
			this.outgoing = xhr;
		}
		//this.error("outgoing state is " + xhr.readyState);

		xhr.open("POST", this.url + "?" + this.client_id, true);
		// we do this charset hackery because we have internal utf8 and plain ascii
		// for the rest of atom. this is supposed to be a binary transport
		xhr.setRequestHeader("Content-Type", "application/binary");
		xhr.onreadystatechange = this.outgoing_state_change;
		xhr.meteor = this;
		this.ready = 1;

		if (this.buffer.length > 0) this.write();
	},
	init_state_change : function(change_event) { // fetch the client_id and go
		var con = this.meteor;
		if (this.readyState == 4) {
			if (this.status == 200) {
				con.client_id = this.responseText;
				if (meteor.debug) meteor.debug("got client ID " + con.client_id);
				con.init_xhr = null;
				con.connect_outgoing();
				con.connect_new_incoming();
			} else {
				con.error(this.statusText);
			}
			this.meteor = null;
			//console.debug("not yet in readyState 4\n");
		}
	},
	/**
	 * Initialize the connection. This needs to be called before any data can be sent or received.
	 */
	init : function() { // fetch the client_id and go
		var xhr = new XMLHttpRequest();
		xhr.meteor = this;
		this.reconnect = 1;
		this.init_xhr = xhr;
		//this.error("initialization state is " + xhr.readyState);

		xhr.onreadystatechange = this.init_state_change;
		xhr.meteor = this;
		xhr.open("GET", this.url, true);
		xhr.send("");
	},
	/**
	 * Close incoming connection and clean up cyclic references.
	 */
	destruct : function() {
		var list = [this.init_xhr, this.new_incoming, this.incoming, this.outgoing];
		for (var t in list) {
			t = list[t];
			try {
				if (t) {
					t.abort();
					t.meteor = null;
				}
			} catch(e) { }
		}
		this.init_xhr = null;
		this.incoming = null;
		this.new_incoming = null;
		this.outgoing = null;
		this.callback = null;
		this.error = null;
	},
	/**
	 * Send some data.
	 * @param {String} data String to be sent.
	 */
	send : function(data) {
		// check for status
		meteor.debug("Appending ("+data.substr(data.length-40, 39)+")\n");
		meteor.debug(this.ready +" "+this.will_write);
		this.buffer += data;

		if (this.client_id && this.ready && !this.will_write) {
			// ifdef firefox
			//this.outgoing.setRequestHeader("Content-Length", this.buffer.length);
			// endif
			var self = this;
			var cb = function() {
				self.will_write = 0;
				self.write();
			}
			this.will_write = 1;
			window.setTimeout(cb, 20);
		}
	},
	write : function() {
		this.outgoing.send(this.buffer);
		meteor.debug("Sending ("+this.buffer.substr(this.buffer.length-40, 39)+")\n");
		this.ready = 0;
		this.buffer = "";
	}
};
// the params handed by the user could be prototyped with a
// msg and something more
meteor.CallbackWrapper = function(params, mapping) {
	this.mapping = mapping;
	this.params = params;
};
meteor.CallbackWrapper.prototype = {
	msg : function(message) {
		if (!this.params.source || this.params.source == message.vars.get("_source")) {
			if (this.params.object) {
				if (this.params.cb) {
					return this.params.cb.call(this.params.object, message, this);
				}

				return this.params.object.msg(message, this);
			} else {
				return this.params.cb(message, this);
			}
		}
	},
	active : function() {
		return this.mapping == 0 ? 0 : 1;
	},
	unregister : function() {
		if (this.mapping == 0) return;

		var list = this.mapping.get(this.params.method);

		for (var i = 0; i < list.length; i++) {
			if (list[i] == this) {
				list.splice(i, 1);
			}
		}

		this.mapping = 0;
	}
};
<|endoftext|>