var id_prefix = 14;
var listLoadingMenus = [];
listLoadingMenus.lastIndex = 0;

function makeItems(tagName, allowMove, allowSelection, useMenu)
{
	ListItem.prototype.tagName = tagName;
	ListItem.prototype.allowMove = (allowMove == 'false') ? false : allowMove;
	ListItem.prototype.allowSelection = allowSelection;
	ListItem.prototype.useMenu = useMenu;
	ListItem.prototype.isThumbnailLimeList = ($$('ul.thumbnail_area')[0] ? true : false);
	window.top.dragItem = null;

	// the context menu hides the menu on blur, and this event occurs unexpectedly,
	// to we must prevent this onblur handler from acting
	PbLib.setHideEvents = true;
	// however, we do want the context menu remover
	if (ListItem.prototype.isThumbnailLimeList) {
		var thumbnailArea = $$('ul.thumbnail_area')[0];
	}

	document.observe('mouseup', function(event){
		// don't remove contextmenu's if ThumbnailLimeList is used
		var element = event.element();

		if (PbLib.currentContextMenu !== null && !ListItem.prototype.isThumbnailLimeList) {
			PbLib.hideCurrentContextMenu();
		} else if (PbLib.currentContextMenu !== null && !element.descendantOf(thumbnailArea) && !element.descendantOf(PbLib.currentContextMenu.htmlElem)) {
			PbLib.hideCurrentContextMenu();
		}
	});

	var items = $$(tagName);
	for (var i = 0; i < items.length; i++) {
		var item = items[i];
		if (!Object.isUndefined(item.id) && item.id.substring(0, id_prefix) == 'itemlist_item_') {

			new ListItem(item);
			DropTarget.prototype.registeredElements = DropTarget.prototype.registeredElements.concat(item);
		}
	}
}

/*********************** ListItem **********************************************/

function ListItem(item)
{
	item.listItem = this;

	if (ListItem.prototype.allowMove) {

		item.style.cursor = 'pointer';

		// disable text selection (test)
		for (var i=0; i < item.childNodes.length; i++) {
			var n = item.childNodes[i];
			n.onmousedown = function(){return false;}
		}

		new DropTarget(item);
	}

	if (ListItem.prototype.useMenu) {
		item.pbContextMenu = false;
		list_contextmenu_placeholder_immediate(item);
	}

	// allow mouse actions
	item.onmousedown = function(event)
	{
		var event = event || window.event;
		ListItem.prototype.handleMouseDown(event);
		if (event.button != 2) return true;
		Event.stop(event);
		return false;
	};

	// disallow text selection (Firefox)
	// see http://wiki.procurios.org/Javascript_Tricks#Disable_text_selection
	item.style.MozUserSelect = 'none';

	if (ListItem.prototype.allowSelection) {
		Event.observe(item, 'focus', ListItem.prototype.focus);
	}

	$(item).select('a.shortcut_a').each(function (e) {
		e.onclick = function(evt){evt.cancelBubble = true;return false;};
	});
}

ListItem.prototype = {

	selectedElements: [],
	latestSelectedElement: null,
	focusIsGenerated: false,
	tagName: null,
	allowMove: false,
	allowSelection: false,
	useMenu: false,

	handleMouseDown: function(event)
	{
		// the element is stored at class level
		var element = ListItem.prototype.getTargetElement(event);

		// if the user clicked a child element, select the draggable parent
		if (!element) return true;

		if (pbGetMouseButton(event) == 'left') {

			// keep track of the object selected for dragging
			ListItem.prototype.localDraggingElement = element;

			if (ListItem.prototype.allowMove) {

				// do not drag while clicking a button
				if (event.findElement('a')) {
					return true;
				}

				// clear drop target information
				ListItem.prototype.dropTargetData = null;
				ListItem.prototype.dropTargetData = {element: null, before: false}

				// allow dragging; outside the iframe too
				// the drag element is created if the mouse starts moving for the first time
				attachEventToAllFrames(window.top, 'mousemove', ListItem.prototype.drag);
			}

			if (ListItem.prototype.allowMove || ListItem.prototype.allowSelection) {
				attachEventToAllFrames(window.top, 'mouseup', ListItem.prototype.handleMouseUp);
			}

		} else {

			if (ListItem.prototype.allowSelection) {

				// if right mouse button is clicked outside the selection, start a new selection
				if (!ListItem.prototype.isSelected(element)) {
					ListItem.prototype.deselectAll();
				}

				// if the element wasn't selected, select it now
				if (!ListItem.prototype.isSelected(element)) {
					ListItem.prototype.select(element);
				}
			}

			if (ListItem.prototype.useMenu) {

				// start loading the contextmenu
				if (!ListItem.prototype.allowSelection) {
					// temporarily select
					ListItem.prototype.selectedElements = [element];
					list_contextmenu(event);
					ListItem.prototype.selectedElements = [];
				} else {
					list_contextmenu(event);
				}
			}
		}

		return true;
	},

	handleKeys: function(evt)
	{
		var event = evt ? evt : window.event;
		var keyCode = event.which ? event.which : event.keyCode;
		if (keyCode == 38) {
			ListItem.prototype.selectInDirection('up');
		} else if (keyCode == 39) {
			ListItem.prototype.selectInDirection('right');
		} else if (keyCode == 40) {
			ListItem.prototype.selectInDirection('down');
		} else if (keyCode == 37) {
			ListItem.prototype.selectInDirection('left');
		} else if (keyCode == 35) { // end
			ListItem.prototype.selectIndex(DropTarget.prototype.registeredElements.length - 1);
		} else if (keyCode == 36) { // home
			ListItem.prototype.selectIndex(0);
		} else {
			return true;
		}

		Event.stop(event);
		return false;
	},

	/**
	 * This function is called (indirectly) from code, and when the user focuses on an item
	 * (via keyboard or mouse-button).
	 */
	focus: function(event)
	{
		var generated = ListItem.prototype.focusIsGenerated;
		ListItem.prototype.focusIsGenerated	= false;

		var element = ListItem.prototype.getTargetElement(event);
		if (!element) return true;

		if (ListItem.prototype.isSelected(element)) return false;

		if (!generated) {
			ListItem.prototype.deselectAll();
			ListItem.prototype.select(element, true);
		}

		return false;
	},

	selectIndex: function(index)
	{
		ListItem.prototype.deselectAll();
		ListItem.prototype.select(DropTarget.prototype.registeredElements[index]);
	},

	selectInDirection: function(dir)
	{
		var selectedElement = ListItem.prototype.selectedElements[0];
		var currentIndex = ListItem.prototype.getCurrentIndex(selectedElement);
		if (dir == 'up' || dir == 'left') {
			var newIndex = currentIndex == 0 ? DropTarget.prototype.registeredElements.length - 1 : currentIndex - 1;
		} else {
			var newIndex = currentIndex == DropTarget.prototype.registeredElements.length - 1 ? 0 : currentIndex + 1;
		}
		ListItem.prototype.selectIndex(newIndex);
	},

	isSelected: function(element)
	{
		for (var i = 0; i < ListItem.prototype.selectedElements.length; i++) {
			if (ListItem.prototype.selectedElements[i] == element) {
				return true;
			}
		}
		return false;
	},

	getTargetElement: function(event)
	{
		// the element is stored at class level
		var element = pbGetEventTarget(event);
		// if the user clicked a child element, select the draggable parent
		while (!element.listItem && element.parentNode) element = element.parentNode;
		return (!element.listItem ? null : element);
	},

	/**
	 * Returns the index of element in the registered elements
	 */
	getCurrentIndex: function(element)
	{
		for (var i = 0; i < DropTarget.prototype.registeredElements.length; i++) {
			if (DropTarget.prototype.registeredElements[i] == element) {
				return i;
			}
		}
		return -1;
	},

	select: function(element, skipFocus)
	{
		if (!ListItem.prototype.isSelected(element)) {
			// add the element
			ListItem.prototype.selectedElements = ListItem.prototype.selectedElements.concat(element);

			// change the element's color
			element.addClassName('selected');

			// focus the anchor tag, to catch keydown events
			var elems = element.getElementsByTagName('a');
			if (elems.length > 0) {
				focusElement = elems[0];
				if (ListItem.prototype.latestSelectedElement) {
					ListItem.prototype.latestSelectedElement.onkeydown = null;
				}

				focusElement.onkeydown = ListItem.prototype.handleKeys;
			}

			// perform a selection change function, if any
			if (ListItem.prototype.selectionChanged) {
				var ids = ListItem.prototype.getSelectedElementIds();
				ListItem.prototype.selectionChanged(ids);
			}

			if (!skipFocus) {
				// we are going to ask the browser to generate a focus event,
				// but we don't want it to be treated like a user-created focus event
				// (we don't want to deselect all selected items)
				ListItem.prototype.focusIsGenerated = true;

				focusElement.focus();
			}

			ListItem.prototype.latestSelectedElement = element;
		}
	},

	deselect: function(element)
	{
		// change the element's color
		element.removeClassName('selected');
		// remove the element from the list of selections
		var newSelectedElements = [];
		for (var i = 0; i < ListItem.prototype.selectedElements.length; i++) {
			var e = ListItem.prototype.selectedElements[i];
			if (e != element) {
				newSelectedElements = newSelectedElements.concat(e);
			}
		}
		ListItem.prototype.selectedElements = newSelectedElements;

		if (element == ListItem.prototype.latestSelectedElement) {
			ListItem.prototype.latestSelectedElement = null;
		}
	},

	deselectAll: function()
	{
		// remove the element from the list of selections
		for (var i = 0; i < ListItem.prototype.selectedElements.length; i++) {
			ListItem.prototype.selectedElements[i].removeClassName('selected');
		}
		ListItem.prototype.selectedElements = [];
		ListItem.prototype.latestSelectedElement = null;
	},

	getSelectedElementIds: function()
	{
		var ids = [];
		for (var i = 0; i < ListItem.prototype.selectedElements.length; i++) {
			ids[i] = ListItem.prototype.selectedElements[i].id.substring(id_prefix);
		}
		return ids;
	},

	/**
	 * Selects all elements between firstElement and secondElement, inclusive, in any order.
	 */
	selectRange: function(firstElement, secondElement)
	{
		var phase = 0;
		for (var i = 0; i < DropTarget.prototype.registeredElements.length; i++) {
			var add = false;
			var e = DropTarget.prototype.registeredElements[i];
			if (e == firstElement) {
				phase++;
				add = true;
			}
			if (e == secondElement) {
				phase++;
				add = true;
			}
			if ((phase == 1) || add) {
				ListItem.prototype.select(e, true);
			}
		}

		if (ListItem.prototype.selectionChanged) {
			ListItem.prototype.selectionChanged();
		}
	},

	unregister: function(element)
	{
		if (ListItem.prototype.isSelected(element)) {
			ListItem.prototype.deselect(element);
		}

		var index = ListItem.prototype.getCurrentIndex(element);
		if (index == -1) {
			return;
		} else {
			// shift other items left
			for (var i = index; i < DropTarget.prototype.registeredElements.length - 1; i++) {
				DropTarget.prototype.registeredElements[i] = DropTarget.prototype.registeredElements[i + 1];
			}
			DropTarget.prototype.registeredElements.length = i;
		}
	},

	drag: function(event)
	{
		var dragItem = window.top.dragItem;
		var dragElement = dragItem && dragItem.element;
		var iList = null;
		var pagePos = getGlobalizedMousePosition(event);

		if (!ListItem.prototype.localDraggingElement) return true;

		// if we just start dragging, create the drag element from the selected element
		if (!dragItem) {
			window.top.dragItem = dragItem = new DragItem(ListItem.prototype.localDraggingElement);
			dragElement = dragItem.element;
		}

		if (ListItem.prototype.allowMove == 'true') {
			var dropTarget = DropTarget.prototype.getActiveDropTarget(pagePos[0], pagePos[1]);
			if (dropTarget) {
				iList = dropTarget.up('table.itemlist');
			}
			if (dropTarget && (!dropTarget.isPlaceHolder)) {
				// move the placeholder
				var placeHolder = ListItem.prototype.placeHolder;
				var parentNode = dropTarget.parentNode;
				if (isPlaceHolderAfter(dropTarget)) {
					// move placeholder just before the dropTarget
					var before = true;
					parentNode.insertBefore(placeHolder, dropTarget);
				}
				else {
					// move placeholder just after the droptarget
					var before = false;
					var nextSibling = $(dropTarget).next(ListItem.prototype.tagName);
					parentNode.insertBefore(placeHolder, nextSibling);
				}
				ListItem.prototype.dropTargetData = {
					element: dropTarget,
					before: before
				};
			}
		}

		// set the element's new position
		pbSetMarginBoxLocalTop(dragElement, pagePos[1] + 5);
		if (iList) {
			pbSetMarginBoxLocalLeft(dragElement, iList.cumulativeOffset()[0]);
		} else {
			pbSetMarginBoxLocalLeft(dragElement, pagePos[0] + 5);
		}

		// do not text-select other elements
		Event.stop(event);
		return false;
	},

	handleMouseUp: function(event)
	{
		if (ListItem.prototype.allowMove) {
			detachEventFromAllFrames(window.top, 'mousemove', ListItem.prototype.drag);
		}

		detachEventFromAllFrames(window.top, 'mouseup', ListItem.prototype.handleMouseUp);

		// get the dragging element
		if (window.top.dragItem) {

			// an item was dragged
			var dragItem = window.top.dragItem;
			var placeHolder = ListItem.prototype.placeHolder;

			// remove the placeholder from the registered elements
			if (DropTarget.prototype.registeredElements[DropTarget.prototype.registeredElements.length - 1] == placeHolder) {
				DropTarget.prototype.registeredElements.pop();
			}

			if (dragItem.targetWidget == 'itemlist') {

				var listElement = ListItem.prototype.insertElement(placeHolder, dragItem);

			} else if (dragItem.targetWidget == 'tree') {

				placeHolder.parentNode.removeChild(placeHolder);
				ListItem.prototype.moveToTree(dragItem);

				var listElement = null;
			}

			ListItem.prototype.unregister(placeHolder);

			// remove the global dragging object
			dragItem.element.parentNode.removeChild(dragItem.element);

			// let other widgets know that the dragged item has gone
			window.top.dragItem = null;

			if (ListItem.prototype.allowSelection) {
				// deselect all selected elements
				ListItem.prototype.deselectAll();
				// add the element
				if (listElement) {
					if (!ListItem.prototype.isSelected(listElement)) {
						ListItem.prototype.select(listElement);
					}
				}
			}

		} else {

			if (ListItem.prototype.allowSelection) {

				var element = ListItem.prototype.getTargetElement(event);

				if (element == ListItem.prototype.localDraggingElement) {

					// control button pressed?
					if (event.ctrlKey) {
						// invert the selection of the selected element
						if (ListItem.prototype.isSelected(element)) {
							ListItem.prototype.deselect(element);
						} else {
							ListItem.prototype.select(element);
						}
					} else if (event.shiftKey && (ListItem.prototype.selectedElements.length > 0)) {
						// get the previously selected element
						var previousElement = ListItem.prototype.selectedElements[ListItem.prototype.selectedElements.length - 1];
						// deselect all selected elements
						ListItem.prototype.deselectAll();
						// select all elements from the previously selected element to the current element
						ListItem.prototype.selectRange(previousElement, element);
					} else {
						// deselect all selected elements
						ListItem.prototype.deselectAll();
						// add the element
						if (!ListItem.prototype.isSelected(element)) {
							ListItem.prototype.select(element);
						}
					}
				}
			}
		}
		return true;
	},

	insertElement: function(placeHolder, dragItem)
	{
		// copy the dragging element back to the document of the itemlist
		var listElement = copyToDocument(dragItem.element, document, true);
		// drag the TR out of the table wrapper
		if (listElement.nodeName.toUpperCase() == 'TABLE') {
			listElement = listElement.getElementsByTagName('tr')[0];
		}

		// replace placeholder by listElement
		placeHolder.parentNode.insertBefore(listElement, placeHolder);
		placeHolder.parentNode.removeChild(placeHolder);

		dropTargetData = ListItem.prototype.dropTargetData;
		if (dropTargetData && dropTargetData.element) {

			dropTarget = dropTargetData.element;
			before = dropTargetData.before;

			var sourceId = listElement.id.substring(id_prefix);
			var targetId = dropTarget.id.substring(id_prefix);
			var url = PbLib.getNewURI('l/lime/node/' + sourceId + '/insert/' + targetId + '/' + (before ? 'before' : 'after'));
			new Ajax.Request(url);

			// update the order of the registered elements, used for range selection
			var reorderedElements = [];
			for (var i = 0; i < DropTarget.prototype.registeredElements.length; i++) {
				var element = DropTarget.prototype.registeredElements[i];
				if (element == listElement) {
					// drop listElement
				} else if (element == dropTarget) {
					if (before) reorderedElements.push(listElement);
					reorderedElements.push(element);
					if (!before) reorderedElements.push(listElement);
				} else {
					reorderedElements.push(element);
				}
			}
			DropTarget.prototype.registeredElements = reorderedElements;
		}

		// reset saved values
		for (var property in ListItem.prototype.saved) {
			listElement.style[property] = ListItem.prototype.saved[property];
		}

		// turn this new element into a complete listitem
		new ListItem(listElement);

		return listElement;
	},

	moveToTree: function(dragItem)
	{
		var dropNodeId = dragItem.targetNodeId;
		var dragNodeId = dragItem.limeId;

		var url = PbLib.getNewURI('l/lime/node/') + dragNodeId + '/move_node/' + dropNodeId;
		new Ajax.Request(url);

		PbLib.notice('success', 'The document was moved.');
	}
}

/*********************** DragItem **********************************************/

function DragItem(element)
{
	// create placeholder thumbnail
	var placeHolder = document.createElement(ListItem.prototype.tagName);
	if (ListItem.prototype.tagName == 'tr') {
		for(var i = 0; i < element.childNodes.length; i++) {
			var td = document.createElement('td');
			td.style.height = element.offsetHeight + 'px';
			placeHolder.appendChild(td);
		}
	}

	placeHolder.className = 'thumbnail_placeholder';
	placeHolder.isPlaceHolder = true;
	element.parentNode.insertBefore(placeHolder, element);
	ListItem.prototype.placeHolder = placeHolder;

	// remove this element from the list of registered elements
	ListItem.prototype.unregister(element);

	// add the placeHolder to this list
	DropTarget.prototype.registeredElements = DropTarget.prototype.registeredElements.concat(placeHolder);

	// save values
	ListItem.prototype.saved = {
		position: element.style.position,
		cssFloat: element.style.cssFloat,
		styleFloat: element.style.styleFloat,
		zIndex: element.style.zIndex
	};

	// clones an element into absolute positioned element of the top-window's space
	var dragElement = copyToDocument(element, window.top.document, true);
	dragElement.style.height = element.style.height;
	dragElement.style.position = 'absolute';
	dragElement.style.cssFloat = '';
	dragElement.style.styleFloat = '';
	dragElement.style.zIndex = 1000;

	// remove the original element that was selected
	element.parentNode.removeChild(element);
	// add the dragging element to the top-level window
	window.top.document.body.appendChild(dragElement);

	// expose the html element of this item
	this.element = dragElement;
	// presume the target is an itemlist, until told otherwise
	this.targetWidget = 'itemlist';
	// expose this item's lime-id
	this.limeId = element.id.substring(id_prefix);
}

/*********************** DropTarget **********************************************/

function DropTarget(element)
{
	//Event.observe(element, 'mouseup', this.drop);
	element.isDropTarget = true;
}

DropTarget.prototype = {

	registeredElements: [],

	/**
	 * Returns the dropTarget element under element.
	 * If element overlaps no dropTarget, false is returned.
	 */
	getActiveDropTarget: function(mouseX, mouseY)
	{
		var activeDropTarget = false;
		for (var i=0; i < DropTarget.prototype.registeredElements.length; i++) {
			var dropTarget = DropTarget.prototype.registeredElements[i];
			var dropTargetRect = getPageRectangle(dropTarget);
			if (mouseX >= dropTargetRect.left && mouseX < dropTargetRect.right &&
				mouseY >= dropTargetRect.top && mouseY < dropTargetRect.bottom) {
				activeDropTarget = dropTarget;
			}
		}
		return activeDropTarget;
	}
}

/*********************** helper functions **********************************************/

/* is this stuff defined? */
if (!document.ELEMENT_NODE) {
	document.ELEMENT_NODE = 1;
	document.ATTRIBUTE_NODE = 2;
	document.TEXT_NODE = 3;
	document.CDATA_SECTION_NODE = 4;
	document.ENTITY_REFERENCE_NODE = 5;
	document.ENTITY_NODE = 6;
	document.PROCESSING_INSTRUCTION_NODE = 7;
	document.COMMENT_NODE = 8;
	document.DOCUMENT_NODE = 9;
	document.DOCUMENT_TYPE_NODE = 10;
	document.DOCUMENT_FRAGMENT_NODE = 11;
	document.NOTATION_NODE = 12;
}

// http://www.alistapart.com/articles/crossbrowserscripting
// http://www.alistapart.com/d/crossbrowserscripting/xbImportNode.js
function copyToDocument(node, document, allChildren)
{
	/* find the node type to import */
	switch (node.nodeType) {
		case document.ELEMENT_NODE:

			/* create a new element */
			var newNode = document.createElement(node.nodeName);
			/* does the node have any attributes to add? */
			if (node.attributes && node.attributes.length > 0)
				/* add all of the attributes */
				for (var i = 0, il = node.attributes.length; i < il;)
					newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName));
			/* are we going after children too, and does the node have any? */
			if (allChildren && node.childNodes && node.childNodes.length > 0)
				/* recursively get all of the child nodes */
				for (var i = 0, il = node.childNodes.length; i < il;)
					newNode.appendChild(copyToDocument(node.childNodes[i++], document, allChildren));

			copyStyle(node, newNode);

			/* ie does not allow you to drag a TR */
			if (node.nodeName.toUpperCase() == 'TR') {
				var t = document.createElement('TABLE');
				var b = document.createElement('TBODY');
				t.appendChild(b);
				b.appendChild(newNode);
				copyStyle(node.parentNode.parentNode, t);
				copyStyle(node.parentNode, b);
				t.style.width = parseInt(node.parentNode.parentNode.offsetWidth + 'px');
				return t;
			}

			return newNode;
			break;
		case document.TEXT_NODE:
		case document.CDATA_SECTION_NODE:
		case document.COMMENT_NODE:
			return document.createTextNode(node.nodeValue);
			break;
	}
};

function copyStyle(node, newNode)
{
	newNode.className = node.className;

	if (window.getComputedStyle) {
		var style = window.getComputedStyle(node, null);
		for (att in style) {
			if (att != 'height' && att != 'border') {
				try {newNode.style[att] = style[att];} catch (e) { };
			}
		}
	}
	newNode.style.backgroundColor = node.style.backgroundColor;
	if (newNode.nodeName.toUpperCase() == 'TD') {
		newNode.style.borderWidth = '1px';
	}
}

function getPageRectangle(element)
{
	var position = getGlobalizedPosition(element);
	var left = position[0];
	var top = position[1];

	return {left: left, top: top,
		right: left + pbGetMarginBoxWidth(element) - 1, bottom: top + pbGetMarginBoxHeight(element) - 1}
}

function getGlobalizedPosition(element)
{
	var scrollX = !Object.isUndefined(document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft;
	var scrollY = !Object.isUndefined(document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop;
	return globalize(element,
		[
			pbGetMarginBoxPageLeft(element),
			pbGetMarginBoxPageTop(element)]);
}

function getGlobalizedMousePosition(event)
{
	return globalize(pbGetEventTarget(event),
		[
			pbGetEventPageX(event),
			pbGetEventPageY(event)]);
}

function globalize(element, pos)
{
	var iframe = null;
	var iframes = window.top.document.getElementsByTagName('iframe');
	for (var i = 0; i < iframes.length; i++) {
		if (iframes[i].contentWindow.document == element.ownerDocument) {
			iframe = iframes[i];
		}
	}
	if (iframe) {
		var scrollX = !Object.isUndefined(document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft;
		var scrollY = !Object.isUndefined(document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop;
		pos[0] += pbGetMarginBoxPageLeft(iframe) - scrollX;
		pos[1] += pbGetMarginBoxPageTop(iframe) - scrollY;
	}

	return pos;
}

function attachEventToAllFrames(window, event, handler)
{
	if (!!Object.isUndefined(window.PbLib) || !window.document.body) {
		// iframe not yet loaded
		setTimeout(function(){
			attachEventToAllFrames(window, event, handler);
		}, 1000);
	}
	else {
		window.Event.observe(window.document, event, handler);
		var frames = window.document.getElementsByTagName('iframe');
		for (var i = 0; i < frames.length; i++) {
			attachEventToAllFrames(frames[i].contentWindow, event, handler);
		}
	}
}

function detachEventFromAllFrames(window, event, handler)
{
	if (!!Object.isUndefined(window.PbLib) || !window.document.body) {
		// window is destroyed
		setTimeout(function(){detachEventFromAllFrames(window, event, handler);}, 1000);
	} else {
		window.Event.stopObserving(window.document, event, handler);
		var frames = window.document.getElementsByTagName('iframe');
		for (var i = 0; i < frames.length; i++) {
			detachEventFromAllFrames(frames[i].contentWindow, event, handler);
		}
	}
}

function getNextSiblingNode(node, tagName)
{
	var nextSibling = node.nextSibling;
	while (nextSibling) {
		if (nextSibling.tagName == tagName) return nextSibling;
		nextSibling = nextSibling.nextSibling;
	}
	return nextSibling;
}
function isPlaceHolderAfter(node)
{
	var sibling = node;
	while (sibling = sibling.nextSibling) {
		if (sibling.isPlaceHolder) return true;
	}
	return false;
}


/*********************** context menu **********************************************/

function list_contextmenu_placeholder(event)
{
	var object = ListItem.prototype.getTargetElement(event);
	list_contextmenu_placeholder_immediate(object);
}

function list_contextmenu_placeholder_immediate(object)
{
	var struct = [{
					name : 'Loading...',
					icon : PbLib.getNewURI('/ui/uibase/img/loader.gif'),
					action : function(){ }
				}];
	object.pbContextMenu = false;
	PbLib.addContextMenu(object, struct);
}

function list_contextmenu(event)
{
	var object = ListItem.prototype.getTargetElement(event);

	if (event.button != 2) return true;

	var nodeIds = ListItem.prototype.getSelectedElementIds();

	// clone the event, because the coordinates of the original will be modified
	var savedEvent = {}
	for (var key in event) {
		savedEvent[key] = event[key];
	}

	var index = listLoadingMenus.lastIndex++
	listLoadingMenus[index] = {object: object, savedEvent: savedEvent};

	new Ajax.Request(PbLib.getNewURI('l/lime/node/' + nodeIds.join('_') + '/contextmenu'),
		{
			onSuccess: list_contextmenu_success,
			onFailure: list_failed,
			parameters: {index: index}
		});

	return true;
}

function list_contextmenu_success(response)
{
	var json = response.responseJSON;
	var parameters = listLoadingMenus[response.request.parameters.index];
	var object = parameters.object;
	var savedEvent = parameters.savedEvent;

	for (var i = 0, l = json.length; i < l; ++i) {
		json[i].action = new Function(json[i].action);
	}

	object.pbContextMenu = false;
	PbLib.addContextMenu(object, json);
	object.pbContextMenu.show(savedEvent);
}

function list_failed()
{
	alert('Error connecting to server.');
}
