// dataview represents a view of a datatable. dataviews can also be joined with each other.
/* TODO:s
	Fix NaN error when entering two decimal dots in a row.
*/
var JSDC_DATAVIEW_EXISTS = true;
function visible_to_col_idx(node, dataview)
{
	var col_idx, curr_col;
	var visible_col = 0;
	for (col_idx in dataview.columns)
	{
		curr_col = dataview.columns[col_idx];
		if (curr_col.visible == true) 
		{
			if (node.parentNode.childNodes[visible_col] == node)
			{
				return col_idx;
			}
			visible_col++;
		}
	
	}	
	return -1;

}

function src_idx_to_column(dataview, src_idx)
{
	
	var col_idx = 0;
	var col_length = dataview.columns.length;
	while (col_idx < col_length)
	{
		if (dataview.columns[col_idx].src_idx == src_idx)
		{
			return dataview.columns[col_idx];
			break;
		}
		col_idx++;
	}
	return null;

}

function col_idx_to_visible(curr_col_idx, dataview)
{
	var col_idx;
	var visible_col = 0;
	for (col_idx in dataview.columns)
	{
		if (dataview.columns[col_idx].visible == true) 
		{
			if (col_idx == curr_col_idx)
			{
				return visible_col;
			}
			visible_col++;
		}
	
	}	
	return -1;

}


// A "data-aware" object - daobj is the object(s..some kinds are arrays of values)
function data_aware_object(dataview, daobj, kind, idcol_idx, valuecol_idx, onChange, onDoubleClick, onEdit)
{
	this.daobj = daobj;
	var function_ref = this;
	this.kind	= kind;
	this.idcol_idx = idcol_idx;
	this.valuecol_idx = valuecol_idx;
	this.data_row_objects = new Array();
	var init_error = null;
	// Create kind-specific settings
	switch(kind)
	{
		case "table":
			if (typeof JSDC_DAO_TABLE_EXISTS!="undefined")
			{
				this.options = new tableoptions();
			}
			else
			{
				init_error = 'JSDC error: You must include dao_table.js to use the "table" data aware object kind.';
			}
	 	break;			
		case "select":
			if (typeof JSDC_DAO_SELECT_EXISTS!="undefined")
			{
				this.options = new selectoptions();
			}
			else
			{
				init_error = 'JSDC error: \nYou must include dao_select.js to use the "select" data aware object kind.';
			}
	 	break;		

		case "tree":
			if (typeof JSDC_DAO_TREE_EXISTS!="undefined")
			{
				this.options = new treeoptions(this);
			}
			else
			{
				init_error = 'JSDC error: \nYou must include dao_tree.js to use the "tree" data aware object kind.';
			}
	 	break;	
		case "input_group":  
	/*		if (typeof JSDC_DAO_INPUT_GROUP_EXISTS!="undefined")
			{
				this.options = new inputgroupoptions(this);
			}
			else
			{
				init_error = 'JSDC error: \nYou must include dao_tree.js to use the "tree" data aware object kind.';
			}			*/
			init_error = 'JSDC error : \nThe "input_group" data aware object kind is not implemented yet.';    	
	 	break;			
	}

	if (init_error) 
	{
		alert(init_error);
		throw(init_error);	
	}
	if (dataview)
	{
		this.dataview = dataview;
		dataview.aware_objects.push(this);    
	}

	this.onChange = onChange;
	this.onDoubleClick = onDoubleClick;
	this.onEdit = onEdit;
	this.DeleteBtn = null;
	
	this.DoOnChange = function DoOnChange() 
  	{ 
  		switch(kind)
  		{
  			case "table":
  				/* No onChange event implemented yet on this level 
  				if (onChange) {onChange(dataview); } */
  			break;
  			case "select":
  				dataview.gotoRecord(daobj.selectedIndex);
		  		if (daobj.selectedIndex > -1) 
		  		{
		  			if (dataview.application)
		  			{
						dataview.application.fulltextview(daobj.options[daobj.selectedIndex].value);
					}
	  				if (onChange) {onChange(dataview); } 
	  			}
			break;
  			

  		}
  	}
  	
  	
	this.DoOnDblClick = function DoOnDblClick() 
  	{ 
  		switch(kind)
  		{
  		
  			case "table":
  				/* No onChange event implemented yet on this level 
  				if (onChange) {onChange(dataview); } */
  			break;
  			case "select":
	  		if (daobj.selectedIndex > -1) 
	  		{
  				if (onDoubleClick) {onDoubleClick(dataview); } 
  			}
			break;
  		}
  	}
  	
	this.DoOnDelete = function DoOnDelete(e) 
  	{ 
		var target = window.event ? event.srcElement : e.target; 

  		switch(kind)
  		{
  			case "table":
				// The row has a hidden input field with the row index.
				if (target.parentNode.parentNode.lastChild.id == "row_ref")
				{
		  			dataview.remove(target.parentNode.parentNode.lastChild.value);
	  			}
	  			else
	  			{
	  				throw ("Internal JSDC error in DoOnDelete() - 'table' , row_ref object expected.");
	  			}

		  		  
		  	break;			
  			case "select":
		  		if (daobj.selectedIndex > -1) 
				{
		  			dataview.remove(target.selectedIndex);
		  			dataview.refresh();
		  		}  
		  	break;			

  			case "input_group":      	
	  		  	alert('JSDC alert! : This is unfinished code. the input_group type has not yet been implemented.');
	  			dataview.remove(target.selectedIndex);
	  			dataview.refresh();
		  	//		if (onChange) {onChange(dataview); } 
  
		  	break;			
 		}

  	}
	switch(kind)
	{	
	case "table":
		if (daobj.attachEvent) 
		{
			daobj.attachEvent("onchange", this.DoOnChange);
			daobj.attachEvent("ondblclick", this.DoOnDblClick);
		}
		else
		{
			daobj.addEventListener("change", this.DoOnChange, true); // standards compliant; doesn't work in IE
			daobj.addEventListener("dblclick", this.DoOnDblClick, true); // standards compliant; doesn't work in IE
		}
			 	
	break;
	case "select":
		if (daobj.attachEvent) 
		{
			daobj.attachEvent("onchange", this.DoOnChange);
			daobj.attachEvent("onkeyup", this.DoOnChange);
			daobj.attachEvent("ondblclick", this.DoOnDblClick);
		}
		else
		{
			daobj.addEventListener("change", this.DoOnChange, true); // standards compliant; doesn't work in IE
			daobj.addEventListener("keyup", this.DoOnChange, true); 
			daobj.addEventListener("dblclick", this.DoOnDblClick, true); // standards compliant; doesn't work in IE
		}
			 	
	break;
	}

	this.DoOnEdit = function DoOnEdit(e)
	{	
		var event_object = new edit_event_object(e);
		// The row has a hidden input field with the row index.

		if (event_object.target.parentNode.parentNode.lastChild.id == "row_ref")
		{
			var history = dataview.application.begin_history();
			try 
			{
				var row_idx = event_object.target.parentNode.parentNode.lastChild.value;
				var col_idx = visible_to_col_idx(event_object.target.parentNode, dataview);
				if (col_idx > -1)
				{
	
					var currcolumn = dataview.columns[col_idx];
					if (event_object.key) // The event was triggered by a non-control key.
					{
						if (function_ref.onEdit)
						{
							var oldvalue = currcolumn.getdata(row_idx);
						}
	
						if (currcolumn.datatype !='custom')
						{				
							var val_result = currcolumn.setdata(row_idx, event_object.value);
							setErrorBackground(val_result, event_object, dataview.application);
						}
						else
						{
							
							// The custom datatype has it's own editor.
							val_result = null;
						}
	
	
					}
					else
					if (currcolumn.datatype =='multiple')
					{
						if (currcolumn.multiple_editor)
						{
							currcolumn.multiple_editor.edit(row_idx, event_object);
						}
						else
						{
							alert('JSDC error: Please assign an editor to the dataviewcolumn.multiple_editor property\n when using the "multiple"-datatype');
							throw('JSDC error: Please assign an editor to the dataviewcolumn.multiple_editor property\n when using the "multiple"-datatype');
						}
					}
					else
					if (currcolumn.lookup_values.length > 0)
					{
						var val_result = currcolumn.setdata(row_idx, currcolumn.lookup_values[event_object.target.selectedIndex].value);
					}
					
					if (!val_result && function_ref.onEdit)
					{
						function_ref.onEdit(dataview, row_idx, col_idx, oldvalue, event_object.value);
					}
				}
				dataview.application.end_history();
			}
			catch (err)
			{
				dataview.application.end_history(); // Step out of the current history.
				history.parent.undo(true); // Only what we've done should be undone and removed from history so that it can't be redone.
				event_object.target.blur(); // Lose focus, otherwise the edit event will be triggered.
				alert('An error occured when calculating values : "'+ err + '"\n');		
				
			}
			
			
		}
		else
		{
			throw ("Internal JSDC error in DoOnDelete() , row_ref object expected.");
		}
	}
	


	 /* this.DoOnMouseover = function DoOnMouseover() {if (daobj.selectedIndex > -1) { window.status = daobj.options[daobj.selectedIndex];}}
  try {
  	daobj.EventListener("mouseover", this.DoOnMouseover, true); // standards compliant; doesn't work in IE
  }
  catch(ex) {
    daobj.attachEvent("mouseover", this.DoOnMouseover);
  }*/	
}

function masterdetail (master, detail,mastercolumn_idx,detailcolumn_idx)
{
	this.master = master;
	this.detail = detail;
	this.mastercolumn_idx = mastercolumn_idx;
	this.detailcolumn_idx = detailcolumn_idx;
	detail.masterdetail = this;
	master.detaildataviews.push(detail);
	this.getmasterdata = function getmasterdata() 
	{ return (this.master.columns[this.mastercolumn_idx].getdata(this.master.curr_row));}
} 

// Return a boolean value telling whether // the first argument is a string. 

function isString() 
{
  if (typeof arguments[0] == 'string') return true;
  if (typeof arguments[0] == 'object') 
  {  
  	var criterion = arguments[0].constructor.toString().match(/string/i); 
		return (criterion != null);  
	} return false;
}

// aggregate() - Settingsobject for dataviewcolumn
function aggregate(kind, decimal_places, displayclass, customaggregate) 
{
	this.kind = kind;
	this.decimal_places = decimal_places;
	this.displayclass = displayclass;
	this.customaggregate = customaggregate;
	this.value = null;
                               
}

function get_column_style(dest, column)
{
	if (column.displayclass)
	{
		return column.displayclass;
	}
	else
	if (dest.options.valueClass)
	{
		return dest.options.valueClass;
	}
	else
	{
		return null;
	}

}

function make_column_styles(dest)
{
	var col_idx, curr_column;
	var colstyles = new Array();
	for (col_idx in dest.dataview.columns)
	{
		curr_column = dest.dataview.columns[col_idx];
		if (curr_column.visible)
		{
			colstyles.push(get_column_style(dest, curr_column));
		}
	
	}
	return colstyles;
}

// The lookup_value class defines a lookup value
function lookup_value(name, value)
{
	this.name = name;
	this.value = value;
}

// dataviewcolumn() - Represents a column in the dataview. 
function dataviewcolumn(dataview, column_name, src_idx, displaylength, valueclass, aggregate, datatype)
{
	this.column_name = column_name;
	this.src_idx = src_idx;
	this.displaylength = displaylength;
	this.valueclass = valueclass;
	this.headerclass = null;
	this.decimal_places = null;
	this.lookup_values = new Array();
	this.editable = null; // Applies only to tables.
	this.multiple_editor = null; // Multiple-type editor.
	if (datatype)
	{
		this.datatype = datatype;
	}
	else
	{
		this.datatype = 'string';
	}
	this.aggregate = aggregate;
	this.visible = true;
	this.col_idx = null;
	
	if (dataview)
	{
		this.dataview = dataview;
		dataview.columns.push(this); 
		this.col_idx = dataview.columns.length;   
	}
	else
	{
		this.dataview = null;
	}
	
	this.getdata = function getdata(row_idx) {
		switch (this.datatype)
		{
			case "string": 
				return (this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]); 
			break;
			case "integer": 
				
				var curr_data = Number(this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]);
				if (isNaN(curr_data) == false && curr_data.toFixed)
				{
					return curr_data.toFixed(0);			  	  
				}
				else
				if (curr_data = "")
				{
					return "0";
				}
				else
				if (curr_data = null)
				{
					return "NaN";
				}

			break;
			case "decimal": 
				
				if (this.decimal_places) 
				{
					var curr_data = Number(this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]);
					if (isNaN(curr_data) == false && curr_data.toFixed)
					{
						return curr_data.toFixed(this.decimal_places);			  	  
					}
					else
					if (curr_data = "")
					{
						return "0";
					}
					else
					if (curr_data = null)
					{
						return "NaN";
					}
				}
				else
				{
					return Number(this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]);
				}
			break;
			case "date": 
				return (Date(this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]])); 
			break;
			case "custom": 
				return (this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]); 
			break;
			case "multiple": 
				return (this.dataview.datatable.columns[this.src_idx].data[this.dataview.selection[row_idx]]); 
			break;
		}
	}
	this.setdata = function setdata(row_idx, value, do_not_validate)  
	{
		// Suspends updating when a decimal dot is entered. Otherwise the entered "1." will be interpreted as "1" and reset to "1". TODO: Make nicer. No idea how, though.
		if (!((this.datatype == "decimal") && (Number(value) == Number(dataview.datatable.columns[src_idx].data[dataview.selection[row_idx]]))))
		{
			if (!do_not_validate)
			{
				var val_result = validate_datatype(value, this.datatype);
				if (val_result) { return val_result;}
			}
			dataview.datatable.updateat(dataview.selection[row_idx], src_idx, value); 
		}
		return null;
		
	}	
	
	this.addlookupvalue = function addlookupvalue(name, value)
	{
		this.lookup_values.push(new lookup_value(name, value));
	}


}

function dataview_recalculate_aggregates(dataview)
{

	var curr_value = null;
	var col_idx = 0;
	dataview.aggregate_values.length = 0;
	for (col_idx in dataview.columns)
	{
		curr_col = dataview.columns[col_idx];
		if (curr_col.aggregate)
		{
			switch (curr_col.aggregate.kind) 
			{
				case "sum":
					var agg_sum, curr_idx;
					agg_sum = Number(0);
					for (curr_idx in dataview.selection)
					{
						agg_sum = Number(agg_sum) + Number(curr_col.getdata(curr_idx));
					}
					if ((agg_sum.toFixed) && (curr_col.aggregate.decimal_places))
					{
						curr_value = agg_sum.toFixed(curr_col.aggregate.decimal_places);
					}
					else
					{
						curr_value = agg_sum;
					}
					
					break;
				case "avg":
					var agg_sum, curr_idx;
					agg_sum = Number(0);
					for (curr_idx in dataview.selection)
					{
						agg_sum = Number(agg_sum) + Number(curr_col.getdata(curr_idx));
					}
					if ((agg_sum.toFixed) && (curr_col.aggregate.decimal_places))
					{
						curr_value = Number(agg_sum/sel_length).toFixed(curr_col.aggregate.decimal_places);
					}
					else
					{
						curr_value = agg_sum/sel_length;
					}
				break;
				case "caption":
					curr_value = curr_col.aggregate.value;
				break;
				case "custom":
				
					curr_value = curr_col.aggregate.customaggregate(dataview, curr_col);
				break;
			}
			if (curr_col.aggregate.displayclass)
			{
				curr_value = MakeSpanned(curr_value, curr_col.aggregate.displayclass); // TODO: set className on TD instead
			}
			dataview.aggregate_values.push(curr_value);	
		}
		else
		{
			dataview.aggregate_values.push(null);	
		}
		
	}
}; 


function view_condition(dataview,condition_name, comparator, value, src_col_idx)
{
	this.dataview = dataview;
	this.condition_name = condition_name;
	this.comparator = comparator;
	this.src_col_idx = src_col_idx;
	this.value = value;
	if (dataview)
	{
		this.dataview = dataview;
		dataview.conditions.push(this);    
	}
} 

function calculate_query_checksum(conditions, masterdata)
{
	var checksum = Number(1); // So that also empty conditions get a checksum > 0
	var cond_value = null;
	if (masterdata)
	{
		checksum+=masterdata.length;
	}
	if (conditions.length > 0)
	{
		var cond_idx = Number(0);
		for (cond_idx in conditions)
		{
			checksum+=conditions[cond_idx].src_col_idx;
			checksum+=conditions[cond_idx].comparator.charCodeAt(0);
	
			var str_idx = 0;
			cond_value = conditions[cond_idx].value.toUpperCase();

			for (str_idx=0;str_idx<cond_value.length;str_idx++)
			{
				checksum+= cond_value.charCodeAt(str_idx);
			}
		}
	}
	return(checksum);
}

function compare_conditions(conds1, conds2)
{
	if (conds1.length == conds2.length)
  	{
  		if (conds1.length > 0) 
  		{
		  	var cond_idx, curr_cond1, curr_cond2;
		  	for (cond_idx in conds1)
		  	{
		  		curr_cond1 = conds1[cond_idx];
		  		curr_cond2 = conds2[cond_idx];
		  		
		  		if (curr_cond1.comparator == curr_cond2.comparator && 
		  				curr_cond1.src_col_idx == curr_cond2.src_col_idx &&
		  				curr_cond1.value == curr_cond2.value)
		  		{
		  			return (true);
		  		}
		  		else
		  		{
		  			return (false);
		  		}
		  	}
	  	}
	  	else
	  	{
	  		return (true);
	  	}
	}
	else
	{
		return (false);
	}

}

// A postive result is a query_cache index. A negative result is a checksum. (Ugly?)
function check_query_cache(dataview, masterdata)
{
	var checksum = calculate_query_checksum(dataview.conditions, masterdata);
	var cache_idx = 0;
	var curr_cache;
	if (dataview.query_cache.length > 0)
	{
		for (cache_idx in dataview.query_cache)
		{
			curr_cache = dataview.query_cache[cache_idx];
			if (curr_cache.checksum == checksum && curr_cache.masterdata == masterdata)
			{
				// if the checksums matched, compare the conditions. 
				if (compare_conditions(dataview.query_cache[cache_idx].conditions, dataview.conditions) == true)
				{
					return(cache_idx);			
				}
			}
		}
			return(-checksum);	
	}
	else
	{
		return(-checksum);
	}
}

// A set of query conditions. An entry in the query cache.
function query_cache_item(conditions, checksum, masterdata, selection)
{
	this.checksum = checksum;
	this.conditions = conditions;
	this.masterdata = masterdata;
	this.selection = new Array();
	this.selection = this.selection.concat(selection);
} 

function dataview_applyconditions(dataview)
{
	var row_idx, col_idx, cond_idx, curr_result, curr_cond;
  
	// If applicable, prefetch master table data
	var masterdata = null;
	if (dataview.masterdetail && dataview.masterdetail.master.selection.length >0)
	{
		masterdata  = dataview.masterdetail.getmasterdata(); 
	}
	// Check cache
	var query_cache_idx = check_query_cache(dataview, masterdata);
  
	// Negative query_cache_idx values are checksums. 
	if (query_cache_idx > -1) 
	{
		dataview.selection.length = 0;
		dataview.selection = dataview.selection.concat(dataview.query_cache[query_cache_idx].selection);
	}
	else
	{
		
		var datas = new Array();
		for (col_idx in dataview.datatable.columns)
		{
			datas.push(dataview.datatable.columns[col_idx].data);
		}
		dataview.selection.length = 0;
		var curr_value = null;
		var curr_cond_value = null;
		for (row_idx in datas[0])
		{
			curr_result = true;
			
	  		for (cond_idx in dataview.conditions)
	  		{
				curr_cond = dataview.conditions[cond_idx];
				
				curr_value = datas[curr_cond.src_col_idx][row_idx];
				
				if (curr_value) 
				{curr_value = curr_value.toUpperCase()} 
				else 
				{curr_value = ""}
				
				curr_cond_value = curr_cond.value.toUpperCase();
				switch(curr_cond.comparator)
	  			{
	  			case "<>":
		  			curr_result = (curr_value != curr_cond_value);
	  			break;
	  			case "=":
		  			curr_result = (curr_value == curr_cond_value);
					break;
	  			case "<":
		  			curr_result = (curr_value < curr_cond_value);
					break;
	  			case ">":
		  			curr_result = (curr_value > curr_cond_value);
					break;
	  			case "in":
					if (isString(curr_value)) 
					{ 
						curr_result = (curr_value.indexOf(curr_cond_value) > -1);
					}
					break;
				}
				if (curr_result == false) {break;}
			}
			if (curr_result == true) 
			{
				dataview.selection.push(row_idx); 
			}
		}
		// Apply detail selection.
		if (masterdata)
		{
			var detaildatas = datas[dataview.masterdetail.detailcolumn_idx];
			var row_idx = 0;
			if (dataview.conditions.length == 0) // If there no conditions, only masterdetail.
			{
				dataview.selection.length = 0;
				for (row_idx in detaildatas)
				{	
				
					if (detaildatas[row_idx] == masterdata)
					{
						dataview.selection.push(row_idx);
					}
				}
			}
			else
			{ 
				// Delete from selection those that are not in the master-detail relation.
			  	var deleted = 0;
			  	var selection_length = dataview.selection.length;
			  	// Del_count is here to avoid .length(which is slow)
			  	var del_count = 0;
			  	while (row_idx < selection_length - del_count)
			  	{	  
			  		if (detaildatas[dataview.selection[row_idx]] != masterdata)
			  		{
						dataview.selection.splice(row_idx, 1);
						del_count++;
			  		}
			  		else
			  		{
			  			row_idx++;
			  		}
			  	}
			}
		}
		
		// Query to cache (A negative query_cache_idx is actually a checksum)
		var curr_query = new query_cache_item(dataview.conditions, -query_cache_idx, masterdata, dataview.selection);
		dataview.query_cache.push(curr_query);
	}
	dataview.count = dataview.selection.length;
}


function dataview_automatic_fields(dataview)
{
	if (dataview.datatable)
	{
		// Loop and add the underlying datatables columns.
		var col_idx = 0;
		for (col_idx in dataview.datatable.columns)
		{
			new dataviewcolumn(dataview, dataview.datatable.columns[col_idx].column_name, 0, 30, "");
		}
	}

}

function dataview_data_event(dataview, action, row_idx, col_idx)
{

	if (action != "remove")
	{
	
		dataview.query_cache.length = 0;
		dataview_applyconditions(dataview);
	}
	else
	{
		// TODO: Check how long it takes to loop through caches on "remove".
		dataview.query_cache.length = 0;
		dataview_applyconditions(dataview);
	}

	dataview_propagate(dataview, action, row_idx, col_idx);
}


function dataview_propagate(dataview, action, row_idx, col_idx)
{

	var obj_idx;
	for (obj_idx in dataview.aware_objects)
	{
		switch (dataview.aware_objects[obj_idx].kind)
		{
		case "table":
			table_change(dataview.aware_objects[obj_idx], action, row_idx, col_idx);	
		break;
		case "select":
			select_change(dataview.aware_objects[obj_idx], action, row_idx, col_idx);
		break;
		case "tree":
			tree_change(dataview.aware_objects[obj_idx], action, row_idx, col_idx);
		break;
		case "input_group":
			input_group_change(dataview.aware_objects[obj_idx], action, row_idx, col_idx);
     	break;

		}      
	}
	// Update detail dataviews.
	 // TODO: Handle refresh also.
	if (action != "refresh")
	{
		var dv_idx;
		if (dataview.selection.length > 0)
		{
		
			for (dv_idx in dataview.detaildataviews)
			{    
				dataview.detaildataviews[dv_idx].refresh();
				dataview.detaildataviews[dv_idx].gotoRecord(Number(0));
			}
		}
		else
		{
			for (dv_idx in dataview.detaildataviews)
			{ 
				dataview.detaildataviews[dv_idx].selection.length = 0;
				dataview_propagate(dataview.detaildataviews[dv_idx]); 
			}	  
		}
	}
}


function getfieldvalue(dataview, index)
{
	if ((index < dataview.columns.length) && (index > -1) )
	{
		return dataview.columns[index].getdata(dataview.curr_row);
	}
  else
  {
    throw("getfieldvalue:Invalid column index - " + index);
  }
}

function setfieldvalue(dataview, index, value)
{
	if ((index < dataview.columns.length) && (index > -1) )
	{
		dataview.columns[index].setdata(dataview.curr_row, value);
	}
	else
	{
		throw("setfieldvalue:Invalid column index - " + index);
	}
}


function dataview(name, datatable, appobj)
{

	if (datatable) 
		{this.datatable = datatable; datatable.dataviews.push(this);}  

	this.curr_row = 0; 
	this.count = 0;
	this.name = name;
	this.columns = new Array();
	this.aware_objects = new Array();
	this.selection = new Array();

	this.application = appobj;

	this.conditions = new Array();
	this.query_cache = new Array();
	this.aggregate_values = new Array();
	  
	// Master/detail dataview.
	this.masterdetail = null;
	this.detaildataviews = new Array();
	  
	// Functions
	
	this.getvalue = function getvalue(index) 
		{return getfieldvalue(this, index);} 
	this.setvalue = function setvalue(index, value) 
		{return setfieldvalue(this, index, value);} 

	this.refresh = function refresh() 
		{dataview_applyconditions(this); dataview_propagate(this, "refresh"); } 

/*	TODO: implement these in all dao:s. */
	this.gotoRecord = function gotoRecord(row_idx) 
		{ this.curr_row = row_idx; dataview_propagate(this, "goto");}
	
	this.next = function next() 
		{ curr_row++; dataview_propagate(this, "next");}
	this.previous = function previous() 
		{ curr_row--; dataview_propagate(this, "previous");}
	this.first = function first() 
		{ curr_row = 0; dataview_propagate(this, "first");}
	this.last = function last() 
		{ curr_row = count - 1; dataview_propagate(this, "last");}

	this.remove = function remove(row_idx) 
		{this.datatable.removeat(row_idx);}
	this.insert = function insert(row_idx) 
		{this.datatable.insertat(row_idx); this.curr_row = row_idx;} // Should this apply conditions on the new row?
	this.applyconditions = function applyconditions() 
		{dataview_applyconditions(this);}
	this.automatic_fields = function automatic_fields()
		{dataview_automatic_fields(this);}
	
}

