	var storeElements = new Object();
	var gCurrentStoreElement;

	function setupAddToCartButtons(elementID)
	{
		$('#products-' + elementID + ' form.add').submit
		(
			function()
			{
				var options = 
				{ 
					target:		'#cart-' + elementID,
					success:	cartUpdate
				};
				
				$(this).ajaxSubmit(options);
				return false;
			}
		);

		$('#products-' + elementID + ' input.add-to-cart').click
		(
			function()
			{
				$('#' + $(this).attr('id') + '-added').show().fadeOut(1000);
				return true;
			}
		);
	}
	
	function setupSearchAndFilter(elementID)
	{
		getCategoryChooser(elementID).change
		(
			function()
			{
				showCategory($(this).val(), $('#' + elementID + ' select.category-chooser :selected').text(), elementID);
				return false;
			}
		);
		
		$('#search-form-' + elementID).submit
		(
			function()
			{
				doSearch(getSearchBoxForElement(elementID).val(), elementID);
				return false;
			}
		);

		$('#search-display-' + elementID + ' div.all-results').show();
		$('#search-display-' + elementID + ' div.filter-results').hide();
		$('#search-display-' + elementID + ' div.search-results').hide();
	}
	
	$(document).ready
	(
		function()
		{
			for (elementID in storeElements)
			{
				var storeElement = storeElements[elementID];
				
				$('#' + elementID + ' b').each
				(
					function()
					{
						if ($(this).text().indexOf('Failed to execute script') != -1)
						{
							$(this).hide();
							$(this).siblings('br').hide();
						}
					}
				);

				setupAddToCartButtons(elementID);
				setupSearchAndFilter(elementID);

				showCategory(0, '', elementID);

				var bEmptyCart = document.location.href.indexOf('merchant_return_link') != -1;
				if ($('#cart-' + elementID))
				{
					$('#cart-' + elementID).load('/~site/Scripts_StoreElement/StoreElement.dll?CMD=CMDStoreCart&STOREID=' + storeElement.storeID + '&ELEMENTID=' + elementID + '&INITIAL=INITIAL' + (bEmptyCart ? '&EMPTY=EMPTY' : ''), {}, cartUpdateInitial);
				}
			}
		}
	);
	
	$(window).load(
		function()
		{
			for (elementID in storeElements)
			{
				getProductsForElement(elementID).each
				(
					function()
					{
						var storeElement = storeElements[elementID];
						var sku = $(this).attr('id');
						var searchableText = sku;
						searchableText += ' ' + $(this).find('h3').text();
						searchableText += ' ' + $(this).find('.product-description').text();
						searchableText += ' ' + $(this).find('.product-price').text();
						searchableText += ' ' + $(this).find('.categories').text();
						searchableText = searchableText.toLowerCase();
						storeElement.arrSearchEngineDB[sku] = searchableText;
					}
				);
			}
		}
	);

	function setCurrentElement(elementID, numItems, bCartModified)
	{
		gCurrentStoreElement = storeElements[elementID];
		if (numItems == 0)
		{
			getViewCart(elementID).text('View Cart');
			getCartDetails(elementID).slideUp();
			gCurrentStoreElement.cartShown = false;
		}
		gCurrentStoreElement.cartModified = bCartModified;
	}
	
	function cartUpdate()
	{
		var elementID = gCurrentStoreElement.elementID;

		var storeElement = storeElements[elementID];
		
		getCartDetailsShell(elementID).css('border-width', '0px');
		getCartDetails(elementID).minmax();

		if (gCurrentStoreElement.cartShown)
		{
			getViewCart(elementID).text('Hide Cart');
			getCartDetailsShell(elementID).css('border-width', '1px');
			getCartDetails(elementID).show();
		}
		else
		{
			getViewCart(elementID).text('View Cart');
			getCartDetails(elementID).hide();
			getCartDetailsShell(elementID).css('border-width', '0px');
		}

		getCartDetails(elementID).bgiframe( { top: '-1px', bottom: '-1px', left: '-1px', right: '-1px' });

		getViewCart(elementID).click
		(
			function()
			{
				if (storeElement.cartShown)
				{
					$(this).text('View Cart');
					getCartDetailsShell(elementID).css('border-width', '0px 1px 1px 1px');
					getCartDetails(elementID).slideUp(0, function() { getCartDetailsShell(elementID).css('border-width', '0px'); });
					storeElement.cartShown = false;
				}
				else
				{
					$(this).text('Hide Cart');
					getCartDetailsShell(elementID).css('border-width', '1px');
					getCartDetails(elementID).slideDown();
					storeElement.cartShown = true;
				}
				return false;
			}
		);
		
		$('#cart-' + elementID + ' a.totalitems').click
		(
			function()
			{
				if (storeElement.cartShown)
				{
					getViewCart(elementID).text('View Cart');
					getCartDetailsShell(elementID).css('border-width', '0px 1px 1px 1px');
					getCartDetails(elementID).slideUp(0, function() { getCartDetailsShell(elementID).css('border-width', '0px'); });
					storeElement.cartShown = false;
				}
				else
				{
					getViewCart(elementID).text('Hide Cart');
					getCartDetailsShell(elementID).css('border-width', '1px');
					getCartDetails(elementID).slideDown();
					storeElement.cartShown = true;
				}
				return false;
			}
		);

		$('#cart-' + elementID + ' a.hidecart').click
		(
			function()
			{
				getViewCart(elementID).text('View Cart');
				getCartDetailsShell(elementID).css('border-width', '0px 1px 1px 1px');
				getCartDetails(elementID).slideUp(0, function() { getCartDetailsShell(elementID).css('border-width', '0px'); });
				storeElement.cartShown = false;
				return false;
			}
		);

		$('#cart-' + elementID + ' a.checkoutlink').click(
			function()
			{
				$('#cart-' + elementID + ' input.checkout').trigger('click');
				return false;
			}
		);
		
		$('#cart-' + elementID + ' form.empty').submit(
			function()
			{
				$('#cart-' + elementID).load('/~site/Scripts_StoreElement/StoreElement.dll?CMD=CMDStoreCart&STOREID=' + storeElement.storeID + '&ELEMENTID=' + elementID + '&EMPTY=EMPTY', {}, cartUpdateInitial);
				return false;
			}
		);

		$('#cart-' + elementID + ' form.addone,form.removeone').submit
		(
			function()
			{
				var options = 
				{ 
					target:		'#cart-' + elementID,
					success:	cartUpdate
				};
				
				$(this).ajaxSubmit(options);
				return false;
			}
		);

		$('#cart-' + elementID + ' form.checkoutform').submit(checkPayPal);
		$('#cart-' + elementID + ' input.checkout').click(checkPayPal);
		
		$('#cart-' + elementID + ' a.expander').click
		(
			function()
			{
				var type = $(this).attr('id');
				if (type.indexOf('-ticklink') != -1)
				{
					type = type.substring(0, type.indexOf('-ticklink'));
				}
				toggleCartDescription(elementID, type);
				getCartDetails(elementID).minmax();
				return false;
			}
		);
		
		if (gCurrentStoreElement.cartModified)
		{
			var options = { 
				startColor: '#fdffd4'
			};
			$('#cart-' + elementID + ' div.cart-preview').addFade(options);
			$('#cart-' + elementID + ' tr.cart-total').addFade(options);
			gCurrentStoreElement.cartModified = false;
		}
	}

	function checkPayPal()
	{
		if ($('#cart-' + elementID + ' form.checkoutform input[@name="business"]').fieldValue() == '')
		{
			alert('PayPal has not yet been activated for this store.  If you are trying to make a purchase, please contact the store owner for assistance.');
			return false;
		}
		
		return true;
	}
	
	function toggleCartDescription(elementID, type)
	{
		var storeElement = storeElements[elementID];
		var imgUrl = $('#' + type + '-tick').attr('src');
		if (imgUrl.indexOf('right') != -1)
		{
			imgUrl = imgUrl.replace(/right/,'down');
		}
		else
		{
			imgUrl = imgUrl.replace(/down/,'right');
		}
		
		$('#' + type + '-tick').attr('src', imgUrl);
		$('#' + type + '-description').toggle();
	}
	
	function showCategory(tagID, tagName, elementID)
	{
		$('#search-display-' + elementID + ' div.search-results').hide();
		$('#products-' + elementID + ' div.filter-results').hide();
		$('#products-' + elementID + ' div.search-results').hide();
		var storeElement = storeElements[elementID];
		storeElement.firstItemToDisplay = 1;
		if (tagID == '' || tagID == '0')
		{
			getProductsForElement(elementID).hide();
			storeElement.lastSearchSelectorText = '';
			var collection;
			if (storeElement.lastSearchSelectorText == '')
			{
				collection = getProductsForElement(elementID);
			}
			else
			{
				collection = $(storeElement.lastSearchSelectorText);
			}
			storeElement.lastResults = collection;
			updateNumberResults(1, elementID);
			$('#search-display-' + elementID + ' div.filter-results').hide();
			$('#search-display-' + elementID + ' div.all-results').show();

			if (storeElement.lastResults.length == 0)
			{
				$('#products-' + elementID + ' div.filter-results').show();
			}
		}
		else
		{
			getProductsForElement(elementID).hide();
			storeElement.lastSearchSelectorText = '#products-' + elementID + ' div.tag-' + tagID, elementID;
			var collection;
			if (storeElement.lastSearchSelectorText == '')
			{
				collection = getProductsForElement(elementID);
			}
			else
			{
				collection = $(storeElement.lastSearchSelectorText);
			}
			storeElement.lastResults = collection;
			updateNumberResults(1, elementID);
			$('#search-display-' + elementID + ' div.all-results').hide();
			$('#search-display-' + elementID + ' div.filter-results strong').text(tagName);
			$('#search-display-' + elementID + ' div.filter-results').show();

			if (storeElement.lastResults.length == 0)
			{
				$('#products-' + elementID + ' div.filter-results').show();
			}
		}
		getSearchBoxForElement(elementID).val('');
	}
	
	function showProductResults(elementID)
	{
		getProductsForElement(elementID).hide()
		var storeElement = storeElements[elementID];
		storeElement.lastResults.each
		(
			function(intIndex)
			{
				if (storeElement.firstItemToDisplay <= intIndex + 1 && intIndex < ((storeElement.firstItemToDisplay - 1) / storeElement.nProductsPerPage) * storeElement.nProductsPerPage + storeElement.nProductsPerPage)
				{
					$(this).show();
				}
			}
		);
	}
	
	function updateNumberResults(firstItem, elementID)
	{
		var storeElement = storeElements[elementID];
		var numItemsTotal = storeElement.lastResults.length;
		if (firstItem <= numItemsTotal)
		{
			storeElement.firstItemToDisplay = firstItem;
		}
		var lastItemOnPage = Math.min(Math.floor((storeElement.firstItemToDisplay - 1) / storeElement.nProductsPerPage) * storeElement.nProductsPerPage + storeElement.nProductsPerPage, numItemsTotal);
		if (numItemsTotal > storeElement.nProductsPerPage)
		{
			var productsPerPageText = '(' + storeElement.firstItemToDisplay + '-' + lastItemOnPage + ') of ' + numItemsTotal;
			var prevNumberResultsText = '', nextNumberResultsText = '';
			var curPage = Math.ceil(storeElement.firstItemToDisplay / storeElement.nProductsPerPage);
			var lastPage = Math.ceil(numItemsTotal / storeElement.nProductsPerPage);
			var paginationText = '<strong class="product standard">' + curPage + '</strong>';
			var showNearestNumPages = 4;
			if (firstItem > 1)
			{
				prevNumberResultsText = '<a href="#" onclick="updateNumberResults(' + Math.max(1, firstItem - storeElement.nProductsPerPage) + ', \'' + elementID + '\'); return false;">&lt;</a> ';
				for (i = curPage - 1; i >= 1; i--)
				{
					// Check if index is outside the nearest-4-pages range of the current page (if on page 1, show 1 2 3 4 5...; if on page 10, show ... 8 9 10 11 12 ...).
					// In other words, if there are no more pages after the current page because it's the last page, then up to 4 previous pages will be shown.
					// If there are at least two pages after the current page, then up to 2 previous pages will be shown.
					// "lastPage - curPage" calculates how many pages there are after the current page, but we cap this to 2 with Math.min(2, lastPage - curPage),
					// or Math.min(Math.floor(showNearestNumPages/2), lastPage - curPage)
					// A page with index i is outside the 4 page range if the sum of the above calculation and the distance curPage - i is greater than 4
					// Lastly, verify that i != 1 because the first page should always be listed.
					if ((curPage - i + Math.min(Math.floor(showNearestNumPages/2), lastPage - curPage) > showNearestNumPages) && i != 1)
					{
						paginationText = "... " + paginationText;
						i = 2;
						continue;
					}
					paginationText = '<a href=#" onclick="updateNumberResults(' + ((i - 1) * storeElement.nProductsPerPage + 1) + ', \'' + elementID + '\'); return false;">' + i + '</a> ' + paginationText;
				}
				paginationText = '<a href="#" onclick="updateNumberResults(' + Math.max(1, firstItem - storeElement.nProductsPerPage) + ', \'' + elementID + '\'); return false;">Previous &lt;</a> ' + paginationText;
			}
			if (lastItemOnPage < numItemsTotal)
			{
				nextNumberResultsText = ' <a href="#" onclick="updateNumberResults(' + (lastItemOnPage + 1) + ', \'' + elementID + '\'); return false;">&gt;</a>';
				for (i = curPage + 1; i <= lastPage; i++)
				{
					// check if index is outside the nearest-4-pages range of the current page (see comment ~15 lines up for more info)
					if ((i - curPage + Math.min(Math.floor(showNearestNumPages/2), curPage - 1) > showNearestNumPages) && i != lastPage)
					{
						paginationText = paginationText + " ...";
						i = lastPage - 1;
						continue;
					}
					paginationText += ' <a href=#" onclick="updateNumberResults(' + ((i - 1) * storeElement.nProductsPerPage + 1) + ', \'' + elementID + '\'); return false;">' + i + '</a>';
				}
				paginationText += ' <a href="#" onclick="updateNumberResults(' + (lastItemOnPage + 1) + ', \'' + elementID + '\'); return false;">&gt; Next</a>';
			}
			if (numItemsTotal == 0)
			{
				paginationText = '';
			}

			getNumberResults(elementID).html(prevNumberResultsText + '<span id="productsPerPage">' + productsPerPageText + '</span> shown' + nextNumberResultsText);
			getPagination(elementID).html(paginationText);
		}
		else
		{
			getNumberResults(elementID).html('<span id="productsPerPage">' + numItemsTotal + ' of ' + numItemsTotal + '</span> shown');
			getPagination(elementID).html('');
		}
		showProductResults(elementID);

		// scroll store to the top of the product area, and also scroll the window to the top of the store if the store is above the current view
		$('#' + elementID + ' div.products-scrolldiv').get(0).scrollTop = 0;
		var storeTop = $('#' + elementID + ' .store').offset().top;
		if (storeTop < document.body.scrollTop)
		{
			window.scroll(document.body.scrollLeft, storeTop); // keep current left position
		}
	}
	
	function doSearch(searchTerms, elementID)
	{
		var storeElement = storeElements[elementID];
		storeElement.firstItemToDisplay = 1;
		$('#search-display-' + elementID + ' div.filter-results').hide();
		$('#search-display-' + elementID + ' div.all-results').hide();
		$('#products-' + elementID + ' div.filter-results').hide();
		$('#products-' + elementID + ' div.search-results').hide();
		var arrKeywords = searchTerms.toLowerCase().split(' ');
		
		var nCount = 0;
		var results = new Object();
		for (i = 0; i < arrKeywords.length; i++)
		{
			var keyword = arrKeywords[i];
			var searchEngineDB = storeElements[elementID].arrSearchEngineDB;
			for (var key in searchEngineDB)
			{
				if (searchEngineDB[key].indexOf(keyword) != -1)
				{
					$('#'+key).show();
					results[key] = $('#'+key+':first');
					nCount++;
				}
			}
		}
		
		var strippedResults = new Array();
		for (key in results)
		{
			strippedResults[strippedResults.length] = results[key];
		}
		
		storeElement.lastResults = $(strippedResults);
		updateNumberResults(1, elementID);
		
		$('#search-display-' + elementID + ' div.search-results strong, #products-' + elementID + ' div.search-results strong').text(searchTerms);
		$('#search-display-' + elementID + ' div.search-results').show();
		if (storeElement.lastResults.length == 0)
		{
			$('#products-' + elementID + ' div.search-results').show();
		}
		getCategoryChooser(elementID).selectOptions('');
	}
	
	function cartUpdateInitial()
	{
		if (gCurrentStoreElement)
		{
			var elementID = gCurrentStoreElement.elementID;
			var storeElement = storeElements[elementID];
			getCartDetails(elementID).minmax();
			getCartDetails(elementID).hide();
			storeElement.cartShown = false;
			cartUpdate();
		}
	}
	
	function getProductsForElement(elementID)
	{
		return $('#products-' + elementID + ' div.item');
	}
	
	function getSearchBoxForElement(elementID)
	{
		return $('#search-query-' + elementID);
	}
	
	function getCategoryChooser(elementID)
	{
		return $('#category-chooser-' + elementID + ' select.category-chooser');
	}
	
		
	function getCartDetails(elementID)
	{
		return $('#cart-' + elementID + ' div.cart-detail');
	}
	
	function getCartDetailsShell(elementID)
	{
		return $('#cart-' + elementID + ' div.cart-detail-shell');
	}
	
	function getNumberResults(elementID)
	{
		return $('#search-display-' + elementID + ' div.number-results');
	}
	
	function getProductsPerPage(elementID)
	{
		return $('#search-display-' + elementID + ' #productsPerPage');
	}
	
	function getPagination(elementID)
	{
		return $('#products-' + elementID + ' div.pagination');
	}
	
	function getViewCart(elementID)
	{
		return $('#cart-' + elementID + ' a.viewcart');
	}
	
	// Code taken from http://www.codylindley.com/blogstuff/js/jquery/
	function easeInOut(minValue,maxValue,totalSteps,actualStep,powr)
	{
		var delta = maxValue - minValue;
		var stepp = minValue+(Math.pow(((1 / totalSteps)*actualStep),powr)*delta);
		return Math.ceil(stepp)
	}

	// Code taken and modified from http://www.codylindley.com/blogstuff/js/jquery/
	jQuery.fn.addFade = function(settings)
	{
		var elem = this;
		
		settings = jQuery.extend(
			{
				startColor: '#fdffd4',
				seconds: 1,
				steps: 50
			}, settings);
		
		return elem.each(
			function()
			{
				var domElem = this;
				var endColor = $(domElem).css('background-color');

				var endRGB;
				if (endColor.indexOf('transparent') != -1)
				{
					endRGB = [255,255,255];
				}
				else if (endColor.indexOf('rgb') == -1)
				{
					endRGB = HexToRGB(endColor);
				}
				else
				{
					endRGB = HexToRGB(RGBToHex(endColor));
				}
				
				var startRGB;
				if (settings.startColor.indexOf('transparent') != -1)
				{
					startRGB = [255,255,255];
				}
				else if (settings.startColor.indexOf('rgb') == -1)
				{
					startRGB = HexToRGB(settings.startColor);
				}
				else
				{
					startRGB = HexToRGB(RGBToHex(settings.startColor));
				}
				
				if (domElem.bgFadeInt)
				{
					window.clearInterval(domElem.bgFadeInt);
				}
				
				var msIntervals = Math.ceil(settings.seconds * 1000 / settings.steps);

				var actStep = 0;
				domElem.bgFadeInt = window.setInterval(
					function()
					{
						domElem.style.backgroundColor = "rgb(" +
							easeInOut(startRGB[0], endRGB[0], settings.steps, actStep, 4) + "," +
							easeInOut(startRGB[1], endRGB[1], settings.steps, actStep, 4) + "," +
							easeInOut(startRGB[2], endRGB[2], settings.steps, actStep, 4) + ")";
						actStep++;
						if (actStep > settings.steps)
						{
							domElem.style.backgroundColor = endColor;
							window.clearInterval(domElem.bgFadeInt);
						}
					}
				,msIntervals);
			}
		);
	}
	
	function RGBToHex()
	{
	    var colors = (arguments.length == 1) ? arguments[0].replace(/rgb\(/, '').replace(/\)/, '').split(",") : arguments;
	    return (colorToHex(colors[0]) + colorToHex(colors[1]) + colorToHex(colors[2]));
	}

	function colorToHex(N)
	{
	    N = parseInt(N); 
	    if (N == 0 || isNaN(N)) return "00";
	    N = Math.round(Math.min(Math.max(0,N),255));
	    return "0123456789ABCDEF".charAt((N - N % 16)/16) + "0123456789ABCDEF".charAt(N % 16);  // Rather clever, by Peter Bromberg
	} 
	
	function HexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
	function HexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
	function HexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
	function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}

	function HexToRGB()
	{
		var rgb = new Object();
		rgb[0] = HexToR(arguments[0]);
		rgb[1] = HexToG(arguments[0]);
		rgb[2] = HexToB(arguments[0]);
		
		return rgb;
	}
