function Filtertable() {

	this.initialize = function(handle, storagePre, columnCount, useUpper,
			useCategories, isNormalFiltertable, showPictures, ndd1, ndd2, ndd3,
			ndd4) {
		this.handle = handle;
		this.storagePre = storagePre;
		this.columnCount = columnCount;
		this.useUpper = useUpper;
		this.useCategories = useCategories;
		this.isNormalFiltertable = isNormalFiltertable;
		this.showThePictures = showPictures;
		this.noDropdown1Array = new Array();
		this.noDropdown1Array[1] = ndd1;
		this.noDropdown1Array[2] = ndd2;
		this.noDropdown1Array[3] = ndd3;
		this.noDropdown1Array[4] = ndd4;

		var imageDiv = document.getElementById('filter-img-init');
		if (imageDiv) {
			var image = imageDiv.getElementsByTagName('img')[0];
			if (image) {
				this.initialImage = image.src;
			}
		}

		try {
			document.execCommand("BackgroundImageCache", false, true);
		} catch (e) {
			// This is needed to remove the Background-Image-Flicker in IE
			// during transitions
			// Will always throw an exception in Firefox
		}

		// instantiate an ajax object
		this.ajaxConnector = new AJAXConnector();
		ajaxConnector.registerDataHandler(this.replaceTable);
		ajaxConnector.setMaxRequestTime(5000);

		this.selectValues = new Array();
		this.selectValuesOld = new Array();
		for ( var i = 1; i < columnCount; i++) {
			if (this.useUpper) {
				var upperSelect = document.getElementById("upper" + i);
			}
			var lowerSelect = document.getElementById("lower" + i);
			var lowestSelect = document.getElementById("lowest" + i);

			// if the lower has only one value then reset the upper
			if (this.useUpper && (lowerSelect.length == 1)) {
				upperSelect.selectedIndex = 0;
			}

			if (this.useUpper) {
				if (upperSelect.selectedIndex == 0) {
					lowerSelect.disabled = true;
					if (lowestSelect) {
						lowestSelect.disabled = true;
					}
				}
				this.selectValues["upper" + i] = upperSelect.selectedIndex;
				this.selectValuesOld["upper" + i] = "0";
				// Now set initial images in case of preset values
				if (this.showThePictures && upperSelect.selectedIndex != 0) {
					var initialImageDiv = document
							.getElementById('filter-img-init');
					var initialImage = initialImageDiv
							.getElementsByTagName('img')[0];
					initialImage.style.display = "none";
					var imageDiv = document.getElementById('filter-img-' + i);
					var newImage = new Image();
					newImage.src = filtertableImages[upperSelect.selectedIndex][lowerSelect.selectedIndex];
					imageDiv.appendChild(newImage);
				}
			} else {
				this.selectValues["upper" + i] = this.noDropdown1Array[i];
				this.selectValuesOld["upper" + i] = "0";
			}

			// #BUG 28157: lowerSelect might be null. AVOID an exception or the
			// script terminates!
			if (lowerSelect != null) {
				this.selectValues["lower" + i] = lowerSelect.selectedIndex;
				this.selectValuesOld["lower" + i] = "0";
			}

			if (lowestSelect) {
				this.selectValues["lowest" + i] = lowestSelect.selectedIndex;
				this.selectValuesOld["lowest" + i] = "0";
			} else {
				this.selectValues["lowest" + i] = "0";
				this.selectValuesOld["lowest" + i] = "0";
			}
		}

		// save me!
		document.filtertable = this;
	}

	this.changeUpper = function(option, form, upperName) {
		this.event = upperName;

		// Remember upper values
		this.selectValuesOld[upperName] = this.selectValues[upperName];
		this.selectValues[upperName] = option;

		var lowerName = "lower" + upperName.substr("upper".length);
		var lowestName = "lowest" + upperName.substr("upper".length);

		// Remember lower values
		this.selectValuesOld[lowerName] = this.selectValues[lowerName];
		this.selectValues[lowerName] = "0";

		// Enable/Disable lower select
		if (form[upperName].selectedIndex == 0) {
			form[lowerName].disabled = true;
		} else {
			form[lowerName].disabled = false;
		}
		// Disable lowest select
		if (form[lowestName]) {
			form[lowestName].disabled = true;
		}

		// set the options for the lower dropdown
		var length = form[lowerName].length;
		for ( var i = 1; i < length; i++) {
			form[lowerName].remove(1);
		}
		if (option != 0) {
			for ( var i = 0; i < (dropdown1[option - 1]).length; i++) {
				newOption = new Option((dropdown1[option - 1][i][0]),
						(dropdown1[option - 1][i][1]), false, false);
				form[lowerName].options[form[lowerName].length] = newOption;
			}
		}
		// set the options for the lowest dropdown if available
		if (document.getElementById("lowest1")) {
			for ( var i = 0; i < form[lowestName].length; i++) {
				form[lowestName].remove(1);
			}
			// Insert new options
			var upperSelect = this.selectValues[upperName];
			if (upperSelect != 0 && option != 0) {
				var currentUpper = dropdown1[upperSelect - 1];
				if (currentUpper[option - 1]) {
					for ( var i = 2; i < (currentUpper[option - 1]).length; i++) {
						newOption = new Option(
								(currentUpper[option - 1][i][0]),
								(currentUpper[option - 1][i][1]), false, false);
						form[lowestName].options[form[lowestName].length] = newOption;
					}
				}
			}
		}

		this.sendRequest();
	}

	this.changeLower = function(option, form, lowerName) {
		this.event = lowerName;
		var curNumber = lowerName.substr("lower".length);
		var upperName = "upper" + curNumber;
		var lowestName = "lowest" + curNumber;

		// Remember lower values
		this.selectValuesOld[lowerName] = this.selectValues[lowerName];
		this.selectValues[lowerName] = option;
		this.selectValuesOld[lowestName] = this.selectValues[lowestName];
		this.selectValues[lowestName] = 0;

		// the following part is not needed if the category dropdown box
		// has been used (curNumber == 0)
		if (curNumber != 0) {

			// Enable/Disable lowest select
			if (document.getElementById("lowest1")) {
				if (form[lowerName].selectedIndex == 0) {
					form[lowestName].disabled = true;
				} else {
					form[lowestName].disabled = false;
				}
			}

			var upperSelect = this.selectValues[upperName];
			if (this.useUpper) {
				// Remember upper values
				var upperName = "upper" + lowerName.substr("lower".length);
				this.selectValuesOld[upperName] = this.selectValues[upperName];
			}

			// set the options for the lowest dropdown if available
			if (document.getElementById("lowest1")) {
				for ( var i = 0; i < form[lowestName].length; i++) {
					form[lowestName].remove(1);
				}
				// Insert new options
				if (upperSelect != 0 && option != 0) {
					var currentUpper = dropdown1[upperSelect - 1];
					for ( var i = 2; i < (currentUpper[option - 1]).length; i++) {
						newOption = new Option(
								(currentUpper[option - 1][i][0]),
								(currentUpper[option - 1][i][1]), false, false);
						form[lowestName].options[form[lowestName].length] = newOption;
					}
				}
			}
		}// end of if(curNumber == 0)

		this.sendRequest();
	}

	// NEW for 9.7
	this.changeLowest = function(option, lowestName) {
		this.event = lowestName;

		// Remember lowest values
		this.selectValuesOld[lowestName] = this.selectValues[lowestName];
		this.selectValues[lowestName] = option;

		if (this.useUpper) {
			// Remember upper values
			var upperName = "upper" + lowestName.substr("lowest".length);
			this.selectValuesOld[upperName] = this.selectValues[upperName];
		}

		this.sendRequest();
	}

	this.setImages = function() {
		if (this.initialImage) {
			var anythingSelected = false;
			for ( var i = 1; i < this.columnCount; i++) {
				var imageDiv = document.getElementById('filter-img-' + i);
				var imageIndex = document.getElementById('upper' + i).selectedIndex;
				var lowerImageIndex = document.getElementById('lower' + i).selectedIndex;
				if (imageIndex != 0) {
					anythingSelected = true;
					var imageDivTemp = document
							.getElementById('filter-img-init');
					var imageTemp = imageDivTemp.getElementsByTagName('img')[0];
				}
				if (imageDiv) {
					var images = imageDiv.getElementsByTagName('img');
					while (images.length > 1) {
						imageDiv.removeChild(images[0]);
					}

					var image = imageDiv.getElementsByTagName('img')[0];

					if (this.theColumnToChange == i) {
						if (image) {
							this.fadeOutImages.push(image);
						}
						newImage = new Image();
						if (this.event.indexOf('lower') > -1) {
							newImage.src = filtertableImages[imageIndex][lowerImageIndex];
						} else if (imageIndex > 0) {
							newImage.src = filtertableImages[imageIndex][0];
						} else {
							newImage.src = filtertableImages[imageIndex][lowerImageIndex];
						}

						newImage.style.display = "none";
						imageDiv.appendChild(newImage);

						if (imageIndex != 0) {
							this.fadeInImages.push(newImage);
						}
						if (imageTemp) {
							this.fadeOutImages.push(imageTemp);
						}
					} else {

						if (image) {
							image.src = filtertableImages[imageIndex][lowerImageIndex];

						} else {
							image = new Image();
							image.src = filtertableImages[imageIndex][lowerImageIndex];
							imageDiv.appendChild(image);
						}

						if (imageIndex == 0) {
							image.style.display = 'none';
						} else {
							image.style.display = '';
							var imageDivInit = document
									.getElementById('filter-img-init');
							var imageInit = imageDivInit
									.getElementsByTagName('img')[0];
							imageInit.style.display = 'none';
						}
					}

				}
			}
			if (!anythingSelected) {
				var imageDiv = document.getElementById('filter-img-init');
				var image = imageDiv.getElementsByTagName('img')[0];
				image.src = this.initialImage;
				this.fadeInImages.push(image);
			}
		}
	}

	this.sendRequest = function() {
		var requestURL = this.handle;
		if (this.isNormalFiltertable) {
			requestURL += ".ajax.component.filtertable.";
		} else {
			requestURL += ".ajax.component.filtertable_mc.";
		}
		requestURL += this.storagePre;
		for ( var i = 1; i < this.columnCount; i++) {
			if (this.useUpper) {
				requestURL += "." + this.selectValues["upper" + i];
				requestURL += "." + this.selectValues["lower" + i];
				requestURL += "." + this.selectValues["lowest" + i];
				requestURL += "." + this.selectValuesOld["upper" + i];
				requestURL += "." + this.selectValuesOld["lower" + i];
				requestURL += "." + this.selectValuesOld["lowest" + i];
			} else {
				requestURL += "." + this.noDropdown1Array[i];
				requestURL += "." + this.selectValues["lower" + i];
				requestURL += "." + this.selectValues["lowest" + i];
				requestURL += "." + this.noDropdown1Array[i];
				requestURL += "." + this.selectValuesOld["lower" + i];
				requestURL += "." + this.selectValuesOld["lowest" + i];
			}
		}
		if (this.useCategories) {
			var categorySelect = document.getElementById("lower0");
			requestURL += "." + categorySelect.selectedIndex;
		}
		requestURL += ".html";
		ajaxConnector.sendRequest(requestURL, "", AJAXConnector.REQUEST_GET);
	}

	this.replaceTable = function(status, response, param, type, statusmsg) {
		if (status == AJAXConnector.SUCCID_LOAD) {
			try {
				var footnotesStart = response.indexOf("|#_");
				var footnotesEnd = response.lastIndexOf("|#_") + 3;
				var footnotes = response.substring(footnotesStart, footnotesEnd);
				document.filtertable.prepareDynamicFootnotes(footnotes);
				this.changeFootnotes = true;

				var tempString1 = response.substring(0, footnotesStart);
				var tempString2 = response.substr(footnotesEnd);
				var tableString = tempString1.concat(tempString2);
				
				/* we cannot do something like jQuery("tbody").append(tableString)
				 * since the tableString itself is not necessarily valid html snippet
				 * (i.e. it contains <tr>s which are filtered out outside of a <table>,
				 * thus use the workaround with string concatenation.
				 */
				/* create new tbody element with tableString as innerHTML */
				var tempTBody = jQuery("<tbody>".concat(tableString).concat("</tbody>"));
				
				/* empty filtertable-temp tbody and append tempTBodys children */
				jQuery("#filtertable-temp tbody").empty().append(tempTBody.children());
				
				document.filtertable.fadeOutElements = new Array();
				document.filtertable.fadeInElements = new Array();

				// Find out what has changed
				if (document.filtertable.event.indexOf("upper") > -1) {
					// We have an upper event
					document.filtertable.theColumnToChange = parseInt(document.filtertable.event
							.substring("upper".length));
				} else if (document.filtertable.event.indexOf("lower") > -1) {
					// We have a lower event
					document.filtertable.theColumnToChange = parseInt(document.filtertable.event
							.substring("lower".length));
				} else {
					// We have a lowest event
					document.filtertable.theColumnToChange = parseInt(document.filtertable.event
							.substring("lowest".length));
				}

				document.filtertable.diffTables();

				document.filtertable.fadeOutImages = new Array();
				document.filtertable.fadeInImages = new Array();
				if (!document.filtertable.isNormalFiltertable
						&& document.filtertable.showThePictures) {
					document.filtertable.setImages();
				}
				
				document.filtertable.fade();

				// CR 631: Replace SLT NG Links
				if (window.msSltLinkRewriter
						&& window.msSltLinkRewriter.replaceSltLinks) {
					window.msSltLinkRewriter.replaceSltLinks();
				}
				
			} catch (exc) {

				alert(exc.message);
			}
		} else {
			alert(statusmsg);
		}
	}

	this.diffTables = function() {
		var jQueryTBodySelector = "#filtertable tbody";
		var jQueryTempTBodySelector = "#filtertable-temp tbody";
		var jQueryRowSelectorPrefix = jQueryTBodySelector + " .ms-table-row-";
		var jQueryColumnSelectorPrefix = " .ms-table-col-";

		var oldElementRows = jQuery(jQueryTBodySelector).get(0).rows;
		var newElementRows = jQuery(jQueryTempTBodySelector).get(0).rows;

		// NOTE: since we copy the new table from filtertabletemp to the "real"
		// filtertable, we cannot store the elements directly,
		// in fadeInElements... thus we store a jQuery usable selector string

		// Only change the footnotes if the leftmost column changes or number of
		// rows changes
		this.changeFootnotes = (newElementRows.length != oldElementRows.length)
				|| (this.theColumnToChange == 0);

		if ((this.theColumnToChange == 0) || (newElementRows.length != oldElementRows.length)) {
			// column 0 changed or the number of rows changed... let's fade in/out the complete tbody and all its subelements
			document.filtertable.fadeOutElements.push(jQueryTBodySelector);
			document.filtertable.fadeOutElements.push(jQueryTBodySelector + " *");
			document.filtertable.fadeInElements.push(jQueryTBodySelector);
			document.filtertable.fadeInElements.push(jQueryTBodySelector + " *");
		} else {
			for ( var rowId = 0; rowId < oldElementRows.length; rowId++) {
				// Do we have this row still in the new table??
				if (oldElementRows[rowId].cells[document.filtertable.theColumnToChange]) {
					document.filtertable.fadeOutElements.push(jQueryRowSelectorPrefix + (1 + rowId) + jQueryColumnSelectorPrefix + (1 + document.filtertable.theColumnToChange) + " :first-child");
				}
				newElementRows[rowId].cells[document.filtertable.theColumnToChange].firstChild.style.display = "none";
				document.filtertable.fadeInElements.push(jQueryRowSelectorPrefix + (1 + rowId) + jQueryColumnSelectorPrefix + (1 + document.filtertable.theColumnToChange) + " :first-child");
			}
		}

	}

	this.fade = function() {
		this.fadeOut();
	}

	this.fadeIn = function() {

		var filtertable = document.getElementById("filtertable");
		var filtertableBody = filtertable.getElementsByTagName("tbody")[0];
		var fadeInObjects = [];

		for ( var i = 0; i < this.fadeInElements.length; i++) {
			// fetch the object with jQuery, but extract the dom object (.get() of jQuery object),
			// so we can use the jQuery(Array of DOMObjects) syntax later on...
			object = jQuery(this.fadeInElements[i]).get();
			if (object && object.length > 0) {
				fadeInObjects = fadeInObjects.concat(object);
			}
		}

		for ( var i = 0; i < this.fadeInImages.length; i++) {
			object = this.fadeInImages[i];
			if (object) {
				fadeInObjects.push(object);
			}
		}

		var footnotesTag = document.getElementById("ms-footnote-dyn");
		if (footnotesTag && this.changeFootnotes) {
			object = footnotesTag;
			if (object) {
				fadeInObjects.push(object);
			}
		}
		
		jQuery(fadeInObjects).fadeIn(1000);
	}

	this.fadeOut = function() {
		var fadeOutObjects = [];

		if (!(this.fadeOutElements.length == 0 && this.fadeOutImages.length == 0)) {
			for ( var i = 0; i < this.fadeOutElements.length; i++) {
				// fetch the object with jQuery, but extract the dom object (.get() of jQuery object),
				// so we can use the jQuery(Array of DOMObjects) syntax later on...
				object = jQuery(this.fadeOutElements[i]).get();
				if (object && object.length > 0) {
					fadeOutObjects = fadeOutObjects.concat(object);
				}
			}
			for ( var i = 0; i < this.fadeOutImages.length; i++) {
				object = this.fadeOutImages[i];
				if (object) {
					fadeOutObjects.push(object);
				}
			}

			var footnotesTag = document.getElementById("ms-footnote-dyn");
			if (footnotesTag && this.changeFootnotes) {
				if (footnotesTag) {
					fadeOutObjects.push(footnotesTag);
				}
			}

			var fadeCallbackCounter = fadeOutObjects.length;
			var self = this;
			// the callback is a little complicated... jQuery calls
			// the callback for every fadOutObject, but we only want to call
			// prepareFadeIn() once... so we count down the number of
			// elements in fadeOutObjects and only call prepareFadeIn()
			// on the last one
			jQuery(fadeOutObjects).fadeOut(1000, function() {
				fadeCallbackCounter--;
				if (fadeCallbackCounter == 0) {
					self.prepareFadeIn();
				}
			});
		} else {
			// nothing to fadeout... let's fadein...
			this.prepareFadeIn();
		}
	}

	this.prepareFadeIn = function() {
		for ( var i = 0; i < this.fadeOutImages.length; i++) {
			object = this.fadeOutImages[i];
			object.style.display = "none";
		}

		var filtertable = document.getElementById("filtertable");
		var filtertableBody = filtertable.getElementsByTagName("tbody")[0];

		if (this.theColumnToChange == 0) {
			filtertableBody.style.display = "none";
		}

		var filtertableTemp = document.getElementById("filtertable-temp");
		var filtertableTable = filtertable.getElementsByTagName("table")[0];

		var filtertableTempTable = filtertableTemp
				.getElementsByTagName("table")[0];
		var filtertableTempBody = filtertableTempTable.tBodies[0]
				.cloneNode(true);
		
		filtertableTempBody.style.display = filtertableBody.style.display;
		filtertableTable.replaceChild(filtertableTempBody,
				filtertableTable.tBodies[0]);

		var footnotesTag = document.getElementById("ms-footnote-dyn");

		if (footnotesTag && this.changeFootnotes) {
			footnotesTag.style.display = "none";
			footnotesTag.innerHTML = this.dynamicFootnoteString;
		}
		this.fadeIn();
	}

	this.prepareDynamicFootnotes = function(footnotesString) {
		var footnotes = footnotesString.split("|#_");
		strFootnote = "";

		for ( var i = 0; i < footnotes.length; i++) {
			if (footnotes[i] != "") {
				strFootnote += '<p class="ms-text ms-tx3">';
				strFootnote += footnotes[i];
				strFootnote += '</p>';
			}
		}

		this.dynamicFootnoteString = strFootnote;
	}
}
