// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]

/**
 * @file         aimauction.js
 * @fileoverview Collection of application level classes.<br/>
 * This collection implements the rich-internet-application tier of
 * the Auction Module for AIM (Auction Inventory Management),
 * which is a demonstration of the Gravey framework.
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     grvEDO.js
 * @requires     grvAJAX.js
 * @version      2.0
 */

////////////////////////////////////////////
//////////// Domain Objects ////////////////
////////////////////////////////////////////

Class(AuctionSummary,["unique Key","auction ID","item count"])
.Extends(EDOHolderSelectionItem);
/**
 * @class This class encapsulates the identifying attributes of an Auction.
 * @extends EDOHolderSelectionItem
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function AuctionSummary()
{
	/** @param {int} theKey unique key of this record
	 *  @param {String} theID auction ID
	 *  @param {int} itemCount count of items attached to this auction
	 */
	this.konstructor = function( theKey, theID, itemCount )
	{
		// define instance variables
		this.key	= theKey;
		this.bsID	= theID;
		this.count  = itemCount;
	}

	/** return "this" formatted for main menu item @type String */
	this.getDescription = function() {
		return grvDiamond(this.count)+this.bsID;
	}

	/** return "this" as human-readable string @type String */
	this.toString = function()
	{
		var		s = new Array();
				s.push( this.key );
				s.push( this.bsID );
		return	s.join( "-" );
	}

	/** set name to given value */
	this.updateProperties = function( ID ){
		this.bsID = ID;
	}
}


Class(ALIndex).Extends(EDOIndex);
/**
 * @class This class encapsulates an EDO index for Auctions/Lots.
 * @extends EDOIndex
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function ALIndex()
{
  if (!ALIndex.Auction){//keep this line above jsdoc comments!

	/** Static factory method to generate Auction-flavored index
	 * @param {int} index Auction index
	 * @type ALIndex
	 */
	ALIndex.Auction = function() {
		return new ALIndex(0,null);
	}
	/** Static factory method to generate Lot-flavored index
	 * @param {int} index Lot index
	 * @type ALIndex
	 */
	ALIndex.Lot = function( index ) {
		return new ALIndex(0,index);
	}
  }

	/** @param {int} optA index into auctions list for current Auction
	 *  @param {int} optL index into lots list for specified Auction
	 * If args not specified or if all are null then this represents "no selection".
	 */
	this.konstructor = function( optA,optL )
	{
		// define instance variables
		this.a = optA===undefined ? null : optA;
		this.l = optL===undefined ? null : optL;
	}

	/** return "this" as event handler arg list string @type String */	
	this.asEventArgs = function() {
		return " new ALIndex("+this.a+","+this.l+") ";
	}

	this.asString = function() {
		return this.invoke( this, this._noString, this._lotString )
		     + this._auctionString( this.a );
	}

	this._noString = function(){ return ""; }

	/** return auction description @type String */
	this._auctionString = function( aindex ){ 
		var a = gAuctionData._getAuction( aindex );
		return (a && a[ kStrIdAuctionID ]) ? a[ kStrIdAuctionID ] : "New Auction";
	}
	/** return lot description @type String */
	this._lotString = function( lindex ){
		lindex -= 0; //type cast to int
		return "Lot["+(lindex+1)+"] of ";
	}

	/** return whether "this" contains a selection @type boolean */
	this.hasSelection = function(){ return this.a!=null
	                                    || this.l!=null; }
	
	/** return whether "this" equals given EDOIndex @type boolean */
	this.equals = function(x){
		return x ? (this.a==x.a && this.l==x.l) : false;
	}

	/** Invoke the appropriate method given the index type and
	 * return its result. Methods are expected to have the signature<pre>
	 * self.method(this.subindex,optionalArgument)
	 *</pre>
	 * @param {Object} self the parent object of the method
	 * @param {Function} sMethod method to call if xi is Auction type
	 * @param {Function} lMethod method to call if xi is Lot     type
	 * @param {Object} optArg optional extra param to pass to method
	 * @return the result of the called method
	 */
	this.invoke = function(self,sMethod,lMethod,optArg){
		var aMethod;
		var i;
		if (this.l!=null) { aMethod = lMethod; i = this.l; } else
                          { aMethod = sMethod; i = this.a; }
		return aMethod.call( self, i, optArg );
	}

}

////////////////////////////////////////////////////////////////////////////////
// Constants defining property names for the auction/lot properties
////////////////////////////////////////////////////////////////////////////////

var kStrIdAuctionID   = 'Auction ID';
var kDateIdCreate     = 'Create Date';
var kDateIdLaunch     = 'Launch Date';
var kDateIdLockdown   = 'Lockdown Date';
var kDateIdCutoff     = 'Cutoff Date';
var kDateIdAward      = 'Bid Award Date';
var kDateIdClosed     = 'Closed Date';

// Lot fields
var kIntIdLotNum      = 'Lot#';	//should not be null
var kStrIdLotName     = 'Lot Name';
var kDateIdPayDate    = 'Effective Pay Date';
var kDateIdClosed     = 'Closed Date';
var kAmtIdExpPerItem  = 'Expenses/Item';
var kPctIdActualBid   = 'Actual Bid %';
var kPctIdCommission  = 'Commission %';
var kPctIdEstTarget   = '% of Est Target %';
var kIntIdContactsKey = 'Contact';
var kIntIdShippingKey = 'Shipping';
// transient Lot field(s)
var kIntIdBidderKey   = 'Bidder';


Class(ALEDO,["parent AuctionModel","item count",
	"edo Class","property values array",
	"edit Date","edit User","unique Key"])	
.Extends(EDO);
/**
 * @class This is the abstract base class for Auction/Lot EDOs.
 * @extends EDO
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function ALEDO()
{
	/** 
	 *  @param {AuctionModel} holder parent EDOHolderModel
	 *  @param {int} itemcount how many items are linked to this
	 *  @param {Function} CLAZZ this EDO's "class"
	 *  @param {array}  propValues array of property initial values
	 *  @param {String} editDate date of last edit
	 *  @param {String} editUser ID of last user to edit
	 *  @param {int} theKey unique key of this record
	 *  @param {int} optEditStatus optional edit status where the
	 * status codes are: 0=no-change; 1=delete; 2=create; 3=hidden; 4=update
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function
	(
		holder,
		itemcount,
		CLAZZ,
		propValues,
		editDate,
		editUser,
		theKey,
		optEditStatus,
		optName
	){
		this.souper( CLAZZ, holder, propValues, editDate, editUser,
					theKey, optEditStatus, optName );

		// define instance variables
		this.itemCount = itemcount;
	}

	this.toolTip = function( xi ){
		return this.edoToolTip(xi) + " Items="+this.itemCount;
	}

	this.toString = function() {
		return this[ kStrIdAuctionID ];
	}

	this.canDelete = function(){
		if (!this.isActive())
		  return "This is already deleted.";

		var c = this.itemCount;
		if (c>0)
		  return "This can't be deleted. It is referenced by "
		            +c+" Item"+(c==1?".":"s.");

		if (this.isLotZero && this.isLotZero())
		  return "Lot 0 must always exist.";

		return null;
	}

	/** push into given array all ALEDO-specific fields of the UpdateItem message
	 * formatted as<pre>
	 * "{parentAuctionID}~{property1}~...~{propertyN}"
	 * where the fields are defined as follows...
	 *     {parentAuctionID} = "my" parent auction ID
	 *     {propertyI}       = the i-th editable property of this EDO
	 *</pre>
	 * @param {Array} uFields  string array containing Update Msg fields
	 */
	this.pushUpdateFields = function( uFields )
	{
		uFields.push( encodeURIComponent(this.holder.getID()) );
		this.pushEncodedValues( uFields );
	}
}

Class(Auction,["property values array","edit Date","edit User",
			 "unique Key","parent holder","lot count"])
.Extends(ALEDO);
/**
 * @class This class encapsulates an Auction record.
 * @extends ALEDO
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function Auction()
{
	// init "static" Class methods/elements
	if (!Auction.Properties) {//keep this line above jsdoc comments!

		/** Static factory method to generate blank Auction @type Auction */
		Auction.Factory = function(index,holder)
		{
			var x = new Auction( null, grvGetTodayAsMMDDYYYY(),kUserID,null,holder,0 );
			x[ kDateIdCreate ] = x.date;
			return x;
		}

		// init static member variables
		Auction.UpdateID   = "Auction";
		Auction.Properties = [ kStrIdAuctionID,
				kDateIdCreate, kDateIdLaunch, kDateIdLockdown,
				kDateIdCutoff, kDateIdAward, kDateIdClosed ];

		Auction.EditRules  = [ MVCEditRule.kALNO,
				MVCEditRule.kDATE, MVCEditRule.kDate, MVCEditRule.kDate,
				MVCEditRule.kDate, MVCEditRule.kDate, MVCEditRule.kDate ];
		
		Auction.States     = [ kDateIdCreate, kDateIdLaunch, kDateIdLockdown,
				kDateIdCutoff, kDateIdAward, kDateIdClosed ];
	}

	/** @param {array} array of editable property values in same order as .Properties
	 *  @param {String} editDate date of last edit
	 *  @param {String} editUser ID of last user to edit
	 *  @param {int} theKey unique key of this record
	 *  @param {AuctionModel} holder parent EDOHolderModel
	 *  @param {int} itemcount how many Items are linked to this
	 *  @param {int} optEditStatus optional edit status
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function
	( propArray, editDate, editUser, theKey, holder, itemcount, optEditStatus, optName )
	{
		//Cant edit ID if items are already assigned to auction
		Auction.EditRules[0] = (itemcount>0) ? MVCEditRule.kRO : MVCEditRule.kALNO;

		this.souper( holder, itemcount, Auction,
		 propArray, editDate, editUser,	theKey, optEditStatus, optName );

		//if new object then init a few fields
		if (!propArray) this[kDateIdCreate] = editDate;

		//set up special validations
		this[ kStrIdAuctionID+MVCAttributeModel.kValidateSuffix ] = this.validateBSID;

		for (var i=0; i<Auction.States.length; ++i)
		  this[ Auction.States[i]+MVCAttributeModel.kValidateSuffix ] = this.validateDate;
	}

	/** custom validation method to verify legality of a phase date
	 * @type String
	 * @return error message or null if no error
	 */
	this.validateDate = function( dateID )
	{
		var dateStr = this[ dateID ];      if ( grvIsEmpty(dateStr) ) return null;
		var date    = Date.parse(dateStr); if ( date == null        ) return null;

		switch ( dateID )
		{
	      case kDateIdCreate:
			if (grvAfterNow(dateStr))
			  return "Create date can't be in the future.";
			break;

	      case kDateIdLaunch:
	        dateStr = this[kDateIdCreate];
			if (grvIsEmpty(dateStr) || grvAfter(dateStr,date+1))
			  return "Launch date must be after Create date";
			break;

	      case kDateIdLockdown:
	        dateStr = this[kDateIdLaunch];
			if (grvIsEmpty(dateStr) || grvAfter(dateStr,date+1))
			  return "Lockdown date must be after Launch date";
			break;

	      case kDateIdCutoff:
	        dateStr = this[kDateIdLockdown];
			if (grvIsEmpty(dateStr) || grvAfter(dateStr,date+1))
			  return "Cutoff date must be after Lockdown date";
			break;

	      case kDateIdAward:
	        dateStr = this[kDateIdCutoff];
			if (grvIsEmpty(dateStr) || grvAfter(dateStr,date+1))
			  return "Bid Award date must be after Cutoff date";
			break;

	      case kDateIdClosed:
	        dateStr = this[kDateIdAward];
			if (grvIsEmpty(dateStr) || grvAfter(dateStr,date+1))
			  return "Close date must be after Bid Award date";
			break;

	      default:
	      	return "Illegal Auction State";
		}

		return null;	//no error
	}
	
	/** return the current auction phase or null if in an illegal state @type String */
	this.nextState = function()
	{
		if (this.isValid()){
		  for (var i=0; i<Auction.States.length; ++i){
		    var stateID = Auction.States[i];
			if ( grvIsEmpty(this[stateID]) ) return stateID;
		  }
		}
		return null;//illegal state
	}

	/** custom validation method to verify uniqueness of auction ID
	 * @type String
	 * @return error message or null if no error
	 */
	this.validateBSID = function()
	{
		if ( this.hasDuplicateBSID()) return "Duplicate Auction IDs are not allowed.";
		return null;
	}

	/** verify uniqueness of Auction ID (as best we can)
	 * @type boolean
	 * @return true iff our auction ID duplicates any in parent auction summary list
	 */
	this.hasDuplicateBSID = function() {
		var key = this.holder.model.auctionKey( this[kStrIdAuctionID] );
		return (key>0) && (key!=this.key);
	}

	/** return a clone of this object @type Auction */
	this.clone = function() {
		return new Auction( this.propValues(), this.date, this.whom,
			this.key, this.holder, this.itemCount, this.editStatus );
	}

	this.canCreate = function(appendFlag) {
	  return this.holder.isTrue()
		 ? "Can't add a new Auction until pending changes have been saved"
		 : null;
	}

	/** If created, can we undo creation? @type boolean
	 * @param {boolean} appendFlag if true then append else insert
	 */
	this.canUncreate = function(appendFlag){ return false; }//override me
}


////////////////////////////////////////////////////////////////////////////////

var kSpecialLotName = "special unassigned items lot";

Class(Lot,["property values array","edit Date","edit User",
				"unique Key","parent holder","item count"])
.Extends(ALEDO);
/**
 * @class This class encapsulates a Auction "lot" record.
 * @extends ALEDO
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function Lot()
{
	// init "static" Class methods/elements
	if (!Lot.Properties) {//keep this line above jsdoc comments!

		/** Static factory method to generate blank Lot @type Lot */
		Lot.Factory = function(index,holder)
		{
			var x = new Lot( null,grvGetTodayAsMMDDYYYY(),kUserID,null,holder,0 );
			x[ kAmtIdExpPerItem ] = 0;
			x[ kPctIdActualBid  ] = 0;
			x[ kPctIdCommission ] = 0;
			x[ kPctIdEstTarget  ] = 0;
			x[ kIntIdBidderKey  ] = 0;
			x.expanded = true;
			return x;
		}

		// init static member variables
		Lot.UpdateID   = "Lot";
		Lot.Properties = [ kIntIdLotNum, kStrIdLotName,
				kDateIdPayDate, kDateIdClosed, kAmtIdExpPerItem,
				kPctIdActualBid, kPctIdCommission, kPctIdEstTarget,
				kIntIdContactsKey, kIntIdShippingKey,
				//transient field(s)
				kIntIdBidderKey
				 ];
		Lot.EditRules  = [ MVCEditRule.kINT, MVCEditRule.kEdit,
				MVCEditRule.kDate, MVCEditRule.kDate, MVCEditRule.kDec,
				MVCEditRule.kDec, MVCEditRule.kDec, MVCEditRule.kDec,
				MVCEditRule.kInt, MVCEditRule.kInt,
				//transient field(s)
				MVCEditRule.kEdit
				 ];
	}

	/** @param {array} array of editable property values in same order as .Properties
	 *  @param {String} editDate date of last edit
	 *  @param {String} editUser ID of last user to edit
	 *  @param {int} theKey unique key of this record
	 *  @param {AuctionModel} holder parent EDOHolderModel
	 *  @param {int} itemcount how many Items are linked to this
	 *  @param {int} optEditStatus optional edit status
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function
	( propArray, editDate, editUser, theKey, holder, itemcount, optEditStatus, optName )
	{
		this.souper( holder, itemcount, Lot,
		 propArray, editDate, editUser,	theKey, optEditStatus, optName );
		
		this[ kIntIdLotNum+MVCAttributeModel.kValidateSuffix ] = this.validateLotNum;
		this.expanded = false;
	}

	this.hasLotNum = function( n ){ return n == this[kIntIdLotNum]; }

	this.isLotZero = function(){
		return grvIsEmpty(this[kIntIdLotNum]) ? false
		 : (this[kIntIdLotNum]==0 && this[kStrIdLotName]==kSpecialLotName);
	}

	/** custom validation method to verify uniqueness of Lot number
	 * @type String
	 * @return error message or null if no error
	 */
	this.validateLotNum = function()
	{
		if ( this.isLotZero()         ) return null;
		if ( this[kIntIdLotNum]==0    ) return "Lot number zero is only for special lot.";
		if ( this.hasDuplicateLotNum()) return "Duplicate Lot numbers are not allowed.";
		return null;
	}

	/** custom validation method to verify uniqueness of Lot number
	 * @type boolean
	 * @return true iff our lot number duplicates any in parent auction
	 */
	this.hasDuplicateLotNum = function() {
		return this.holder.lotCount( this[kIntIdLotNum] ) > 1;
	}

	/** return a clone of this object @type Lot */
	this.clone = function() {
		return new Lot( this.propValues(), this.date, this.whom,
			this.key, this.holder, this.itemCount, this.editStatus );
	}

	this.toString = function() {
		var s = grvIsEmpty(this[kIntIdLotNum ]) ? "" : ("#"+this[kIntIdLotNum]);
		var t = grvIsEmpty(this[kStrIdLotName]) ? "" : (" "+this[kStrIdLotName]);
		return s+t;
	}

	/** Is it legal to edit "this"?
	 * @return errmsg or null
	 * @type String
	 */
	this.canEdit = function(){
		if ( kReadOnly ) return "Not allowed in read-only mode.";
		if (!this.isActive()) return "This is deleted.";
		return this.isLotZero() ? "Lot 0 is not editable." : null;
	}

	/** mark this entity as having been edited */
	this.editMe = function()
	{
		this.souper();
		_GetBidderLists( this[kIntIdBidderKey] );
	}
}

////////////////////////////////////////////
////////////// Data Models /////////////////
////////////////////////////////////////////

Class(AuctionModel,["Auction Selection Model"]).Extends(EDOHolderModel);
/**
 * @class This class encapsulates the data model of a particular Auction.
 * This model subscribes to a {@link AuctionSelectionModel}
 * so that new Lot lists can be downloaded whenever a new Auction is selected.
 * <p>NOTE: Other parts of AIM assume that the lot lists will never be null;
 * zero items is ok though.</p>
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function AuctionModel()
{
	/** @param {AuctionSelectionModel} auctionSelectionModel model of which Auction is selected
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( auctionSelectionModel, optName )
	{
		// init static DOM (must do this here instead of at static load time!)
		if (!AuctionModel.LoadXslDOM) AuctionModel.LoadXslDOM = grvLoadXslDOM( kXSLPath+"AIMaucToJS.xsl" );
		var holderName = optName ? optName : "Auction";
		this.souper( auctionSelectionModel, AuctionModel.LoadXslDOM, holderName );

		// init instance variables
		this._resetEDOs();
	}

	/** return the parent Auction ID for this model @type String */
	this.getID = function(){
		return this.auction ? this.auction[kStrIdAuctionID] : null;
	}

	/** return whether any EDOs are changed for this model @type boolean */
	this.isDirty = function()
	{
		if (this.auction && this.auction.inEdit()) return true;
		if (this.lList.isDirty()) return true;
		return false;
	}

	/** return whether all EDOs are valid for this model @type boolean */
	this.isValid = function()
	{
		if (!this.auction || !this.auction.isValid()) return false;

		//special case: if we are deleting auction then other errs dont matter
		if (this.auction.isDeleted()) return true;

		return this.lList.isValid();
	}

	/** clear EDOs */
	this._resetEDOs = function( optAuction )
	{
		this.auction = optAuction ? optAuction : null;

		if ( this.lList ) this.lList.reset();
		else this.lList = new EDOListModel( Lot );
	}

	/** set our Auction object   but dont publish */
	this._setAuction = function(o){ this.auction = o; }

	/** get a new Lot list @type ListModel */
	this._newLots = function(){ return this.lList.reset(); }

	/** return the debug details of "this" @type String */
	this.dump = function()
	{
		var		dStr = new Array();
				dStr.push( "AuctionModel>>>[" );
				dStr.push( "auction=" +(this.auction ?this.auction .dump():"null") );
				dStr.push( "lots="    +(this.lList   ?this.lList   .dump():"null") );
				dStr.push( "]" );
		return	dStr.join("\n");
	}

	/** create a new item for the selection list from current EDO
	 * @type EDOHolderSelectionItem
	 */
	this.createSelectionItem = function() {
		return new AuctionSummary( this.auction.key, this.auction[kStrIdAuctionID], 0 );
	}
	/** return the updated properties from the current EDO needed for select list update
	 * @return arbitrary value as needed @type Object
	 */
	this.getUpdatedProperty = function() {
		return this.getID();
	}

	/** return how many lots have the given lot number @type int */
	this.lotCount = function( lotnum )
	{
		var count = 0;
		this.lList.iterate( function(i,lot){ if (lot.hasLotNum(lotnum)) ++count; } );
		return count;
	}

	/** return whether there is currently an EDO @type boolean */
	this.hasEDO = function(){ return this.auction!=null; }

	/** return whether current EDO matches selection item "key"
	 * @type boolean
	 * @param {EDOHolderSelectionItem} item selection menu item
	 */
	this.keysMatch = function(item){ return this.auction.key==item.key; }

	/** return whether current EDO matches selection item "properties"
	 * @type boolean
	 * @param {EDOHolderSelectionItem} item selection menu item
	 */
	this.propMatch = function(item){ return this.getID()==item.bsID; }

	/** push into given array all AJAX SAVE Post params for this model
	 * @param {Array} posts  string array containing POST params
	 */
	this.pushPosts = function( posts )
	{
		this.auction.pushPosts( posts );	//THIS **MUST** BE FIRST!!
		this.lList  .pushPosts( posts );
	}

	/** return a new blank EDO of the type implied by EDOIndex @type EDO
	 * @param {EDOIndex} xi edo index
	 */
	this._newEDO = function(xi) { 
		return xi.invoke( this, Auction.Factory, Lot.Factory, this );
	}

	/** return the specified lot @type Lot
	 * @param {int} i index into lot list
	 */
	this._getLot = function( i ) {
	  return (i!=null && i>=0
	          && i<this.lList.getCount())
				 ? this.lList.getItem( i ) : null;
	}

	/** return the specified auction @type Auction
	 * @param {int} i index into auction list
	 */
	this._getAuction = function( i ) {
	  return (i==0 && this.auction)
	                ? this.auction : null;
	}
	/** return reference to the specified editable domain object
	 * @param {ALIndex} xi edo index
	 */
	this.getEDO  = function(xi){
		return xi.invoke( this, this._getAuction, this._getLot );
	}

	/** insert a new lot
	 * @param {int} i lot list index
	 * @param {EDO} edo the shipping
	 */
	this._insertLot = function( xi ) {
	    this.lList.addBefore( i, edo );
	}
	/** create new EDO which is inserted after selected EDO
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {EDOIndex} xi edo index
	 */
	this.createEDO = function(xi){
		xi.invoke( this, this._newAuction, this._insertLot, this.newEDO(xi) );
		return xi;
	}

	/** append a new lot
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {int} i lot list index
	 * @param {EDO} edo the lot
	 */
	this._appendLot = function( i, edo ) {
	    return ALIndex.Lot( this.lList.addItem( edo ) );
	}
	/** create a new auction
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {int} i auction list index
	 * @param {EDO} edo the auction
	 */
	this._newAuction = function( i, auctionEDO )
	{
		this._resetEDOs( auctionEDO );

		//auto-create special lot zero
		var lotEDO = this.newEDO(ALIndex.Lot(0));
		lotEDO[ kIntIdLotNum  ] = 0;
		lotEDO[ kStrIdLotName ] = kSpecialLotName;
		lotEDO.makeReadOnly();
		this._appendLot( 0, lotEDO );
		
		this.updateStamp();
		return ALIndex.Auction();
	}

	/** create new EDO which is appended at end of EDO list
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {EDOIndex} xi edo index
	 */
	this.appendEDO = function(xi){
		return xi.invoke( this, this._newAuction, this._appendLot, this.newEDO(xi) );
	}

	/** toggle the "expanded" flag of the specified EDOs.
	 * @param {boolean} doAllFlag if true, set all of the EDOs to new value
	 * @param {EDOIndex} xi edo index
	 */
	this.toggleExpand = function( doAllFlag, xi )
	{
		var edo = this.getEDO(xi);
		if (doAllFlag)
		{
			var newVal = !edo.isExpanded();
			this.lList  .toggleExpandAll( newVal );
			this.auction.toggleExpand   ( newVal );
		}
		else edo.toggleExpand();
		this.updateStamp();
	}
}


Class(AuctionSelectionModel,["AuctionSummary List Model"]).Extends(EDOHolderSelectionModel);
/**
 * @class This class encapsulates the data model for
 * "which Auction is currently selected".
 * @extends EDOHolderSelectionModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function AuctionSelectionModel()
{
	/** @param {ListModel} listModel {@link AuctionSummary} list */
	this.konstructor = function( listModel ){
		this.souper( listModel, "Auction " );
	}

	/** Return the list index of the specified Auction or <0 if not found. @type int */
 	this.findAuction = function( auctionID, auctionKey )
	{
		var list = this.model;
		var N    = list.getCount();
		if (N<1) return -2;//NO DATA IN auction SUMMARY LIST!!

		for (var i=0; i<N; ++i)
		  if (list.getItem(i).bsID == auctionID
		  ||  list.getItem(i).key  == auctionKey) return i;

		return -1;
	}

	/** return the key of the auction with the given auction ID @type int */
	this.auctionKey = function( auctionID )
	{
		var key = -1;
		this.model.iterate( function(i,ss){ if (ss.bsID==auctionID) key = ss.key; } );
		return key;
	}

	/** Select the "initially selected" EDOHolder<br>
	 * @param {boolean} selectFirst if selection critera has no match then
	 * if selectFirst==true then select the first in list
	 *                      else leave selection alone 
	 */
 	this.selectInitial = function( selectFirst )
	{
		var i = this.findAuction( kAuctionID, kAuctionKey );
		if (i==-2){ this.selectNothing(); return; }//EMPTY LIST!!
		if (i>= 0){ this.select(i);       return; }//found it
		if (selectFirst) this.select(0);
	}
}

////////////////////////////////////////////
////////////// Controllers /////////////////
////////////////////////////////////////////


Class(PhaseButtonController,["button text model"])
.Extends(MVCTxtButtonController);
/**
 * @class This class is a Button Controller that manages the button that
 * "moves" an auction from one phase to the next.
 * @extends MVCTxtButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function PhaseButtonController()
{
	/** @param {Auction} auction Auction we are to observe
	 */
	this.konstructor = function( auction ) {
		this.souper( this, "Declare Phase as met", "onPhaseBtnPressed" );

		// init instance variables
		this.auction = auction;
		this.enabld  = false;
	}

	/** Return whether this button should be enabled. @type boolean */
	this.isEnabled = function() {
		return kReadOnly ? false : (this.enabld && this.auction.inEdit());
	}

	/** simulate MVCScalarModel interface enough to supply button text @type String */
	this.getValue = function()
	{
		this.enabld = true;
		switch ( this.auction.nextState() )
		{
	      case kDateIdLaunch:	return "Launch Auction";
	      case kDateIdLockdown:	return "Lockdown Auction";
	      case kDateIdCutoff:	return "Cutoff Auction";
	      case kDateIdAward:	return "Award Bids";
	      case kDateIdClosed:	return "Close Auction";
		}
		this.enabld = false;
		return "N/A";
	}
}


////////////////////////////////////////////
////////////// Data Views  /////////////////
////////////////////////////////////////////

Class(AuctionView,["Auction object"])
.Extends(EDOView);
/**
 * @class This class produces a view of a {@link Auction}.
 * @extends EDOView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function AuctionView()
{
	/** @param {Auction} auction Auction we are to observe
	 */
	this.konstructor = function( auction ) {
		this.souper( auction, ALIndex.Auction() );
	}

	this.buildFields = function( HTML )
	{
		HTML.push( this.buildUprField( kStrIdAuctionID, 10, 10, false ) );

		HTML.push( this.buildDayField( kDateIdCreate  ,         false ) );
		HTML.push( this.buildDayField( kDateIdLaunch  ,         false ) );
		HTML.push( this.buildDayField( kDateIdLockdown,         false ) );

		HTML.push( this.buildDayField( kDateIdCutoff  ,         false ) );
		HTML.push( this.buildDayField( kDateIdAward   ,         false ) );
		HTML.push( this.buildDayField( kDateIdClosed  ,         false ) );

		HTML.push( "<td>"+ this.embedHTML( this.getWidgetID()+".btn",
							new PhaseButtonController( this.model ) )
				  +"</td>" );

	}
}

Class(LotView,["Lot object","EDOIndex of Lot"])
.Extends(EDOView);
/**
 * @class This class produces a view of a {@link Lot}.
 * @extends EDOView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function LotView()
{
	/** @param {Lot} edo EDO we are to observe
	 *  @param {EDOIndex} edoIndex index to EDO
	 */
	this.konstructor = function( edo, edoIndex ) {
		this.souper( edo, edoIndex );
	}

	this.preSelect = function()
	{
		var bk = this.model[ kIntIdBidderKey ];
		this  .sharedMenuSelect(   gBidderSelected    , kIntIdBidderKey );
		this.cascadedMenuSelect( _ContactsSelected(bk), kIntIdContactsKey );
		this.cascadedMenuSelect( _ShippingSelected(bk), kIntIdShippingKey );
	}

	this.buildFields = function( HTML )
	{
		var bidderKey = this.model[kIntIdBidderKey];

		HTML.push( this.buildStrField( kIntIdLotNum      ,  3,  3, false ) );
		HTML.push( this.buildStrField( kStrIdLotName     , 50, 50, false, 3 ) );
//		HTML.push( this.buildStrField( kIntIdBidderKey   , 10, 10, true  ) );
		HTML.push( this.buildPopField( kIntIdBidderKey   ,         true,
			gBidderSelected, 1, "onBidderSelect" ) );

		HTML.push( this.buildNulField(                             false ) );
		HTML.push( this.buildDayField( kDateIdPayDate    ,         false ) );
		HTML.push( this.buildPctField( kPctIdActualBid   ,         false ) );
		HTML.push( this.buildAmtField( kAmtIdExpPerItem  ,         false ) );
//		HTML.push( this.buildStrField( kIntIdContactsKey , 10, 10, true  ) );
		HTML.push( this.buildPopField( kIntIdContactsKey ,         true,
			_ContactsSelected(bidderKey),null,null,this.model.contName   ) );

		HTML.push( this.buildNulField(                             false ) );
		HTML.push( this.buildDayField( kDateIdClosed     ,         false ) );
		HTML.push( this.buildPctField( kPctIdCommission  ,         false ) );
		HTML.push( this.buildPctField( kPctIdEstTarget   ,         false ) );
//		HTML.push( this.buildStrField( kIntIdShippingKey , 10, 10, true  ) );
		HTML.push( this.buildPopField( kIntIdShippingKey ,         true,
			_ShippingSelected(bidderKey),null,null,this.model.shipName ) );
	}
}


Class(LotListView,["EDO ListModel"])
.Extends(EDOListView);
/**
 * @class This class produces a view of a List of Lot EDOs.
 * @extends EDOListView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function LotListView()
{
	/** @param {EDOListModel} edoListModel EDO list we are to observe
	 */
	this.konstructor = function( edoListModel ) {
		this.souper( edoListModel, LotView );
	}

	this.itemEDOIndex = function( index ) {
		return ALIndex.Lot( index );
	}
}


Class(AuctionModelView,["Auction data model"])
.Extends(EDOHolderView);
/**
 * @class This class produces the view of the entire data panel.
 * @extends EDOHolderView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function AuctionModelView()
{
	/** @param {AuctionModel} auctionModel Auction/lots we are to observe */
	this.konstructor = function( auctionModel ) {
		this.souper( auctionModel );
	}

	this.buildFields = function( HTML )
	{
	  var vwID = this.getWidgetID();

	  if ( !this.model.auction || this.model.auction.inLimbo() )
	  {
		if (!grvWAITING())
		HTML.push( EDOView.ShellHTML( ALIndex.Auction(), Auction ) );
	  }
	  else if ( this.model.auction.isActive() )
      {
		HTML.push( this.embedHTML( vwID+".auctionWdgt",
						new AuctionView( this.model.auction ) ) );

		HTML.push( this.embedHTML( vwID+".lotWdgt",
		                new LotListView( this.model.lList   ) ) );
      }
      else HTML.push( '<thead>This Auction is deleted. You must Cancel, Save, or Undo.</thead>' );
	}
}
				////////////////////////////////////////////
				//////////////// Commands //////////////////
				////////////////////////////////////////////

/** Load in contacts and shippings list from specified bidder<br>
 *  NOTE: this is a Command helper function.<br>
 *  NEVER call this from outside the context of a Command object!
 * @param {long} bidderKey unique key of bidder
 */
function _GetBidderLists( bidderKey )
{
	if (_ContactsSelected(bidderKey).loaded) return;//already loaded
	_gGrvHackStartupInhibitWait = true;
	EDOHolderModel.AjaxRequest( "Bidder", bidderKey, onBidderLoadReply );
}

function _ContactsSelected( bidderKey )
{
	if (gContactsSelected[bidderKey]){/*already created*/} else
	{
		var bs = "#"+bidderKey;
		var L  = new MVCMapModel(bs+":Contacts List");
		L.map.addItem( 0, new MVCDecode( "Contact", "0", "", 1 ) );
		gContactsSelected[bidderKey] = new MVCSelectionModel( L, bs+":shared contact selection model" );
		gContactsSelected[bidderKey].loaded = (bidderKey==0);
	}
	return gContactsSelected[bidderKey];
}

function _ShippingSelected( bidderKey )
{
	if (gShippingSelected[bidderKey]){/*already created*/} else
	{
		var bs = "#"+bidderKey;
		var L  = new MVCMapModel(bs+":Shipping List");
		L.map.addItem( 0, new MVCDecode( "Shipping", "0", "", 1 ) );
		gShippingSelected[bidderKey] = new MVCSelectionModel( L, bs+":shared shipping selection model" );
		gShippingSelected[bidderKey].loaded = (bidderKey==0);
	}
	return gShippingSelected[bidderKey];
}


Class(BidderMenuCmd,["view ID"]).Extends(EDOSuperMenuCmd);
/**
 * @class This class implements the bidder menu edit command.
 * @extends EDOSuperMenuCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function BidderMenuCmd()
{
	/** @param {String} viewID viewID of menu generating this event */
	this.konstructor = function( viewID ) {
		this.souper( viewID );
	}

	this.updateEDO = function( exemplar, isRedo )
	{
		this.edo[ kIntIdContactsKey ] = exemplar[ kIntIdContactsKey ];
		this.edo[ kIntIdShippingKey ] = exemplar[ kIntIdShippingKey ];
	}

	this.prefillEDO = function()
	{
		_GetBidderLists( this.newValue );
		this.updateOtherController( kIntIdContactsKey, 0 );
		this.updateOtherController( kIntIdShippingKey, 0 );
	}
}

Class(BidderLoadReplyCmd,["xml request object"]).Extends(MVCAJAXReplyCmd);
/**
 * @class This Command processes the reply of the "load Bidder data" request.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCAJAXReplyCmd
 * @see #konstructor
 * @deprecated ajax call was made synchronous to make undo possible
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function BidderLoadReplyCmd()
{
	/** @param {grvXMLreq} xmlReq the XML request being replied to
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xmlReq, optName ) {
		if (!BidderLoadReplyCmd.DOM)  BidderLoadReplyCmd.DOM = grvLoadXslDOM( kXSLPath+"AIMbidListsToJS.xsl" );
		this.souper( xmlReq, BidderLoadReplyCmd.DOM, optName );
	}
}

Class(NextPhaseCmd).Extends(EDOCmd);
/**
 * @class This class implements the Move to Next Auction Phase cmd.
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function NextPhaseCmd()
{
	this.konstructor = function(){
		this.souper("move to next phase", gEDOSelected.canEditSelected());
		this.auctionEDO = gEDOSelected.getSelected();
		this.dateID     = this.auctionEDO.nextState();
		if (this.dateID==null) this.state = -1;
		this.dateStr    = grvFormatDate( new Date(), "mm/dd/yyyy" );
	}

	this.doit = function(){ this.auctionEDO[ this.dateID ] = this.dateStr; }
	this.undo = function(){ this.auctionEDO[ this.dateID ] =           ""; }
}

////////////////////////////////////////////
////////// Static Event Handlers ///////////
////////////////////////////////////////////

/** handle event for 'user selects from the bidder menu'
 * @param {event} e browser event
 * @param {String} viewID ID of the controller generating this event
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function onBidderSelect( e, viewID ){
	mvcDoCmd( new BidderMenuCmd( viewID ) );
	return true;
}

/** handle AJAX event for 'reply received from "load Bidder data" server-request'
 * @param {grvXMLreq} xmlReq the XML request being replied to
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onBidderLoadReply( xmlReq ){
	//replace async with sync to allow undo
	//mvcDoCmd( new BidderLoadReplyCmd( xmlReq ) );

	if (!onBidderLoadReply.DOM) onBidderLoadReply.DOM = grvLoadXslDOM( kXSLPath+"AIMbidListsToJS.xsl" );
	
	//translate received XML into javascript (via XSL) and save
	onBidderLoadReply.script = xmlReq.xform2( onBidderLoadReply.DOM );
	if (gGrvTraceEvt) grvDebugWindow( onBidderLoadReply.script );
	eval( onBidderLoadReply.script );
}

/** handle event for 'next phase' button press
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onPhaseBtnPressed(){
	mvcDoCmd( new NextPhaseCmd() );
}

