var Droppables = {
	drops: [],

	remove: function(element) {
		var arr = [];
		for (var i = 0; (d = this.drops[i]); i++) {
			if (d.element != element) arr.push(d);
		}
		
		this.drops = arr;
	},

	add: function(element) {
		element = _(element);
		var options = Object.extend({
			hoverclass: null,
			tree:       false
		}, arguments[1] || {});

		// cache containers
		if (options.containment) {
			options._containers = [];
			var containment = options.containment;
			if (Object.isArray(containment)) {
				for (var i = 0, l = containment.length; i < l; i++) {
					options._containers.push(_(containment[i]));
				}
			} else {
				options._containers.push(_(containment));
			}
		}
		
		if (options.accept) options.accept = Object.isArray(options.accept) ? options.accept : [options.accept];

		options.element = element;

		this.drops.push(options);
	},
	
	findDeepestChild: function(drops) {
		deepest = drops[0];
			
		for (i = 1; i < drops.length; ++i)
			if (_isParent(drops[i].element, deepest.element)) deepest = drops[i];
		
		return deepest;
	},

	isContained: function(element, drop) {
		var containmentNode = drop.tree ? element.treeNode : element.parentNode;
		for (var i = 0; (c = drop._containers[i]); i++)
			if (c == containmentNode) return containmentNode;
		
		return null;
	},
	
	isAffected: function(point, element, drop) {
		return (
			(drop.element != element) && 
			((!drop.accept) || (element.className.indexOf(drop.accept)) || (_classNames(element).detect(function(v) { return drop.accept.indexOf(v) !== -1}))) &&
			((!drop._containers) || this.isContained(element, drop)) &&
			Position.within(drop.element, point[0], point[1]));
	},

	deactivate: function(drop) {
		if (drop.hoverclass) _removeClassName(drop.element, drop.hoverclass);
		this.last_active = null;
	},

	activate: function(drop) {
		if (drop.hoverclass) _addClassName(drop.element, drop.hoverclass);
		this.last_active = drop;
	},

	show: function(point, element) {
		if (!this.drops.length) return;
		var drop, affected = [];
		
		for (var i = 0; (drop = this.drops[i]); i++) {
			if (Droppables.isAffected(point, element, drop)) {
				affected.push(drop);
				break;
			}
		}
				
		//if (affected.length > 0) drop = Droppables.findDeepestChild(affected);
		if (this.last_active && this.last_active != drop) this.deactivate(this.last_active);
		
		if (drop) {
			//Position.within(drop.element, point[0], point[1], true);
			if (drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.element, drop.overlap));
			if (drop != this.last_active) Droppables.activate(drop);
		}
	},

	fire: function(event, element) {
		if (!this.last_active) return;
		Position.prepare();

		if (this.isAffected(Event.pointer(event), element, this.last_active)) {
			if (this.last_active.onDrop) {
				this.last_active.onDrop(element, this.last_active.element, event); 
				return true; 
			}
		}
	},

	reset: function() {
		if (this.last_active) this.deactivate(this.last_active);
	}
}

var Draggables = {
	drags: [],
	observers: [],
	
	findByElement: function(element) {
		for (var i = 0, l = this.drags.length; i < l; i++) {
			if (this.drags[i].element == element) return this.drags[i];
		}
		
		return null;
	},
	
	register: function(draggable) {
		if (this.drags.length == 0) {
			this.eventMouseUp   = this.endDrag.bind(this);
			this.eventMouseMove = this.updateDrag.bind(this);
			this.eventKeypress  = this.keyPress.bind(this);
			
			Event.bind(document, 'mouseup', this.eventMouseUp);
			Event.bind(document, 'mousemove', this.eventMouseMove);
			Event.bind(document, 'keypress', this.eventKeypress);
		}
		
		this.drags.push(draggable);
	},
	
	unregister: function(draggable) {
		var arr = [];
		for (var i = 0; (d = this.drags[i]); i++) {
			if (d != draggable) arr.push(d);
		}
		
		this.drags = arr;
		if (this.drags.length == 0) {
			Event.unbind(document, 'mouseup', this.eventMouseUp);
			Event.unbind(document, 'mousemove', this.eventMouseMove);
			Event.unbind(document, 'keypress', this.eventKeypress);
		}
	},
	
	activate: function(draggable) {
		if (draggable.options.delay) { 
			this._timeout = setTimeout(function() { 
				Draggables._timeout = null; 
				window.focus(); 
				Draggables.activeDraggable = draggable; 
			}.bind(this), draggable.options.delay); 
		} else {
			window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
			this.activeDraggable = draggable;
		}
	},
	
	deactivate: function() {
		this.activeDraggable = null;
	},
	
	updateDrag: function(event) {
		if (!this.activeDraggable) return;
		var pointer = Event.pointer(event);
		// Mozilla-based browsers fire successive mousemove events with
		// the same coordinates, prevent needless redrawing (moz bug?)
		if (this._lastPointer && this._lastPointer[0] == pointer[0] && this._lastPointer[1] == pointer[1]) return;
		
		this._lastPointer = pointer;
		this.activeDraggable.updateDrag(event, pointer);
	},
	
	endDrag: function(event) {
		if (this._timeout) { 
			clearTimeout(this._timeout); 
			this._timeout = null; 
		}
		
		if (!this.activeDraggable) return;
		this._lastPointer = null;
		this.activeDraggable.endDrag(event);
		this.activeDraggable = null;
	},
	
	keyPress: function(event) {
		if (this.activeDraggable) this.activeDraggable.keyPress(event);
	},
	
	addObserver: function(observer) {
		this.observers.push(observer);
		this._cacheObserverCallbacks();
	},
	
	removeObserver: function(element) {  // element instead of observer fixes mem leaks
		var arr = [];
		for (var i = 0; (o = this.observers[i]); i++) {
			if (o.element != element) arr.push(o);
		}
		
		this.observers = arr;
		this._cacheObserverCallbacks();
	},
	
	notify: function(eventName, draggable, event) {
		if (this[eventName + 'Count'] > 0) {
			for (var i = 0; (o = this.observers[i]); i++) {
				if (o[eventName]) o[eventName](eventName, draggable, event);
			}
		}
		
		if (draggable.options[eventName]) draggable.options[eventName](draggable, event);
	},
	
	_cacheObserverCallbacks: function() {
		Draggables['onStartCount'] = Draggables.observers.filter(function(o) {return o['onStart'];}).length;
		Draggables['onEndCount'] = Draggables.observers.filter(function(o) {return o['onEnd'];}).length;
		Draggables['onDragCount'] = Draggables.observers.filter(function(o) {return o['onDrag'];}).length;
	}
}

/*--------------------------------------------------------------------------*/

var Draggable = Class.create({
	initialize: function(element) {
		var defaults = {
			handle: false,
			reverteffect: function(element, top_offset, left_offset) {
				var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.015;
				new FX.Move(element, {x: -left_offset, y: -top_offset, duration: dur});
			},
			endeffect: function(element) {
				//var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
				//new FX.Appear(element, {duration:0.2, start:0.7, stop:toOpacity, 
					//onFinish: function(){ 
						Draggable._dragging[element] = false 
					//}
				//}); 
			},
			zindex: 64000,
			revert: false,
			quiet: false,
			snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
			delay: 0
		};
		
		if (!arguments[1] || Object.isUndefined(arguments[1].endeffect)) {
			Object.extend(defaults, {
				starteffect: function(element) {
					Draggable._dragging[element] = true;
					//new FX.Disappear(element, {duration:0.2, start:1.0, stop:0.7}); 
				}
			});
		}
		
		var options = Object.extend(defaults, arguments[1] || {});

		this.element = _(element);
		
		if (options.handle && Object.isString(options.handle)) {
			this.handle = _down(this.element, '.' + options.handle);
		}
		
		if (!this.handle) this.handle = _(options.handle);
		if (!this.handle) this.handle = this.element;
		
		this.options  = options;
		this.dragging = false;   

		this.eventMouseDown = this.initDrag.bind(this);
	 	Event.bind(this.handle, 'mousedown', this.eventMouseDown);
	 	Event.bind(this.handle, 'selectstart', this.selectStart);
	 	
	 	Position.makePositioned(this.element);
			
		Draggables.register(this);
	},
	
	selectStart: function(event) {
		Event.stop(event);
		return false;
	},
	
	resetHandle: function() {
		Event.unbind(this.handle, 'mousedown', this.eventMouseDown);
		Event.unbind(this.handle, 'selectstart', this.selectStart);
	},
	
	setHandle: function(className) {
		this.handle = _down(this.element, '.' + className);
		if (!this.handle) this.handle = this.element;
		
		Event.bind(this.handle, 'mousedown', this.eventMouseDown);
		Event.bind(this.handle, 'selectstart', this.selectStart);
	},
	
	destroy: function() {
		Event.unbind(this.handle, 'mousedown', this.eventMouseDown);
		Event.unbind(this.handle, 'selectstart', this.selectStart);
		Draggables.unregister(this);
	},
	
	initDrag: function(event) {
		if (!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return;
		if (Event.isLeftClick(event)) {
			// abort on form elements, fixes a Firefox issue
			var src = Event.element(event);
			if ((tag_name = src.tagName.toUpperCase()) && (
				tag_name=='INPUT' ||
				tag_name=='SELECT' ||
				tag_name=='OPTION' ||
				tag_name=='BUTTON' ||
				tag_name=='TEXTAREA')) return;
				
			var pointer = Event.pointer(event);
			var pos = Position.cumulativeOffset(this.element);
			this.offset = [];
			this.offset[0] = pointer[0] - pos[0];
			this.offset[1] = pointer[1] - pos[1];
			
			Draggables.activate(this);
			this.startDrag(event);
			Event.stop(event);
		}
	},
	
	startDrag: function(event) {
		this.dragging = true;
		if (!this.delta) this.delta = [parseInt(this.element.style.left || '0'), parseInt(this.element.style.top || '0')];
		
		if (this.options.zindex) {
			this.originalZ = parseInt(this.element.style.zIndex || 0);
			this.element.style.zIndex = this.options.zindex;
		}
		
		if (this.options.ghosting) {
			this._clone = this.element.cloneNode(true);
			this.element.parentNode.insertBefore(this._clone, this.element);
		}
		
		Draggables.notify('onStart', this, event);
				
		if (this.options.starteffect) this.options.starteffect(this.element);
	},
	
	updateDrag: function(event, pointer) {
		if (!this.dragging) this.startDrag(event);
		
		if (!this.options.quiet){
			Position.prepare();
			Droppables.show(pointer, this.element);
		}
		
		Draggables.notify('onDrag', this, event);
		
		this.draw(pointer);
		if (this.options.change) this.options.change(this);
		
		// fix AppleWebKit rendering
		if (P.Browser.WebKit) window.scrollBy(0,0);
		
		Event.stop(event);
	},
	
	finishDrag: function(event, success) {
		this.dragging = false;
		
		if (this.options.quiet){
			Position.prepare();
			var pointer = Event.pointer;
			Droppables.show(pointer, this.element);
		}

		if (this.options.ghosting) {
			_remove(this._clone);
			this._clone = null;
		}

		var dropped = false; 
		if (success) { 
			dropped = Droppables.fire(event, this.element); 
			if (!dropped) dropped = false; 
		}
		
		if (dropped && this.options.onDropped) this.options.onDropped(this.element);
		Draggables.notify('onEnd', this, event);

		var revert = this.options.revert;
		if (revert && Object.isFunction(revert)) revert = revert(this.element);
		
		var d = [parseInt(this.element.style.left || '0'), parseInt(this.element.style.top || '0')];
		if (revert && this.options.reverteffect) {
			if (dropped == 0 || revert != 'failure') this.options.reverteffect(this.element, d[1] - this.delta[1], d[0] - this.delta[0]);
		} else {
			this.delta = d;
		}

		if (this.options.zindex) this.element.style.zIndex = this.originalZ;

		if (this.options.endeffect) this.options.endeffect(this.element);
			
		Draggables.deactivate(this);
		Droppables.reset();
	},
	
	keyPress: function(event) {
		if (event.keyCode != Event.KEY_ESC) return;
		this.finishDrag(event, false);
		Event.stop(event);
	},
	
	endDrag: function(event) {
		if (!this.dragging) return;
		this.finishDrag(event, true);
		Event.stop(event);
	},
	
	draw: function(point) {
		var options = this.options, pos = Position.cumulativeOffset(this.element);
		if (options.ghosting) {
			var r = Position.cumulativeScrollOffset(this.element);
			pos[0] += r[0] - Position.deltaX;
			pos[1] += r[1] - Position.deltaY;
		}
		
		pos[0] -= parseInt(this.element.style.left || '0');
		pos[1] -= parseInt(this.element.style.top || '0');
		
		var p = [point[0] - pos[0] - this.offset[0],
				 point[1] - pos[1] - this.offset[1]]
		
		if (options.snap) {
			if (Object.isFunction(options.snap)) {
				p = options.snap(p[0], p[1], this);
			} else {
				if (Object.isArray(options.snap)) {
					p = p.map(function(v, i) {
						return (v / options.snap[i]).round() * options.snap[i]}.bind(this))
				} else {
					p = p.map(function(v) {
						return (v / options.snap).round() * options.snap}.bind(this))
				}
			}
		}
		
		var style = this.element.style;
		if ((!options.constraint) || (options.constraint == 'horizontal')) style.left = p[0] + 'px';
		if ((!options.constraint) || (options.constraint == 'vertical')) style.top = p[1] + 'px';
		//if (style.visibility == 'hidden') style.visibility = ''; // fix gecko rendering
	}
});

Draggable._dragging = {};

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create({
	initialize: function(element, observer) {
		this.element   = _(element);
		this.observer  = observer;
		this.lastValue = Sortable.serialize(this.element);
	},
	
	onStart: function() {
		this.lastValue = Sortable.serialize(this.element);
	},
	
	onEnd: function() {
		//Sortable.unmark();
		if (this.lastValue != Sortable.serialize(this.element))
			this.observer(this.element)
	}
});

var Sortable = {
	SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
	
	sortables: {},
	
	_findRootElement: function(element) {
		while (element.parentNode) {
			if (element.id && Sortable.sortables[element.id]) return element;
			element = element.parentNode;
		}
	},

	options: function(element) {
		element = Sortable._findRootElement(_(element));
		if (!element) return;
		return Sortable.sortables[element.id];
	},
	
	destroy: function(element){
		var s = Sortable.options(element);
		
		if (s) {
			Draggables.removeObserver(s.element);
			for (var i = 0, l = s.droppables.length; i < l; i++) {
				Droppables.remove(s.droppables[i]);
			}
			
			for (var i = 0, l = s.draggables.length; i < l; i++) {
				s.draggables[i].destroy();
			}
			
			delete Sortable.sortables[s.element.id];
		}
	},

	create: function(element) {
		element = _(element);
		var options = Object.extend({ 
			element:     element,
			tag:         'li',       // assumes li children, override with tag: 'tagname'
			dropOnEmpty: false,
			tree:        false,
			treeTag:     'ul',
			overlap:     'vertical', // one of 'vertical', 'horizontal'
			constraint:  'vertical', // one of 'vertical', 'horizontal', false
			containment: element,    // also takes array of elements (or id's); or false
			accept:      false,
			handle:      false,      // or a CSS class
			only:        false,
			delay:       0,
			hoverclass:  null,
			ghosting:    false,
			quiet:       false, 
			format:      this.SERIALIZE_RULE,
			
			// these take arrays of elements or ids and can be 
			// used for better initialization performance
			elements:    false,
			handles:     false,
			
			onChange:    P.emptyFunction,
			onUpdate:    P.emptyFunction
		}, arguments[1] || {});

		// clear any old sortable with same element
		this.destroy(element);

		// build options for the draggables
		var options_for_draggable = {
			revert:      true,
			quiet:       options.quiet,
			delay:       options.delay,
			ghosting:    options.ghosting,
			constraint:  options.constraint,
			handle:      options.handle };

		if (options.starteffect)
			options_for_draggable.starteffect = options.starteffect;

		if (options.reverteffect) {
			options_for_draggable.reverteffect = options.reverteffect;
		} else {
			if (options.ghosting) options_for_draggable.reverteffect = function(element) {
				element.style.top  = 0;
				element.style.left = 0;
			};
		}

		if (options.endeffect) options_for_draggable.endeffect = options.endeffect;

		if (options.zindex) options_for_draggable.zindex = options.zindex;

		// build options for the droppables
		var options_for_droppable = {
			overlap:     options.overlap,
			containment: options.containment,
			accept:      options.accept,
			tree:        options.tree,
			hoverclass:  options.hoverclass,
			onHover:     Sortable.onHover
		}
		
		var options_for_tree = {
			onHover:      Sortable.onEmptyHover,
			overlap:      options.overlap,
			containment:  options.containment,
			hoverclass:   options.hoverclass
		}

		// fix for gecko engine
		_cleanWhitespace(element); 

		options.draggables = [];
		options.droppables = [];

		// drop on empty handling
		if (options.dropOnEmpty || options.tree) {
			Droppables.add(element, options_for_tree);
			options.droppables.push(element);
		}
		
		var elements = (options.elements || this.findElements(element, options) || []);
		for (var i = 0; (e = elements[i]); i++) {
			var handle = options.handles ? _(options.handles[i]) : (options.handle ? _down(e, '.' + options.handle) : e); 
			options.draggables.push(new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
			Droppables.add(e, options_for_droppable);
			if (options.tree) e.treeNode = element;
			options.droppables.push(e);
		}
		
		if (options.tree) {
			var elements = (Sortable.findTreeElements(element, options) || []);
			for (var i = 0; (e = elements[i]); i++) {
				Droppables.add(e, options_for_tree);
				e.treeNode = element;
				options.droppables.push(e);
			}
		}

		// keep reference
		this.sortables[element.id] = options;

		// for onupdate
		Draggables.addObserver(new SortableObserver(element, options.onUpdate));

	},

	// return all suitable-for-sortable elements in a guaranteed order
	findElements: function(element, options) {
		return _findChildren(element, options.only, options.tree ? true : false, options.tag);
	},
	
	findTreeElements: function(element, options) {
		return _findChildren(element, options.only, options.tree ? true : false, options.treeTag);
	},

	onHover: function(element, dropon, overlap) {
		//if (_isParent(dropon, element)) return;

		//if (overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
			//return;
		/*} else*/ if (overlap > 0.5) {
			//Sortable.mark(dropon, 'before');
			if (dropon.previousSibling != element) {
				var oldParentNode = element.parentNode;
				//element.style.visibility = 'hidden'; // fix gecko rendering
				dropon.parentNode.insertBefore(element, dropon);
				var options = Sortable.options(oldParentNode);
				if (options && dropon.parentNode != oldParentNode) options.onChange(element);
				Sortable.options(dropon.parentNode).onChange(element);
			}
		} else {
			//Sortable.mark(dropon, 'after');
			var nextElement = dropon.nextSibling || null;
			if (nextElement != element) {
				var oldParentNode = element.parentNode;
				//element.style.visibility = 'hidden'; // fix gecko rendering
				dropon.parentNode.insertBefore(element, nextElement);
				var options = Sortable.options(oldParentNode);
				if (options && dropon.parentNode != oldParentNode) options.onChange(element);
				Sortable.options(dropon.parentNode).onChange(element);
			}
		}
	},
	
	onEmptyHover: function(element, dropon, overlap) {
		var oldParentNode = element.parentNode;
		var droponOptions = Sortable.options(dropon);
				
		if (!_isParent(dropon, element)) {
			var index;
			
			var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
			var child = null;
						
			if (children) {
				var offset = _offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
				
				for (index = 0; index < children.length; index += 1) {
					if (offset - _offsetSize (children[index], droponOptions.overlap) >= 0) {
						offset -= _offsetSize (children[index], droponOptions.overlap);
					} else if (offset - (_offsetSize(children[index], droponOptions.overlap) / 2) >= 0) {
						child = index + 1 < children.length ? children[index + 1] : null;
						break;
					} else {
						child = children[index];
						break;
					}
				}
			}
			
			dropon.insertBefore(element, child);
			
			Sortable.options(oldParentNode).onChange(element);
			droponOptions.onChange(element);
		}
	},

	unmark: function() {
		if (Sortable._marker) Sortable._marker.hide();
	},

	mark: function(dropon, position) {
		// mark on ghosting only
		var sortable = Sortable.options(dropon.parentNode);
		if (sortable && !sortable.ghosting) return; 

		if (!Sortable._marker) {
			Sortable._marker = _('dropmarker') || document.createElement('DIV');
			_hide(Sortable._marker);
			_addClassName(Sortable._marker, 'dropmarker');
			Sortable._marker.style.position = 'absolute';
			document.getElementsByTagName('body')[0].appendChild(Sortable._marker);
		}
		var offsets = Position.cumulativeOffset(dropon);
		Sortable._marker.style.left = offsets[0] + 'px';
		Sortable._marker.style.top = offsets[1] + 'px';
		
		if (position=='after')
			if (sortable.overlap == 'horizontal') 
				Sortable._marker.style.left = (offsets[0] + dropon.clientWidth) + 'px';
			else
				Sortable._marker.style.top = (offsets[1] + dropon.clientHeight) + 'px';
		
		_show(Sortable._marker);
	},
	
	_tree: function(element, options, parent) {
		var children = Sortable.findElements(element, options) || [];
	
		for (var i = 0; i < children.length; ++i) {
			var match = children[i].id.match(options.format);

			if (!match) continue;
			
			var child = {
				id: encodeURIComponent(match ? match[1] : null),
				element: element,
				parent: parent,
				children: [],
				position: parent.children.length,
				container: _down(children[i], options.treeTag)
			}
			
			/* Get the element containing the children and recurse over it */
			if (child.container) this._tree(child.container, options, child);
			
			parent.children.push (child);
		}

		return parent; 
	},

	tree: function(element) {
		element = _(element);
		var sortableOptions = this.options(element);
		var options = Object.extend({
			tag: sortableOptions.tag,
			treeTag: sortableOptions.treeTag,
			only: sortableOptions.only,
			name: element.id,
			format: sortableOptions.format
		}, arguments[1] || {});
		
		var root = {
			id: null,
			parent: null,
			children: [],
			container: element,
			position: 0
		}
		
		return Sortable._tree(element, options, root);
	},

	/* Construct a [i] index for a particular node */
	_constructIndex: function(node) {
		var index = '';
		do {
			if (node.id) index = '[' + node.position + ']' + index;
		} while ((node = node.parent) != null);
		return index;
	},

	sequence: function(element) {
		element = _(element);
		var options = Object.extend(this.options(element), arguments[1] || {});
		
		return _(this.findElements(element, options) || []).map(function(item) {
			return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
		});
	},

	setSequence: function(element, new_sequence) {
		element = _(element);
		var options = Object.extend(this.options(element), arguments[2] || {});
		
		var nodeMap = {};
		var elements = this.findElements(element, options)
		for (var i = 0; (n = elements[i]); i++) {
			if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
			n.parentNode.removeChild(n);
		}
	 	
	 	for (var i = 0; (ident = new_sequence[i]); i++) {
			var n = nodeMap[ident];
			if (n) {
				n[1].appendChild(n[0]);
				delete nodeMap[ident];
			}
		}
	},
	
	serialize: function(element) {
		element = _(element);
		var options = Object.extend(Sortable.options(element), arguments[1] || {});
		var name = encodeURIComponent((arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
		
		if (options.tree) {
			return Sortable.tree(element, arguments[1]).children.map( function (item) {
				return [name + Sortable._constructIndex(item) + '[id]=' + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
			}).flatten().join('&');
		} else {
			return name + '=' + Sortable.sequence(element, arguments[1]).map( function(item) {
				return encodeURIComponent(item);
			}).join(',');
		}
	}
}

_offsetSize = function (element, type) {
	return element['offset' + ((type == 'vertical' || type == 'height') ? 'Height' : 'Width')];
}
