// This script contains the outliner object.

function LSOutliner(inKey, inOutlineId, inDiv, inClient)
{
var container_div;
var key = '';
var outline_id = '';
var title = '';
var body = new Object();
var max_id = 1;
var row_no = 0;
var selected_row;
var expanded_rows = new Array();		// this is used in SaveAsOPML
var row_counter = 0;					// used in SaveAsOPML / GenerateOPML

var file_ids = new Array();				// TEMPORARY: when we generate the outliner, we dump the files IDs in the outline in this array and return it to client.

var client;								// reference to the client to which this Outliner instance belongs. Used to call functions of client

var that = this;						// this is a workaround for a bug in the ECMAScript
										// Language Specification (cf: http://www.crockford.com/javascript/private.html)

	this.Init=function(key, outline_id, div, client)
	{
		if (typeof key === "undefined")
			key = '';
			
		this.max_id = 1;
		this.key = key;
		this.outline_id = outline_id;
		this.container_div = div;
		this.body = new Object();
		this.expanded_rows = new Array();
		this.selected_row = null;
		this.client = client;
	};
	
	this.SetOutlineKey=function(new_key)
	{
		this.key = new_key;
	};
	
	this.GetOutlineId=function()
	{
		return this.outline_id;
	};
	
	this.GetContainerDiv=function()
	{
		return this.container_div;
	};
	
	this.SelectRow=function(row)
	{
		this.selected_row = row;
	};
	
	this.DeselectRow=function()
	{
		if (this.selected_row != null)
			this.selected_row = null;
	};
	
	this.MouseOverRow=function(row_id)
	{
		sel_row = document.getElementById(row_id);
		AddClass(sel_row, "mouse-hover");
		
		if ($jq('#drag-handle'+row_id))
			$jq('#drag-handle'+row_id).height($jq('#'+row_id).height()-4);
	};
	
	this.MouseOutRow=function(row_id)
	{
		sel_row = document.getElementById(row_id);
		RemoveClass(sel_row, "mouse-hover");
	};
	
	this.GetSelectedRow=function()
	{
		return this.selected_row;
	};
	
	// this function is called when we want to select previous row
	// if previous sibling has children, it returns the last child (recursively)
	// else returns previous sibling
	// if there is no previous sibling, it returns the parent row
	// if it is the top-most row(no prev sibling and parent is 'body'), it returns false
	this.FindPreviousRow=function(row)
	{
		if (!row)
			return false;
			
	var prev_sibling = that.GetPreviousSibling(row);
		
		if (prev_sibling)						// it has a previous sibling
		{
			if (!prev_sibling.hasChildren() || !prev_sibling.isExpanded())
				return prev_sibling;
			else
				return that.FindLastVisibleChild(prev_sibling);
		}
		else									// no previous sibling
		{
			var parent_row = row.getParent();
			if (parent_row == this.body)		// top-most row, return false
				return false;
			else return parent_row;				// return parent
		}
	};
	
	this.FindLastVisibleChild=function(row)
	{
		if (!row)
			return false;

		if (!row.hasChildren() || !row.isExpanded())
			return row;
		else
		{
		var last_child = that.GetLastChild(row);
			if (last_child)
				return that.FindLastVisibleChild(last_child);
		}
	};
	
	// this function is called when we want to select next row
	// if row has children and is expanded, return first child
	// else if next sibling exists, return it
	// else returns parent's next sibling (recursively)
	// if it is the bottom-most row(no next sibling and ancestor's dont have next siblings), it returns false
	this.FindNextRow=function(row)
	{
		if (!row)
			return false;
			
		if (row.hasChildren() && row.isExpanded())
		{
		
		var children = row.GetChildren();
			return children[0];
		}
			
	var next_sibling = that.GetNextSibling(row);
		
		if (next_sibling)						// it has a next sibling
			return next_sibling;
		else									// no previous sibling, look for ancestors' next sibling
		{
			var parent_row = row.getParent();
			while (parent_row != this.body)
			{
				if (that.GetNextSibling(parent_row))
					return that.GetNextSibling(parent_row);
					
				parent_row = parent_row.getParent();
			}
			return false;						// bottom-most row, return false
		}
	};
	
	// after loading the OPML, this function calls the server to get the files.
	// client.StoreFiles() is called on call back, and that function renders the Outline
	this.LoadOPML=function(response)
	{
	var opml_body = response.getElementsByTagName('body')[0];
	var expansion = response.getElementsByTagName('expansionState')[0].firstChild.data;

		title = response.getElementsByTagName('title')[0].firstChild.data;
		expansionState = (expansion != "") ? expansion.split(',') : new Array();
		
	var children = new Array();
	var n = opml_body.firstChild;
		
		while (n)
		{
			if (n.nodeType == 1)
				children[children.length] = n;
			n = n.nextSibling;
		}

		b = that.AddRow('body');
	
	var	parent_stack = new Array();
		parent_stack.push(b);

		that.file_ids = new Array();

		that.GenerateOutliner(parent_stack, children);
		
//		that.client.ComputeAmountForAllRows();				// now that the outline data has been loaded,
															// we can compute the amount for all parent rows
	};
	
	this.GenerateOutliner=function(parent_stack, children)
	{
		for (var i = 0; i < children.length; i++)
		{
		var outline = that.AddRow('child', parent_stack[parent_stack.length-1]);
		var attrs = children[i].attributes;

			// dump attributes
			for(var a=0; a < attrs.length; a++)
	        	outline.setAttribute(attrs[a].name, NormaliseText(attrs[a].value, 'opml2attr'));

			// THIS IS A TEMPORARY FIX. ONCE WE STORE THE ROWS IN THE DB, WE CAN REMOVE THIS BLOCK
			var file_id = outline.getAttribute('_file_id');
			var exists = false;
			if (file_id)
			{
	        	for (var x = 0; x < that.file_ids.length; x++)
				{
					if (that.file_ids[x] == file_id)
					{
						exists = true;
						break;
					}
				}
				if (!exists)
					that.file_ids[that.file_ids.length] = file_id;
			}
			// TEMPORARY BLOCK ENDS HERE

	        if (expansionState.indexOf(outline.id.toString()) != -1)
	        	outline.expanded = true;

			if (children[i].hasChildNodes() && CountChildren(children[i]) != 0)					// row has children
			{
				parent_stack.push(outline);

				// create children array
				c = new Array();
				n = children[i].firstChild;
				while (n)
				{
					if (n.nodeType == 1)
						c[c.length] = n;
					n = n.nextSibling;
				}

				that.GenerateOutliner(parent_stack, c);
				p = parent_stack.pop();
			}
		}
	};
	
	// TEMPORARY FUNCTION. IT RETURNS this.files. ONCE WE STORE ROWS IN DB, WE WONT NEED THIS FUNCTION.
	this.GetFileIds=function()
	{
		return that.file_ids;
	};
	
	this.ConvertToOPML = function()
	{
	var children = new Array();
	this.expanded_rows = new Array();
	this.row_counter = 0;
	var opml = "";
	
		if (this.body.children)
		{
			children = this.body.children;
			opml = this.GenerateOPML(children);
		}
	
	var expansionState = (this.expanded_rows.length) ? this.expanded_rows.join() : 0;
		
	var code =  '<opml version="1.0">\r\n';
		code += '<head>\r\n';
		code += '<title>Untitled Document</title>\r\n';
		code += '<expansionState>' + expansionState + '</expansionState>\r\n';
		code += '</head>\r\n';
		code += '<body>\r\n';
		code += opml;
		code += '</body>\r\n';
		code += '</opml>\r\n';

		return code;
	};
	
	this.GetOPMLCallBack=function(t)
	{
	var response = t.responseXML.documentElement;
	var status = response.getElementsByTagName('status')[0].firstChild.data;
	var new_sids = response.getElementsByTagName('new_sids')[0].firstChild.data;

		new_rows = (new_sids != "") ? new_sids.split(',') : new Array();
		for (i = 0; i < new_rows.length; i++)
		{
			parts = new_rows[i].split(':');		// client_ID : server_ID
			
			curr_row = that.GetRowById(parts[0]);
			if (curr_row)
				curr_row.setAttribute('_sid', parts[1]);
		}
	};

	this.GenerateOPML=function(children)
	{
	var opml = "";
		for (var i = 0; i < children.length; i++)
		{
			this.row_counter++;
			row_attributes = children[i].getAttributesAsString();
			
			if (children[i].expanded)
				this.expanded_rows.push(this.row_counter);
				
			if (children[i].hasChildren())
			{
			//	opml += "<outline " + row_attributes + ">\r\n";
				opml += "<outline _cid=\"" + children[i].getId() + "\" " + row_attributes + ">\r\n";			// we need to send the client ID also
				opml += this.GenerateOPML(children[i].children);
				opml += "</outline>\r\n";
			}
			else
			{
				opml += "<outline _cid=\"" + children[i].getId() + "\" " + row_attributes + "/>\r\n";
			}
		}
		return opml;
	};
	
	this.CreateRow=function()
	{
	var	outline = new Outline(max_id);
		max_id++;
	
		return outline;
	};
	
	// position:	string ('before', 'after', child', 'first_child')
	//				determines where the new row should
	//				be created in reference to ref_row
	// ref_row:		row near which we want to put the new rows
	this.AddRow=function(position, ref_row)
	{
	var parent;
	var	outline;
	
		if (position == 'body')
		{
			outline = new Outline('body');
			outline.setParent(null);		// parent of body is NULL
			this.body = outline;
			return outline;
		}
		
		outline = this.CreateRow();
	
		switch(position)
		{
		case 'before':	if (ref_row && ref_row.getParent())
							parent = ref_row.getParent();
						else break;
		
						for (var i = 0; i < parent.children.length; i++)
						{
							if (parent.children[i] == ref_row)
								break;
						}

						parent.children.splice(i, 0, outline);
						outline.setParent(parent);
						break;
						
		case 'after':	if (ref_row && ref_row.getParent())
							parent = ref_row.getParent();
						else break;
		
						for (var i = 0; i < parent.children.length; i++)
						{
							if (parent.children[i] == ref_row)
								break;
						}
						
						parent.children.splice(i+1, 0, outline);
						outline.setParent(parent);
						break;
						
		case 'child':	if (!ref_row)
							break;
							
						ref_row.children.push(outline);
						outline.setParent(ref_row);
						break;
						
	case 'first_child':	if (!ref_row)
							break;
							
						ref_row.children.splice(0, 0, outline);
						outline.setParent(ref_row);
						break;
		}
		return outline;
	};
	
	this.DeleteRow=function(row)
	{
	var parent_row = row.getParent();

		if (parent_row == null)			// deleting body node
		{
			delete this.body;
			this.body = new Object();
		}
		else
		{
		var children = parent_row.children;
			for (var i = 0; i < children.length; i++)
			{
				if (children[i].id == row.id)
				{
					children.splice(i, 1);
					break;
				}
			}
		}
		this.selected_row = null;
		return true;
	};
	
	this.DuplicateRow=function(row)
	{
		
	};
	
	this.IndentRow=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == null)
			return false;

		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		
		if (i != 0)
		{
			new_parent = children[i-1];
			new_parent.children.push(row);
			row.setParent(new_parent);
			children.splice(i, 1);
			return true;
		}
		else return false;				// if this is the first child of a row
										// it can't be indented further
	};
	
	this.OutdentRow=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == this.body)
			return false;
			
		gparent = parent_row.getParent();
		gchildren = gparent.children;
		
		for (var i = 0; i < gchildren.length; i++)
		{
			if (gchildren[i] == parent_row)
				break;
		}
		
		gparent.children.splice(i+1, 0, row);
		row.setParent(gparent);
		
		// now delete the row from its old position
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		children.splice(i, 1);
		
		return true;
	};
	
	this.MoveRow=function(row, relative_pos, after_row)
	{
		row_parent = row.getParent();				// delete the row from its previous position
		row_siblings = row_parent.children;
		for (row_idx = 0; row_idx < row_siblings.length; row_idx++)
		{
			if (row_siblings[row_idx] == row)
				break;
		}
		row_parent.children.splice(row_idx, 1);

		if (!after_row)								// row has been moved as first row of the outline
		{
			this.body.children.splice(0, 0, row);
			row.setParent(this.body);
		}
		else
		{
			if (relative_pos == 'sibling')			// move row after this one as a sibling
			{
				after_row_parent = after_row.getParent();
				
				after_row_siblings = after_row_parent.children;
				for (after_row_idx = 0; after_row_idx < after_row_siblings.length; after_row_idx++)
				{
					if (after_row_siblings[after_row_idx] == after_row)
						break;
				}
				
				after_row_parent.children.splice(after_row_idx+1, 0, row);
				row.setParent(after_row_parent);
			}
			else if (relative_pos == 'child')		// move row as a child of this row
			{
				after_row_children = after_row.children;
				after_row_children.splice(0, 0, row);
				row.setParent(after_row);
			}
		}
	};
	
	this.MoveUp=function(row)
	{
	var parent_row = row.getParent();
		if (!row || parent_row == null)
			return false;

	var children = parent_row.children;
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}
		
		if (i != 0)			// if its the first child of a row, it can't be moved up
		{
			parent_row.children.splice(i-1, 0, children[i]);		// insert sel row @ i-1 pos
			parent_row.children.splice(i+1, 1);						// remove it from its old pos
			
			return true;
		}
		else return false;
	};
	
	this.MoveDown=function(row)
	{
	var parent_row = row.getParent();
	var children = parent_row.children;
		
		if (!row || parent_row == null)
			return false;
					
		for (var i = 0; i < children.length; i++)
		{
			if (children[i] == row)
				break;
		}

		if (i != children.length-1)		// if its the last child of a row, it can't be moved down
		{
			parent_row.children.splice(i+2, 0, children[i]);		// insert sel row @ i+2 pos
			parent_row.children.splice(i, 1);						// remove it from its old pos
			
			return true;
		}
		else return false;
	};
	
	this.ExpandAll=function(row)
	{
		if (typeof row === "undefined")
			row = this.body;

		if (row.hasChildren())
		{
			row.expanded = true;
			
			for (var i = 0; i < row.children.length; i++)
				that.ExpandAll(row.children[i]);
		}		
	};

	this.CollapseAll=function(row)
	{
		if (typeof row === "undefined")
			row = this.body;

		if (row.hasChildren())
		{
			row.expanded = false;
			
			for (var i = 0; i < row.children.length; i++)
				that.CollapseAll(row.children[i]);
		}
	};

	this.ExpandRow=function(row)
	{
		if (row)
			row.expanded = true;
			
		return false;
	};
	
	this.CollapseRow=function(row)
	{
		if (row)
			row.expanded = false;
			
		return false;
	};
	
	this.RemoveAllColors=function()
	{
		if (this.body.hasChildren())
		{
			var children = this.body.children;
			for (var i = 0; i < children.length; i++)
			{
				children[i].setAttribute('_color', '');
				that.SetAttributeForChildren(children[i].GetChildren(), '_color', '');
			}
		}
	};
	
	this.GetRowById=function(id)
	{
		if (this.body.getId() == id)
			return this.body;
			
	var children = this.body.children;

		return this.FindIdInChildren(children, id);
	};
	
	// this function is called by GetRowById().
	// It recursively finds the row with the given id
	this.FindIdInChildren=function(children, id)
	{
	var row = "";
		for (var i = 0; i < children.length; i++)
		{
			if (children[i].getId() == id)
				row = children[i];
			else row = this.FindIdInChildren(children[i].children, id);
			
			if (row != "")		 // we have found the row, break out of the loop and return
				break;
		}
		return row;
	};
	
	this.GetNextSibling=function(row)
	{
		if (row == null)
			return false;
	
		var parent_row = row.getParent();
		var children = parent_row.children;
			
			if (!row || parent_row == null)
				return false;
						
			for (var i = 0; i < children.length; i++)
			{
				if (children[i] == row)
					break;
			}
			
			if (i == children.length-1)					// this is the last child	
				return false;
			else return children[i+1];
	};
	
	this.GetPreviousSibling=function(row)
	{
		if (row == null)
			return false;
			
		var parent_row = row.getParent();
		var children = parent_row.children;
			
			if (!row || parent_row == null)
				return false;
						
			for (var i = 0; i < children.length; i++)
			{
				if (children[i] == row)
					break;
			}
			
			if (i == 0)					// this is the first child	
				return false;
			else return children[i-1];
	};
	
	this.GetLastChild=function(row)
	{
		if (row == null)
			return false;
			
		if (row.hasChildren())
		{
		var children = row.GetChildren();
			return children[children.length-1];
		}
		else return false;
	}
		
	this.SetAttributeForChildren=function(rows, attr, value)
	{
		var children;
		for (var i = 0; i < rows.length; i++)
		{
			rows[i].setAttribute(attr, value);
			if (rows[i].hasChildren())
			{
				children = rows[i].GetChildren();
				that.SetAttributeForChildren(children, attr, value);
			}
		}
	};
	
	// new_row is only used while adding a row
	this.Render=function(row, action, new_row)
	{
		if (typeof row === 'undefined')
			this.RenderAll();
		else
		{
			var row_div = document.getElementById(row.id);
			if (row_div)
			{
				var next_div = row_div.nextSibling;
			}
			
			switch(action)
			{
			case 'remove_row':	this.container_div.removeChild(row_div);
								this.RemoveChildren(row);
								
								break;
								
			case 'redraw':		var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								
								break;
								
		case 'redraw_children':	var parent_count = this.GetParentCount(row);
								this.DisplayChildren(row, parent_count-1);
								
								break;
								
			case 'expand':		if (!row.hasChildren())
									return false;
									
								var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								
								this.DisplayChildren(row, parent_count-1);
								
								ShowSelectionOverlay();							// redraw selection overlay
								
								this.PaintBackground(row.id);
								break;
							
			case 'collapse':	if (!row.hasChildren())
									return false;
								
								var parent_count = this.GetParentCount(row);	
								this.DisplayRow(row, parent_count-1, row_div);
								
								this.RemoveChildren(row);
								
								ShowSelectionOverlay();							// redraw selection overlay
								
								this.PaintBackground(row.id);
								break;
								
		case 'delete_before':	this.RemoveChildren(row);
								this.RemoveRow(row);
								
								break;
						
		case 'delete_after':	if (row.id != 'body')							// the row has been deleted, check to see if
								{												// the parent row needs to be rendered again
									var parent_count = this.GetParentCount(row);
									this.DisplayRow(row, parent_count-1, row_div);
									
									this.PaintBackground();
								}
								
								break;
								
		case 'add_first_child':	var parent_count = this.GetParentCount(row);
								d = document.createElement('div');
								this.DisplayRow(new_row, parent_count, d);
								
								this.container_div.insertBefore(d, row_div.nextSibling);
								
								if (!that.client.IsInReadOnlyMode())
									this.InitContextualMenuForRow(new_row);
									
								EnableRowDragging();							// defined in dragndrop.js
								
								this.PaintBackground(new_row.id);
								break;
								
			case 'add_after':	// adding after a row happens only if the selected row is not expanded.
								// if you want to add a row after the selected row's children, this block will have to be changed
								
								var parent_count = this.GetParentCount(row);
								d = document.createElement('div');
								this.DisplayRow(new_row, parent_count-1, d);
								
								this.container_div.insertBefore(d, row_div.nextSibling);
								
								if (!that.client.IsInReadOnlyMode())
									this.InitContextualMenuForRow(new_row);
									
								EnableRowDragging();							// defined in dragndrop.js
								
								this.PaintBackground(new_row.id);
								break;
								
		case 'add_last_child':	// *** this is a special case, it appends new_row at the end of the document.
								
								d = document.createElement('div');
								this.DisplayRow(new_row, 0, d);
								this.container_div.appendChild(d);
								
								if (!that.client.IsInReadOnlyMode())
									this.InitContextualMenuForRow(new_row);
									
								EnableRowDragging();							// defined in dragndrop.js
									
								this.PaintBackground(new_row.id);
								break;
								
			case 'indent':		parent_row = row.getParent();
								if (parent_row.hasChildren() && parent_row.isExpanded())
								{
									var parent_count = this.GetParentCount(row);
									
									if (row.hasChildren() && row.isExpanded())
									{
										this.RemoveChildren(row);
										this.DisplayChildren(row, parent_count-1);
									}
									this.DisplayRow(row, parent_count-1, row_div);
									
									if (row_div.previousSibling.getAttribute('data-depth') < row_div.getAttribute('data-depth'))
									{
									var prev_row = that.GetRowById(row_div.previousSibling.id);
										this.DisplayRow(prev_row, parent_count-2, row_div.previousSibling);
									}
								}
								else					// since the parent is hidden, just delete this row and its children
								{
									this.RemoveChildren(row);
									this.RemoveRow(row);
								}
								this.PaintBackground(row.id);
								
								break;
								
			case 'outdent':		if (row.hasChildren() && row.isExpanded())
									this.RemoveChildren(row);
								this.RemoveRow(row);
								
								var parent_count = this.GetParentCount(row);
								var prev_sibling = this.GetPreviousSibling(row);
								
								if (prev_sibling.hasChildren() && prev_sibling.isExpanded())
									add_after = this.FindLastVisibleChild(prev_sibling);
								else					// previous row doesn't children any more. redraw it
								{
									add_after = prev_sibling;
									
									var prev_parent_count = this.GetParentCount(prev_sibling);				// re-render previous parent since
									var prev_sibling_div = document.getElementById(prev_sibling.id);
									this.DisplayRow(prev_sibling, prev_parent_count-1, prev_sibling_div);	// it may not have any children any more
								}
								
								var add_after_div = document.getElementById(add_after.id);
								d = document.createElement('div');
								this.DisplayRow(row, parent_count-1, d);
								this.container_div.insertBefore(d, add_after_div.nextSibling);
								
								if (!that.client.IsInReadOnlyMode())
									this.InitContextualMenuForRow(row);
									
								EnableRowDragging();							// defined in dragndrop.js
								
								if (row.hasChildren() && row.isExpanded())
									this.DisplayChildren(row, parent_count-1);
								
								this.PaintBackground(row.id);
								break;
								
		case 'move_as_first':	this.container_div.insertBefore(row_div, this.container_div.firstChild);
								var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								
								if (row.hasChildren() && row.isExpanded())
								{
									this.RemoveChildren(row);
									this.DisplayChildren(row, parent_count-1);
								}
								this.PaintBackground();
								break;
		
		case 'move_as_sibling':	prev_sibling = this.GetPreviousSibling(row);
								if (prev_sibling)
								{
									var prev_sibling_div = document.getElementById(prev_sibling.id);

									if (prev_sibling_div.nextSibling.getAttribute('data-depth') > prev_sibling_div.getAttribute('data-depth'))
									{
										prev_row_div = prev_sibling_div.nextSibling;
										while (prev_row_div.getAttribute('data-depth') > prev_sibling_div.getAttribute('data-depth'))
										{
											drop_after_row_div = prev_row_div;
											prev_row_div = prev_row_div.nextSibling;
										}
									}
									else drop_after_row_div = prev_sibling_div;
									
									this.container_div.insertBefore(row_div, drop_after_row_div.nextSibling);
									
									var parent_count = this.GetParentCount(row);
									this.DisplayRow(row, parent_count-1, row_div);
									
									if (row.hasChildren() && row.isExpanded())
									{
										this.RemoveChildren(row);
										this.DisplayChildren(row, parent_count-1);
									}
									
									// redraw selection overlay
									ShowSelectionOverlay();
									
									this.PaintBackground();
								}
								
								// we need to redraw the old parent if it doesn't have children any more. also repaint from old parent/new parent
								break;
								
		case 'move_as_child':	parent_row = row.getParent();
								if (parent_row)
								{
									var parent_row_div = document.getElementById(parent_row.id);
									this.container_div.insertBefore(row_div, parent_row_div.nextSibling);
									
									var parent_count = this.GetParentCount(row);
									this.DisplayRow(row, parent_count-1, row_div);
									
									if (row.hasChildren() && row.isExpanded())
									{
										this.RemoveChildren(row);
										this.DisplayChildren(row, parent_count-1);
									}
									
									if (row_div.previousSibling.getAttribute('data-depth') < row_div.getAttribute('data-depth'))
									{
									var prev_row = that.GetRowById(row_div.previousSibling.id);
										this.DisplayRow(prev_row, parent_count-2, row_div.previousSibling);
									}
								}
								
								// redraw selection overlay
								ShowSelectionOverlay();
								
								this.PaintBackground();
								
								break;
								
			case 'move_up':		var prev_sibling = this.GetNextSibling(row);				// since this function is called after the move is done
								if (prev_sibling)											// the next sibling is still the previous DOM node
								{
									var prev_sibling_div = document.getElementById(prev_sibling.id);
									this.container_div.insertBefore(row_div, prev_sibling_div);
									
									this.RemoveChildren(row);
									
									var parent_count = this.GetParentCount(row);
									this.DisplayChildren(row, parent_count-1);
								}
								
								// redraw selection overlay
								ShowSelectionOverlay();
								
								this.PaintBackground(row.id);
								break;

			case 'move_down':	var next_sibling = this.GetPreviousSibling(row);			// since this function is called after the move is done
								if (next_sibling)											// the previous sibling is still the next DOM node
								{
									if (next_sibling.hasChildren() && next_sibling.isExpanded())
									{
										last_visible_child = this.FindLastVisibleChild(next_sibling);
										after_this_div = document.getElementById(last_visible_child.id);
									}
									else after_this_div = document.getElementById(next_sibling.id);
									
									this.container_div.insertBefore(row_div, after_this_div.nextSibling);
									
									this.RemoveChildren(row);
									
									var parent_count = this.GetParentCount(row);
									this.DisplayChildren(row, parent_count-1);
								}
								
								// redraw selection overlay
								ShowSelectionOverlay();
								
								this.PaintBackground(next_sibling.id);
								break;

			case 'save_notes':	
		case 'update_status':	
			case 'save_text':	// we don't need to redraw the row!
			
							/*	var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
							*/
								break;
								
	case 'save_text_multirow':	var parent_count = this.GetParentCount(row);
								var parent_row = row.getParent();
								var children_div = row_div.parentNode;
								
								this.RemoveChildren(parent_row);
								this.DisplayChildren(parent_row, parent_count-2, children_div);
								
								ShowSelectionOverlay();							// redraw selection overlay
								this.PaintBackground();
								
								break;
							
		case 'toggle_status':	// re-render selected row
								var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								
								// re-render children
								if (row.hasChildren() && row.isExpanded())
								{
									this.RemoveChildren(row);
									this.DisplayChildren(row, parent_count-1);
								}
								
								// re-render ancestors
								var ancestors = this.GetAncestors(row);
								var depth = parent_count-2;
								for (i = 0; i < ancestors.length; i++)
								{
									var a = document.getElementById(ancestors[i].id);
									this.DisplayRow(ancestors[i], depth, a);
									depth--;
								}
								this.PaintBackground(row.id);
								
								break;
								
		case 'save_amount':		row_amount = row.getAttribute('_amount');
								amount_div = document.getElementById('amount'+row.id);
								if (amount_div)
								{
									if (row_amount != '' && row_amount != 0.00)
									{
										amount_div.innerHTML = FormatNumberWithCommas(row_amount);
										
										if (row.getAttribute('_color'))
											amount_div.style.color = row.getAttribute('_color');
										else amount_div.style.color = "";
									}
									else amount_div.innerHTML = '';
									
									// re-render ancestors
									var parent_count = this.GetParentCount(row);
									var ancestors = this.GetAncestors(row);
									var depth = parent_count-2;
									
									for (i = 0; i < ancestors.length; i++)
									{
										amount_div = document.getElementById('amount'+ancestors[i].id);
										
										row_amount = ancestors[i].getAttribute('_amount');
										if (row_amount != '' && row_amount != 0.00)
										{
											amount_div.innerHTML = FormatNumberWithCommas(row_amount);
											
											if (ancestors[i].getAttribute('_color'))
												amount_div.style.color = ancestors[i].getAttribute('_color');
											else amount_div.style.color = "";
										}
										else amount_div.innerHTML = '';
										
										depth--;
									}
								}
								break;

								
			case 'set_color':	
			case 'attachment':	
			case 'set_duedate':						
			case 'assign_to':	var parent_count = this.GetParentCount(row);
								this.DisplayRow(row, parent_count-1, row_div);
								
								ShowSelectionOverlay();
								
								break;
								
			default:			alert("invalid action");
								break;
			}
		}
	};
	
	// this function clears the container div
	// and loads the entire document
	this.RenderAll=function()
	{
		// clear the container div
		while(this.container_div.firstChild)
			this.container_div.removeChild(this.container_div.firstChild);

		this.row_no = 0;
		if (this.body.hasChildren())
		{
			for (var i = 0; i < this.body.children.length; i++)
			{
				that.RenderRow(this.body.children[i], 0);
			}
		}
		
		this.PaintBackground();
	};
	
	this.RenderRow=function(row, depth)
	{
		var row_div = document.createElement('div');
		this.DisplayRow(row, depth, row_div);
		
		this.container_div.appendChild(row_div);

		if (!that.client.IsInReadOnlyMode())
			this.InitContextualMenuForRow(row);

		// render children
		if (row.hasChildren() && row.expanded == true)
		{
			for (var i = 0; i < row.children.length; i++)
				that.RenderRow(row.children[i], depth+1);	
		}
	};
	
	// this function actually creates the div
	// that contains the elements that are displayed in a row
	// we should call this function RenderRow
	this.DisplayRow=function(row, depth, row_div)
	{
		// ROW-HANDLE
		var handle = document.createElement('img');
		handle.className = "row-handle";
		is_collapsed_str = 'false';
		if (row.hasChildren())
		{
			if (row.expanded == true)
			{
				handle.src = "/images/expanded.gif";
				handle.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.CollapseRow(row); };
			}
			else
			{
				handle.src = "/images/collapsed.gif";
				handle.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ExpandRow(row); };
				is_collapsed_str = 'true';
			}
		}
		else handle.src = "/images/leafrowhandle.gif";
		
		$jq(handle).click(function(e) {
		   e.stopPropagation();
		});
		
		// TEXT
		var txt;
		if (that.client.GetUserAgent() == 'ipad')
		{
			txt = document.createElement('textarea');
			if (!that.client.IsInReadOnlyMode())
			{
				txt.contentEditable = "true";
				txt.onfocus = function() { that.client.EnterEditMode(); };
				txt.onblur = function() { that.client.SaveText(txt.value); that.client.LeaveEditMode(); };
			}
		}
		else
		{
			txt = document.createElement('div');
			if (!that.client.IsInReadOnlyMode())
			{
				txt.contentEditable = "true";
				txt.onfocus = function() { that.client.EnterEditMode(); that.client.AddUpDownKeyboardShortcuts('propagate'); };
				txt.onblur = function() { if (!that.client.DidUserPressTab()) { that.client.SaveText(txt.innerHTML); } that.client.LeaveEditMode(); that.client.AddUpDownKeyboardShortcuts(''); };
			}
		}
		
		txt.className = 'row-text';
		txt.id = 'text'+row.id;
		if (row.getAttribute('text'))
			txt.innerHTML = NormaliseText(row.getAttribute('text'), 'html');
		else txt.innerHTML = "";
		
		if (row.getAttribute('_color'))
			txt.style.color = row.getAttribute('_color');
		else txt.style.color = "";
		
		// NOTES
		var notes = document.createElement('div');
		notes.className = 'row-notes';
		notes.id = 'notes'+row.id;
		
		if (!that.client.IsInReadOnlyMode())
		{
			notes.contentEditable = "true";
			notes.onfocus = function() { that.client.EnterEditNotes(); that.client.AddUpDownKeyboardShortcuts('propagate'); };
			notes.onblur = function() { if (!that.client.didPressTab) { that.client.SaveNotes(notes.innerHTML); } that.client.LeaveEditNotes(); that.client.AddUpDownKeyboardShortcuts(''); }; 
		}
		
		if (row.getAttribute('_notes'))
		{
			notes.style.display = 'block';
			notes.innerHTML = NormaliseText(row.getAttribute('_notes'), 'html');
		}
		else notes.style.display = 'none';
			
		// AMOUNT
		var row_amount = row.getAttribute('_amount');
		var amount = document.createElement('div');
		amount.className = 'amount';
		amount.id = 'amount'+row.id;

		if (row_amount != '' && row_amount != 0.00)
		{
			amount.innerHTML = FormatNumberWithCommas(row_amount);
			
			if (row.getAttribute('_color'))
				amount.style.color = row.getAttribute('_color');
			else amount.style.color = "";
		}
		else amount.innerHTML = '';

		if (!that.client.IsInReadOnlyMode() && !row.hasChildren())
		{
			AddClass(amount, 'allow-hover');
			amount.contentEditable = "true";
			amount.onfocus = function() { StripCommasFromDiv(amount); that.client.EnterEditAmount(); that.client.AddUpDownKeyboardShortcuts('propagate'); };
			amount.onblur = function() { if (!that.client.didPressTab) { that.client.SaveAmount(amount.innerHTML); } that.client.LeaveEditAmount(); that.client.AddUpDownKeyboardShortcuts(''); }; 
		}
				
		// DUE-DATE
		var duedate, row_duedate = row.getAttribute('_duedate');
		duedate = document.createElement('div');
		duedate.className = 'due-date';
		duedate.id = 'due-date'+row.id;
		if (row_duedate != '')
		{
			duedate.innerHTML = GetFriendlyDate(row_duedate);

			if (IsDatePassed(row_duedate))
				AddClass(duedate, 'due-date-overdue');
		}
		else duedate.innerHTML = '';
		
		if (!that.client.IsInReadOnlyMode())
		{
			duedate_placeholder = 'due on';
			
			duedate.onclick = function() { that.client.ShowDueDateHUD(row); };
			duedate.onmouseover = function() { if ($jq('#due-date'+row.id).html() == '') { $jq('#due-date'+row.id).html(duedate_placeholder); $jq('#due-date'+row.id).addClass('dummy-text'); } };
			duedate.onmouseout = function() { if ($jq('#due-date'+row.id).html() == duedate_placeholder) { $jq('#due-date'+row.id).html('');  $jq('#due-date'+row.id).removeClass('dummy-text'); } };
		}
		
		if (that.client.IsInReadOnlyMode())
			bubble_status = 'readonly';
		else bubble_status = '';
		
		// ASSIGN TO
		var assign_to;
		assign_to = document.createElement('div');
		assign_to.className = 'assigned-to';
		assign_to.id = 'assigned-to'+row.id;

		if (row.getAttribute('_assigned_to')  != "")
			AddClass(assign_to, 'assigned');
		else
		{
			AddClass(assign_to, 'unassigned');
			assign_to.title = 'Assign this as a task to someone';
		}
		
		assign_to.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ShowAssignedToBubbleAtRow(row.getAttribute('_assigned_to'), assign_to.id, bubble_status); };
		
		// ATTACHMENT
		var attachment, attachment_img, attachment_preview, attachment_preview_img;
		attachment = document.createElement('div');
		attachment.className = 'attachment';
		attachment.id = 'attachment'+row.id;
		
		text_notes_class = '';
		load_file_preview = false;
		
		if (row.getAttribute('_file_id')  != "")// && that.client.GetFileById(row.getAttribute('_file_id')) != {})
		{
			if (that.client.GetFilenameForFileId(row.getAttribute('_file_id')))
				attachment.title = that.client.GetFilenameForFileId(row.getAttribute('_file_id'));
			
			AddClass(attachment, 'assigned');
			
			if (that.client.CanShowPreview(row.getAttribute('_file_id')))
			{
				attachment_preview_img = document.createElement('img');
				attachment_preview_img.src = '/complete/getfile.php?size=thumb&file_id='+row.getAttribute('_file_id');
				attachment_preview_img.height = 100;
				attachment_preview_img.width = 100;
	
				attachment_preview = document.createElement('div');
				attachment_preview.id = 'attachment-preview'+row.id;
				AddClass(attachment_preview, 'attachment-preview');
				attachment_preview.onclick = function() { TogglePreview(row.id, row.getAttribute('_file_id')); };
				attachment_preview.appendChild(attachment_preview_img);
				
				text_notes_class = 'on-the-side';
			}
		}
		else
		{
			AddClass(attachment, 'unassigned');
			attachment.title = 'Attach a file to this row';
		}
		
		attachment.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ShowFileBubbleAtRow(row.getAttribute('_file_id'), attachment.id, bubble_status, row.id); };
		
		// CHECKBOX
		var chkbox;
		chkbox = document.createElement('div');
		AddClass(chkbox, 'checkbox');
		
		if (row.getAttribute('_status')  == "checked")
			AddClass(chkbox, 'checked');
		else if (row.getAttribute('_status')  == "intermediate")
			AddClass(chkbox, 'intermediate');
		else AddClass(chkbox, 'unchecked');
				
		if (!that.client.IsInReadOnlyMode())
			chkbox.onclick = function() { if (that.client.IsUserInteractionEnabled()) that.client.ToggleStatusOfRow(row.id); };
		
		// build the wrappers
		var text_n_notes = document.createElement('div');
			text_n_notes.id = 'text-and-notes'+row.id;
			text_n_notes.className = 'text-and-notes';
			text_n_notes.appendChild(txt);
			text_n_notes.appendChild(notes);
			
		var txt_wrapper = document.createElement('div');
			txt_wrapper.id = 'text-wrapper'+row.id;
			txt_wrapper.className = 'text-wrapper';
			txt_wrapper.style.paddingLeft = depth*15 + "px";
			txt_wrapper.appendChild(handle);
			
			if (attachment_preview)
			{
				txt_wrapper.appendChild(attachment_preview);
				AddClass(text_n_notes, text_notes_class);
			}
			
			txt_wrapper.appendChild(text_n_notes);
			
		var amount_wrapper = document.createElement('div');
			amount_wrapper.className = 'amount-wrapper';
			amount_wrapper.appendChild(amount);
			amount_wrapper.appendChild(txt_wrapper);

		var duedate_wrapper = document.createElement('div');
			duedate_wrapper.className = 'duedate-wrapper';
			duedate_wrapper.appendChild(duedate);
			duedate_wrapper.appendChild(amount_wrapper);
			
		var assigned_to_wrapper = document.createElement('div');
			assigned_to_wrapper.className = 'assigned-to-wrapper';
			assigned_to_wrapper.appendChild(assign_to);
			assigned_to_wrapper.appendChild(duedate_wrapper);
			
		var attachment_wrapper = document.createElement('div');
			attachment_wrapper.className = 'assigned-to-wrapper';
			attachment_wrapper.appendChild(attachment);
			attachment_wrapper.appendChild(assigned_to_wrapper);

		var drag_handle = document.createElement('div');
			drag_handle.id = 'drag-handle'+row.id;
			drag_handle.className = 'drag-handle';
			
		// first clear the div
		while(row_div && row_div.firstChild)
			row_div.removeChild(row_div.firstChild);
		
		// now rebuild the row
		row_div.id = row.id;
		AddClass(row_div, 'row');
		
		if (that.client.IsDraggingEnabled() && !that.client.IsInReadOnlyMode())
			row_div.appendChild(drag_handle);
		
		row_div.appendChild(chkbox);
		row_div.appendChild(attachment_wrapper);
		row_div.onclick = function() { if (that.client.IsUserInteractionEnabled()) { that.client.SelectRow(row); }};
		row_div.onmouseover = function() { that.MouseOverRow(row.id); };
		row_div.onmouseout = function() { that.MouseOutRow(row.id); };
		row_div.setAttribute('data-depth', depth);
		row_div.setAttribute('data-collapsed', is_collapsed_str);
		
		if (row.getAttribute('_status') == 'checked')
			AddClass(row_div, "strike");
		else RemoveClass(row_div, "strike");
		
		// check if this is the selected row
		if (this.selected_row && row.id == this.selected_row.id)
			AddClass(row_div, "selected");
			
		return row_div;
	};
	
	this.RemoveRow=function(row)
	{
		var row_div = document.getElementById(row.id);
		if (row_div)
			this.container_div.removeChild(row_div);
	};
	
	// this function renders the children of row into children_div
	this.DisplayChildren=function(row, depth)
	{
		if ((row.hasChildren() && row.expanded == true) || row.getId() == 'body')	// row.expanded for BODY is false, thats why we need
		{																			// to check for the case where row is body separately
			current_row = document.getElementById(row.id);
			var d,c;
			for (var i = 0; i < row.children.length; i++)
			{
				d = document.createElement('div');
				this.DisplayRow(row.children[i], depth+1, d);
				
				if (row.id == 'body')
					this.container_div.appendChild(d);
				else this.container_div.insertBefore(d, current_row.nextSibling);
				
				if (!that.client.IsInReadOnlyMode())
					this.InitContextualMenuForRow(row.children[i]);
				
				current_row = d;
				if (row.children[i].hasChildren() && row.children[i].expanded == true)
					this.DisplayChildren(row.children[i], depth+1);
			}
		}
		
		EnableRowDragging();														// defined in dragndrop.js
	};
	
	// this function renders the children of row into children_div
	this.RemoveChildren=function(row)
	{
/*
		row_div = document.getElementById(row.id);
		if (row_div)
		{
			depth = row_div.getAttribute('data-depth');
			row_div = row_div.nextSibling;
			while(row_div && row_div.getAttribute('data-depth') > depth)
			{
				next_row = row_div.nextSibling;
				this.container_div.removeChild(row_div);
				row_div = next_row;
			}
		}
*/
		if (row.hasChildren())
		{
			var d,c;
			for (var i = 0; i < row.children.length; i++)
			{
				if (row.children[i].hasChildren() && row.children[i].expanded == true)
					this.RemoveChildren(row.children[i]);
					
				child_row = document.getElementById(row.children[i].id);
				if (child_row)
					this.container_div.removeChild(child_row);

			}
		}
	};
	
	this.RenderSelectRow=function()
	{
		sel_row = document.getElementById(this.selected_row.id);
		RemoveClass(sel_row, "mouse-hover");
		AddClass(sel_row, "selected");
		
		if (!that.client.IsInEditMode() &&  !that.client.IsEditingAmount() && !that.client.IsEditingNotes())
			ShowSelectionOverlay();
	};
	
	this.RenderDeselectRow=function()
	{
		if (this.selected_row != null)
		{
			sel_row = document.getElementById(this.selected_row.id);
			RemoveClass(sel_row, "selected");
			
			that.RemoveFocusFromTextInSelectedRow();
			
			HideSelectionOverlay();
		}
	};
	
	this.RemoveFocusFromTextInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_text = document.getElementById('text'+this.selected_row.id);
			
			sel_text_status = sel_text.contentEditable;
			if (sel_text_status == 'true')
			{
				sel_text.contentEditable = 'false';
				sel_text.contentEditable = 'true';
			}
		}
	};
	
	this.DoBlurOnTextInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_text = document.getElementById('text'+this.selected_row.id);
			sel_text.blur();
		}
	};
	
	this.DoBlurOnNotesInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_notes = document.getElementById('notes'+this.selected_row.id);
			sel_notes.blur();
		}
	};
	
	this.DoBlurOnAmountInSelectedRow=function()
	{
		if (this.selected_row != null)
		{
			sel_amount = document.getElementById('amount'+this.selected_row.id);
			sel_amount.blur();
		}
	};
	
	this.ShowAddNote=function()
	{
		sel_row_notes = document.getElementById('notes'+this.selected_row.id);
		if (sel_row_notes)
		{
			sel_row_notes.style.display = 'block';
			var length = sel_row_notes.innerHTML.length;
			SetCursorPosition(that.client.GetBrowser(), 'notes'+this.selected_row.id, length);		// defined in misc.js
		}
	};
	
	this.InitContextualMenuForRow=function(row)
	{
		$jq("#"+row.id).rightClick(function(e) {
			gContextMenuDisplayedForRow = row.id;
			that.client.SelectRow(row);
			that.client.RenderContextMenuForRow();
	
			$jq('<div id="context-menu-overlay" class="overlay" style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 100;"></div>').click(function() {
				that.client.HideContextMenu();
			}).appendTo(document.body);
			that.client.ShowContextMenu(e.pageX, e.pageY);
		});
	}
	
	this.PaintBackground=function(from_row)
	{
		if (typeof from_row === 'undefined')
			from_row = document.getElementById('outliner-root').firstChild.id;
		
		row_div = document.getElementById(from_row);

		if (row_div)
		{
			prev_div = row_div.previousSibling;
			if (prev_div)
			{
				if (HasClass(prev_div, 'oddrow'))
					counter = 1;
				else counter = 0;
			}
			else counter = 0;
			
			while (row_div)
			{
				RemoveClass(row_div,'oddrow');
				RemoveClass(row_div,'evenrow');
				
				if (counter % 2)
					AddClass(row_div,'evenrow');
				else AddClass(row_div,'oddrow');
	
				counter++;
				row_div = row_div.nextSibling;
			}
		}
	};
	
	// THIS FUNCTION SHOULD RETURN FALSE IF NOT FOUND, BUT IT ISN'T RETURNING ANYTHING RIGHT NOW
	// this returns true if child is a decendent of parent, false otherwise
	this.isDecendentOf=function(parent, child)
	{
		if (parent.hasChildren())
		{
			for (i = 0; i < parent.children.length; i++)
				return this.isDecendentOf(parent.children[i], child);
		}
		else
		{
			if (child == parent)
				return true;
		}
	};
	
	// this function returns the number of parents that this row has
	this.GetParentCount=function(row)
	{
		var parent = row.getParent();
		var count = 0;
		while (parent != null)
		{
			count++;
			parent = parent.getParent();
		}
		return count;
	};
	
	this.GetAncestors=function(row)
	{
		var ancestors = new Array();
		var parent = row.getParent();
		var count = 0;
		while (parent.id != 'body')
		{
			ancestors[ancestors.length] = parent;
			parent = parent.getParent();
		}
		return ancestors;
		
	};
	
	this.Init(inKey, inOutlineId, inDiv, inClient);
};

// Outline Class
function Outline(inId)
{
this.id = '';
this.attributes = new Object();
this.parent_row;
this.children = new Array();
this.expanded = false;
this.preview_state;					// thumbnail / preview

this.extras = new Object();			// temporary attributes that don't get stored in the OPML (ex: filename, file extension)

	this.getId = function()
	{
		return this.id;
	};

	this.init = function(id)
	{
		this.id = id;
		this.preview_state = 'thumbnail';
	};
	
	this.isExpanded=function()
	{
		return this.expanded;
	}
	
	this.setParent = function(par_row)
	{
		this.parent_row = par_row;
	};
	
	this.getParent=function()
	{
		return this.parent_row;
	};

	this.setAttribute = function(_name, _value)
	{
		this.attributes[_name] = _value;
	};
	
	this.unsetAttribute = function(_name)
	{
		delete this.attributes[_name];
	};

	this.getAttribute = function(_name)
	{
		if (typeof this.attributes[_name] === "undefined")
			return '';
		else return this.attributes[_name];
	};

	this.getAttributesAsString = function()
	{
	var attrs = new Array();
	var attr = "";

		for (key in this.attributes)
		{
			if (this.attributes[key] || key == 'text')
				attrs[attrs.length] = key + "=\"" + this.attributes[key] + "\"";
		}

		return attrs.join(' ');
	};
	
	this.getAttributesAsArray=function()
	{
		return this.attributes;
	};
	
	this.setExtra = function(_name, _value)
	{
		this.extras[_name] = _value;
	};

	this.getExtra = function(_name)
	{
		if (typeof this.extras[_name] === "undefined")
			return '';
		else return this.extras[_name];
	};
	
	this.hasChildren = function()
	{
		if (this.children.length > 0)
			return true;
		else return false;
	};
	
	this.GetChildren=function()
	{
		if (this.children.length > 0)
			return this.children;
		else return false;
	};
	
	this.GetChildrenCount=function()
	{
		return this.children.length;
	};
	
	this.SetPreviewState=function(state)
	{
		this.preview_state = state;
	};
	
	this.GetPreviewState=function()
	{
		return this.preview_state;
	};
	
	this.CopyAttributesFromRow=function(row)
	{
		delete row.attributes['_sid'];
		this.attributes = DuplicateObject(row.attributes);
		
		this.expanded = row.expanded;
		this.preview_state = row.preview_state;
	};
		
	this.init(inId);
};
