Learn how to use jQuery at the Blog

Gambolio « visit

  • Added 6 months ago
  • 2058 Lines of Code shown
  • 0 Links of Interest
http://gambolio.com
This is my Source Code and I don't want to show it here
View Source Code only (as overlay)
// That code snippet belongs to Gambolio - http://gambolio.com

try {
  document.execCommand("BackgroundImageCache", false, true);
} catch(err) {}

var AJKHelpers = {
	waitForId: function(dataObj) {
		var anObject = dataObj.anObject;
		var callback = dataObj.callback;
		var checkForId = function() {
			if (anObject.id == "awaiting") {
				var thisFunc = arguments.callee;
				setTimeout( function() {
					thisFunc();
				}, 100);	
			}
			else if (callback) {
				callback();
			}
		}
		checkForId();
	},
	cloneObj: function(dataObj) {
		var obj = dataObj.obj
    	if (obj == null || typeof(obj) != 'object') {
        	return obj;
    	}
    	var temp = new obj.constructor(); // changed (twice)
    	for (var key in obj) {
        	temp[key] = this.cloneObj(obj[key]);
    	}
    	return temp;
	},
	jiggleDomEl: function(dataObj) {
		var	domEl = dataObj.domEl;
		var displacementFactor = dataObj.displacementFactor;
		var leftOffset = ($(domEl).css("left") && $(domEl).css("left") != "auto") ? parseInt($(domEl).css("left")) : 0;
		var position = $(domEl).css("position");
		if (position != "absolute") {
			$(domEl).css({position: "relative"});
		}
		var numRadians = 16;
		var steps = 25;
		for (var counter = 0; counter <= numRadians; counter+=0.2) {
			(function() {
				var lOffset = leftOffset + Math.cos(counter)*displacementFactor;
				var delay = parseInt(counter * steps);
				setTimeout( function() {
					$(domEl).css({left: lOffset});
					if (Math.abs(delay - (numRadians * steps)) < 5) {
						$(domEl).css({position: position, left: leftOffset});
					}
				}, delay);
			}());	
		}
	},
	flashDomEl: function(dataObj) {
		var	domEl = dataObj.domEl;
		var numRadians = 12;
		var steps = 50;
		for (var counter = 0; counter <= numRadians; counter+=0.2) {
			(function() {
				var opacity = Math.cos(counter);
				var delay = parseInt(counter * steps);
				setTimeout( function() {
					$(domEl).css({opacity: 0.3 + 0.7 * opacity});
					if (Math.abs(delay - (numRadians * steps)) < 5) {
						$(domEl).css({opacity: 1 });
					}
				}, delay);
			}());	
		}
	},
	isEmail: function(dataObj) {
		var self = this;
		var aString = dataObj.aString;	
		return aString.match(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
	},
	cancelSelectionOnDomEl: function(dataObj) {
		var domEl = dataObj.domEl;
	    domEl.onselectstart = function() {
	        return false;
	    };
	    domEl.unselectable = "on";
	    domEl.style.MozUserSelect = "none";
	},
	getCoordsOfDomEl: function(dataObj) {
		var domEl = dataObj.domEl;
		var xPos = yPos = 0;
		if (domEl.offsetParent) {
			xPos = domEl.offsetLeft;
			yPos = domEl.offsetTop;
			while (domEl = domEl.offsetParent) {
				xPos += domEl.offsetLeft;
				yPos += domEl.offsetTop;
			}
		}
		return { x:xPos, y:yPos };
	},
	get4CoordsOfDomEl: function(dataObj) {
		var domEl = dataObj.domEl;
		var coords = this.getCoordsOfDomEl(dataObj);
		coords.x2 = coords.x + $(domEl).width();
		coords.y2 = coords.y + $(domEl).height();
		return coords;
	},
	calculateDomElHeight: function(dataObj) {
		var self;
		var domEl = dataObj.domEl;
		var height = $(domEl).height()
		height += parseInt($(domEl).css("marginTop"));
		height +parseInt($(domEl).css("marginBottom"));
		return height;		
	},
	getNowDate: function() {
		return new Date();	
	},
	trimArray: function(dataObj) {
		var anArray = dataObj.anArray;
		var limit = (anArray.length < dataObj.limit) ? anArray.length : dataObj.limit;
		var counter = 0;
		var retArray = new Array();
		while (counter < limit) {
			retArray.push(anArray[counter++]);
		}
		return retArray;
	},
	dateFromMySQLDate: function(dataObj){
		var dateString = dataObj.dateString;
		if (!dateString || dateString == "undefined") {
			return false;
		}
		// Sample date: 2009-04-13 20:16:58
		var dateAllArray = dateString.split(" ");
		var dateArray = dateAllArray[0].split("-");
		var timeArray = dateAllArray[1].split(":"); 
		//...
		var date = new Date();
		date.setDate(parseInt(dateArray[2],10));
		date.setMonth(parseInt(dateArray[1],10)-1);
		date.setFullYear(parseInt(dateArray[0],10));
		
		date.setHours(parseInt(timeArray[0],10));
		date.setMinutes(parseInt(timeArray[1],10));
		date.setSeconds(parseInt(timeArray[2],10));

		return date;
	},
	dateWeekDayArray: ["Sunday","Monday","Tuesday","Wednesday", "Thursday","Friday","Saturday"],
	dateWeekDayShortArray: ["Sun","Mon","Tue","Wed", "Thu","Fri","Sat"],
	dateMonthsArray: ["January","February","March","April","May","June", "July","August","September","October","November","December"],
	dateMonthsShortArray: ["Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec"],
	dateDaySuffixArray: ["st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th", "st","nd","rd","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th"],
    dateOneDayInMS: 1000*60*60*24,
	prettyDateFromMySQLDate: function(dataObj) {
		var jsDate = this.dateFromMySQLDate({ dateString: dataObj.dateString });
		return this.prettyDateFromDate({date: jsDate});	    
	},
	prettyDateFromDate: function(dataObj) {
		var jsDate = dataObj.date;
		var smallDate = dataObj.smallDate;
	    
	    var x = jsDate.getYear();
        var y = x % 100;
        y += (y < 38) ? 2000 : 1900;
        var year = y;
		
		if (smallDate) {
			return jsDate.getDate()+"/"+(jsDate.getMonth()+1)+"/"+year.toString().substring(2,4);
		}
		else {
			return jsDate.getDate()+this.dateDaySuffixArray[jsDate.getDate()-1]+" "+this.dateMonthsShortArray[jsDate.getMonth()]+" "+year;
		}
	},
	prettyTimeFromDate: function(dataObj) {
		var jsDate = dataObj.date;
		var amOrPm = (jsDate.getHours() > 11) ? "pm" : "am";  
		var hour = jsDate.getHours() % 12;
		hour = (hour == 0) ? 12 : hour;
		var minutes = jsDate.getMinutes();
		
		return hour+":"+this.doubleDigitNum({ num: minutes })+amOrPm;	
	},
	prettyTimeFromMySQLDate: function(dataObj) {
		var jsDate = this.dateFromMySQLDate({ dateString: dataObj.dateString });
		return this.prettyTimeFromDate({date: jsDate});
	},
	doubleDigitNum: function(dataObj) {
		var num = parseInt(dataObj.num);
		if (num == 0) {
			return "00";	
		}
		else if (num < 10) {
			return "0"+num;	
		}
		else {
			return num;	
		}
	},
	deleteCookie: function(dataObj) {
		var name = dataObj.name;
		document.cookie = name +'=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
	},
	setCookie: function(dataObj) {
	    var name = dataObj.name;
	    var value = dataObj.value;
	    var expires = dataObj.expires;
	    var path = dataObj.path;
	    var domain = dataObj.domain;
	    var secure = dataObj.secure;
	    var today = new Date();
	    today.setTime( today.getTime() );
	    if (expires) {
	        expires = expires * 1000 * 60 * 60 * 24;
	    }
	    var expires_date = new Date( today.getTime() + (expires) );
	
	    document.cookie = name + "=" +escape( value ) +
	    ( ( expires ) ? ";expires=" + expires_date.toGMTString() : "" ) +
	    ( ( path ) ? ";path=" + path : "" ) +
	    ( ( domain ) ? ";domain=" + domain : "" ) +
	    ( ( secure ) ? ";secure" : "" );
	},
	getCookie: function(dataObj) {
		var name = dataObj.name;
	    var start = document.cookie.indexOf( name + "=" );
	    var len = start + name.length + 1;
	    if ((!start) && (name != document.cookie.substring(0, name.length))) {
	        return null;
	    }
	    if (start == -1) { return null; }
	    var end = document.cookie.indexOf( ";", len );
	    if (end == -1) { end = document.cookie.length; }
	    return unescape(document.cookie.substring(len, end));
	},
	removeItemFromArray: function(dataObj) {
		var item = dataObj.item;
		var anArray = dataObj.anArray;
		var returnArray = new Array();
		for (var counter = 0; counter < anArray.length; counter++) {
			if (anArray[counter] != item) {
				returnArray.push(anArray[counter]);	
			}
		}
		return returnArray;
	},
	isItemInArray: function(dataObj) {
		var item = dataObj.item;
		var anArray = dataObj.anArray;
		for (var counter = 0; counter < anArray.length; counter++) {
			if (anArray[counter] == item) {
				return true;	
			}
		}
		return false;
	},
	cloneArray: function(dataObj) {
		var anArray = dataObj.anArray;
		var returnArray = new Array();
		for (var counter = 0; counter < anArray.length; counter++) {
			returnArray.push(anArray[counter]);	
		}
		return returnArray;
	},
	scrollbarWidth: function() {
		// Scrollbalken im Body ausschalten
		document.body.style.overflow = 'hidden';
		var width = document.body.clientWidth;
	 
		// Scrollbalken
		document.body.style.overflow = 'scroll';
	 
		width -= document.body.clientWidth;
	 
		// Der IE im Standardmode
		if(!width) width = document.body.offsetWidth-document.body.clientWidth;
	 
		// urspr?ngliche Einstellungen
		document.body.style.overflow = '';
	 
		return width;
	},
	extend: function(subClass, superClass) {
		// Inheritance function courtesy Ross Harmes/Dunstan Diaz
		var F = function() {};
		F.prototype = superClass.prototype;
		subClass.prototype = new F();
		subClass.prototype.constructor = subClass;
		subClass.superClass = superClass.prototype;
		if (superClass.prototype.constructor == Object.prototype.constructor) {
			superClass.prototype.constructor = superClass;	
		}	
	},
	clipToMaxCharWords: function(dataObj) {
		var aString = dataObj.aString;
		var maxChars = dataObj.maxChars;
	    var wordarray = aString.split(" ");
	    var numwords=wordarray.length;
	    var newstring="";
	    var laststring="";
	    for (var counter=0; counter<numwords; counter++) {
	        laststring=newstring
	        if (counter!=0) {
	            newstring+=(" "+wordarray[counter]);
	        }
	        else {
	            newstring+=wordarray[counter];
	        }
	        if (newstring.length>maxChars) {
	            return (laststring+"...");
	        }
	    }
	    return newstring;
	},
	viewportSize: function() {
		var myWidth = 0, myHeight = 0;
		if (typeof(window.innerWidth) == 'number' ) {
			//Non-IE
			myWidth = window.innerWidth;
			myHeight = window.innerHeight;
		} 
		else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
			//IE 6+ in 'standards compliant mode'
			myWidth = document.documentElement.clientWidth;
			myHeight = document.documentElement.clientHeight;
		} 
		else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
			//IE 4 compatible
			myWidth = document.body.clientWidth;
			myHeight = document.body.clientHeight;
		}
		return {
	  		width: myWidth,
	  		height: myHeight	
		}
	},
	decipherQueryResult: function(dataObj) {
		return dataObj.queryResult;	
	}	
	
}
var QMBrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
QMBrowserDetect.init();
var AJKAjaxController = function(dataObj) {
	self.loginController = "";
	return this;
}

AJKAjaxController.prototype = {
	init: function() {
		var self = this;
		return self;	
	},
	request: function(dataObj) {
		var self = this;
		var action = dataObj.action;
		var vars = dataObj.vars;
		var callback = dataObj.callback;
		var method = dataObj.method;
		var alwaysAllow = dataObj.alwaysAllow;
		
		if (action == "assets/ajax/load-range-of-shop-games.php") {
			action = "assets/ajax/cached-data/shop-data-"+vars.genre+"-"+vars.start+"-"+vars.limit+".php";
		}
		
		//alert(action);
		if (self.loginController.user.loggedIn || method == "get" || alwaysAllow) {
			// Only get functions work for people who aren't logged in
			//vars.verifyCode = self.loginController.user.verifyCode;
			if (!vars.userId && self.loginController.user.loggedIn) {
				// userId defaults to the logged in user unless it has been reset
				vars.userId = anAJKLibraryController.user.id; 
			}
			var rFunc = (method == "post") ? $.post : $.get;
			rFunc(action, vars, function(data) {
				if (data == "verify-code-mismatch") {
					// Refresh page
					AJKHelpers.deleteCookie({name: self.loginController.userCookieName });
					location.reload(true);
				}
				else if (callback) {
					callback(data);
				}
			});
		}
		else {
			// Error user not logged in. Prob just do nothing. Prob looking at the demo	
		}
	}
}
var AJKTableController = function(dataObj) {
	this.domRootEl = dataObj.domRootEl;
	this.domCarousel = $(this.domRootEl).find(".mgr-g-carousel").get()[0];
	this.domStage = $(this.domRootEl).find(".mgr-g-stage").get()[0];
	this.domBody = $("body").get()[0];
	this.domSortButtons	= $(this.domRootEl).find(".rt-list-header li").get();
	this.domShowButton = dataObj.domShowButton;
	this.domShopTable = dataObj.domShopTable;
	this.domNumGamesDisplayer  = dataObj.domNumGamesDisplayer; // The domel when the number of games for the table is displayed
	this.title = dataObj.title;
	this.domTablePrototype = $(this.domRootEl).clone().get()[0];
	this.containerHeight = dataObj.containerHeight;
	this.carouselHeightOffset = 110;
	this.delegate = dataObj.delegate;
	this.defaultSort = dataObj.defaultSort;
	this.type = dataObj.type; // standard, shop, friend
	this.genre = dataObj.genre; // standard, shop, friend
	this.carouselRatio = 0;
	this.games = dataObj.games;
	this.gamesByKey = new Array();
	this.entries = new Array();
	this.entriesByKey = new Array();
	this.entriesByGameKey = new Array();
	this.nextUniqueKey = 1;
	this.uniqueKeyText = "entry-";
	this.entriesPerBlock = 30;
	this.entriesHeight = 30;
	this.thumbHeight = 214;
	this.listToThumbRatio = this.entriesHeight * this.entriesPerBlock / (this.thumbHeight * this.entriesPerBlock/5);
	this.infoPanelHeight = 230;
	this.fullBlockHeight = this.entriesPerBlock * this.entriesHeight;  
	this.blocks = new Array();
	this.numBlocks = 0;
	this.visibleBlocks = new Array();
	this.lastVisibleBlockNum = -1;
	this.sortController = "";
	this.entriesReady = false;
	this.controlsDisabled = true;
	this.addingEntries = false;
	this.windowScrollTop = 0;
	this.bodyHeight = "";
	this.stageHeight = "";
	this.currentlyAddingGames = false;
	this.lastVisibleBlocks = new Array();
	this.controller = dataObj.controller;
	this.ownerTable = ""; // Only set for tables that are the filtered results of other tables
	this.searchTable = ""; // A reference to a tables current table of filtered results
	this.tableMode = dataObj.tableMode;
	this.domTableViewOptions = $(this.domRootEl).find(".rt-l-top-options");
	this.domShowListView = $(this.domTableViewOptions).find(".list").get()[0]; 
	this.domShowThumbView = $(this.domTableViewOptions).find(".thumbs").get()[0]; 
	return this;
}

AJKTableController.prototype = {
	init: function() {
		var self = this;
		
		if (self.tableMode == "list") {
			$(self.domRootEl).removeClass("mgr-table-thumbnails");
			$(self.domShowThumbView).removeClass("view-selected");
			$(self.domShowListView).addClass("view-selected");
		}
		else {
			$(self.domRootEl).addClass("mgr-table-thumbnails");
			$(self.domShowThumbView).addClass("view-selected");
			$(self.domShowListView).removeClass("view-selected");
		}
				
		$.each(self.games, function() {
			self.gamesByKey[this.id] = this;
		});

		self.sortController = new AJKTableSortController({
			domSortEls: self.domSortButtons,
			defaultSort: self.defaultSort,
			sortFunc: function(dataObj) {
				self.sortEntries(dataObj);
				self.refreshEntries();
			}	
		}).init();
		
		var multipleEventsController = new AJKMultipleEventController({
			domRootEl: self.domStage,
			clickFunc: function(dataObj) {
				var e = dataObj.event;
				var entryEl = $(e.target).parents(".mgr-g-item");
				if (entryEl.length > 0) {
					var entry = self.entriesByKey[$(entryEl).attr("key")];
					if (e.target.className.indexOf("mgr-gi-select-add") != -1) {
						self.addGameToUserLibrary({ entry: entry });
					}
					else if (self.tableMode == "list") {
						entry.toggleInfoPanel();
					}
					else if (e.target.className.indexOf("mgr-gi-t-option") != -1) {
						e.preventDefault();
						switch ($(e.target).attr("option")) {
							case "play":
							self.delegate.loadGame({
								game: entry.game
							});
							break;
							case "more-info":
							self.delegate.showGameInfo({
								game: entry.game
							});
						}				
					}
				}
				else if (e.target.className.indexOf("mgr-g-info-button") != -1) {
					e.preventDefault();
					switch ($(e.target).attr("option")) {
						case "close":
						self.entriesByKey[$(e.target).attr("entryKey")].toggleInfoPanel();
						break;	
						case "play":
						self.delegate.loadGame({
							game: self.gamesByKey[$(e.target).attr("gameKey")]
						});
						break;
						case "more-info":
						self.delegate.showGameInfo({
							game: self.gamesByKey[$(e.target).attr("gameKey")]
						});						
						break;	
						case "comments":
						self.delegate.showGameInfo({
							game: self.gamesByKey[$(e.target).attr("gameKey")],
							jumpToComments: true
						});						
						break;	
					}
				}
			},
			clickClasses: new Array("mgr-g-item","mgr-g-info-inner"),	
			mousemoveFunc: function(dataObj) {
				var e = dataObj.event;
				var entryEl = $(e.target).parents(".mgr-g-item:first").get()[0];
				if (entryEl) {
					var entryKey = $(entryEl).attr("key");
					var game = self.entriesByKey[entryKey].game;
					var cloneClass = (self.tableMode == "list") ? "" : "mgr-g-item-thumb-drag";
					var aDraggable = new AJKDraggable({
						domDragEl: entryEl,
						clone: true,
						cloneClass: cloneClass,
						type: "game",
						data: game,
						owner: self
					});
								
					aDraggable.initiateDrag({
						event: e
					});
	
				}
			},
			mousemoveClasses: new Array("mgr-g-item") 	
		}).init();
				
		$(self.domCarousel).css({ height: self.containerHeight-95 }); // Set the size of the table carousel in relation to container		

		$(self.domShowListView).click( function() {
			self.controller.changeTableViewsTo({ mode: "list" });
			return false;
		});
		$(this.domShowThumbView).click( function() {
			self.controller.changeTableViewsTo({ mode: "thumb" });
			return false;
		});

		return self;
	},
	changeTableViewTo: function(dataObj) {
		var self = this;
		var mode = dataObj.mode;
		if (self.tableMode != mode) {
			self.tableMode = mode;
			if (self.searchTable) {
				self.searchTable.changeTableViewTo(dataObj);	
			}
		
			var percentFromTop = false;
			if (self.bodyHeight) {	// Bodyheight is only set for tables that have been visible at some point
				percentFromTop = self.windowScrollTop / (self.bodyHeight - AJKHelpers.viewportSize().height);
			}

			switch(mode) {
				case "list":
					$(self.domRootEl).removeClass("mgr-table-thumbnails");
					$(self.domShowThumbView).removeClass("view-selected");
					$(self.domShowListView).addClass("view-selected");
					self.carouselRatio *= self.listToThumbRatio;
				break;	
				case "thumb":
					$(self.domRootEl).addClass("mgr-table-thumbnails");
					$(self.domShowThumbView).addClass("view-selected");
					$(self.domShowListView).removeClass("view-selected");
					self.carouselRatio /= self.listToThumbRatio;
					if (!self.controlsDisabled) {
						$.each(self.visibleBlocks, function() {
							this.loadEntryThumbnails();
						});
					}
				break;	
			}
			var newHeight = 0;
			$.each(self.blocks, function() {
				this.refreshHeight({setAutoHeightIfDisplayed: true});
				newHeight += this.height;
			});

			if (percentFromTop) {
				self.bodyHeight = newHeight * (AJKHelpers.viewportSize().height / (self.containerHeight-self.carouselHeightOffset));
				self.windowScrollTop = percentFromTop * (self.bodyHeight - AJKHelpers.viewportSize().height);
			}

			if (!self.controlsDisabled) {
				self.updateControls();
				self.lastVisibleBlockNum = -1;
				setTimeout( function() {
					self.updateTableDisplay();
				});
			}
		}
	},
	filterBy: function(dataObj) {
		var self = this;
		var searchTerm = dataObj.searchTerm;
		var fader = dataObj.fader;
		self.filter = searchTerm;
		
		var filteredGames = new Array();
		
		$.each(self.games, function() {
			if (this.searchText.indexOf(searchTerm) != -1) {
				filteredGames.push(this);	
			}
		});

		var domSeachTable = $(self.domTablePrototype).clone().get()[0];
		$(self.controller.domTableStage).append(domSeachTable);

		var searchTable = new AJKTableController({
			domRootEl: domSeachTable,
			delegate: self.delegate,
			games: new Array(),
			domShowButton: self.domShowButton,
			domNumGamesDisplayer: "",
			containerHeight: anAJKMainCarouselController.dimensions.height,
			defaultSort: "", // Need to set that to the current sort of this table
			type: self.type,
			title: self.title,
			genre: "search",
			controller: self.controller,
			tableMode: self.tableMode
		}).init();
		
		searchTable.ownerTable = self;						
		searchTable.games = filteredGames;
		$.each(searchTable.games, function() {
			searchTable.gamesByKey[this.id] = this;  	
		});  

		searchTable.createEntriesFromGames({
			games: filteredGames, 
			progressFunc: function(dataObj) {
				var percentage = dataObj.percentage;
				fader.setComplete({
					percentage: parseInt(percentage)
				});
			},
			completionFunc: function() {
				fader.setComplete({
					percentage: 100
				});
				self.controller.showTable({ table: searchTable });
				if (self.searchTable) {
					self.searchTable.kill();
				}
				self.searchTable = searchTable;
				fader.fadeOut();
			}
		});
	},
	clearFilter: function() {
		var self = this;
		self.filter = "";
		if (self.searchTable) {
			self.searchTable.kill();
			self.searchTable = "";
		}
		self.controller.showTable({table: self});
	},
	show: function() {
		var self = this;
		if (!self.entriesReady) {
			$.each(self.games, function() {
				self.gamesByKey[this.id] = this;
			});
			self.sortGames({sortHierarchy: self.sortController.sortHierarchy});
			self.createEntriesFromGames( {
				games: self.games
			});
		}
		//theAJKWindowScrollEvent.registerAsObserver({ observer: self });
		self.showViaCss();

		if (self.tableMode=="thumb") {
			$.each(self.visibleBlocks, function() {
				this.loadEntryThumbnails();
			});
		}
		//self.updateControls();
	},
	showViaCss: function() {
		var self = this;
		$(self.domRootEl).css({display:"block"});
	},
	hide: function() {
		var self = this;	
		$(self.domRootEl).css({display:"none"});
	},
	createNewEntryFromGame: function(dataObj) {
		var self = this;
		var game = dataObj.game;
		var uniqueKey = self.uniqueKeyText+(self.nextUniqueKey++);
		var cancelVariableUpdate = (self.type == "friend-list") ? true : false;
		
		var anEntry = new AJKTableEntry({
			game: game,
			key: uniqueKey,
			controller: self,
			type: self.type,
			cancelVariableUpdate: cancelVariableUpdate
		}).init();
		self.entries.push(anEntry);
		self.entriesByKey[uniqueKey] = anEntry;
		self.entriesByGameKey[game.id] = anEntry; 
		return anEntry;
	},
	kill: function() {
		var self = this;
		$(self.domRootEl).remove();	
	},
	createEntriesFromGames: function(dataObj) {
		var self = this;	
		var games = dataObj.games;
		var totalNumOfEntries = games.length;
		var progressFunc = dataObj.progressFunc;
		var completionFunc = dataObj.completionFunc;

		var entriesInBatchesOf = parseInt(totalNumOfEntries/20);
		entriesInBatchesOf = (entriesInBatchesOf < 1) ? 1 : entriesInBatchesOf;
		entriesInBatchesOf = (entriesInBatchesOf >500) ? 500 : entriesInBatchesOf;


		if (progressFunc) {// If there is a progress func, we create entries in batches, updating the progres func at each batch
			var createEntriesRange = function(dataObj) {
				var start = dataObj.start;
				var end = ((start+entriesInBatchesOf) > totalNumOfEntries) ? totalNumOfEntries : start+entriesInBatchesOf;
				for (var counter=start; counter<end; counter++) {
					var anEntry = self.createNewEntryFromGame({
						game: games[counter]
					});
				self.gamesByKey[games[counter].id] = games[counter];
				}
				if (end < totalNumOfEntries) {
					var thisFunc = arguments.callee;
					setTimeout( function() {
						thisFunc({start: end});
					},1);		
					progressFunc({
						percentage: end/totalNumOfEntries * 50
					});
				}
				else {
					self.addEntriesToTable({
						progressFunc: progressFunc,
						completionFunc: completionFunc
					});
				}
			}
			createEntriesRange({ start: 0 });	
		}
		else { // No prgress fucn so we create all the entries in one go
			$.each(games, function() {
				self.createNewEntryFromGame({ game: this }); 
			});
			self.addEntriesToTable(dataObj);
		}
	},
	sortEntries: function(dataObj) {
		var self = this;	
		self.sortGames(dataObj);		
		self.entries = new Array();
		$.each(self.games, function() {
			self.entries.push(self.entriesByGameKey[this.id]);
		});
	},
	sortGames: function(dataObj) {
		var self = this;
		var sortHierarchy = dataObj.sortHierarchy;
		
		var compareGames = function(dataObj) {
			var sortHierarchy = dataObj.sortHierarchy;
			if (sortHierarchy.length == 0) {
				return 0;	
			}
			var sortField = sortHierarchy[0].field; 
			var game1 = dataObj.game1;
			var game2 = dataObj.game2;
			var thisFunc = arguments.callee;

			if (game1[sortField] == game2[sortField] || (sortHierarchy[0].type == "text" && game1[sortField].toLowerCase() == game2[sortField].toLowerCase())) {
				return thisFunc({ game1: game1, game2: game2, sortHierarchy: sortHierarchy.slice(1) });
			}
			else if (sortHierarchy[0].type == "text") {
				return (game1[sortField].toLowerCase() > game2[sortField].toLowerCase()) ? sortHierarchy[0].order : -sortHierarchy[0].order; 
			}
			else {
				return (game1[sortField] > game2[sortField]) ? sortHierarchy[0].order : -sortHierarchy[0].order; 
			}
		}

		self.games = self.games.sort(function(a,b) {
			return compareGames({
				game1: a,
				game2: b,
				sortHierarchy: sortHierarchy 	
			});
		});
	},
	addEntriesToTable: function(dataObj) {
		var self = this;
		if (self.addingEntries) { 
			return false;	
		}
		self.addingEntries = true;
		
		var progressFunc = (dataObj && dataObj.progressFunc) ? dataObj.progressFunc : "";
		var completionFunc = (dataObj && dataObj.completionFunc) ? dataObj.completionFunc : "";
		var totalNumberOfBlocks = Math.ceil(self.entries.length/self.entriesPerBlock);
		var blocksInBatchesOf = 20;
		var numOfEntries = self.entries.length;

		if (progressFunc) { // If there is a progress func, we create entries in batches, updating the progres func at each batch
			var createBlockRange = function(dataObj) {
				var start = dataObj.start;
				var end = ((start+blocksInBatchesOf) > totalNumberOfBlocks) ? totalNumberOfBlocks : start+blocksInBatchesOf;
				
				for (var counter = start; counter < end; counter++) {
					var aBlock = new AJKTableBlock({ controller: self, index: self.blocks.length }).init();
					self.blocks.push(aBlock);
				
					var forStart = counter * self.entriesPerBlock;
					var forEnd = ((forStart + self.entriesPerBlock) > numOfEntries) ? numOfEntries : forStart + self.entriesPerBlock;
					for (var counter2 = forStart; counter2 < forEnd; counter2++) {
						aBlock.addEntry({ entry: self.entries[counter2] });
					}
				}
				var thisFunc = arguments.callee;
				if (end < totalNumberOfBlocks) {
					setTimeout( function() {
						thisFunc({
							start: end
						});
					},1);
					if (progressFunc) {
						progressFunc({ percentage: 50 + end/totalNumberOfBlocks * 50 });	
					}
				}
				else {
					self.numBlocks = self.blocks.length;
					$.each(self.blocks, function() {
						this.refreshHeight();
						$(self.domStage).append(this.domRootEl);	
					});
					//setTimeout( function() {
						self.updateTableDisplay();
					//},1);
					self.entriesReady = true;
					self.addingEntries = false;
					if (completionFunc) {
						completionFunc();	
					}
				}
			}
			createBlockRange({ start: 0 });
		}
		else { // No prgress fucn so we add all the entries in one go
			var counter = 0;
			var currentBlock = "";
			var blockCounter = 0;
			$.each(self.entries, function() {
				if (counter++ % self.entriesPerBlock == 0) {
					// create a new block
					currentBlock = new AJKTableBlock({ controller: self, index: blockCounter++ }).init();
					self.blocks.push(currentBlock);
				}
				currentBlock.addEntry({ entry: this });			
			});
			
			self.numBlocks = self.blocks.length;
			$.each(self.blocks, function() {
				this.refreshHeight();
				$(self.domStage).append(this.domRootEl);	
			});
			//setTimeout( function() {
				self.updateTableDisplay();
			//},1);
			self.entriesReady = true;
			self.addingEntries = false;
			if (completionFunc) {
				completionFunc();	
			}
		}
	},
	refreshEntries: function() { // This should be undertaken after a sort change
	var self = this;
		$.each(self.blocks, function() {
			//delete(this);
		});
		$(self.domStage).empty();
		self.blocks = new Array();
		self.visibleBlocks = new Array();
		self.lastVisibleBlockNum = -1;
		self.addEntriesToTable();
		if (!self.controlsDisabled) {
			self.updateControls();
		}
	},
	displayBlock: function(dataObj) {
		var self = this;
		var block = dataObj.block;
		block.displayBlockContent();
		self.visibleBlocks.push(block);	
	},
	updateSize: function(dataObj) { // Called by MainCarousel on Resize
		var self = this;
		self.containerHeight = (dataObj && dataObj.newContainerHeight) ? dataObj.newContainerHeight : self.containerHeight;
		if (!self.controlsDisabled) {
			$(self.domCarousel).css({ height: self.containerHeight-self.carouselHeightOffset });			
			self.updateControls();
		}
	},
	updateControls: function() {
		var self = this;
		self.carouselRatio =  $(self.domStage).height()/$(self.domCarousel).height();
		var viewportHeight = AJKHelpers.viewportSize().height;
		self.bodyHeight = (self.carouselRatio > 1) ? self.carouselRatio * viewportHeight : 1.1 * viewportHeight;
		self.stageHeight = $(self.domStage).height();
		self.windowScrollTop = (self.carouselRatio > 1) ? self.windowScrollTop : 0;
		$(self.domBody).css({height: self.bodyHeight});
		window.scrollTo(0,self.windowScrollTop);
	},
	windowDidScroll: function(dataObj) {
		var self= this;
		var windowTopOffset = dataObj.windowTopOffset;
		self.windowScrollTop = $(window).scrollTop();
		if (!self.controlsDisabled) {
			self.updateTableDisplay();
		}
	},
	disableControls: function() {
		var self = this;
		self.controlsDisabled = true;
		theAJKWindowScrollEvent.removeAsObserver({ observer: self });
	},
	enableControls: function() {
		var self = this;
		self.controlsDisabled = false;
		self.updateSize();
		//self.updateControls();
		theAJKWindowScrollEvent.registerAsObserver({ observer: self });
	},
	updateTableDisplay: function(dataObj) {
		var self = this;
		if (self.blocks.length == 0) { // Empty table so simply return;
			return false;
		}
		
		var topOffset = self.windowScrollTop / self.bodyHeight * self.stageHeight;
		
		topOffset = (self.carouselRatio > 1) ? topOffset : 0;
		
		var combinedHeight = 0;
		var visibleTableNum = 0;
		for (var counter = 0; counter < self.numBlocks; counter++) {
			combinedHeight += self.blocks[counter].height;
			if (combinedHeight >= topOffset) {
				visibleTableNum = counter;
				break;	
			}
		}
		var blocksToHide = new Array();
		if (visibleTableNum != self.lastVisibleBlockNum) {
			self.lastVisibleBlockNum = visibleTableNum; 
			// hide currently visible blocks
			
			blocksToHide = AJKHelpers.cloneArray({anArray: self.visibleBlocks});			
			self.visibleBlocks = new Array();
			self.displayBlock({ block: self.blocks[visibleTableNum] });
			if (++visibleTableNum < self.numBlocks && self.blocks[visibleTableNum]) {
				self.displayBlock({ block: self.blocks[visibleTableNum] }); // Display the next block too just in case
			}
			// Remove any blocks that have just been made visible from the blocks to hide array
			$.each(self.visibleBlocks, function() {
				blocksToHide = AJKHelpers.removeItemFromArray({ item: this, anArray: blocksToHide });
			});
		}
		$(self.domStage).css({top: -topOffset});
		
		setTimeout( function() { // Timeout needed to prevent flashing in Firefox. Might fuck up ie
			$.each(blocksToHide, function() {
				this.removeBlockContent();
			});
		});
	},
	deactivateAddGameToLibraryForGame: function(dataObj) {
		var self = this;
		var game = dataObj.game;
		if (self.entriesByGameKey[game.id]) {
			self.entriesByGameKey[game.id].deactivateAddGameToLibrary();
		}	
	},
	addGameToUserLibrary: function(dataObj) {
		var self = this;
		if (self.currentlyAddingGames) {
			return;	
		}
		self.currentlyAddingGames = true;
		// This func should only get called by a shop table
		var entry = dataObj.entry;
		var game = entry.game;
		anAJKLibraryController.addGameToUserLibrary({ game: game });
		
		if (self.type == "friend-list" || self.type == "shop") {
			// entry.deactivateAddGameToLibrary();
			anAJKLibraryController.deactivateAddGameToLibraryForAllReleventTables({
				game: game
			});
			self.currentlyAddingGames = false;
		}
		else { // Shop
			if (entry.infoPanel && entry.infoPanel.isOpen) {
				entry.infoPanel.hidePanel({ 
					fade: true, 
					callback: function() {
						entry.containerBlock.itemHasClosed();
					} 
				});
			}
			$(entry.domRootEl)
			.click( function() {
				return false;	
			})
			.animate({ opacity: 0 },300, function() {
				$(this).animate({ height: 0, marginBottom: 0 }, 300, function() {
					$(this).remove();
					self.removeGameFromTable({ game: game });
					self.updateControls();
					self.currentlyAddingGames = false;
				})
			});
		}
						
	},
	addEntryFromNewGame: function(dataObj) {
		var self = this;
		var game = dataObj.game;
		var limit = dataObj.limit;
		var limitSort = dataObj.limitSort;
		var isGameAlreadyInTable = self.gamesByKey[game.id];
		if (!isGameAlreadyInTable) {
			self.games.push(game);
			self.gamesByKey[game.id] = game;
		}
		// This limits the number of entries on the table, dropping the ones at the bottom
		if (limit && self.games.length > limit) {
			// Firstly sort games by provided hierachy
			var tempSortHierarchy = new Array();
			tempSortHierarchy.push(limitSort);
			self.sortGames({ sortHierarchy: tempSortHierarchy });
			var counter = self.games.length;

			// Remove all surplus entries and games
			while (counter > limit) {
				self.removeGameFromTable({ game: self.games[--counter] });
			}
		}

		if (self.entriesReady) {
			if (!isGameAlreadyInTable) {
				self.createNewEntryFromGame({
					game: game
				});
			}
			self.sortEntries({sortHierarchy: self.sortController.sortHierarchy});
			self.refreshEntries();
		}
		else {
			self.sortGames({sortHierarchy: self.sortController.sortHierarchy});
		}

		if (self.domNumGamesDisplayer) {
			$(self.domNumGamesDisplayer).text(self.games.length);
		}
	},
	removeGameFromTable: function(dataObj) {
		var self = this;
		var game = dataObj.game;
		if (!self.gamesByKey[game.id]) {
			return;	
		}
		self.games = AJKHelpers.removeItemFromArray({ item: game, anArray: self.games });
		self.gamesByKey[game.id] = "";
		if (self.entriesReady) {
			var thisEntry =  self.entriesByGameKey[game.id];
			self.entries = AJKHelpers.removeItemFromArray({ item: thisEntry, anArray: self.entries });
			self.entriesByGameKey[game.id] = "";
			if (thisEntry) {
				thisEntry.kill();
			}
		}
		if (self.domNumGamesDisplayer) {
			$(self.domNumGamesDisplayer).text(self.games.length);
		}
		if (self.searchTable) {
			self.searchTable.removeGameFromTable(dataObj);	
		}
	}
}

var AJKTableEntry = function(dataObj) {
	this.game = dataObj.game;
	this.key = dataObj.key;
	this.domRootEl="";
	this.containerBlock = "";
	this.infoPanel = "";
	this.controller = dataObj.controller;
	this.type = dataObj.type;
	this.cancelVariableUpdate = dataObj.cancelVariableUpdate;
	this.searchVisible = true;
	this.imageLoaded = false;
	self.domThumbnail = "";
	return this;
}

AJKTableEntry.prototype = {
	init: function() {
		var self = this;	
		self.domRootEl = self.createHTML();
		self.domThumbnail = $(self.domRootEl).find(".image-holder:eq(0) img").get()[0];

		if (!self.cancelVariableUpdate) {
			self.game.registerAsObserver({
				observer: self
			});
		}
		return self;
	},
	loadThumbnail: function() {
		var self = this;
		if (!self.imageLoaded) {
			$(self.domThumbnail).attr("src",self.game.thumbnailUrl);
			self.imageLoaded =  true;	
		}
	},
	searchShow: function() {
		var self = this;
		if (!self.searchVisible) {
			self.searchVisible = true;
			self.containerBlock.entryWasSearchShown({
				entryInfoPanelOpen: (self.infoPanel && self.infoPanel.isOpen) ? true : false	
			});	
			$(self.domRootEl).css({ display: "block" });
			if (self.infoPanel && self.infoPanel.isOpen) {
				$(self.infoPanel.domRootEl).css({display:"block"});	
			}
		}	
	},
	searchHide: function() {
		var self = this;	
		if (self.searchVisible) {
			self.searchVisible = false;
			self.containerBlock.entryWasSearchHidden({
				entryInfoPanelOpen: (self.infoPanel && self.infoPanel.isOpen) ? true : false
			});
			$(self.domRootEl).css({ display: "none" });
			if (self.infoPanel && self.infoPanel.isOpen) {
				$(self.infoPanel.domRootEl).css({display:"none"});	
			}
		}
	},
	deactivateAddGameToLibrary: function() {
		var self = this;
		if (self.domRootEl) {
			setTimeout(function() {
				$(self.domRootEl).find(".mgr-gi-add p").html('<span class="mgr-g-field-title">Status:</span>In library');
			},10);	
		}	
		
	},
	gameValueChanged: function(dataObj) {
		var self = this;
		var valueName = dataObj.valueName;	
		var valueValue = dataObj.valueValue;
		switch (valueName) {
			case "played":
				var timesString = (valueValue == 1) ? "time": "times";
				$(self.domRootEl).find(".mgr-gi-played p").html('<span class="mgr-g-field-title">Played:</span>'+valueValue+' '+timesString);
			break;
			case "numComments":
				if (self.infoPanel) {
					self.infoPanel.updateNumComments({
						commentNum: self.game.numComments
					});				
				}
			break;
		}	
		
	},
	createHTML: function() {
		var self = this;
		if (self.type == "shop" || self.game.isFriendGame) {
			var insertHTML = '<div key="'+self.key+'" dragType="game" class="mgr-g-item mgr-g-item-'+self.game.cssClass+'">';	
			insertHTML+='<div class="mgr-g-item-inner">';
			insertHTML+='<div class="image-holder"><div><img src="assets/ui/empty-image.gif" alt="" /></div><span></span></div>';
			insertHTML+='<ul>';
			insertHTML+='<li class="mgr-gi-first mgr-gi-name mgr-gi-38">';
			insertHTML+='<p>'+AJKHelpers.clipToMaxCharWords({aString: self.game.name, maxChars: 40 })+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-20">';
			insertHTML+='<p><span class="mgr-g-field-title">Author:</span>'+self.game.author+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-10">';
			insertHTML+='<p><span class="mgr-g-field-title">Genre:</span>'+self.game.categories.split(",")[0]+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-10 mgr-gi-rating">';
			insertHTML+='<p><span class="mgr-g-field-title">Rating:</span>'+self.game.recommendation+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-last mgr-gi-10 mgr-gi-add">';
			if (anAJKLibraryController.gamesByKey[self.game.id]) {
				insertHTML+='<p><span class="mgr-g-field-title">Status:</span>In library</p>';
			}
			else {
				insertHTML+='<p><span class="mgr-g-field-title">Status:</span>Add to library<a href="#" class="mgr-gi-select mgr-gi-select-add">x</a></p>';
			}		
			insertHTML+='</li>';
			insertHTML+='</ul>';
			insertHTML+='<div class="mgr-gi-thumbnail-options">';
			insertHTML+='<a option="more-info" class="mgr-gi-t-option" href="#">More info</a><a class="mgr-gi-t-option last" href="#" option="play">Play</a>';
			insertHTML+='<div class="clear"></div>';
			insertHTML+='</div>';
			insertHTML+='</div>';
		}
		else {
			var insertHTML = '<div key="'+self.key+'" class="mgr-g-item mgr-g-item-'+self.game.cssClass+'">';	
			insertHTML+='<div class="mgr-g-item-inner">';

			insertHTML+='<div class="image-holder"><div><img src="assets/ui/empty-image.gif" alt="" /></div><span></span></div>';
			insertHTML+='<ul>';
			insertHTML+='<li class="mgr-gi-first mgr-gi-name mgr-gi-38">';
			insertHTML+='<p>'+AJKHelpers.clipToMaxCharWords({aString: self.game.name, maxChars: 40 })+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-20">';
			insertHTML+='<p><span class="mgr-g-field-title">Author:</span>'+self.game.author+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-10">';
			insertHTML+='<p><span class="mgr-g-field-title">Genre:</span>'+self.game.categories.split(",")[0]+'</p>';
			insertHTML+='</li>';
			insertHTML+='<li class="mgr-gi-10 mgr-gi-played">';
			var timesString = (self.game.played == 1) ? "time": "times";
			insertHTML+='<p><span class="mgr-g-field-title">Played:</span>'+self.game.played+' '+timesString+'</p>';
			insertHTML+='<li class="mgr-gi-last mgr-gi-10 mgr-gi-rating">';
			insertHTML+='<p><span class="mgr-g-field-title">Rating:</span>'+self.game.recommendation+'/5</p>';
			insertHTML+='</li>';
			insertHTML+='</ul>';
			insertHTML+='<div class="mgr-gi-thumbnail-options">';
			insertHTML+='<a option="more-info" class="mgr-gi-t-option" href="#">More info</a><a class="mgr-gi-t-option last" href="#" option="play">Play</a>';
			insertHTML+='</div>';
			insertHTML+='<div class="clear"></div>';
			insertHTML+='</div>';
			insertHTML+='</div>';
		}
		
		return $(insertHTML).get()[0];
	},
	getDomRootEl: function() {
		var self = this;
		return self.domRootEl;	
	},
	setContainerBlock: function(dataObj) {
		var self = this;
		self.containerBlock = dataObj.block;
	}, 
	toggleInfoPanel: function() {
		var self = this;
		if (!self.infoPanel) {
			// Panel has not been initialised so let's create it
			self.infoPanel = new AJKTableEntryInfoPanel({
				game: self.game,
				entry: self,
				domBeforeEl: self.domRootEl,
				height: self.controller.infoPanelHeight
			}).init();
		}
		if (self.infoPanel.isOpen) {
			self.containerBlock.itemHasClosed();
			self.infoPanel.hidePanel({
				callback: function() {
					$(self.domRootEl).removeClass("mgr-g-item-open");
				}
			});
		}
		else {
			self.containerBlock.itemHasOpened();
			$(self.domRootEl).addClass("mgr-g-item-open");
			self.infoPanel.showPanel();
		}
	},
	forceOpenInfoPanel: function() {
		var self = this; // Should only called on entries where an infopanel has already been created
		self.infoPanel.forceOpen();	
	},
	kill: function() {
		var self = this;
		self.containerBlock.removeEntry({ entry: self });
		$(self.domRootEl).remove();
		self.domRootEl = "";
		if (self.infoPanel) {
			self.infoPanel.kill();
		}		
	}
}

var AJKTableEntryInfoPanel = function(dataObj) {
	this.game = dataObj.game;
	this.entry = dataObj.entry;
	this.domBeforeEl = dataObj.domBeforeEl;
	this.height = dataObj.height;
	this.domRootEl = "";
	this.domObscurer = "";
	this.isOpen = false;
	this.panelContentLoaded = false;
	this.type = dataObj.type;
	return this;
}

AJKTableEntryInfoPanel.prototype = {
	init: function() {
		var self = this;
		self.domRootEl = $('<div class="mgr-g-info-panel mgr-g-info-panel-'+self.game.cssClass+'"></div>').get()[0];
		self.domObscurer = $('<div class="mgr-g-info-obscurer"></div>').get()[0];
		$(self.domRootEl).append(self.domObscurer);
		return self;
	},
	kill: function() {
		var self = this;
		if (self.isOpen) {
			$(self.domRootEl).remove();
			self.domRootEl = "";
		}
	},
	generateHTML: function() {
		var self = this;
		var insertHTML = '<div class="mgr-g-info-shadow">';
		insertHTML += '<div class="mgr-g-info-inner">';
		insertHTML += '<div class="mgr-g-info-image-block">';
		insertHTML += '<h4>Rating</h4>';
		insertHTML += '<div class="mgr-g-info-details">';
		insertHTML += '<h4>'+self.game.recommendation+'</h4>';
		insertHTML += '</div>';
		insertHTML += '<div class="img-holder">';
		insertHTML += '<img src="'+self.game.extraData.thumbnail_url+'" />';
		insertHTML += '<div class="img-mask"></div>';
		insertHTML += '</div>';
		insertHTML += '</div>';
		insertHTML += '<div class="mgr-g-info-content">';
		insertHTML += '<div class="mgr-g-info-content-inner">';
		insertHTML += '<h4>Brief description</h4>';
		insertHTML += '<p>'+AJKHelpers.clipToMaxCharWords({aString: self.game.extraData.description, maxChars: 200 })+'</p>';
		insertHTML += '<h4>Instructions</h4>';
		insertHTML += '<p>'+AJKHelpers.clipToMaxCharWords({aString: self.game.extraData.instructions, maxChars: 200 })+'</p>';
		insertHTML += '</div>';
		insertHTML += '<a class="mgr-g-info-button rt-button-1" gameKey="'+self.game.id+'" option="play" href="#">Play now</a>';
		insertHTML += '<a class="mgr-g-info-button rt-button-3" entryKey="'+self.entry.key+'" option="close" href="#">Close</a>';
		insertHTML += '<a class="mgr-g-info-button rt-button-3" gameKey="'+self.game.id+'" option="more-info" href="#">More info</a>';
		insertHTML += '<a class="gi-comment-button mgr-g-info-button rt-button-3 rt-button-3-medium" gameKey="'+self.game.id+'" option="comments" href="#">Comments ('+self.game.numComments+')</a>';
		insertHTML += '</div>';												
		insertHTML += '</div>';
		insertHTML += '</div>';
		return insertHTML;
	},
	updateNumComments: function(dataObj) {
		var self = this;
		var commentNum = dataObj.commentNum;
		if (self.domRootEl) {
			$(self.domRootEl).find(".gi-comment-button").text('Comments ('+commentNum+')');			
		}
			
	},
	showPanel: function() {
		var self = this;
		self.isOpen = true;
		if (!self.panelContentLoaded) {
			panelContentLoaded = true;
			self.game.getDataForInfoPanel({
				callback: function() {
					$(self.domRootEl).prepend(self.generateHTML());
					$(self.domObscurer).animate({opacity: 0}, 500, function() {
						$(this).remove();
					});
				}
			});
		}
		$(self.domRootEl)
		.insertAfter(self.domBeforeEl)
		.stop()
		.animate({height: self.height }, 500, function() { 
			self.entry.controller.updateControls();			
		});
		
	},
	hidePanel: function(dataObj) {
		var self = this;
		self.isOpen = false;
		var callback = dataObj.callback;
		var fade = dataObj.fade;
		var changes = (fade) ? { height: 0, opacity: 0 } : { height: 0 };
		$(self.domRootEl)
		.stop()
		.animate( changes, 500, function() {
			$(self.domRootEl).remove();
			self.entry.controller.updateControls();			
			if (callback) {
				callback();	
			}
		})
	},
	forceOpen: function() { // Should only be called on panels that habe already created their html
		var self = this;
		self.isOpen = true;
		$(self.domRootEl)
		.insertAfter(self.domBeforeEl)
		.css({height: self.height }); 
	}
}


var AJKTableBlock = function(dataObj) {
	this.domRootEl = "";
	this.domStage = "";
	this.height = "";
	this.entries = new Array();
	this.numEntries = 0;
	this.entryHeight = "";
	this.isContentDisplayed = false;
	this.controller = dataObj.controller;
	this.numOpenItems = 0;
	this.searchHiddenItems = 0;
	this.numOpenItemsSearchHidden = 0;
	this.index = dataObj.index;
	this.allEntriesThumbsLoaded = false;
	return this;
}

AJKTableBlock.prototype = {
	init: function() {
		var self = this;
		self.domRootEl = $('<div class="mgr-table-block"><div class="clear"></div></div>').get()[0];
		self.domStage = $('<div></div>').get()[0];
		return this;
	},
	addEntry: function(dataObj) {
		var self = this;
		var entry = dataObj.entry;
		entry.setContainerBlock({ block: self });
		self.entries.push(entry);
		$(self.domStage).append(entry.getDomRootEl());
		self.numEntries++;
		if (entry.infoPanel && entry.infoPanel.isOpen) {
			entry.forceOpenInfoPanel();
			self.numOpenItems++;
		}
	},
	refreshHeight: function(dataObj) { // Should be called after a block of entries have been added
		var self = this;
		var setAutoHeightIfDisplayed = (dataObj) ? dataObj.setAutoHeightIfDisplayed : "";
		if (self.controller.tableMode == "list") {
			self.height = (self.controller.entriesHeight * (self.numEntries - self.searchHiddenItems) + (self.numOpenItems - self.numOpenItemsSearchHidden) * self.controller.infoPanelHeight);
		}
		else { // thumb view
			var numVisibleItems = self.numEntries - self.searchHiddenItems;
			var rollOver = (numVisibleItems % 5) ? 1 : 0;
			self.height = (self.controller.thumbHeight * (parseInt(numVisibleItems/5)+rollOver));
		}
		if (setAutoHeightIfDisplayed && self.isContentDisplayed) {	
			$(self.domRootEl).css({ height: "auto" });
		}
		else {
			$(self.domRootEl).css({ height: self.height });
		}
	},
	displayBlockContent: function() {
		var self= this;
		if (!self.isContentDisplayed) {
			$(self.domRootEl).prepend(self.domStage);
			$(self.domRootEl).css({height:"auto"});
			self.isContentDisplayed = true;
			if (self.controller.tableMode == "thumb") {
				self.loadEntryThumbnails();
			}
		}
	},
	loadEntryThumbnails: function() {
		var self = this;
		if (!self.allEntriesThumbsLoaded) {
			var counter = 0;
			var entriesCounter = 0;
			var loadArray = new Array();
			while (counter < 5 && self.entries[entriesCounter]) {
				if (!self.entries[entriesCounter].imageLoaded) {
					loadArray.push(self.entries[entriesCounter]);
					counter++;
				}
				entriesCounter++;	
			}
			if (counter < 5) {
				self.allEntriesThumbsLoaded=true;	
			}
			setTimeout( function() {
				if (self.isContentDisplayed) {
					$.each(loadArray, function() {
						this.loadThumbnail();	
					});
					if (!self.allEntriesThumbsLoaded) {
						self.loadEntryThumbnails();
					}
				}
			},200);
		}
	},
	removeBlockContent: function() {
		var self = this;
		if (self.isContentDisplayed) {
			self.refreshHeight();
			$(self.domStage).remove();
			self.isContentDisplayed = false;
		}
	},
	itemHasClosed: function() {
		var self = this;
		self.numOpenItems--;
		self.height -= self.controller.infoPanelHeight;
	},
	itemHasOpened: function() {
		var self = this;
		self.numOpenItems++;
		self.height += self.controller.infoPanelHeight;
	},
	entryWasSearchShown: function(dataObj) {
		var self = this;
		var entryInfoPanelOpen = dataObj.entryInfoPanelOpen;
		self.searchHiddenItems--;
		self.height += self.controller.entriesHeight;
		if (entryInfoPanelOpen) {
			self.numOpenItemsSearchHidden--;
		}
	},
	entryWasSearchHidden: function(dataObj) {
		var self = this;
		var entryInfoPanelOpen = dataObj.entryInfoPanelOpen;
		self.searchHiddenItems++;
		self.height -= self.controller.entriesHeight;
		if (entryInfoPanelOpen) {
			self.numOpenItemsSearchHidden++;
		}
	},
	removeEntry: function(dataObj) {
		var self = this;
		var entry = dataObj.entry;	
		self.entries = AJKHelpers.removeItemFromArray({
			item: entry,
			anArray: self.entries
		});
		self.numEntries--;
		if (entry.infoPanel && entry.infoPanel.isOpen) {
			self.itemHasClosed();
		}
	}
}


var AJKTableSortController = function(dataObj) {
	this.domSortEls = dataObj.domSortEls;
	this.sortFunc = dataObj.sortFunc;
	this.sortHierarchy = new Array();
	this.numSortOptions = this.domSortEls.length;
	this.defaultSort = dataObj.defaultSort;
	return this;
}

AJKTableSortController.prototype = {
	init: function() {
		var self = this;
		if (self.defaultSort) {
			self.sortHierarchy.push({
				field: self.defaultSort.field,
				order: (self.defaultSort.order == "ASC") ? 1 : -1,
				domEl: "",
				type: "date"
			});	
		}
		$(self.domSortEls).each( function() {
			var field = $(this).attr("sortField");
			var order = $(this).attr("sortOrder");
			var type = $(this).attr("sortType");
			
			var sortObj = {
				field: field,
				order: (order == "ASC") ? 1 : -1,
				domEl: this,
				type: type
			}
			self.sortHierarchy.push(sortObj)
			$(this).click( function() {
				if (self.sortHierarchy[0] == sortObj) { // This sort is already he primary sort so we reverse direction
					sortObj.order = -sortObj.order;
					if (sortObj.order == -1) {
						$(this).addClass("rt-lh-sort-desc");	
					}
					else {
						$(this).removeClass("rt-lh-sort-desc");	
					}
				}
				else {
					$(self.sortHierarchy[0].domEl).removeClass("rt-lh-sort-selected");
					$(sortObj.domEl).addClass("rt-lh-sort-selected");
					var newHierarchy = new Array();
					
					newHierarchy.push(sortObj);
					$.each(self.sortHierarchy, function() {
						if (this != sortObj) {
						 newHierarchy.push(this);	
						}
					});
					self.sortHierarchy = newHierarchy;
				}
				self.sortFunc({
					sortHierarchy: self.sortHierarchy
				});
				return false;
			});
		});
		
		return self;
	}	
}
		


var AJKMainCarouselController = function(dataObj) {
	this.domRootEl = dataObj.domRootEl;
	this.domCarouselStage = $(this.domRootEl).find(".rt-main-carousel-stage").get()[0];
	this.domFooterCarousel = $("#rt-footer-carousel").get()[0];
	this.domFooterCarouselStage = $(this.domFooterCarousel).find(".rt-footer-carousel-stage").get()[0];
	this.footerCarouselHeight = $(this.domFooterCarousel).height();
	this.domItems = $(this.domRootEl).find(".rt-content").get();
	this.domFooterItems = $(this.domFooterCarouselStage).find(".rt-footer-carousel-item").get();
	this.numItems = this.domItems.length;
	this.currentItem = 0;
	this.images = new Array();
	this.dimensions = {
		width: $(this.domRootEl).width(),
		height: 0	
	}
	this.panelVisibility = new Array();
	this.itemControllers = new Array();
	return this;
}

AJKMainCarouselController.prototype = {
	init: function() {
		var self = this;
		
		var counter = 0;
		$.each(self.domItems, function() {
			self.panelVisibility[counter++] = 1;	
		});
		 
		self.windowDidResize({newSize: AJKHelpers.viewportSize()});		
		$(self.domCarouselStage).css({width: ((self.numItems+1) * self.dimensions.width) });
		$(self.domItems).each( function() {
			$(this).css({ width: self.dimensions.width });
			
			if ($(this).find(".rt-content-main-image").length > 0) {
				// This is an image item	
				self.images.push( new AJKMainCarouselImage({
					domRootEl: this,
					displaySize: self.dimensions,
					carouselObj: self
				}).init());
			}
			
		});
		
		theAJKWindowResizeEvent.registerAsObserver({observer:self});
		
		// When all buttons of this class are clicked the main carousel scrolls to the pos in the href tag
		$(".rt-main-carousel-button").click( function() {
			var newPos = parseInt($(this).attr("href"));
			self.animateTo({
				pos: newPos
			});
			return false; 
		});
		return self;
	},
	hideItem: function(dataObj) {
		var self = this;
		var position = dataObj.position;
		$(self.domItems[position]).css({display:"none"});
		$(self.domFooterItems[position]).css({display:"none"});
		self.panelVisibility[position] = 0;
	},
	showItem: function(dataObj) {
		var self = this;
		var position = dataObj.position;
		$(self.domItems[position]).css({display:"block"});
		$(self.domFooterItems[position]).css({display:"block"});
		self.panelVisibility[position] = 1;
	},
	getLeftVisiblesForPosition: function(dataObj) {
		var self = this;
		var position = dataObj.position;
		var adjustedPos = 0;
		for (var counter = 0; counter < position; counter++) {
			adjustedPos += self.panelVisibility[counter];
		}
		return adjustedPos;
	},
	animateTo: function(dataObj) {
		var self = this;
		var toPos = dataObj.pos;
		var prevPos = self.currentItem;
		var callback = dataObj.callback;
		var instantly = dataObj.instantly;
		var duration = (instantly) ? 0 : 500;
		if (prevPos == toPos) {
			if (callback) {
				callback();	
			}
			return;
		}

		if (toPos == 0) {
			window.location.hash="/home/";			
		}
		if (!self.panelVisibility[toPos]) {
			// The panel is not visible, so we need to display it
			if (self.currentItem > toPos) {
				self.showItem({ position: toPos });
				var adjustedLeftPos = self.getLeftVisiblesForPosition({ position: self.currentItem });
				$(self.domCarouselStage).css({ left: -(adjustedLeftPos * self.dimensions.width) });
				$(self.domFooterCarouselStage).css({top: -(adjustedLeftPos * self.footerCarouselHeight) });
			}
			else {
				self.showItem({ position: toPos });
			}	
		}
		self.informControllersOfAnimateStart({ prevPos: prevPos, toPos: toPos }); 

		self.currentItem = toPos;
		
		var adjustedPos = self.getLeftVisiblesForPosition({position:self.currentItem });

		$(self.domCarouselStage).animate({ left: -(adjustedPos * self.dimensions.width) }, duration, function() {
			if (callback) {
				callback();	
			}
			setTimeout( function() { // Needed for ie8/ie7 dicks
				self.informControllersOfAnimateEnd({ prevPos: prevPos, toPos: toPos }); 
				self.hideItem({ position: prevPos });
				if (prevPos < toPos) {
					$(self.domCarouselStage).css({ left: -((adjustedPos-1) * self.dimensions.width) });
					$(self.domFooterCarouselStage).css({ top: -((adjustedPos-1) * self.footerCarouselHeight) });
				}
			});
		});
		
		$(self.domFooterCarouselStage).animate({top: -(adjustedPos * self.footerCarouselHeight) }, duration, function() {
			if (prevPos < toPos) {
			}
		});
	},
	windowDidResize: function(dataObj) {
		var self = this;
		var newSize = dataObj.newSize;
		self.dimensions = {
			width: $(self.domRootEl).width(),
			height: newSize.height-120	
		}
		$(self.domRootEl).css({ height: self.dimensions.height });

		var adjustedPos = self.getLeftVisiblesForPosition({position:self.currentItem });

		$(self.domCarouselStage).css({
			left: -(adjustedPos * self.dimensions.width),
			width: (self.numItems+1) * self.dimensions.width,
			height: self.dimensions.height 
		});
		$(self.domItems).each( function() {
			$(this).css({ width: self.dimensions.width, height: self.dimensions.height });
		});
		
		$.each(self.images, function() {
			this.resizeTo({
				size: self.dimensions
			});
		});
		
		self.informControllersOfResize();
	},
	registerAsControllerOfItem: function(dataObj) {
		var self = this;
		var controller = dataObj.controller;
		var position = dataObj.position;
		self.itemControllers[position] = controller;
	},
	informControllersOfResize: function(dataObj) {
		var self = this;
		$.each(self.itemControllers, function() {
			if (this.carouselDidResize) {
				this.carouselDidResize({
					newSize: self.dimensions
				});
			}
		});
	},
	informControllersOfAnimateStart: function(dataObj) {
		var self = this;
		var prevPos = dataObj.prevPos;
		var toPos = dataObj.toPos;
		if (self.itemControllers[prevPos] && self.itemControllers[prevPos].itemWillScrollOutOfView) {
			self.itemControllers[prevPos].itemWillScrollOutOfView();
		}	
		if (self.itemControllers[toPos] && self.itemControllers[toPos].itemWillScrollIntoView) {
			self.itemControllers[toPos].itemWillScrollIntoView();
		}	
	},
	informControllersOfAnimateEnd: function(dataObj) {
		var self = this;
		var prevPos = dataObj.prevPos;
		var toPos = dataObj.toPos;
		if (self.itemControllers[prevPos] && self.itemControllers[prevPos].itemDidScrollOutOfView) {
			self.itemControllers[prevPos].itemDidScrollOutOfView();
		}	
		if (self.itemControllers[toPos] && self.itemControllers[toPos].itemDidScrollIntoView) {
			self.itemControllers[toPos].itemDidScrollIntoView();
		}	
	} 
}
var AJKMainCarouselImage = function(dataObj) {
	this.displaySize = dataObj.displaySize;
	this.domRootEl = dataObj.domRootEl;
	this.domHolder = $(this.domRootEl).find(".rt-content-main-image-holder").get()[0];
	this.domImage = $(this.domHolder).find(".rt-content-main-image").get()[0];
	this.size = {
		width: 0,
		height: 0	
	}
	this.adjustedDimensions = {
		width: 0,
		height: 0,
		xOffset: 0,
		yOffset: 0
	}
	this.loaded = false;
	this.carouselObj = dataObj.carouselObj;
	this.imageLoadedFunc;
	return this;	
}

AJKMainCarouselImage.prototype = {
	init: function() {
		var self = this;
		
		self.carouselObj.registerAsControllerOfItem({
			controller: self,
			position: 0
		});

		$(self.domHolder).css({
			width: self.displaySize.width, 
			height: self.displaySize.height
		});

		self.imageLoadedFunc = function() {
			self.size = {
				width: $(this).width(),
				height: $(this).height()
			}
			
			self.fitInBoxOfSize({ 
				boxSize: { 
					width: self.displaySize.width, 
					height: self.displaySize.height 
				}
			});
			$(this).animate({ opacity: 1}, 1000, function() {
			});
			self.loaded = true;
		}
		
		if (this.domImage.complete) {
			setTimeout( function() { // timeout needed for ie
				self.imageLoadedFunc.apply(self.domImage);
			},1);
		}
		else {
			$(this.domImage).load( function() {
				self.imageLoadedFunc.apply(this);
			});
		}
		return self;
	},
	itemWillScrollIntoView: function() {
		var self = this;
		self.imageLoadedFunc.apply(self.domImage);
	},
	fitInBoxOfSize: function(dataObj) {
		var boxSize = dataObj.boxSize;
		if (this.size.width/this.size.height > boxSize.width/boxSize.height) {
			this.adjustedDimensions.height = boxSize.height;
			this.adjustedDimensions.width = Math.round(this.adjustedDimensions.height/this.size.height * this.size.width);
			this.adjustedDimensions.xOffset = -Math.round((this.adjustedDimensions.width - boxSize.width)/2);
			this.adjustedDimensions.yOffset = 0;
		} 
		else {
			this.adjustedDimensions.width = boxSize.width;
			this.adjustedDimensions.height = Math.round(this.adjustedDimensions.width/this.size.width * this.size.height);
			this.adjustedDimensions.yOffset = -Math.round((this.adjustedDimensions.height - boxSize.height)/2);
			this.adjustedDimensions.xOffset = 0;
		}
		$(this.domImage).css({
			width: this.adjustedDimensions.width+"px",
			height: this.adjustedDimensions.height+"px",
			left: this.adjustedDimensions.xOffset+"px",
			top: this.adjustedDimensions.yOffset+"px"
		});
	},
	removeFr