// Timeouts are in milliseconds
// 10 minutes between portlet content updates (barring any indirect update
// requests resulting from the user adding a portlet or portlet subitem)
var UPDATE_TIMEOUT = 10 * 60 * 1000;
// 1.2 seconds between loading TT images in the background
var LAZY_LOAD_TIMEOUT = 1200;
var RETRY_TIMEOUT = 450;
//var INTERLEAVE_TIMEOUT = 340;
var MAX_RETRIES = 36;

function AutoUpdater(mainDiv, portletID, contentCodes, providerObject)
{
	this.mainDiv = mainDiv;
	this.portletID = portletID;
	this.contentCodes = contentCodes;
	this.providerObject = providerObject;
	this.portletDefinitions = null;

	this.setupSubItemArrays();
	this.hasSubItems = this.portletDefinitions[0].subItems.length > 0;
	// Initialise saved hashes
	if (this.portletDefinitions[0].type == portletTypes.htmlItems)
	{
		this.lastHashes = new Array();
		for (var i = 0; i < this.portletDefinitions[0].maxNumberOfItems; i++)
		{
			this.lastHashes[i] = "";
		}
	}
}

AutoUpdater.prototype.MAX_RETRIES = 36;

AutoUpdater.prototype.setupSubItemArrays = function()
{
	this.portletDefinitions = new Array();
	this.portletDefinitions[this.portletDefinitions.length] = getPortletInfo(this.contentCodes[0]);
	for (var i = 1; i < this.contentCodes.length; i++)
	{
		this.portletDefinitions[this.portletDefinitions.length] = getPortletSubItemInfo(this.contentCodes[0], this.contentCodes[i]);
	}
};

AutoUpdater.prototype.stop = function()
{
	this.providerObject.stop();
};

function stringIsInArray(string, array)
{
	if (string == null ||
		array == null)
	{
		return false;
	}

	var found = false;

	for (var i = 0; i < array.length && !found; i++)
	{
		if (array[i] == string)
		{
			found = true;
		}
	}
	return found;
}

AutoUpdater.prototype.update = function(content, contentIndex)
{
	var portletType = getPortletInfo(this.contentCodes[0]).type;

	if (portletType != portletTypes.topStory)
	{
		// Set/update the title and its link
		if (typeof content != "undefined" &&
			typeof content.portlet != "undefined")
		{
			$(this.mainDiv).find(".titleOfBlock > h4 > a").text(content.portlet.title).attr("href", content.portlet.link);
			// Track portlet update
			trackPageView("/portlets/" + content.portlet.title);
		}
	}

	switch(portletType)
	{
	case portletTypes.standard: // Fall through!
	case portletTypes.standardPlusRollOvers:
	case portletTypes.standardPlusTopImage:
		this.updateRollOverImagesWithList(content.items, contentIndex);
		break;
	case portletTypes.topStory:
		this.updateTopStory(content.items);
		break;
	case portletTypes.imageBrowse:
		this.updateTT(content.items);
		break;
	case portletTypes.htmlItems:
		this.updateHtmlItems(content.items);
		break;
	}
};

AutoUpdater.prototype.updateRollOverImagesWithList = function(items, contentIndex)
{
	if (items == null ||
		items.length == 0)
	{
		return;
	}

	if (this.hasSubItems)
	{
		// Make it 0-based for indexing lists of <div>'s and <ul>'s
		contentIndex--;
	}

	var currentListElement = null;
	var lists = this.mainDiv.getElementsByTagName("ul");
	var rolloverList = lists[this.hasSubItems ? 2 : 0];
	var itemList = lists[(this.hasSubItems ? 3 : 1) + contentIndex];

	var firstItem = 0;

	if (this.portletDefinitions[0].type == portletTypes.standardPlusTopImage)
	{
		rolloverList = null;
		itemList = lists[(this.hasSubItems ? 2 : 0) + contentIndex];
		// Did we receive an image item in the data?
		if (items[firstItem].imageLink != null)
		{
			// Unhide the parent div for the 'topImage'
			var topImageDiv = this.mainDiv.getElementsByTagName("div")[0].nextSibling.firstChild.firstChild;
			topImageDiv.style.display = "block";
			// Fill in the image source
			var image = topImageDiv.firstChild.getElementsByTagName("img")[0];
			image.src = items[firstItem].imageLink;
			firstItem++;
		}
	}

	if (rolloverList != null)
	{
		// Received data contains rollover image items?
		if (items[firstItem].imageLink != null)
		{
			// Unhide the parent div for the 'rolloverList'
			rolloverList.parentNode.style.display = "block";
			// Get the anchor of the picture
			var imageAnchor = rolloverList.parentNode.getElementsByTagName("a")[0];
			// Fill in the image sources, link references and link texts
			var images = this.mainDiv.getElementsByTagName("img");
			var imageIndex = 0;
			currentListElement = rolloverList.firstChild;
			for (var i = firstItem; i < firstItem + 3 && i < items.length; i++)
			{
				images[imageIndex].src = items[i].imageLink;
				// Set the image anchor reference to the one that belongs to the visible one's item
				if (images[imageIndex].style.display != "none")
					imageAnchor.href = items[i].link;
				imageIndex++;
				// Get the 'a' element inside the 'li' element
				var link = currentListElement.firstChild;
				link.href = items[i].link;
				// Get the text node of the 'a' element inside the 'li' element
				var textNode = currentListElement.firstChild.firstChild;
				textNode.nodeValue = items[i].title;
				currentListElement = currentListElement.nextSibling;
			}
			firstItem += 3;
		}
	}

	if (itemList != null)
	{
		var numberOfVisibleItems = draggablePortlets[this.portletID - 1]["numberOfItems"][this.hasSubItems ? contentIndex + 1 : 0];
		currentListElement = itemList.firstChild;
		// Check for the special case where we want only the first item set with a message, not a link
		if (items.length == firstItem + 1 &&
			items[firstItem].link == null)
		{
			currentListElement = itemList.firstChild;
			if (currentListElement != null)
			{
				// We also don't want the item to look like a list item (e.g. have a bullet in front of it)
				currentListElement.className = "liAsP";
				// Get the 'a' element inside the 'li' element
				var link = currentListElement.firstChild;
				// Make sure it won't work as an anchor (i.e., show a hand cursor on hover)
				if (link.removeAttribute)
				{
					link.removeAttribute("href");
				}
				else
				{
					link.href = null;
				}
				// Get the text node of the 'span' element inside the 'a' element inside the 'li' element
				var textNode = currentListElement.firstChild.firstChild.firstChild;
				textNode.nodeValue = items[firstItem].title;
				// Get the second text node of the 'a' element inside the 'li' element (skip the 'br' element)
				textNode = textNode.parentNode.nextSibling.nextSibling;
				// Clear it
				textNode.nodeValue = "";
				currentListElement = currentListElement.nextSibling;
			}
		}
		else
		{
			for (var i = firstItem; i < items.length; i++)
			{
				if (currentListElement != null)
				{
					if (i - firstItem < numberOfVisibleItems)
					{
						currentListElement.className = null;
					}
					else
					{
						currentListElement.className = "hidden";
					}
					// Get the 'a' element inside the 'li' element
					var link = currentListElement.firstChild;
					link.href = items[i].link;
					var lines = items[i].title.split("\n");
					// Get the first text node of the 'span' element inside the 'a' element inside the 'li' element
					var textNode = currentListElement.firstChild.firstChild.firstChild;
					textNode.nodeValue = lines[0];
					// Get the second text node of the 'a' element inside the 'li' element (skip the 'br' element)
					var textNode = textNode.parentNode.nextSibling.nextSibling;
					if (lines.length > 1)
					{
						textNode.nodeValue = lines[1];
					}
					else
					{
						textNode.nodeValue = "";
					}
					currentListElement = currentListElement.nextSibling;
				}
			}
		}

		// Update the (possibly changed) number of visible items (in case of sub items we need to skip position 1 for the main block)
		if (items.length < numberOfVisibleItems)
		{
			draggablePortlets[this.portletID - 1]["numberOfItems"][this.hasSubItems ? contentIndex + 1 : 0] = items.length;
			numberOfVisibleItems = items.length;
		}

		// In case there are now fewer items than the last time and - more importantly -
		// less than the number of visible items, we hide the next (and now empty) ones
		for (var i = items.length; i < this.portletDefinitions[contentIndex].maxNumberOfItems; i++)
		{
			if (currentListElement != null)
			{
				// Get the 'a' element inside the 'li' element
				var link = currentListElement.firstChild;
				link.href = null;
				// Get the text node of the 'span' element inside the 'a' element inside the 'li' element
				var textNode = currentListElement.firstChild.firstChild.firstChild;
				textNode.nodeValue = "";
				currentListElement.className = "hidden";
				currentListElement = currentListElement.nextSibling;
			}
		}

		if (this.portletDefinitions[contentIndex].changeNumber)
		{
			var refs = this.mainDiv.getElementsByTagName("a");
			var plusButton = null;
			var minusButton = null;
			if (!this.hasSubItems)
			{
				plusButton = refs[2];
				minusButton = refs[3];
			}
			else
			{
				var subItems = getElementsWithClassName(this.mainDiv.getElementsByTagName("div"), "subItem", 20);
				var subItemRefs = subItems[contentIndex].getElementsByTagName("a");
				plusButton = subItemRefs[1];
				minusButton = subItemRefs[2];
			}

			adjustButtonImages(plusButton, minusButton, numberOfVisibleItems, items.length - firstItem);
		}

		// Hide the 'spinner' image because we're ready updating
		var allBlockImages = this.mainDiv.getElementsByTagName("img");
		var spinnerImage = getElementsWithClassName(allBlockImages, "spinner", contentIndex + 1)[contentIndex];
		spinnerImage.style.display = "none";
	}
};

AutoUpdater.prototype.updateTT = function(items)
{
	if (this.imageLocations == null)
	{
		this.imageLocations = new Array();
	}
	else
	{
		this.imageLocations.length = items.length;
	}
	var imageCollection = this.mainDiv.getElementsByTagName("img");
	var spinnerImage = getElementsWithClassName(imageCollection, "spinner", 1)[0];
	var images = getElementsWithClassName(imageCollection, "", 99);

	if (images != null &&
		items != null &&
		items.length > 0)
	{
		// Load the first image
		images[0].src = items[0].link;
		for (var i = 0; i < items.length; i++)
		{
			if (i >= images.length)
			{
				// Create any missing 'img' elements
				var image = document.createElement("img");
				image.style.display = "none";
				images[0].parentNode.appendChild(image);
			}
			this.imageLocations[i] = items[i].link;
		}
		// Remove any no longer used 'img' elements
		for (var i = this.imageLocations.length; i < images.length; i++)
		{
			images[0].parentNode.removeChild(images[i]);
		}
		if (items.length > 1)
		{
			// Schedule lazy loading of the other images
			var oThis = this;
 			this.timeoutId = setTimeout(function() {
 					oThis.lazyLoadTTImages();
 				}, LAZY_LOAD_TIMEOUT);
		}
	}
	if (spinnerImage != null)
	{
		// Hide the 'spinner' in order to indicate that we are through updating
		spinnerImage.style.display = "none";
	}
};

AutoUpdater.prototype.updateTopStory = function(items)
{
	var image = this.mainDiv.getElementsByTagName("img")[0];
	var subDivs = this.mainDiv.getElementsByTagName("div");

	var titleDiv = getElementsWithClassName(subDivs, "titleOfBlock", 1)[0];
	var contentDiv = getElementsWithClassName(subDivs, "blockContent", 1)[0];
	var titleAnchor = titleDiv.getElementsByTagName("a")[0];
	var contentAnchor = contentDiv.getElementsByTagName("a")[0];

	if (items != null)
	{
		// Set the image
		image.src = items[0].imageLink;

		// Get the text node of the anchor within the 'h4' and set the title text
		var titleTextNode = titleAnchor.firstChild;
		titleTextNode.nodeValue = items[0].title;

		var bodyText = contentDiv.getElementsByTagName("p")[0];
		// Get the text node of the 'p' element
		var bodyTextNode = bodyText.firstChild;
		bodyTextNode.nodeValue = items[0].bodyText;

		// Set the link for both anchors to the same destination (full news article)
		titleAnchor.href = items[0].link;
		contentAnchor.href = items[0].link;
	}
};

AutoUpdater.prototype.updateHtmlItems = function(items)
{
	var subDivs = this.mainDiv.getElementsByTagName("div");
	var maxNumberOfItems = getPortletInfo(this.contentCodes[0]).maxNumberOfItems;
	var titleDivs = getElementsWithClassName(subDivs, "itemTitle", maxNumberOfItems);
	var contentDivs = getElementsWithClassName(subDivs, "itemContent", maxNumberOfItems);

	if (contentDivs.length > 0)
	{
		for (var i = 0; i < items.length && i < maxNumberOfItems; i++)
		{
			if (titleDivs.length > 0)
			{
				// Set the content of the text node for the header element
				var anchor = titleDivs[i].firstChild.firstChild;
				if (items[i].link != null)
				{
					anchor.href = items[i].link;
				}
				else
				{
					anchor.href = "javascript:void(0)";
				}
				// Unhide the title of this item
				titleDivs[i].className = "itemTitle";
				var textNode = anchor.firstChild;
				if (items[i].title != null)
				{
					textNode.nodeValue = items[i].title;
				}
				else
				{
					textNode.nodeValue = "";
					// Make sure it's hidden
					titleDivs[i].className += " hidden";
				}
			}

			// Set the HTML content if the hash is different from last time
			if (this.lastHashes[i] != items[i].hash)
			{
				// Save the new hash
				this.lastHashes[i] = items[i].hash;

				if (items[i].content != null)
				{
					contentDivs[i].innerHTML = items[i].content;
				}
				else
				{
					contentDivs[i].innerHTML = "";
				}
	
				// Unhide this item's content
				contentDivs[i].className = "itemContent";
				if (items[i].content == null)
				{
					contentDivs[i].className += " hidden";
				}
			}
		}

		// Clear and hide the no longer defined items
		for (var i = items.length; i < maxNumberOfItems; i++)
		{
			if (titleDivs.length > 0)
			{
				// Set the content of the text node for the header element
				var anchor = titleDivs[i].firstChild.firstChild;
				anchor.href = "javascript:void(0)";
				var textNode = anchor.firstChild;
				textNode.nodeValue = "";
				// Hide the title of this item
				titleDivs[i].className = "itemTitle hidden";
			}

			// Clear the HTML content
			contentDivs[i].innerHTML = "";

			// Hide this item's content
			contentDivs[i].className = "itemContent hidden";
		}
	}

	// Hide the 'spinner' image because we're ready updating
	var allBlockImages = this.mainDiv.getElementsByTagName("img");
	var spinnerImage = getElementsWithClassName(allBlockImages, "spinner", 1)[0];
	if (spinnerImage != null)
	{
		spinnerImage.parentNode.removeChild(spinnerImage);
	}
};

AutoUpdater.prototype.lazyLoadTTImages = function()
{
	var oThis = this;
	var images = this.mainDiv.getElementsByTagName("img");
	// Make sure we don't include the 'spinner' image
	var images = getElementsWithClassName(images, "", 99);
	var nextUnloaded = -1;

	for (var i = 0; i < this.imageLocations.length &&
		nextUnloaded == -1; i++)
	{
		// Compare only the end of the 'src' string in order to avoid any dependencies on server path etc.
		if (images[i] != null &&
			images[i].src.substring(images[i].src.length - this.imageLocations[i].length) != this.imageLocations[i])
		{
			nextUnloaded = i;
		}
	}
	// Is there at least one more image that needs to be loaded?
	if (nextUnloaded != -1)
	{
		// Set the 'src' attribute of the next 'img' to be loaded and implicitly have the browser download it
		images[nextUnloaded].src = this.imageLocations[nextUnloaded];
		// Are there still images to be loaded?
		if (nextUnloaded < images.length - 1)
		{
			// Schedule retrieval of next image
			this.timeoutId = setTimeout(function() {
					oThis.lazyLoadTTImages();
				}, LAZY_LOAD_TIMEOUT);
		}
	}
}

function UpdateProvider()
{
	this.xhr = zXmlHttp.createRequest();
	this.busy = false;
	this.retryCount = 0;
	this.timeoutID = -1;
	// Create an empty list of portlets that need to be updated
	this.portletList = new Array();
};

UpdateProvider.prototype.addBlock = function(autoUpdaterObject)
{
	this.portletList[this.portletList.length] = autoUpdaterObject;
};

UpdateProvider.prototype.removeBlock = function(autoUpdaterObject)
{
	for (var i = 0; i < this.portletList.length; i++)
	{
		if (this.portletList[i].mainDiv == autoUpdaterObject.mainDiv)
		{
			this.portletList.splice(i, 1);
		}
	}
};

UpdateProvider.prototype.stop = function()
{
	clearTimeout(this.timeoutID);
	if (this.xhr.readyState != 0)
	{
		this.xhr.abort();
	}
};

UpdateProvider.prototype.findCookieObligatory = function(obligatories, contentCode)
{
	for (var i = 0; i < obligatories.length; i++)
	{
		if (typeof obligatories[i] != "undefined" &&
			typeof obligatories[i].code != "undefined" &&
			obligatories[i].code == contentCode)
		{
			return obligatories[i];
		}
	}

	return null;
};

UpdateProvider.prototype.handleObligatories = function(obligatories)
{
	// Create a list of the obligatories in the cookie
	var cookie = getCookie(OBLIGATORIES_COOKIE_NAME);
	if (cookie == null)
	{
		cookie = "";
	}
	var cookieParts = cookie.split(";");
//alert("Num current obligatories: " + cookieParts.length);
	var currentObligatories = new Array();
	for (var i = 0; i < cookieParts.length; i++)
	{
		var current = cookieParts[i].split("=");
		if (current != null &&
			current.length == 2)
		{
			currentObligatories[i] = {
				"code": current[0],
				"hash": current[1]
			};
//alert("Obligatory " + i + " in cookie: " + currentObligatories[i].code + " = " + currentObligatories[i].hash);
		}
	}

	// Compare the current with the new list
	var changed = false;
	var added = false;
	for (var i = 0; i < obligatories.length; i++)
	{
		var cookieObligatory = this.findCookieObligatory(currentObligatories, obligatories[i].code);
		if (obligatories[i].visible)
		{
			if (cookieObligatory == null && obligatories[i].code != '')
			{
				// This obligatory portlet has to be (re-)added at the default position
				// Find the first empty slot in the portlets array
				var firstEmpty = getFirstEmptyCell(draggablePortlets);
				// Create it as the first portlet of the third column
				var numberOfItems = new Array();
				var contentCodes = new Array();
				numberOfItems[0] = getPortletInfo(obligatories[i].code).defaultNumberOfItems;
				contentCodes[0] = obligatories[i].code;
				createAPortlet(firstEmpty, 3, 0, contentCodes, numberOfItems, true);
				changed = true;
				added = true;
			}
			else if (cookieObligatory.hash != obligatories[i].hash)
			{
				// Move the existing portlet to the start position (first item in the third column)
				var columnDiv = $("#" + columnIDfixed + 3);
				if (columnDiv.length > 0)
				{
					// Find the divs in this column that represent the already present portlets
					// in order to decide where to place the new portlet
					// Just consider moveable portlets
					var columnBlocks = columnDiv.find("div[id^=" + portletIDfixed + "].pageBlock");

/*columnBlocks.each(function() {
	alert($(this).attr("id"));
});*/
					// Place the new portlet in its column
					var arrayIndex = findContentCodeInPortletsArray(obligatories[i].code);
					// Found
					if (arrayIndex != -1)
					{
						if (columnBlocks.length > 0)
						{
//alert("Inserting DIV with ID " + draggablePortlets[arrayIndex]["obj"].id + " before DIV with ID " +  columnBlocks.eq(1).get(0).id + "; from parent DIV with ID " + columnDiv.get(0).id);
							columnDiv.get(0).insertBefore(draggablePortlets[arrayIndex]["obj"], columnBlocks.eq(0).get(0));
						}
						else
						{
//alert("Appending DIV with ID " + draggablePortlets[arrayIndex]["obj"].id + " as child of parent DIV with ID " + columnDiv.get(0).id);
							columnDiv.get(0).appendChild(draggablePortlets[arrayIndex]["obj"]);
						}
					}
					// Make sure it is expanded? Check: draggablePortlets[arrayIndex]["expandedState"]
					changed = true;
				}
			}
		}
		else
		{
			if (cookieObligatory != null)
			{
				// This obligatory portlet has to be removed
				// Remove the portlet from the array and from the column
				var arrayIndex = findContentCodeInPortletsArray(obligatories[i].code);
				// Found
				if (arrayIndex != -1)
				{
					// Stop de auto updater
					updateProvider.removeBlock(draggablePortlets[arrayIndex]["itemProvider"]);

					// Remove the affected div from its column
					draggablePortlets[arrayIndex]["parentObj"].removeChild(draggablePortlets[arrayIndex]["obj"]);
					draggablePortlets[arrayIndex] = null;
					changed = true;
				}
			}
		}
	}

	if (changed)
	{
		if (useCookieToRememberPortlets)
		{
			// Write the cookie with general portlet info
			savePortletsCookie();
		}
	}

	// Write the new cookie with obligatories hashes
	var cookieString = "";
	var isFirst = true;
	for (var i = 0; i < obligatories.length; i++)
	{
		if (obligatories[i].visible)
		{
			if (isFirst)
			{
				isFirst = false;
			}
			else
			{
				cookieString += ";";
			}
			cookieString += obligatories[i].code + "=" + obligatories[i].hash;
		}
	}

	// Expire after 3000 days (a bit more than 8 years)
	setCookie(OBLIGATORIES_COOKIE_NAME, cookieString, 3000);

	if (added)
	{
		updateProvider.requestUpdate();
	}
};

/**
 * Binary search function from http://jsfromhell.com/array/search
 *
 * search(vector: Array, value: Object, [insert: Boolean = false]): Integer
 *
 * Do a binary search on an *ordered* array, if it's not ordered, the behaviour
 * is undefined. The function can return the index of the searched object as well
 * as the index where it should be inserted to keep the array ordered.
 *
 * vector	array that will be looked up
 * value	object that will be searched
 * insert	if true, the function will return the index where the value should be
 * 			inserted to keep the array ordered, otherwise returns the index where
 * 			the value was found or -1 if it wasn't found
 */
search = function(o, v, i)
{
	var h = o.length, l = -1, m;
	while (h - l > 1)
	{
		if (o[m = h + l >> 1] < v)
		{
			l = m;
		}
		else
		{
			h = m;
		}
	}
	return o[h] != v ? i ? h : -1 : h;
};

UpdateProvider.prototype.requestUpdate = function()
{
	var oThis = this;
	var oXHR = this.xhr;

	// Kill any timers still running (e.g. when requesting an immediate update while a refresh timer is still running) 
	if (this.timeoutID != -1)
	{
		clearTimeout(this.timeoutID);
	}

	if (oXHR.readyState != 0)
	{
		if (this.busy == true)
		{
			if (this.retryCount < MAX_RETRIES)
			{
				this.retryCount++;

				// Schedule a retry
				this.timeoutID = setTimeout(function() {
						oThis.requestUpdate();
					}, RETRY_TIMEOUT);
				return;
			}
			else
			{
				this.retryCount = 0;
			}
		}

		// Cancel any active requests (this is also always needed after a completed request!)
		oXHR.abort();
	}

	// Fix for garbled portlet cookies: remove all duplicate entries from the 'portletList' array
	var uniquePortletIDs = new Array();
	for (var i = 0; i < this.portletList.length; i++)
	{
		// Use the portlet type ('ID') for reference
		var portletID = this.portletList[i].contentCodes[0];
		var pos = search(uniquePortletIDs, portletID);
		if (pos == -1)
		{
			// Not in array yet, put it in its correct position to maintain sorting
			uniquePortletIDs.splice(search(uniquePortletIDs, portletID, true), 0, portletID);
		}
		else
		{
			// Remove the current (duplicate) entry
			this.portletList.splice(i, 1);
		}
	}

	// Construct the array with request info for all the portlets
	var oRequests = new Array();
	for (var i = 0; i < this.portletList.length; i++)
	{
		var startIndex = 0;
		if (this.portletList[i].hasSubItems)
		{
			startIndex = 1;
		}
		for (var j = startIndex; j < this.portletList[i].contentCodes.length; j++)
		{
			var requestRecord =
			{
				contentType: this.portletList[i].contentCodes[j],
				limit: this.portletList[i].portletDefinitions[j].maxNumberOfItems
			};
			if (this.portletList[i].hasSubItems)
			{
				requestRecord.portletType = this.portletList[i].contentCodes[0];
			}
			// Add it to the end of the array
			oRequests[oRequests.length] = requestRecord;
		}
	}

	if (oRequests.length > 0)
	{
		this.busy = true;
		this.retryCount = 0;

		// Open connection to the server
		oXHR.open("post", "ajax/updatePortletDetails.jsp", true);
		oXHR.setRequestHeader("Content-type", "text/html; charset=utf-8");
// Below header is considered 'unsafe'
//		oXHR.setRequestHeader("Accept-Charset", "iso-8859-1");
		oXHR.onreadystatechange = function()
		{
			// Request completely processed
			if (oXHR.readyState == 4)
			{
				// Firefox may throw an exception while getting XHR status
				try
				{
					var xhrStatus = oXHR.status;
				}
				catch (e)
				{
					// Ignore
				}

				// No errors
				if (xhrStatus == 200 || xhrStatus == 304)
				{
					// Evaluate the returned JSON (an object)
					var portletDetails = JSON.parse(oXHR.responseText);
					var portletContent = portletDetails.content;

					// Provide the corresponding part of the item list to the updaters
					var arrayIndex = 0;
					for (var i = 0; i < oThis.portletList.length; i++)
					{
// Work-around: stop if we didn't get all answers
if (i >= portletContent.length)
{
//alert("DEBUG: Stopping update of portlets because there are no more replies! (" + i + " instead of " + oThis.portletList.length + ")"); 
	break;
}
						var startIndex = 0;
						if (oThis.portletList[i].hasSubItems)
						{
							startIndex = 1;
						}
						for (var j = startIndex; j < oThis.portletList[i].contentCodes.length; j++)
						{
if (portletContent[arrayIndex] == undefined)
{
//	alert("Update items *undefined* for content: " + oThis.portletList[i].contentCodes[0] + ", " + oThis.portletList[i].contentCodes[j]);
}
else
{
							oThis.portletList[i].update(portletContent[arrayIndex++], j);
}
						}
					}

					oThis.handleObligatories(portletDetails.obligatories);

					oThis.busy = false;
				}
				else
				{
					oThis.busy = false;
					oThis.requestUpdate();
				}
			}
		};

		// Send the request
		oXHR.send(JSON.stringify(oRequests));
	}

	this.timeoutID = setTimeout(function() {
			oThis.requestUpdate();
		}, UPDATE_TIMEOUT);
};
