Learn how to use jQuery at the Blog

Westminster Cathedral Gift Shop « visit

  • Added 7 months ago
  • 2538 Lines of Code shown
  • 6 Links of Interest
http://westminstercathedral-gift.co.uk
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 Westminster Cathedral Gift Shop - http://westminstercathedral-gift.co.uk

/// <reference path="firebug.js" />
/// <reference path="jquery-1.3.2.js" />
/// <reference path="jquery.mousewheel.js" />
/// <reference path="jquery.ajaxdotnet.3.1.js" />
/// <reference path="json2.js" />
/// <reference path="jquery.color.js" />
/// <reference path="jquery.hash.1.js" />
/// <reference path="jquery.history.js" />
/// <reference path="jquery.cookie.js" />

// Cache
//#region
var cache_find = function($element, key, fn) {
	var arr = $element.data(key);
	if ($.isArray(arr)) {

	}
	else
		return null;
};
//#endregion

// Ajax
//#region
var a_categoryKey = "categoryKey.ajax";
var a_productKey = "productKey.ajax";

var a_loadCategory = function(categoryId, success, error) {
	a_invoke("LoadCategory", true, { categoryId: categoryId }, success, error);
};

var a_loadCategoryByProduct = function(productId, success, error) {
	a_invoke("LoadCategoryByProduct", true, { productId: productId }, success, error);
};

var a_listCategories = function(success, error, complete) {
	a_invoke("ListCategories", true, {}, success, error, complete);
};

var a_loadProduct = function(productId, success, error) {
	a_invoke("LoadProduct", true, { productId: productId }, success, error);
};

var a_loadProductArray = function(productIdArray, success, error) {
	a_invoke("LoadProductArray", true, { productIdArray: productIdArray }, success, error);
};

var a_getUser = function(success, error) {
	a_invoke("GetUser", true, {}, success, error);
};

var a_addNewsletter = function(email, success, error) {
	a_invoke("AddNewsletter", false, { email: email }, success, error);
};
//#endregion

// Resize Window
//#region
var g_resize = function() {
	var c_scrollHeight = 20;
	var c_headerHeight = 98;
	var c_timeout = "timeout.resize";
	var c_delay = 1;
	var c_rightOffset = 88;
	var c_productHeight = 193;
	var c_productWidth = 160;
	var c_collapseRequired = "collapseRequired.resize";

	var _newsletterHeight = 0;
	var _registrationHeight = 0;

	var $window, $column, $footer, $products, $shoppingBasketWrapper, $scrollTrack, $productList, $welcomePanel;

	// Methods
	//#region
	var _resize = function() {
		var height = $window.height() - $footer.height();

		$column.height(height);

		var heightOffset = c_headerHeight;
		if ($welcomePanel.is(":visible")) {
			heightOffset += 20;
		}

		// Black Bar Top + Height + Scroll Bar height.
		$products.height((height - $products.position().top) - heightOffset);

		var productsHeight = $products.height();
		var productsWidth = $products.width();
		var productCount = $productList.children("li").length;
		var rows = Math.floor(productsHeight / c_productHeight);
		var productsPerRow = Math.ceil(productCount / rows);
		var productsCubed = productsPerRow * c_productWidth;
		$productList.width(productsCubed > productsWidth ? productsCubed : productsWidth);

		// Make sure g_scroll has initialised first.
		g_scroll.resize();

		if ($scrollTrack.is(":visible")) {
			//heightOffset += c_scrollHeight;
			$products.height(productsHeight - c_scrollHeight);
		}

		g_shoppingbag.resize(height - (c_rightOffset + _newsletterHeight + _registrationHeight));

		// Make sure g_prodinfo has intialised first.
		// Don't need to account for content above Products Div.
		// Not sure what the 6 is for. I assume I've missed a margin or some padding.
		g_prodinfo.resize(height - heightOffset - 6);
	};

	var _resizeTimeout = function() {
		if (!$window.data(c_timeout)) {
			$window.data(c_timeout, setTimeout(function() {
				_resize();
				$window.data(c_timeout, false);
			}, c_delay));
		}
	};

	var _adjustRightBar = function() {
		var height = $window.height() - $footer.height();
		height -= c_rightOffset + _newsletterHeight + _registrationHeight;
		g_shoppingbag.resize(height);

		$("#newsletter").animate({ bottom: _registrationHeight + 1 });
	};

	var _shoppingBasketDifference = function() {
		return $shoppingBasketWrapper[0].scrollHeight - $shoppingBasketWrapper.height();
	};
	//#endregion

	// Event Handlers
	//#region
	var _window_resize = function() {
		if ($.browser.msie) {
			_resizeTimeout();
		}
		else {
			_resize();
		}
	};
	//#endregion

	// Constructor
	//#region
	var _init = function(newsletterHeight) {
		$window = $(window);
		$column = $(".column");
		$footer = $("#footer");
		$products = $("#products");
		$shoppingBasketWrapper = $("#shopping_basket_wrapper");
		$productList = $("#products ul");
		$welcomePanel = $("#ctl00_ctl00_middleCPH_middleCPH_welcomePanel");

		if ($productList.length == 0)
			$productList = $("<ul />").appendTo($products);

		// Make sure g_scroll has initialised first.
		$scrollTrack = $("#scroll_track");

		$(".column, #products").css({
			"min-height": 0
		});

		$window.resize(_window_resize);
	};
	//#endregion

	return {
		init: function(newsletterHeight, registrationHeight) {
			if (newsletterHeight)
				_newsletterHeight = newsletterHeight;

			if (registrationHeight)
				_registrationHeight = registrationHeight;

			_init();
		},
		resize: function(collapseIfNeeded) {
			_resize();

			if (collapseIfNeeded) {
				var heightDiff = _shoppingBasketDifference();
				if (heightDiff > 0) {
					$("#right").triggerHandler(c_collapseRequired, [heightDiff]);
				}
			}
		},
		setNewsletterHeight: function(height) {
			_newsletterHeight = height;
			_adjustRightBar();
		},
		setRegistrationHeight: function(height) {
			_registrationHeight = height;
			_adjustRightBar();
		},
		bindCollapseRequired: function(fun) {
			$("#right").bind(c_collapseRequired, fun);
		},
		unbindCollapseRequired: function(fun) {
			$("#right").unbind(c_collapseRequired, fun);
		},
		shoppingBasketDifference: function() {
			return _shoppingBasketDifference();
		}
	};
} ();
//#endregion

// Drag Nav
//#region
var g_drag = function() {
	var c_down = "down.drag";
	var c_index = "index.drag";
	var c_offset = 3;
	var c_moving = "moving";
	var c_selected = "selected";
	var c_catSelect = "cat_select.drag";
	var c_navId = "#nav";

	var $breadCrumb, $dragMe, $container, $navBar, $document;

	var _timeout = null;

	// Methods
	//#region
	var _setBreadCrumb = function(value, useShop) {
		useShop = useShop === undefined ? true : useShop;

		$breadCrumb
			.html(useShop ? "shop / <span class=\"highlight\">" + value.toLowerCase() + "</span>" : value);
	};

	var _load = function(idx, cat, name) {
		$container.data(c_index, idx);

		_setBreadCrumb(name.toLowerCase());

		$(c_navId).triggerHandler(c_catSelect, [idx, cat, name]);
	};
	//#endregion

	// Event Handlers
	//#region
	var _document_mousemove = function(evt) {
		var startingPosition = $dragMe.data(c_down);
		var clientY = evt.clientY;
		var dragMeHeight = $dragMe.height();
		var containerTop = $container.position().top;
		var containerHeight = $container.height();

		var boundary = containerTop + containerHeight;

		if (startingPosition) {
			var dragMeTop = clientY - startingPosition - containerTop;
			var dragMeBottom = dragMeTop + dragMeHeight;

			if (dragMeBottom > containerHeight) {
				dragMeTop = containerHeight - dragMeHeight;
				dragMeBottom = containerHeight;
			}
			else if (dragMeTop < 0) {
				dragMeTop = 0;
				dragMeBottom = dragMeHeight;
			}

			$dragMe.css({
				top: dragMeTop
			});

			var links = $("#nav a");
			var linkHeight = links.height() + c_offset;
			var linkMiddle = linkHeight / 2;

			links.each(function(idx) {
				var link = $(this);
				var middle = link.position().top + linkMiddle;

				if (dragMeBottom >= middle && dragMeBottom < (middle + linkHeight)) {
					link.addClass(c_selected);

					var catMatches = link.attr("href").match(/\/([\d]+).cat/);
					if (catMatches.length > 1) {
						var cat = Number(catMatches[1]);

						if (!isNaN(cat)) {
							if (_timeout)
								clearTimeout(_timeout);

							_timeout = setTimeout(function() {
								_load(idx, cat, link.attr("title"));
								_timeout = null;
							}, 500);
						}
					}
				}
				else {
					link.removeClass();
				}
			});
		}
	};

	var _document_mouseup = function(evt) {
		$dragMe
			.animate({
				top: 0
			}, function() {
				$container.removeClass(c_moving);
			})
			.data(c_down, false);

		$document
			.unbind("mousemove", _document_mousemove);
	};

	var _bar_mousedown = function(evt) {
		evt.preventDefault();

		var $this = $(this);

		var clientY = evt.clientY;
		var containerTop = $container.position().top;

		var startingPosition = clientY - containerTop;

		$container
			.addClass(c_moving);

		$this
			.data(c_down, startingPosition);

		$document
			.mousemove(_document_mousemove)
			.one("mouseup", _document_mouseup);
	};

	var _nav_click = function(evt) {
		evt.preventDefault();

		var $this = $(this);
		var list = $("#nav a");
		var idx = list.index(this);

		list.removeClass(c_selected);
		$this.addClass(c_selected);

		var catMatches = $this.attr("href").match(/\/([\d]+).cat/);
		if (catMatches.length > 1) {
			var cat = Number(catMatches[1]);

			if (!isNaN(cat))
				_load(idx, cat, $this.attr("title"));
		}
		else modal.show("Category not recognised.");
	};

	var _preventDefault = function(evt) { evt.preventDefault(); };
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		$document = $(document);
		$container = $("#nav_container");
		$navBar = $("#nav_bar");
		$breadCrumb = $("#bread_crumb");

		$navBar.append("<span>DRAG ME TO BROWSE</span>");

		$dragMe = $("<div id=\"drag_me\">")
			.mousedown(_bar_mousedown)
			.bind("dragstart", _preventDefault)
			.bind("selectstart", _preventDefault)
			.appendTo($container);

		$("#nav")
			.bind("dragstart", _preventDefault)
			.bind("selectstart", _preventDefault);

		$("#nav a").click(_nav_click);
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		bindSelect: function(fun) {
			$(c_navId).bind(c_catSelect, fun);
		},
		unbindSelect: function(fun) {
			if (fun !== undefined)
				$(c_navId).unbind(c_catSelect, fun);
			else $(c_navId).unbind(c_catSelect);
		},
		setBreadCrumb: function(value, useShop) {
			_setBreadCrumb(value, useShop);
		}
	};
} ();
//#endregion

// Scroll Products
//#region
var g_scroll = function() {
	var c_down = "down.scroll";
	var c_selected = "selected";

	var $products, $bar, $track, $document, $productList;

	// Methods
	//#region
	var _resize = function() {
		var totalWidth = $productList.width();
		var visibleWidth = $products.width();
		var scrollLeft = $products.scrollLeft();

		var barLeft = (scrollLeft / totalWidth) * visibleWidth;
		var barWidth = (visibleWidth / totalWidth) * visibleWidth;

		$bar.css({
			width: barWidth,
			left: barLeft
		});

		var display = "block";
		if (barWidth >= visibleWidth) {
			display = "none";
			$products.scrollLeft(0);
		}
		else $products.scrollLeft(scrollLeft);

		$track.css({
			display: display
		});
	};
	//#endregion

	// Event Handlers
	//#region
	var _bar_mousedown = function(evt) {
		evt.preventDefault();

		var $this = $(this);

		var clientX = evt.clientX;
		var trackLeft = $track.position().left;
		var barLeft = $bar.position().left;

		var startingPosition = clientX - trackLeft - barLeft;

		$this
			.data(c_down, startingPosition)
			.addClass(c_selected);

		$document
			.mousemove(_document_mousemove)
			.one("mouseup", _document_mouseup);
	};

	var _document_mouseup = function(evt) {
		$bar
			.removeClass(c_selected)
			.data(c_down, false);

		$document
			.unbind("mousemove", _document_mousemove);
	};

	var _document_mousemove = function(evt) {
		var startingPosition = $bar.data(c_down);
		var clientX = evt.clientX;
		var trackLeft = $track.position().left;
		var trackWidth = $track.width();
		var barWidth = $bar.width();
		var totalWidth = $products[0].scrollWidth;
		var visibleWidth = $products.width();

		var trackDiff = trackWidth - barWidth;

		if (startingPosition) {
			var position = (clientX - trackLeft) - startingPosition;
			position = position > trackDiff ? trackDiff : position < 0 ? 0 : position;

			var productsLeft = ((position / trackDiff) * (totalWidth - visibleWidth));
			$products.scrollLeft(productsLeft);

			$bar.css({ left: position });
		}
	};

	var _bar_mousewheel = function(evt, delta) {
		var scrollWidth = $products.scrollLeft();
		$products.scrollLeft(scrollWidth + (delta < 0 ? 80 : -80));
		_resize();
	};

	var _preventDefault = function(evt) { evt.preventDefault(); };
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		var pager = $("#products_pager");

		$document = $(document);
		$products = $("#products");
		$productList = $("#products ul");

		if ($productList.length == 0)
			$productList = $("<ul />").appendTo($products);

		$products
			.css({
				"overflow": "hidden"
			});

		$track = $("<div id=\"scroll_track\" />")
			.insertAfter(pager);

		pager.remove();

		$products.mousewheel(_bar_mousewheel);

		$bar = $("<div id=\"scroll_bar\"><span>SCROLL WITH ME</span></div>")
			.mousedown(_bar_mousedown)
			.bind("dragstart", _preventDefault)
			.bind("selectstart", _preventDefault)
			.appendTo($track);
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		resize: function() {
			_resize();
		}
	};
} ();
//#endregion

// Product Listing
//#region
var g_listing = function() {
	var c_buildComplete = "buildComplete.listing";
	var c_expanded = "expanded";
	var c_itemClick = "itemClick.listing";
	var c_catClick = "catClick.listing";
	var c_over = "over.listing";
	var c_parentId = "#products";
	var c_productData = "product.listing";
	var c_categoryId = "categoryId.listing";
	var c_filters = "filters.listing";
	var c_term = "term.listing";

	// Helpers
	//#region
	var _bindEvents = function() {
		var $list = $("#products li");
		$list.hover(_listing_over, _listing_out)
				.click(_info_click);
		$("#products .product_list .info a").click(_preventDefault);
	};

	var _unbindEvents = function() {
		var $list = $("#products li");
		$list.unbind("mouseenter mouseleave click");
		$("#products .product_list .info a").unbind("click");
	};
	//#endregion

	// Ajax
	//#region
	var _list = function(categoryId, filters, callback) {
		var url;
		var data = { categoryId: categoryId };

		$("#middle_container")
			.addClass("loading")
			.children()
				.css({ visibility: "hidden" });

		$(c_parentId).data(c_categoryId, categoryId);
		$(c_parentId).data(c_filters, (filters === undefined ? null : filters));
		$(c_parentId).data(c_term, null);

		if (filters) {
			url = "/Service.asmx/FilterProducts";
			data.filters = filters;
		}
		else url = "/Service.asmx/PageProducts";

		$.ajaxDotNet(url, {
			useGet: true,
			data: data,
			success: function(obj) {
				if (obj && obj.d) {
					_buildList(obj.d);
					_bindEvents();
					$(c_parentId).triggerHandler(c_buildComplete, [categoryId, null, filters]);
				}
			},
			complete: function() {
				$("#middle_container")
					.removeClass("loading")
					.children()
						.css({ visibility: "visible" });

				if ($.isFunction(callback))
					callback();
			}
		});
	};

	var _listCategories = function(callback) {
		$("#middle_container")
			.addClass("loading")
			.children()
				.css({ visibility: "hidden" });

		$(c_parentId).data(c_categoryId, null);
		$(c_parentId).data(c_filters, null);
		$(c_parentId).data(c_term, null);

		a_listCategories(function(list) {
			_buildCategoryList(list);
			_bindEvents();
			$(c_parentId).triggerHandler(c_buildComplete, [null, null, null]);

			if ($.isFunction(callback))
				callback();
		}, null, function() {
			$("#middle_container")
				.removeClass("loading")
				.children()
					.css({ visibility: "visible" });
		});
	};

	var _search = function(term) {
		$(c_parentId).data(c_categoryId, null);
		$(c_parentId).data(c_filters, null);
		$(c_parentId).data(c_term, term);

		a_searchProducts(term, function(obj) {
			_buildList(obj);
			_bindEvents();
			$(c_parentId).triggerHandler(c_buildComplete, [null, term, null]);
		});
	};

	var _load = function(productId, callback) {
		a_loadProduct(productId, callback);
	};
	//#endregion

	// Builders
	//#region
	var _buildItem = function(product, $ul) {
		var $li = $("<li class=\"" + product.ID + "\"><img src=\"/128/128/" + product.ID + ".prodimg\" height=\"128\" width=\"128\""
			+ " alt=\"" + product.Name + " Image\" />"
			+ (product.FeaturedIntro && product.FeaturedIntro != ""
				? "<div class=\"feature_intro\"><p>" + product.FeaturedIntro + "</p></div>"
				: "")
			+ "<div class=\"info\"><span class=\"price\">" + product.FormattedWithVat + "</span>"
			+ "<a href=\"" + product.Url + "\">" + product.Name.toUpperCase() + "</a></div></li>")
			.appendTo($ul);

		$li.data(c_productData, product);

		return $li;
	};

	var _buildCategoryItem = function(category, $ul) {
		var $li = $("<li class=\"" + category.ID + "\"><img width=\"128\" height=\"128\""
			+ " alt=\"" + category.Name + "\" src=\"/128/128/" + category.ID + ".catimg\" />"
			+ (category.FeaturedIntro && category.FeaturedIntro != ""
				? "<div class=\"feature_intro\"><p>" + category.FeaturedIntro + "</p></div>"
				: "")
			+ "<div class=\"info\"><a class=\"cat_title\" href=\"" + category.Url + "\">"
			+ category.Name.toUpperCase() + "</a></div></li>")
			.appendTo($ul);

		$li.data(c_productData, category);

		return $li;
	};

	var _buildList = function(productList) {
		var $ul = $("#products ul");

		if ($ul.length == 0)
			$ul = $("<ul />").appendTo("#products");
		else $ul.children().remove();

		$ul.removeClass("category_list").addClass("product_list");

		$("#products").scrollLeft(0);

		$.each(productList, function() {
			_buildItem(this, $ul);
		});
	};

	var _buildCategoryList = function(categoryList) {
		var $ul = $("#products ul");

		if ($ul.length == 0)
			$ul = $("<ul />").appendTo("#products");
		else $ul.children().remove();

		$ul.removeClass("product_list").addClass("category_list");

		$("#products").scrollLeft(0);

		$.each(categoryList, function() {
			_buildCategoryItem(this, $ul);
		});
	};
	//#endregion

	// Event Handlers
	//#region
	var _listing_over = function(evt) {
		var $this = $(this);
		if (!$this.data(c_over)) {
			var $info = $this.find(".info");
			var $clickHere = $info.find(".click_here");

			$info.addClass(c_expanded);
			$this.data(c_over, true);
		}
	};

	var _listing_out = function(evt) {
		var $this = $(this);
		if ($this.data(c_over)) {
			var $info = $this.find(".info");

			$info.find(".click_here").css({
				display: "none"
			});

			$info.removeClass(c_expanded);
			$this.data(c_over, false);
		}
	};

	var _info_click = function(evt) {
		evt.preventDefault();

		var $this = $(this);
		if ($this.parent().hasClass("category_list")) {
			$(c_parentId).triggerHandler(c_catClick, [Number($this.attr("class")), $.trim($this.find(".cat_title").text())]);
		}
		else {
			var callback = function(prod) {
				var categoryId = $(c_parentId).data(c_categoryId);
				var filters = $(c_parentId).data(c_filters);
				var term = $(c_parentId).data(c_term);

				$(c_parentId).triggerHandler(c_itemClick, [prod, categoryId, term, filters]);
			};

			var product = $(this).data(c_productData);
			if (product === undefined) {
				_load(Number($(this).attr("class")), callback);
			}
			else callback(product);
		}
	};

	var _preventDefault = function(evt) { evt.preventDefault(); };
	//#endregion

	// Construct & Destruct
	//#region
	var _init = function() {
		_bindEvents();
	};
	var _dispose = function() {
		_unbindEvents();
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		dispose: function() {
			_dispose();
		},
		load: function(categoryId, filters, callback) {
			if (categoryId) {
				_list(categoryId, filters, callback);
			}
			else _listCategories(callback);
		},
		search: function(term) {
			_search(term);
		},
		bindClick: function(fun) {
			$(c_parentId).bind(c_itemClick, fun);
		},
		unbindClick: function(fun) {
			if (fun !== undefined)
				$(c_parentId).unbind(c_itemClick, fun);
			else $(c_parentId).unbind(c_itemClick);
		},
		bindCatClick: function(fun) {
			$(c_parentId).bind(c_catClick, fun);
		},
		unbindCatClick: function(fun) {
			if (fun !== undefined)
				$(c_parentId).unbind(c_catClick, fun);
			else $(c_parentId).unbind(c_catClick);
		},
		bindBuildComplete: function(fun) {
			$(c_parentId).bind(c_buildComplete, fun);
		},
		unbindBuildComplete: function(fun) {
			if (fun !== undefined)
				$(c_parentId).unbind(c_buildComplete, fun);
			else $(c_parentId).unbind(c_buildComplete);
		},
		loadProduct: function(productId, callback) {
			_load(productId, callback);
		}
	}
} ();
//#endregion

// Product Info
//#region
var g_prodinfo = function() {
	var c_down = "down.prodinfo";
	var c_selected = "selected.prodinfo";
	var c_infoWidth = 350;
	var c_infoOffset = 1;
	var c_infoScrollWidth = 10;
	var c_infoMargin = 20;
	var c_closeOffset = 2;
	var c_imageHeight = 220;
	var c_imageWidth = 220;
	var c_addClick = "addClick.prodinfo";
	var c_productId = "productId.prodinfo";
	var c_anotherClass = "another";
	var c_peopleAlsoIndex = "peopleAlsoIndex.prodinfo";
	var c_product = "product.prodinfo";

	var $productInfo, $middleContainer, $products, $productInfoContainer,
		$infoScrollTrack, $infoScrollBar, $document, $productInfoWrapper, $productInfoClose;

	// Helpers
	//#region
	var _getInfoWidth = function() {
		var productsWidth = $products.width();
		var infoWidth = (productsWidth < (c_infoWidth + c_infoOffset))
			? productsWidth - c_infoOffset
			: c_infoWidth;

		return infoWidth;
	};

	var _getContainerWidth = function(width) {
		return width - (c_infoMargin + c_infoScrollWidth);
	};

	var _productInBag = function(inBag) {
		var addButton = $("#product_info_add");
		if (inBag) {
			addButton.addClass(c_anotherClass);
		}
		else {
			addButton.removeClass(c_anotherClass);
		}
	};

	var _togglePeopleAlso = function(forward) {
		var $listItems = $("#people_images li");
		var length = $listItems.length;
		if (length > 0) {
			forward = forward == undefined ? true : forward;

			var idx = Number($productInfo.data(c_peopleAlsoIndex));
			idx = (isNaN(idx) ? 0 : (forward ? ++idx : --idx));
			idx = (idx >= length ? 0 : (idx < 0 ? length - 1 : idx));

			$listItems.css({ display: "none" });
			var product = $("#people_images li:eq(" + idx + ")")
				.css({ display: "block" })
				.data(c_product);

			if (product)
				$("#people_title").text("/ " + product.Name);

			$productInfo.data(c_peopleAlsoIndex, idx);
		}
	};
	//#endregion

	// Builders
	//#region
	var _buildPeopleAlso = function(productId) {
		var $peopleAlso = $("#people_also").css({ visibility: "hidden" });
		var $ul = $("#people_images")
					.children()
						.remove()
					.end();

		rv_load(productId, function(list) {
			if (list && list.length > 0) {
				$.each(list, function() {
					if (this.ID != productId)
						$ul.append($("<li><a href=\"#" + this.ID + "\"><img width=\"110\" height=\"80\" src=\"/80/110/"
							+ this.ImageName + "\" alt=\"" + this.Name + "\" /></a></li>").data(c_product, this));
				});

				$peopleAlso.css({ visibility: "visible" });
				_togglePeopleAlso();

				$ul.find("a").click(function(evt) {
					evt.preventDefault();
					goToProduct($(this).hash());
				});
			}
		});
	};
	//#endregion

	// Resize
	//#region
	var _resizeScroll = function() {
		var totalHeight = $productInfoWrapper[0].scrollHeight;
		var visibleHeight = $productInfoWrapper.height();
		var scrollTop = $productInfoWrapper.scrollTop();

		var barTop = (scrollTop / totalHeight) * visibleHeight;
		barTop = isNaN(barTop) ? 0 : barTop;
		var barHeight = totalHeight != 0 ? ((visibleHeight / totalHeight) * visibleHeight) : 0;

		$infoScrollBar.css({
			height: barHeight,
			top: barTop
		});

		var display = "none";
		var width = $productInfo.width();
		var right = c_closeOffset;
		if (barHeight < $infoScrollTrack.height()) {
			display = "block";
			width -= c_infoScrollWidth;
			right += c_infoScrollWidth;
		}

		$infoScrollTrack.css({
			display: display
		});

		$productInfoWrapper.css({
			width: width
		});

		$productInfoClose.css({
			right: right
		});
	};

	var _resize = function(height) {
		if (height) {
			$productInfo.height(height);
			$productInfoWrapper.height(height);
			$infoScrollTrack.height(height);
		}

		var width = _getInfoWidth();

		if ($productInfo.is(":visible")) {
			$productInfo.width(width);
		}

		if ($productInfoContainer.is(":visible")) {
			_resizeScroll();
		}
	};
	//#endregion

	// Show/Hide
	//#region
	var _openInfo = function(product, callback) {
		var width = _getInfoWidth();

		$("#product_info_title")
			.text(product.Name + " - " + product.FormattedWithVat);

		$("#product_info_image")
			.attr("src", "/" + product.ID + ".infoimg")
			.attr("alt", product.Name + " image.");


		if (product.Description != "") {
			$("#product_info_decription_title")
				.css({ display: "block" });
			$("#product_info_description")
				.css({ display: "block" })
				.html(product.HtmlDescription);
		}
		else {
			$("#product_info_decription_title")
				.css({ display: "none" });
			$("#product_info_description")
				.css({ display: "none" });
		}

		_productInBag(product.InBag);
		_buildPeopleAlso(product.ID);

		$productInfo
			.css({
				display: "block"
			})
			.animate({
				width: width
			}, 1000, function() {
				$productInfoWrapper
					.css({
						width: width
					});

				$productInfoContainer
					.css({
						display: "block"
					});

				$productInfoClose
					.css({
						display: "block"
					});

				$productInfoWrapper.scrollTop(0);
				_resizeScroll();

				if ($.isFunction(callback)) {
					callback();
				}
			})
			.data(c_productId, product.ID);
	};

	var _closeInfo = function(callback) {
		$infoScrollTrack
		.css({
			display: "none"
		});

		$productInfoClose
			.css({
				display: "none"
			});

		$productInfoContainer
			.css({
				display: "none"
			});

		$productInfo
			.animate({
				width: 0
			}, 1000, function() {
				$productInfo.css({
					display: "none"
				})

				if ($.isFunction(callback)) {
					callback();
				}
			});
	};
	//#endregion

	// Event Handlers
	//#region
	var _close_click = function(evt) {
		evt.preventDefault();

		$productInfo.stop();
		_closeInfo();
	};

	var _add_click = function(evt) {
		evt.preventDefault();
		$productInfo.triggerHandler(c_addClick, [$productInfo.data(c_productId)]);
	};

	var _bar_mousedown = function(evt) {
		evt.preventDefault();

		var $this = $(this);

		var clientY = evt.clientY;
		var barTop = $infoScrollBar.position().top;

		var startingPosition = clientY - barTop;

		$this
			.data(c_down, startingPosition)
			.addClass(c_selected);

		$document
			.mousemove(_document_mousemove)
			.one("mouseup", _document_mouseup);
	};

	var _document_mouseup = function(evt) {
		$infoScrollBar
			.removeClass(c_selected)
			.data(c_down, false);

		$document
			.unbind("mousemove", _document_mousemove);
	};

	var _document_mousemove = function(evt) {
		var startingPosition = $infoScrollBar.data(c_down);
		var clientY = evt.clientY;
		var trackHeight = $infoScrollTrack.height();
		var barHeight = $infoScrollBar.height();
		var totalHeight = $productInfoWrapper[0].scrollHeight;
		var visibleHeight = $productInfoWrapper.height();

		var trackDiff = trackHeight - barHeight;

		if (startingPosition) {
			var position = clientY - startingPosition;
			position = position > trackDiff ? trackDiff : position < 0 ? 0 : position;

			var infoTop = ((position / trackDiff) * (totalHeight - visibleHeight));
			$productInfoWrapper.scrollTop(infoTop);

			$infoScrollBar.css({ top: position });
		}
	};

	var _wrapper_mousewheel = function(evt, delta) {
		var trackHeight = $infoScrollTrack.height();
		var barHeight = $infoScrollBar.height();
		var trackDiff = trackHeight - barHeight;

		var totalHeight = $productInfoWrapper[0].scrollHeight;
		var visibleHeight = $productInfoWrapper.height();
		var wrapperOffset = totalHeight - visibleHeight;

		var scrollTop = $productInfoWrapper.scrollTop();
		var calcTop = delta > 0 ? scrollTop - 20 : scrollTop + 20;
		var newTop = $productInfoWrapper.scrollTop(calcTop).scrollTop();
		newTop = (newTop / wrapperOffset) * trackDiff;

		if (isFinite(newTop) && !isNaN(newTop))
			$infoScrollBar.css({ top: newTop });
	};

	var _preventDefault = function(evt) { evt.preventDefault(); };
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		$document = $(document);
		$middleContainer = $("#middle_container");
		$products = $("#products");

		$productInfo = $("<div id=\"product_info\" />")
			.appendTo($middleContainer);

		$productInfoWrapper = $("<div id=\"product_info_wrapper\" />")
			.mousewheel(_wrapper_mousewheel)
			.appendTo($productInfo);

		$productInfoContainer = $("<div id=\"product_info_container\">"
				+ "<h2 id=\"product_info_title\">Product Title</h2>"
				+ "<img id=\"product_info_image\" alt=\"Product Image\" />"
				+ "<button id=\"product_info_add\" class=\"button\">Add to Bag</button>"
				+ "<h3 id=\"product_info_decription_title\">description:</h3>"
				+ "<div id=\"product_info_description\"></div>"
				+ "<div id=\"people_also\"><p>you recently viewed</p><p id=\"people_title\">/ </p>"
					+ "<ul id=\"people_images\"><li></li></ul>"
					+ "<a href=\"#\" class=\"arrow left_arrow\">left</a><a href=\"#\" class=\"arrow right_arrow\">right</a></div>"
			+ "</div>")
			.appendTo($productInfoWrapper);

		$infoScrollTrack = $("<div id=\"product_info_track\" />")
			.appendTo($productInfo);

		$infoScrollBar = $("<div id=\"product_info_bar\" />")
			.mousedown(_bar_mousedown)
			.bind("dragstart", _preventDefault)
			.bind("selectstart", _preventDefault)
			.appendTo($infoScrollTrack);

		$productInfoClose = $("<a id=\"product_info_close\" href=\"#\" title=\"Close Product Information\">CLOSE X</a>")
			.appendTo($productInfo);

		$("#product_info_close").click(_close_click);
		$("#product_info_add").click(_add_click);

		$("#people_also .arrow").click(function(evt) {
			evt.preventDefault();
			_togglePeopleAlso($(this).hasClass("right_arrow"));
		});
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		resize: function(height) {
			_resize(height);
		},
		open: function(product, callback) {
			_openInfo(product, callback);
		},
		close: function(callback) {
			_closeInfo(callback);
		},
		isOpen: function() {
			return $productInfo.is(":visible");
		},
		bindAddClick: function(fun) {
			$productInfo.bind(c_addClick, fun);
		},
		unbindAddClick: function(fun) {
			$productInfo.unbind(c_addClick, fun);
		},
		productInBag: function(inBag) {
			_productInBag(inBag);
		},
		currentProductId: function() {
			return $productInfo.data(c_productId);
		}
	};
} ();
//#endregion

// Drop Down Boxes
//#region
var g_drop = function() {
	var c_selected = "selected";
	var c_cbSelected = "cb_selected";
	var c_searchSubmit = "searchSubmit.drop";
	var c_filterSelect = "filterSelect.drop";
	var c_timeout = "timeout.drop";
	var c_categoryId = "categoryId.drop";

	var $drop, $productSearch, $productFilter, $all;

	//Helpers
	//#region
	var _showFilters = function(show) {
		show = (show === undefined ? true : show);

		if (!show) {
			if ($productFilter.hasClass(c_selected))
				$productFilter.removeClass(c_selected);
		}

		$("#product_filter_button").css({ visibility: (show ? "visible" : "hidden") });
	};
	//#endregion

	//Builders
	//#region
	var _buildFilter = function(key, value, parent) {
		return $("<li><div><a class=\"check_box\" href=\"#" + key + "\">unselected</a><span>" + value.toUpperCase() + "</span></div></li>")
			.appendTo(parent);
	};
	//#endregion

	//Ajax
	//#region
	var _loadFilters = function(categoryId, filters) {
		var oldCategoryId = $productFilter.data(c_categoryId);

		var callback = function() {
			if (filters) {
				$("#drop .content a.check_box")
					.removeClass(c_cbSelected)
					.each(function() {
						var $this = $(this);
						var hash = $this.hash();
						$.each(filters, function() {
							if (hash == this) {
								$this.addClass(c_cbSelected);
								return false;
							}
						});
					});
			}
		};

		if (categoryId != oldCategoryId) {
			$productFilter.data(c_categoryId, categoryId);

			a_listFilters(categoryId, function(filterList) {
				var $ul = $productFilter
					.find("ul")
					.children()
					.remove()
					.end();

				if (filterList.length > 0) {
					$.each(filterList, function() {
						_buildFilter(this.Key, this.Value, $ul);
					});

					_showFilters();
					$("#drop .content a.check_box").click(_checkBox_select);
					callback();
				}
				else _showFilters(false);
			});
		}
		else {
			$("#drop .content a.check_box")
			.removeClass(c_cbSelected);

			callback();
		}
	};
	//#endregion

	//Event Handlers
	//#region
	var _button_click = function(evt) {
		evt.preventDefault();

		var $this = $(this).is("#product_search_button") ? $productSearch : $productFilter;

		if ($this.hasClass(c_selected)) {
			$this.removeClass(c_selected);
		}
		else {
			$all.removeClass(c_selected);
			$this.addClass(c_selected);
		}

		$("#search_text").val("");
	};

	var _search_click = function(evt) {
		evt.preventDefault();
		$productSearch.triggerHandler(c_searchSubmit, [$("#search_text").val()]);
	};

	var _search_keyup = function(evt) {
		evt.preventDefault();

		var timeout = $productSearch.data(c_timeout);
		if (timeout)
			clearTimeout(timeout);

		var value = $.trim($("#search_text").val());

		if (evt.keyCode == 13 && value != "")
			$productSearch.triggerHandler(c_searchSubmit, [$("#search_text").val()]);
		else if (value.length > 3) {
			$productSearch.data(c_timeout, setTimeout(function() {
				$productSearch.triggerHandler(c_searchSubmit, [$("#search_text").val()]);
			}, 500));
		}
	};

	var _checkBox_select = function(evt) {
		evt.preventDefault();

		var $this = $(this);
		$this.toggleClass(c_cbSelected);

		var filters = new Array();
		$("#drop .content a." + c_cbSelected).each(function() {
			filters.push($(this).hash());
		});

		$productFilter.triggerHandler(c_filterSelect, [$productFilter.data(c_categoryId), filters]);
	};
	//#endregion

	var _init = function() {
		$drop = $("<div id=\"drop\"><div id=\"search_drop\" class=\"content\"><span>WHAT ARE YOU LOOKING FOR?</span>"
			+ "<input type=\"text\" class=\"text\" id=\"search_text\" />"
			+ "<input type=\"image\" class=\"button\" src=\"/images/find.gif\" id=\"search_submit\" /></div>"
			+ "<div id=\"filter_drop\" class=\"content\"><ul /><input type=\"image\" class=\"button\" src=\"/images/find.gif\" id=\"filter_submit\" /></div></div>").insertAfter("#aspnetForm");

		$productSearch = $("#product_search_button, #search_drop");
		$productFilter = $("#product_filter_button, #filter_drop");

		$all = $("#black_bar .drop, #drop .content");

		$("#black_bar .drop").click(_button_click);
		$("#search_text").keyup(_search_keyup);
		$("#search_submit").click(_search_click);

		$("#search_drop").css({
			left: $("#product_search_button").position().left
		});

		$("#filter_drop").css({
			left: $("#product_filter_button").position().left
		});
	};

	return {
		init: function() {
			_init();
		},
		bindSearchSubmit: function(fun) {
			$productSearch.bind(c_searchSubmit, fun);
		},
		unbindSearchSubmit: function(fun) {
			$productSearch.unbind(c_searchSubmit, fun);
		},
		bindFilterSelect: function(fun) {
			$productFilter.bind(c_filterSelect, fun);
		},
		unbindFilterSelect: function(fun) {
			$productFilter.unbind(c_filterSelect, fun);
		},
		loadFilters: function(categoryId, filters) {
			_loadFilters(categoryId, filters);
		},
		showFilters: function(show) {
			_showFilters(show);
		},
		hideAll: function() {
			$all.removeClass(c_selected);
		}
	};
} ();
//#endregion

// Newsletter
//#region
var g_newsletter = function() {
	var c_change = "heightchange.newsletter";
	var c_defaultHeight = 175;
	var c_successHeight = 125;
	var c_minHeight = 21;
	var c_maxClass = "max";

	var _height = c_defaultHeight;
	var _successful = false;

	// Methods
	//#region
	var _isEmail = function(value) {
		return new RegExp(/^[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$/).test(value);
	};

	var _validate = function() {
		var $email = $("#ctl00_ctl00_rightCPH_newsletter_email");
		var $valid = $("#ctl00_ctl00_rightCPH_newsletter_valid");

		var email = $.trim($email.val());
		$email.val(email);

		var valid = _isEmail(email);
		$valid.css({ display: valid ? "none" : "inline" });
		return valid;
	};

	var _changeText = function(text) {
		$("#ctl00_ctl00_rightCPH_newsletter_text")
			.text(text)
			.animate({ color: "#deb844" })
			.animate({ color: "#ffffff" });
	};

	var _changeHeight = function(height) {
		_height = height;
		$("#newsletter")
			.trigger(c_change, [_height])
			.animate({ height: _height });
	};

	var _addNewsletter = function(email) {
		a_addNewsletter(email, function() {
			_changeText("Thank you. You will receive an email shortly, please click on the link within the email to complete registration.");
			_changeHeight(c_successHeight);
			_successful = true;
			$("#ctl00_ctl00_rightCPH_newsletter_email, #ctl00_ctl00_rightCPH_newsletter_submit").css({ display: "none" });
		},
		function(xhr, text, err) {
			switch (err.Message) {
				case "Already Exists":
					text = "This email already appears to be registered. Please make sure you have entered it correctly.";
					break;
				default:
					text = "Sorry, an error has occured. Please try again later.";
					break;
			}
			_changeText(text);
		});
	};

	var _expand = function() {
		_changeHeight(_successful ? c_successHeight : c_defaultHeight);
		$("#newsletter .inside").css({ display: "block" });
		$("#newsletter .min").removeClass(c_maxClass);
	};

	var _collapse = function() {
		_changeHeight(c_minHeight);
		$("#newsletter .inside").css({ display: "none" });
		$("#newsletter .min").addClass(c_maxClass);
	};

	var _isCollapsed = function() {
		return $("#newsletter .min").hasClass(c_maxClass);
	};
	//#endregion

	// Event Handlers
	//#region
	var _submit_click = function(evt) {
		evt.preventDefault();

		if (_validate()) {
			var $email = $("#ctl00_ctl00_rightCPH_newsletter_email");
			_addNewsletter($email.val());
		}
	};

	var _email_change = function(evt) {
		_validate();
	};

	var _min_click = function(evt) {
		evt.preventDefault();

		if (_isCollapsed())
			_expand();
		else _collapse();
	};
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		var $email = $("#ctl00_ctl00_rightCPH_newsletter_email");
		var $submit = $("#ctl00_ctl00_rightCPH_newsletter_submit");

		var $min = $("<a href=\"#\" class=\"min\">min</a>")
			.click(_min_click)
			.appendTo("#newsletter");

		$email.change(_email_change);
		$submit.click(_submit_click);

		if ($email.val() != "")
			_validate();
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		height: function() {
			return _height;
		},
		bindHeightChange: function(fun) {
			$("#newsletter").bind(c_change, fun);
		},
		expand: function() {
			_expand();
		},
		collapse: function() {
			_collapse();
		},
		isCollapsed: function() {
			return _isCollapsed();
		}
	};
} ();
//#endregion

// Registration
//#region
var g_registration = function() {
	var c_change = "heightchange.registration";
	var c_loggedOutHeight = 170;
	var c_loggedInHeight = 40;
	var c_errorHeight = 40;
	var c_minHeight = 21;
	var c_maxClass = "max";

	var _height = c_loggedOutHeight;
	var _loggedIn = false;
	var _error = false;

	// Methods
	//#region
	var _changeHeight = function(height) {
		_height = height;
		$("#logon")
			.trigger(c_change, [_height])
			.animate({ height: _height });
	};

	var _expand = function() {
		_changeHeight(_loggedIn ? c_loggedInHeight : (_error ? c_loggedOutHeight + c_errorHeight : c_loggedOutHeight));
		$("#logon .inside:eq(" + (_loggedIn ? 1 : 0) + ")").css({ display: "block" });

		if (_error)
			$("#logon .inside:eq(2)").css({ display: "block" });

		$("#logon .min").removeClass(c_maxClass);
	};

	var _collapse = function() {
		_changeHeight(c_minHeight);
		$("#logon .inside").css({ display: "none" });
		$("#logon .min").addClass(c_maxClass);
	};

	var _isCollapsed = function() {
		return $("#logon .min").hasClass(c_maxClass);
	};

	var _bindEvents = function() {
		$("#ctl00_ctl00_rightCPH_logon_submit").click(_submit_click);
		$("#ctl00_ctl00_rightCPH_logout_submit").click(_logout_click);
	};
	//#endregion

	// Event Handlers
	//#region
	var _submit_click = function(evt) {
		evt.preventDefault();

		a_logon($("#ctl00_ctl00_rightCPH_logon_email").val(),
			$("#ctl00_ctl00_rightCPH_logon_password").val(),
			function(successful) {
				if (successful) {
					$("#ctl00_ctl00_rightCPH_logon_password").val("");

					$("#logon .inside:eq(0)").css({ display: "none" });
					$("#logon .inside:eq(1)").css({ display: "block" });
					$("#logon .inside:eq(2)").css({ display: "none" });

					_changeHeight(c_loggedInHeight);
					_loggedIn = true;
					_error = false;
				}
				else {
					$("#logon .inside:eq(2)")
						.find("span")
							.text("Username or password incorrect.")
							.end()
						.css({ display: "block" });

					_changeHeight(c_loggedOutHeight + c_errorHeight);
					_error = true;
				}
			});
	};

	var _min_click = function(evt) {
		evt.preventDefault();

		if (_isCollapsed())
			_expand();
		else _collapse();
	};

	var _logout_click = function(evt) {
		evt.preventDefault();

		a_logout(function() {
			$("#logon .inside:eq(0)").css({ display: "block" });
			$("#logon .inside:eq(1)").css({ display: "none" });
			$("#logon .inside:eq(2)").css({ display: "none" });

			_changeHeight(c_loggedOutHeight);
			_loggedIn = false;
			_error = false;
		});
	};
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		var $logon = $("#logon");

		var $min = $("<a href=\"#\" class=\"min\">min</a>")
			.click(_min_click)
			.appendTo($logon);

		if ($logon.height() == c_loggedInHeight) {
			_loggedIn = true;
			_height = c_loggedInHeight;
			$logon.trigger(c_change, [_height]);
		}

		_bindEvents();
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		height: function() {
			return _height;
		},
		bindHeightChange: function(fun) {
			$("#logon").bind(c_change, fun);
		},
		expand: function() {
			_expand();
		},
		collapse: function() {
			_collapse();
		},
		isCollapsed: function() {
			return _isCollapsed();
		}
	};
} ();
//#endregion

// Shopping Bag
//#region
var g_shoppingbag = function() {
	var c_down = "down.shoppingbag";
	var c_selected = "selected.shoppingbag";
	var c_productRemoved = "productRemoved.shoppingbag";
	var c_stateChanged = "stateChanged.shoppingbag";
	var c_scrollWidth = 10;

	var $scrollTrack, $scrollBar, $shoppingBasket, $shoppingBasketWrapper, $shoppingBasketContainer, $document;

	// Helpers
	//#region
	var _heightDiff = function() {
		return $shoppingBasket[0].scrollHeight - $shoppingBasket.height();
	};

	var _resizeScroll = function() {
		var totalHeight = $shoppingBasketWrapper[0].scrollHeight;
		var visibleHeight = $shoppingBasketWrapper.height();
		var scrollTop = $shoppingBasketWrapper.scrollTop();

		var barTop = (scrollTop / totalHeight) * visibleHeight;
		barTop = isNaN(barTop) ? 0 : barTop;
		var barHeight = totalHeight != 0 ? ((visibleHeight / totalHeight) * visibleHeight) : 0;

		$scrollTrack.css({
			height: visibleHeight
		});

		$scrollBar.css({
			height: barHeight,
			top: barTop
		});

		var display = "none";
		var width = $shoppingBasket.width();
		if (barHeight < visibleHeight) {
			display = "block";
			width -= c_scrollWidth;
			$shoppingBasketWrapper.scrollTop(scrollTop);
		}
		else $shoppingBasketWrapper.scrollTop(0);

		$scrollTrack.css({
			display: display
		});

		$shoppingBasketWrapper.css({
			width: width
		});
	};

	var _resize = function(height) {
		if (height < 100)
			height = 100;
		$shoppingBasket.height(height);
		$shoppingBasketWrapper.height(height);
		_resizeScroll();
	};

	var _isInBag = function(productId) {
		var $tbody = $("#shopping_basket_container tbody");
		if ($tbody.length == 0) {
			return false;
		}

		var found = false;
		$tbody.children("tr").each(function() {
			if (Number($(this).attr("class")) == productId) {
				found = true;
				return false;
			}
		});
		return found;
	};

	var _bindTableEvents = function() {
		$("#shopping_basket tbody input").click(_remove_click);
		$("#ctl00_ctl00_rightCPH_checkout_submit").click(function(evt) {
			evt.preventDefault();
			location.href = "/Checkout.aspx";
		});
		// $("#ctl00_ctl00_rightCPH_delivery_list").change(_locations_change);
	};
	//#endregion

	// Builders
	//#region
	var _buildRow = function(product, $tbody) {
		$tbody.append("<tr class=\"" + product.ID + "\"><td>" + product.Name + "</td><td>" + product.Quantity + "</td><td>"
			+ product.FormattedWithVat + "</td><td><input type=\"submit\" value=\"X\"  class=\"" + product.ID + "\" /></td></tr>");
	};

	var _buildTable = function(bag) {
		if ($shoppingBasketContainer.children("table").length == 0) {
			$shoppingBasketContainer.children().remove();
			$shoppingBasketContainer.append("<table><thead><tr><th scope=\"col\">ITEM</th><th scope=\"col\">QTY</th>"
				+ "<th scope=\"col\">PRICE</th><th scope=\"col\"></th></tr></thead><tfoot><tr><td colspan=\"2\">TOTAL</td>"
				+ "<td colspan=\"2\" /></tr></tfoot><tbody /></table>"
				+ "<input type=\"submit\" id=\"ctl00_ctl00_rightCPH_checkout_submit\" value=\"Checkout\" />");
		}

		//	<tr><td colspan=\"2\">" + "<select id=\"ctl00_ctl00_rightCPH_delivery_list\" /></td><td colspan=\"2\" /></tr>

		//	_buildPostageLocations(bag.DeliveryLocation && bag.DeliveryLocation.ID ? bag.DeliveryLocation.ID : null);

		//	$shoppingBasketContainer.find("tfoot tr:eq(0) td:eq(1)").text(bag.FormattedPostage);
		$shoppingBasketContainer.find("tfoot tr:eq(0) td:eq(1)").text(bag.FormattedWithVat);

		var $tbody = $shoppingBasketContainer.find("tbody");
		if ($tbody.children().length > 0) {
			$tbody.children().remove();
		}

		$.each(bag.List, function() {
			_buildRow(this, $tbody);
		});
	};
	//#endregion

	// Ajax
	//#region
	var _refreshBag = function(callback) {
		a_getShoppingBag(function(obj) {
			if (obj.List.length > 0) {
				_buildTable(obj);
				_bindTableEvents();
			}
			else {
				if ($shoppingBasketContainer.children("p.empty").length == 0) {
					$shoppingBasketContainer.children().remove();
					$shoppingBasketContainer.append("<p class=\"empty\">Your shopping bag is currently empty..."
							+ " what are you waiting for? Go shopping.</p>");
				}
			}

			if ($.isFunction(callback)) {
				callback(obj);
			}
		},
		function() {
			if ($shoppingBasketContainer.children("p.error").length == 0) {
				$shoppingBasketContainer.children().remove();
				$shoppingBasketContainer.append("<p class=\"error\">An error occured while updating the shopping bag."
					+ " Please try refreshing the page.</p>");
			}
		},
		function() {
			_resizeScroll();
			$shoppingBasket.triggerHandler(c_stateChanged);
		});
	};

	var _addToBag = function(productId, quantity) {
		a_addToBag(productId, quantity, function(obj) { _refreshBag(); });
	};

	var _removeFromBag = function(productId, quantity) {
		a_removeFromBag(productId, quantity, function(obj) {
			_refreshBag(function(bag) {
				if (bag.List.length == 0) {
					$shoppingBasket.triggerHandler(c_productRemoved, [productId]);
				}
				else {
					var found = false;

					$.each(bag.List, function() {
						if (this.ID == productId) {
							found = true;
							return false;
						}
					});

					if (!found)
						$shoppingBasket.triggerHandler(c_productRemoved, [productId]);
				}
			});
		});
	};

	//	var _buildPostageLocations = function(selectedLocation) {
	//		$.ajaxDotNet("Service.asmx/ListPostageLocations", {
	//			useGet: true,
	//			success: function(obj) {
	//				var locationList = obj.d;
	//				var $locations = $("#ctl00_ctl00_rightCPH_delivery_list")
	//					.children().remove()
	//					.end()
	//					.append("<option value=\"---\">---</option>");

	//				$.each(locationList, function() {
	//					$locations.append("<option "
	//						+ (this.Key == selectedLocation ? "selected=\"selected\"" : "")
	//						+ " value=\"" + this.Key + "\">" + this.Value + "</option>");
	//				});
	//			}
	//		});
	//	};

	//	var _setPostageLocation = function(id) {
	//		$.ajaxDotNet("Service.asmx/SetPostageLocation", {
	//			useGet: true,
	//			data: { id: id },
	//			success: function(obj) {
	//				if (obj && obj.d) {
	//					if (obj.d.List.length > 0) {
	//						_buildTable(obj.d);
	//						_bindTableEvents();
	//					}
	//					else {
	//						if ($shoppingBasketContainer.children("p.empty").length == 0) {
	//							$shoppingBasketContainer.children().remove();
	//							$shoppingBasketContainer.append("<p class=\"empty\">Your shopping bag is currently empty..."
	//							+ " what are you waiting for? Go shopping.</p>");
	//						}
	//					}
	//				}
	//			},
	//			error: function() {
	//				if ($shoppingBasketContainer.children("p.error").length == 0) {
	//					$shoppingBasketContainer.children().remove();
	//					$shoppingBasketContainer.append("<p class=\"error\">An error occured while updating the shopping bag."
	//						+ " Please try refreshing the page.</p>");
	//				}
	//			},
	//			complete: function() {
	//				_resizeScroll();
	//				$shoppingBasket.triggerHandler(c_stateChanged);
	//			}
	//		});
	//	};
	//#endregion

	// Event Handlers
	//#region
	var _remove_click = function(evt) {
		evt.preventDefault();
		_removeFromBag(Number($(this).attr("class")), 1);
	};

	var _bar_mousedown = function(evt) {
		evt.preventDefault();

		var $this = $(this);
		var clientY = evt.clientY;
		var barTop = $scrollBar.position().top;

		var startingPosition = clientY - barTop;

		$this
			.data(c_down, startingPosition)
			.addClass(c_selected);

		$document
			.mousemove(_document_mousemove)
			.one("mouseup", _document_mouseup);
	};

	var _document_mouseup = function(evt) {
		$scrollBar
			.removeClass(c_selected)
			.data(c_down, false);

		$document
			.unbind("mousemove", _document_mousemove);
	};

	var _document_mousemove = function(evt) {
		var startingPosition = $scrollBar.data(c_down);
		var clientY = evt.clientY;
		var trackHeight = $scrollTrack.height();
		var barHeight = $scrollBar.height();
		var totalHeight = $shoppingBasketWrapper[0].scrollHeight;
		var visibleHeight = $shoppingBasketWrapper.height();

		var trackDiff = trackHeight - barHeight;

		if (startingPosition) {
			var position = clientY - startingPosition;
			position = position > trackDiff ? trackDiff : position < 0 ? 0 : position;

			var infoTop = ((position / trackDiff) * (totalHeight - visibleHeight));
			$shoppingBasketWrapper.scrollTop(infoTop);

			$scrollBar.css({ top: position });
		}
	};

	var _shoppingbag_mousewheel = function(evt, delta) {
		var trackHeight = $scrollTrack.height();
		var barHeight = $scrollBar.height();
		var trackDiff = trackHeight - barHeight;

		var totalHeight = $shoppingBasketWrapper[0].scrollHeight;
		var visibleHeight = $shoppingBasketWrapper.height();
		var wrapperOffset = totalHeight - visibleHeight;

		var scrollTop = $shoppingBasketWrapper.scrollTop();
		var calcTop = delta > 0 ? scrollTop - 20 : scrollTop + 20;
		var newTop = $shoppingBasketWrapper.scrollTop(calcTop).scrollTop();
		newTop = (newTop / wrapperOffset) * trackDiff;

		if (isFinite(newTop) && !isNaN(newTop))
			$scrollBar.css({ top: newTop });
	};

	//	var _locations_change = function(evt) {
	//		var id = Number($(this).val());
	//		if (!isNaN(id))
	//			_setPostageLocation(id);
	//		else $shoppingBasketContainer.find("tfoot tr:eq(0) td:eq(1)").text("---");
	//	};

	var _preventDefault = function(evt) { evt.preventDefault(); };
	//#endregion

	// Constructor
	//#region
	var _init = function() {
		$document = $(document);
		$shoppingBasket = $("#shopping_basket");
		$shoppingBasketWrapper = $("#shopping_basket_wrapper");
		$shoppingBasketContainer = $("#shopping_basket_container");

		$shoppingBasket.mousewheel(_shoppingbag_mousewheel);

		_bindTableEvents();

		$scrollTrack = $("<div id=\"shopping_basket_track\" />")
			.appendTo($shoppingBasket);

		$scrollBar = $("<div id=\"shopping_basket_bar\" />")
					.mousedown(_bar_mousedown)
					.bind("dragstart", _preventDefault)
					.bind("selectstart", _preventDefault)
					.appendTo($scrollTrack);
	};
	//#endregion

	return {
		init: function() {
			_init();
		},
		refreshBag: function() {
			_refreshBag();
		},
		addToBag: function(productId, quantity) {
			_addToBag(productId, quantity);
		},
		removeFromBag: function(productId, quantity) {
			_removeFromBag(productId, quantity);
		},
		isInBag: function(productId) {
			return _isInBag(productId);
		},
		bindProductRemoved: function(fun) {
			$("#shopping_basket").bind(c_productRemoved, fun);
		},
		unbindProductRemoved: function(fun) {
			$("#shopping_basket").unbind(c_productRemoved, fun);
		},
		bindStateChanged: function(fun) {
			$("#shopping_basket").bind(c_stateChanged, fun);
		},
		unbindStateChanged: function(fun) {
			$("#shopping_basket").unbind(c_stateChanged, fun);
		},
		resize: function(height) {
			return _resize(height);
		},
		heightDiff: function() {
			return _heightDiff();
		},
		resizeScroll: function() {
			return _resizeScroll();
		}
	};
} ();
//#endregion

// Ready
//#region
if (!$.browser.msie || $.browser.version > 6) {
	$(document).ready(function() {
		g_drag.init();
		g_prodinfo.init();
		g_drop.init();
		g_newsletter.init();
		g_registration.init();
		g_listing.init();
		g_scroll.init();
		g_shoppingbag.init();
		// Must initiate last.
		g_resize.init(g_newsletter.height(), g_registration.height());


		// Drag Events
		g_drag.bindSelect(function(evt, idx, categoryId, categoryName) {
			g_drop.hideAll();
			goTo(categoryId, categoryName);
		});

		// Product Info
		g_prodinfo.bindAddClick(function(evt, productId) {
			g_shoppingbag.addToBag(productId, 1);
			g_prodinfo.productInBag(true);
		});

		// Dropdown Boxes
		g_drop.bindSearchSubmit(function(evt, term) {
			goTo(null, null, null, term, null);
		});

		g_drop.bindFilterSelect(function(evt, categoryId, filters) {
			goTo(categoryId, $("#bread_crumb span").text(), filters);
		});

		// Newsletter Events
		g_newsletter.bindHeightChange(function(evt, height) {
			g_resize.setNewsletterHeight(height);
		});

		// Registration Events
		g_registration.bindHeightChange(function(evt, height) {
			g_resize.setRegistrationHeight(height);
		});

		// Product Listing Events
		g_listing.bindClick(function(evt, product, categoryId, term, filters) {
			goTo(categoryId, $("#bread_crumb span").text(), filters, term, product);
		});

		g_listing.bindBuildComplete(function(evt, categoryId, term, filters) {
			g_resize.resize();
		});

		g_listing.bindCatClick(function(evt, categoryId, categoryName) {
			g_drop.hideAll();
			goTo(categoryId, categoryName);
		});

		// Shopping Bag
		g_shoppingbag.bindProductRemoved(function(evt, productId) {
			if (productId == g_prodinfo.currentProductId())
				g_prodinfo.productInBag(false);
		});

		g_shoppingbag.bindStateChanged(function(evt) {
			if (g_resize.shoppingBasketDifference() > 0 && !g_newsletter.isCollapsed())
				g_newsletter.collapse();
		});

		// Resize
		g_resize.bindCollapseRequired(function(evt, diff) {
			g_newsletter.collapse();
		});

		g_resize.resize(true);
		g_shoppingbag.refreshBag();

		$.history.init(history_load);
	});
}
//#endregion

// History
//#region
var goTo = function(categoryId, categoryName, filters, term, product) {
	var hash = "";
	if (categoryId && categoryName) {
		hash += "cat::" + categoryId + ":" + categoryName;
		if (filters && filters.length > 0) {
			hash += "/fil:";
			$.each(filters, function() {
				hash += ":" + this;
			});
		}
	}
	else if (term)
		hash += "term::" + term.replace("/", "").replace(":", "");

	if (product)
		hash += "/prod::" + product.ID;

	$.history.load(urlEncode(hash), product);
};

var goToProduct = function(productId) {
	a_loadCategoryByProduct(productId, function(category) {
		$.history.load(urlEncode("cat::" + category.ID + ":" + category.Name + "/prod::" + productId));
	});
};

window.postNo = 0;
var history_load = function(hash, oldHash, product, ignoreOldHash) {
	postNo++;
	var analytics = "";

	var path = location.pathname;
	var isCatPage = false;
	var isIcPage = /\/([\w]*).ic\/([\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12})/.test(path);
	var catPage = null;

	if (postNo > 1 && path.length > 3) {
		isCatPage = path.substr(path.length - 3) == "cat";
		if (isCatPage) {
			var catMatches = path.match(/\/([\d]+).cat/);
			if (catMatches.length > 1) {
				catPage = Number(catMatches[1]);
				if (isNaN(catPage))
					isCatPage = false;
			}
			else isCatPage = false;
		}
	}

	var parseHash = function(value) {
		var obj = {
			cat: null,
			fil: null,
			term: null,
			prod: null
		};

		if (value) {
			var arr = urlDecode(value).split("/");
			$.each(arr, function() {
				var i = this.split("::");
				if (i.length == 2) {
					switch (i[0]) {
						case "fil":
							obj[i[0]] = i[1].split(":").sort(function(a, b) {
								return a - b;
							});
							break;
						case "cat":
							var cat = i[1].split(":");
							obj[i[0]] = { key: cat[0], value: cat[1] };
							break;
						default:
							obj[i[0]] = i[1];
							break;
					}
				}
			});
		}

		return obj;
	};

	var nav = parseHash(hash);
	var oldNav = ignoreOldHash ? { cat: null, fil: null, term: null, prod: null} : parseHash(oldHash);

	// Welcome Panel
	var $welcomePanel = $("#ctl00_ctl00_middleCPH_middleCPH_welcomePanel");

	if (nav.cat || nav.term || isCatPage) {
		$welcomePanel.css({ display: "none" });

		if (isIcPage)
			$("#ctl00_ctl00_middleCPH_middleCPH_icPanel").css({ display: "none" });

		// It's Category or Search, not both.
		if (nav.cat || isCatPage) {
			var callback = function() {
				if (!oldNav.cat || nav.cat.key != oldNav.cat.key || nav.fil != oldNav.fil) {
					g_listing.load(nav.cat.key, nav.fil);
					g_drag.setBreadCrumb(nav.cat.value);

					$("#nav a.selected").removeClass("selected");
					$("#nav a").each(function() {
						var $this = $(this);
						var href = $this.attr("href").match(/\/([\d]+).cat/);
						if (href.length > 1 && href[1] == nav.cat.key) {
							$this.addClass("selected");
							return false;
						}
					});

					g_drop.loadFilters(nav.cat.key, nav.fil);
				}

				analytics += "/category/" + nav.cat.value;
				if (nav.fil) {
					analytics += "/filters:";
					$.each(nav.fil, function() {
						analytics += ":" + this;
					});
				}
			};

			if (isCatPage && !nav.cat)
				a_loadCategory(catPage, function(category) {
					if (category != null) {
						nav.cat = {};
						nav.cat.key = category.ID;
						nav.cat.value = category.Name;
						callback();
					}
				});
			else callback();
		}
		else if (nav.term && nav.term != oldNav.term) {
			g_listing.search(nav.term);
			g_drop.showFilters(false);
			g_drag.setBreadCrumb(nav.term);

			$("#nav a.selected").removeClass("selected");

			analytics += "/search/" + nav.term;
		}

		if (nav.prod) {
			if (nav.prod != oldNav.prod) {
				var callback = function(product) {

					if (!g_prodinfo.isOpen()) {
						g_prodinfo.open(product);
					}
					else {
						g_prodinfo.close(function() { g_prodinfo.open(product); });
					}

					logEvent("/product/" + product.Name.replace("/", ""));
				};

				if (product)
					callback(product);
				else g_listing.loadProduct(nav.prod, callback);
			}
		}
		else {
			g_prodinfo.close();

			if (analytics != "")
				logEvent(analytics);
		}
	}
	else if (postNo > 1) {
		if (isIcPage) {
			$welcomePanel.css({ display: "none" });
			$("#products").removeClass().find("ul").children().remove();
			g_drag.setBreadCrumb("expired");
			$("#ctl00_ctl00_middleCPH_middleCPH_icPanel").css({ display: "block" }).children().remove().end().append("<p>This page has expired.</p>");
		}
		else {
			g_drag.setBreadCrumb("welcome");
			g_listing.load(null, null, function() {
				$welcomePanel.css({ display: "block" });
			});
		}
	}
};
//#endregion

// Recently Viewed
//#region
var rv_cookie = "viewedCookie";
var rv_divider = ",";
var rv_options = { path: "/" };

var rv_add = function(productId) {
	var arr = rv_loadArray();

	if ($.isArray(arr)) {
		var exists = false;

		$.each(arr, function() {
			if (productId == this) {
				exists = true;
				return false;
			}
		});

		if (!exists) {
			if (arr.length > 9)
				arr.pop();

			arr.unshift(productId);

			$.cookie(rv_cookie, arr.join(rv_divider), rv_options);
		}
	}
	else $.cookie(rv_cookie, [productId], rv_options);

	return arr;
};

var rv_loadArray = function() {
	var str = $.cookie(rv_cookie);
	var arr = new Array();

	if (str)
		arr = $.map(str.split(rv_divider), function(i) { return i != "" && !isNaN(i) ? Number(i) : null; });

	return arr;
};

var rv_load = function(productId, callback) {
	var arr = rv_add(productId);
	if (arr.length > 0) {
		a_loadProductArray(arr, function(list) {
			$.cookie(rv_cookie, $.map(arr, function(id) {
				var found = false;

				$.each(list, function() {
					if (this.ID == id) {
						found = true;
						return false;
					}
				});

				return found ? id : null;
			}).join(rv_divider), rv_options);

			if ($.isFunction(callback))
				callback(list);
		});
	}
	else callback();
};
//#endregion
//




/// <reference path="firebug.js" />
/// <reference path="jquery-1.3.2.js" />
/// <reference path="jquery.mousewheel.js" />
/// <reference path="jquery.ajaxdotnet.3.1.js" />
/// <reference path="json2.js" />
/// <reference path="jquery.color.js" />
/// <reference path="jquery.hash.1.js" />
/// <reference path="jquery.color.js" />

var isEmail = function(value) {
	return /^[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$/.test(value);
};

// Ajax
//#region
var a_invoke = function(url, useGet, data, success, error, complete) {
	$.ajaxDotNet("/Service.asmx/" + url, {
		useGet: useGet,
		data: data,
		success: function(obj) {
			if (obj && (typeof obj.d != "undefined" || obj.d == null) && $.isFunction(success))
				success(obj.d);
		},
		error: error,
		complete: complete
	});
};

var a_getCountryCodes = function(success, error) {
	a_invoke("GetCountryCodes", true, {}, success, error);
};

var a_getStateCodes = function(success, error) {
	a_invoke("GetStateCodes", true, {}, success, error);
};

var a_register = function(data, success, error) {
	a_invoke("Register", false, data, success, error);
};

var a_getUser = function(sucess, error) {
	a_invoke("GetUser", true, {}, sucess, error);
};

var a_forgotDetails = function(email, success, error) {
	a_invoke("ForgotDetails", false, { email: email }, success, error);
};

var a_logon = function(email, password, success, error) {
	a_invoke("Logon", false, { email: email, password: password }, success, error);
};

var a_logout = function(success, error) {
	a_invoke("Logout", false, {}, success, error);
};

var a_fetchAddress = function(key, success, error) {
	a_invoke("FetchAddress", true, { key: key }, success, error);
};

var a_byPostcode = function(postcode, success, error) {
	a_invoke("ByPostcode", true, { postcode: postcode }, success, error);
};

var a_getPage = function(pageType, success, error) {
	var url = "Get";
	switch (pageType) {
		case "home":
			url += "Home";
			break;
		case "terms":