

/****************************************************************************
//	SECTION::Hash
*/

// restore Hash.remove
Hash.alias({erase: 'remove'});

/*
//	End SECTION::Hash
****************************************************************************/




/****************************************************************************
//	SECTION::Element.Form functionality
*/
Element.implement({

	/*
	Property: getValue
		Returns the value of the Element, if its tag is textarea, select or input. getValue called on a multiple select will return an array.
	*/

	getValue: function(){
		switch(this.getTag()){
			case 'select':
				var values = [];
				$each(this.options, function(option){
					if (option.selected) values.push($pick(option.value, option.text));
				});
				return (this.multiple) ? values : values[0];
			case 'input': if (!(this.checked && ['checkbox', 'radio'].contains(this.type)) && !['hidden', 'text', 'password'].contains(this.type)) break;
			case 'textarea': return this.value;
		}
		return false;
	},

	getFormElements: function(){
		return $$(this.getElementsByTagName('input'), this.getElementsByTagName('select'), this.getElementsByTagName('textarea'));
	},

	/*
	Property: toQueryString
		Reads the children inputs of the Element and generates a query string, based on their values. Used internally in <Ajax>

	Example:
		(start code)
		<form id="myForm" action="submit.php">
		<input name="email" value="bob@bob.com">
		<input name="zipCode" value="90210">
		</form>

		<script>
		 $('myForm').toQueryString()
		</script>
		(end)

		Returns:
			email=bob@bob.com&zipCode=90210
	*/

	toQueryString: function(){
		var queryString = [];
		this.getFormElements().each(function(el){
			var name = el.name;
			var value = el.getValue();
			if (value === false || !name || el.disabled) return;
			var qs = function(val){
				queryString.push(name + '=' + encodeURIComponent(val));
			};
			if ($type(value) == 'array') value.each(qs);
			else qs(value);
		});
		return queryString.join('&');
	}

});
/*
//	End SECTION::Element.Form functionality
****************************************************************************/






/****************************************************************************
//	SECTION::Element.getPosition remix for efficiency (copy of 1.11 function)
*/
Element.implement({

	getPosition: function(relative){
		//================================================================================
		// jpl: bring it back to how computation was done before.  Is perhaps less accurate
		// but runs WAAAAY faster:
		relative = relative || [];
		var el = this, left = 0, top = 0;
		do {
			left += el.offsetLeft || 0;
			top += el.offsetTop || 0;
			el = el.offsetParent;
		} while (el);
		if(relative.length) {
			relative.each(function(element){
				left -= element.scrollLeft || 0;
				top -= element.scrollTop || 0;
			});
		}
		return {'x': left, 'y': top};
		//================================================================================
		
	}
});
/*
//	End SECTION::Element.getPosition remix for efficiency (copy of 1.11 function)
****************************************************************************/




/****************************************************************************
//	SECTION::Drag that can handle % in addition to px in position styling
*/

Drag.implement({
	
	start: function(event){
		if (this.options.preventDefault) event.preventDefault();
		this.fireEvent('beforeStart', this.element);
		this.mouse.start = event.page;
		
		
		// jpl added
		this.modifierUnits = {x: 'px', y: 'px'};
		
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
	
	// jpl change to:	
			this.modifierUnits[z] = this.getStyleUnit(this.element.getStyle(this.options.modifiers[z]));
			if (this.options.style) this.value.now[z] = this.getElementModifierStyleAsPixels(this.options.modifiers[z]);
			else this.value.now[z] = this.getElementModifierStyleAsPixels(this.options.modifiers[z]);
	
	// Was:
	//		if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
	//		else this.value.now[z] = this.element[this.options.modifiers[z]];
	
	
			if (this.options.invert) this.value.now[z] *= -1;
			this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}
		if ($type(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
		this.document.addEvent(this.selection, this.bound.eventStop);
	},
	
	
	drag: function(event){
		if (this.options.preventDefault) event.preventDefault();
		this.mouse.now = event.page;
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
			if (this.options.invert) this.value.now[z] *= -1;
			if (this.options.limit && this.limit[z]){
				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
					this.value.now[z] = this.limit[z][1];
				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
					this.value.now[z] = this.limit[z][0];
				}
			}
			if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);


			if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
			else this.element[this.options.modifiers[z]] = this.value.now[z];



		//	dbug.log('Setting ' + this.options.modifiers[z] + ' in ' + this.modifierUnits[z]);
			// jpl: we need to set the style back as a % if it's expressed as a %:
			var unitValue = this.getElementStyleFromPixelsToUnit(this.options.modifiers[z], this.value.now[z], this.modifierUnits[z]);
			if (this.options.style)
				this.element.setStyle(this.options.modifiers[z], unitValue);
			else
				this.element[this.options.modifiers[z]] = unitValue;
		//	this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);


		//	if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
		//	else this.element[this.options.modifiers[z]] = this.value.now[z];



		}
		this.fireEvent('drag', this.element);
	},
	

	
	// jpl:  handles either % or px units
	getElementModifierStyleAsPixels: function(modifier) {
		var theStyle = this.element.getStyle(modifier) + '';
		if(theStyle.substring(theStyle.length-1) == '%') {
			// a percentage of what?
			var APCoordinates = this.getClosestPositionedAncester(this.element).getCoordinates();
			if(['height', 'top'].contains(modifier)) 			// the parent height:
				return (theStyle.toInt() * APCoordinates.height/100).round();
			
			else if(['width', 'left'].contains(modifier))		// the parent width:
				return (theStyle.toInt() * APCoordinates.width/100).round();
			
		}
		else
			return theStyle.toInt();
	},
	
	
	// jpl:  handles either % or px units
	getElementStyleFromPixelsToUnit: function(modifier, pixelValue, unit) {
		if(unit == '%') {
			// a percentage of what?
			var APCoordinates = this.getClosestPositionedAncester(this.element).getCoordinates();
			if(['height', 'top'].contains(modifier))			// the parent height:
				return (pixelValue * 100/APCoordinates.height).toFixed(2) + '%';
			
			else if(['width', 'left'].contains(modifier))		// the parent width:
				return (pixelValue * 100/APCoordinates.width).toFixed(2) + '%';
		}
		else // cheap but highly legit assumption
			return pixelValue + 'px';
	},
	
	// jpl:  extract the unit from a given style value:
	getStyleUnit: function(theStyle) {
		if(theStyle.substring(theStyle.length-1) == '%')
			return '%';
		if(theStyle.substring(theStyle.length-2) == 'px')
			return 'px';
		if(theStyle.substring(theStyle.length-2) == 'em')
			return 'em';
		if(theStyle.substring(theStyle.length-2) == 'ex')
			return 'ex';
		if(theStyle.substring(theStyle.length-2) == 'pt')
			return 'pt';
		
		return 'px'; // reasonable default
		
	},
	
	// jpl:  general purpose function to find an element's nearest ancestor that
	// is positioned (this is the element to which top: and left: styling will be relative to)
	getClosestPositionedAncester: function(element) {
		var result = document.body;  // unless we find otherwise
		var target = element.parentNode;
		while(target != document.body) {
			if(['absolute', 'relative'].contains(target.getStyle('position'))) {
				result = target;
				break;
			}
			target = target.getParent();
		}
		return result;
	}
	
	
});


/*
//	End SECTION::Drag that can handle % in addition to px in position styling
****************************************************************************/


/****************************************************************************
//	SECTION::Revert from silly change of which object is calling its
//	registered event (in 1.2 the Drag.Move object calls, we're putting
//	it back to the droppable element that calls.
*/

Drag.Move.implement({

	checkDroppables: function(){
		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
		if (this.overed != overed){
			dbug.log(overed);
		
	// Was:	if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
	// Now:
			if (this.overed) this.overed.fireEvent('leave', [this.element, this]);
			
			if (overed){
				this.overed = overed;
				dbug.log('fire enter event on ' + overed);
		// Was:	this.fireEvent('enter', [this.element, overed]);
		// Now:
				overed.fireEvent('enter', [this.element, this]);
			} else {
				this.overed = null;
			}
		}
	},

	stop: function(event){
		this.checkDroppables();
//Was:	this.fireEvent('drop', [this.element, this.overed]);
//Now:
		this.overed.fireEvent('drop', [this.element, this]);
		this.overed = null;
		return this.parent(event);
	}

});

/*
//	End SECTION::Revert of who's calling drag move events
****************************************************************************/