
var RenderViewCache = {};
/**
 * This function Renders a View into a Content Element via Ajax.
 * @param String aObject The Content Object that is passed to the server as an argument. REQUIRED ATTRIBUTE: "Type"
 * @param var vContentElement DOM Element to render the View into.
 * @param Object Configuration options and callbacks.
 */
function RenderView( aObject, vContentElement, aOptions ){
	aOptions = Object.extend({
		CacheParameters: true,
		FormSourceElement: vContentElement,
		onLoadCallback: Prototype.emptyFunction
	}, aOptions || {});
	
	if(Object.isString(aOptions.onLoadCallback)){
		try{
			eval('aOptions.onLoadCallback = ' + aOptions.onLoadCallback);
		}
		catch(e){
			aOptions.onLoadCallback = Prototype.emptyFunction;
		}
	}
	
	aObject = Object.extend( {
		Type: 'View'
	}, aObject || {} );
	
	RenderViewCache[aObject.Type] = Object.extend( {
		Loading: false,
		Parameters: {}
	}, RenderViewCache[aObject.Type] || {} );
	if(!aOptions.CacheParameters){
		RenderViewCache[aObject.Type].Parameters = {};
	}
	
	var oContentElement = $( vContentElement );
	var oFormSourceElement = $( aOptions.FormSourceElement );
	
	if( oContentElement && ! RenderViewCache[aObject.Type].Loading ){
		for( var i = 0; i < window.beforeSubmits.length; i++ ) {
			if( typeof window.beforeSubmits[i] == "function" ){
				window.beforeSubmits[i]();
			}
		}
		
		if( oFormSourceElement ){
			RenderViewCache[aObject.Type].Parameters = Object.extend( RenderViewCache[aObject.Type].Parameters, Form.serializeElements( Form.getElements( oFormSourceElement ), true ) );
		}
		
		for( var i = 0; i < window.afterSubmits.length; i++ ) {
			if( typeof window.afterSubmits[i] == "function" ){
				window.afterSubmits[i]();
			}
		}
		
		RenderViewCache[aObject.Type].Parameters = Object.extend( RenderViewCache[aObject.Type].Parameters, aObject );
		RenderViewCache[aObject.Type].Parameters.toJSON = undefined;	// The toJSON method on this object kills the next Object.toJSON call.
		
		RenderViewCache[aObject.Type].Loading = true;
		
		// Maintain height and positioning while new content is loading...
		Element.makePositioned( oContentElement );
		var oldHeight = oContentElement.style.height || '';
		
		var onComplete = function onCompleteRenderView( results ){
			// ...and reset for new content
			Element.undoPositioned( oContentElement );
			Element.setStyle( oContentElement, { height: oldHeight } );
			
			RenderViewCache[aObject.Type].Loading = false;
			
			if( results ){
				if( results.View ){
					oContentElement.innerHTML = results.View;
				}
				else if( !results.Redirect ){
					oContentElement.innerHTML = 'An error has occurred. The appropriate persons have been notified and are addressing the problem.';
					window.onerror('Error Loading Content', '', results);
				}
				
				aOptions.onLoadCallback( results );
				
				if( results.JavaScript ){
					EvalJavaScript( results.JavaScript );
					results.JavaScript = '';	// Consume JavaScript after eval so it won't get evaled twice.
				}
			}
		};
		
		var oRequest = new ServerRequest();
		oRequest.addAction( 'View', RenderViewCache[aObject.Type].Parameters, '', {} );
		oRequest.onComplete = onComplete;
		oRequest.execute();
		
		var currentHeight = Math.max( Element.getHeight( oContentElement ), 64 );	// Leave enough room for Loading Content
		currentHeight = Math.min( document.viewport.getHeight(), currentHeight );	// Don't exceed document height (Bug #11033)
		Element.setStyle( oContentElement, {
			height: currentHeight + 'px'
		} );
		oContentElement.innerHTML = GetLoadingContent();
	}
}

function ControllerRequest( aObject, sMethod, aOptions ){
	aOptions = Object.extend({
		FormSourceElement: $('page_form'),
		onLoadCallback: Prototype.emptyFunction,
		Message: 'Loading...'
	}, aOptions || {});
	
	aObject = Object.extend( {
		Type: 'Controller'
	}, aObject || {} );
	
	sMethod = sMethod || '';
	
	var oFormSourceElement = $( aOptions.FormSourceElement );
	
	if( oFormSourceElement ){
		YUIPopupFormProvider.SetLoadingModalForm(aOptions.Message);
		
		for( var i = 0; i < window.beforeSubmits.length; i++ ) {
			if( typeof window.beforeSubmits[i] == "function" ){
				window.beforeSubmits[i]();
			}
		}
		
		var oParameters = Form.serializeElements( Form.getElements( oFormSourceElement ), true );
		
		for( var i = 0; i < window.afterSubmits.length; i++ ) {
			if( typeof window.afterSubmits[i] == "function" ){
				window.afterSubmits[i]();
			}
		}
		
		oParameters = Object.extend( oParameters, aObject );
		oParameters.toJSON = undefined;	// The toJSON method on this object kills the next Object.toJSON call.
		
		var onComplete = function onCompleteControllerRequest( results ){
			if( results ){
				if( results.Controller ){
					YUIPopupFormProvider.DisposeModalForm();
				}
				else if( !results.Redirect ){
					YUIPopupFormProvider.SetErrorModalForm('An error has occurred. The appropriate persons have been notified and are addressing the problem.');
					window.onerror('Error Loading Content', '', results);
				}
				
				aOptions.onLoadCallback( results );
				
				if( results.JavaScript ){
					EvalJavaScript( results.JavaScript );
					results.JavaScript = '';	// Consume JavaScript after eval so it won't get evaled twice.
				}
			}
		};
		
		var oRequest = new ServerRequest();
		oRequest.addAction( 'Controller', oParameters, sMethod, {} );
		oRequest.onComplete = onComplete;
		oRequest.execute();
	}
}

/**
 * JavaScript reflection of Persistent Item used for saving 
 * JS items
 * 
 */
var PersistentItemList = Class.create({
	Type: 'PersistentItemList',
	BaseDirectory: '/c/',
	//Filters: {},
	//IDs : [],
	initialize: function initializePersistentItemList(sType, aFields, sPrimaryKey) {
		this.Type = sType || '';
		this.PrimaryKey = sPrimaryKey ? sPrimaryKey : this.Type.replace('List', 'ID');
		this.Populate = true;
		this.SetValues = false;				//use to set properties on PIList objects
		for (var sField in aFields) {
			this[sField] = aFields[sField];	
		}	
		this.Filters = {};
		this.IDs = [];
	},
	
	addFilter: function addFilter(sFilter, vValue) {
		this.Filters[sFilter] = vValue;
	},
	
	addID: function addID(iID){
		this.IDs.push(iID);
	},
	
	saveSortOrder: function saveSortOrder(aOptions, aParameters){
		var aOptions = aOptions || {};
		var aParameters = aParameters || {};
		this.Populate = false;
		var inputNamePrefix = "Sort"+this.PrimaryKey;
		var sortVars = $$('input[name^="'+inputNamePrefix+'"]');
		for(var i = 0; i < sortVars.length; i++){
			this.addID(sortVars[i].value);
		}
		if (this.IDs.length) {
			var oRequest = new ServerRequest();
			oRequest.addAction('SaveSort',this,'AjaxSaveSort',aParameters);
			if (typeof aOptions['complete'] == 'function') {
				oRequest.onComplete = aOptions['complete'].bind(this);
			}
			oRequest.execute();
		}
		this.Populate = true;
	}
});

var Controller = Class.create({
	Type: 'Controller',
	initialize: function initializeController(sController) {
		this.Type = sController;
	}
});

var View = Class.create({
	Type: 'View',
	initialize: function initializeView(sView) {
		this.Type = sView;
	}
});

var PersistentItem = Class.create({
	Type: 'PersistentItem',
	initialize: function initializePersistentItem(aFields) {
		this.Populate = false;
		for (var sField in aFields) {
			this[sField] = aFields[sField];	
		}
	},
	
	Delete: function Delete(aOptions,aParameters) {
		aOptions = aOptions || {};
		aParameters = aParameters || [];
		var oRequest = new ServerRequest();
		oRequest.addAction('Delete',this,'AsyncDelete',aParameters);
		if (typeof aOptions['complete'] == 'function') {
			oRequest.onComplete = aOptions['complete'].bind(this);
		}
		oRequest.execute();
	},
	
	Save: function Save(aOptions, aParameters){
		aOptions = aOptions || {};
		aParameters = aParameters || [];
		var oRequest = new ServerRequest();
		oRequest.addAction('Save',this,'AsyncSave',aParameters);
		if (typeof aOptions['complete'] == 'function') {
			oRequest.onComplete = aOptions['complete'].bind(this);
		}
		oRequest.execute();	
	}
});

var ServerAction = Class.create({
	initialize: function initializeServerAction(oObject, sMethod, aParameters) {
		this.Object = oObject;
		this.Method = sMethod;
		this.Parameters = aParameters || {};
	}
});

var ServerRequest = Class.create({
	Actions: {},
	Results: {},
	BaseDirectory: '/c/',
	
	initialize: function initializeServerRequest() {
		this.Actions = {};
		this.Results = {};
	},
	
	onComplete: function onComplete(){
		return;
	},
	
	onSuccess: function onSuccess(){
		return;
	},
	
	addAction: function addAction(sName, oObject, sMethod, aParameters) {
		this.Actions[sName] = new ServerAction(oObject, sMethod, aParameters);
	},
	
	execute: function execute(oController) {
		if (document.location.href.indexOf('/c/') > 0) {
			this.BaseDirectory = '/c/';
		}
		else {
			this.BaseDirectory = '/i/';
		}
		sURL = this.BaseDirectory + 'pi/handler.php';
		
		new Ajax.Request(sURL, {
			method: 'post',
			onFailure: (function(transport){
				var sData = transport.responseText;
				this.onFailure(sData);
			}).bind(this),
			onComplete: (function onCompleteServerRequest(transport) {
				var sData = transport.responseText;
				try{
					this.Results = sData.evalJSON();
				}
				catch(e){
					this.Results = sData;
				}
				this.Controller = oController;
				this.onComplete(this.Results);
				if(this.Results.JavaScript){
					EvalJavaScript( this.Results.JavaScript );
					this.Results.JavaScript = '';	// JR 09/04/2009 - Consume JavaScript after eval so it won't get evaled twice.
				}
			}).bind(this),
			parameters: { 'PageType': window.PageType, 'Actions': Object.toJSON(this.Actions) }
		});
	}
});

var Teacher = Class.create(PersistentItem, {
	Type: 'Teacher',
	initialize: function initializeTeacher($super, aFields) {
		$super(aFields);
	},
	displayEmailLink: function displayEmailLink() {
		alert(this.save());
	}
});

var TeacherList = Class.create(PersistentItemList, {
	Type: 'TeacherList',
	initialize: function initializeTeacherList($super, aFields) {
		$super(aFields);
	}
});

/**
 *  Below is the general code that drives editing of persistent items through
 *  asynchronous javascript.
 * 
 */
TableDelegates = {};
EventHandlersCreatedForTableDelegates = false;
function InitDelegateListener(){
	//don't do this more than once - avoid duplicate event handlers
	if (!EventHandlersCreatedForTableDelegates) {
		Element.observe( document, 'click', function(e){
			if(Event.isLeftClick(e) || Prototype.Browser.IE){
				$H( TableDelegates ).find( function( DelegateHash ){
					if( typeof DelegateHash.value == 'object' && DelegateHash.value.HandleClick(e) ){
						return true;
					}
				} );
			}
		} );
		Element.observe( document, 'keypress', function(e){
			$H( TableDelegates ).find( function( DelegateHash ){
				if( typeof DelegateHash.value == 'object' && DelegateHash.value.HandleKeyPress(e) ){
					return true;
				}
			} );
		} );
		EventHandlersCreatedForTableDelegates = true;
	}
}
/**
 * Below is the general code that drives editing of persistent items through
 * asynchronous javascript.  There is a three level inheritance hierarchy.
 * The top level is the AjaxEditTableItem which extends our Persistent Item class.
 * Each AjaxEditTableItem instantiation acts as the controller between the server and one
 * row of the admin table (the row representing that item). 
 * This object is considered abstract, and is not intended to be instantiated.  It
 * contains the general code for editing, saving, updating, deleting items from the list.
 * 
 * The two concrete subclasses of AjaxEditTableItem are AjaxEditTableModalFormItem and AjaxEditTableFastEntryItem
 * They represent two distinctly different "styles" of editing.  The first uses a Modal window containing a form 
 * to create and edit items.  The modal form is defined by the server and sent back through Ajax and added to the page.
 * The second type, AjaxEditTableFastEntryItem, is edited inline by clicking on the items in the row and editing their contents.
 * This type of editing generates fewer requests, requires less waiting for the user, but is only capable of handling items
 * that have just a basic textual field.  An example of where this is used is all the Standard Item objects in standards maintenance.
 *   
 * The Edit Table Items are all managed by a delegate class AjaxEditTableDelegate.  There is one instance of this per type of item on the page.
 * For example, on Standards Maintenance, if there are nested tables of Content Areas and Content Sub Areas on one page
 * there will be two delegates, one for each type. The delegates are the "first responders" to click events on the page and will detect and call
 * the appropriate methods on the individual items based on what was clicked.
 * 
 * Subclassing Notes: 
 * Many times it is not enough to use the default objects, so they are written to be easy extended by subclassing.
 * An example is in Standards Maintenance, since there is so much custom behavior and nested tables, all of those objects are handled
 * by subclasses of the AjaxEditTableFastEntryItem. In general when you create a subclass, you will subclass both the delegate and either
 * AjaxEditTableModalFormItem or AjaxEditTableFastEntryItem depending on the base style of editing you are working with.
 * 
 * Examples of both types of subclass can be found in the code base, and subclass considerations are incorporated in the comments in line in the functions below.
 */
var AjaxEditTableDelegate = Class.create({
	//Available Options:
	//type : class type for the delegated object
	//isFastEntry : set true to enable fast entry mode for single entry text click objects 
	//entryFields : an array of form variable ID names that are used to create new items
	//sortable : a boolean indicating if this item is sortable or not
	//editFormClass : for popup editing, a class name to put on the edit form to hook into extra styling
	initialize: function initializeAjaxEditTableDelegate(options){	
		this.items = {};
		this.Type = options.type || '';
		this.PrimaryKey = options.primaryKey ? options.primaryKey : this.Type+'ID';
		this.SingletonDelegate = options.SingletonDelegate || false;
		this.isFastEntry = options.isFastEntry || false;
		this.addRowOnBottom = options.addRowOnBottom || false;
		this.sortable = options.sortable || false;
		this.editFormClass = options.editFormClass || false;
		this.striped = ( options.striped !== false );	// JR 10/21/2009 - Default striped value to true, #TA368
		this.ItemLabel = options.ItemLabel || 'Item';
		this.deleteConfirmation = options.deleteConfirmation || '';
		this.ImportHeader = options.ImportHeader || 'Import';
		if(this.isFastEntry){
			this.fastEntryField = options.fastEntryField;
			this.AutoAddFastEntryEditingRow();
		}
		
		if(!this.editFormClass){
			this.editFormClass = this.Type + 'EditForm';
		}
		
		//register self
		if(TableDelegates[this.Type] && !this.SingletonDelegate){
			var delegateIndex = $R(1,9).find(function(index){	//TODO: Current limit of 10 delegates per type - need more?
				if(!TableDelegates[this.Type + '_' + index]){
					TableDelegates[this.Type + '_' + index] = this;
					return true;
				}
			}, this);
		}
		else{
			TableDelegates[this.Type] = this;			
		}
		
		//create all items
		$$("."+this.Type).each(function(itemElement){
			this.RegisterItemElement(itemElement);
		}, this);
		if (options.FieldsToSetFromHiddenFields) {
			this.FieldsToSetFromHiddenFields = options.FieldsToSetFromHiddenFields;
		}
	},
	
	/**
	 * this function will insert the item into the set of
	 * items managed by the delegate.  In general,
	 * this method is not meant to be overriden. Instead
	 * for custom types of objects override CreateItem with your
	 * delegate sublcass
	 */
	RegisterItemElement: function RegisterItemElement(element, fromAjax){
		var itemObj = this.CreateItem({
			'element' : element, 
			'Type' : this.Type,
			'PrimaryKey' : this.PrimaryKey,
			'addRowOnBottom' : this.addRowOnBottom
		}, fromAjax);
		if(this.isFastEntry){
			itemObj.fastEntryField = this.fastEntryField;
		}
		if(fromAjax && this.sortable){
			AjaxEditTableItem.MakeRowDraggable(itemObj.row );
		}
		this.items[element.id] = itemObj;
		//this.items.set(element.id, itemObj);
		return itemObj;
	},

	/**
	 * When you create a custom subclass of item
	 * the delegate needs to override this function so that
	 * the correct type of item is returned.  The default implementation
	 * works for both of the general concrete sub classes
	 */
	CreateItem: function CreateItem(options, fromAjax){
		if(this.isFastEntry){
			return new AjaxEditTableFastEntryItem(options);
		}
		else{
			return new AjaxEditTableModalFormItem(options);
		}	
	},
	
	AutoAddFastEntryEditingRow: function AutoAddFastEntryEditingRow(){
		$$('textarea.new'+this.Type).each(function(textarea){
			if(textarea.value != ''){
				this.items[AjaxEditTableItem.GetItemIDFromTargetElement(textarea)].AddEditingRow();
				textarea.value = '';
			}
		}, this);
		
		this.AutoAddFastEntryEditingRow.bind(this).delay(.75);	// Keep executing every .75 seconds
	},
	
	ItemDeleted: function ItemDeleted(item){
		//TODO remove the item from the array
		//this.items = this.items.unset(item.element.id);
		this.RowCountChanged(-1);
	},
	
	/**
	 * @param int numRows The number of rows that have changed. Typically [-1, 0, 1]
	 */
	RowCountChanged: function RowCountChanged(numRows){
		$$('.'+this.Type+'ListTable').each(function(table){
			if(this.striped){
				StripeTable(table);
			}
			var footerElem = Element.next(table, '.displayTableFooter');
			if(footerElem){
				var totalRecordsElem = Element.down(footerElem, '.TotalRecords');
				if(totalRecordsElem){
					var TotalRecords = parseInt(totalRecordsElem.innerHTML,10);
					var netRows = TotalRecords + numRows;
					var PagingLinks = Element.select(footerElem, '.UIPagingButtonPrev, .UIPagingButtonNext');
					if (PagingLinks.length && parseInt(netRows, 10) === 0) {
						SubmitForm();
					}
					totalRecordsElem.innerHTML = netRows;
				}
			}
		}, this);
	},
	
	/**
	 * Returns true if the element is contained within the table
	 * managed by the delegate.  Useful for verying origin and types of clicks
	 * that are received
	 */
	VerifyChildContent: function VerifyChildContent(element){
		var parentPreHeader = Element.up( element, '.displayTablePreHeader' );	//handle preheader clicks
		var parentTable = parentPreHeader ? Element.next( parentPreHeader, 'table.ajaxAdminTable' ) : Element.up( element, 'table.ajaxAdminTable' );
		if( ! parentTable ){
			parentTable = parentPreHeader ? Element.next( parentPreHeader, 'table' ) : Element.up( element, 'table' );
		}
		if( parentTable && parentTable.className ){
			if( Element.hasClassName( parentTable, '.*ListTable' ) && ! Element.hasClassName( parentTable, this.Type+'ListTable' ) ){
				return false;
			}
		}
		var EditForm = $('EditForm');
		if( EditForm && EditForm.className ){
			if( Element.hasClassName( EditForm, '.*EditForm' ) && ! Element.hasClassName( EditForm, this.Type+'EditForm' ) ){
				return false;
			}
		}
		return true;
	},
	
	/**
	 * Front line for event handling in this system.  This is called
	 * from the page's click handler.  If true is returned then it signifies
	 * that the event was handled, if false is returned it signifies that the event
	 * was not relevant to this table.
	 * 
	 * Subclass Note: to create special click handler's do not override this method
	 * in your subclasses, rather there is a more convenient CustomClickHandler method that is
	 * passed the element directly.  This is what should be overriden
	 */
	HandleClick: function HandleClick(e){
		var element = Event.element(e);
		
		if(!this.VerifyChildContent(element)){
			 return false;
		}
		
		//allow subclasses a chance to respond
		if(this.CustomClickHandler(e)){
			return true;
		}

		if (Element.hasClassName(element, 'cancelDelete')) {
			AjaxEditTableItem.deletingItem.CloseDeleteDialog();
			return true;
		}
		//JG should be specifc to Standards, please remove from parent class
		else if (Element.hasClassName(element, 'notify') ) {
			//AjaxEditTableItem.deletingItem.ForceDelete(true);
			return true;
		}
		//JG should be specific to Standards, please remove from parent class
		else if (Element.hasClassName(element, 'dontNotifyDelete') || Element.hasClassName(element, 'forceDelete')) {
			AjaxEditTableItem.deletingItem.ForceDelete(false);
			return true;
		}
		else if(Element.hasClassName(element, 'saveButton') && AjaxEditTableItem.editingItem){
			AjaxEditTableItem.editingItem.Save();
			return true;
		}
		else if(Element.hasClassName(element, 'cancelEdit') && AjaxEditTableItem.editingItem){
			AjaxEditTableItem.editingItem.CancelEdit();
			return true;
		}
		var EditItemElement = Element.up(element,'.EditItem');
		
		//JG 4/2/09 support for external add buttons
		if(Element.hasClassName(element, 'AddNewButton')){
			this.NewItem.Edit();
		}
		else if(element.hasClassName('Import')){
			this.NewItem.Import();
		}
		else if (typeof this.items[element.id] == "object" && (!AjaxEditTableItem.editingItem || this.isFastEntry)) {
			this.items[element.id].Edit();
		}
		else if(EditItemElement && typeof this.items[EditItemElement.id] == "object" && (!AjaxEditTableItem.editingItem || this.isFastEntry)){
			this.items[EditItemElement.id].Edit();
		}
		else if(Element.hasClassName(element, 'AddNew') || Element.hasClassName(element, 'editButton')){
			this.items[AjaxEditTableItem.GetItemIDFromTargetElement(element)].Edit();
		}
		else if (Element.hasClassName(element, 'collapser')) {
			AjaxEditTableItem.ToggleCollapse(element);
		}
		else if (Element.hasClassName(element, 'expandAll')){
			var parentTable = AjaxEditTableItem.GetParentWithTagForChild('TABLE', element);
			Element.select(parentTable, '.collapser').each(AjaxEditTableItem.Expand);
		}
		else if(Element.hasClassName(element, 'collapseAll')){
			var parentTable = AjaxEditTableItem.GetParentWithTagForChild('TABLE', element);
			Element.select(parentTable, '.collapser').each(AjaxEditTableItem.Collapse);		
		}
		else if(Element.hasClassName(element, 'delete') || Element.hasClassName(element, 'deleteButton')) {
			if (!this.deleteConfirmation || confirm(this.deleteConfirmation)) {
				this.items[AjaxEditTableItem.GetItemIDFromTargetElement(element)].AttemptDelete();
			}
		}
		else{
			return false;
		}
		//stop the event from further bubbling
		e.stop();		
		return true;
	},
	
	/**
	 * This method is intended to be overriden by subclasses
	 * to perform specific events. 
	 * 
	 * If this method returns true then no more events will be triggered
	 * from the click, indicating that it was handled by the subclass
	 */
	CustomClickHandler: function CustomClickHandler(event){
		return false;
	}, 
	
	/**
	 * Front line for keypress event handling in this system.
	 * 
	 * For performance, we only handle certain keycodes for now.
	 */
	HandleKeyPress: function HandleKeyPress(e){
		var element = Event.element(e);
		// Only handle certain keycodes!
		if( e.keyCode == Event.KEY_RETURN ){
			if(!this.VerifyChildContent(element)){
				 return false;
			}
			
			//allow subclasses a chance to respond
			if(this.CustomKeyPressHandler(element)){
				return true;
			}

			if(Element.match(element, 'input[type="text"]') && !Element.hasClassName(element, 'yui-ac-input') && AjaxEditTableItem.editingItem){
				AjaxEditTableItem.editingItem.Save();
			}
			else{
				return false;
			}
			
			//stop the event from further bubbling
			e.stop();		
			return true;
		}
	},
	
	/**
	 * This method is intended to be overriden by subclasses
	 * to perform specific events. 
	 * 
	 * If this method returns true then no more events will be triggered
	 * from the keypress, indicating that it was handled by the subclass
	 */
	CustomKeyPressHandler: function CustomKeyPressHandler(element){
		return false;
	}, 
	
	/**
	 * For use in nested tables with dragging.  It is necessary
	 * for the case when a new item is created in the parent table
	 * and therefor an uninitialized sub table is passed to the page in ajax
	 * this method is necessary to initialize the drag and drop sorting on that table
	 */
	InitSubTable: function InitSubTable(objectType, newTable){
		var itemObj = TableDelegates[objectType].RegisterItemElement(Element.down(newTable, '.'+objectType));
		var sortTableID = 'tableDnD_'+newTable.id;
		window[sortTableID] = new TableDnD();
		window[sortTableID].init(newTable, this.SaveSort.bind(this));				
	},

	GetParameters: function GetParameters(){
		var aParameters = {};
		if (this.FieldsToSetFromHiddenFields) {
			$A(this.FieldsToSetFromHiddenFields).each(function(field) {
				var fieldValue = GetValue(field);
				if( fieldValue ){
					aParameters[field] = fieldValue;
				}
			},this);
		}
		return aParameters;
	},
	
	/**
	 * A general and simple function to save the sort order
	 * on the table. Called by the TableDND code when there
	 * has been a drag event.
	 */
	SaveSort: function SaveSort(){
		var ItemList = new PersistentItemList(this.Type+'List',{},this.PrimaryKey);
		ItemList.saveSortOrder({'complete': this.AfterSaveSort.bind(this)}, this.GetParameters());
	},
	
	AfterSaveSort: function AfterSaveSort(){
		// override me
	}
});

/**
 * Abstract super class for all Editing controllers.
 * There are a few important properties to consider here
 * the most significant is the idea of a 'newElement'.  This is a place holder
 * row that is responsible for generating and placing new items in the list.
 */
var AjaxEditTableItem = Class.create({
	initialize: function initializeAjaxEditTableItem(options) {
		this.Type = options.Type || '';
		this.PrimaryKey = options.PrimaryKey ? options.PrimaryKey : this.Type+'ID';
		this.addRowOnBottom = options.addRowOnBottom;
		this.element = options.element || '';
		if (this.element) { //ensure element isnt undefined - we might not have an element
			this.row = AjaxEditTableItem.GetRowForChildElement(this.element);
			//set the boolean if the row is dedicated for adding elements
			this.newElement = Element.hasClassName(this.row,'newItemRow'); 
		}
		//creates the item instance
		this.SetItem();
		this.SetState('display');
	},
	
	SetItem: function SetItem(){
		this.Item = new PersistentItem();
		this.Item.Type = this.Type;
	},
	
	/**
	 * Begins the edit poperation.
	 */
	Edit: function Edit(){
		if (!this.BeforeEdit()) {
			return;
		}
		this.CommitEditingChanges();
		AjaxEditTableItem.editingItem = this;
		this.SetState('editing');
	},
	
	/**
	 * This method is responsible for returning wheither or not
	 * it is valid to begin editing a given object.  Right now it just checks
	 * the state to ensure that it is display. You can override this
	 * to ensure any other necessary editing preconditions
	 */
	BeforeEdit: function BeforeEdit() {
		if (this.state != 'display') {
			return false;
		}
		return true;
	},
	
	/**
	 * Cancels the editing operation. Override this
	 * depending on your concrete subclass.
	 */
	CancelEdit: function CancelEdit(){
		//implement in sub class
	},
	
	/**
	 * A quick convenience function that will
	 * save any existing changes.
	 */
	CommitEditingChanges: function CommitEditingChanges() {
		if(AjaxEditTableItem.editingItem != null){
			AjaxEditTableItem.editingItem.Save();
			AjaxEditTableItem.editingItem = null;
		}
	},
	
	/**
	 * A funciton to provide flexability to return the "edit souce row"
	 * The Edit Source Row is the row that generated some sort of editing.  It is used
	 * as a DOM point of reference for inserting new rows that are coming back from the server
	 * so they are inserted into the correct position in the table
	 */
	GetEditSourceRow: function GetEditSourceRow(){
		return this.row;
	},
	
	/**
	 * Called before the save operation is sent to the server to confirm that
	 * it is a valid operation.  Subclasses may override this for extra checks
	 * to ensure saving is valid.  The default implementation simply checks
	 * the current state to ensure it is not already in save mode, preventing
	 * double saves from occurring
	 */
	BeforeSave: function BeforeSave(){
		if(this.state == 'saving'){
			return false;
		}
		return true;
	},
	
	/**
	 * Saves this item.  This involves sending a request to the server
	 * Once the request returns, the AfterSave method is called
	 */
	Save: function Save(aParams, aOptions) {
		aParams = aParams || [];
		aOptions = aOptions || {};
		
		if(!this.BeforeSave(aOptions)){
			return;
		}
		this.SetState('saving');
		var oRequest = new ServerRequest();
		oRequest.addAction('Save', this.Item, (aOptions.Method || 'AsyncSave'), aParams);
		// here we need to create a closure around the editingRow incase a new one is created while this request is still in process
		var EditSourceRow = this.GetEditSourceRow();
		if ('EditSourceRow' in aOptions) { //allow custom specification of edit row (e.g. when copying instead of updating)
			EditSourceRow = aOptions.EditSourceRow;
		}
		oRequest.onComplete = this.AfterSave.bind(this, EditSourceRow, aOptions);  
		oRequest.execute();
	},
	
	/**
	 * This method is called on the response of the save from the server.
	 * It is passed, most importantly, the new HTML for the row that was created or edited.
	 * Currently if there is no ID present in the same we assume there was an error 
	 * TODO: we really need to actually return error text to have better descriptions
	 * for the user what problem occurred.
	 */
	AfterSave: function AfterSave(sourceEditRow, aOptions, results) {
		//Handle Error message (e.g. duplicate)		
		if (isNaN(parseInt(results.Save.ID))) {
			this.SaveFailure(sourceEditRow, results);
		}
		else{
			this.SaveSuccess(sourceEditRow, aOptions, results);
		}
	},
	
	/**
	 * Called in the event that there was no ID returned from a save.
	 * Use this function for any special clean up when things don't go right
	 * This can happen most often on duplicates.
	 */
	SaveFailure: function SaveFailure(sourceEditRow, results){
		this.Display();
		alert(results.Save.stripTags());
	},
	
	/**
	 * Returned when we are given a successful save and
	 * new HTML for an updated row element
	 */
	SaveSuccess: function SaveSuccess(sourceEditRow, aOptions, results){
		var itemObj = null;
		if( aOptions.DontInsertNewItemRow ){
			if( ! this.newElement ){
				this.AfterInsertNewItemRow( sourceEditRow );
				this.GetDelegate().RowCountChanged(-1);
			}
		}
		else{
			itemObj = this.InsertNewItemRow(sourceEditRow, results.Save.AdminRow);
		}
		
		if(!this.Display() && !this.GetDelegate().isFastEntry && itemObj ){
			// If we didn't Display, we're still editing. Reset editingItem and continue.
			AjaxEditTableItem.editingItem = itemObj;
			AjaxEditTableItem.editingItem.SetState('editing');
		}
	},	
	
	/**
	 * Begins the delete process. Since many classes will have potenintal
	 * warning methods around delete, we name this AttemptDelete.  It calls
	 * the Persistent Item's delete function without a force flag enabled
	 */
	AttemptDelete: function AttemptDelete() {
		if (AjaxEditTableItem.deletingItem != null) {
			return;
		}
		this.BeforeEdit();
		this.SetState('deleting');		
		this.Item.Delete({'complete' : this.AfterAttemptDelete.bind(this) });
		AjaxEditTableItem.deletingItem = this;
	},	
	
	/**
	 * Response for the attempt delete.  This can either
	 * see that the delete is successful and do nothing
	 * or it can alert the user of any warning message that the
	 * attempted delete returned
	 */
	AfterAttemptDelete: function AfterAttemptDelete(Results) {
		if (Results.Delete) {
			if (Results.Delete.Result){
				if (Results.Delete.Result == 'OK') {
					this.AfterDelete();
				}
				else {
					YUIPopupFormProvider.ShowModalForm(Results.Delete.Result, {
						FormID : this.EditFormID,
						height: 350,
						CloseCallback : this.CloseButtonHit.bind(this),
						Header : 'Delete Warning'
					});
				}
			}
			else if(Results.Delete.Body){
				YUIPopupFormProvider.ShowModalForm(Results.Delete.Body, {
					FormID : this.EditFormID,
					height: 350,
					CloseCallback : this.CloseButtonHit.bind(this),
					Header : Results.Delete.Header || 'Delete Warning',
					Footer: Results.Delete.Footer
				});
			}
			
		}
		this.Display();
	},
	
	CloseDeleteDialog: function CloseDeleteDialog() {
		//this.dialog.hide();
		//this.dialog = null;
		YUIPopupFormProvider.DisposeModalForm();
		AjaxEditTableItem.deletingItem = null;		
		this.SetState('display');
	},	
	
	ForceDelete: function ForceDelete(bNotify) {
		//JG 3/25/09 in case you need BeforeDelete it would go here.
		this.CloseDeleteDialog();
		this.Item.Delete({},{'FORCE_DELETE': true, 'Notify': bNotify});
		this.AfterDelete();
	},
	
	/**
	 * After delete cleans this item.  It means
	 * that this item is now deleted and dead
	 */
	AfterDelete: function AfterDelete(){
		if(this.row && this.row.parentNode){
			Element.remove(this.row);
		}
		this.row = null;
		AjaxEditTableItem.deletingItem = null;
		//TODO inform the delegate of the deletion to free up resources
		this.GetDelegate().ItemDeleted(this);
	},
	
	/**
	 * sets the row back into 'display mode', meaning it is at its neutral state
	 */
	Display: function Display() {
		if(this.state == 'display' || this.state == 'saving' || this.state == 'canceling'){
			if(AjaxEditTableItem.editingItem == this){
				AjaxEditTableItem.editingItem = null;
			}
			this.SetState('display');
			return true;
		}
		else {
			return false;
		}
	},		
	//end CRUD
	
	/**
	 * This inserts a row into the table, eitehr a new row or the replacement of an edited row
	 * this registers the item with the delegate for future interaction from the user
	 */
	InsertNewItemRow: function InsertNewItemRow(sourceEditRow, rowHTML){
		if( this.row && this.row.parentNode ){
			rowHTML = '<table><tbody>' + rowHTML + '</tbody></table>';
			var renderdiv = new Element('div');
			renderdiv.innerHTML = rowHTML;
			//        div       table      tbody      row 
			var row = renderdiv.firstChild.firstChild.firstChild;
			this.InsertRowIntoTable(sourceEditRow, row);
			this.AfterInsertNewItemRow(sourceEditRow, row);
			var itemObj = this.RegisterNewElement(row);
			this.GetDelegate().RowCountChanged(this.newElement ? 1 : 0);
			
			return itemObj;
		}
	},
	InsertRowIntoTable: function InsertRowIntoTable(sourceEditRow,row) {
		if(this.addRowOnBottom){
			Element.insert(sourceEditRow, {before: row});
		}
		else{
			Element.insert(sourceEditRow, {after: row});
		}
	},	
	/**
	 * Clean up after inserting the new row.  A hook for subclasses
	 * to do anything necessary.
	 */
	AfterInsertNewItemRow: function AfterInsertNewItemRow(sourceEditRow, newRow){
		if(sourceEditRow && sourceEditRow.parentNode){
			Element.remove(sourceEditRow);
		}
	},
	
	RegisterNewElement: function RegisterNewElement(row){
		//implement sub-class
	},	
	/**
	 * sets the state variable of this item
	 * The state controles not only its appearance 
	 * (as the color of the row will reflect its state) but
	 * also its behavior, as certain operations aren't permitted in
	 * different states.
	 */
	SetState: function SetState(newState){
		this.state = newState;
		var row = this.GetEditSourceRow();
		if(!row){
			return;
		}
		if(this.state == 'deleting'){
			Element.addClassName(row, 'deleting');//'#ffdcd9;
		}
		else if(this.state == 'saving'){
			Element.addClassName(row, 'saving');//#e8f2ff;
		}
		else if(this.state == 'editing'){
			Element.addClassName(row, 'editing');//#e8f2ff;
		}
		else{
			Element.removeClassName(row, 'deleting');
			Element.removeClassName(row, 'editing');
			Element.removeClassName(row, 'saving');		
		}	
	},
	GetDelegate: function GetDelegate(){
		//TODO: This method doesn't find the correct Delegate if there is more than one defined [keyed as Type_1, Type_2, etc]
		return TableDelegates[this.Item.Type];
	},
	CloseButtonHit: function CloseButtonHit(){
		if (AjaxEditTableItem.editingItem) {
			AjaxEditTableItem.editingItem.SetState('canceling');
			AjaxEditTableItem.editingItem.Display();
		}
		else if (AjaxEditTableItem.deletingItem) {
			AjaxEditTableItem.deletingItem.SetState('canceling');
			AjaxEditTableItem.deletingItem.Display();
		}
	},
	/**
	 * Get Parameters used for saving
	 * @return JSON Object
	 */
	GetSaveParameters: function GetSaveParameters() {
		return {};
	}
});

var AjaxEditTableModalFormItem = Class.create(AjaxEditTableItem, {
	EditFormID: 'EditForm',
	EditFormWidth: null,	// Width via CSS
	EditFormHeight: null,	// Auto-height
	EditFormFocusElement: null,	// Auto-focus first element
	
	initialize: function initializeAjaxEditTableModalFormItem($super, options){
		$super(options);
		//these items are shared in this context
		this.row = this.element; 
		if(this.newElement){
			//This is a trick for external buttons in Poup forms
			//We use this row as an anchor but do not show it
			//instead we regers the position with the delegate so that
			//when the "add new" button is hit it has reference
			//to delgate the task to this instance
			this.row.hide();
			this.GetDelegate().NewItem = this;
			this.Populate = false;
		}
		else if(this.element){
			//the item ID is on the row element for modal items
			if (this.element && this.element.id) {
				var idParts = this.element.id.split('_');	// Type_row_ID
				this.Item[this.PrimaryKey] = idParts[2];	
			}
		}
	},
	
	Edit: function Edit($super, aOptions){
		aOptions = aOptions || {};
		//need to refactor this into ShouldEdit, BeforeEdit change
		if (this.state == 'editing'){
			return;
		}
		$super(aOptions);
		if (this.state != 'editing') {//kloogy check - confirms super::Edit was successful
			return;
		}
		var oParameters = aOptions.Parameters || { }; //AE 7/20/09 allow passing parameters
		YUIPopupFormProvider.ModalContentRequest(this.Item, (aOptions.Method ? aOptions.Method : 'GetEditForm'), {
			FormID : this.EditFormID,
			FormClass : this.GetDelegate().editFormClass,
			Header : this.GetEditFormTitle(),
			width: this.EditFormWidth,
			height: this.EditFormHeight,
			FocusElement: this.EditFormFocusElement,
			CloseCallback : this.CloseButtonHit.bind(this),
			Parameters: oParameters,
			ShowCallback: this.ShowEditFormCallback.bind(this)
		});	
	},
	Import: function Import() {
		this.BeforeEdit();
		if(this.state == 'editing'){
			return;
		}
		AjaxEditTableItem.editingItem = this;
		this.SetState('editing');
		
		YUIPopupFormProvider.ModalContentRequest(this.Item, 'GetImportForm',{
			FormID : 'EditForm',
			FormClass : 'ImportForm',
			Header : this.GetDelegate().ImportHeader
		});		
	},
	ShowEditFormCallback: function ShowEditFormCallback() {
		// override
	},
	BeforeEdit: function BeforeEdit($super) {
		Object.extend(this.Item, this.GetDelegate().GetParameters());
		return $super();
	},
	/**
	 * Allows customs subclasses to change the form title 
	 * for editing
	 */
	GetEditFormTitle: function GetEditFormTitle(){
		//TODO change default in some intelligent way
		if(this.newElement){
			return 'Create '+this.GetDelegate().ItemLabel;
		}
		return 'Edit '+this.GetDelegate().ItemLabel;
	},
	
	CancelEdit: function CancelEdit(){
		YUIPopupFormProvider.DisposeModalForm();
		this.CloseButtonHit();
	},
	
	BeforeSave: function BeforeSave( aOptions ) {
		aOptions = Object.extend({
			DisposeModalForm: true
		}, aOptions || {});
		
		if(!this.ValidateForm()){
			return false;
		}
		
		Object.extend(this.Item, this.GetDelegate().GetParameters());
		
		if ($('editItemForm')) {
			var formObjects = Form.getElements('editItemForm'); //AE 5/19/09 changed from $('editItemForm).getElements() -
			var isRadioObjectValueFound = [];
			var isArrayObjectCreated = [];
			for(var i = 0; i < formObjects.length; i++){
				var formEl = formObjects[i];
				// skip this form object if it doesn't have a name, 
				// or if its radio group value has already been found
				if( ! formEl.name || isRadioObjectValueFound[formEl.name] ){
					continue;
				}
				else if( formEl.type == 'radio' ){	// handle radio objects separately
					var radioValue = Form.getInputs( 'editItemForm', 'radio', formEl.name ).find( function( radio ){
						return radio.checked;
					} ).value;
					isRadioObjectValueFound[formEl.name] = true;
					this.Item[formEl.name] = radioValue;
					
					continue;
				}
				else if( /\[\]$/.test( formEl.name ) ){	// handle array-like fields that look like "fieldName[]"
					var formArrayName = formEl.name.replace( /\[\]$/, '' );
					
					if( ! isArrayObjectCreated[formArrayName] ){
						isArrayObjectCreated[formArrayName] = true;
						this.Item[formArrayName] = [];
					}
					this.Item[formArrayName].push( formEl.getValue() );
					
					continue;
				}
				else{	// Otherwise, we know that this is a "normal" form field that can be accessed by name, so we get its value.
					this.Item[formEl.name] = formEl.getValue();
				}
			}
		}
		if(aOptions.DisposeModalForm){
			YUIPopupFormProvider.DisposeModalForm();
		}
		return true;
	},
	
	SaveSuccess: function SaveSuccess($super, sourceEditRow, aOptions,results){
		$super(sourceEditRow, aOptions, results);
		if(this.newElement){
			//clean out the old saved item so it doesn't show up  
			//when the form loads again
			this.SetItem();
		}
	},
	
	ValidateForm: function ValidateForm(){
		var missingFields = false;
		if($('editItemForm')){
			//reset any previously missing fields
			$('editItemForm').select('.requiredFieldRow').each(function(row){
				Element.removeClassName(row, 'MissingField');
			});
			var requiredFields = $('editItemForm').select('.required');
			for(var i = 0; i < requiredFields.length; i++ ){
				var UIDynamicSelectListWrapper = Element.up(requiredFields[i],'.UIDynamicSelectListWrapper');
				if(UIDynamicSelectListWrapper) {
					if (Element.select(UIDynamicSelectListWrapper, '.UIDynamicItemOption').length) {
						continue;
					}
				}
				
				if($F(requiredFields[i]) === '') {
					missingFields = true;
					var parentContainer = AjaxEditTableItem.GetParentWithClassForChild('requiredFieldRow', requiredFields[i]);
					if(parentContainer){
						Element.addClassName(parentContainer, 'MissingField');
					}
				}
			}
		}
		if(missingFields){
			alert('Please complete all required fields');
		}
		return !missingFields;
	},
	
	AfterInsertNewItemRow: function AfterInsertNewItemRow($super, sourceEditRow, newRow){
		if(!this.newElement){
			$super(sourceEditRow, newRow);
		}
		if(typeof supersleight != 'undefined' && newRow){	// IE PNG Fix
			supersleight.run(newRow);
		}
	},
	
	RegisterNewElement: function RegisterNewElement(row){
		return this.GetDelegate().RegisterItemElement(row, true);
	}
});

var AjaxEditTableModalFormItemWithLineItems = Class.create(AjaxEditTableModalFormItem, {
	initialize: function initializeAjaxEditTableModalFormItemWithLineItems($super,options) {
		$super(options);
		//keep a count of the number of line items.  -1 means unknown
		this.LineItemCount = -1;
	},
	EditLineItems: function EditLineItems(){
		if(this.state == 'editing'){
			return;
		}
		AjaxEditTableItem.editingItem = this;
		this.SetState('editing');
		
		YUIPopupFormProvider.ModalContentRequest(this.Item, this.GetEditLineItemsMethod(),{
			FormID : this.EditFormID,
			FormClass : 'EditLineItems',
			Header : this.GetEditLineItemHeader(),
			width: this.EditFormWidth,
			height: this.EditFormHeight,
			FocusElement: 'SimpleLineItem',
			ShowCallback : this.ShowEditLineItem.bind(this),
			CloseCallback : this.EditLineItemsClosed.bind(this)
		});	
	},
	ShowEditLineItem: function ShowEditLineItem(results){
		//make the simple item draggable
		new Draggable($('SimpleItemDragContainer'), { 
			revert : true,
			zindex: 15000	// JR 08/12/2009 Set z-index higher than YUI Popup Form
		});

		new Draggable($('ContentAreaContainer'), { 
			revert : true,
			zindex: 15000,
			onStart:this.ReselectSelectList.bind(this) //AE 5/19/08 fix bug # 8405 - first option gets selected
		});
		new Draggable($('ContentSubAreaContainer'), { 
			revert : true,
			zindex: 15000,
			onStart:this.ReselectSelectList.bind(this) //AE 5/19/08 fix bug # 8405 - first option gets selected
		});
		this.StandardsSelectionController = new StandardsSelectionController(this);
		this.NewStandardsLoaded();
		
		//make the container a drop target
		Droppables.add($('FieldContainer'),{
			hoverclass : 'dropHover',
			accept : 'StandardSelectionItem',
			onDrop : this.CreateLineItem.bind(this)
		});
		//minus 1 for place holder
		this.LineItemCount = $('FieldContainerScroll').childElements().size() - 1;
		this.InitSortableLineItems();
	},
	ReselectSelectList: function ReselectSelectList(draggable) {		//AE 5/19/08 this method fixes bug # 8405 - first option gets selected (allegedly due to superghosting)
		for (i=0; i< draggable.element.childNodes.length; i++) {
			childNode = draggable.element.childNodes[i];
			if (childNode.tagName == 'SELECT') {
				$(childNode.name).value = $F(childNode); //set the value using the value from the cloned selectlist
			}
		}
		this.StandardsSelectionController.SetupObservers();
	},
	//delegate call from standards selection controller
	NewStandardsLoaded: function NewStandardsLoaded(){
		$('StandardItems').select('.StateStandardLevel, .StateStandard').each(function(obj){
			new Draggable(obj, {
				revert : true,
				zindex: 15000,
				onStart: (function(){
					//Element.setStyle('StandardItems', {overflow: 'visible'});
				}).bind(this),
				onEnd: (function(){
					//Element.setStyle('StandardItems', {overflow: 'auto'});
				}).bind(this)
			});
		});
	},
	InitSortableLineItems: function InitSortableLineItems(){
		Position.includeScrollOffsets = true;
		Sortable.create('FieldContainerScroll',{
			tag: 'div',
			only: 'FieldRow',
			scroll: 'FieldContainerScroll',
			onUpdate: this.SaveLineItemListSortOrder.bind(this)
		});
	},
	SaveLineItemListSortOrder: function SaveLineItemListSortOrder() {
		var ItemList = this.GetNewLineItemList();//new PersistentItemList('RubricFieldList');
		ItemList.saveSortOrder();
	},
	EditLineItemsClosed: function EditLineItemsClosed(){
		AjaxEditTableItem.editingItem = null;
		this.SetState('display');
	},
	CreateLineItem: function CreateLineItem(draggable){
		var saveItem = new PersistentItem();
		//saveItem.ClassEvaluationID = this.Item.ClassEvaluationID;
		saveItem = this.SetPrimaryKeyOnLineItem(saveItem);
		saveItem.Type = this.GetLineItemType();//'RubricField';
		//save simple
		var idParts = draggable.id.split('_');
		var containerID = idParts[0]; //superghost draggable.id should be in form: clone_[realid]
		if(containerID == 'SimpleItemDragContainer'){
			var name = $F('SimpleLineItem'); 
			if(name == ''){
				alert('Please enter a name for the Simple Objective'); //TODO: make method to get alert?
				return;
			}
			saveItem.Name = name;
		}
		else if(containerID == 'ContentAreaContainer'){
			saveItem.ContentAreaID = $F('ContentAreaID');
		}
		else if(containerID == 'ContentSubAreaContainer'){
			saveItem.ContentSubAreaID = $F('ContentSubAreaID');
		}
		//save standards based
		else{			
			
			var type = idParts[0];
			var id = idParts[1];
			saveItem[type+'ID'] = id;
		}
		saveItem.Save({ 'complete' : this.oncomplete_CreateLineItem.bind(this)});
	},
	
	oncomplete_CreateLineItem: function oncomplete_CreateLineItem(results){
		$('SimpleLineItem').setValue('');
		$('DropPlaceholder').hide();
		$('FieldContainerScroll').insert(results.Save.AdminRow);
		$('FieldContainerScroll').scrollTop = $('FieldContainerScroll').scrollHeight;
		this.InitSortableLineItems();
		this.LineItemCount++;
		//recreate the drop target becasue it is larger now
	},
	
	EditLineItem: function EditLineItem(fieldContainer){
		if(!fieldContainer.hasClassName('editing')){
			fieldContainer.addClassName('editing');
			var editItem = this.GetLineItemFromElement(fieldContainer);
			editItem.Populate = true;
			var oRequest = new ServerRequest();
			oRequest.addAction('EditLineItem', editItem, 'Edit'+this.GetLineItemType());
			oRequest.onComplete = this.oncomplete_EditLineItem.bind(this, fieldContainer);
			oRequest.execute();	
		}		
	},
	oncomplete_EditLineItem: function oncomplete_EditLineItem(fieldContainer, results){
		fieldContainer.update(results.EditLineItem);
	},
	
	UpdateLineItem: function UpdateLineItem(fieldContainer){
		if(!fieldContainer.hasClassName('saving')){
			fieldContainer.addClassName('saving');
			var saveItem = this.GetLineItemFromElement(fieldContainer);
			saveItem.GradingSchemeID = $F(this.GetLineItemType()+'GradingScheme_' + saveItem.ID);
			saveItem.Name = $F('LineItemName_' + saveItem.ID);
			var oRequest = new ServerRequest();
			var aParams = [{ 'FieldsToUpdate' : [ 'GradingSchemeID', 'Name' ] }];
			oRequest.addAction('UpdateLineItem', saveItem, 'Update'+this.GetLineItemType(), aParams);
			oRequest.onComplete = this.oncomplete_UpdateLineItem.bind(this, fieldContainer);
			oRequest.execute();	
		}	
	},
	oncomplete_UpdateLineItem: function oncomplete_UpdateLineItem(fieldContainer, results){
		fieldContainer.update(results.UpdateLineItem);
		fieldContainer.removeClassName('saving');
		fieldContainer.removeClassName('editing');
	},
	
	DeleteLineItem: function DeleteLineItem(fieldContainer){
		if(!fieldContainer.hasClassName('deleting')){
			fieldContainer.addClassName('deleting');
			var deleteItem = this.GetLineItemFromElement(fieldContainer);
			deleteItem.Delete({ complete : this.oncomplete_DeleteLineItem.bind(this, fieldContainer)});
		}	
		this.LineItemCount--;
	},
	oncomplete_DeleteLineItem: function oncomplete_DeleteLineItem(fieldContainer, results){
		Effect.Fade(fieldContainer, {
			duration: .25
		});
		setTimeout((function(){
			Element.remove(fieldContainer);
		}).bind(this), 1000);
	},
	
	GetLineItemFromElement: function GetLineItemFromElement(fieldContainer){
		var field = new PersistentItem();
		field.Type = this.GetLineItemType();//'RubricField';
		field.ID = fieldContainer.id.split('_')[1];
		field = this.SetPrimaryKeyOnLineItem(field);//field.ClassEvaluationID = this.Item.ClassEvaluationID;
		return field;
	}
});


var AjaxEditTableFastEntryItem = Class.create(AjaxEditTableItem, {
	initialize: function initializeAjaxEditTableFastEntryItem($super, options){
		$super(options);
		if(this.newElement){
			//our input isn't created on click - we always have a textarea in the new standard box
			this.input = Element.down(this.element, 'textarea');
			//if the user tabs to the box we need to handle it like a click;
			Event.observe(this.input, 'focus', this.CommitEditingChanges.bind(this));
		}
	},
	
	SetItem: function SetItem($super){
		$super();
		if(!this.newElement){
			this.Item[this.PrimaryKey] = this.element.id.split("_")[1];
		}		
	},
	
	BeforeEdit: function BeforeEdit($super) {
		if(this.newElement){
			this.CommitEditingChanges();
			return false;
		}
		return $super();
	},
	
	Edit: function Edit($super){
		if (this == AjaxEditTableItem.editingItem) {
			return;
		}
		$super();
		if (this.state != 'editing') {//kloogy check - confirms super::Edit was successful
			return;
		}
		this.valueBeforeEditing = this.element.innerHTML;
		this.input = new Element('textarea',{'rows' : 2, 'cols' : 40, 'tabindex' : 50 } );	//in-line editing effect
		Element.insert(this.input, this.element.innerHTML.br2nl().stripTags());
		Element.update(this.element, this.input);
		this.saveControls = AjaxEditTableItem.CreateEditControls();
		Element.insert(this.element, this.saveControls);
	
		//JG 1/6/09: we don't want this to fire on update, rather do it when another item is selected
		Element.observe(this.input,'keyup', function(){
			AjaxEditTableItem.AutoGrowTextArea(this.input);
		}.bind(this));
		this.input.activate();
		AjaxEditTableItem.AutoGrowTextArea(this.input);
		this.SetState('editing');	
	},
	
	CancelEdit: function CancelEdit($super) {
		this.SetState('canceling');	
		if (this.newElement) {	
			Element.remove(this.editingRow.row);
			this.editingRow = null;
		}
		else {
			Element.insert(this.element, this.valueBeforeEditing);
			if(this.saveControls != null){
				Element.remove(this.saveControls);
				this.saveControls = null;
			}
			if(this.input != null){
				Element.remove(this.input);
				this.input = null;
			}
		}
		this.Display();
	},
	AddEditingRow: function AddEditingRow(){
		this.CommitEditingChanges();
		AjaxEditTableItem.editingItem = this;
		
		var newRow = new Element('tr');			
		Element.insert(newRow,new Element('td' , {'class' : 'draggableCell' }).update('&nbsp'));
		var itemCell = new Element('td',{ 'class' : 'adminDisplayTableCell'});
		
		var newDiv = new Element('div', {'class' : this.element.className });
		var newInput = new Element('textarea',{ 'rows' : 2, 'cols' : 40, 'tabindex' : 50});
		
		//push any existing text into the new text area
		if (this.input != null) {
			newInput.update(this.input.value);
			this.input.value = '';
		}
		newDiv.update(newInput);
		Element.insert(itemCell, newDiv);
		Element.insert(newRow, itemCell);
		
		var ActionTd = new Element('td' , {'class' : 'adminDisplayTableCell' });
		Element.insert(ActionTd,  '&nbsp;');
		Element.insert(newRow,ActionTd);
		this.saveControls = AjaxEditTableItem.CreateEditControls();
		Element.insert(newDiv, this.saveControls);	
		this.editingRow = { row : newRow, input : newInput };
		if(this.addRowOnBottom){
			this.row.parentNode.insertBefore(this.editingRow.row, this.row);
		}
		else{
			this.row.parentNode.insertBefore(this.editingRow.row, this.row.nextSibling);			
		}

		this.editingRow.input.focus();
		//Safari/chrome: move to end of text area
		if(this.editingRow.input.setSelectionRange){ 			
			this.editingRow.input.setSelectionRange(this.editingRow.input.value.length,this.editingRow.input.value.length);
		}
		//IE: move to end of text area
		else {
			var range = this.editingRow.input.createTextRange();
			range.moveStart( 'character', this.editingRow.input.value.length - 0 );
			range.moveEnd( 'character', 0 );
			range.select();
		}
		Element.observe(this.editingRow.input,'keyup',function(){ 
			AjaxEditTableItem.AutoGrowTextArea(this.editingRow.input); 
		}.bind(this));
		this.SetState('editing');
	},
	
	GetEditSourceRow: function GetEditSourceRow(){
		if(this.newElement){
			if(this.editingRow != null){
				return this.editingRow.row;
			}
			else{
				return null;
			}
		}
		else{
			return this.row;
		}
	},
			
	BeforeSave: function BeforeSave(){
		var entryField = this.fastEntryField;
		if (this.newElement) {
			var newValue = this.editingRow.input.value;
			Form.Element.disable(this.editingRow.input);
		}
		else if(this.input != null){			
			var newValue = this.input.value;
			Form.Element.disable(this.input);
		}
		else{
			var newValue = this.element.innerHTML;
		}
		//if the value hasn't changed, or is blank cancel the edit
		if(newValue == this.valueBeforeEditing || newValue.length == 0){
			this.CancelEdit();
			return false;
		}
		
		this.Item[this.fastEntryField] = newValue;
		if(this.saveControls != null){
			Element.remove(this.saveControls);
			this.saveControls = null;
		}
		return true;
	},
	Save: function Save($super, aParams) {
		var aParams = aParams || [{ 'FieldsToUpdate' : [ 'Name'] }];
		return $super(aParams);
	},
	SaveFailure: function SaveFailure($super, sourceEditRow, results){
		$super(sourceEditRow, results);
		if (this.newElement) {
			Element.remove(sourceEditRow);	
		}
		else {
			this.CancelEdit();
		}
	},
	
	SaveSuccess: function SaveSuccess($super, sourceEditRow, aOptions, results){
		//super call addes element andr removes editing row operation in the super class
		if(this.newElement){
			$super(sourceEditRow, aOptions,results);
		}
		//otherwise just update the value, no need to replace the whole row.
		//here we need the new results from the server return so as to clean html
		else{
			this.element.innerHTML = results.Save.Value;
			this.Display();
		}
	},
	InsertRowIntoTable: function InsertRowIntoTable($super, sourceEditRow,row) {
		var tbody = Element.up(sourceEditRow, 'tbody');
		Element.insert(tbody, {bottom: row});
	},
	RegisterNewElement: function RegisterNewElement(row){
		var newContents = Element.down(row, "." + this.Item.Type);
		return TableDelegates[this.Item.Type].RegisterItemElement(newContents, true);
	}
});

/**** STATIC METHODS *******/
//All these methods can be called as static methods, state independent
AjaxEditTableItem.editingItem = null;
AjaxEditTableItem.deletingItem = null;
AjaxEditTableModalFormItem.EditingDialog = null;

AjaxEditTableItem.GetItemIDFromTargetElement = function GetItemIDFromTargetElement(element){
	var tblRow = AjaxEditTableItem.GetRowForChildElement(element);
	var editItem = Element.down(tblRow, ".EditItem");
	
	if(!editItem){
		return tblRow.id;
	}
	return editItem.id;
};


AjaxEditTableItem.GetParentWithTagForChild = function GetParentWithTagForChild(tag, child){
	var trgParent = child;
	while(trgParent && trgParent.tagName && trgParent.tagName.toUpperCase() != tag.toUpperCase()){
		trgParent = trgParent.parentNode;		
	}
	return trgParent;	
};

AjaxEditTableItem.GetParentWithClassForChild = function GetParentWithClassForChild(className, child){
	var trgParent = child;
	while(trgParent){
		if(trgParent.className && Element.hasClassName(trgParent, className)){
			return trgParent;
		}
		trgParent = trgParent.parentNode;		
	}
	return false;	
	
};

AjaxEditTableItem.GetRowForChildElement = function GetRowForChildElement(child){
	return AjaxEditTableItem.GetParentWithTagForChild('TR', child);
};

AjaxEditTableItem.CreateEditControls = function CreateEditControls(){		
	var saveButton = new Element('span',  { 'class' : 'saveButton AtlasButton'}).update('Save');
	var cancelButton = new Element('span',  { 'class' : 'cancelEdit AtlasButton'}).update('Cancel');
	var saveContainer = new Element('div', { 'class' : 'saveEditItem AtlasButtonWrapper'} );
	saveContainer.insert(saveButton);
	saveContainer.insert(cancelButton);
	return saveContainer;
};


AjaxEditTableItem.ToggleCollapse = function ToggleCollapse(collapser){
	if (collapser.src.indexOf('/common_images/arrow_expanded.png') > -1) {
		AjaxEditTableItem.Collapse(collapser);
	}
	else {
		AjaxEditTableItem.Expand(collapser);
	}	
};

AjaxEditTableItem.Expand = function Expand(collapser){
	Element.show(collapser.id.replace('collapser','SubTable'));
	collapser.src = '/common_images/arrow_expanded.png?v=' + ATLAS_VERSION;
};

AjaxEditTableItem.Collapse = function Collapse(collapser){
	Element.hide(collapser.id.replace('collapser','SubTable'));
	collapser.src = '/common_images/arrow_collapsed.png?v=' + ATLAS_VERSION;
};
//end collapse/expand functions

AjaxEditTableItem.AutoGrowTextArea = function AutoGrowTextArea(textarea) {
	// Source :  http://stackoverflow.com/questions/7477/autosizing-textarea
	textarea.rows = 1;
	$A(textarea.value.split("\n")).each( function(line) {
		textarea.rows += Math.ceil( line.length / textarea.cols ); // take into account long lines
	} );
};

AjaxEditTableItem.MakeRowDraggable = function MakeRowDraggable(row){
	var parentTable = AjaxEditTableItem.GetParentWithTagForChild('TABLE', row);
	if(parentTable.id) {
		var tableSortObject = window['tableDnD_'+parentTable.id];
		tableSortObject.makeDraggable(row.firstChild);
	}
};

/**
 * Overrides Ajax.InPlaceEditor to provide...
 * - submit event stopping capability
 * - onFormReady callback
 * - AtlasButton links
 */
if( Ajax.InPlaceEditor ){	// Only if Ajax.InPlaceEditor has been included
	Ajax.AtlasInPlaceEditor = Class.create(Ajax.InPlaceEditor, {
		handleFormSubmission: function($super, e) {
			var form = this._form;
		    var value = $F(this._controls.editor);
			var params = this.options.callback(form, value);
			if( params !== false ){
				$super(e);
			}
			else if(e){
				Event.stop(e);
			}
		},
		enterEditMode: function($super,e) {
			$super(e);
			this.triggerCallback('onFormReady',this._form);
		},
		createControl: function(mode, handler, extraClasses) {
			var text = this.options[mode + 'Text'];
			var link = document.createElement('a');
			link.href = '#';
			link.appendChild(document.createTextNode(text));
			link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
			link.className = 'AtlasButton editor_' + mode + '_link';
			if (extraClasses){
				link.className += ' ' + extraClasses;
			}
			this._form.appendChild(link);
			this._controls[mode] = link;
		}
	});
}
