//  public/javascripts/availability.js
//  Copyright (C) 2008, VaVoca Inc.
//  Author: Chad Lester
//
//  This script enhances the availability widget with graphics and mouse input.
//
//  enhanceAvailabilityWidget() scans the document for the vanila HTML availability
//  widget and spices it up with some JavaScript.   After spicing, the boring
//  old checkbox elements are replaced with sexy graphical dots and the user is
//  able to select whole regions of dots with a click-and-drag of the mouse.
//
//  Implementation Overview:
//
//  This works by first finding the TABLE element in the document that is marked
//  with the CSS Class Name "availwidget".  We then iterate row by row over each
//  TD element and begin to modify it.
//
//  First, we hide the checkbox and insert a graphic instead.  Pretty!
//
//  Then we give the TD object some new methods and variables so each TD object
//  remembers what row and column it is in and knows how to select or deselect itself.
//  We save all of the TD objects that we find in an array so that we can easily
//  iterate over them later.
//
//  Finally, we cause the whole Table to respond to onmousedown events.  When such
//  an event comes in, we then capture onmousemove and onmouseup events for the whole
//  document so we can update the selection region as the mouse moves.   When the mouse
//  is released, the hidden checkbox elements have their "checked" status updated and
//  the event handlers are restored to their previous values.
//
//  To keep the code simple, whenever the selection region changes we iterate over 
//  ALL of the TD elements that we saved in the array earlier, not just the ones that 
//  are changing.  If the table was large, this might be an issue.   But for a 7x3 
//  table, this is not worth changing.
//

function enhanceAvailabilityWidget() {
	if  (document.getElementById) {  // This tests whether we are using DOM level 2.
		(
			// Here we create an anonymous function which immediately gets called.
			// TODO:  Is this necessary?  What this does is create a separate namespace.
			function() {
				//Stop Opera selecting anything whilst dragging.
				if (window.opera) {
					document.write("<input type='hidden' id='Q' value=' '>");
				}

				// these constants refer to the graphics files and text that we are using
				var CLASS_NAME = "availwidget"
				var YES_IMG_SRC = "/images/availwidget-yes.png"
				var NO_IMG_SRC = "/images/availwidget-no.png"
				var YES_IMG_TITLE = "Click and drag to clear your availability"
				var NO_IMG_TITLE = "Click and drag to select your availability"
				var SELECT_COLOR = "#B5D5FF"  // used as a background color while selecting day/times

				// these variables are used to hold state while selecting elements.
				var availWidgetTds = new Array();  // These hold the TD elements of the "availwidget" table.
				var firstTarget; // the TD element that was selected when the mouse went down.
				var lastTarget;  // the TD element that the mouse is currently over (or null if out of bounds)
				var selectChecked;  // true iff the selection should be set to Checked.
				var oldMouseUp, oldMouseMove;  // save the old document event handlers.

				// This function becomes the TD method to set the element as selected.
				function tdAwSetSelected() {
					this.awSelected = true;
					this.style.backgroundColor = SELECT_COLOR;
					if (selectChecked) {
						this.awImg.src = YES_IMG_SRC;
					} else {
						this.awImg.src = NO_IMG_SRC;
					}
				}

				// This function becomes the TD method to clear the selection of this element.
				function tdAwClearSelected() {
					this.awSelected = false;
					this.style.backgroundColor = this.awOldBackgroundColor;
					if (this.awChx.checked) {
						this.awImg.src = YES_IMG_SRC;
						this.awImg.title = YES_IMG_TITLE;
					} else {
						this.awImg.src = NO_IMG_SRC;
						this.awImg.title = NO_IMG_TITLE;
					}		
				}

				// Update the selection to only include those TD elements between first and last
				function updateSelection(first, last) {
					var minRow, maxRow, minCol, maxCol;
					if (first.awRow < last.awRow) {
						minRow = first.awRow;
						maxRow = last.awRow;
					} else {
						minRow = last.awRow;
						maxRow = first.awRow;
					}
					if (first.awCol < last.awCol) {
						minCol = first.awCol;
						maxCol = last.awCol;
					} else {
						minCol = last.awCol;
						maxCol = first.awCol;
					}
					for (var i=0; i<availWidgetTds.length; ++i) {
						var td = availWidgetTds[i];
						if (td.awRow >= minRow && td.awRow <= maxRow && td.awCol >= minCol && td.awCol <= maxCol) {
							td.awSetSelected();
						} else {
							td.awClearSelected();
						}
					}
				}

				// Update the checkbox value for each TD element that was selected and clear the selection.
				function applySelection() {
					for (var i=0; i<availWidgetTds.length; ++i) {	
						td = availWidgetTds[i]
						if (td.awSelected) td.awChx.checked = selectChecked;
						td.awClearSelected()
					}
				}

				// Clear the selection by telling every TD element to clear its own selection.
				function clearSelection() {
					for (var i=0; i<availWidgetTds.length; ++i) {					
						availWidgetTds[i].awClearSelected()
					}
				}

				// Determine which TD element the mouse is over or null if none
				function getTd(e) {
					if (!e) e = window.event;
					var target = (typeof e.target != "undefined") ? e.target : e.srcElement;
					if ((target.tagName == "TD" || target.tagName == "IMG") && target.className == CLASS_NAME) {
						return target.awTd;  // Note: awTd is set for both IMG and TD elements.
					}
					return null;
				}

				// This is mousedown event handler for our table.  When we receive a mousedown event that
				// lands one one of our TD elements, we begin the selection process.
				function OnMouseDown(e) {
					var target = getTd(e);
					if (target) {
						if (window.opera) {  // this prevents elements from being selected.
							document.getElementById("Q").focus();
						}
						// Initialize the starting and ending position of our selection.
						firstTarget = target;
						lastTarget = target;
						// If the element we clicked on was already checked, then we are going to 
						// uncheck a group.   Otherwise we will check the selected group.
						selectChecked = !target.awChx.checked;
						updateSelection(firstTarget,lastTarget);
						// Begin capturing the mousemove and mouseup events for the entire document
						oldOnMouseMove = document.onmousemove;
						oldOnMouseUp = document.onmouseup
						document.onmousemove = OnMouseMove;
						document.onmouseup = OnMouseUp;
						return false;
					}
				}

				// This is the mousemove event handler that is set for the entire document after
				// a mousedown event is received over one of our availwidget TD elements.  If the
				// event lands on a TD element that is different from the lastTarget, then we
				// update the selection.   If it doesn't land on a TD element at all (when the user
				// moves the mouse outside of our table, for example), then we clear the entire
				// selection, but we remember where the selection started in case they move the 
				// mouse back inside the table.
				function OnMouseMove(e){
					var target = getTd(e);
					if (target) {
						if (target != lastTarget) {
							// target.awImg.toggleCheckbox()
							updateSelection(firstTarget, target)
							lastTarget = target
						}
					} else {
						clearSelection();
						lastTarget = target;
					}
					return false
				}
				
				// This is the mouseup event handler that is set for the entire document after
				// a mousedown event is received on one of our availwidget TD elements.  MouseUp
				// will end the selection process by applying the current selection and restoring
				// the old event handlers.
				function OnMouseUp(){
					applySelection();
					document.onmousemove = oldOnMouseMove;
					document.onmouseup = oldOnMouseUp;
				}

				// Now it's time to enhance the avail widget tables!

				// First, find the first table element whose class is set to CLASS_NAME
				var table, tables = document.getElementsByTagName("table");
				for (var i=0; i<tables.length; i++) if ((table = tables[i]).className == CLASS_NAME) break;
				// now for each row (except the first row):
				var rows = table.getElementsByTagName("tr");
				for (var row=1; row<rows.length; row++) {
					var cols = rows[row].getElementsByTagName("td")
					for (var col=0; col<cols.length; col++) {
						td = cols[col];
						td.awOldBackgroundColor = td.style.backgroundColor;
						td.className = CLASS_NAME;

						// find the original checkbox element and hide it.
						var chx = td.getElementsByTagName("input")[0];	  	
						chx.style.display='none';

						// create the graphical alternative:
						var img = document.createElement("img");
						img.className = CLASS_NAME;
						img.awTd = td;

						// insert the new, clickable image into the DOM
						// infront of the original checkbox:
						td.insertBefore(img, chx);

						// Save the row, column, image, checkbox, and a reference to myself
						td.awRow = row;
						td.awCol = col;
						td.awImg = img;
						td.awChx = chx;
						td.awTd = td;
						td.awSetSelected = tdAwSetSelected;
						td.awClearSelected = tdAwClearSelected;
						td.awClearSelected();
						availWidgetTds.push(td);
					} // for each td element
				} // for each table row (except the first)
				
				// Now that the table elements have been given some self awareness, we are ready to 
				// accept mousedown events!
				table.onmousedown = OnMouseDown;
			}  // end of our anonymous function
		)();
	}
} // enhanceAvailabilityWidget
