/*==============================================================================

                                  Smart Grid 1.3
                                  ==============
                       Copyright (c) 2007 Vyacheslav Smolin


Author:
-------
Vyacheslav Smolin (http://www.richarea.com, http://html2xhtml.richarea.com,
re@richarea.com)

About the script:
-----------------
Smart Grid is 100% JavaScript Ajax-based table editing tool. Powered with
Smart Cells - Ajax-based library that allow to make any part or your page
editable.

Requirements:
-------------
Smart Cells works in IE, Mozilla-based browsers such as Firefox, Opera 9+,
and Safari 3.0.

Usage:
------
Please see example.html.

Demo:
-----
http://www.richarea.com/demo/smart_grid/

License:
--------
Free for non-commercial using. Copyright information must stay intact.
Please contact author for permission to use it in commercial projects.


==============================================================================*/

// smart grid object
function SmartGrid(obj) {

	// parent object of the grid
	if (typeof(obj) == 'object') {
		this.obj = obj;
	} else {
		this.obj = document.getElementById(obj);
	}

	// columns info (header names, cells that the columns are based on, etc)
	this.columns = new Array();

	// initial grid data
	this.data = new Array();


	// id of the last clicked row
	this.last_clicked_row_id = null;

	// ids of the selected rows
	this.selected_rows = new Array();


	// on save cell handler
//	this.on_save_cell = null;


	// paging mode
	this.page_mode = false;
	// current page
	this.current_page = 0;
	// page size, 0 - no paging
	this.page_size = 0;
	// page sizes
	this.page_sizes = new Array(0, 1, 2, 3, 5, 10, 20, 50, 100);
	// actual number of rows in page
	this.real_page_size = 0;
	// total number of pages
	this.total_pages = 1;

	// url to read data from
	this.data_url = '';


	// dynamic loading mode (grid loads and renders data that are visible only)
	// invisible smart cells are converted to common cells
	this.dynamic_loading = false;

	// max number of rows loaded first time when number of rows in visible area
	// could not be determined
	this.dynamic_loading_size = 3;

	// row cache
	this.rows = new Array();

	// timer to initiate loading rows that became visible after scrolling
	this.dl_timer = null;

	// flag to prevent addition the same rows twice from different data sets
	this.dl_adding_rows = false;


	// number of columns to be freezed
	this.frozen_col_num = 0;
	this.frozen_grid = null; // grid with frozen columns if any


	// actions
	this.show_actions = false;
	// url to send add/update/delete queries to
	this.actions_url = '';
	// permissions to do the actions
	this.add_allowed = true;
	this.update_allowed = true;
	this.delete_allowed = true;


	this.language = 'en';
	// use default langauge if lang data not found for the language specified
	this.default_language = 'en';

	// header's border draggable area width (used to drag cells' borders)
	this.header_drag_width = 10;

	// true when resizing a column
	this.resize_mode = false;

	// path to grid images
	this.image_path = 'img/';

	// row height (used if could not determine - happens eg in IE when
	// page is not loaded completely)
	this.row_height = 20;

	// index of the last sorted column
	this.last_sorted = null;

	this.obj.className = 'sg_main_div';
//	this.obj.style.position = "relative"; //!!!!


	this.obj.onselectstart = function(){return false;};


	// create main table
	this.main_table = document.createElement('TABLE');

	this.main_table.border = 0;
	this.main_table.cellSpacing = 0;
	this.main_table.cellPadding = 0;
	this.main_table.width = "100%";
	this.main_table.style.tableLayout = "fixed";
	this.main_table.className = "sg_main_table";
//	this.main_table.style.position = "relative"; //!!!!


	var main_header_cell = this.main_table.insertRow(0).insertCell(0);
	var main_content_cell = this.main_table.insertRow(1).insertCell(0);


	// header div
	this.header_div = document.createElement("DIV");
	this.header_div.style.width = "100%";
//	this.header_div.style.height = '100px';
	this.header_div.style.overflow = "hidden";
	this.header_div.style.position = "relative";
	this.header_div.className = "sg_header_div";


	// header table
	this.header_table = document.createElement('TABLE');
	this.header_table.width = '100%';
//	this.header_table.border = 1;
	this.header_table.cellSpacing = 0;
	this.header_table.cellPadding = 0;
	this.header_table.className = "sg_header_table";
	this.header_table.style.tableLayout = "fixed";

	this.header_table.onmousemove = new Function("e","this.grid.header_mousemove(e||window.event)");
	this.header_table.onmousedown = new Function("e","this.grid.start_header_drag(e||window.event)");

	// change cursor state to see if it is inside a draggable area or not
	this.header_mousemove = function(e) {
		var src_obj = e.target?e.target:window.event.srcElement;
		var cell_obj = get_previous_object(src_obj, 'TD');
		if (!cell_obj) return;

		if (this.cursor_in_drag_area(e, cell_obj)) {
			cell_obj.style.cursor = 'E-resize';
		} else {
			cell_obj.style.cursor = 'default';
		}

		// Opera fix, otherwise it jumps to 0 scroll position - sucs browser ;)
		this.header_div.scrollLeft = this.content_div.scrollLeft;
	};

	// check if cursor is inside draggable area
	this.cursor_in_drag_area = function (e, cell_obj) {

		var cell_pos = get_obj_pos(cell_obj);

		if (cell_pos[0]+cell_obj.offsetWidth - e.clientX <=
			this.header_drag_width) {
			return true;
		}
		return false;

	};

	// set event parsers necessary to change column sizes
	this.start_header_drag = function(e) {

		this.resize_mode = false;

		var src_obj = e.target?e.target:window.event.srcElement;
		var cell_obj = get_previous_object(src_obj, 'TD');
		if (!cell_obj) return;

		if (!this.cursor_in_drag_area(e, cell_obj)) {
			return;
		}

		var mouse_x = e.clientX;
//		alert('Start Drag!');
//		this.obj.onmousemove = new Function("e","this.grid.resize_column(e||window.event, cell_obj)");
		var start_x = e.clientX;
		var start_col_width = parseInt(cell_obj.style.width);
//alert(start_col_width);
		var start_table_width = parseInt(this.content_table.style.width);
//alert(start_table_width);
		this.obj.onmousemove = function(e) {this.grid.resize_column(e||window.event, cell_obj, start_x, start_col_width, start_table_width);};

		sc_attach_event(document.body, 'mouseup', this.doc_mouseup);

	};

	this.doc_mouseup = new Function("e","document.getElementById('" + this.obj.id + "').grid.stop_header_drag();");

	// resize column
	this.resize_column = function(e, cell_obj, mouse_x, col_width, table_width) {
		this.resize_mode = true;

		var incr = e.clientX - mouse_x;
		var new_width = Math.max(col_width + incr, this.header_drag_width);
		var new_incr = new_width - col_width;
//var new_width = col_width+incr;
//var new_incr = incr;

//alert(cell_obj.cell_index);
//alert(this.columns[cell_obj.cell_index]['col_width']);
//if (incr != 0) alert(e.clientX + ' => ' + mouse_x + ' => ' + incr);

		this.columns[cell_obj.cell_index+this.frozen_col_num]['col_width'] = new_width;
		this.header_table.rows[0].cells[cell_obj.cell_index].style.width = new_width + 'px';
//		this.header_table.style.width = table_width + incr + 'px';

		this.content_table.style.width = table_width + new_incr + 'px';

		// change width of frozen grid if this is such a grid
		// does not work in Opera (get back to this later)
		if (0 && this.common_grid) {
			this.frozen_div.style.width = table_width + new_incr + 'px';
			this.frozen_cell.width = table_width + new_incr;
			this.frozen_cell.style.width = table_width + new_incr + 'px';
		}

//alert(this.content_table.style.width);

//		this.header_div.scrollLeft = 0;
//alert(cell_obj.cell_index + ' => ' + mouse_x + ' => ' + col_width + ' => ' + table_width);

		// set new width to column of data rows
		var rows = this.get_rows();
		var rows_len = rows.length;

		var i;
		for (i=0; i<rows_len; i++) {
			var data_cell = rows[i].cells[cell_obj.cell_index];
			data_cell.style.width = new_width + 'px';
		}

		this.header_div.scrollLeft = this.content_div.scrollLeft;

		// move sort image if it is in the column resized
		if (this.last_sorted != null) {
			var sorted_cell_obj = cell_obj.parentNode.cells[this.last_sorted];
			this.move_sort_image(sorted_cell_obj);
		}

	};

	// exit resize column size mode
	this.stop_header_drag = function() {
		this.obj.onmousemove = "";
		sc_detach_event(document.body, 'mouseup', this.doc_mouseup);

//alert('Stop drag!');
	};

	this.header_table.onclick = new Function("e","this.grid.header_click(e||window.event)");

	// parse mouseclick on a header cell
	this.header_click = function(e) {
		if (this.resize_mode) return;

		var src_obj = e.target?e.target:window.event.srcElement;
		var cell_obj = get_previous_object(src_obj, 'TD');
		if (!cell_obj) return;

		this.sort_rows(cell_obj);

	};


	// image to display sorting order
	this.sort_img = document.createElement('IMG');
	this.sort_img.setAttribute('src', this.image_path + 'sort_desc.gif');
	this.sort_img.style.display = 'none';
	this.sort_img.style.position = 'absolute';
//	document.body.appendChild(this.sort_img);
	this.header_div.appendChild(this.sort_img);


	// returns path to image corresponding to sort order sort_order
	this.get_sort_img_src = function(sort_order) {
		if (sort_order == 'desc') {
			return this.image_path + 'sort_desc.gif';
		} else {
			return this.image_path + 'sort_asc.gif';
		}
	};

	// load images in cache
	var sort_asc_img = new Image();
	sort_asc_img.src = this.get_sort_img_src('asc');
	var sort_desc_img = new Image();
	sort_desc_img.src = this.get_sort_img_src('desc');

	// sort column chosen
	this.sort_rows = function(cell_obj) {

		// do not sort if not all rows are loaded
		if (this.dynamic_loading ||
			this.common_grid && this.common_grid.dynamic_loading) {

			alert(this.get_text_data('dynamic_loading'));
			return;
		}

		var i;
		var cell_index = cell_obj.cell_index;


		// determine sort order
		if (this.last_sorted != null && this.last_sorted == cell_index &&
			String(this.sort_img.getAttribute('src')).indexOf('asc') >= 0) {

			var sort_order = 'desc';
		} else {
			var sort_order = 'asc';
		}

		this.sort_img.setAttribute('src', this.get_sort_img_src(sort_order));

		// move sort image into the right column
		this.move_sort_image(cell_obj);


		// get values of column cells
		var cell_values = new Array();
		// rows in the order desired
		var rows_sorted = new Array();

		var rows = this.get_rows();
		var rows_len = rows.length;

		for (i=0; i<rows_len; i++) {
			rows_sorted[i] = rows[i];

			var cur_cell_obj = rows[i].cells[cell_index];
			var sc = SmartCells.get_cell(cur_cell_obj);
			cell_values[rows[i].getAttribute('id')] = sc.get_sort_value();
		}

		var sc_column = this.columns[cell_index+this.frozen_col_num]['cell'];

		// choose compare function necessary
		switch (sc_column.sort_type) {
			case 'number':
				var cmp_function = function(a, b) {
					var a_val = Number(cell_values[a.getAttribute('id')]);
					var b_val = Number(cell_values[b.getAttribute('id')]);

					if (isNaN(a_val) && isNaN(b_val)) return 0;

//alert(a.getAttribute('id') + ' => ' + b.getAttribute('id') + ' | ' + a_val + ' => ' + b_val);
					if (sort_order == 'asc') {
						return isNaN(b_val)||a_val>b_val?1:-1;
					} else {
						return isNaN(a_val)||b_val>a_val?1:-1;
					}
				};
				break;
			case 'date':
				var cmp_function = function(a, b) {
					var a_val = Number(cell_values[a.getAttribute('id')]);
					var b_val = Number(cell_values[b.getAttribute('id')]);

					if (sort_order == 'asc') {
						return a_val-b_val;
					} else {
						return b_val-a_val;
					}
				};
				break;
			case 'string':
			case 'time':
				var cmp_function = function(a, b) {
					var a_val = cell_values[a.getAttribute('id')];
					var b_val = cell_values[b.getAttribute('id')];

					if (a_val == b_val) return 0;

					if (sort_order == 'asc') {
//alert(cell_values[a.getAttribute('id')] + ' > ' + cell_values[b.getAttribute('id')] + ' = ' + (cell_values[a.getAttribute('id')]>cell_values[b.getAttribute('id')]?1:-1));
						return a_val>b_val?1:-1;
					} else {
						return a_val>b_val?-1:1;
					}
				};
				break;
			default:
				break;
		}

		rows_sorted.sort(cmp_function);


		// if there are frozen rows then sort the both of the grids
		if (this.frozen_grid || this.common_grid) {
			if (this.frozen_grid) {
				var grid = this.frozen_grid;
			} else {
				var grid = this.common_grid;
			}

			var rows2 = grid.get_rows();

			// hide sorting image of this grid
			grid.sort_img.style.display = 'none';
		} else {
			var rows2 = null;
		}


		for (i=0; i<rows_len; i++) {
			if (rows_sorted[i].getAttribute('id') != rows[i].getAttribute('id')) {

				// sort another grid if any
				if (rows2) {
					// find row with the same position as current in second grid
					if (this.frozen_grid) {
						var reverse = false;
					} else {
						var reverse = true;
					}
					var row_sorted2 = this.get_frozen_row_analog(rows_sorted[i], reverse);
					var row2 = this.get_frozen_row_analog(rows[i], reverse);

					row2.parentNode.insertBefore(row_sorted2, row2);
				}

				// move row on the right position
				rows[i].parentNode.insertBefore(rows_sorted[i], rows[i]);

			}
		}

		this.last_sorted = cell_index;

	};


	// moves sort image in cell cell_obj
	// returns current sort order
	this.move_sort_image = function(cell_obj) {

		this.sort_img.style.display = '';

		// move image in the upper right corner of the appropriate cell
		var cell_pos = get_obj_pos(cell_obj, this.header_div);

		this.sort_img.style.left = cell_pos[0] + cell_obj.offsetWidth - 10 + 'px';
		this.sort_img.style.top = cell_pos[1] + 2 + 'px';

	};


	var header_row = this.header_table.insertRow(0);


	// outer header table
	this.outer_header_table = document.createElement('TABLE');
	this.outer_header_table.border = 0;
	this.outer_header_table.cellSpacing = 0;
	this.outer_header_table.cellPadding = 0;

	var outer_header_row = this.outer_header_table.insertRow(0);
	var outer_header_cell = outer_header_row.insertCell(0);
	var outer_header_cell2 = outer_header_row.insertCell(1);
	outer_header_cell2.innerHTML = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
	outer_header_cell2.className = 'sg_header_right_cell';


	outer_header_cell.appendChild(this.header_table);
	this.header_div.appendChild(this.outer_header_table);
	main_header_cell.appendChild(this.header_div);


	// content div
	this.content_div = document.createElement("DIV");
	this.content_div.style.width = "100%";
//	this.content_div.style.height = this.obj.style.height;
	this.content_div.style.overflow = "auto";
	this.content_div.style.position = "relative"; //!!!!
	this.content_div.className = "sg_content_div";

	var onscroll = function(e){
		var content_div = e.target?e.target:window.event.srcElement;
		var grid = content_div.grid;

		// IE fix: sometimes horizontal scrollbar is shown when not necessary
		if (content_div.offsetWidth == content_div.scrollWidth) {
			content_div.scrollLeft = 0;
		}
		grid.header_div.scrollLeft = content_div.scrollLeft;

		// scroll frozen grid if any
		if (grid.frozen_grid) {
			grid.frozen_grid.content_div.scrollTop = grid.content_div.scrollTop;
		}

		// load new data
		if (grid.dynamic_loading) {
			if (grid.dl_timer) window.clearTimeout(grid.dl_timer);
			grid.dl_timer = window.setTimeout(function(){grid.dl_load_data()}, 500);
		}

	};
	sc_attach_event(this.content_div, 'scroll', onscroll);


	// content table
	this.content_table = document.createElement('TABLE');
	this.content_table.width = '100%';
//this.content_table.border = 1;
	this.content_table.cellSpacing = 0;
	this.content_table.cellPadding = 0;
	this.content_table.className = "sg_content_table";
	this.content_table.style.overflow = "hidden";
	this.content_table.style.tableLayout = "fixed";
//	this.content_table.style.position = "relative"; //!!!!

	this.content_table.onclick = new Function("e","this.grid.row_clicked(e||window.event)");

	this.row_clicked = function(e){
		var src_obj = e.target?e.target:window.event.srcElement;
		var row_obj = get_previous_object(src_obj, 'TR');
		if (!row_obj) return;

		var row_id = row_obj.getAttribute('id');
		if (!row_id || this.is_row_new(row_id)) return;

		var mode = 0;
		if (e.ctrlKey) {
			mode = 1;
		}
		if (e.shiftKey) {
			mode = 2;
		}

		this.parse_row_clicked(row_id, mode);

		if (this.frozen_grid) {
			this.frozen_grid.parse_row_clicked(this.get_frozen_id_attribute(row_id), mode);
		}

		if (this.common_grid) {
			this.common_grid.parse_row_clicked(this.remove_frozen_prefix(row_id), mode);
		}
	};
//	sc_attach_event(this.content_table, 'click', onclick);


	this.content_div.appendChild(this.content_table);
	main_content_cell.appendChild(this.content_div);


	this.obj.appendChild(this.main_table);


	// store grid object in sub objects
	this.obj.grid = this;
	this.main_table.grid = this;
	this.header_div.grid = this;
	this.header_table.grid = this;
	this.content_div.grid = this;
	this.content_table.grid = this;


	// functions
	this.add_column = function(col_params, cell) {
		var index = this.columns.length;

		this.columns[index] = new Array();
		this.columns[index]['col_name'] = col_params['name'];
		this.columns[index]['col_width'] = col_params['width'];
		this.columns[index]['cell'] = cell;
	}


	// show grid content
	this.show = function() {
		var i;

		// add headers
		var table_width = 0;
		var col_len = this.columns.length;
		for (i=0; i<col_len; i++) {
			// do not add frozen columns to common grid
			if (this.frozen_col_num && i < this.frozen_col_num) continue;

			var new_cell = header_row.insertCell(i-this.frozen_col_num);
			new_cell.innerHTML = this.columns[i]['col_name'];
			new_cell.style.width = this.columns[i]['col_width']+'px';

			// remember cell index
			new_cell.cell_index = i - this.frozen_col_num;

			table_width += Number(this.columns[i]['col_width']);

		}


//		this.header_table.style.width = table_width+'px';


		// add content cells
		var data_len = this.data.length;
		for (i=0; i<data_len; i++) {
			var data_row = this.data[i]['data'];
			var db_row_id = this.data[i]['id'];

			this.add_row(db_row_id, data_row);
		}

		this.content_table.style.width = table_width+'px';



		// fix height of header row (must do this after all objects are appended!
//alert(this.obj.style.height + ' => ' + this.header_table.offsetHeight);
		this.content_div.style.height = parseInt(this.obj.style.height) - this.header_table.offsetHeight + 'px';
//	this.content_div.style.top = this.header_table.offsetHeight - this.header_div.offsetHeight + 'px';


		if (this.page_mode) {
			this.show_paging();
		}

		// show add and delete buttons
		if (this.show_actions) {
			this.show_actions();
		}

		if (this.data_url) {
			this.load_xml(this.data_url);
		}

	}


	// add new row
	// add after row prev_row if specified
	this.add_row = function(db_row_id, data_row, prev_row) {

		if ((this.dynamic_loading ||
		this.common_grid && this.common_grid.dynamic_loading) && prev_row) {
			var data_tr = document.createElement('TR');
			prev_row.parentNode.insertBefore(data_tr, prev_row.nextSibling);
		} else {
			if (document.all) {
				var data_tr = this.content_table.insertRow(this.content_table.rows.length);
			} else {
				var data_tr = document.createElement('TR');
				this.content_table.appendChild(data_tr);
			}
		}


//		var row_id = 'sg_row'+db_row_id;
		// frozen grid
		var row_id = this.get_id_attribute(db_row_id);

		// determine height of common grid cells so that frozen ones have the
		// same, as depending on cell types in the grids heights may differ
		if (this.common_grid) {
			var rows = this.common_grid.get_rows();
			var cell_height = rows[0].cells[0].offsetHeight + 'px';
		} else {
			var cell_height = 0;
		}

		data_tr.setAttribute('id', row_id);
		data_tr.grid = this;

		var table_width = 0;
		var col_len = this.columns.length;
		var j;
//alert(col_len + ' => ' + this.columns[0]['cell'].type);
		for (j=0; j<col_len; j++) {
			if (this.frozen_col_num && j < this.frozen_col_num) continue;

			var new_cell = data_tr.insertCell(j-this.frozen_col_num);

			if (cell_height) {
				// fix cell height
				new_cell.style.height = cell_height;
			}

			// make row cell smart
			this.do_smart_cell(new_cell, row_id, j, data_row?data_row[j]:null);
			table_width += Number(this.columns[j]['col_width']);
		}


		// add row to frozen grid
		if (this.frozen_grid) {
			var frozen_prev_row = this.get_frozen_row_analog(prev_row);

if (frozen_prev_row) {
//	show_val('PREV = ' + prev_row.id);
//	show_val('FROZEN PREV = ' + frozen_prev_row.id);
}
			this.frozen_grid.add_row(db_row_id, data_row, frozen_prev_row);
		}

/*
		var rows = this.get_rows();
		var rows_len = rows.length;
		var i;
		var last_spacer = null;
		var spacer_name = this.dl_spacer_id_prefix();
		for (i=0; i<rows_len; i++) {
			var row = rows[i];
			if (row.getAttribute('name') == spacer_name) {
show_val((this.common_grid?'Frozen ':'')+'Spacer = ' + row.id);
			}
		}
*/

		// set tab order for the row smart cells
//		if (!this.common_grid) {
			this.set_smart_row_tab_order(data_tr);
//		}

/*
		// set tab order for the row smart cells
		for (j=0; j<col_len; j++) {
			var cell_obj = data_tr.cells[j];
			var sc = SmartCells.get_cell(cell_obj);

			sc.set_tab_next_cell(data_tr.cells[(j+1)%col_len].getAttribute('id'));
		}
*/

		if (!document.all) {
//			this.content_table.appendChild(data_tr);
		}

		this.content_table.style.width = table_width+'px';

		this.header_div.scrollLeft = this.content_div.scrollLeft;

		return data_tr;
	}


	// returns value of id attribute for db row with ID = id
	this.get_id_attribute = function(db_row_id) {
		return this.get_row_id_prefix() + db_row_id;
	}


	// returns value of id attribute prefix for db row with ID = id
	this.get_row_id_prefix = function() {
		return this.obj.getAttribute('id')+'_sg_row';
	}


	// change row id (need this after a new row added to DB)
	this.change_row_id = function(row_id, new_db_row_id) {
		var i;
		var row_obj = document.getElementById(row_id);
		var new_row_id = this.get_id_attribute(new_db_row_id);

		row_obj.setAttribute('id', new_row_id);

		var row_len = row_obj.cells.length;

		for (i=0; i<row_len; i++) {
			var cell_obj = row_obj.cells[i];
			var sc = SmartCells.get_cell(cell_obj);
			sc.params['row_id'] = new_row_id;
		}

		// change selection entry corresponding to the row
		if (this.selected_rows[row_id]) {
			this.selected_rows[row_id] = false;
			this.selected_rows[new_row_id] = true;
		}


		return new_row_id;
	}

	// add data row
	this.add_data_row = function(id, row, index) {
		if (typeof(index) == 'undefined') {
			var index = this.data.length;
		}
		this.data[index] = new Array();
		this.data[index]['id'] = id;
		this.data[index]['data'] = row;
	}


	// add data rows
	this.add_data_rows = function(rows) {
		var i;
		for (i in rows) {
			this.add_data_row(i, rows[i]);
		}
	}

	// parse row click to change row selection
	this.parse_row_clicked = function(row_id, mode) {

		switch (mode) {
			// simple click
			case 0:
				this.unselect_rows();

				// select if data of this row is loaded yet
				if (!this.is_smart_row(row_id)) break;

				this.select_row(row_id);
				this.last_selected_row_id = row_id;
				break;
			// Ctrl+click
			case 1:
				// select if data of this row is loaded yet
				if (!this.is_smart_row(row_id)) break;

				if (this.selected_rows[row_id]) {
					this.unselect_row(row_id);
				} else {
					this.select_row(row_id);
				}
				this.last_selected_row_id = row_id;
				break;
			// Shft+click
			case 2:
				var src_index = this.get_row_index(this.last_selected_row_id);
				var dest_index = this.get_row_index(row_id);

				if (src_index >= 0) {
					// unselect_rows() erase this.last_selcted_row_id
					var last_selected = this.last_selected_row_id;
					this.unselect_rows();
					this.select_row(last_selected);
					this.last_selected_row_id = last_selected;
				}

				// just leave this one row selected
				if (src_index < 0 || src_index == dest_index) {
					// select if data of this row is loaded yet
					if (this.is_smart_row(row_id)) {
						this.select_row(row_id);

						// no row is selected yet
						if (src_index < 0) {
							this.last_selected_row_id = row_id;
						}
					}

				} else {

					if (src_index < dest_index) {
						var start_index = src_index;
						var end_index = dest_index;
					} else {
						var start_index = dest_index;
						var end_index = src_index;
					}

/*
					var rows = this.content_table.rows;
					var rows_len = rows.length;
*/

					var rows = this.get_rows();
					var rows_len = rows.length;
    
					var i;
					for (i=start_index; i<=end_index; i++) {
						var cur_row_id = rows[i].getAttribute('id');
						// select if data of this row is loaded yet
						if (this.is_smart_row(cur_row_id)) {
							this.select_row(cur_row_id);
						}
					}

				}

				break;
			default:
				break;
		}

	}

	// unselect all rows selected
	this.unselect_rows = function() {

		var row_id;
		for (row_id in this.selected_rows) {
			this.unselect_row(row_id);
		}

		// clear array of selected rows
		this.selected_rows = new Array();

		this.last_selected_row_id = null;
	}

	// select row with id row_id
	this.select_row = function(row_id) {

		if (this.selected_rows[row_id]) return;

		var row_obj = document.getElementById(row_id);
		row_obj.className += ' sg_row_selected';

		// remember that the row is selected
		this.selected_rows[row_id] = true;

	}

	// unselect row with id row_id
	this.unselect_row = function(row_id) {

		if (!this.selected_rows[row_id]) return;

		var row_obj = document.getElementById(row_id);
		row_obj.className = row_obj.className.replace(/[ ]?sg_row_selected/g,'');

		// clear up the selection flag
		this.selected_rows[row_id] = false;

	}

	// return index of row with id row_id in table
	this.get_row_index = function(row_id) {
	    var i;

		var rows = this.get_rows();
		var rows_len = rows.length;

		for (i=0; i<rows_len; i++) {
			if (rows[i].getAttribute('id') == row_id) return i;
		}

		// not found
		return -1;
	}

	// return data rows
	this.get_rows = function() {
		var rows = this.content_table.rows;

		if (rows.length == 0) return this.content_table.childNodes;
		return rows;
	}

	// delete selected row
	this.delete_selected_rows = function() {

		var row_id;
		for (row_id in this.selected_rows) {
			// switch current cell in default mode if it is in edit on and
			// belongs to the row to be deleted
			if (SmartCells.active_cell &&
				SmartCells.active_cell.params['row_id'] == row_id) {
				SmartCells.active_cell.cancel_edit();
			}

			var row_obj = document.getElementById(row_id);

			if (this.frozen_grid) {
				var frozen_row_obj = this.get_frozen_row_analog(row_obj);
			}


			if (!this.common_grid &&
				(!this.dynamic_loading ||
				this.dl_is_row_smart(this.dl_get_row_index(row_id))) ) {

				// delete Smart cells of the row (from frozen grid also)
				var col_len = this.columns.length;
				var j;
				for (j=0; j<col_len; j++) {
					// these columns are frozen
					if (this.frozen_col_num && j < this.frozen_col_num) {
						var cur_cell = frozen_row_obj.cells[j];
					} else {
						var cur_cell = row_obj.cells[j-this.frozen_col_num];
					}

					var sc = SmartCells.get_cell(cur_cell);
					SmartCells.delete_smart_cell(sc);
				}

			}

			// will work for common grid only as frozen is non dynamic anyway
			if (this.dynamic_loading) {
				var row_index = this.dl_get_row_index(row_id);

				// delete the appropriate element from this.rows
				var i;
				var new_rows = new Array();
				for (i in this.rows) {
					if (i == row_index) continue;

					if (parseInt(i) > parseInt(row_index)) {
						new_rows[i-1] = this.rows[i];
					} else {
						new_rows[i] = this.rows[i];
					}
				}

				this.rows = new_rows;
				// page size changed also
				this.real_page_size--;
			}

			row_obj.parentNode.removeChild(row_obj);
		}

		// clear array of selected rows
		this.selected_rows = new Array();

		this.last_selected_row_id = null;

		// after we deleted rows selected some spacers follow one after other
		// so, join such spacers
		if (this.dynamic_loading ||
				this.common_grid && this.common_grid.dynamic_loading) {
			this.dl_join_spacers();
		}

		// delete selected rows from frozen grid
		if (this.frozen_grid) {
			this.frozen_grid.delete_selected_rows();
		}
	}

	// add rows from an xml document
	this.add_xml_rows = function(xml_data, dl_start_with) {
//clear();
//show_val('Old height: ' + this.content_div.scrollHeight);
/*
if (this.frozen_grid) {
show_val('Frozen Old height: ' + this.frozen_grid.content_div.scrollHeight);
}
*/
		if (this.dl_adding_rows) return;
		this.dl_adding_rows = true;


		var prev_row; // row added last time

		if (typeof(dl_start_with) == 'undefined') {
			dl_start_with = 0;
		}

		if (this.dynamic_loading && this.rows.length == 0) {
			var first_load = true;
		} else {
			var first_load = false;
		}

		try {
			var dom_parser = new DOMParser();
			var xml_doc = dom_parser.parseFromString(xml_data, "text/xml");
			var root_node = xml_doc.documentElement;
//			alert(root_node.tagName);
		} catch(e) {

			var xml_doc = new ActiveXObject("Microsoft.XMLDOM");
			xml_doc.async = false;
			xml_doc.loadXML(xml_data);

			if (xml_doc.parseError.errorCode) {
				alert(xml_doc.parseError.reason);
			} else {
				var root_node = xml_doc.childNodes[0].tagName?xml_doc.childNodes[0]:xml_doc.childNodes[1];
//				alert(root_node.tagName);
			}

		}

		// dl specific vars
		// index of the first row not created yet
		var start_with = -1;
		// number of rows to create
		var rows_num = 0;
		// flag to know that we found all rows to create
		var stop_row_search = false;


		// prepare an array for add_data_rows
		var children = root_node.childNodes;
		var child_len = children.length;

		// rows
		var i;
		// number of actually loaded rows
		var rows_loaded = 0;
		for (i=0; i<child_len; i++) {

			if (children[i].nodeType != 1) continue;

//			alert(children[i].getAttribute('id'));

			// cells
			var cells = children[i].childNodes;
			var cells_len = cells.length;

			var j;
			var row_data = new Array();
			var k = 0;
			for (j=0; j<cells_len; j++) {
				if (cells[j].nodeType != 1) continue;

				row_data[k] = cells[j].firstChild?cells[j].firstChild.data:'';
				k++;
			}

			var id = Number(children[i].getAttribute('id'));

			if (!this.dynamic_loading) {
				this.add_row(id, row_data);
			} else {
				// number of the current row in grid
				var row_index = parseInt(dl_start_with)+rows_loaded;

				// row is loaded yet
				if (this.rows[row_index]) {
					continue;
				}

				// store row data in cache
				this.rows[row_index] = new Array();
				this.rows[row_index]['id'] = id;
				this.rows[row_index]['data'] = row_data;


				var row_exists = this.dl_is_row_exists(row_index);
				if (start_with < 0 && !row_exists) {
					start_with = row_index;
				}

				if (start_with >= 0 && !stop_row_search) {
					if (!row_exists) rows_num++;
						else stop_row_search = true;
				}

			}

			rows_loaded++;
		}


		// add spacer / change existing spacer
		if (this.dynamic_loading && rows_loaded) {

			var real_page_size = root_node.getAttribute('realpagesize');
			if (real_page_size) {
				if (!this.real_page_size) {
					this.real_page_size = real_page_size;
				}
			} else {
				this.real_page_size = this.page_size;
			}


			// store current scroll pos to restore it later
			var scroll_pos = this.content_div.scrollTop;

			// get index of the nearest prev data row
			var prev_row_index = this.dl_get_prev_row_index(start_with);
			var next_row_index = this.dl_get_next_row_index(start_with);

			// there are some rows loaded yet above
			if (prev_row_index >= 0) {
				var prev_row_obj = this.dl_is_row_exists(prev_row_index);

//var rows = this.get_rows();
//var rows_len = rows.length;
//for (var t=0; t<rows_len; t++) alert(t + ' => ' + rows[t].getAttribute('id'));
//alert(rows[rows_len-2].getAttribute('id') + ' |=> ' + rows[rows_len-2].nextSibling.getAttribute('id'));
				// as we insert rows after then next row is a spacer
//alert(prev_row_obj + ' => ' + prev_row_obj.getAttribute('id'));
				var spacer_obj = prev_row_obj.nextSibling;

//alert(spacer_obj + ' => ' + spacer_obj.getAttribute('id'));
//alert('Start = ' + start_with + ' | Prev = ' + prev_row_index);
//show_val('Start = ' + start_with + ' | Prev = ' + prev_row_index);
				var spacer_rows_num = start_with - prev_row_index - 1;

				// get frozen row and spacer
				if (this.frozen_grid) {
					var frozen_prev_row_obj = this.get_frozen_row_analog(prev_row_obj);
					var frozen_spacer_obj = frozen_prev_row_obj.nextSibling;
				}

//show_val('Rows above: ' + spacer_rows_num);
				if (spacer_rows_num > 0) {
					this.dl_change_spacer(spacer_obj, spacer_rows_num);
					prev_row = spacer_obj;

					// change frozen spacer
					if (this.frozen_grid) {
						this.frozen_grid.dl_change_spacer(frozen_spacer_obj, spacer_rows_num);
					}

//show_val('Add after spacer');
				} else {
					spacer_obj.parentNode.removeChild(spacer_obj);
					prev_row = prev_row_obj;

					// remove frozen spacer
					if (this.frozen_grid) {
						frozen_spacer_obj.parentNode.removeChild(frozen_spacer_obj);
					}
				}
			}

			// create rows
			for (i=start_with; i<start_with+rows_num; i++) {

if (prev_row && this.frozen_grid) {
//show_val('Add after new row: ' + prev_row.id + ' => ' + this.get_frozen_row_analog(prev_row).id);
//show_val('Row_id_index = ' + this.get_row_index(prev_row.id));
//show_val('Frozen_row_id_index = ' + this.frozen_grid.get_row_index(this.get_frozen_row_analog(prev_row).id));
}
				prev_row = this.add_row(this.rows[i]['id'],
										this.rows[i]['data'], prev_row);

			}


			// get lastly added frozen row
			if (this.frozen_grid) {
				var frozen_prev_row = this.get_frozen_row_analog(prev_row);
			}


			// there are some rows loaded yet below
			if (next_row_index >= 0) {
				var spacer_rows_num = next_row_index - start_with - rows_loaded;
			} else {
				// add a spacer if there are some rows not loaded below
				var spacer_rows_num = this.real_page_size - start_with - rows_loaded;
			}

			if (spacer_rows_num > 0) {
				var new_spacer = this.dl_add_spacer(prev_row, spacer_rows_num);

				if (this.frozen_grid) {
					this.frozen_grid.dl_add_spacer(frozen_prev_row,
						spacer_rows_num,
						this.get_frozen_prefix()+new_spacer.getAttribute('id'));
				}
			}

/*
			// there are some rows loaded yet below
			if (next_row_index >= 0) {
				var spacer_rows_num = next_row_index - start_with - rows_loaded;
//alert('Rows below: ' + spacer_rows_num);
//show_val('Rows below: ' + spacer_rows_num);
				if (spacer_rows_num > 0) {
					this.dl_add_spacer(prev_row, spacer_rows_num);

					if (this.frozen_grid) {
						this.frozen_grid.dl_add_spacer(frozen_prev_row, spacer_rows_num);
					}
				}
			} else {
				// add a spacer if there are some rows not loaded below
				var spacer_rows_num = this.real_page_size - start_with - rows_loaded;
//alert('!!!!Rows below: ' + spacer_rows_num);
//show_val('!!!!Rows below: ' + spacer_rows_num);
				if (spacer_rows_num) {
					this.dl_add_spacer(prev_row, spacer_rows_num);

					if (this.frozen_grid) {
						this.frozen_grid.dl_add_spacer(frozen_prev_row, spacer_rows_num);
					}
				}
			}
*/

//show_val('New height: ' + this.content_div.scrollHeight);
/*
if (this.frozen_grid) {
show_val('Frozen New height: ' + this.frozen_grid.content_div.scrollHeight);
}
*/

			this.content_div.scrollTop = scroll_pos;

			if (this.frozen_grid) {
				this.frozen_grid.content_div.scrollTop = scroll_pos;
			}

			// first load => need only one spacer after rows created
			// if part of them loaded only
			if (first_load && this.real_page_size > rows_loaded && prev_row) {
//				this.dl_add_spacer(prev_row, this.real_page_size - rows_loaded);
			}

			// make visible rows smart and invisible - common ones
			this.dl_resmart_rows();
		}


		this.dl_adding_rows = false;


		if (!this.dynamic_loading || first_load) {

			// Opera spoils cell widths when change pages - sucs browser!!!
			// switching a cell to edit mode and back fixes this - ugly solution!
			if (this.page_mode && navigator.userAgent.indexOf('Opera') >= 0) {
				var rows = this.get_rows();
				var sc = SmartCells.get_cell(rows[0].cells[0]);
				sc.edit();
				sc.cancel_edit();
//			sc.save();

				// do the same for frozen grid
				if (this.frozen_grid) {
					var sc = SmartCells.get_cell(this.get_frozen_row_analog(rows[0]).cells[0]);
					sc.edit();
					sc.cancel_edit();
				}
			}

		}


		// update paging div if page mode on
		if (this.page_mode) {
			var current_page = root_node.getAttribute('page');
			var page_size = root_node.getAttribute('pagesize');
			var total_pages = root_node.getAttribute('totalpages');

			this.update_paging(current_page, page_size, total_pages);
		}

	}


	// load xml data and add rows to grid
	// dl_start_with - number of row to read data with
	// dl_size - numer of rows to read
	this.load_xml = function(url, dl_start_with, dl_size) {

		// store grid object
		var grid_obj = this;

		// create a new object JSHttpRequest.
		var req = new JsHttpRequest();

		// code executed automatically on loading completed
		req.onreadystatechange = function() {

//alert(req.readyState);
			if (req.readyState == 4) {
				if (req.responseJS) {
					var status = req.responseJS.status;

					if (status != 'ok') {
						// restore old value
						alert(status);
					} else {
//alert(req.responseJS.xml_data);
						grid_obj.add_xml_rows(req.responseJS.xml_data,
											dl_start_with);
					}

				}

		// Debug info
//			document.getElementById('debug').innerHTML = req.responseText;
			}
		}

		var query_params = {};


		// send current page number and page size
		if (this.page_mode) {
			query_params.page = this.current_page;
			query_params.page_size = this.page_size;
			query_params.real_page_size = this.real_page_size;
		}


		// dynamic loading mode
		if (this.dynamic_loading) {
			query_params.dl_start_with = parseInt(dl_start_with);

			var size = parseInt(dl_size);
			var dl_size = parseInt(this.page_size)?Math.min(this.page_size, this.dynamic_loading_size):this.dynamic_loading_size;
			query_params.dl_size = size?size:dl_size;
		}


		// Enable caching
		req.caching = false;
		// Prepare object
		req.open('POST', url, true);
		// Send query data
		req.send(query_params);

	}


	// show paging div
	this.show_paging = function() {
		var i;

		this.paging_div = document.createElement("DIV");
		this.paging_div.style.width = this.obj.style.width;
		this.paging_div.className = "sg_paging_div";

		// paging elements

		// span elements
		this.paging_start_page = document.createElement('A')
		this.paging_start_page.innerHTML = '|&lt;';
		this.paging_start_page.setAttribute('href', '#');
		this.paging_start_page.onclick = new Function("e", "this.grid._change_page(e||window.event, 'first'); return false;");

		this.paging_div.appendChild(this.paging_start_page);

		var space_node = document.createElement('SPAN');
		space_node.innerHTML = '&nbsp;&nbsp';
		this.paging_div.appendChild(space_node);

		this.paging_prev_page = document.createElement('A')
		this.paging_prev_page.innerHTML = '&lt;&lt;';
		this.paging_prev_page.setAttribute('href', '#');
		this.paging_prev_page.onclick = new Function("e", "this.grid._change_page(e||window.event, 'prev'); return false;");

		this.paging_div.appendChild(this.paging_prev_page);

		var space_node = document.createElement('SPAN');
		space_node.innerHTML = '&nbsp;&nbsp';
		this.paging_div.appendChild(space_node);

		this.paging_next_page = document.createElement('A')
		this.paging_next_page.innerHTML = '&gt;&gt;';
		this.paging_next_page.setAttribute('href', '#');
		this.paging_next_page.onclick = new Function("e", "this.grid._change_page(e||window.event, 'next'); return false;");

		this.paging_div.appendChild(this.paging_next_page);

		var space_node = document.createElement('SPAN');
		space_node.innerHTML = '&nbsp;&nbsp';
		this.paging_div.appendChild(space_node);

		this.paging_last_page = document.createElement('A')
		this.paging_last_page.innerHTML = '&gt;|';
		this.paging_last_page.setAttribute('href', '#');
		this.paging_last_page.onclick = new Function("e", "this.grid._change_page(e||window.event, 'last'); return false;");

		this.paging_div.appendChild(this.paging_last_page);

		var space_node = document.createElement('SPAN');
		space_node.innerHTML = '&nbsp;&nbsp';
		this.paging_div.appendChild(space_node);
		// span elements


		this.paging_current_page = document.createElement('SELECT');
		this.paging_current_page.className = 'sg_paging_current_page';
		this.paging_current_page.onchange = new Function("e", "this.grid._change_page(e||window.event, 'current'); return false;");

/*
		for (i=0; i<this.total_pages; i++){
			var option = document.createElement("option");
			option.value = i;
			option.text = i;

			if (document.all) {
				this.paging_current_page.add(option);
			} else {
				this.paging_current_page.appendChild(option);
			}
		}
		this.paging_current_page.value = this.current_page;
*/

		this.paging_div.appendChild(this.paging_current_page);


		var space_node = document.createElement('SPAN');
		space_node.innerHTML = '&nbsp;&nbsp';
		this.paging_div.appendChild(space_node);


		this.paging_page_size = document.createElement('SELECT');
		this.paging_page_size.className = 'sg_paging_page_size';
		this.paging_page_size.onchange = new Function("e", "this.grid.change_pagesize(e||window.event); return false;");

		for (i in this.page_sizes) {
			var option = document.createElement("option");
			option.value = this.page_sizes[i];
			if (this.page_sizes[i] == 0) {
				option.text = this.get_text_data('all_rows');
			} else {
				option.text = this.get_text_data('rows_per_page') + ' - ' + this.page_sizes[i];
			}

			if (document.all) {
				this.paging_page_size.add(option);
			} else {
				this.paging_page_size.appendChild(option);
			}
		}

		this.paging_div.appendChild(this.paging_page_size);


		// insert paging div
		if (this.obj.nextSibling) {
			this.obj.parentNode.insertBefore(this.paging_div, this.obj.nextSibling);
		} else {
			this.obj.parentNode.appendChild(this.obj.paging_div);
		}


		// store grid object in sub-objects
		this.paging_start_page.grid = this;
		this.paging_prev_page.grid = this;
		this.paging_next_page.grid = this;
		this.paging_last_page.grid = this;
		this.paging_current_page.grid = this;
		this.paging_page_size.grid = this;


	}


	// parse navigation buttons clicks
	this._change_page = function(e, page){

		switch (page) {
			case 'first':
				current_page = 0;
				break;
			case 'prev':
				current_page = this.current_page-1;
				break;
			case 'last':
				current_page = this.total_pages-1;
				break;
			case 'next':
				current_page = parseInt(this.current_page)+1;
				break;
			case 'current':
				var obj = e.target?e.target:window.event.srcElement;
				current_page = obj.options[obj.selectedIndex].value;
				break;
			default:
				current_page = parseInt(page);
				break;
		}

		// invalid page number or this page is shown yet
		if (current_page == this.current_page ||
			current_page < 0 || current_page > this.total_pages-1) {

			return;
		}

		this.change_page(current_page);

	};


	this.change_pagesize = function(e){
		var obj = e.target?e.target:window.event.srcElement;

		this.change_page(this.current_page, obj.options[obj.selectedIndex].value);
	};


	// change page shown in grid
	this.change_page = function(page, page_size) {
		if (!this.page_mode) return;

		// exit edit mode if any cell is active
		if (SmartCells.active_cell) {
			SmartCells.active_cell.cancel_edit();
		}

		if (arguments.length < 2) page_size = this.page_size;

		// unselect selected rows if any
		this.unselect_rows();

		// delete existing rows
		this.delete_rows();

		// clear cache
		this.rows = new Array();

		// clear number of rows to be loaded
		this.real_page_size = 0;

		this.current_page = page;
		this.page_size = page_size;

		// load new page
		this.load_xml(this.data_url);

		// hide sort image and cancel sort settings
		this.sort_img.style.display = 'none';
		this.last_sorted = null;

	};


	// delete all rows from grid
	this.delete_rows = function() {
		var rows = this.get_rows();

		while (rows[0]) {
			rows[0].parentNode.removeChild(rows[0]);
		}

		// delete rows from frozen grid
		if (this.frozen_grid) this.frozen_grid.delete_rows();
	};


	// update paging
	this.update_paging = function(current_page, page_size, total_pages) {
		var i;

		// change current page list
		if (this.total_pages != total_pages) {

			// delete old options
			while(this.paging_current_page.options[0] != null){
				this.paging_current_page.options[0].parentNode.removeChild(this.paging_current_page.options[0]);
			}

			// add new list of pages
			for (i=0; i<total_pages; i++){
				var option = document.createElement("option");
				option.value = i;
				option.text = this.get_text_data('page') + ' - ' + i;

				if (document.all) {
					this.paging_current_page.add(option);
				} else {
					this.paging_current_page.appendChild(option);
				}
			}

		}
		this.paging_current_page.value = current_page;


		// select page size number
		this.paging_page_size.value = page_size;


		// make navigation links active/inactive
		if (current_page == 0)  {
			this.paging_start_page.className = 'sg_inactive';
			this.paging_prev_page.className = 'sg_inactive';
		} else {
			this.paging_start_page.className = '';
			this.paging_prev_page.className = '';
		}

		if (current_page == total_pages-1)  {
			this.paging_last_page.className = 'sg_inactive';
			this.paging_next_page.className = 'sg_inactive';
		} else {
			this.paging_last_page.className = '';
			this.paging_next_page.className = '';
		}


		this.current_page = current_page;
		this.page_size = page_size;
		this.total_pages = total_pages;


	}


	// returns text required in current language
	this.get_text_data = function(text) {
		if (!sg_lang_data[this.language]) {
			var lang = this.default_language;
		} else {
			var lang = this.language;
		}

		if (!sg_lang_data[lang]) return ''; // no data found

		return sg_lang_data[lang][text]?sg_lang_data[lang][text]:'';
	}


	// changes grid layout
	this.change_skin = function(skin) {
		if (skin != '') skin = '_' + skin;

		this.obj.className = 'sg_main_div' + skin;
		if (this.paging_div) {
			this.paging_div.className = 'sg_paging_div' + skin;
		}
		if (this.actions_div) {
			this.actions_div.className = 'sg_actions_div' + skin;
		}

	}


	// show actions div
	this.show_actions = function() {

		if (!this.actions_url) return;

		// do not permit to add if no permission to update
		var can_add = this.add_allowed && this.update_allowed;
		var can_delete = this.delete_allowed;

		if (!can_add && !can_delete) return;

		this.actions_div = document.createElement("DIV");
		this.actions_div.style.width = this.obj.style.width;
		this.actions_div.className = "sg_actions_div";

		// buttons

		if (can_add) {

			// add button
			this.actions_add_button = document.createElement('INPUT')
			this.actions_add_button.setAttribute('type', 'button');
			this.actions_add_button.value = this.get_text_data('add_new_row');
			this.actions_add_button.onclick = new Function("e", "this.grid.actions_add_new_row(e||window.event); return false;");

			this.actions_div.appendChild(this.actions_add_button);

			var space_node = document.createElement('SPAN');
			space_node.innerHTML = '&nbsp;&nbsp';
			this.actions_div.appendChild(space_node);

		}

		if (can_delete) {

			// delete button
			this.actions_delete_button = document.createElement('INPUT')
			this.actions_delete_button.setAttribute('type', 'button');
			this.actions_delete_button.value = this.get_text_data('delete_selected_rows');
			this.actions_delete_button.onclick = new Function("e", "this.grid.actions_delete_selected_rows(e||window.event); return false;");

			this.actions_div.appendChild(this.actions_delete_button);

		}

		// insert the actions grid after paging one if exist
		var prev_obj = this.paging_div?this.paging_div:this.obj;

		// insert paging div
		if (prev_obj.nextSibling) {
//alert(prev_obj.className);
			prev_obj.parentNode.insertBefore(this.actions_div, prev_obj.nextSibling);
		} else {
//			prev_obj.parentNode.appendChild(this.actions_div);
		}


		// store grid object in sub-objects
		if (this.actions_add_button) this.actions_add_button.grid = this;
		if (this.actions_delete_button) this.actions_delete_button.grid = this;

	}

	// adds a new row (potential new db record)
	this.actions_add_new_row = function(event) {

		if (!this.actions_url) return;

		// do not permit to add if no permission to update
		var can_add = this.add_allowed && this.update_allowed;

		if (!can_add) return;

		// exit edit mode if any cell is active
		if (SmartCells.active_cell) {
			SmartCells.active_cell.cancel_edit();
		}

		var db_row_id = this.get_new_row_id_prefix()+(new Date().getTime());

		var new_row = this.add_row(db_row_id);

		if (smart_grid.frozen_grid) {
			var new_row = this.get_frozen_row_analog(new_row);
		}

		// scroll to the very bottom of the grid
		this.content_div.scrollTop = this.content_div.scrollHeight;
		if (this.frozen_grid) {
			this.frozen_grid.content_div.scrollTop = this.content_div.scrollTop;
		}

		// switch first cell in edit mode
		var cell_obj = new_row.cells[0];
		if (!cell_obj) return;
		var sc = SmartCells.get_cell(cell_obj);

		if (SmartCells.active_cell) SmartCells.active_cell.save();
		// set current active cell
		SmartCells.active_cell = sc;
		sc.edit();

		if (!document.all) {
//			event.preventDefault();
//			event.stopPropagation();
		}

		event.cancelBubble = true;

		return false;
	}

	// delete selected rows (from db and grid)
	this.actions_delete_selected_rows = function() {

		if (!this.actions_url) return;

		// do not permit to add if no permission to update
		var can_delete = this.delete_allowed;

		if (!can_delete) return;

		var row_id;
		var row_id_list = '';
		for (row_id in this.selected_rows) {
			if (row_id_list != '') row_id_list += '|';
			var row_tr = document.getElementById(row_id);
			if (row_tr) row_id_list += String(row_id).replace(this.get_row_id_prefix(), '');

			// switch current cell in default mode if it is in edit one and
			// belongs to the row to be deleted
			if (SmartCells.active_cell &&
				SmartCells.active_cell.params['row_id'] == row_id) {
				SmartCells.active_cell.cancel_edit();
			}

		}

		// no rows selected
		if (row_id_list == '') return;

		var grid = this;

		// create a new object JSHttpRequest.
		var req = new JsHttpRequest();

		// code executed automatically on loading completed
		req.onreadystatechange = function() {

			if (req.readyState == 4) {
				if (req.responseJS) {
					var status = req.responseJS.status;

					if (status != 'ok') {
						// restore old value
						alert(status);
					} else {

						grid.delete_selected_rows();

						// redraw current page
						if (smart_grid.page_mode && grid.page_size > 0) {
							grid.change_page(grid.current_page);
						}

					}

				}

		// Debug info
//			document.getElementById('debug').innerHTML = req.responseText;
			}
		}

		var query_params = {};

		// submit cell params
		for (i=0; i<this.columns.length; i++) {
			var column = this.columns[i]['cell'];

			if (column.type == 'image') {
				query_params['folder'+i] = column.folder;
			}

		}

		// Enable caching
		req.caching = false;
		// Prepare object
		req.open('POST', this.actions_url+'?action=delete&row_id_list='+row_id_list, true);
		// Send query data
		req.send(query_params);
	}

	// called on save cell event and saves cell's row
	// called as method of a Smart Cell!
	this.on_save_cell = function() {

		var sc_obj = this;
		var grid = this.obj.grid;
		if (grid.common_grid) grid = grid.common_grid;

		if (!grid.actions_url) return;

		// do not permit to add if no permission to update
		var can_update = grid.update_allowed;

		if (!can_update) {
			this.cancel_edit();
			return;
		}


		var row_id = this.params['row_id'];
		if (grid.remove_frozen_prefix) row_id = grid.remove_frozen_prefix(row_id);

		if (!grid.is_row_new(row_id)) {
			var action = '';
		} else {
			var action = '?action=add';
		}


		var old_value = this.value;
		var new_value = sc_obj.get_current_value();    

		// create a new object JSHttpRequest.
		var req = new JsHttpRequest();

		// code executed automatically on loading completed
		req.onreadystatechange = function() {
			var value = old_value;
			if (req.readyState == 4) {
				if (req.responseJS) {
					var status = req.responseJS.status;

					if (status != 'ok') {
						// restore old value
						alert(status);
					} else {

						var value = new_value;

						// function called if modification was successfull
						// non db field cells update data with some delay
						// (eg after uploading => they must call this function
						// themselves when finish uploading
						if (sc_obj.params['db_field'] == '') {
							sc_obj.post_modify_func = new Function();
						} else {
							sc_obj.post_modify_func();
						}

						var new_db_row_id = req.responseJS.new_row_id;
						// just added a new row successfully => set valid row id
						if (new_db_row_id) {
							var new_row_id = grid.change_row_id(row_id, new_db_row_id);

							// update id of the appropriate frozen row
							if (smart_grid.frozen_grid) {
								var frozen_row_id = grid.get_frozen_id_attribute(row_id);
								var frozen_new_row_id = grid.frozen_grid.change_row_id(frozen_row_id, new_db_row_id);
							}

							if (grid.page_mode && grid.page_size > 0) {
								// redraw current page instantly for db field cells
								// only, non db field cells will redraw it themselves
								// after uploading is finished
								if (sc_obj.params['db_field'] == '') {
									// Ajax.js trows an exception in Firefox if run
									// change_page without a delay
									sc_obj.post_modify_func = function(){setTimeout(function(){grid.change_page(grid.current_page);}, 10);};
//									sc_obj.post_modify_func = function(){setTimeout('document.getElementById("'+sc_obj.obj.id+'").grid.change_page(document.getElementById("'+sc_obj.obj.id+'").grid.current_page);', 10);};
//							sc_obj.post_modify_func = function(){smart_grid.change_page(smart_grid.current_page);};
									} else {
										grid.change_page(grid.current_page);
										return;
									}
//							smart_grid.change_page(smart_grid.current_page);
							} else {
/*
								// some values could be set by default by DB =>
								// update all row cells if page is not redrawn
								var row_obj = document.getElementById(new_row_id);
								var row_cells = row_obj.cells;
								if (grid.frozen_grid) {
									var frozen_row_obj = document.getElementById(frozen_new_row_id);
									var frozen_row_cells = frozen_row_obj.cells;
								}
								var i;
								for (i=0; i<grid.columns.length; i++) {
									if (grid.frozen_grid &&
											i < grid.frozen_col_num) {
    
										var cur_row_cells = frozen_row_cells;
										var decr = 0;
									} else {
										var cur_row_cells = row_cells;
										var decr = grid.frozen_col_num;
									}

									var sc = SmartCells.get_cell(cur_row_cells[i-decr]);

									if (sc.obj.id == sc_obj.id ||
										sc.params['db_field'] == '') {
    
										continue;
									}

//alert(eval('req.responseJS.cell_value'+i));
									var cell_index = i;
									sc.set_value(eval('req.responseJS.cell_value'+cell_index));
									sc.show_value();
								}
*/    
							}

						} else {
							// some values could be changed by DB, eg dates
							if (sc_obj.params['db_field'] != '') {
								value = eval('req.responseJS.cell_value'+(sc_obj.obj.cell_index+sc_obj.obj.grid.frozen_col_num));
							}
						}

//alert(eval('req.responseJS.cell_value'+sc_obj.obj.cell_index));
						// for cells that do not correspond to db table fields
						// (eg images) run _save method anyway
						if (sc_obj.params['db_field'] == '') {
							if (new_db_row_id) {
								value = new_db_row_id;
								sc_obj.set_value(value);
							}
							sc_obj._save();
						}
    
					}
    
				}

				// update cell content
				if (sc_obj.params['db_field'] != '') {
//					sc_obj.set_value(value);
					sc_obj.set_value(new_value);
					sc_obj.show_value();
				}

			// Debug info
//			document.getElementById('debug').innerHTML = req.responseText;
			}
		}

		var query_params = this.query_params;
//		query_params.value = new_value;

		// add cell params
		var i;
		for (i in this.params) {
			query_params[i] = this.params[i];
		}


		var row_obj = document.getElementById(row_id);
		var row_cells = row_obj.cells;

		if (grid.frozen_grid) {
			var frozen_row_id = grid.get_frozen_id_attribute(row_id);
			var frozen_row_obj = document.getElementById(frozen_row_id);
			var frozen_row_cells = frozen_row_obj.cells;
		}

		for (i=0; i<grid.columns.length; i++) {
			if (grid.frozen_grid &&
				i < grid.frozen_col_num) {
    
				var cur_row_cells = frozen_row_cells;
				var decr = 0;
			} else {
				var cur_row_cells = row_cells;
				var decr = grid.frozen_col_num;
			}
			var cell_obj = cur_row_cells[i-decr];
			var sc = SmartCells.get_cell(cell_obj);
//alert(sc.get_current_value());
			query_params[sc.params['db_field']] = sc.get_current_value();
//alert(sc.params['db_field']);
		}

		var row_tr = document.getElementById(row_id);
		query_params['row_id'] = String(row_id).replace(row_tr.grid.get_row_id_prefix(), '');

		// Enable caching
		req.caching = false;
		// Prepare object
		req.open('POST', grid.actions_url+action, true);
//alert(grid.actions_url+action);
		// Send query data
		req.send(query_params);

	}


	// returns prefix used for new row id
	this.get_new_row_id_prefix = function() {
		return 'sg_new_row';
	}


	// return true if row is new (not saved yet)
	this.is_row_new = function(row_id) {
		var re = new RegExp(this.get_new_row_id_prefix(), '');
		return re.test(row_id);
	}


	// make a cell Smart
	this.do_smart_cell = function(cell_obj, row_id, col_num, cell_data) {

		// make frozen row id differ from the appropriate row of the common grid
		if (this.common_grid) {
			var cell_row_id = this.get_frozen_id_attribute(row_id);
		} else {
			var cell_row_id = row_id;
		}

		cell_obj.setAttribute('id', 'sg_cell'+cell_row_id+'_'+col_num);
		cell_obj.style.width = this.columns[col_num]['col_width']+'px';
		cell_obj.cell_index = col_num - this.frozen_col_num;
		cell_obj.grid = this;


		var sc = this.columns[col_num]['cell'].clone();


		sc.obj = cell_obj;
		if (cell_data) {
			sc.set_value(cell_data);
		} else {
			sc.set_value(sc.default_value);
		}

		// always store id of common grid row
		sc.params['row_id'] = row_id;

		if (this.common_grid) {
			sc.user_save = this.common_grid.on_save_cell;
		} else {
			sc.user_save = this.on_save_cell;
		}

		// do not save anything on Tab key pressed to allow to modify all
		// cells before saving the whole row
		sc.save_on_tab = false;

		// for image cells set initial max size
		if (sc.type == 'image') {
			sc.default_max_size[0] = this.columns[col_num]['col_width'];
			if (this.row_height) sc.default_max_size[1] = this.row_height;
		}

		sc.show_value();
		SmartCells.add_smart_cell(sc);

	}


	// set tab order for the row smart cells
	this.set_smart_row_tab_order = function(data_tr) {
		var j;
		var col_len = data_tr.cells.length;

		for (j=0; j<col_len; j++) {
			var cell_obj = data_tr.cells[j];
			var sc = SmartCells.get_cell(cell_obj);

			sc.set_tab_next_cell(data_tr.cells[(j+1)%col_len].getAttribute('id'));
		}

		// change tab order for last cells in common and frozen grids
		// so that the both of the grids are connected
		if (this.frozen_grid) {
			var frozen_data_tr = this.get_frozen_row_analog(data_tr);
//clear();
//show_val('frozen_data_tr' + frozen_data_tr);
			var frozen_col_len = frozen_data_tr.cells.length;

			var first_cell = data_tr.cells[0];
			var frozen_first_cell = frozen_data_tr.cells[0];
			var last_cell = data_tr.cells[col_len-1];
			var frozen_last_cell = frozen_data_tr.cells[frozen_col_len-1];

			var sc = SmartCells.get_cell(last_cell);
			sc.set_tab_next_cell(frozen_first_cell.getAttribute('id'));
			var sc = SmartCells.get_cell(frozen_last_cell);
			sc.set_tab_next_cell(first_cell.getAttribute('id'));
		}
	}

	// check if row is smart one (not a not-saved new row or a spacer)
	this.is_smart_row = function(row_id) {
		var re = new RegExp(this.get_row_id_prefix(), '');
		return re.test(row_id);
	}

}

// clone object obj
function clone(obj) {
	if (typeof(obj) != 'object') {
		return obj;
	}

	if(obj == null) return obj;

	if (obj instanceof Array) {
		var new_obj = new Array();
	} else {
		var new_obj = new Object();
	}

	for (var i in obj) {
//alert(i + ' => ' + typeof(obj[i]));
		new_obj[i] = clone(obj[i]);
	}

	return new_obj;
}

// Smart Grid lang data

var sg_lang_data = new Array();

	sg_lang_data['en'] = {
'page' : 'Page',
'rows_per_page' : 'Rows per page',
'all_rows' : 'All rows',
'dynamic_loading' : 'Cannot sort if dynamic loading on!',
'add_new_row' : 'add new row',
'delete_selected_rows' : 'delete selected rows'
	};

	sg_lang_data['ru'] = {
'page' : 'Страница',
'rows_per_page' : 'Строк на странице',
'all_rows' : 'Все строки',
'dynamic_loading' : 'Сортировка недоступна в режиме динамической загрузки!',
'add_new_row' : 'вставить новую строку',
'delete_selected_rows' : 'удалить выделенные строки'
	};

// Smart Grid lang data