
// ***************************************
// DataGrid Client Javascript Include File
// ***************************************

// initialize the DataGrid with the given id
function DGInit(id) {

	// get DataGrid html elements
	var DG = document.getElementById(id);
	var FF = document.getElementById(id + ":FF");
	var HD = document.getElementById(id + ":HD");
	var HR = document.getElementById(id + ":HR");
	var ID = document.getElementById(id + ":ID");
	var IT = document.getElementById(id + ":IT");
	var LI = document.getElementById(id + ":LI");

	// declare client only properties
	DG.FilterNumber = 0;
	DG.FilterGroup = 0;

	// load server side DataGrid properties into client side properties
	DG.ScrollEnabled = eval(id + "ScrollEnabled");
	DG.ChooseItemLabelCssClass = eval(id + "ChooseItemLabelCssClass");
	DG.FilterVisibleText = eval(id + "FilterVisibleText");
	DG.FilterHiddenText = eval(id + "FilterHiddenText");
	DG.FilterFieldWidth = eval(id + "FilterFieldWidth");
	DG.FilterBetweenFieldWidth = eval(id + "FilterBetweenFieldWidth");
	DG.FilterOperatorFieldWidth = eval(id + "FilterOperatorFieldWidth");
	DG.FilterFieldCssClass = eval(id + "FilterFieldCssClass");
	DG.FilterDivCssClass = eval(id + "FilterDivCssClass");
	DG.FilterAddButtonCssClass = eval(id + "FilterAddButtonCssClass");
	DG.FilterAddButtonOverCssClass = eval(id + "FilterAddButtonOverCssClass");
	DG.FilterAddGroupButtonCssClass = eval(id + "FilterAddGroupButtonCssClass");
	DG.FilterAddGroupButtonOverCssClass = eval(id + "FilterAddGroupButtonOverCssClass");
	DG.FilterDeleteButtonCssClass = eval(id + "FilterDeleteButtonCssClass");
	DG.FilterDeleteButtonOverCssClass = eval(id + "FilterDeleteButtonOverCssClass");
	DG.FilterLogicButtonCssClass = eval(id + "FilterLogicButtonCssClass");
	DG.FilterLogicButtonOverCssClass = eval(id + "FilterLogicButtonOverCssClass");
	DG.FilterGroupGlobalCssClass = eval(id + "FilterGroupGlobalCssClass");
	DG.FilterGroupCssClasses = eval(id + "FilterGroupCssClasses").split(",");
    DG.AutoSuggestListCssClass = eval(id + "AutoSuggestListCssClass");
    DG.AutoSuggestListItemCssClass = eval(id + "AutoSuggestListItemCssClass");
    DG.AutoSuggestListItemHoverCssClass = eval(id + "AutoSuggestListItemHoverCssClass");
	DG.ItemSelectMode = eval(id + "ItemSelectMode");
	DG.ItemDblClickMode = eval(id + "ItemDblClickMode");
	DG.ItemDblClickClientFunction = eval(id + "ItemDblClickClientFunction");
	DG.SelectedItemCssClass = eval(id + "SelectedItemCssClass");
	DG.HoverItemCssClass = eval(id + "HoverItemCssClass");

	// load server side DataGrid columns into client side array
	DG.Columns = new Array();
	var columns = eval(id + "Columns");
	var column, length = columns.length;
	for (var i = 0; i < length; i++) {
		column = columns[i].split("|");
		DG.Columns[i] = new DGColumn(column[0], column[1], column[2], column[3], column[4], column[5]);
	}

	// load server side DataGrid filters into client side array
	DG.Filters = new Array();
	var filters = eval(id + "Filters");
	var filter, length = filters.length;
	for (var i = 0; i < length; i++) {
		filter = filters[i].split("|");
		DG.Filters[DG.FilterNumber] = new DGFilter(DG.FilterNumber++, filter[0], filter[1], filter[2], filter[3], filter[4], filter[5], filter[6]);
		DG.FilterGroup = filter[0];
	}

	// if scrolling is enabled
	if (DG.ScrollEnabled) {
	
		// if the DataGrid's width or height is not set or is relative (a percentage)
		if (!DG.style.width || DG.style.width.indexOf("%") > -1 || !DG.style.height || DG.style.height.indexOf("%") > -1) {

			// set window onresize event to call DGResize
			window.onresize = function() {DGResize(id);};
			
			// if the DataGrid is to fill the entire page (width/height 100%)
			if ((!DG.style.width || DG.style.width == "100%") && (!DG.style.height || DG.style.height == "100%")) {

				// add fillPage property to remember this
				// DataGrid's original width/height was 100%
				DG.fillPage = true;

				// disable scrollbars on the document body
				document.body.style.overflow = "hidden";

				// remove extra space from bottom of page (gecko)
				document.forms[0].style.marginBottom = "0px";
			}
		}
		
		// if the header and items are enabled
		// (header div and item div exist)
		if (HD && ID) {

			// if no item rows, hide header div
			if (IT.rows.length == 0)
				HD.style.display = "none";
			
			// iterate through header cell divs, set style to hide overflow
			// (this assumes the div is the first node within each cell which
			//  in gecko requires no whitespace between the <td> and <div> tags)
			var length = HR.cells.length;
			for (var i = 0; i < length; i++)
				HR.cells[i].childNodes[0].style.overflow = "hidden";

			// set item div onscroll event to call DGScroll
			ID.onscroll = function() {DGScroll(id);};

			// if gecko, set item div and header div onmouseup events to call DGScroll,
			// if the user drag-selects header/item text, this will re-sync the header/item columns
			if (!document.all) {
				ID.onmouseup = ID.onscroll;
				HD.onmouseup = ID.onscroll;
			}
		}
	}

	// if items are enabled (item table exists)
	if (IT) {

		// iterate through item rows in the item table, adding properties and event handlers to each,
		// if scrolling is enabled or header is disabled, start at row 0, else header is row 0 so start at row 1
		var rowsLength = IT.rows.length;
		for (var i = DG.ScrollEnabled || !HR ? 0 : 1; i < rowsLength; i++) {
			var IR = IT.rows[i];

			// add classNameDefault property which remembers this item row's original style
			// (keep in mind this could be a special style applied by a DataGridRule)
			IR.classNameDefault = IR.className;

			// if either ItemSelectMode or ItemDblClickMode is not "Off" and DataGrid has a HoverItemCssClass
			// set this item row's onmouseover and onmouseout events to create a hover effect
			if ((DG.ItemSelectMode != 0 || DG.ItemDblClickMode != 0) && DG.HoverItemCssClass) {

				// set onmouseover to changes the item row class to HoverItemCssClass
				IR.onmouseover = function() {this.className = DG.HoverItemCssClass;};

				// if ItemSelectMode is not "Off" and DataGrid has a SelectedItemCssClass, set onmouseout
				// to check if the item row is selected and should be changed back to the SelectedItemCssClass,
				// else onmouseout changes the item row css class to back to the default css class for that row
				if (DG.ItemSelectMode != 0 && DG.SelectedItemCssClass)
					IR.onmouseout = function() {this.className = this.selected ? DG.SelectedItemCssClass : this.classNameDefault;};
				else
					IR.onmouseout = function() {this.className = this.classNameDefault;};
			}
			
			// if ItemSelectMode is not off, set this item row's onclick event
			if (DG.ItemSelectMode != 0)
				IR.onclick = function() {DGIRClick(id, this);};

			// if ItemDblClickMode is "Client" and DataGrid has an ItemDblClickClientFunction,
			// set this item row's ondblclick event to client side handler
			if (DG.ItemDblClickMode == 1 && DG.ItemDblClickClientFunction) {

				// IE fires onclick once for a double-click, whereas Gecko browsers
				// fire onclick twice for a double-click.  Force IE to fire onclick
				// again (in order to undo the select made on the first click.)
				if (document.all)
					eval("IR.ondblclick = function() {" + DG.ItemDblClickClientFunction + "(this.getAttribute('datakeys')); this.click();};");
				else 
					eval("IR.ondblclick = function() {" + DG.ItemDblClickClientFunction + "(this.getAttribute('datakeys'));};");

			// else if ItemDblClickMode is "Server", 
			// set this item row's ondblclick event to server side handler
			} else if (DG.ItemDblClickMode == 2) {
				IR.ondblclick = function() {__doPostBack(id, "IDC;" + this.getAttribute("datakeys"));};
			}
		}
	}

	// if filtering is enabled (filter content cell exists), initialize filter content,
	// insert trailing filter div with a filter add or filter add group button,
	// insert a filter div for each filter in DG.Filter or an initial blank filter,
	if (FF) DGFFInit(id, FF);

	// if ItemSelectMode is not off, iterate through this DataGrid's checkboxes setting
	// the onclick event to short circuit (reverse selection) when a checkbox is clicked
	// on directly, since DGIRClick is going to fire and handle checking and unchecking
	if (DG.ItemSelectMode != 0) {
		var CB = document.getElementsByName(id + ":CB");
		var length = CB.length;
		for (var i = 0; i < length; i++)
			CB[i].onclick = function() {this.checked = !this.checked};
	}

	// if scrolling is enabled, call DGResize
	// (instead of attaching it to window.onload)
	if (DG.ScrollEnabled)
		DGResize(id);
		
	// if the load img was found, add
	// form.onsubmit handler to call LI.Show
	if (LI) {

		// call to show and position the load img,
		// subtract 12 pixels from width for scrollbar
		LI.Show = function() {
			LI.style.display = "";
			LI.style.left = ((document.body.offsetWidth - LI.offsetWidth - 12) / 2) + "px";
			LI.style.top = ((document.body.offsetHeight - LI.offsetHeight) / 2.4) + "px";
		}

		// can't use attachEvent/addEventListener because form.submit() is called
		// in javascript which short circuit's events attached in that way,
		// if onsubmit is already defined in the form tag or by other javascript,
		// that definition will be overriden, this is problem
		document.forms[0].onsubmit = function() {window.setTimeout(LI.Show, 500)};
	}
}

// if scrolling is enabled, and the space available to the DataGrid changes,
// this function resizes the scrollable item div and syncs the header div
function DGResize(id) {

	// get DataGrid html elements
	var DG = document.getElementById(id);
	var TT = document.getElementById(id + ":TT");
	var FT = document.getElementById(id + ":FT");
	var PT0 = document.getElementById(id + ":PT:0");
	var PT1 = document.getElementById(id + ":PT:1");
	var HD = document.getElementById(id + ":HD");
	var HT = document.getElementById(id + ":HT");
	var HR = document.getElementById(id + ":HR");
	var ID = document.getElementById(id + ":ID");
	var IT = document.getElementById(id + ":IT");
	var IR = IT.rows[0];
	var bodyClientWidth = document.body.clientWidth;
	var bodyClientHeight = document.body.clientHeight;
	var offsetHeightTotal = 0;
	var length = 0;
	var i = 0;

	// if the DataGrid is to fill the entire page, set outer div
	// width/height to the body clientWidth/Height, and re-set overflow to hidden
	// (fillPage property created in DGInit if DataGrid's original width/height was 100%)
	// (user specified css class and/or styles are not allowed on the outer div because margins and
	//  padding throw off the fit of the elements contained, and borders cause it to exceed the page
	//	size when fitting it to the fill the entire page, re-setting the width/height subtracting
	//	the extra width/height added by the border worked but was too slow in gecko)
	if (DG.fillPage)
		DG.style.cssText = "overflow:hidden; width:" + bodyClientWidth + "px; height:" + bodyClientHeight + "px";

	// if the DataGrid item table does not have
	// at least one item row, exit the function
	if (!IR) return;

	// calculate the total offsetHeight
	// the DataGrid is using excluding items
	if (TT) offsetHeightTotal += TT.offsetHeight;
	if (FT) offsetHeightTotal += FT.offsetHeight;
	if (PT0) offsetHeightTotal += PT0.offsetHeight;
	if (HD) offsetHeightTotal += HD.offsetHeight;
	if (PT1) offsetHeightTotal += PT1.offsetHeight;

	// set item div overflow to scroll,
	// set item div width to the outer div clientWidth,
	// set item div height to the outer div clientHeight minus the
	// height of other elements in the document, but no smaller than 0
	// (must be set here so that if the browser adds a vertical scrollbar to the item div,
	//  the automatically resized item cell widths will be applied to the header cells)
	ID.style.cssText = "overflow:scroll; width:" + DG.clientWidth + "px; height:" + Math.max(0, DG.clientHeight - offsetHeightTotal) + "px";

	// if header is enabled
	// (header div exists)
	if (HD) {

		// set header div width to the outer div clientWidth
		// (matching the header div width with the item div width keeps them
		//  visually the same width and allows them to correctly scroll together)
		HD.style.width = DG.clientWidth + "px";
		
		// loop through the first item row's cells, skipping the checkbox cell if present,
		// setting the corresponding header cell div to 0 width so scrollWidth returns the width
		// of the header cell div's content, and using that scrollWidth to set the item cell img width
		// (the item cell now has a minimum width equal the width of the content in the header cell) 
		// (this assumes the div is the first node within the header cell and the img is the first
		//  node within the item cell which in gecko requires no whitespace between the tags)
		length = IR.cells.length;
		for (i = DG.ItemSelectMode ? 1 : 0; i < length; i++) {
			var HCD = HR.cells[i].childNodes[0];
			if (!HCD.defaultWidth) {
				HCD.style.width = "0px";
				HCD.defaultWidth = HCD.scrollWidth;
			}
			IR.cells[i].childNodes[0].style.width = HCD.defaultWidth;
		}

		// loop through the first item row's cells in reverse order starting with the 2nd
		// to last column (reverse order results in more accurate column widths in IE)
		length = IR.cells.length;
		for (i = length - 1; i > -1; i--) {

			// get the cell at this index within the first item row
			// get the cell at this index within the header row
			// get the div within the cell at this index within the header row
			// (this assumes the div is the first node within the cell which
			//  in gecko requires no whitespace between the <td> and <div> tags)
			var IC = IR.cells[i];
			var HC = HR.cells[i];
			var HCD = HC.childNodes[0];

			// set header cell width to the item cell offsetWidth
			// set header cell div width to the item cell offsetWidth
			HC.style.width = IC.offsetWidth + "px";
			HCD.style.width = IC.offsetWidth + "px";

			// if an adjustWidth has not already been calculated and ItemSelectMode is "Off" (no header checkbox cell)
			// or this isn't the first cell (the header checkbox cell), remember the difference in pixels between the
			// header cell offsetWidth and item cell offSetWidth to apply to the header cell above vertical scrollbar
			if (!DG.adjustWidth && DG.ItemSelectMode == 0 || i > 0)
				DG.adjustWidth = HC.offsetWidth - IC.offsetWidth;

			// if the header cell offsetWidth does not match the item cell offSetWidth,
			// the header cell has padding and/or borders that are increasing its total width
			// (style.width is the width within the padding and borders), re-set the header cell
			// and header cell div widths subtracting the extra width added by padding and/or borders
			if (HC.offsetWidth != IC.offsetWidth) {
				HC.style.width = (IC.offsetWidth - (HC.offsetWidth - IC.offsetWidth)) + "px";
				HCD.style.width = (IC.offsetWidth - (HC.offsetWidth - IC.offsetWidth)) + "px";
			}
		}

		// header cell above vertical scrollbar
		var HC = HR.cells[length];
		var HCD = HC.childNodes[0];
		HC.style.width = ((ID.offsetWidth - ID.clientWidth) - DG.adjustWidth) + "px";
		HCD.style.width = ((ID.offsetWidth - ID.clientWidth) - DG.adjustWidth) + "px";

		// re-calculate the total offsetHeight
		// the DataGrid is using excluding items
		// (at this point the column headers or filter fields may have
		//  wrapped causing the initial item div height to be too large)
		offsetHeightTotal = 0;
		if (TT) offsetHeightTotal += TT.offsetHeight;
		if (FT) offsetHeightTotal += FT.offsetHeight;
		if (PT0) offsetHeightTotal += PT0.offsetHeight;
		if (HD) offsetHeightTotal += HD.offsetHeight;
		if (PT1) offsetHeightTotal += PT1.offsetHeight;
	}

	// re-set item div height to the outer div clientHeight minus the
	// height of other elements in the document, but no smaller than 0
	ID.style.height = Math.max(0, DG.clientHeight - offsetHeightTotal) + "px";
}

// when scrolling is enabled and item div is scrolled
// this function scrolls the header div to match
function DGScroll(id) {
	document.getElementById(id + ":HD").scrollLeft = document.getElementById(id + ":ID").scrollLeft;
}

// when ItemSelectMode = "Multiple" and the select/deselect all
// checkbox is clicked, and when ItemSelectMode = "Single"
// and a row is selected, this function is called, and when 
function DGIRClickAll(id, checked) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's checkboxes
	var CB = document.getElementsByName(id + ":CB");
	
	// iterate through this DataGrid's checkboxes
	var length = CB.length;
	for (var i = 0; i < length; i++) {
		if (CB[i].checked != checked) {
			var IR = document.getElementById(id + ":IR:" + CB[i].id.substr(CB[i].id.lastIndexOf(":") + 1));
            CB[i].checked = checked;
            IR.selected = checked;
            if (DG.SelectedItemCssClass)
                IR.className = checked ? DG.SelectedItemCssClass : IR.classNameDefault; 
		} 
	} 
}

// when ItemSelectMode is not off "Off" and an
// item row is clicked, this function is called
function DGIRClick(id, IR) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this item row's checkbox
	var CB = document.getElementById(id + ":CB:" + IR.id.substr(IR.id.lastIndexOf(":") + 1));

    // if this item row's checkbox is checked (being unchecked), uncheck it, unselect it,
    // and change the item row css class to back to the default css class for that row
	if (CB.checked) {
		CB.checked = false;
		IR.selected = false;
        if (DG.SelectedItemCssClass)
    		IR.className = IR.classNameDefault;
	} else {

		// if ItemSelectMode is "Single", uncheck/unselect all rows
		if (DG.ItemSelectMode == 1)
			DGIRClickAll(id, false);

		// check this item row's checkbox, select this item row,
		// and change it's css class to the SelectedItemCssClass 
		CB.checked = true;
		IR.selected = true;
        if (DG.SelectedItemCssClass)
    		IR.className = DG.SelectedItemCssClass;
	} 
}

// when the OK button is enabled and the DataGrid has an OkClickClientFunction
// or the DataGrid has one or more DataGridButtons with a ClientFunction defined,
// this function concatenates the datakey values of selected rows into a string
// and passes that string to the user specified ClientFunction
function DGOKClick(id, clientFunction) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's checkboxes
	var CB = document.getElementsByName(id + ":CB");
	
	// iterate through this DataGrid's checkboxes, appending
	// the values of those checked onto a datakeys string,
	// finally stripping the trailing comma
	var s = ""
	var length = CB.length;
	for (var i = 0; i < length; i++)
		if(CB[i].checked)
			s = s + CB[i].value + ",";
	if(s.length > 0)
		s = s.substr(0, s.length - 1); 
	
	// call user specified ClientFunction
	eval(clientFunction + "(s);");
}

// when the filter is enabled and the filter visibility button
// (Show/Hide Filter) is clicked, this function is called
function DGFVClick(id, FVButton) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get the filter visibility hidden input field and filter html table
	var FV = document.getElementById(id + ":FV");
	var FT = document.getElementById(id + ":FT");

	// if the filter is hidden, show it, else, hide it
	if (FT.style.display == "none") {
		FV.value = "true";
		FT.style.display = "";
		FVButton.value = DG.FilterVisibleText;
	} else {
		FV.value = "false";
		FT.style.display = "none";
		FVButton.value = DG.FilterHiddenText;
	}

	// if scrolling is enabled, call DGResize
	if (DG.ScrollEnabled)
		DGResize(id);
}

// when the column chooser is enabled, this function
// is called when the choose button is clicked
function DGChooseClick(id, ChooseButton) {

	// get this DataGrid's choose div
	var CD = document.getElementById(id + ":CD");
	
	// the first time the choose button is
	// clicked, initialize the choose list
	if (!CD.Initialized)
		DGChooseInit(id, CD);

	// remember choose div is being shown
	// (to short circuit document.onclick from hiding it)
	CD.SuppressHide = true;

	// show and position the choose div
	var top = GetDocumentOffsetTop(ChooseButton) - 5;
	var left = GetDocumentOffsetLeft(ChooseButton);
	CD.style.display = "";
	var left_max = document.body.offsetWidth - CD.offsetWidth - 20;
	if (left > left_max)
		left = left_max; 
	CD.style.left = left + "px";
	CD.style.top = top + "px"; 
}

// called the first time the choose button is clicked, for each
// column, a checkbox and label is inserted into the choose list div
function DGChooseInit(id, CD) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's choose list div
	var CL = document.getElementById(id + ":CL");
	
	// build choose list html with array and push method
	// (this is faster than concatenating with +)
	var string = new Array();

	// insert an item into the choose list
	// for each visible DataGrid column
	var length = DG.Columns.length;
	for (var i = 0; i < length; i++) {

		// choose list checkbox
		// <DataGrid.ClientID>:CC:<DataGridColumnIndex>
		string.push('<input id="');
		string.push(id);
		string.push(':CC:');
		string.push(i);
		string.push('" name="');
		string.push(id);
		string.push(':CC" value="');
		string.push(DG.Columns[i].Name);
		string.push('" type="checkbox"');
		if (DG.Columns[i].Visible)
			string.push(' checked');
		string.push('><label for="');
		string.push(id);
		string.push(':CC:');
		string.push(i);
		string.push('"');
		if (DG.ChooseItemLabelCssClass) {
			string.push(' class="');
			string.push(DG.ChooseItemLabelCssClass);
			string.push('"');
		}
		string.push('>');
		string.push(DG.Columns[i].Label);
		string.push('</label><br>');
	}

	// insert choose list html into chose list div
	CL.innerHTML = string.join("");

	// if choose list height is greater than 60% of the page height,
	// set choose list height to 60% of page height (choose div
	// must be visible for CL.offsetHeight to not equal 0)
	CD.style.display = "";
	if (CL.offsetHeight > Math.floor(document.body.offsetHeight * 0.6))
		CL.style.height = Math.floor(document.body.offsetHeight * 0.6);

	// call to hide the choose div
	CD.Hide = function() {

		// if choose div is being shown, short circuit
		// this hide triggered by document.onclick,
		// else, hide the choose div
		if (CD.SuppressHide)
			CD.SuppressHide = false;
		else
			CD.style.display = "none";
	};
	
	// add document.onclick handler to call CD.Hide
	if (document.attachEvent)
		document.attachEvent("onclick", CD.Hide);
	else if (document.addEventListener)
		document.addEventListener("click", CD.Hide, false);

	// add window.onresize handler to call CD.Hide
	if (window.attachEvent)
		window.attachEvent("onresize", CD.Hide);
	else if (window.addEventListener)
		window.addEventListener("resize", CD.Hide, false);

	// call to prevent CD.Hide from hiding the
	// choose div when the div itself is clicked
	CD.Click = function() {CD.SuppressHide = true;};

	// add CD.onclick handler to call CD.Click
	if (CD.attachEvent)
		CD.attachEvent("onclick", CD.Click);
	else if (CD.addEventListener)
		CD.addEventListener("click", CD.Click, false);

	// remember choose has been initialized
	CD.Initialized = true;
}

// DGColumn in DG.Columns array
function DGColumn(name, type, label, visible, filterEnabled, autoSuggestUrl) {
	this.Name = name;
	this.Type = type;
	this.Label = label;
	this.Visible = visible;
	this.FilterEnabled = filterEnabled;
	this.AutoSuggestUrl = autoSuggestUrl;
}

// DGFilter in DG.Filters array
function DGFilter(number, group, columnName, columnType, logic, operator, value1, value2) {
	this.Number = number;
	this.Group = group;
	this.ColumnName = columnName? columnName: "";
	this.ColumnType = columnType? columnType: "0";
	this.Logic = logic? logic: "0";
	this.Operator = operator? operator: "0";
	this.Value1 = value1? value1: "";
	this.Value2 = value2? value2: "";
}

// initialize the filter area (filter form fields cell),
// insert trailing filter div with a filter add or filter add group button,
// insert a filter div for each filter in DG.Filter or an initial blank filter,
// called when the DataGrid is initialized and when the filter mode is changed
function DGFFInit(id, FF) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's filter mode hidden input field
	var FM = document.getElementById(id + ":FM");

	// build trailing filter div html with array and push method
	// (this is faster than concatenating with +)
	var string = new Array();

	// trailing filter div start
	string.push('<div');
	if (DG.FilterDivCssClass) {
		string.push(' class="');
		string.push(DG.FilterDivCssClass);
		string.push('"');
	}
	string.push(' style="float:left; white-space:nowrap');
	// CORY: THIS IS A HACK
	// vertically aligning the trailing filter div with the other filter divs is a problem,
	// this code statically adjusts the padding around the trailing filter div
	// to force alignment with the css styles we currently use for TCI
	if (FM.value == "1") {
		string.push('; padding:5px 2px 5px 2px');
	} else {
		string.push('; padding:3px 2px 3px 2px');
	}
	string.push('">');

	// if filter mode is advanced (1)
	if (FM.value == "1") {

		// filter add group button
		string.push('<input type="button" title="Add Filter Group"');
		if (DG.FilterAddGroupButtonCssClass) {
			string.push(' class="');
			string.push(DG.FilterAddGroupButtonCssClass);
			string.push('"');
			if (DG.FilterAddGroupButtonOverCssClass) {
				string.push(' onmouseover="this.className=\'');
				string.push(DG.FilterAddGroupButtonOverCssClass);
				string.push('\'" onmouseout="this.className=\'');
				string.push(DG.FilterAddGroupButtonCssClass);
				string.push('\'"');
			}
		}
		string.push(' onclick="DGFAGBClick(\'')
		string.push(id)
		string.push('\',this)">')

	// else, filter mode is default (0) or basic (-1)
	} else {
	
		// filter add button
		string.push('<input type="button" title="Add Filter"');
		if (DG.FilterAddButtonCssClass) {
			string.push(' class="');
			string.push(DG.FilterAddButtonCssClass);
			string.push('"');
			if (DG.FilterAddButtonOverCssClass) {
				string.push(' onmouseover="this.className=\'');
				string.push(DG.FilterAddButtonOverCssClass);
				string.push('\'" onmouseout="this.className=\'');
				string.push(DG.FilterAddButtonCssClass);
				string.push('\'"');
			}
		}
		string.push(' onclick="DGFABClick(\'')
		string.push(id)
		string.push('\',this)">')
	}

	// trailing filter div end
	string.push('</div>');

	// insert trailing filter div html into filter content cell
	// (this will replace existing filter content cell content)
	FF.innerHTML = string.join("");
	
	// get reference to trailing filter div
	var FD = FF.childNodes[0];

	// if the DataGrid has Filters, insert a filter div for each,
	// else, add an initial filter div with blank filter fields		
	var length = DG.Filters.length;
	if (length > 0) {
		for (var i = 0; i < length; i++) {
			if (DG.Filters[i])
				DGFilterRender(id, FD, DG.Filters[i]);
		}
	} else {
		var Filter = new DGFilter(DG.FilterNumber++, DG.FilterGroup);
		DG.Filters[Filter.Number] = Filter;
		DGFilterRender(id, FD, Filter);
	}
}

// toggle filter mode
function DGFMToggle(id, value) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's filter mode hidden input field
	var FM = document.getElementById(id + ":FM");

	// toggle filter mode between default and advanced
	FM.value = FM.value == "0"? "1": "0";

	// get this DataGrid's filter content cell
	var FF = document.getElementById(id + ":FF");

	// if filtering is enabled (filter content cell exists), initialize filter content,
	// insert trailing filter div with a filter add or filter add group button,
	// insert a filter div for each filter in DG.Filter or an initial blank filter,
	if (FF) DGFFInit(id, FF);
	
	// if scrolling is enabled, call DGResize
	if (DG.ScrollEnabled)
		DGResize(id);
}

// when the filter add button is clicked, this function is called
function DGFABClick(id, FAB) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);
	
	// unfocus filter add button
	FAB.blur();
	
	// if the filter add button has an id, the filter mode is advanced (filter add button belongs to a filter),
	// insert the new filter after the filter add button's parent filter div (</td></tr></tbody></table></div>)
	// and use this filter's group, else, insert filter before the filter add div and use the current filter group
	var insertBeforeNode;
	var filterGroup;
	if (FAB.id) {
		insertBeforeNode = FAB.parentNode.parentNode.parentNode.parentNode.parentNode.nextSibling;
		filterGroup = DG.Filters[FAB.id.substr(FAB.id.lastIndexOf(":") + 1)].Group;
	} else {
		insertBeforeNode = FAB.parentNode;
		filterGroup = DG.FilterGroup;
	}
	
	// create new Filter and add it to the DG.Filters array
	var Filter = new DGFilter(DG.FilterNumber++, filterGroup);
	DG.Filters[Filter.Number] = Filter;
	
	// render Filter in filter content cell
	DGFilterRender(id, insertBeforeNode, Filter);

	// if scrolling is enabled, call DGResize
	if (DG.ScrollEnabled)
		DGResize(id);
}

// when the filter add group button is clicked, this function is called
function DGFAGBClick(id, FAGB) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// unfocus filter add group button
	FAGB.blur();

	// create new Filter and add it to the DG.Filters array
	var Filter = new DGFilter(DG.FilterNumber++, ++DG.FilterGroup);
	DG.Filters[Filter.Number] = Filter;

	// render Filter in filter content cell
	DGFilterRender(id, FAGB.parentNode, Filter);

	// if scrolling is enabled, call DGResize
	if (DG.ScrollEnabled)
		DGResize(id);
}

// render Filter in filter content cell
function DGFilterRender(id, insertBeforeNode, Filter) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's filter content cell
	var FF = document.getElementById(id + ":FF");

	// get this DataGrid's filter mode hidden input field
	var FM = document.getElementById(id + ":FM");
	
	// create a new filter div
	// <DataGrid.ClientID>:FD:<FilterNumber>
	var FD = document.createElement("div");
	FD.id = id + ":FD:" + Filter.Number;
	FD.className = DG.FilterDivCssClass;
	FD.style.cssText = "float:left; white-space:nowrap";

	// build filter html with array and push method
	// (this is faster than concatenating with +)
	var string = new Array();

	// filter table start
	string.push('<table cellpadding="0" cellspacing="0" border="0"><tr>');
	
	// if filter mode is advanced
	if (FM.value == "1") {
	
		// get the filter div before the filter div being rendered,
		// add group css class to new filter div's logic cell if previous filter is in the same group,
		// hide new filter div's logic cell if no previous filter exists
		var prevFilter = insertBeforeNode.previousSibling;
		var prevFilterNumber = prevFilter? prevFilter.id.substr(prevFilter.id.lastIndexOf(":") + 1): null;
		var prevFilterGroup = prevFilterNumber? DG.Filters[prevFilterNumber].Group: null;

		// filter logic cell start
		string.push('<td id="');
		string.push(id);
		string.push(':FLC:');
		string.push(Filter.Number);
		string.push('" class="');
		string.push(DG.FilterGroupGlobalCssClass)
		if (prevFilterGroup == Filter.Group) {
			string.push(' ');
			string.push(DG.FilterGroupCssClasses[Filter.Group % DG.FilterGroupCssClasses.length]);
		}
		string.push('"');
		if (!prevFilterGroup)
			string.push(' style="visibility:hidden"');
		string.push('>');

		// filter group field
		// <DataGrid.ClientID>:FG:<FilterNumber>
		string.push('<input id="');
		string.push(id);
		string.push(':FG:');
		string.push(Filter.Number);
		string.push('" name="');
		string.push(id);
		string.push(':FG:');
		string.push(Filter.Number);
		string.push('" type="hidden" value="');
		string.push(Filter.Group);
		string.push('">');

		// filter logic field
		// <DataGrid.ClientID>:FL:<FilterNumber>
		string.push('<input id="');
		string.push(id);
		string.push(':FL:');
		string.push(Filter.Number);
		string.push('" name="');
		string.push(id);
		string.push(':FL:');
		string.push(Filter.Number);
		string.push('" type="hidden" value="');
		string.push(Filter.Logic);
		string.push('">');

		// filter logic button
		// <DataGrid.ClientID>:FLB:<FilterNumber>
		string.push('<input id="');
		string.push(id);
		string.push(':FLB:');
		string.push(Filter.Number);
		string.push('" type="button"');
		if (DG.FilterLogicButtonCssClass) {
			string.push(' class="');
			string.push(DG.FilterLogicButtonCssClass);
			string.push('"');
			if (DG.FilterLogicButtonOverCssClass) {
				string.push(' onmouseover="this.className=\'');
				string.push(DG.FilterLogicButtonOverCssClass);
				string.push('\'" onmouseout="this.className=\'');
				string.push(DG.FilterLogicButtonCssClass);
				string.push('\'"');
			}
		}
		string.push(' onclick="DGFLBClick(\'');
		string.push(id);
		string.push('\',this)" value="');
		if (Filter.Logic == "1") {
			string.push('OR');
		} else {
			string.push('AND');
		}
		string.push('">');

		// filter logic cell end
		string.push('</td>');
	}

	// filter field cell start
	string.push('<td nowrap');
	if (FM.value == "1") {
		string.push(' class="');
		string.push(DG.FilterGroupGlobalCssClass)
		string.push(' ');
		string.push(DG.FilterGroupCssClasses[Filter.Group % DG.FilterGroupCssClasses.length]);
		string.push('"');
	}
	string.push('>');

	// filter column field
	// <DataGrid.ClientID>:FC:<FilterNumber>
	// <DataGridColumn.Name>|<DataGridColumn.Type>
	string.push('<select id="');
	string.push(id);
	string.push(':FC:');
	string.push(Filter.Number);
	string.push('" name="');
	string.push(id);
	string.push(':FC:');
	string.push(Filter.Number);
	string.push('" onchange="DGFCChange(\'');
	string.push(id);
	string.push('\',this)"');
	if (DG.FilterFieldCssClass) {
		string.push(' class="');
		string.push(DG.FilterFieldCssClass);
		string.push('"');
	}
	string.push('>');
	var length = DG.Columns.length;
	for (var i = 0; i < length; i++) {
		if (DG.Columns[i].FilterEnabled) {
			string.push('<option value="');
			string.push(DG.Columns[i].Name);
			string.push('|');
			string.push(DG.Columns[i].Type);
			string.push('"');
			if (Filter.ColumnName == DG.Columns[i].Name)
				string.push(' selected');
			string.push('>');
			string.push(DG.Columns[i].Label);
			string.push('</option>');
		}
	}
	string.push('</select>');

	// filter operator field
	// <DataGrid.ClientID>:FO:<FilterNumber>
	// (option elements for this select element are created
	//  by calling DGFCChange at the end of this function)
	string.push('&nbsp;<select id="');
	string.push(id);
	string.push(':FO:');
	string.push(Filter.Number);
	string.push('" name="');
	string.push(id);
	string.push(':FO:');
	string.push(Filter.Number);
	string.push('" onchange="DGFOChange(\'');
	string.push(id);
	string.push('\',this)"');
	if (DG.FilterFieldCssClass) {
		string.push(' class="');
		string.push(DG.FilterFieldCssClass);
		string.push('"');
	}
	string.push(' style="width:');
	string.push(DG.FilterOperatorFieldWidth);
	string.push('"></select>');

	// filter value 1 field
	// <DataGrid.ClientID>:F1:<FilterNumber>
	string.push('&nbsp;<input id="');
	string.push(id);
	string.push(':F1:');
	string.push(Filter.Number);
	string.push('" name="');
	string.push(id);
	string.push(':F1:');
	string.push(Filter.Number);
	string.push('" type="text" maxlength="100"');
	if (DG.FilterFieldCssClass) {
		string.push(' class="');
		string.push(DG.FilterFieldCssClass);
		string.push('"');
	}
	switch (Filter.Operator) {
		case "7":
			string.push(' style="width:');
			string.push(DG.FilterBetweenFieldWidth);
			string.push('px"');
			break;
		case "8": case "9": case "10": case "11":
			string.push(' style="width:');
			string.push(DG.FilterFieldWidth);
			string.push('px; background-color:#d0d0d0" disabled="disabled"');
			break;
		default:
			string.push(' style="width:');
			string.push(DG.FilterFieldWidth);
			string.push('px"');
	} 
	string.push(' value="');
	string.push(Filter.Value1.replace('"', '&quot;'));
	string.push('" />');

	// filter value 2 field (used for Between operator)
	// <DataGrid.ClientID>:F2:<FilterNumber>
	string.push('<input id="');
	string.push(id);
	string.push(':F2:');
	string.push(Filter.Number);
	string.push('" name="');
	string.push(id);
	string.push(':F2:');
	string.push(Filter.Number);
	string.push('" type="text" maxlength="100"');
	if (DG.FilterFieldCssClass) {
		string.push(' class="');
		string.push(DG.FilterFieldCssClass);
		string.push('"');
	}
	string.push(' style="width:');
	string.push(DG.FilterBetweenFieldWidth);
	string.push('px; margin-left:2px');
	if (Filter.Operator != "7") {
		string.push('; display:none');
	}
	string.push('" value="');
	string.push(Filter.Value2.replace('"', '&quot;'));
	string.push('" />');

	// filter field cell end
	string.push('</td>');

	// filter button cell start
	string.push('<td>');

	// filter delete button
	// <DataGrid.ClientID>:FDB:<FilterNumber>
	string.push('<input id="');
	string.push(id);
	string.push(':FDB:');
	string.push(Filter.Number);
	string.push('" type="button" title="Remove Filter"');
	if (DG.FilterDeleteButtonCssClass) {
		string.push(' class="');
		string.push(DG.FilterDeleteButtonCssClass);
		string.push('"');
		if (DG.FilterDeleteButtonOverCssClass) {
			string.push(' onmouseover="this.className=\'');
			string.push(DG.FilterDeleteButtonOverCssClass);
			string.push('\'" onmouseout="this.className=\'');
			string.push(DG.FilterDeleteButtonCssClass);
			string.push('\'"');
		}
	}
	string.push(' onclick="DGFDBClick(\'');
	string.push(id);
	string.push('\',this)">');

	// if filter mode is advanced
	if (FM.value == "1") {

		// filter add button
		// <DataGrid.ClientID>:FAB:<FilterNumber>
		string.push('<input id="');
		string.push(id);
		string.push(':FAB:');
		string.push(Filter.Number);
		string.push('" type="button" title="Add Filter"');
		if (DG.FilterAddButtonCssClass) {
			string.push(' class="');
			string.push(DG.FilterAddButtonCssClass);
			string.push('"');
			if (DG.FilterAddButtonOverCssClass) {
				string.push(' onmouseover="this.className=\'');
				string.push(DG.FilterAddButtonOverCssClass);
				string.push('\'" onmouseout="this.className=\'');
				string.push(DG.FilterAddButtonCssClass);
				string.push('\'"');
			}
		}
		string.push(' onclick="DGFABClick(\'');
		string.push(id);
		string.push('\',this)">');
	}

	// filter button cell end
	string.push('</td>');

	// filter table end
	string.push('</tr></table>');

	// insert filter html into new filter div
	FD.innerHTML = string.join("");

	// insert new filter div just before the filter add div
	FF.insertBefore(FD, insertBeforeNode);
	
	// raise filter column changed (to populate filter operator field option elements)
	DGFCChange(id, document.getElementById(id + ":FC:" + Filter.Number), Filter);
}

// when a filter delete button is clicked, this function is called
function DGFDBClick(id, FDB) {
	
	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get this DataGrid's filter content cell
	var FF = document.getElementById(id + ":FF");

	// get number of the Filter being deleted
	// <DataGrid.ClientID>:FDB:<FilterNumber>
	var filterNumber = FDB.id.substr(FDB.id.lastIndexOf(":") + 1);
	
	// delete the Filter from the DG.Filters array (becomes undefined)
	delete DG.Filters[filterNumber];

	// get the filter div rendered for this Filter
	var FD = document.getElementById(id + ":FD:" + filterNumber);

	// get the filter divs before and after the filter div being deleted,
	// remove group css class from it's logic cell if next filter is becoming the first filter in a group,
	// hide it's logic cell if the next filter is becoming the first filter overall
	var prevFilter = FD.previousSibling;
	var prevFilterNumber = prevFilter? prevFilter.id.substr(prevFilter.id.lastIndexOf(":") + 1): null;
	var prevFilterGroup = prevFilterNumber? DG.Filters[prevFilterNumber].Group: null;
	var nextFilter = FD.nextSibling;
	var nextFilterNumber = nextFilter.id.substr(nextFilter.id.lastIndexOf(":") + 1);
	var nextFilterGroup = nextFilterNumber? DG.Filters[nextFilterNumber].Group: null;
	var nextFilterLogicCell = document.getElementById(id + ":FLC:" + nextFilterNumber);
	if (nextFilterLogicCell) {
		if (prevFilterGroup != nextFilterGroup)
			nextFilterLogicCell.className = DG.FilterGroupGlobalCssClass;
		if (!prevFilterGroup)
			nextFilterLogicCell.style.visibility = "hidden";
	}

	// delete the filter div rendered for this Filter
	FF.removeChild(FD);

	// if no filters remain, add a new blank filter
	if (FF.childNodes.length == 1) {
		var Filter = new DGFilter(DG.FilterNumber++, DG.FilterGroup);
		DG.Filters[Filter.Number] = Filter;
		DGFilterRender(id, FF.childNodes[0], Filter);
	}

	// if scrolling is enabled, call DGResize
	if (DG.ScrollEnabled)
		DGResize(id);
}

// when a filter logic button is clicked, this function is called
function DGFLBClick(id, FLB) {
	FLB.blur();
	FLB.value = FLB.value == "AND"? "OR": "AND";
	document.getElementById(FLB.id.replace(":FLB:", ":FL:")).value = FLB.value == "AND"? "0": "1";
}

// when a filter is created or the user
// changes the selected filter column value
function DGFCChange(id, FC, Filter) {

	// <DataGrid.ClientID>:FC:<FilterNumber>
	// <DataGridColumn.Name>|<DataGridColumn.Type>
	var FCFilterNumber = FC.id.substr(FC.id.lastIndexOf(":") + 1);
	var FCSelectedValue = FC.options[FC.selectedIndex].value;
	var FCType = FCSelectedValue.substr(FCSelectedValue.indexOf("|") + 1);
	var FO = document.getElementById(id + ":FO:" + FCFilterNumber);
	var FOSelectedValue;

	// remember the currently selected filter operator, if this operator
	// exists for the newly selected column, it will be reselected
	if (FO.selectedIndex > -1)
		FOSelectedValue = FO.options[FO.selectedIndex].value;

	// clear options from this filter operator field
	FO.options.length = 0;

	// get an array of filter operators for the given filter type
	var optionsTextArray = new Array();
	var optionsValueArray = new Array();
	switch (FCType) {
		case "2": case "6": case "9":
			optionsTextArray = new Array("Starts With", "Contains", "Not Contain", "Equals", "Not Equal", "Is Empty", "Is Not Empty");
			optionsValueArray = new Array("1", "2", "12", "3", "4", "8", "9");
			break;
		case "3": case "4": case "5":
			optionsTextArray = new Array("Equals", "Not Equal", "Less Than", "Greater Than", "Between", "Is Empty", "Is Not Empty");
			optionsValueArray = new Array("3", "4", "5", "6", "7", "8", "9");
			break;
		case "8":
			optionsTextArray = new Array("Is True", "Is False");
			optionsValueArray = new Array("10", "11");
	}

	// add an option to the filter operator field for each filter operator,
	// selecting the operator of the DGFilter passed in or
	// reselecting the previously selected operator
	var option, length = optionsTextArray.length;
	for (var i = 0; i < length; i++) {
		option = document.createElement("option");
		FO.options.add(option);
		option.text = optionsTextArray[i];
		option.value = optionsValueArray[i];
		if ((Filter && Filter.Operator == optionsValueArray[i]) || FOSelectedValue == optionsValueArray[i])
			option.selected = true;
	}
	
	// raise filter operator changed
	DGFOChange(id, FO);
}

// when a filter is created or the user
// changes the selected filter operator value
function DGFOChange(id, FO) {

	// get this DataGrid (outer div)
	var DG = document.getElementById(id);

	// get the filter value input fields for this filter operator input field
	var F1 = document.getElementById(FO.id.replace(":FO:", ":F1:"));
	var F2 = document.getElementById(FO.id.replace(":FO:", ":F2:"));

	// get the selected filter operator value
	var FOSelectedValue = FO.options[FO.selectedIndex].value;

	switch (FOSelectedValue) {
		case "7":
			F1.disabled = false;
			F1.style.backgroundColor = "#ffffff";
			F1.style.width = F2.style.width;
			F2.style.display = "";
			break;
		case "8": case "9": case "10": case "11":
			F1.disabled = true;
			F1.style.backgroundColor = "#d0d0d0";
			F1.style.width = DG.FilterFieldWidth + "px";
			F2.style.display = "none";
			break;
		default:
			F1.disabled = false;
			F1.style.backgroundColor = "#ffffff";
			F1.style.width = DG.FilterFieldWidth + "px";
			F2.style.display = "none"; 
	}
	
	// Get the filter column field for this filter.
	var FC = document.getElementById( FO.id.replace( ":FO:", ":FC:" ) );
	var FCSelectedValue = FC.options[ FC.selectedIndex ].value;
	var FCName = FCSelectedValue.substr( 0, FCSelectedValue.indexOf( "|" ) );

    // If an AutoSuggest object exists for this filter, disable it.
	// ~ The filter value 1 input field is the text input element AutoSuggest
	// ~ is associated with and has a reference to the AutoSuggest object if one exists. 
    if ( F1.AutoSuggest ) {
        F1.AutoSuggest.Disable();
    }

    // Get the AutoSuggestUrl property for this filter's selected DataGrid column.
	var autoSuggestUrl;
	for ( var i = 0, length = DG.Columns.length; i < length; i++ ) {
	    if ( DG.Columns[i].Name == FCName ) {
	        autoSuggestUrl = DG.Columns[i].AutoSuggestUrl;
	        break;
	    }
	}

    // If an AutoSuggestUrl is defined for this filter's selected DataGrid
    // column and the selected filter operator is Starts With or Contains.
    if ( autoSuggestUrl && ( FOSelectedValue == "1" || FOSelectedValue == "2" ) ) {
        
        var autoSuggestOperator = "Contains";
        if ( FOSelectedValue == "1" ) {
            autoSuggestOperator = "Starts With";
        }

        // If an AutoSuggest object does not exist for this filter, create one.
        // Else, set the existing AutoSuggest object's LoadSuggestionDataTableUrl and enable it.
        if ( !F1.AutoSuggest ) {
            F1.AutoSuggest = new AutoSuggest( F1, {
                LoadSuggestionDataTableUrl: autoSuggestUrl,
                LoadSuggestionDataTableOperator: autoSuggestOperator,
                SuggestionListCssClass: DG.AutoSuggestListCssClass,
                SuggestionListItemCssClass: DG.AutoSuggestListItemCssClass,
                SuggestionListItemHoverCssClass: DG.AutoSuggestListItemHoverCssClass
            } );
        } else {
            F1.AutoSuggest.LoadSuggestionDataTableUrl = autoSuggestUrl;
            F1.AutoSuggest.LoadSuggestionDataTableOperator = autoSuggestOperator;
            F1.AutoSuggest.Enable();
        }
    }
}

function GetDocumentOffsetTop(element) {
	var offsetTop = 0;
	while (element.offsetParent) {
		offsetTop += element.offsetTop;
		element = element.offsetParent;
	}
	return offsetTop;
}

function GetDocumentOffsetLeft(element) {
	var offsetLeft = 0;
	while (element.offsetParent) {
		offsetLeft += element.offsetLeft;
		element = element.offsetParent;
	}
	return offsetLeft;
}

