/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2010 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

  //////////////////
 //   LiveGrid   //
//////////////////

/**
 * The LiveGrid constructor.
 * @constructor
 * @param {Array} columns An array with column definitions.
 * Each column definition is an object with the following fields:
 * <ul><li>text is used as the column header,</li>
 * <li>i18n specifies whether the text is translated or used directly as
 * innerHTML (if text is an I18nString, i18n should be false),</li>
 * <li>index is used to extract the value of this column from an item object.
 * If not specified, the entire object is passed to {@link #set}.</li>
 * <li>width specifies the width of the column as a CSS length value,</li>
 * <li>style specifies an object with style attributes as elements,</li>
 * <li>sortable specifies whether sorting based on this column is supported,</li>
 * <li>id specifies the column ID,</li>
 * <lI>clear specifies a function which is called with an HTML DIV element as
 * parameter whenever the content of that DIV must be fetched from
 * the server. In the case of a static text in every row, {@link #makeClear}
 * can be used to create a function for this field.</li>
 * <li>set specifies a function which is called with an HTML DIV element and
 * the contents of that DIV whenever new contents must be displayed in the DIV.
 * In the case of pure text content, {@link #defaultSet} can be used
 * directly.</li></ul>
 * @param {Selection} selection The selection to use.
 * @param {String} module The name of a module for which to register Drag'n'Drop
 * functions.
 */
function LiveGrid(columns, selection, module) {
	this.disablehover=false;
	this.emptylivegridtext = _("This folder is empty");

	/**
	 * @private
	 */
	this.columns = columns;
	
	/**
	 * @private
	 */
	this.clickprocessed=false;

	/**
	 * The selection displayed in this LiveGrid.
	 */
	this.selection = selection || console.error("LiveGrid without Selection");
	
	/**
	 * @private
	 */
	this.module = module;

	/**
	 * Index of the anchor row.
	 * The anchor is used for multiple-selects with the Shift key.
	 * 0&nbsp;&lt;=&nbsp;anchor&nbsp;&lt;&nbsp;rowCount.
	 * @private
	 */
	this.anchor = 0;
	
	/**
	 * Index of the focus row.
	 * The focus is used for all selects.
	 * 0&nbsp;&lt;=&nbsp;focus&nbsp;&lt;&nbsp;rowCount.
	 * @private
	 */
	this.focus = 0;
	
	/**
	 * @private
	 * Sort order. Either "asc" or "desc".
	 */
	this.sort_order = "asc";
	
	/**
	 * @private
	 * ID of the column used for sorting. null if sorting is disabled.
	 */
	this.sort_id = null;
	
	for (var i = 0; i < columns.length; i++)
		if (!this.sort_id && columns[i].sortable) this.sort_id = columns[i].id;

	this.lineHeight = 1.6;
	
	/**
	 * @private
	 */
	this.from = 0;

	/**
	 * @private
	 */
	this.to = 0;
	
	/**
	 * @private
	 */
	this.rowcache = [];
	
	/**
	 * @private
	 * {Int: true}
	 */
	this.unfetched = {};

	var Self = this;

	/**
	 * @private
	 */
	this.fullRedraw = true;
	
	/**
	 * @private
	 */
	this.scroll_cb = function() { Self.scroll(); };
	
	/**
	 * @private
	 */
	this.key_cb = function(e) { return Self.key(e); };

	/**
	 * @private
	 */
	this.keydown_cb = function(e) {
		this.keydown = true;
		return Self.key(e);
	};
	
	/**
	 * @private
	 */
	this.keypress_cb = function(e) {
		if (this.keydown)
			this.keydown = false;
		else
			return Self.key(e);
	};
	
	/**
	 * @private
	 */
	this.mousedown_cb = function(e) { Self.mousedown(e); };
	
	/**
	 * @private
	 */
	this.ctxtmenu_cb = function(e) { Self.ctxtmenu(e); };
	
	/**
	 * @private
	 */
	this.click_cb = function(e) { Self.click(e); };
	
	this.language_cb = function(e) { Self.scroll(true); };
	/**
	 * @private
	 */
	this.dblclick_cb = function (e) {
	    // not top element?
        if (e.target !== Self.parent) {
            Self.dblclick(e);
        }
	};
	
	/**
	 * @private
	 */
	this.focus_cb = function() {
		focusedElement = Self.parent;
		Self.updateStyles();
	};
	
	/**
	 * @private
	 */
	this.blur_cb = function() {
		if (focusedElement == Self.parent)
			focusedElement = null;
		Self.updateStyles();
	};
	
	/**
	 * @private
	 */
	this.change_cb = function(from, to) {
		Self.rowCount = Self.storage.ids.length;
		from = Math.max(from, Self.from);
		to = Math.min(to, Self.to);
		for (var i = from; i < to; i++) Self.unfetched[i] = true;
		Self.doScroll();
		Self.updateStyles();
		Self.events.trigger("Changed", Self.storage);
	};
	
	/**
	 * @private
	 */
	this.selected_cb = function(count) {
		Self.updateStyles();
		Self.events.trigger("Selected", count);
	};
	
	/**
	 * Events triggered by this LiveGrid.
	 * <dl><dt>Selected</dt><dd>The selection has changed. Triggered only when
	 * this grid is enabled. Parameters:
	 * <ul><li>Number of selected elements.</li></ul></dd>
	 * <dt>Activated</dt><dd>One or more elements have been activated.
	 * Parameters:
	 * <ul><li>Array with object IDs of activated objects.</li></ul></dd>
	 * <dt>Delete</dt><dd>One or more elements should be deleted.
	 * Parameters:
	 * <ul><li>Array with object IDs of activated objects.</li></ul></dd>
	 * </dl>
	 */
	this.events = new Events();
}

function getRowIndex(e) {
	var n = e.target || e.srcElement;
	while (n.oxRowIndex == undefined && n.parentNode) n = n.parentNode;
	return n.oxRowIndex;
}

LiveGrid.prototype = {
	/**
	 * How many additional invisible rows to create to allow flicker-free
	 * scrolling at faster speeds.
	 */
	preview: 20,
	
	/**
	 * Granularity of row fetches.
	 */
	granularity: 1,
	
    /**
     * Recreates the LiveGrid, taking into account updated column definitions
     * etc.
     */
	recreate: function() {
        var storage = this.storage;
        var oldHeader = this.headerNode;
        this.disable();
        for (var i in this.rows) this.deleteRow(i);
        this.rowcache = [];
        this.from = 0;
        this.to = 0;
        this.ruWidth = 0;
        var header = this.getHeader();
        if (oldHeader && oldHeader.parentNode) {
            oldHeader.parentNode.replaceChild(header, oldHeader);
        }
        if (this.parent) {
            this.parent.removeChild(this.heightDiv);
            this.parent.removeChild(this.emptyMessage);
            this.getTable(this.parent);
        }
        if (this.hoverNode) {
            this.addHover(this.hoverNode, this.hoverCallback);
            if (this.hoverSizeNode) this.hover.setSize(this.hoverSizeNode);
        }
        if (storage) this.enable(storage);
    },
	
	/**
	 * Creates a DOM tree for the headers of the LiveGrid.
	 * The returned DOM tree must be inserted in its place in the document.
	 * @type Object
	 * @return A DOM tree which displays the headers of the LiveGrid.
	 * @see #getTable
	 */
	getHeader: function() {
		var Self = this;
		this.headers = new Array(this.columns.length);
		this.outer_headers = new Array(this.columns.length);
		this.arrows = new Array(this.columns.length);
		this.arrow_events = new Array(this.columns.length);
		for (var i in this.columns) this.arrow_events[i] = sortHandler(i);
		function sortHandler(i) {
			return function(e) {
				Self.setSort(Self.columns[i].id,
					(Self.sort_id == Self.columns[i].id &&
					 Self.sort_order == "asc") ? "desc" : "asc");
				stopEvent(e);
			}
		}
		this.ids = {};
		for (var i in this.columns) this.ids[this.columns[i].id] = i;
		var colgroup = this.getColGroup();
		colgroup.appendChild(this.ruCorner = newnode("col", {width: "0"}));
		var tbody = newnode("tbody",{verticalAlign : "top"});
		var table = this.headerNode = newnode("table", {tableLayout: "fixed"},
		                    {cellPadding: "0", cellSpacing: "0", className: "livegrid"},
		                    [colgroup, tbody]);
		var row = newnode("tr", {height: this.lineHeight + "em"},
		                  {className: "th"});
		for (var i = 0; i < this.columns.length; i++) {
			var outerCell, innerCell;
            var headerClassName = this.columns[i].name
                ? "header livegrid-column-" +
                      this.columns[i].name.join(" livegrid-column-")
                : "header";
			if (this.columns[i].sortable) {
				outerCell = this.outer_headers[i] =
					newnode("th", this.columns[i].style, {className: headerClassName}, [
						newnode("table", 0, {cellPadding: "0", cellSpacing: "0"}, [
							newnode("tbody", 0, 0, [
								newnode("tr", 0, 0, [
									innerCell = this.headers[i] = newnode("th", {whiteSpace: "nowrap"}),
									newnode("th", 0, 0, [
										this.arrows[i] = newnode("img", {verticalAlign: "middle"}, {src: getFullImgSrc("img/dummy.gif"), width: "16", height: "16"})
									])
								])
							])
						])
					]);
			} else
				innerCell = outerCell = this.headers[i] = this.outer_headers[i] =
					newnode("th", this.columns[i].style, {className: headerClassName});
			if (this.columns[i].i18n) {
			    var ctx = this.columns[i].i18n;
                if (ctx === true) ctx = "";
				var node = new I18nNode(pgettext(ctx, this.columns[i].text));
				node.parent = innerCell;
				node.update = function() {
					this.parent.title = this.node.data = this.callback();
				};
				innerCell.appendChild(node.node);
				node.update();
			} else if (this.columns[i].text instanceof I18nString) {
                innerCell.appendChild(addTranslated(this.columns[i].text));
			} else switch (typeof this.columns[i].text) {
                case "string":
				    innerCell.innerHTML = this.columns[i].text;
                    break;
                case "function":
                    innerCell.appendChild(addTranslated(this.columns[i].text));
                    break;
                default:
                    innerCell.appendChild(this.columns[i].text);
            }
			row.appendChild(outerCell);
		}
		row.appendChild(newnode("th"));
        var index = this.ids[this.sort_id];
		if (this.sort_id && this.arrows[index]) {
			this.arrows[index].src = 
				this.sort_order == "asc" ? getFullImgSrc("img/arrows/sort_up.gif") :
				this.sort_order == "desc" ? getFullImgSrc("img/arrows/sort_down.gif") :
				getFullImgSrc("img/dummy.gif"); // just in case
            LiveGrid.setSortClass(this.outer_headers[index], true);
        }
		tbody.appendChild(row);
		return table;
	},
	
	/**
	 * Adds the scrollable area of the LiveGrid to the specified parent node.
	 * The parent node should have an explicit size and "overflow: auto".
	 */
	getTable: function(parent) {
		this.rows = {};
		var last = this.columns.length - 1;
		this.parent = parent;
		this.heightDiv = newnode("div", {height: "0em"}, {className: "livegrid"});
		parent.appendChild(this.heightDiv);
		parent.tabIndex = 0;
		parent.style.outline = "none";
		parent.style.overflowX = "hidden";
		parent.hideFocus = true;
		var emptyText = addTranslated(this.emptylivegridtext);
		this.emptyMessage = newnode("div",
			{display: "none", position: "absolute", textAlign: "center", 
			 width: "100%", marginTop: "5px"}, 0,
			[emptyText]);
		parent.appendChild(this.emptyMessage);
		var tr = newnode("tr");
		for (var i = 0; i < this.columns.length; i++)
			tr.appendChild(newnode("td", this.columns[i].style,
			                       {className: "cell"}));
		this.rowTemplate = newnode("table", 0,
			{cellPadding: "0", cellSpacing: "0", className: "tr"},
			[this.getColGroup(), newnode("tbody", 0, 0, [tr])]);
	},
	
	/**
	 * Adds a hover to the LiveGrid.
	 * This method should be called between getTable() and the first enable(). 
	 * @param {Object} hover A DOM node which serves as a hover. It should be
	 * attached directly to the body and have the CSS style
	 * "position: absolute".
	 * @param {Function} callback A callback function which is called when
	 * the hover is shown. As its only parameter, it receives the ID of
	 * the entry over which the mouse is hovering.
	 * @type {Object}
	 * @return A new Hover object. The enable and disable methods of the hover
	 * object will be called automatically by the LiveGrid.
	 */
	addHover: function(hover, callback) {
        this.hoverNode = hover;
	    this.hoverCallback = callback;
		this.hover = new Hover(this.heightDiv, hover);
		var Self = this;
        this.hover.setSize = function(node) {
            Self.hoverSizeNode = node;
            return Hover.prototype.setSize.call(this, node);
        }
		this.hover.onShow = function(node) {
			var i = node.oxRowIndex;
			if (i !== undefined)
				callback(Self.storage.ids[i]);
			else
				return false;
		}
		return this.hover;
	},
	
	/**
	 * @private
	 */
	getColGroup: function() {
		var colgroup = newnode("colgroup");
		for (var i = 0; i < this.columns.length; i++) {
			var w = this.columns[i].width;
			colgroup.appendChild(newnode("col", w ? {width: w} : 0));
		}
		return colgroup;
	},
	
	/**
	 * Appends the contents either of the entire grid or of selected rows to
	 * the specified node.
	 * @param {Object} parent The node to append the data to.
	 * @param {Function} cb The callback to call after all data is appended.
	 * @param {Boolean} selected True if only selected rows should be appended.
	 * Otherwise, all rows are appended.
	 */
	print: function(parent, cb, selected) {
		var doc = parent.ownerDocument;
		var tr = doc.createElement("tr");
		for (var i = 0; i < this.columns.length; i++) {
			var th = doc.createElement("th");
			if (this.columns[i].i18n)
				th.appendChild(doc.createTextNode(_(this.columns[i].text)) || "\u00a0");
			else
				th.innerHTML = this.columns[i].text || "\u00a0";
			tr.appendChild(th);
		}
		var table = newnode("table", {width: "100%"}, {border: 1},
			[newnode("thead", 0, 0, [tr], doc)], doc);
		var trs = [];
		var Self = this;
		function set(n, obj) {
			trs[n] = doc.createElement("tr");
			for (var i = 0; i < Self.columns.length; i++) {
				var td = doc.createElement("td");
				trs[n].appendChild(td);
				var col = Self.columns[i];
				col.set(td, col.index ? obj[col.index] : obj);
			}
		}
		function final_() {
			var tbody = newnode("tbody", 0, 0, 0, doc);
			for (var i = 0; i < trs.length; i++) 
				if (trs[i]) tbody.appendChild(trs[i]);
			table.appendChild(tbody);
			parent.appendChild(table);
			if(trs.length==0) {
				//return; 
			} 
			cb();
		}
		this.storage.newIterate(
			selected ? this.selection.getSelected() : this.storage.ids,
			emptyFunction, set, final_);
	},
	
	/**
	 * Displays the specified sort order as an arrow in the headers.
	 * @param {String} id The column ID used for sorting, or null for no
	 * sorting.
	 * @param {String} order The sort order. One of "asc", "desc" or null for no
	 * sorting.
	 */
	setSort: function(id, order) {
		if (this.sort_id && id != this.sort_id) {
            var index = this.ids[this.sort_id];
			this.arrows[index].src =
                getFullImgSrc("img/dummy.gif");
            LiveGrid.setSortClass(this.outer_headers[index], false);
        }
        var index = this.ids[id];
        if (index || index === 0) {
    		this.arrows[index].src = 
    			order == "asc" ? getFullImgSrc("img/arrows/sort_up.gif") :
    			order == "desc" ? getFullImgSrc("img/arrows/sort_down.gif") :
    			getFullImgSrc("img/dummy.gif"); // just in case
            LiveGrid.setSortClass(this.outer_headers[index], true);
        }
		this.sort_id = id;
		this.sort_order = order;
		if (this.storage) this.sort(id, order);
	},
	
	/**
	 * Callback function which changes the displayed data.
	 * Should be overridden in each instance.
	 * @param {String} id The column ID used for sorting, or null for no
	 * sorting.
	 * @param {String} order The sort order. Either "asc" or "desc".
	 */
	sort: emptyFunction,

	getSelectedIDs: function() {
		return this.selection.getSelected();
	},
	
	/**
	 * Deletes items with specified object IDs from the storage and from
	 * the selection. If the entire selection was deleted, the first unselected
	 * item at or after the focus is selected.
	 */
	deleteIDs: function (ids) {
	    if (this.storage) {
    		var sids = {};
    		for (var i = 0; i < ids.length; i++)
    			sids[this.storage.serialize(ids[i])] = true;
    		while (this.storage.getSID(this.focus) in sids) this.focus++;
    		var newID = this.storage.ids[this.focus];
    		this.storage.removeIDs(ids);
    		this.selection.deselectSIDs(sids);
    		this.focus = newID ? this.storage.getIndex(newID) : 0;
    		if (!this.selection.count)
    			this.selection.click(this.focus);
	    }
	},
	
	updateSelected: function(callback) {
		if (this.storage) {
			this.storage.localUpdate(this.selection.getSelected(), callback);
		}
	},

	/**
	 * Initializes the LiveGrid.
	 * The DOM tree must have been created by {@link #getHeader} and
	 * {@link #getTable}.
	 * @see #getHTML
	 */
	enable: function(storage) {
		if (this.storage) {
			this.storage.events.unregister("Changed", this.change_cb);
		} else {
			for (var i in this.arrows)
				addDOMEvent(this.outer_headers[i], "click", this.arrow_events[i]);
			addDOMEvent(this.parent, "scroll", this.scroll_cb);
			addDOMEvent(this.parent, "keydown", this.keydown_cb);
			addDOMEvent(this.parent, "keypress", this.keypress_cb);
			addDOMEvent(this.parent, "mousedown", this.mousedown_cb);
			addDOMEvent(this.parent, "click", this.click_cb);
			// bind double click via jQuery (due to bug #14560)
            jQuery(this.parent).bind("dblclick", this.dblclick_cb);
			addDOMEvent(this.parent, "focus", this.focus_cb);
			addDOMEvent(this.parent, "blur", this.blur_cb);
            if (this.contextmenu)
                addDOMEvent(this.parent, "contextmenu", this.ctxtmenu_cb);
			if (this.hover && !this.disablehover) this.hover.enable();
			resizeEvents.register("Resized", this.scroll_cb);
			register("LanguageChanged",this.language_cb);
			if (this.module && (this.module != "links")) {
				var Self = this;
				var disabled;
				var validfield;
				switch(this.module) {
					case "calendar": disabled=calendardefaultdisabled;	validfield=5; break;
					case "tasks":	disabled=taskdefaultdisabled; validfield=3; break;
					case "contacts": disabled=contactdefaultdisabled; validfield=2; break;
					case "infostore": disabled=infostoredefaultdisabled; validfield=3; break;
					case "mail": disabled=maildefaultdisabled; validfield=5; break;
				}
				this.dragEvent = registerSource(this.heightDiv, this.module, function() {
					var myelements=clone(Self.selection.getSelected());
					if(myelements.length==1) {
						Self.storage.newIterate(myelements,emptyFunction,function (index,id) {
							myelements[0].livegridfield=id[validfield];
						})
					}
					objMoveCopy.begin(Self.module,myelements);
					return objMoveCopy;
					
				},null,null,disabled,defaultdisabledremove);
			}
		}
		storage.events.register("Changed", this.change_cb);
		this.storage = storage;
		this.rowCount = storage.ids.length;
		this.selection.setStorage(storage);
		this.selection.events.register("Selected", this.selected_cb);
		this.scroll(true);
	},
	
	disable: function() {
		if (!this.storage) return;
		this.selection.events.unregister("Selected", this.selected_cb);
		this.storage.events.unregister("Changed", this.change_cb);
		this.storage = null;
		this.selection.setStorage(null);
		for (var i in this.arrows)
			removeDOMEvent(this.outer_headers[i], "click", this.arrow_events[i]);
		removeDOMEvent(this.parent, "scroll", this.scroll_cb);
		removeDOMEvent(this.parent, "keydown", this.keydown_cb);
		removeDOMEvent(this.parent, "keypress", this.keypress_cb);
		removeDOMEvent(this.parent, "mousedown", this.mousedown_cb);
		removeDOMEvent(this.parent, "click", this.click_cb);
		jQuery(this.parent).unbind("dblclick", this.dblclick_cb);
		removeDOMEvent(this.parent, "focus", this.focus_cb);
		removeDOMEvent(this.parent, "blur", this.blur_cb);
        if (this.contextmenu)
            removeDOMEvent(this.parent, "contextmenu", this.ctxtmenu_cb);
		if (this.hover) this.hover.disable();
		resizeEvents.unregister("Resized", this.scroll_cb);
		unregister("LanguageChanged",this.language_cb);
		if (this.module) {
			unregisterSource(this.parent, this.dragEvent);
		}
	},
	
	/**
	 * @private
	 */
	createRow: function() {
		var last = this.columns.length - 1;
		var row = {tr: this.rowTemplate.cloneNode(true),
		           cells: new Array(this.columns.length)};
		for (var i = 0, cell = row.tr.lastChild.firstChild.firstChild; cell;
		     i++, cell = cell.nextSibling)
		{
			row.cells[i] = cell;
			this.columns[i].clear(cell);
		}
		this.heightDiv.appendChild(row.tr);
		if (!this.rowHeight) this.rowHeight = row.tr.offsetHeight;
		return row;
	},

	/**
	 * To be removed.
	 * @private
	 */
	updateStyles: function() {
		for (var i in this.rows) {
			var newclass = this.selection.get(i) ? "tr selected" : i%2 ? "tr_h " : "tr ";
			if (focusedElement == this.parent && i == this.focus)
				newclass += "focused";
			var tr = this.rows[i].tr;
			if (tr.className != newclass) tr.className = newclass;
		}
	},

	/**
	 * @private
	 */
	deleteRow: function(index) {
		var row = this.rows[index];
		row.tr.style.display = "none";
		this.rowcache.push(row);
		delete this.rows[index];
		delete this.unfetched[index];
	},

	/**
	 * @private
	 */
	insertRow: function(index) {
		var row = this.rows[index] =
			this.rowcache.length ? this.rowcache.pop() : this.createRow();
		row.tr.className = this.selection.get(index) ? "tr selected" : index%2 ? "tr_h" : "tr";
		row.tr.style.display = "";
		row.tr.style.top = (index * this.rowHeight) + "px";
		row.tr.oxRowIndex = index;
		this.unfetched[index] = true;
	},

	/**
	 * @private
	 */
	scroll: function(fullRedraw) {
		if (fullRedraw) this.fullRedraw = true;
		if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
		var Self = this;
		this.scrollTimeout = setTimeout(function() {
			Self.scrollTimeout = null;
			Self.doScroll();
		}, 0);
	},
	
	/**
	 * @private
	 */
	calculateRowHeight: function() {
		var row = this.createRow();
		row.tr.style.display = "none";
		this.rowcache.push();
	},

	/**
	 * @private
	 */
	doScroll: function() {
		var newruWidth = this.parent.offsetWidth - this.parent.clientWidth;
		if (newruWidth > 0 && newruWidth != this.ruWidth) {
			this.ruCorner.style.width = newruWidth + "px";
			this.ruWidth = newruWidth;
		}
		if (!this.rowHeight) this.calculateRowHeight();
		this.height = Math.floor(this.parent.clientHeight / this.rowHeight);
		var len = this.storage ? this.storage.ids.length : 0;
		var newHeight = (len * this.rowHeight) + "px"
		var hDivStyle = this.heightDiv.style;
		if (newHeight != hDivStyle.height) hDivStyle.height = newHeight;
		var Self = this;
		if (this.heightDiv.offsetWidth != this.parent.clientWidth)
			hDivStyle.width = Self.parent.clientWidth + "px";
		var scrolltop = this.parent.scrollTop;
		var from2 = Math.max(0, Math.floor(                           scrolltop  / this.rowHeight / this.granularity) * this.granularity - this.preview);
		var to2 = Math.min(len, Math.ceil((this.parent.clientHeight + scrolltop) / this.rowHeight / this.granularity) * this.granularity + this.preview);
		var Self = this;
		if (this.fullRedraw || from2 != this.from || to2 != this.to) {
			if (this.fullRedraw || from2 > this.to || this.from > to2) {
				for (var i = this.from; i < this.to; i++) this.deleteRow(i);
				for (var i = from2; i < to2; i++) this.insertRow(i);
			} else {
				for (var i = this.from; i < from2; i++) this.deleteRow(i);
				for (var i = to2; i < this.to; i++) this.deleteRow(i);
				for (var i = from2; i < this.from; i++) this.insertRow(i);
				for (var i = this.to; i < to2; i++) this.insertRow(i);
			}
			this.from = from2;
			this.to = to2;
		}
		this.fullRedraw = false;
        this.emptyMessage.style.display = (!this.storage || len) ? "none"
                                                                : "block";
		var ids = [];
		for (var i in this.unfetched) ids.push(this.storage.ids[i]);
		if (ids.length) {
			if (this.iterateRequest) this.storage.cancel(this.iterateRequest);
			this.iterateRequest = this.storage.newIterate(ids, function(n) {
				if (!(n in Self.unfetched)) return;
				var cellrow = Self.rows[n].cells;
				for (var i in cellrow) Self.columns[i].clear(cellrow[i]);
			}, function(n, obj) {
				if (!(n in Self.unfetched)) return;
				delete Self.unfetched[n];
                var cellrow = Self.rows[n].cells;
				for (var i in cellrow) {
					var col = Self.columns[i];
					col.set(cellrow[i], col.index ? obj[col.index] : obj);
				}
			}, function() { Self.iterateRequest = null; }, 20);
		}
	},
	
	/**
	 * @private
	 */
	scrollTo: function(y) {
		var parent = this.parent;
		setTimeout(function() { parent.scrollTop = y; }, 0);
	},
	
	/**
	 * Scrolls if necessary to display the currently focused row.
	 */
	showFocus: function() {
		var scrolltop = this.parent.scrollTop;
		if (!this.rowHeight) this.calculateRowHeight();
		if (this.focus < scrolltop / this.rowHeight)
			this.scrollTo(this.focus * this.rowHeight);
		else {
			var height = this.parent.clientHeight;
			if (this.focus + 1 > (scrolltop + height) / this.rowHeight)
				this.scrollTo((this.focus + 1) * this.rowHeight - height);
		}
	},

	/**
	 * @private
	 */
	key: function(e) {
		if (!this.storage) return;
		switch (e.keyCode || e.which) {
		case 13: // enter
			this.events.trigger("Activated", this.selection.getSelected());
			break;
		case 32: // spacebar
			this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 46: // delete
		case 108: // numpad delete
			this.events.trigger("Delete", this.selection.getSelected());
			break;
		case 38: // arrow up
			if (this.focus > 0) this.focus--;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 40: // arrow down
			if (this.focus < this.rowCount - 1) this.focus++;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 33: // page up
			this.focus -= this.height;
			if (this.focus < 0) this.focus = 0;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 34: // page down
			this.focus += this.height;
			if (this.focus >= this.rowCount) this.focus = this.rowCount - 1;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 35: // end
			this.focus = this.rowCount - 1;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 36: // home
			this.focus = 0;
			if (!(Mac ? e.metaKey : e.ctrlKey))
				this.selection.click(this.focus, e);
			this.showFocus();
			break;
		case 65: // A
		case 97: // a
			if (e.ctrlKey)
				this.selection.select(0, this.storage.ids.length);
			break;
		default:
			return;
		}
		this.updateStyles();
		stopEvent(e);
		return false;
	},
	
    /**
     * @private
     */
    mousedown: function(e) {
        this.clickprocessed = false;
        var focus = getRowIndex(e);
        if (this.storage.ids.length < this.height) {
            if (focus === undefined) focus = -1; else this.focus = focus;
            if(!this.selection.get(focus)) {
                this.clickprocessed=true;
                this.selection.click(focus, e);
            }
        } else if (focus !== undefined) {
            this.focus = focus;
            if(!this.selection.get(focus)) {
                this.clickprocessed=true;
                this.selection.click(focus, e);
            }
        }
        setFocus(this.parent);
    },

	/**
	 * @private
	 */
	ctxtmenu: function(e) {
		stopEvent(e);
        this.contextmenu.display(e.clientX, e.clientY, this.selection);
	},
	
	/**
	 * @private
	 */
	click: function(e) {
		if (this.clickprocessed) return;
		var focus = getRowIndex(e);
		if (this.storage.ids.length < this.height) {
			if (focus === undefined) focus = -1; else this.focus = focus;
			this.selection.click(focus, e);
		} else if (focus !== undefined) {
			this.focus = focus;
			this.selection.click(focus, e);
		}
		setFocus(this.parent);
	},
	
	/**
	 * @private
	 */
	dblclick: function(e) {
		if (!this.storage) return;
		this.focus = getRowIndex(e) || 0;
		this.events.trigger("Activated", this.selection.getSelected());
	}
};

LiveGrid.setSortClass = classNameSetter("livegrid-th-sorted");


/**
 * Default set function for use in the second parameter of the constructor.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified content value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} text The text to set in the DIV element.
 */
LiveGrid.defaultSet = function(div, text) {
	text = text || "\u00a0";
	if (div.firstChild)
		div.firstChild.data = text;
	else
		div.appendChild(document.createTextNode(text));
};

/**
 * Set function for use in formatting dates in the live grid.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified date value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} utc The utc to format in the DIV element.
 */
LiveGrid.dateSet = function(div, date) {
	var dateString = "\u00a0";
	if (null != date) dateString = formatDate(date, "date");
	if (div.firstChild)
		div.firstChild.data = dateString;
	else
		div.appendChild(document.createTextNode(dateString));
};

/**
 * Set function for use in formatting dates in the live grid.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified date value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} utc The utc to format in the DIV element.
 */
LiveGrid.dateSetTime = function(div, date) {
    var dateString = "\u00a0";
    if (null != date) dateString = formatDate(date, "datetime");
	if (div.firstChild)
		div.firstChild.data = dateString
	else
		div.appendChild(document.createTextNode(dateString));
};

/**
 * Creates a clear function for use in the second parameter of the constructor.
 * This function creates a function which sets all DIV elements of the LiveGrid
 * to the text which is specified as parameter to this function.
 * @param {String} text The text to set in DIV elements.
 * @type Function
 * @return A function which takes a HTML DIV element as parameter, adds a text
 * node to it, and sets the text to a constant value.
 */
LiveGrid.makeClear = function(text) {
	return function(div) {
		if (div.firstChild)
			div.firstChild.data = text;
		else
			div.appendChild(document.createTextNode(text));
	};
};

/**
 * Sort function for use with purely local storages.
 */
LiveGrid.localSort = function(id, order) {
    var a = [];
    var Self = this;
    this.storage.newIterate(this.storage.ids, emptyFunction,
        function(div, data) { a.push(data); }, function() {
            Self.storage.remove(0, Self.storage.ids.length);
            a.sort(order == "asc" ? function(a, b) {
                return a[id] > b[id] ? 1 : a[id] < b[id] ? -1 : 0;
            } : function(b, a) {
                return a[id] > b[id] ? 1 : a[id] < b[id] ? -1 : 0;
            });
            Self.storage.append(a);
        });
};















function LiveGrid2(columns, selection, module) {
    this.disablehover=false;
    this.emptylivegridtext = _("This folder is empty");

    /**
     * @private
     */
    this.columns = columns;
    
    /**
     * @private
     */
    this.clickprocessed=false;

    /**
     * The selection displayed in this LiveGrid2.
     */
    this.selection = selection || console.error("LiveGrid2 without Selection");
    
    /**
     * @private
     */
    this.module = module;
    this.dndModule = module;
    /**
     * Index of the anchor row.
     * The anchor is used for multiple-selects with the Shift key.
     * 0&nbsp;&lt;=&nbsp;anchor&nbsp;&lt;&nbsp;rowCount.
     * @private
     */
    this.anchor = 0;
    
    /**
     * Index of the focus row.
     * The focus is used for all selects.
     * 0&nbsp;&lt;=&nbsp;focus&nbsp;&lt;&nbsp;rowCount.
     * @private
     */
    this.focus = 0;
    
    /**
     * @private
     * Sort order. Either "asc" or "desc".
     */
    this.sort_order = "asc";
    
    /**
     * @private
     * ID of the column used for sorting. null if sorting is disabled.
     */
    this.sort_id = null;
    
    for (var i = 0; i < columns.length; i++)
        if (!this.sort_id && columns[i].sortable) this.sort_id = columns[i].sort;

    this.lineHeight = 1.6;
    
    /**
     * @private
     */
    this.from = 0;

    /**
     * @private
     */
    this.to = 0;
    
    /**
     * @private
     */
    this.rowcache = [];
    
    /**
     * @private
     * {Int: true}
     */
    this.unfetched = {};

    var Self = this;

    /**
     * @private
     */
    this.fullRedraw = true;
    
    /**
     * @private
     */
    this.scroll_cb = function() { Self.scroll(); };
    
    /**
     * @private
     */
    this.key_cb = function(e) { return Self.key(e); };

    /**
     * @private
     */
    this.keydown_cb = function(e) {
        this.keydown = true;
        return Self.key(e);
    };
    
    /**
     * @private
     */
    this.keypress_cb = function(e) {
        if (this.keydown)
            this.keydown = false;
        else
            return Self.key(e);
    };
    
    /**
     * @private
     */
    this.mousedown_cb = function(e) { Self.mousedown(e); };
    
    /**
     * @private
     */
    this.ctxtmenu_cb = function(e) { Self.ctxtmenu(e); };
    
    /**
     * @private
     */
    this.click_cb = function(e) { Self.click(e); };
    
    /**
     * @private
     */
    this.dblclick_cb = function (e) {
        // not top element?
        if (e.target !== Self.parent) {
            Self.dblclick(e);
        }
    };
    
    /**
     * @private
     */
    this.focus_cb = function() {
        focusedElement = Self.parent;
        Self.updateStyles();
    };
    
    this.language_cb = function(e) { Self.scroll(true); }
    
    /**
     * @private
     */
    this.blur_cb = function() {
        if (focusedElement == Self.parent)
            focusedElement = null;
        Self.updateStyles();
    };
    
    /**
     * @private
     */
    this.change_cb = function(from, to) {
        Self.rowCount = Self.collection.objects.length;
        from = Math.max(from, Self.from);
        to = Math.min(to, Self.to);
        for (var i = from; i < to; i++) Self.unfetched[i] = true;
        Self.doScroll();
        Self.updateStyles();
    };
    
    /**
     * @private
     */
    this.selected_cb = function(count) {
    	Self.updateStyles();
        Self.events.trigger("Selected", count);
    };
    
    /**
     * Events triggered by this LiveGrid2.
     * <dl><dt>Selected</dt><dd>The selection has changed. Triggered only when
     * this grid is enabled. Parameters:
     * <ul><li>Number of selected elements.</li></ul></dd>
     * <dt>Activated</dt><dd>One or more elements have been activated.
     * Parameters:
     * <ul><li>Array with object IDs of activated objects.</li></ul></dd>
     * <dt>Delete</dt><dd>One or more elements should be deleted.
     * Parameters:
     * <ul><li>Array with object IDs of activated objects.</li></ul></dd>
     * </dl>
     */
    this.events = new Events();
}

LiveGrid2.prototype = {
    constructor : "LiveGrid2",
    /**
     * How many additional invisible rows to create to allow flicker-free
     * scrolling at faster speeds.
     */
    preview: 15,
    
    /**
     * Granularity of row fetches.
     */
    granularity: 15,
    
    /**
     * Recreates the LiveGrid, taking into account updated column definitions
     * etc.
     */
    recreate: function() {
        var enabled = Boolean(this.collection);
        var oldHeader = this.headerNode;
        this.disable();
        for (var i in this.rows) this.deleteRow(i);
        this.rowcache = [];
        this.from = 0;
        this.to = 0;
        this.ruWidth = 0;
        var header = this.getHeader();
        if (oldHeader && oldHeader.parentNode) {
            oldHeader.parentNode.replaceChild(header, oldHeader);
        }
        if (this.parent) {
            this.parent.removeChild(this.heightDiv);
            this.parent.removeChild(this.emptyMessage);
            this.getTable(this.parent);
        }
        if (this.hoverNode) {
            this.addHover(this.hoverNode, this.hoverCallback);
            if (this.hoverSizeNode) this.hover.setSize(this.hoverSizeNode);
        }
        if (enabled) this.enable(this.collectiondata.criteria,
                                 this.collectiondata.search);
    },
    
    /**
     * Creates a DOM tree for the headers of the LiveGrid2.
     * The returned DOM tree must be inserted in its place in the document.
     * @type Object
     * @return A DOM tree which displays the headers of the LiveGrid2.
     * @see #getTable
     */
    getHeader: function() {
        var Self = this;
        this.headers = new Array(this.columns.length);
        this.outer_headers = new Array(this.columns.length);
        this.arrows = new Array(this.columns.length);
        this.arrow_events = new Array(this.columns.length);
        for (var i in this.columns) this.arrow_events[i] = sortHandler(i);
        function sortHandler(i) {
            return function(e) {
                Self.setSort(Self.columns[i].sort,
                    (Self.sort_id == Self.columns[i].sort &&
                     Self.sort_order == "asc") ? "desc" : "asc");
                stopEvent(e);
            }
        }
        this.ids = {};
        for (var i in this.columns) {
			if(this.columns[i].sort) {
		        this.ids[this.columns[i].sort] = i;		
			}
		}
        this.listColumns = function() {
        	var colSet = {};
            for(var i = 0; i < this.columns.length; i++) {
                var name = this.columns[i].name;
                for(var j = 0; j < name.length; j++) colSet[name[j]] = true;  
            }
            var plainColumns = [], eavColumns = [];
            for (var i in colSet) {
                (i.indexOf("/") < 0 ? plainColumns : eavColumns).push(i);
            }
            eavColumns.sort();
            return plainColumns.concat(eavColumns);
        };
        
        var colgroup = this.getColGroup();
        colgroup.appendChild(this.ruCorner = newnode("col", {width: "0"}));
        var tbody = newnode("tbody",{verticalAlign : "top"});
        var table = this.headerNode = newnode("table", {tableLayout: "fixed"},
                            {cellPadding: "0", cellSpacing: "0", className: "livegrid"},
                            [colgroup, tbody]);
        var row = newnode("tr", {height: this.lineHeight + "em"},
                          {className: "th"});
        for (var i = 0; i < this.columns.length; i++) {
            var outerCell, innerCell;
            var headerClassName = this.columns[i].name
                ? "header livegrid-column-" +
                      this.columns[i].name.join(" livegrid-column-")
                : "header";
            if (this.columns[i].sortable) {
                outerCell = this.outer_headers[i] =
                    newnode("th", this.columns[i].style, {className: headerClassName}, [
                        newnode("table", 0, {cellPadding: "0", cellSpacing: "0"}, [
                            newnode("tbody", 0, 0, [
                                newnode("tr", 0, 0, [
                                    innerCell = this.headers[i] = newnode("th", {whiteSpace: "nowrap"}),
                                    newnode("th", 0, 0, [
                                        this.arrows[i] = newnode("img", {verticalAlign: "middle"}, {src: getFullImgSrc("img/dummy.gif"), width: "16", height: "16"})
                                    ])
                                ])
                            ])
                        ])
                    ]);
            } else
                innerCell = outerCell = this.headers[i] = this.outer_headers[i] =
                    newnode("th", this.columns[i].style, {className: headerClassName});
            if (this.columns[i].i18n) {
                var ctx = this.columns[i].i18n;
                if (ctx === true) ctx = ""; 
                var node = new I18nNode(pgettext(ctx, this.columns[i].text));
                node.parent = innerCell;
                node.update = function() {
                    this.parent.title = this.node.data = this.callback();
                };
                innerCell.appendChild(node.node);
                node.update();
            } else if (this.columns[i].text instanceof I18nString) {
                innerCell.appendChild(addTranslated(this.columns[i].text));
            } else switch (typeof this.columns[i].text) {
                case "string":
                    innerCell.innerHTML = this.columns[i].text;
                    break;
                case "function":
                    innerCell.appendChild(addTranslated(this.columns[i].text));
                    break;
                default:
                    innerCell.appendChild(this.columns[i].text);
            }
            row.appendChild(outerCell);
        }
        row.appendChild(newnode("th"));
        var index = this.ids[this.sort_id];
        if (this.sort_id && index && this.arrows[index]) {
            this.arrows[index].src = 
                this.sort_order == "asc" ? getFullImgSrc("img/arrows/sort_up.gif") :
                this.sort_order == "desc" ? getFullImgSrc("img/arrows/sort_down.gif") :
                getFullImgSrc("img/dummy.gif"); // just in case
            LiveGrid2.setSortClass(this.outer_headers[index], true);
         }
        tbody.appendChild(row);
        return table;
    },
    
    /**
     * Adds the scrollable area of the LiveGrid2 to the specified parent node.
     * The parent node should have an explicit size and "overflow: auto".
     */
    getTable: function(parent) {
        this.rows = {};
        var last = this.columns.length - 1;
        this.parent = parent;
        this.heightDiv = newnode("div", {height: "0em"}, {className: "livegrid"});
        parent.appendChild(this.heightDiv);
        parent.tabIndex = 0;
        parent.style.outline = "none";
        parent.style.overflowX = "hidden";
        parent.hideFocus = true;
        var emptyText = addTranslated(this.emptylivegridtext);
        this.emptyMessage = newnode("div",
            {display: "none", position: "absolute", textAlign: "center", 
             width: "100%", marginTop: "5px"}, 0,
            [emptyText]);
        parent.appendChild(this.emptyMessage);
        var tr = newnode("tr");
        for (var i = 0; i < this.columns.length; i++)
            tr.appendChild(newnode("td", this.columns[i].style,
                                   {className: "cell"}));
        this.rowTemplate = newnode("table", 0,
            {cellPadding: "0", cellSpacing: "0", className: "tr"},
            [this.getColGroup(), newnode("tbody", 0, 0, [tr])]);
    },
    
    /**
     * Adds a hover to the LiveGrid2.
     * This method should be called between getTable() and the first enable(). 
     * @param {Object} hover A DOM node which serves as a hover. It should be
     * attached directly to the body and have the CSS style
     * "position: absolute".
     * @param {Function} callback A callback function which is called when
     * the hover is shown. As its only parameter, it receives the ID of
     * the entry over which the mouse is hovering.
     * @type {Object}
     * @return A new Hover object. The enable and disable methods of the hover
     * object will be called automatically by the LiveGrid2.
     */
    addHover: function(hover, callback) {
        this.hoverNode = hover;
        this.hoverCallback = callback;
		this.hover = new Hover(this.heightDiv, hover);
        var Self = this;
        this.hover.setSize = function(node) {
            Self.hoverSizeNode = node;
            return Hover.prototype.setSize.call(this, node);
        }
        this.hover.onShow = function(node) {
            var i = node.oxRowIndex;
            if (i !== undefined)
                callback(Self.collection.objects[i]);
            else
                return false;
        }
        return this.hover;
    },
    
    /**
     * @private
     */
    getColGroup: function() {
        var colgroup = newnode("colgroup");
        for (var i = 0; i < this.columns.length; i++) {
            var w = this.columns[i].width;
            colgroup.appendChild(newnode("col", w ? {width: w} : 0));
        }
        return colgroup;
    },
    
    /**
     * Appends the contents either of the entire grid or of selected rows to
     * the specified node.
     * @param {Object} parent The node to append the data to.
     * @param {Function} cb The callback to call after all data is appended.
     * @param {Boolean} selected True if only selected rows should be appended.
     * Otherwise, all rows are appended.
     */
    print: function(parent, cb, selected) {
        var doc = parent.ownerDocument;
        var tr = doc.createElement("tr");
        for (var i = 0; i < this.columns.length; i++) {
            var th = doc.createElement("th");
            if (this.columns[i].i18n)
                th.appendChild(doc.createTextNode(_(this.columns[i].text) || "\u00a0"));
            else
                th.innerHTML = this.columns[i].text || "\u00a0";
            tr.appendChild(th);
        }
        var table = newnode("table", {width: "100%"}, {border: 1},
            [newnode("thead", 0, 0, [tr], doc)], doc);
        var trs = [];
        var Self = this;
        function set(n, obj) {
            trs[n] = doc.createElement("tr");
            for (var i = 0; i < Self.columns.length; i++) {
                var td = doc.createElement("td");
                trs[n].appendChild(td);
                var col = Self.columns[i];
                col.set(td, col.index ? obj[col.index] : obj);
            }
        }
        function final_() {
            var tbody = newnode("tbody", 0, 0, 0, doc);
            for (var i = 0; i < trs.length; i++) 
                if (trs[i]) tbody.appendChild(trs[i]);
            table.appendChild(tbody);
            parent.appendChild(table);
            if(trs.length==0) {
                return; 
            }
            cb();
        }
		var selection = (selected) ? this.selection.getSelected() : this.collection.objects;
        OXCache.newRequest(null,this.module,{
                objects : selection,
                columns : this.listColumns()
            },null,function callback(data) {
				if (!data || !data.objects) {
					//return;
				}
				for(var i=0;i<data.objects.length;i++) {
					var obj=data.objects[i];
					var tr = doc.createElement("tr");
					for (var i2 = 0; i2 < Self.columns.length; i2++) {
                        var td = doc.createElement("td");
                        tr.appendChild(td);
					    var col = Self.columns[i2];
                        if(col.name.length==1) {
						  col.set(td,  obj[col.name[0]]);	
						} else {
					      col.set(td,  obj);
						}
  				    }
				    trs.push(tr); 
                 }
				 var tbody = newnode("tbody", 0, 0, 0, doc);
                 for (var i = 0; i < trs.length; i++) {
				 	if (trs[i]) {
						tbody.appendChild(trs[i]);
					}
				 }
                 table.appendChild(tbody);
                 parent.appendChild(table);
                 if(trs.length==0) {
                    //return; 
                 }
                 cb();
            }
		);    
    },
    
    /**
     * Displays the specified sort order as an arrow in the headers.
     * @param {String} id The column ID used for sorting, or null for no
     * sorting.
     * @param {String} order The sort order. One of "asc", "desc" or null for no
     * sorting.
     */
    setSort: function(name, order) {
        if (this.sort_id && name != this.sort_id) {
            var index = this.ids[this.sort_id];
			if(index || index === 0) {
			    this.arrows[index].src = getFullImgSrc("img/dummy.gif");
                LiveGrid2.setSortClass(this.outer_headers[index], false);
			}
		}
        var index = this.ids[name];
        if(index || index === 0) {
            this.arrows[index].src = 
                order == "asc" ? getFullImgSrc("img/arrows/sort_up.gif") :
                order == "desc" ? getFullImgSrc("img/arrows/sort_down.gif") :
                getFullImgSrc("img/dummy.gif"); // just in case
            LiveGrid2.setSortClass(this.outer_headers[index], true);
        }
		this.sort_id = name;
        this.sort_order = order;
        if (this.collection) this.sort(name, order);
    },
    
    /**
     * Callback function which changes the displayed data.
     * Should be overridden in each instance.
     * @param {String} id The column ID used for sorting, or null for no
     * sorting.
     * @param {String} order The sort order. Either "asc" or "desc".
     */
    sort: function() {
		if(!this.collection) {
			return
		}
		this.enable(this.collection.criteria, this.collection.search);
	},
    getSelectedIDs: function() {
        return this.selection.getSelected();
    },
    
    /**
     * Deletes items with specified object IDs from the collection and from
     * the selection. If the entire selection was deleted, the first unselected
     * item at or after the focus is selected.
     */
    deleteIDs: function(ids) {
		/*
        var sids = {};
        for (var i = 0; i < ids.length; i++)
            sids[this.storage.serialize(ids[i])] = true;
        while (this.storage.getSID(this.focus) in sids) this.focus++;
        var newID = this.storage.ids[this.focus];
        this.storage.removeIDs(ids);
        this.selection.deselectSIDs(sids);
        this.focus = newID ? this.storage.getIndex(newID) : 0;
        if (!this.selection.count)
            this.selection.click(this.focus);
        */
    },
    
    updateSelected: function(callback) {
        //if (this.collection)
            //this.storage.localUpdate(this.selection.getSelected(), callback);
    },

    /**
     * Initializes the LiveGrid2.
     * The DOM tree must have been created by {@link #getHeader} and
     * {@link #getTable}.
     * @see #getHTML
     */
	enable : function(criteria,search,force) {
		var Self=this;
		this.collectiondata = {
             "criteria" : criteria,
             columns : []
        };
		if (this.sort_id) {
            this.collectiondata.order = [{ sort : this.sort_id,
                                           order : this.sort_order }];
		}
		if(search) {
			this.collectiondata["search"]=search;
		}
		if(this.uniqueName) {
		    OXCache.unregister(this.uniqueName);
            delete this.uniqueName;
        }
		if(this.listRequest) {
            OXCache.unregister(this.listRequest.uniqueName);
            delete this.listRequest;
        }   
        if(!this.collection && this.collection !== false) {
            this.collection = false;
			for (var i in Self.arrows) {
                addDOMEvent(Self.outer_headers[i], "click", Self.arrow_events[i]);
			}
            addDOMEvent(Self.parent, "scroll", Self.scroll_cb);
            addDOMEvent(Self.parent, "keydown", Self.keydown_cb);
            addDOMEvent(Self.parent, "keypress", Self.keypress_cb);
            addDOMEvent(Self.parent, "mousedown", Self.mousedown_cb);
            addDOMEvent(Self.parent, "click", Self.click_cb);
            // bind double click via jQuery (due to bug #14560)
            jQuery(this.parent).bind("dblclick", this.dblclick_cb);
            addDOMEvent(Self.parent, "focus", Self.focus_cb);
            addDOMEvent(Self.parent, "blur", Self.blur_cb);
            if (this.contextmenu)
                addDOMEvent(this.parent, "contextmenu", this.ctxtmenu_cb);
            if (Self.hover && !Self.disablehover) Self.hover.enable();
            resizeEvents.register("Resized", Self.scroll_cb);
            register("LanguageChanged",Self.language_cb);
            if (Self.dndModule && (Self.dndModule != "links")) {
                var disabled;
                var validfield;
                switch(Self.dndModule) {
                    case "calendar": disabled=calendardefaultdisabled;  validfield="title"; break;
                    case "tasks":   disabled=taskdefaultdisabled; validfield="title"; break;
                    case "contacts": disabled=contactdefaultdisabled; validfield="display_name"; break;
                    case "infostore": disabled=infostoredefaultdisabled; validfield="title"; break;
                    case "mail": disabled=maildefaultdisabled; validfield="subject"; break;
                }
                Self.dragEvent = registerSource(Self.heightDiv, Self.dndModule, function() 
                {
                    var myelements=clone(Self.selection.getSelected());
                    objMoveCopy.begin(Self.dndModule,myelements);
                    return objMoveCopy;
                },null,null,disabled,defaultdisabledremove);
            }
        }
        var oldFocus = 0, fallbackSelection;
        var returnObject=OXCache.newRequest(null, this.module,
            this.collectiondata, {
                before: function() {
                    oldFocus = Self.focus;
                    if (!Self.collection || !Self.selection.collection) return;
                    while (Self.selection.get(oldFocus)) oldFocus++;
                    fallbackSelection = Self.collection.objects[oldFocus];
                },
                fn: function modifiedCallback(data) {
                    Self.collection=data;
                    Self.rowCount = data.objects.length;
                    var index;
                    if (fallbackSelection) {
                        index = Self.collection.getIndex(fallbackSelection) - 1;
                    }
                    if (isNaN(index)) {
                        index = Math.min(oldFocus, Self.rowCount - 1);
                    }
                    if (Self.selection.setCollection(Self.collection, index)) {
                        Self.focus = index;
                    }
                    Self.change_cb(0,data.objects.length); 
                    Self.events.trigger("Changed", data, true);
                }
            },
			function callback(data) {
				Self.collection=data;
	            Self.rowCount = data.objects.length;
	            Self.selection.setCollection(Self.collection, Self.selection.selectedIndex);
	            Self.scroll(true);
				Self.events.trigger("Changed", data, false);
			},force);
		this.uniqueName=returnObject.uniqueName;
		Self.selection.events.register("Selected", Self.selected_cb);
	},
    disable: function() {
        if (!this.collection) return;
        this.selection.events.unregister("Selected", this.selected_cb);
        if(this.uniqueName) {
			OXCache.unregister(this.uniqueName);
            delete this.uniqueName;
		}
		if(this.listRequest) {
            OXCache.unregister(this.listRequest.uniqueName);
            delete this.listRequest;
        }
		this.collection = null;
        this.selection.setCollection(null);
        for (var i in this.arrows)
            removeDOMEvent(this.outer_headers[i], "click", this.arrow_events[i]);
        removeDOMEvent(this.parent, "scroll", this.scroll_cb);
        removeDOMEvent(this.parent, "keydown", this.keydown_cb);
        removeDOMEvent(this.parent, "keypress", this.keypress_cb);
        removeDOMEvent(this.parent, "mousedown", this.mousedown_cb);
        removeDOMEvent(this.parent, "click", this.click_cb);
        jQuery(this.parent).unbind("dblclick", this.dblclick_cb);
        removeDOMEvent(this.parent, "focus", this.focus_cb);
        removeDOMEvent(this.parent, "blur", this.blur_cb);
        if (this.contextmenu)
            removeDOMEvent(this.parent, "contextmenu", this.ctxtmenu_cb);
        unregister("LanguageChanged",this.language_cb);
        if (this.hover) this.hover.disable();
        resizeEvents.unregister("Resized", this.scroll_cb);
        if (this.dndModule) {
            unregisterSource(this.parent, this.dragEvent);
        }
    },
    
    /**
     * @private
     */
    createRow: function() {
        var last = this.columns.length - 1;
        var row = {tr: this.rowTemplate.cloneNode(true),
                   cells: new Array(this.columns.length)};
        for (var i = 0, cell = row.tr.lastChild.firstChild.firstChild; cell;
             i++, cell = cell.nextSibling)
        {
            row.cells[i] = cell;
            this.columns[i].clear(cell);
        }
        this.heightDiv.appendChild(row.tr);
        if (!this.rowHeight) this.rowHeight = row.tr.offsetHeight;
        return row;
    },

    /**
     * To be removed.
     * @private
     */
    updateStyles: function() {
    	// return if the selection has no collection
    	if (!this.selection.collection) return;
        for (var i in this.rows) {
            var newclass = this.selection.get(i) ? "tr selected" : i%2 ? "tr_h " : "tr ";
            if (focusedElement == this.parent && i == this.focus)
                newclass += "focused";
            var tr = this.rows[i].tr;
            if (tr.className != newclass) tr.className = newclass;
        }
    },

    /**
     * @private
     */
    deleteRow: function(index) {
        var row = this.rows[index];
        row.tr.style.display = "none";
        this.rowcache.push(row);
        delete this.rows[index];
        delete this.unfetched[index];
    },

    /**
     * @private
     */
    insertRow: function(index) {
        var row = this.rows[index] =
            this.rowcache.length ? this.rowcache.pop() : this.createRow();
        row.tr.className = this.selection.get(index) ? "tr selected" : index%2 ? "tr_h" : "tr";
        row.tr.style.display = "";
        row.tr.style.top = (index * this.rowHeight) + "px";
        row.tr.oxRowIndex = index;
        this.unfetched[index] = true;
    },

    /**
     * @private
     */
    scroll: function(fullRedraw) {
        if (fullRedraw) this.fullRedraw = true;
        if (this.scrollTimeout) {
			clearTimeout(this.scrollTimeout);
		}
        var Self = this;
        this.scrollTimeout = setTimeout(function() {
            Self.scrollTimeout = null;
            Self.doScroll();
        }, 0);
    },
    
    /**
     * @private
     */
    calculateRowHeight: function() {
        var row = this.createRow();
        row.tr.style.display = "none";
        this.rowcache.push();
    },

    /**
     * @private
     */
    doScroll: function() {
		var Self=this;       
        if (!this.rowHeight) this.calculateRowHeight();
        this.height = Math.floor(this.parent.clientHeight / this.rowHeight);
        var len = this.collection ? this.collection.objects.length : 0;
        var newHeight = (len * this.rowHeight) + "px"
        var hDivStyle = this.heightDiv.style;
        if (newHeight != hDivStyle.height) hDivStyle.height = newHeight;
        var newruWidth = this.parent.offsetWidth - this.parent.clientWidth;
        if (newruWidth > 0 && newruWidth != this.ruWidth) {
            this.ruCorner.style.width = newruWidth + "px";
            this.ruWidth = newruWidth;
        }
        var Self = this;
        if (this.heightDiv.offsetWidth != this.parent.clientWidth)
            hDivStyle.width = Self.parent.clientWidth + "px";
        var scrolltop = this.parent.scrollTop;
        var from2 = Math.max(0, Math.floor(                           scrolltop  / this.rowHeight / this.granularity) * this.granularity - this.preview);
        var to2 = Math.min(len, Math.ceil((this.parent.clientHeight + scrolltop) / this.rowHeight / this.granularity) * this.granularity + this.preview);
		if (this.fullRedraw || from2 != this.from || to2 != this.to) {
            if (this.fullRedraw || from2 > this.to || this.from > to2) {
                for (var i = this.from; i < this.to; i++) this.deleteRow(i);
                for (var i = from2; i < to2; i++) this.insertRow(i);
            } else {
                for (var i = this.from; i < from2; i++) this.deleteRow(i);
                for (var i = to2; i < this.to; i++) this.deleteRow(i);
                for (var i = from2; i < this.from; i++) this.insertRow(i);
                for (var i = this.to; i < to2; i++) this.insertRow(i);
            }
            this.from = from2;
            this.to = to2;
        }
        this.fullRedraw = false;
        this.emptyMessage.style.display = (!this.collection || len) ? "none"
                                                                    : "block";
        var ids = [];
        for (var i in this.unfetched) ids.push(this.collection.objects[i]);
        if (ids.length) {        	
            if(this.listRequest) {
				OXCache.unregister(this.listRequest.uniqueName);
				delete this.listRequest;
			}
            var collection = this.collection;
		    this.listRequest=OXCache.newRequest(null,this.module,{
                objects : ids,
                columns : this.listColumns()
            },null,function cb(data) {
                for(var i=0;i<data.objects.length;i++) {
                    var objIndex=collection.map_objects.get(data.objects[i]);
                    if(!Self.rows[objIndex-1]) return;
                    delete Self.unfetched[objIndex-1];
                    var cellrow = Self.rows[objIndex-1].cells;
                    for (var i2 in cellrow) {
                        var col = Self.columns[i2];
                        if(col.name.length==1 && col.name[0].indexOf("/") < 0) {
                          col.set(cellrow[i2], data.objects[i][col.name[0]]);   
                        } else {
                          col.set(cellrow[i2], data.objects[i]);
                        }
                    }
                }
                delete Self.listRequest;
            },false);
			if(this.listRequest) {
				for(var i in this.listRequest.clear) {
                    var objIndex=this.collection.map_objects.get(this.listRequest.clear[i]);
                    var cellrow = Self.rows[objIndex-1].cells;
                    for (var i in cellrow) Self.columns[i].clear(cellrow[i]);
                }
				for(var i in this.listRequest.set) {
                	var objIndex=this.collection.map_objects.get(this.listRequest.set[i]);
					var cellrow = Self.rows[objIndex-1].cells;
                    for (var i2 in cellrow) {
                        var col = Self.columns[i2];
                        if(col.name.length==1 && col.name[0].indexOf("/") < 0) {
                          col.set(cellrow[i2], this.listRequest.set[i][col.name[0]]);   
                        } else {
                          col.set(cellrow[i2], this.listRequest.set[i]);
                        }
                    }
                }
            }
        }
    },
    
    /**
     * @private
     */
    scrollTo: function(y) {
        var parent = this.parent;
        setTimeout(function() { parent.scrollTop = y; }, 0);
    },
    
    /**
     * Scrolls if necessary to display the currently focused row.
     */
    showFocus: function() {
        var scrolltop = this.parent.scrollTop;
        if (!this.rowHeight) this.calculateRowHeight();
        if (this.focus < scrolltop / this.rowHeight)
            this.scrollTo(this.focus * this.rowHeight);
        else {
            var height = this.parent.clientHeight;
            if (this.focus + 1 > (scrolltop + height) / this.rowHeight)
                this.scrollTo((this.focus + 1) * this.rowHeight - height);
        }
    },

    /**
     * @private
     */
    key: function(e) {
        if (!this.collection) return;
        switch (e.keyCode || e.which) {
        case 13: // enter
            this.events.trigger("Activated", this.selection.getSelected());
            break;
        case 32: // spacebar
            this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 46: // delete
        case 108: // numpad delete
            this.events.trigger("Delete", this.selection.getSelected());
            break;
        case 38: // arrow up
            if (this.focus > 0) this.focus--;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 40: // arrow down
            if (this.focus < this.rowCount - 1) this.focus++;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 33: // page up
            this.focus -= this.height;
            if (this.focus < 0) this.focus = 0;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 34: // page down
            this.focus += this.height;
            if (this.focus >= this.rowCount) this.focus = this.rowCount - 1;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 35: // end
            this.focus = this.rowCount - 1;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 36: // home
            this.focus = 0;
            if (!(Mac ? e.metaKey : e.ctrlKey))
                this.selection.click(this.focus, e);
            this.showFocus();
            break;
        case 65: // A
        case 97: // a
            if (e.ctrlKey)
                this.selection.select(0, this.collection.objects.length);
            break;
        default:
            return;
        }
        this.updateStyles();
        stopEvent(e);
        return false;
    },
    
    /**
     * @private
     */
    mousedown: function(e) {
        this.clickprocessed = false;
        var focus = getRowIndex(e);
        if (this.collection.objects.length < this.height) {
            if (focus === undefined) focus = -1; else this.focus = focus;
            if(!this.selection.get(focus)) {
                this.clickprocessed=true;
                this.selection.click(focus, e);
            }
        } else if (focus !== undefined) {
            this.focus = focus;
            if(!this.selection.get(focus)) {
                this.clickprocessed=true;
                this.selection.click(focus, e);
            }
        }
        setFocus(this.parent);
    },
    
    /**
     * @private
     */
    ctxtmenu: function(e) {
        stopEvent(e);
        this.contextmenu.display(e.clientX, e.clientY, this.selection);
    },
    
    /**
     * @private
     */
    click: function(e) {
        if (this.clickprocessed) return;
        var focus = getRowIndex(e);
        if (this.collection.objects.length < this.height) {
            if (focus === undefined) focus = -1; else this.focus = focus;
            this.selection.click(focus, e);
        } else if (focus !== undefined) {
            this.focus = focus;
            this.selection.click(focus, e);
        }
        cancelDefault(e);
        if (!IE6 && corewindow.hasFocus) setFocus(this.parent);
    },
    
    /**
     * @private
     */
    dblclick: function(e) {
        if (!this.collection) return;
        this.focus = getRowIndex(e) || 0;
        this.events.trigger("Activated", this.selection.getSelected());
    }
};

LiveGrid2.setSortClass = classNameSetter("livegrid-th-sorted");

/**
 * Default set function for use in the second parameter of the constructor.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified content value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} text The text to set in the DIV element.
 */
LiveGrid2.defaultSet = function(div, text) {
    text = text || "\u00a0";
    if (div.firstChild)
        div.firstChild.data = text;
    else
        div.appendChild(document.createTextNode(text));
};

/**
 * Set function for use in formatting dates in the live grid.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified date value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} utc The utc to format in the DIV element.
 */
LiveGrid2.dateSet = function(div, date) {
    var dateString = "\u00a0";
    if (null != date) dateString = formatDate(date, "date");
    if (div.firstChild)
        div.firstChild.data = dateString;
    else
        div.appendChild(document.createTextNode(dateString));
};

/**
 * Set function for use in formatting dates in the live grid.
 * This function adds a text node in the specified DIV element and sets text
 * to the specified date value.
 * @param {Object} div The HTML DIV element to fill with content.
 * @param {String} utc The utc to format in the DIV element.
 */
LiveGrid2.dateSetTime = function(div, date) {
    if (div.firstChild)
        div.firstChild.data = formatDate(date, "datetime");
    else
        div.appendChild(document.createTextNode(formatDate(date, "datetime")));
};

/**
 * Creates a clear function for use in the second parameter of the constructor.
 * This function creates a function which sets all DIV elements of the LiveGrid2
 * to the text which is specified as parameter to this function.
 * @param {String} text The text to set in DIV elements.
 * @type Function
 * @return A function which takes a HTML DIV element as parameter, adds a text
 * node to it, and sets the text to a constant value.
 */
LiveGrid2.makeClear = function(text) {
    return function(div) {
        if (div.firstChild)
            div.firstChild.data = text;
        else
            div.appendChild(document.createTextNode(text));
    };
};




fileloaded();