%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/3163975/root/home/infra/glpiinventory/lib/REDIPS_drag/
Upload File :
Create Path :
Current File : //proc/3163975/root/home/infra/glpiinventory/lib/REDIPS_drag/redips-drag-source.js

/*
Copyright (c) 2008-2011, www.redips.net All rights reserved.
Code licensed under the BSD License: http://www.redips.net/license/
http://www.redips.net/javascript/drag-and-drop-table-content/
Version 5.0.5
Dec 27, 2012.
*/

/*jslint white: true, browser: true, undef: true, nomen: true, eqeqeq: true, plusplus: false, bitwise: true, regexp: true, strict: true, newcap: true, immed: true, maxerr: 14 */
/*global window: false */

/* reveal module-pattern */

/* enable strict mode */
"use strict";


/**
 * @name REDIPS
 * @description create REDIPS namespace (if is not already defined in another REDIPS package)
 */
var REDIPS = REDIPS || {};


/**
 * @namespace
 * @description REDIPS.drag is a JavaScript drag and drop library focused on dragging table content (DIV elements) and table rows.
 * @name REDIPS.drag
 * @author darko.bunic@google.com (Darko Bunic)
 * @see
 * <a href="http://www.redips.net/javascript/redips-drag-documentation-appendix-a/" title="REDIPS.drag documentation - Appendix A">REDIPS.drag documentation - Appendix A</a>
 *  
 * <a href="http://www.redips.net/javascript/drag-and-drop-table-content-animation/">Drag and drop table content plus animation</a>
 * <a href="http://www.redips.net/javascript/drag-and-drop-table-row/">Drag and drop table rows</a>
 * <a href="http://www.redips.net/javascript/drag-and-drop-table-content/">Drag and Drop table content</a>
 * <a href="http://www.redips.net/javascript/drag-and-drop-content-shift/">JavaScript drag and drop plus content shift</a>
 * @version 5.0.5 (2012-12-27)
 */
REDIPS.drag = (function () {
		// methods
	var	init,						// initialization
		initTables,					// table initialization
		enableDrag,					// method attaches / detaches onmousedown and onscroll event handlers for DIV elements
		enableTable,				// method enables / disables tables (selected by className) to accept elements 
		imgOnMouseMove,				// needed to set onmousemove event handler for images
		handlerOnMouseDown,			// onmousedown handler
		handlerOnDblClick,			// ondblclick handler (calls public event.dblClicked)
		tableTop,					// set current table group in "tables" array to the array top
		handlerOnMouseUp,			// onmouseup handler
		handlerOnMouseMove,			// onmousemove handler for the document level
		elementDrop,				// drop element to the table cell
		elementDeleted,				// actions needed after element is deleted (call event handler, updatig, climit1_X or climit2_X classnames, content shifting ...)
		resetStyles,				// reset object styles after element is dropped
		registerEvents,				// register event listeners for DIV element
		cellChanged,				// private method called from handlerOnMouseMove(), autoScrollX(), autoScrollY()
		handlerOnResize,			// onresize window event handler
		setTableRowColumn,			// function sets current table, row and cell
		setPosition,				// function sets color for the current table cell and remembers previous position and color
		setTdStyle,					// method sets background color and border styles for TD
		getTdStyle,					// method returns object containing background color and border styles for TD
		boxOffset,					// calculates object (box) offset (top, right, bottom, left)
		calculateCells,				// calculates table columns and row offsets (cells dimensions)
		getScrollPosition,			// returns scroll positions in array
		autoScrollX,				// horizontal auto scroll function
		autoScrollY,				// vertical auto scroll function
		cloneObject,				// clone object (DIV element)
		copyProperties,				// method copies custom properties from source element to the cloned element.
		cloneLimit,					// clone limit (after cloning object, take care about climit1_X or climit2_X classnames)
		elementControl,				// method returns true or false if element needs to have control
		getStyle,					// method returns style value of requested object and style name
		findParent,					// method returns a reference of the required parent element
		findCell,					// method returns first or last cell: rowIndex, cellIndex and cell reference (input is "first" or "last" parameter and table or object within table)
		saveContent,				// scan tables, prepare query string and sent to the multiple-parameters.php
		relocate,					// relocate objects from source cell to the target cell (source and target cells are input parameters)
		emptyCell,					// method removes elements from table cell
		shiftCells,					// method shifts table content to the left or right (useful for content where the order should be preserved)
		cellList,					// method returns cell list with new coordinates (it takes care about rowspan/colspan cells) 
		maxCols,					// method returns maximum number of columns in a table
		moveObject,					// method moves object to the destination table, row and cell
		deleteObject,				// method deletes DIV element
		animateObject,				// object animation
		getTableIndex,				// find table index - because tables array is sorted on every element click
		getPosition,				// returns position in format: tableIndex, rowIndex and cellIndex (input parameter is optional)
		rowOpacity,					// method sets opacity to table row (el, opacity, color)
		rowEmpty,					// method marks selected row as empty (input parameters are table index and row index)
		rowClone,					// clone table row - input parameter is DIV with class name "row" -> DIV class="drag row"
		rowDrop,					// function drops (delete old & insert new) table row (input parameters are current table and row)
		formElements,				// set form values in cloned row (to prevent reset values of form elements)
		normalize,					// private method returns normalized spaces from input string
		hasChilds,					// private method (returns true if element contains child nodes with nodeType === 1)
	
		// private parameters
		objMargin = null,			// space from clicked point to the object bounds (top, right, bottom, left)
		
		// window width and height (parameters are set in onload and onresize event handler)
		// just for a note: window and Window is reserved word in JS so I named variable "screen")
		screen = {width: 0,
				height: 0},

		// define scroll object with contained properties (this is private property)
		scrollData = {width : null,	// scroll width of the window (it is usually greater then window)
				height : null,		// scroll height of the window (it is usually greater then window)
				container : [],		// scrollable container areas (contains autoscroll areas, reference to the container and scroll direction)
				obj : null},		// scroll object (needed in autoscroll for recursive calls)
		
		edge = {page: {x: 0, y: 0}, // autoscroll bound values for page and div as scrollable container
				div:  {x: 0, y: 0},	// closer to the edge, faster scrolling
				flag: {x: 0, y: 0}},// flags are needed to prevent multiple calls of autoScrollX and autoScrollY from onmousemove event handler

		bgStyleOld,				// (object) old td styles (background color and border styles)

		tables = [],				// table offsets and row offsets (initialized in onload event)
		sortIdx,					// sort index needed for sorting tables in tableTop()
		moved,						// (boolean) true if element is moved
		cloned,						// (boolean) true if element is cloned
		clonedId = [],				// needed for increment ID of cloned elements
		currentCell = [],			// current cell bounds (top, right, bottom, left) and "containTable" flag for nested tables
		dragContainer = null,		// drag container reference
		divBox = null,				// div drag box: top, right, bottom and left margin (decrease number calls of setTableRowColumn)
		pointer = {x: 0, y: 0},		// mouse pointer position (this properties are set in handlerOnMouseMove() - needed for autoscroll)
		threshold = {x: 0,			// initial x, y position of mouse pointer
					y: 0,
					value: 7,		// threshold distance value
					flag: false},	// threshold flag
		shiftKey = false,			// (boolean) true if shift key is pressed (set in handler_mousedown)
		cloneClass = false,		// (boolean) true if clicked element contains clone in class name (set in handler_mousedown)
		animationCounter = [],		// (array) counter of animated elements to be shifted before table should be enabled
		
		// selected, previous and source table, row and cell (private parameters too)
		table = null,
		table_old = null,
		table_source = null,
		row = null,
		row_old = null,
		row_source = null,
		cell = null,
		cell_old = null,
		cell_source = null,
		
		// variables in the private scope revealed as public (see init() method)
		obj = false,				// (object) moved object
		objOld = false,				// (object) previously moved object (before clicked or cloned)
		mode = 'cell',				// (string) drag mode: "cell" or "row" (default is cell)
		// (object) defines color and border styles for current TD and TR
		// hover.borderTr defines border color used in "row" mode to show whether row will be dropped above or below current row
		// borderTd and borderTr are initially undefined
		hover = {colorTd: '#E7AB83',
				colorTr: '#E7AB83'},
		scroll = {enable : true,	// (boolean) enable/disable autoscroll function (default is true) 
				bound : 25,			// (integer) bound width for autoscroll
				speed : 20},		// (integer) scroll speed in milliseconds
		only = {div: [],			// (array) DIVid -> classname, defined DIV elements can be placed only to the marked table cell with class name 'only'
				cname: 'only',		// (string) class name for marked cells (default is 'only') - only defined objects can be placed there
				other: 'deny'},		// (string) allow / deny dropping marked objects with "only" to other cells
		mark = {action: 'deny',
				cname: 'mark',
				exception: []},
		style = {borderEnabled : 'solid',	// (string) border style for enabled elements
				borderDisabled : 'dotted',	// (string) border style for disabled elements
				opacityDisabled : '',		// (integer) set opacity for disabled elements
				rowEmptyColor : 'white'},	// (string) color of empty row
		tableSort = true,				// (boolean) sort tables on DIV element click
		trash = {className : 'trash',	// (object) contains trash class name and confirmation questions for delete DIV or ROW	
				question : null,
				questionRow : null},
		saveParamName = 'p',			// (string) save content parameter name
		dropMode = 'multiple',			// (string) dropMode has the following options: multiple, single, switch, switching and overwrite
		multipleDrop = 'bottom',		// (string) defines position of dropped element in case of 'multiple' drop option
		td = {},						// (object) contains reference to source (set in onmousedown), current (set in onmousemove and autoscroll), previous (set in onmousemove and autoscroll) and target cell (set in onmouseup)
		animation = {pause : 20,		// (object) animation pause (integer), step (integer) and shift (boolean)
					step: 2,
					shift: false},
		// (object)
		shift = {mode : 'horizontal1',	// shift modes (horizontal1, horizontal2, vertical1, vertical2) 
				after : 'default',		// how to shift elements (always, if DIV element is dropped to the empty cell as well or if DIV element is deleted)
				overflow : 'bunch'},	// what to do with overflowed DIV (bunch, delete, source)
		clone = {keyDiv : false,		// (boolean) if true, elements could be cloned with pressed SHIFT key
				keyRow : false,			// (boolean) if true, rows could be cloned with pressed SHIFT key
				sendBack : false,		// (boolean) if true, then cloned element can be returned to its source
				drop : false},			// (boolean) if true, then cloned element will be always dropped to the table no matter if dropped outside of the table
		rowDropMode = 'before',			// (string) drop row before or after highlighted row (values are "before" or "after")
		// (object) event handlers
		event = {changed : function () {},
				clicked : function () {},
				cloned : function () {},
				clonedDropped : function () {},
				clonedEnd1 : function () {},
				clonedEnd2 : function () {},
				dblClicked : function () {},
				deleted : function () {},
				dropped : function () {},
				droppedBefore : function () {},
				finish : function () {},
				moved : function () {},
				notCloned : function () {},
				notMoved : function () {},
				shiftOverflow: function () {},
				relocateBefore : function () {},
				relocateAfter : function () {},
				relocateEnd : function () {},
				rowChanged : function () {},
				rowClicked : function () {},
				rowCloned : function () {},
				rowDeleted : function () {},
				rowDropped : function () {},
				rowDroppedBefore : function () {},
				rowDroppedSource : function () {},
				rowMoved : function () {},
				rowNotCloned : function () {},
				rowNotMoved : function () {},
				rowUndeleted : function () {},
				switched : function () {},
				undeleted : function () {}};


	/**
	 * Drag container initialization. It should be called at least once and it's possible to call a method many times.
	 * Every page should have at least one drag container.
	 * If REDIPS.drag.init() is called without input parameter, library will search for drag container with id="drag".
	 * Only tables inside drag container will be scanned. It is possible to have several drag containers totaly separated (elements from one container will not be visible to other drag containers).
	 * "init" method calls initTables and enableDrag.
	 * @param {String} [dc] Drag container Id (default is "drag").
	 * @example
	 * // init drag container (with default id="drag")
	 * REDIPS.drag.init();
	 *  
	 * // init drag container with id="drag1"
	 * REDIPS.drag.init('drag1');
	 * @public
	 * @see <a href="#initTables">initTables</a>
	 * @see <a href="#enableDrag">enableDrag</a>
	 * @function
	 * @name REDIPS.drag#init
	 */
	init = function (dc) {
		// define local variables
		var self = this,		// assign reference to current object to "self"
			i,					// used in local for loops
			imgs,				// collect images inside div=drag
			redipsClone;		// reference to the DIV element needed for cloned elements 
		// if drag container is undefined or input parameter is not a string, then set reference to DIV element with default id="drag"
		if (dc === undefined || typeof(dc) !== 'string') {
			dc = 'drag';
		}
		// set reference to the drag container
		dragContainer = document.getElementById(dc);
		// append DIV id="redips_clone" if DIV doesn't exist (needed for cloning DIV elements)
		// if automatic creation isn't precise enough, user can manually create and place element with id="redips_clone" to prevent window expanding
		// (then this code will be skipped)
		if (!document.getElementById('redips_clone')) {
			redipsClone = document.createElement('div');
			redipsClone.id = 'redips_clone';
			redipsClone.style.width = redipsClone.style.height = '1px';
			dragContainer.appendChild(redipsClone);
		}
		// attach onmousedown event handler to the DIV elements
		// attach onscroll='calculateCells' for DIV elements with 'scroll' in class name (prepare scrollable container areas)
		enableDrag('init');
		// initialize table array
		// here was the following comment: "initTables should go after enableDrag because sca is attached to the table if table belongs to the scrollable container"
		// not sure about order of enableDrag and initTable - needed some further testing
		initTables();
		// set initial window width/height, scroll width/height and define onresize event handler
		// onresize event handler calls calculate columns
		handlerOnResize();
		REDIPS.event.add(window, 'resize', handlerOnResize);
		// collect images inside drag container
		imgs = dragContainer.getElementsByTagName('img');
		// disable onmousemove/ontouchmove event for images to prevent default action of onmousemove event (needed for IE to enable dragging on image)
		for (i = 0; i < imgs.length; i++) {
			REDIPS.event.add(imgs[i], 'mousemove', imgOnMouseMove);
			REDIPS.event.add(imgs[i], 'touchmove', imgOnMouseMove);
		}
		// attach onscroll event to the window (needed for recalculating table cells positions)
		REDIPS.event.add(window, 'scroll', calculateCells);
	};


	/**
	 * Needed to set "false" for onmousemove event on images. This way, images from DIV element will not be enabled for dragging by default.
	 * imgOnMouseMove is attached as handler to all images inside drag container.
	 * Multiple calling of REDIPS.drag.init() will not attach the same event handler to the images.
	 * @private
	 * @memberOf REDIPS.drag#
	 * @see <a href="#init">init</a>
	 */
	imgOnMouseMove = function () {
		return false;
	};


	/**
	 * Tables layout initialization (preparing internal "tables" array).
	 * Method searches for all tables inside defined selectors and prepares "tables" array. Defaule selector is "#drag table".
	 * Tables with className "nolayout" are ignored (e.g. table with "nolayout" class name in DIV element can be dragged as any other content). 
	 * "tables" array is one of the main parts of REDIPS.drag library.
	 * @example
	 * // call initTables after new empty table is added to the div#drag
	 * // REDIPS.init() method will also work but with some overhead 
	 * REDIPS.drag.initTables();
	 *  
	 * // change default selector for table search (div#sticky may contain table that is attached out of defaule drag container)
	 * // this means that table should not be a part of div#drag, but it should be positioned within drag container otherwise dragging will not work
	 * REDIPS.drag.initTables('#drag table, #sticky table');
	 *  
	 * // if new table is not empty and contains DIV elements then they should be enabled also
	 * // DIV elements in "drag" container are enabled by default
	 * REDIPS.drag.initTables('#drag table, #sticky table');
	 * REDIPS.drag.enableDrag(true, '#sticky div');
	 * @param {String} [selector] Defines selector for table search (default id "#drag table").
	 * @public
	 * @function
	 * @name REDIPS.drag#initTables
	 */
	initTables = function (selector) {
		var	i, j, k,			// loop variables
			tblSelector,		// table selectors
			element,			// used in searhing parent nodes of found tables below div id="drag"
			level,				// (integer) 0 - ground table, 1 - nested table, 2 - nested nested table, 3 - nested nested nested table ...
			groupIdx,			// tables group index (ground table and its nested tables will have the same group)
			tableNodeList,		// nodelist of tables found inside defined selectors (querySelector returns node list and it's not alive)
			nestedTables,		// nested tables nodelist (search for nested tables for every "ground" table)
			tdNodeList,			// td nodeList (needed for search rowspan attribute)
			rowspan;			// flag to set if table contains rowspaned cells
		// empty tables array
		// http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript
		tables.length = 0;
		// if selector is undefined then use reference of current drag container
		if (selector === undefined) {
			tableNodeList = dragContainer.getElementsByTagName('table');
		}
		// otherwise prepare tables node list based on defined selector
		// node list returned by querySelectorAll is not alive
		else {
			tableNodeList = document.querySelectorAll(selector);
		}
		// loop through tables and define table sort parameter
		for (i = 0, j = 0; i < tableNodeList.length; i++) {
			// skip table if table belongs to the "redipsClone" container (possible for cloned rows - if initTables() is called after rowClone())
			// or table has "nolayout" className
			if (tableNodeList[i].parentNode.id === 'redips_clone' || tableNodeList[i].className.indexOf('nolayout') > -1) {
				continue;
			}
			// set start element for "do" loop
			element = tableNodeList[i].parentNode;
			// set initial value for nested level
			level = 0;
			// go up through DOM until DIV id="drag" found (drag container)
			do {
				// if "TD" found then this is nested table
				if (element.nodeName === 'TD') {
					// increase nested level
					level++;
				}
				// go one level up
				element = element.parentNode;
			} while (element && element !== dragContainer);
			// copy table reference to the static list
			tables[j] = tableNodeList[i];
			// create a "property object" in which all custom properties will be saved (if "redips" property doesn't exist)
			if (!tables[j].redips) {
				tables[j].redips = {};
			}
			// set redips.container to the table (needed in case when row is cloned)
			// ATTENTION! Here is needed additional work because table outside div#drag can be added to the "tables" array ...
			tables[j].redips.container = dragContainer;
			// set nested level (needed for sorting in "tables" array)
			// level === 0 - means that this is "ground" table ("ground" table may contain nested tables)
			tables[j].redips.nestedLevel = level;
			// set original table index (needed for sorting "tables" array to the original order in saveContent() function)
			tables[j].redips.idx = j;
			// define animationCounter per table
			animationCounter[j] = 0;
			// prepare td nodeList of current table
			tdNodeList = tables[j].getElementsByTagName('td');
			// loop through nodeList and search for rowspaned cells
			for (k = 0, rowspan = false; k < tdNodeList.length; k++) {
				// if only one rowspaned cell is found set flag to "true" and break loop
				if (tdNodeList[k].rowSpan > 1) {
					rowspan = true;
					break;
				}
			}
			// set redips.rowspan flag - needed in setTableRowColumn()
			tables[j].redips.rowspan = rowspan;
			// increment j counter
			j++;
		}
		/*
		 * define "redips.nestedGroup" and initial "redips.sort" parameter for each table
		 * 
		 * for example if drag area contains two tables and one of them has nested tables then this code will create two groups
		 * with the following redips.sort values: 100, 200, and 201
		 * 100 - "ground" table of the first group
		 * 200 - "ground" table of the second group
		 * 201 - nested table of the second table group
		 * 
		 * this means that nested table of second group will always be sorted before its "ground" table
		 * after clicking on DIV element in "ground" table of second group or nested table in second group array order will be: 201, 200 and 100
		 * after clicking on DIV element in "ground" table of first group array order will be: 100, 201, 200
		 * 
		 * actually, sortIdx will be increased and sorted result will be: 300, 201, 200
		 * and again clicking on element in nested table sorted result will be: 401, 400, 300 
		 * and so on ...
		 */
		for (i = 0, groupIdx = sortIdx = 1; i < tables.length; i++) {
			// if table is "ground" table (lowest level) search for nested tables
			if (tables[i].redips.nestedLevel === 0) {
				// set group index for ground table and initial sort index
				tables[i].redips.nestedGroup = groupIdx;
				tables[i].redips.sort = sortIdx * 100;
				// search for nested tables (if there is any)
				nestedTables = tables[i].getElementsByTagName('table');
				// open loop for every nested table
				for (j = 0; j < nestedTables.length; j++) {
					// skip table if table contains "nolayout" className
					if (nestedTables[j].className.indexOf('nolayout') > -1) {
						continue;
					}
					// set group index and initial sort index
					nestedTables[j].redips.nestedGroup = groupIdx;
					nestedTables[j].redips.sort = sortIdx * 100 + nestedTables[j].redips.nestedLevel;
				}
				// increase group index and sort index (sortIdx is private parameter of REDIPS.drag)
				groupIdx++;
				sortIdx++;
			}
		}
	};



	/**
	 * onmousedown event handler.
	 * This event handler is attached to every DIV element in drag container (please see "enableDrag").
	 * @param {Event} e Event information.
	 * @see <a href="#enableDrag">enableDrag</a>
	 * @see <a href="#add">handlerOnDblClick</a>
	 * @see <a href="#add_events">add_events</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	handlerOnMouseDown = function (e) {
		var evt = e || window.event,	// define event (cross browser)
			offset,						// object offset
			mouseButton,				// start drag if left mouse button is pressed
			position,					// position of table or container box of table (if has position:fixed then exclude scroll offset)
			X, Y;						// X and Y position of mouse pointer
		// if current DIV element is animated, then disable dragging of this element
		if (this.redips.animated === true) {
			return true;
		}
		// stop event propagation (only first clicked element will register onmousedown event)
		// needed in case of placing table inside of <div class="drag"> (after element was dropped to this table it couldn't be moved out
		// any more - table and element moved together because table captures mousedown event also in bubbling proces)
		evt.cancelBubble = true;
		if (evt.stopPropagation) {
			evt.stopPropagation();
		}
		// set true or false if shift key is pressed
		shiftKey = evt.shiftKey;
		// define which mouse button was pressed
		if (evt.which) {
			mouseButton = evt.which;
		}
		else {
			mouseButton = evt.button;
		}
		// exit from event handler if:
		// 1) control should pass to form elements and links
		// 2) device is not touch device and left mouse button is not pressed
		if (elementControl(evt) || (!evt.touches && mouseButton !== 1)) {
			return true;
		}
		// remove text selection (Chrome, FF, Opera, Safari)
		if (window.getSelection) {
			window.getSelection().removeAllRanges();
		}
		// IE8
		else if (document.selection && document.selection.type === "Text") {
			try {
				document.selection.empty();
			}
			catch (error) {
				// ignore error to as a workaround for bug in IE8
			}
		}
		// define X and Y position (pointer.x and pointer.y are needed in setTableRowColumn() and autoscroll methods) for touchscreen devices
		if (evt.touches) {
			X = pointer.x = evt.touches[0].clientX;
			Y = pointer.y = evt.touches[0].clientY;
		}
		// or for monitor + mouse devices
		else {
			X = pointer.x = evt.clientX;
			Y = pointer.y = evt.clientY;
		}
		// set initial threshold position (needed for calculating distance)
		threshold.x = X;
		threshold.y = Y;
		threshold.flag = false;
		// remember previous object if defined or set to the clicked object
		REDIPS.drag.objOld = objOld = obj || this;
		// set reference to the clicked object
		REDIPS.drag.obj = obj = this;
		// set true or false if clicked element contains "clone" class name (needed for clone element and clone table row)
		cloneClass = obj.className.indexOf('clone') > -1 ? true : false;
		// if tableSort is set to true (this is default) then set current table group in "tables" array to the array top
		// tableTop() should go before definition of "mode" property
		if (REDIPS.drag.tableSort) {
			tableTop(obj);
		}
		// if clicked element doesn't belong to the current container then environment should be changed
		if (dragContainer !== obj.redips.container) {
			dragContainer = obj.redips.container;
			initTables();
		}
		// define drag mode ("cell" or "row")
		// mode definition should be after:
		// tableTop() - because "obj" is rewritten with table row reference
		// initTables() - because "obj" is rewritten with table row reference and row doesn't have defined redips.container property
		if (obj.className.indexOf('row') === -1) {
			REDIPS.drag.mode = mode = 'cell';
		}
		else {
			REDIPS.drag.mode = mode = 'row';
			// just return reference of the current row (do not clone)
			REDIPS.drag.obj = obj = rowClone(obj);
		}
		// if user has used a mouse event to increase the dimensions of the table - call calculateCells() 
		calculateCells();
		// set high z-index if object isn't "clone" type (clone object is motionless) for "cell" mode only
		if (!cloneClass && mode === 'cell') {
			// http://foohack.com/2007/10/top-5-css-mistakes/ (how z-index works)
			obj.style.zIndex = 999;
		}
		// reset table row and cell indexes (needed in case of enable / disable tables)
		table = row = cell = null;
		// set current table, row and cell and remember source position (old position is initially the same as source position) 
		setTableRowColumn();
		table_source = table_old = table;
		row_source = row_old = row;
		cell_source = cell_old = cell;
		// define source cell, current cell and previous cell (needed for event handlers)
		REDIPS.drag.td.source = td.source = findParent('TD', obj);
		REDIPS.drag.td.current = td.current = td.source;
		REDIPS.drag.td.previous = td.previous = td.source;
		// call event.clicked for table content
		if (mode === 'cell') {
			REDIPS.drag.event.clicked(td.current);
		}
		// or for table row
		else {
			REDIPS.drag.event.rowClicked(td.current);
		}
		// if start position cannot be defined then user probably clicked on element that belongs to the disabled table
		// (or something else happened that was not supposed to happen - every element should belong to the table)
		// this code must go after execution of event handlers
		if (table === null || row === null || cell === null) {
			// rerun setTableRowColumn() again because some of tables might be enabled in handler events above
			setTableRowColumn();
			table_source = table_old = table;
			row_source = row_old = row;
			cell_source = cell_old = cell;
			// no, clicked element is on the disabled table - sorry
			if (table === null || row === null || cell === null) { 
				return true;
			}
		}
		// reset "moved" flag (needed for clone object in handlerOnMouseMove) and "cloned" flag
		moved = cloned = false;
		// activate onmousemove and ontouchmove event handlers on document object
		REDIPS.event.add(document, 'mousemove', handlerOnMouseMove);
		REDIPS.event.add(document, 'touchmove', handlerOnMouseMove);
		// activate onmouseup and ontouchend event handlers on document object
		REDIPS.event.add(document, 'mouseup', handlerOnMouseUp);
		REDIPS.event.add(document, 'touchend', handlerOnMouseUp);
		// get IE (all versions) to allow dragging outside the window (?!)
		// http://stackoverflow.com/questions/1685326/responding-to-the-onmousemove-event-outside-of-the-browser-window-in-ie
		if (obj.setCapture) {
			obj.setCapture();
		}
		// remember background color if is possible
		if (table !== null && row !== null && cell !== null) {
			bgStyleOld = getTdStyle(table, row, cell);
		}
		// set table CSS position (needed for exclusion "scroll offset" if table box has position fixed)
		position = getStyle(tables[table_source], 'position');
		// if table doesn't have style position:fixed then table container should be tested 
		if (position !== 'fixed') {
			position = getStyle(tables[table_source].parentNode, 'position');
		}
		// define object offset
		offset = boxOffset(obj, position);
		// calculate offset from the clicked point inside element to the
		// top, right, bottom and left side of the element
		objMargin = [Y - offset[0], offset[1] - X, offset[2] - Y, X - offset[3]];
		// dissable text selection (but not for links and form elements)
		// onselectstart is supported by IE browsers, other browsers "understand" return false in onmousedown handler
		dragContainer.onselectstart = function (e) {
			evt = e || window.event;
			if (!elementControl(evt)) {
				// this lines are needed for IE8 in case when leftmouse button was clicked and SHIFT key was pressed
				// IE8 selected text anyway but document.selection.clear() prevented text selection
				if (evt.shiftKey) {
					document.selection.clear();
				}
			    return false;
			}
		};
		// disable text selection for non IE browsers
		return false;
	};


	/**
	 * ondblclick handler event handler.
	 * This event handler is attached to every DIV element in drag container (please see "enableDrag").
	 * @param {Event} e Event information.
	 * @see <a href="#enableDrag">enableDrag</a>
	 * @see <a href="#handlerOnMouseDown">handlerOnMouseDown</a>
	 * @see <a href="#add_events">add_events</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	handlerOnDblClick = function (e) {
		// call custom event handler
		REDIPS.drag.event.dblClicked();
	};

 
	/**
	 * Method sets current table group in "tables" array to the array top ("tables" array is sorted).
	 * The purpose is to enable tables nesting and to improve perfomance. Tables closer to the top of the array will be scanned before other tables in array.
	 * This method is called from "handlerOnMouseDown" on every DIV element click.
	 * DIV element belongs to the table and this table group ("ground" table + its nested tables) should go to the array top.
	 * @param {HTMLElement} obj Clicked DIV element (table of the clicked DIV element will be sorted to the array top).
	 * @see <a href="#handlerOnMouseDown">handlerOnMouseDown</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	tableTop = function (obj) {
		var	e,		// element
			i,		// loop variable
			tmp,	// temporary storage (needed for exchanging array members)
			group;	// tables group
		// find table for clicked DIV element
		e = findParent('TABLE', obj);
		// set tables group
		group = e.redips.nestedGroup;
		// set highest "redips.sort" parameter to the current table group
		for (i = 0; i < tables.length; i++) {
			// "ground" table is table with lowest level hierarchy and with its nested tables creates table group
			// nested table will be sorted before "ground" table
			if (tables[i].redips.nestedGroup === group) {
				tables[i].redips.sort = sortIdx * 100 + tables[i].redips.nestedLevel; // sort = sortIdx * 100 + level
			}
		}
		// sort "tables" array according to redips.sort (tables with higher redips.sort parameter will go to the array top)
		tables.sort(function (a, b) {
			return b.redips.sort - a.redips.sort;
		});
		// increase sortIdx
		sortIdx++;
	};


	/**
	 * Methods returns reference to the table row or clones table row.
	 * If called from handlerOnMouseDown:
	 * <ul>
	 * <li>input parameter is DIV class="row"</li>
	 * <li>method will return reference of the current row</li>
	 * </ul>
	 * If called from handlerOnMouseMove:
	 * <ul>
	 * <li>input parameter is TR (current row) - previously returned with this function</li>
	 * <li>method will clone current row and return reference of the cloned row</li>
	 * </ul>
	 * If called from moveObject:
	 * <ul>
	 * <li>input parameter is TR (row to animate)</li>
	 * <li>method will clone row and return reference of the cloned row</li>
	 * </ul> 
	 * @param {HTMLElement} el DIV class="row" or TR (current row)
	 * @param {String} [row_mode] If set to "animated" then search for last row will not include search for "rowhandler" DIV element.
	 * @return {HTMLElement} Returns reference of the current row or clone current row and return reference of the cloned row.
	 * @see <a href="#handlerOnMouseDown">handlerOnMouseDown</a>
	 * @see <a href="#handlerOnMouseMove">handlerOnMouseMove</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	rowClone = function (el, row_mode) {
		var tableMini,			// original table is cloned and all rows except picked row are deleted
			offset,				// offset of source TR
			rowObj,			// reference to the row object
			last_idx,			// last row index in cloned table
			emptyRow,			// (boolean) flag indicates if dragged row is last row and should be marked as "empty row"
			cr,					// current row (needed for searc if dragged row is last row)
			div,				// reference to the <DIV class="drag row"> element
			i, j;				// loop variables
		// 1) rowClone call in onmousedown will return reference of TR element (input parameter is HTMLElement <div class="drag row">)
		if (el.nodeName === 'DIV') {
			// remember reference to the <DIV class="drag row">
			div = el;
		    // find parent TR element
			el = findParent('TR', el);
			// create a "property object" in which all custom properties will be saved (it is only one property for now)
			if (el.redips === undefined) {
				el.redips = {};
			}
			// save reference to the DIV element as redips.div
			// this will mostly be referenced as objOld.redips.div (because objOld in row dragging context is reference to the source row)
			el.redips.div = div;
			// return reference to the TR element
			return el;
		}
		// 2) rowClone call in onmousemove will clone current row (el.nodeName === 'TR')
		else {
			// remember source row
			rowObj = el;
			// if redips object doesn't exist (possible if rowClone() is called from moveObject() method) then create initialize redips object on TR element
			if (rowObj.redips === undefined) {
				rowObj.redips = {};
			}
		    // find parent table
			el = findParent('TABLE', el);
			// before cloning, cut out "clone" class name from <div class="drag row clone"> element if needed
			if (cloneClass && cloned) {
				// set reference to the <div class="drag row clone"> element
				div = rowObj.redips.div;
				// no more cloning, cut "clone" from class names
				div.className = normalize(div.className.replace('clone', ''));
			}
			// clone whole table
			tableMini = el.cloneNode(true);
			// return "clone" to the source element
			if (cloneClass && cloned) {
				div.className = div.className + ' clone';
			}
			// find last row index in cloned table
			last_idx = tableMini.rows.length - 1;
			// if row mode is "animated" then definition of last row in table is simple
			if (row_mode === 'animated') {
				if (last_idx === 0) {
					emptyRow = true;
				}
				else {
					emptyRow = false;
				}
			}
			// else set initially emptyRow to true (it can be set to false in lower loop) 
			else {
				emptyRow = true;
			}
		    // test if dragged row is the last row and delete all rows but current row
			// the trick is to find rowhandler in cells except current cell and that's fine for user interface
			// if rows are animated, then "rowhandler" cells don't have to exsist and user should take care about marking last row as "empty row"
			for (i = last_idx; i >= 0; i--) {
				// if row is not the current row
				if (i !== rowObj.rowIndex) {
					// search for "rowhandler cell" in table row if row mode is not "animated" (user drags row)
					// this lines are skipped in case of animated mode (
					if (emptyRow === true && row_mode === undefined) {
						// set current row
						cr = tableMini.rows[i];
						// open loop to go through each cell
						for (j = 0; j < cr.cells.length; j++) {
							// if table cell contains "rowhandler" class name then dragged row is not the last row in table
							if (cr.cells[j].className.indexOf('rowhandler') > -1) {
								emptyRow = false;
								break;
							}
						}
						
					}
					// delete row (it should go after searching for "rowhandler" class name)
					tableMini.deleteRow(i);
				}
			}
			// if row is not cloned then set emptyRow property
			// cloned row always leaves original row in the table so emptyRow property should stay as it was before clone operation
			if (!cloned) {
				// set emptyRow flag to the current row
				// * needed in rowDrop() for replacing this row with dropped row
				// * needed in setTableRowColumn() to disable dropping DIV elements to the empty row
				rowObj.redips.emptyRow = emptyRow;
			}
			// create a "property object" in which all custom properties will be saved
			tableMini.redips = {};
			// set reference to the redips.container (needed if moveObject() moves elements in other container)
			tableMini.redips.container = el.redips.container;
			// save source row reference (needed for source row deletion in rowDrop method)
			tableMini.redips.sourceRow = rowObj;
			// set form values in cloned row (to prevent reset values of form elements)
			formElements(rowObj, tableMini.rows[0]);
			// copy custom properties to all child DIV elements and set onmousedown/ondblclick event handlers
			copyProperties(rowObj, tableMini.rows[0]);
			// append cloned mini table to the DIV id="redips_clone"
			document.getElementById('redips_clone').appendChild(tableMini);
			// include scroll position in offset
			offset = boxOffset(rowObj, 'fixed');
			// set position and position type
			tableMini.style.position = 'fixed';
			tableMini.style.top = offset[0] + "px";
			tableMini.style.left = offset[3] + "px";
			// define width of mini table
			tableMini.style.width = (offset[1] - offset[3]) + "px";
			// return reference of mini table
			return tableMini;
		}
	};


	/**
	 * Method drops table row to the target row and calls user event handlers. Source row is deleted and cloned row is inserted at the new position.
	 * Method takes care about the last row in the table only if user drags element. In case of moving rows with moveObject(), control
	 * and logic for last row is turned off. This method is called from handlerOnMouseUp() and animation().
	 * @param {Integer} tableIdx Table index.
	 * @param {Integer} rowIdx Row index.
	 * @param {HTMLElement} [tableMini] Reference to the mini table (table that contains only one row). This is actually clone of source row.
	 * @see <a href="#rowClone">rowClone</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	rowDrop = function (tableIdx, rowIdx, tableMini) {
		// local variables
		var animated = false,	// (boolean) flag shows if row is animated or dragged by user
			drop,				// (boolean) if false then dropping row will be canceled
			trMini,				// reference to the TR in mini table
			source = {},		// object contains: source table reference, source row reference, source row index and source table section (parent of row)
			target = {},		// object contains: target table reference, target row reference and target table section (parent of row)
			deleteTableRow;		// delete row (private method)
		// delete table row - input paremeter is row reference (private method)
		deleteTableRow = function (el) {
			var tbl;
			// if row doesn't have custom "redips" property or is not marked as empty, then it can be deleted
			if (el.redips === undefined || !el.redips.emptyRow) {
				tbl = findParent('TABLE', el);
				tbl.deleteRow(el.rowIndex);
			}
			// else, row is marked as "empty" and it will be only colored (not deleted)
			// content of table cells will be deleted and background color will be set to default color
			else {
				rowOpacity(el, 'empty', REDIPS.drag.style.rowEmptyColor);
			}
		};
		// if tableMini is not defined, then rowDrop() is called from handlerOnMouseUp() and set reference to the currently dragged row - mini table
		if (tableMini === undefined) {
			tableMini = obj;
		}
		// otherwise, rowDrop() is called from animation() (because third input parameter is set)
		// in that case set animated flag to true to turn off "last row" logic
		else {
			animated = true;
		}
		// define source data: row, row index, table and table section (needed for "switch" rowDropMode)
		source.row = tableMini.redips.sourceRow;
		source.rowIndex = source.row.rowIndex;
		source.table = findParent('TABLE', source.row);
		source.tableSection = source.table.rows[0].parentNode;
		// define target data: row, row index, table and table section
		target.table = tables[tableIdx];
		target.row = target.table.rows[rowIdx];
		target.rowIndex = rowIdx;
		target.tableSection = target.table.rows[0].parentNode;
		// set reference to the TR in mini table (mini table has only one row - first row)
		trMini = tableMini.getElementsByTagName('tr')[0];
		// destroy mini table (node still exists in memory)
		tableMini.parentNode.removeChild(tableMini);
		// call event.rowDroppedBefore() - this handler can return "false" value
		drop = REDIPS.drag.event.rowDroppedBefore(source.table, source.rowIndex);
		// if handler returned false then row dropping will be canceled
		if (drop !== false) {
			// row is moved to the "trash"
			if (!animated && td.target.className.indexOf(REDIPS.drag.trash.className) > -1) {
				// test if cloned row is directly dropped to the "trash" cell (call rowDeleted event handler)
				if (cloned) {
					REDIPS.drag.event.rowDeleted();
				}
				// row is not cloned
				else {
					// if trash.questionRow is set then user should should confirm delete row action
					if (REDIPS.drag.trash.questionRow) {
						// ask user if is sure
						if (confirm(REDIPS.drag.trash.questionRow)) {
							// delete source row and call rowDeleted event handler
							deleteTableRow(source.row);
							REDIPS.drag.event.rowDeleted();
						}
						// user is not sure - undelete
						else {
							// delete emptyRow property from source row because emptyRow will be set on next move
							// otherwise row would be overwritten and that's no good
							delete objOld.redips.emptyRow;
							// just call undeleted handler
							REDIPS.drag.event.rowUndeleted();
						}
					}
					// trask_ask_row is set to "false" - source row can be deleted
					else {
						// delete source row and call rowDeleted event handler
						deleteTableRow(source.row);
						REDIPS.drag.event.rowDeleted();
					}
				}
			}
			// normal row move
			else {
				// if row is not dropped to the last row position
				if (target.rowIndex < target.table.rows.length) {
					// if source and target rows are from the same table
					if (table === table_source) {
						// row is dropped above source position from the same table
						if (source.rowIndex > target.rowIndex) {
							target.tableSection.insertBefore(trMini, target.row);
						}
						// row is dropped to the lower position in the same table
						else {
							target.tableSection.insertBefore(trMini, target.row.nextSibling);
						}
					}
					// row is dropped to other table and will be placed after highlighted row
					else if (REDIPS.drag.rowDropMode === 'after') {
						target.tableSection.insertBefore(trMini, target.row.nextSibling);
					}
					// row is dropped to other table and will be placed before highlighted row
					// this code will be executed in case of "before", "switch" and "overwrite" row drop mode (when dropping row to other table)
					else {
						target.tableSection.insertBefore(trMini, target.row);
					}
				}
				// row is dropped to the last row position
				// it's possible to set target row index greater then number of rows - in this case row will be appended to the table end
				else {
					// row should be appended
					target.tableSection.appendChild(trMini);
					// set reference to the upper row
					// after row is appended, upper row should be tested if contains "emptyRow" set to true  
					// this could happen in case when row is moved to the table with only one empty row
					target.row = target.table.rows[0];
				}
				// if table contains only "empty" row then this row should be deleted after inserting or appending to such table
				if (target.row && target.row.redips && target.row.redips.emptyRow) {
					target.tableSection.deleteRow(target.row.rowIndex);
				}
				// in case of "overwrite", delete target row
				else if (REDIPS.drag.rowDropMode === 'overwrite') {
					deleteTableRow(target.row);
				}
				// insert target row to source location and delete source row (if row is not cloned)
				else if (REDIPS.drag.rowDropMode === 'switch' && !cloned) {
					source.tableSection.insertBefore(target.row, source.row);
					// delete emptyRow flag to the source row (needed in case when switching last row from table2 to table1)
					if (source.row.redips !== undefined) {
						delete source.row.redips.emptyRow;
					}
				}
				// delete source row if called from animation() or row is not cloned
				if (animated || !cloned) {
					deleteTableRow(source.row);
				}
				// delete emptyRow property from inserted/appended row because emptyRow will be set on next move
				// copyProperties() in rowClone() copied emptyRow property to the row in tableMini
				// otherwise row will be overwritten and that is not good
				delete trMini.redips.emptyRow;
				// call rowDropped event handler if rowDrop() was not called from animation()
				if (!animated) {
					REDIPS.drag.event.rowDropped(trMini, source.table, source.rowIndex);
				}
			} // end normal row move
			// if row contains TABLE(S) then recall initTables() to properly initialize tables array and set custom properties
			// no matter if row was moved or deleted
			if (trMini.getElementsByTagName('table').length > 0) {
				initTables();
			}
		}
		// event.rowDroppedBefore() returned "false" (it's up to user to return source row opacity to its original state) 
		else {
			// rowOpacity(objOld, 100);
		}
	};


	/**
	 * Method sets form values after cloning table row. Method is called from rowClone.
	 * cloneNode() should take care about form values when performing deep cloning - but some browsers have a problem.
	 * This method will fix checkboxes, selected indexes and so on when dragging table row (values in form elements will be preserved).
	 * @param {HTMLElement} str Source table row.
	 * @param {HTMLElement} ctr Cloned table row. Table row is cloned in a moment of dragging.
	 * @see <a href="#rowClone">rowClone</a>
	 * @private
	 * @memberOf REDIPS.drag# 
	 */
	formElements = function (str, ctr) {
		// local variables
		var i, j, k, type,
			src = [],	// collection of form elements from source row
			cld = [];	// collection of form elements from cloned row
		// collect form elements from source row
		src[0] = str.getElementsByTagName('input');
		src[1] = str.getElementsByTagName('textarea');
		src[2] = str.getElementsByTagName('select');
		// collect form elements from cloned row
		cld[0] = ctr.getElementsByTagName('input');
		cld[1] = ctr.getElementsByTagName('textarea');
		cld[2] = ctr.getElementsByTagName('select');
		// loop through found form elements in source row
		for (i = 0; i < src.length; i++) {
			for (j = 0; j < src[i].length; j++) {
				// define element type
				type = src[i][j].type;
				switch (type) {
				case 'text':
				case 'textarea':
				case 'password':
					cld[i][j].value = src[i][j].value;
					break;
				case 'radio':
				case 'checkbox':
					cld[i][j].checked = src[i][j].checked;
					break;
				case 'select-one':
					cld[i][j].selectedIndex = src[i][j].selectedIndex;
					break;
				case 'select-multiple':
					for (k = 0; k < src[i][j].options.length; k++) {
						cld[i][j].options[k].selected = src[i][j].options[k].selected;
					}
					break;
				} // end switch
			} // end for j
		} // end for i
	};


	/**
	 * onmouseup event handler.
	 * handlerOnMouseUp is attached to the DIV element in a moment when DIV element is clicked (this happens in handlerOnMouseDown).
	 * This event handler detaches onmousemove and onmouseup event handlers.
	 * @param {Event} e Event information.
	 * @see <a href="#handlerOnMouseDown">handlerOnMouseDown</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	handlerOnMouseUp = function (e) {
		var evt = e || window.event,	// define event (FF & IE)
			target_table,				// needed for test if cloned element is dropped outside table
			r_table, r_row,				// needed for mode="row"
			mt_tr,						// needed for returning color to the table cell (mt_tr - "mini table" "table_row")
			X, Y,						// X and Y position of mouse pointer
			i,							// used in local loop
			drop,						// if false then dropped DIV element (in case of dropMode="switch") will be canceled
			// define target elements and target elements length needed for switching table cells
			// target_elements_length is needed because nodeList objects in the DOM are live 
			// please see http://www.redips.net/javascript/nodelist-objects-are-live/
			target_elements, target_elements_length;
		// define X and Y position
		X = evt.clientX;
		Y = evt.clientY;
		// turn off autoscroll "current cell" handling (if user mouseup in the middle of autoscrolling)
		edge.flag.x = edge.flag.y = 0;
		// remove mouse capture from the object in the current document
		// get IE (all versions) to allow dragging outside the window (?!)
		// http://stackoverflow.com/questions/1685326/responding-to-the-onmousemove-event-outside-of-the-browser-window-in-ie
		if (obj.releaseCapture) {
			obj.releaseCapture();
		}
		// detach mousemove and touchmove event handlers on document object
		REDIPS.event.remove(document, 'mousemove', handlerOnMouseMove);
		REDIPS.event.remove(document, 'touchmove', handlerOnMouseMove);
		// detach mouseup and touchend event handlers on document object
		REDIPS.event.remove(document, 'mouseup', handlerOnMouseUp);
		REDIPS.event.remove(document, 'touchend', handlerOnMouseUp);
		// detach dragContainer.onselectstart handler to enable select for IE7/IE8 browser 
		dragContainer.onselectstart = null;
		// reset object styles
		resetStyles(obj);
		// document.body.scroll... only works in compatibility (aka quirks) mode,
		// for standard mode, use: document.documentElement.scroll...
		scrollData.width  = document.documentElement.scrollWidth;
		scrollData.height = document.documentElement.scrollHeight;	
		// reset autoscroll flags
		edge.flag.x = edge.flag.y = 0;
		// this could happen if "clone" element is placed inside forbidden table cell
		if (cloned && mode === 'cell' && (table === null || row === null || cell === null)) {
			obj.parentNode.removeChild(obj);
			// decrease clonedId counter
			clonedId[objOld.id] -= 1;
			REDIPS.drag.event.notCloned();
		}
		// if DIV element was clicked and left button was released, but element is placed inside unmovable table cell
		else if (table === null || row === null || cell === null) {
			REDIPS.drag.event.notMoved();
		}		
		else {
			// if current table is in range, use table for current location
			if (table < tables.length) {
				target_table = tables[table];
				REDIPS.drag.td.target = td.target = target_table.rows[row].cells[cell];
				// set background color for destination cell (cell had hover color)
				setTdStyle(table, row, cell, bgStyleOld);
				// set r_table & r_row (needed for mode === "row")
				r_table = table;
				r_row = row;
			}
			// if any level of old position is undefined, then use source location
			else if (table_old === null || row_old === null || cell_old === null) {
				target_table = tables[table_source];
				REDIPS.drag.td.target = td.target = target_table.rows[row_source].cells[cell_source];
				// set background color for destination cell (cell had hover color)
				setTdStyle(table_source, row_source, cell_source, bgStyleOld);
				// set r_table & r_row (needed for mode === "row")
				r_table = table_source;
				r_row = row_source;
			}
			// or use the previous location
			else {
				target_table = tables[table_old];
				REDIPS.drag.td.target = td.target = target_table.rows[row_old].cells[cell_old];
				// set background color for destination cell (cell had hover color)
				setTdStyle(table_old, row_old, cell_old, bgStyleOld);
				// set r_table & r_row (needed for mode === "row")
				r_table = table_old;
				r_row = row_old;
			}
			// if dragging mode is table row
			if (mode === 'row') {
				// row was clicked and mouse button was released right away (row was not moved)
				if (!moved) {
					REDIPS.drag.event.rowNotMoved();
				}
				// row was moved
				else {
					// and dropped to the source row
					if (table_source === r_table && row_source === r_row) {
						// reference to the TR in mini table (mini table has only one row)
						mt_tr = obj.getElementsByTagName('tr')[0];
						// return color to the source row from the row of cloned mini table
						// color of the source row can be changed in event.rowMoved() (when user wants to mark source row)
						objOld.style.backgroundColor = mt_tr.style.backgroundColor;
						// return color to the each table cell
						for (i = 0; i < mt_tr.cells.length; i++) {
							objOld.cells[i].style.backgroundColor = mt_tr.cells[i].style.backgroundColor;
						}
						// remove cloned mini table
						obj.parentNode.removeChild(obj);
						// delete emptyRow property from source row because emptyRow will be set on next move
						// otherwise row would be overwritten and that's no good
						delete objOld.redips.emptyRow;
						// if row was cloned and dropped to the source location then call rowNotCloned event handler
						if (cloned) {
							REDIPS.drag.event.rowNotCloned();
						}
						// call event.rowDroppedSource() event handler
						else {
							REDIPS.drag.event.rowDroppedSource(td.target);
						}
					}
					// and dropped to the new row
					else {
						rowDrop(r_table, r_row);
					}	
				}
			}
			// clicked element was not moved - DIV element didn't cross threshold value
			// just call event.notMoved event handler
			else if (!cloned && !threshold.flag) {
				REDIPS.drag.event.notMoved();
			}
			// delete cloned element if dropped on the start position
			else if (cloned && table_source === table && row_source === row && cell_source === cell) {
				obj.parentNode.removeChild(obj);
				// decrease clonedId counter
				clonedId[objOld.id] -= 1;
				REDIPS.drag.event.notCloned();
			}
			// delete cloned element if dropped outside current table and clone.drop is set to false
			else if (cloned && REDIPS.drag.clone.drop === false &&
					(X < target_table.redips.offset[3] || X > target_table.redips.offset[1] ||
					Y < target_table.redips.offset[0] || Y > target_table.redips.offset[2])) {
				obj.parentNode.removeChild(obj);
				// decrease clonedId counter
				clonedId[objOld.id] -= 1;
				REDIPS.drag.event.notCloned();
			}
			// remove object if destination cell has "trash" in class name
			else if (td.target.className.indexOf(REDIPS.drag.trash.className) > -1) {
				// remove child from DOM (node still exists in memory)
				obj.parentNode.removeChild(obj);
				// if public property trash.question is set then ask for confirmation
				if (REDIPS.drag.trash.question) {
					setTimeout(function () {
						// Are you sure?
						if (confirm(REDIPS.drag.trash.question)) {
							// yes, do all actions needed after element is deleted
							elementDeleted();
						}
						// no, do undelete
						else {
							// undelete DIV element
							if (!cloned) {
								// append removed object to the source table cell
								tables[table_source].rows[row_source].cells[cell_source].appendChild(obj);
								// and recalculate table cells because undelete can change row dimensions 
								calculateCells();
							}
							// call undeleted event handler
							REDIPS.drag.event.undeleted();	
						}
					}, 20);
				}
				// element is deleted and do all actions needed after element is deleted
				else {
					elementDeleted();
				}
			}
			else if (REDIPS.drag.dropMode === 'switch') {
				// call event.droppedBefore event handler
				drop = REDIPS.drag.event.droppedBefore(td.target);
				// if returned value is false then only call elementDrop with input parameter "false" to delete cloned element (if needed)
				// dragged DIV element will be returned to the initial position
				if (drop === false) {
					elementDrop(false);
				}
				// normal procedure for "switch" drag option
				else {
					// remove dragged element from DOM (source cell) - node still exists in memory
					obj.parentNode.removeChild(obj);
					// move object from the destination to the source cell
					target_elements = td.target.getElementsByTagName('div');
					target_elements_length = target_elements.length;
					for (i = 0; i < target_elements_length; i++) {
						// sourceCell is defined in onmouseup
						if (target_elements[0] !== undefined) { //fixes issue with nested DIVS
							// save reference of switched element in REDIPS.drag.objOld property
							// '0', not 'i' because NodeList objects in the DOM are live
							REDIPS.drag.objOld = objOld = target_elements[0];
							// append objOld to the source cell
							td.source.appendChild(objOld);
							// register event listeners (FIX for Safari Mobile)
							// it seems that Safari Mobile loses registrated events (traditional model) assigned to the DIV element (other browsers work just fine without this line)
							registerEvents(objOld);
						}
					}
					// elementDrop() should be called before event.switched() otherwise targetCell will be undefined
					elementDrop();
					// if destination element exists, then elements will be switched
					if (target_elements_length) {
						// call event.switched because cloneLimit could call event.clonedEnd1 or event.clonedEnd2
						REDIPS.drag.event.switched();
					}
				}
			}
			// overwrite destination cell with dragged content 
			else if (REDIPS.drag.dropMode === 'overwrite') {
				// call event.droppedBefore event handler
				drop = REDIPS.drag.event.droppedBefore(td.target);
				// if event handler didn't return "false" then proceed normaly (otherwise dropped element will be returned to the source table cell)
				if (drop !== false) {
					// empty target cell
					emptyCell(td.target);
				}
				// drop element to the table cell (or delete cloned element if drop="false")
				elementDrop(drop);
			}
			// else call event.droppedBefore(), append object to the cell and call event.dropped() 
			else {
				// call event.droppedBefore event handler
				drop = REDIPS.drag.event.droppedBefore(td.target);
				// drop element to the table cell (or delete cloned element if drop="false")
				elementDrop(drop);
			}
			// force naughty browsers (IE6, IE7 ...) to redraw source and destination row (element.className = element.className does the trick)
			// but careful (table_source || row_source could be null if clone element was clicked in denied table cell)
			//
			// today we are in era of FF5, IE9 ... so maybe this lines were not needed any more (first I will comment them out and if nobody will complain
			// then they will be deleted completely)
			/*
			if (table_source !== null && row_source !== null && tables[table_source].rows[row_source] !== undefined) {
				tables[table_source].rows[row_source].className = tables[table_source].rows[row_source].className;
			}
			targetCell.parentNode.className = targetCell.parentNode.className;
			*/
			// if dropped object contains TABLE(S) then recall initTables() to properly initialize tables array (only in cell mode)
			// if row is dragged and contains tables, then this will be handler in rowDrop() private method
			if (mode === 'cell' && obj.getElementsByTagName('table').length > 0) {
				initTables();
			}
			// recalculate table cells and scrollers because cell content could change row dimensions 
			calculateCells();
			// call last event handler
			REDIPS.drag.event.finish();
		}
		// reset old positions
		table_old = row_old = cell_old = null;
	};


	/**
	 * Element drop. This method is called from handlerOnMouseUp and appends element to the target table cell.
	 * If input parameter "drop" is set to "false" (this is actually return value from event.droppedBefore) then DIV elements will not be dropped (only cloned element will be deleted).
	 * @param {Boolean} [drop] If not "false" then DIV element will be dropped to the cell.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	elementDrop = function (drop) {
		var cloneSourceDiv = null,	// clone source element (needed if clone.sendBack is set to true)
			div,					// nodeList of DIV elements in target cell (needed if clone.sendBack is set to true)
			i;						// local variables
		// if input parameter is not "false" then DIV element will be dropped to the table cell
		if (drop !== false) {
			// if clone.sendBack is set to true then try to find source element in target cell
			if (clone.sendBack === true) {
				// search all DIV elements in target cell
				div = td.target.getElementsByTagName('DIV');
				// loop through all DIV elements in target cell
				for (i = 0; i < div.length; i++) {
					// if DIV in target cell is source of dropped DIV element (dropped DIV id and id of DIV in target cell has the same name beginning like "d12c2" and "d12")
					// of course, the case where source DIV element is dropped to the cell with cloned DIV element should be excluded (possible in climit1 type)
					if (obj !== div[i] && obj.id.indexOf(div[i].id) === 0) {
						// set reference to cloneSourceDiv element
						cloneSourceDiv = div[i];
						// break the loop
						break;
					}
				}
				// if clone source DIV element exists in target cell
				if (cloneSourceDiv) {
					// update climit class (increment by 1)
					cloneLimit(cloneSourceDiv, 1);
					// delete dropped DIV element
					obj.parentNode.removeChild(obj);
					// return from the method (everything is done)
					return;
				}
			}
			// shift table content if dropMode is set to "shift" and target cell is not empty or shift.after option is set to always
			// hasChild() is a private method
			if (REDIPS.drag.dropMode === 'shift' && (hasChilds(td.target) || REDIPS.drag.shift.after === 'always')) {
				shiftCells(td.source, td.target);
			}
			// insert (to top) or append (to bottom) object to the target cell
			if (REDIPS.drag.multipleDrop === 'top' && td.target.hasChildNodes()) {
				td.target.insertBefore(obj, td.target.firstChild);
			}
			else {
				td.target.appendChild(obj);
			}
			// register event listeners (FIX for Safari Mobile)
			registerEvents(obj);
			// call event.dropped because cloneLimit could call event.clonedEnd1 or event.clonedEnd2
			REDIPS.drag.event.dropped(td.target);
			// if object is cloned
			if (cloned) {
				// call clonedDropped event handler
				REDIPS.drag.event.clonedDropped(td.target);
				// update climit1_X or climit2_X classname
				cloneLimit(objOld, -1);
			}
		}
		// cloned element should be deleted (if not already deleted)
		else if (cloned && obj.parentNode) {
			obj.parentNode.removeChild(obj);
		}
	};


	/**
	 * Register event listeners for DIV element.
	 * DIV elements should have only onmousedown, ontouchstart and ondblclick attached (using traditional event registration model).
	 * I had a problem with advanced event registration model.
	 * In case of using advanced model, selected text and dragged DIV element were in collision.
	 * It looks like selected text was able to drag instead of DIV element.
	 * @param {HTMLElement} div Register event listeners for onmousedown, ontouchstart and ondblclick to the DIV element.
	 * @param {Boolean} [flag] If set to false then event listeners will be deleted.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	registerEvents = function (div, flag) {
		// if flag is se to false, then remove event listeners on DIV element
		if (flag === false) {
			div.onmousedown = null;
			div.ontouchstart = null;
			div.ondblclick = null;
		}
		else {
			div.onmousedown = handlerOnMouseDown;
			div.ontouchstart = handlerOnMouseDown;
			div.ondblclick = handlerOnDblClick;
		}
	};

	
	/**
	 * After element is dropped, styles need to be reset.
	 * @param {HTMLElement} el Element reference.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	resetStyles = function (el) {
		// reset top and left styles
		el.style.top  = '';
		el.style.left = '';
		// reset position and z-index style (if not set or default value for position style is "static")
		el.style.position = '';
		el.style.zIndex = '';
	};

	
	/**
	 * Actions needed after element is deleted. This function is called from handlerOnMouseUp. Function deletes element and calls event handlers.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	elementDeleted = function () {
		// set param needed to find last cell (for case where shift.after is 'always' or 'delete')
		var param;
		// if object is cloned, update climit1_X or climit2_X classname
		if (cloned) {
			cloneLimit(objOld, -1);
		}
		// shift table content if dropMode is set to "shift" and shift.after is set to "delete" or "always"
		if (REDIPS.drag.dropMode === 'shift' && (REDIPS.drag.shift.after === 'delete' || REDIPS.drag.shift.after === 'always')) {
			// define last table cell in column, row or table - depending on shift.mode value
			switch (REDIPS.drag.shift.mode) {
			case 'vertical2':
				param = 'lastInColumn';
				break;
			case 'horizontal2':
				param = 'lastInRow';
				break;
			default:
				param = 'last';
			}
			// content from source cell to last cell will be shifted (emulates dropping DIV element to the last table cell)
			shiftCells(td.source, findCell(param, td.source)[2]);
		}
		// call event.deleted() method and send cloned flag
		// inside event.deleted it's possible to know whether cloned element is directly moved to the trash
		REDIPS.drag.event.deleted(cloned);
	};


	/**
	 * onmousemove event handler.
	 * handlerOnMouseMove is attached to document level in a moment when DIV element is clicked (this happens in handlerOnMouseDown).
	 * handlerOnMouseUp detaches onmousemove and onmouseup event handlers.
	 * @param {Event} e Event information.
	 * @see <a href="#handlerOnMouseDown">handlerOnMouseDown</a>
	 * @see <a href="#handlerOnMouseUp">handlerOnMouseUp</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	handlerOnMouseMove = function (e) {
		var evt = e || window.event,			// define event (FF & IE)
			bound = REDIPS.drag.scroll.bound,	// read "bound" public property (maybe code will be faster, and it will be easier to reference in onmousemove handler)
			sca,								// current scrollable container area
			X, Y,								// X and Y position of mouse pointer
			deltaX, deltaY,						// delta from initial position
			i,									// needed for local loop
			scrollPosition;						// scroll position variable needed for autoscroll call
		// define X and Y position (pointer.x and pointer.y are needed in setTableRowColumn() and autoscroll methods) for touchscreen devices
		if (evt.touches) {
			X = pointer.x = evt.touches[0].clientX;
			Y = pointer.y = evt.touches[0].clientY;
		}
		// or for monitor + mouse devices
		else {
			X = pointer.x = evt.clientX;
			Y = pointer.y = evt.clientY;
		}
		// calculate delta from initial position
		deltaX = Math.abs(threshold.x - X);
		deltaY = Math.abs(threshold.y - Y);
		// if "moved" flag isn't set (this is the first moment when object is moved)
		if (!moved) {
			// if moved object is element and has clone in class name or cloneShiftKey is enabled and shift key is pressed
			// then remember previous object, clone object, set cloned flag and call event.cloned
			// (shiftKey is defined in handler_mousedown)
			if (mode === 'cell' && (cloneClass || (REDIPS.drag.clone.keyDiv === true && shiftKey))) {
				// remember previous object (original element)
				REDIPS.drag.objOld = objOld = obj;
				// clone DIV element ready for dragging
				REDIPS.drag.obj = obj = cloneObject(obj, true);
				// set cloned flag
				cloned = true;
				// call event.cloned event handler
				REDIPS.drag.event.cloned();
				// set color for the current table cell and remember previous position and color
				setPosition();
			}
			// else ordinary object is moved
			else {
				// if mode is row then remember reference of the source row, clone source row and set obj as reference to the current row
				if (mode === 'row') {
					// settings of "cloned" flag should go before calling rowClone() because "cloned" is needed in rowClone()
					// to cut out "clone" class name from <div class="drag row clone"> elements
					if (cloneClass || (REDIPS.drag.clone.keyRow === true && shiftKey)) {
						cloned = true;
					}
					// remember reference to the source row
					REDIPS.drag.objOld = objOld = obj;
					// clone source row and set as obj
					REDIPS.drag.obj = obj = rowClone(obj);
					// set high z-index for cloned mini table
					obj.style.zIndex = 999;
				}
				// get IE (all versions) to allow dragging outside the window (?!)
				// this was needed here also - despite setCaputure in onmousedown
				if (obj.setCapture) {
					obj.setCapture();
				}
				// set style to fixed to allow dragging DIV object
				obj.style.position = 'fixed';
				// call calculate cells for case where moved element changed cell dimension
				// place 3 elements in the same cell in example08 and try to move one out of the table cell
				calculateCells();
				// set current table, row and column
				setTableRowColumn();
				// call event handler (row cloned/moved)
				if (mode === 'row') {
					if (cloned) {
						REDIPS.drag.event.rowCloned();
					}
					else {
						REDIPS.drag.event.rowMoved();
					}
				}
				// set color for the current table cell and remember previous position and color
				// setPosition() must go after calling event.moved() and event.rowMoved() if user wants to
				// change color of source row
				setPosition();
			}
			// if element is far away on the right side of page, set possible right position (screen.width - object width)
			// objMargin[1] + objMargin[3] = object width
			if (X > screen.width - objMargin[1]) {
				obj.style.left = (screen.width - (objMargin[1] +  objMargin[3])) + 'px';
			}
			// if element is below page bottom, set possible lower position (screen.width - object height)
			// objMargin[0] + objMargin[2] = object height
			if (Y > screen.height - objMargin[2]) {
				obj.style.top  = (screen.height - (objMargin[0] + objMargin[2])) + 'px';
			}
		}
		// set moved_flag
		moved = true;
		// if REDIPS.drag works in "cell" mode and DIV element is moved out of defined threshold distance 
		if (mode === 'cell' && (deltaX > threshold.value || deltaY > threshold.value) && !threshold.flag) {
			// set threshold flag
			threshold.flag = true;
			// set position (highlight current position)
			setPosition();
			// call event.moved with cloned as input parameter
			REDIPS.drag.event.moved(cloned);
		}
		// set left and top styles for the moved element if element is inside window
		// this conditions will stop element on window bounds
		if (X > objMargin[3] && X < screen.width - objMargin[1]) {
			obj.style.left = (X - objMargin[3]) + 'px';
		}
		if (Y > objMargin[0] && Y < screen.height - objMargin[2]) {
			obj.style.top  = (Y - objMargin[0]) + 'px';
		}
		// set current table, row and cell (this condition should spare CPU):
		// 1) if mouse pointer is inside DIV id="drag"
		// 2) and autoscroll is not working
		// 3) and current table contains nested table or cursor is outside of current cell
		if (X < divBox[1] && X > divBox[3] && Y < divBox[2] && Y > divBox[0] &&
			edge.flag.x === 0 && edge.flag.y === 0 &&
			(currentCell.containTable || (X < currentCell[3] || X > currentCell[1] || Y < currentCell[0] || Y > currentCell[2]))) {
			// set current table row and table cell
			setTableRowColumn();
			// if new location is inside table and new location is different then old location
			cellChanged();
		}
		// if autoScroll option is enabled (by default it is but it can be turned off)
		if (REDIPS.drag.scroll.enable) {
			// calculate horizontally crossed page bound
			edge.page.x = bound - (screen.width / 2  > X ? X - objMargin[3] : screen.width - X - objMargin[1]);
			// if element crosses page bound then set scroll direction and call auto scroll 
			if (edge.page.x > 0) {
				// in case when object is only half visible
				if (edge.page.x > bound) {
					edge.page.x = bound;
				}
				// get horizontal window scroll position
				scrollPosition = getScrollPosition()[0];
				// set scroll direction
				edge.page.x *= X < screen.width / 2 ? -1 : 1;
				// if page bound is crossed and this two cases aren't met:
				// 1) scrollbar is on the left and user wants to scroll left
				// 2) scrollbar is on the right and user wants to scroll right
				if (!((edge.page.x < 0 && scrollPosition <= 0) || (edge.page.x > 0 && scrollPosition >= (scrollData.width - screen.width)))) {
					// fire autoscroll function (this should happen only once)
					if (edge.flag.x++ === 0) {
						// reset onscroll event
						REDIPS.event.remove(window, 'scroll', calculateCells);
						// call window autoscroll 
						autoScrollX(window);
					}
				}
			}
			else {
				edge.page.x = 0;
			}
			// calculate vertically crossed page bound
			edge.page.y = bound - (screen.height / 2 > Y ? Y - objMargin[0] : screen.height - Y - objMargin[2]);
			// if element crosses page bound
			if (edge.page.y > 0) {
				// set max crossed bound
				if (edge.page.y > bound) {
					edge.page.y = bound;
				}
				// get vertical window scroll position
				scrollPosition = getScrollPosition()[1];
				// set scroll direction
				edge.page.y *= Y < screen.height / 2 ? -1 : 1;
				// if page bound is crossed and this two cases aren't met:
				// 1) scrollbar is on the page top and user wants to scroll up
				// 2) scrollbar is on the page bottom and user wants to scroll down
				if (!((edge.page.y < 0 && scrollPosition <= 0) || (edge.page.y > 0 && scrollPosition >= (scrollData.height - screen.height)))) {
					// fire autoscroll (this should happen only once)
					if (edge.flag.y++ === 0) {
						// reset onscroll event
						REDIPS.event.remove(window, 'scroll', calculateCells);
						// call window autoscroll
						autoScrollY(window);
					}
				}
			}
			else {
				edge.page.y = 0;
			}
			// test if dragged object is in scrollable container
			// this code will be executed only if scrollable container (DIV with overflow other than 'visible) exists on page
			for (i = 0; i < scrollData.container.length; i++) {
				// set current scrollable container area
				sca = scrollData.container[i];
				// if dragged object is inside scrollable container and scrollable container has enabled autoscroll option
				if (sca.autoscroll && X < sca.offset[1] && X > sca.offset[3] && Y < sca.offset[2] && Y > sca.offset[0]) {
					// calculate horizontally crossed page bound
					edge.div.x = bound - (sca.midstX  > X ? X - objMargin[3] - sca.offset[3] : sca.offset[1] - X - objMargin[1]);
					// if element crosses page bound then set scroll direction and call auto scroll 
					if (edge.div.x > 0) {
						// in case when object is only half visible (page is scrolled on that object)
						if (edge.div.x > bound) {
							edge.div.x = bound;
						}
						// set scroll direction: negative - left, positive - right
						edge.div.x *= X < sca.midstX ? -1 : 1; 
						// remove onscroll event handler and call autoScrollY function only once
						if (edge.flag.x++ === 0) {
							REDIPS.event.remove(sca.div, 'scroll', calculateCells);
							autoScrollX(sca.div);
						}
					}
					else {
						edge.div.x = 0;
					}
					// calculate vertically crossed page bound
					edge.div.y = bound - (sca.midstY  > Y ? Y - objMargin[0] - sca.offset[0] : sca.offset[2] - Y - objMargin[2]);
					// if element crosses page bound then set scroll direction and call auto scroll
					if (edge.div.y > 0) {
						// in case when object is only half visible (page is scrolled on that object)
						if (edge.div.y > bound) {
							edge.div.y = bound;
						}
						// set scroll direction: negative - up, positive - down
						edge.div.y *= Y < sca.midstY ? -1 : 1;
						// remove onscroll event handler and call autoScrollY function only once
						if (edge.flag.y++ === 0) {
							REDIPS.event.remove(sca.div, 'scroll', calculateCells);
							autoScrollY(sca.div);
						}
					}
					else {
						edge.div.y = 0;
					}
					// break the loop (checking for other scrollable containers is not needed) 
					break;
				}
				// otherwise (I mean dragged object isn't inside any of scrollable container) reset crossed edge
				else {
					edge.div.x = edge.div.y = 0;
				} 
			}
		} // if autoScroll is enabled
		// stop all propagation of the event in the bubbling phase.
		// (save system resources by turning off event bubbling / propagation)
		evt.cancelBubble = true;
		if (evt.stopPropagation) {
			evt.stopPropagation();
		}
	};


	/**
	 * This method is called (from handlerOnMouseMove, autoScrollX, autoScrollY) in case of change of current table cell.
	 * When change happens, then return background color to old position, highlight new position, calculate cell boundaries and call event.changed.
	 * @see <a href="#handlerOnMouseMove">handlerOnMouseMove</a>
	 * @see <a href="#autoScrollX">autoScrollX</a>
	 * @see <a href="#autoScrollY">autoScrollY</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	cellChanged = function () {
		if (table < tables.length && (table !== table_old || row !== row_old || cell !== cell_old)) {
			// set cell background color to the previous cell
			if (table_old !== null && row_old !== null && cell_old !== null) {
				// set background color for previous table cell
				setTdStyle(table_old, row_old, cell_old, bgStyleOld);
				// define previous table cell
				REDIPS.drag.td.previous = td.previous = tables[table_old].rows[row_old].cells[cell_old];
				// define current table cell
				REDIPS.drag.td.current = td.current = tables[table].rows[row].cells[cell];
				// if drop option is 'switching' and drag mode is 'cell' (not 'row')
				// then replace content from current cell to the previous cell
				if (REDIPS.drag.dropMode === 'switching' && mode === 'cell') {
					// move objects from current cell to the previous cell
					relocate(td.current, td.previous);
					// recalculate table cells again (because cell content could change row dimensions) 
					calculateCells();
					// set current table cell again (because cell content can be larger then cell itself)
					setTableRowColumn();
				}
				// target cell changed - call event.changed handler 
				if (mode === 'cell') {
					REDIPS.drag.event.changed(td.current);
				}
				// for mode === 'row', table or row should change (changing cell in the same row will be ignored)
				else if (mode === 'row' && (table !== table_old || row !== row_old)) {
					REDIPS.drag.event.rowChanged(td.current);
				}
			}
			// set color for the current table cell and remembers previous position and color
			setPosition();
		}		
	};


	/**
	 * In initialization phase, this method is attached as onresize event handler for window.
	 * It also calculates window width and window height. Result is saved in variables screen.width and screen.height visible inside REDIPS.drag private scope.
	 * @see <a href="#init">init</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	handlerOnResize = function () {
		// Non-IE
		if (typeof(window.innerWidth) === 'number') {
			screen.width  = window.innerWidth;
			screen.height = window.innerHeight;
		}
		// IE 6+ in 'standards compliant mode'
		else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
			screen.width  = document.documentElement.clientWidth;
			screen.height = document.documentElement.clientHeight;
		}
		// IE 4 compatible
		else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
			screen.width  = document.body.clientWidth;
			screen.height = document.body.clientHeight;
		}
		// set scroll size (onresize, onload and onmouseup event)
		scrollData.width  = document.documentElement.scrollWidth;
		scrollData.height = document.documentElement.scrollHeight;
		// calculate colums and rows offset (cells dimensions)
		calculateCells();  
	};


	/**
	 * Method sets current table, row and cell.
	 * Current cell position is based on position of mouse pointer and calculated grid of tables inside drag container.
	 * Method contains logic for dropping rules like marked/forbidden table cells.
	 * Rows with display='none' are not contained in row_offset array so row bounds calculation should take care about sparse arrays (since version 4.3.6).
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	setTableRowColumn = function () {
		var previous,	// set previous position (current cell will not be highlighted) 
			cell_current,	// define current cell (needed for some test at the function bottom)
			row_offset,		// row offsets for the selected table (row box bounds)
			row_found,		// remember found row
			cells,			// number of cells in the selected row
			empty,			// (boolean) flag indicates if table cell is empty or not
			mark_found,		// (boolean) found "mark" class name
			only_found,		// (boolean) found "only" class name
			single_cell,	// table cell can be defined as single
			tos = [],		// table offset
			X, Y,			// X and Y position of mouse pointer
			i;				// used in local loop
		// set previous position (current cell will not be highlighted)
		previous = function () {
			if (table_old !== null && row_old !== null && cell_old !== null) {
				table = table_old;
				row = row_old;
				cell = cell_old;
			}
		};
		// prepare X and Y position of mouse pointer
		X = pointer.x;
		Y = pointer.y;
		// find table below draggable object
		for (table = 0; table < tables.length; table++) {
			// if table is not enabled then skip table
			// by default tables don't have set redips.enabled property (undefined !== false)
			if (tables[table].redips.enabled === false) {
				continue;
			}
			// prepare table offset
			tos[0] = tables[table].redips.offset[0]; // top
			tos[1] = tables[table].redips.offset[1]; // right
			tos[2] = tables[table].redips.offset[2]; // bottom
			tos[3] = tables[table].redips.offset[3]; // left
			// if table belongs to the scrollable container then set scrollable container offset if needed
			// in case when some parts of table are hidden (for example with "overflow: auto")
			if (tables[table].sca !== undefined) {
				tos[0] = tos[0] > tables[table].sca.offset[0] ? tos[0] : tables[table].sca.offset[0]; // top
				tos[1] = tos[1] < tables[table].sca.offset[1] ? tos[1] : tables[table].sca.offset[1]; // right
				tos[2] = tos[2] < tables[table].sca.offset[2] ? tos[2] : tables[table].sca.offset[2]; // bottom
				tos[3] = tos[3] > tables[table].sca.offset[3] ? tos[3] : tables[table].sca.offset[3]; // left
			}
			// mouse pointer is inside table (or scrollable container)
			if (tos[3] < X && X < tos[1] && tos[0] < Y && Y < tos[2]) {
				// define row offsets for the selected table (row box bounds)
				row_offset = tables[table].redips.row_offset;
				// find the current row (loop skips hidden rows)
				for (row = 0; row < row_offset.length - 1; row++) {
					// if row doesn't exist (in case of hidden row) - skip it
					if (row_offset[row] === undefined) {
						continue;
					}
					// set top and bottom cell bounds
					currentCell[0] = row_offset[row][0];
					// set bottom cell bound (if is possible) - hidden row doesn't exist
					if (row_offset[row + 1] !== undefined) {
						currentCell[2] = row_offset[row + 1][0];
					}
					// hidden row (like style.display === 'none')
					else {
						// search for next visible row
						for (i = row + 2; i < row_offset.length; i++) {
							// visible row found
							if (row_offset[i] !== undefined) {
								currentCell[2] = row_offset[i][0];
								break;
							}
						}
					}
					// top bound of the next row
					if (Y <= currentCell[2]) {
						break;
					}
				}
				// remember found row
				row_found = row;
				// if loop exceeds, then set bounds for the last row (offset for the last row doesn't work in IE8, so use table bounds) 
				if (row === row_offset.length - 1) {
					currentCell[0] = row_offset[row][0];
					currentCell[2] = tables[table].redips.offset[2];
				}
				// do loop - needed for rowspaned cells (if there is any)
				do {
					// set the number of cells in the selected row
					cells = tables[table].rows[row].cells.length - 1;
					// find current cell (X mouse position between cell offset left and right)
					for (cell = cells; cell >= 0; cell--) {
						// row left offset + cell left offset
						currentCell[3] = row_offset[row][3] + tables[table].rows[row].cells[cell].offsetLeft;
						// cell right offset is left offset + cell width  
						currentCell[1] = currentCell[3] + tables[table].rows[row].cells[cell].offsetWidth;
						// is mouse pointer is between left and right offset, then cell is found
						if (currentCell[3] <= X && X <= currentCell[1]) {
							break;
						}
					}
				} // if table contains rowspaned cells and mouse pointer is inside table but cell was not found (hmm, rowspaned cell - try in upper row)
				while (tables[table].redips.rowspan && cell === -1 && row-- > 0);
				// if cell < 0 or row < 0 then use last possible location
				if (row < 0 || cell < 0) {
					previous();
				}
				// current cell found but if current row differ from previously found row (thanks too while loop with row--)
				// then test if Y is inside current cell
				// (this should prevent case where TD border > 1px and upper colspaned row like in example15)
				// logic will end in upper colspaned row while current row will not move - and that was wrong
				else if (row !== row_found) {
					// recalculate top and bottom row offset (again)
					currentCell[0] = row_offset[row][0];
					currentCell[2] = currentCell[0] + tables[table].rows[row].cells[cell].offsetHeight;
					// if Y is outside of the current row, return previous location 
					if (Y < currentCell[0] || Y > currentCell[2]) {
						previous();
					}
				}
				// set current cell (for easier access in test below)
				cell_current = tables[table].rows[row].cells[cell];
				// if current cell contain nested table(s) then set currentCell.containTable property
				// needed in handlerOnMouseMove() - see around line 1070
				if (cell_current.childNodes.length > 0 && cell_current.getElementsByTagName('table').length > 0) {
					currentCell.containTable = true;
				}
				else {
					currentCell.containTable = false;
				}
				// if current cell isn't trash cell, then search for marks in class name
				if (cell_current.className.indexOf(REDIPS.drag.trash.className) === -1) {
					// search for 'only' class name
					only_found = cell_current.className.indexOf(REDIPS.drag.only.cname) > -1 ? true : false;
					// if current cell is marked with 'only' class name
					if (only_found === true) {
						// marked cell "only" found, test for defined pairs (DIV id -> class name)
						if (cell_current.className.indexOf(only.div[obj.id]) === -1) {
							previous();
							break;
						}
					}
					// DIV objects marked with "only" can't be placed to other cells (if property "other" is "deny")
					else if (only.div[obj.id] !== undefined && only.other === 'deny') {
						previous();
						break;
					}
					else {
						// search for 'mark' class name
						mark_found = cell_current.className.indexOf(REDIPS.drag.mark.cname) > -1 ? true : false;
						// if current cell is marked and access type is 'deny' or current cell isn't marked and access type is 'allow'
						// then return previous location
						if ((mark_found === true && REDIPS.drag.mark.action === 'deny') || (mark_found === false && REDIPS.drag.mark.action === 'allow')) {
							// marked cell found, but make exception if defined pairs "DIV id -> class name" exists (return previous location)
							if (cell_current.className.indexOf(mark.exception[obj.id]) === -1) {
								previous();
								break;
							}
						}
					}
				}
				// test if current cell is defined as single
				single_cell = cell_current.className.indexOf('single') > -1 ? true : false;
				// if drag mode is "cell"
				if (mode === 'cell') {
					// if dropMode == single or current cell is single and current cell contains nodes then test if cell is occupied
					if ((REDIPS.drag.dropMode === 'single' || single_cell) && cell_current.childNodes.length > 0) {
						// if cell has only one node and that is text node then break - because this is empty cell
						if (cell_current.childNodes.length === 1 && cell_current.firstChild.nodeType === 3) {
							break;
						}
						// intialize "empty" flag to true
						empty = true;
						// open loop for each child node and jump out if 'drag' className found
						for (i = cell_current.childNodes.length - 1; i >= 0; i--) {
							if (cell_current.childNodes[i].className && cell_current.childNodes[i].className.indexOf('drag') > -1) {
								empty = false;
								break;
							} 
						}
						// if cell is not empty and old position exists ...
						if (!empty && table_old !== null && row_old !== null && cell_old !== null) {
							// .. and current position is different then source position then return previous position
							if (table_source !== table || row_source !== row || cell_source !== cell) {
								previous();
								break;
							}
						}
					}
					// current cell is marked as row handler and user is dragging DIV element over it - do not enable  
					if (cell_current.className.indexOf('rowhandler') > -1) {
						previous();
						break;
					}
					// if current row is defined as emptyRow, elements can't be dropped to these cells
					if (cell_current.parentNode.redips && cell_current.parentNode.redips.emptyRow) {
						previous();
						break;
					}
				}
				// break table loop 
				break;
			}
		}
	};


	/**
	 * Method sets background color for the current table cell and remembers previous position and background color.
	 * It is called from handlerOnMouseMove and cellChanged.
	 * @see <a href="#handlerOnMouseMove">handlerOnMouseMove</a>
	 * @see <a href="#cellChanged">cellChanged</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	setPosition = function () {
		// in case if ordinary element is placed inside 'deny' table cell
		if (table < tables.length && table !== null && row !== null && cell !== null) {
			// remember background color before setting the new background color
			bgStyleOld = getTdStyle(table, row, cell);
			// highlight current TD / TR (colors and styles are read from public property "hover"
			setTdStyle(table, row, cell);
			// remember current position (for table, row and cell)
			table_old = table;
			row_old = row;
			cell_old = cell;
		}
	};


	/**
	 * Method sets table cell(s) background styles (background colors and border styles).
	 * If tdStyle is undefined then current td/tr will be highlighted from public property hover.color_td, hover.color_tr ...
	 * @param {Integer} ti Table index.
	 * @param {Integer} ri Row index.
	 * @param {Integer} ci Cell index.
	 * @param {Object} t Object contains background color and border styles ("t" is TD style object is prepared in getTdStyle method).
	 * @see <a href="#getTdStyle">getTdStyle</a>
	 * @see <a href="#setPosition">setPosition</a>
	 * @see <a href="#cellChanged">cellChanged</a>
	 * @see <a href="#handlerOnMouseUp">handlerOnMouseUp</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	setTdStyle = function (ti, ri, ci, t) {
		// reference to the table row, loop variable and td.style
		var tr, i, s;
		// if drag mode is "cell" and threshold distance is prevailed
		if (mode === 'cell' && threshold.flag) {
			// set TD style reference
			s = tables[ti].rows[ri].cells[ci].style;
			// TD background color - tdStyle is undefined then highlight TD otherwise return previous background color
			s.backgroundColor = (t === undefined) ? REDIPS.drag.hover.colorTd : t.color[0].toString();
			// TD border - if hover.borderTd is set then take care of border style
			if (REDIPS.drag.hover.borderTd !== undefined) {
				// set border (highlight)
				if (t === undefined) {
					s.border = REDIPS.drag.hover.borderTd;
				}
				// return previous state (exit from TD)
				else {
					s.borderTopWidth = t.top[0][0];
					s.borderTopStyle = t.top[0][1];
					s.borderTopColor = t.top[0][2];
					s.borderRightWidth = t.right[0][0];
					s.borderRightStyle = t.right[0][1];
					s.borderRightColor = t.right[0][2];
					s.borderBottomWidth = t.bottom[0][0];
					s.borderBottomStyle = t.bottom[0][1];
					s.borderBottomColor = t.bottom[0][2];
					s.borderLeftWidth = t.left[0][0];
					s.borderLeftStyle = t.left[0][1];
					s.borderLeftColor = t.left[0][2];
				}
			}
		}
		// or drag mode is "row"
		else if (mode === 'row') {
			// set reference to the current table row
			tr = tables[ti].rows[ri];
			// set colors to table cells (respectively) or first color to all cells (in case of settings hover to the row)
			for (i = 0; i < tr.cells.length; i++) {
				// set reference to current TD style
				s = tr.cells[i].style;
				// TR background color - tdStyle is undefined then highlight TD otherwise return previous background color
				s.backgroundColor = (t === undefined) ? REDIPS.drag.hover.colorTr : t.color[i].toString();
				// TR border - if hover.borderTd is set then take care of border style
				if (REDIPS.drag.hover.borderTr !== undefined) {
					// set border (highlight) - source row will not have any border
					if (t === undefined) {
						// target is current table
						if (table === table_source) {
							// if row is moved above source row in current table
							if (row < row_source) {
								s.borderTop = REDIPS.drag.hover.borderTr;
							}
							// if row is moved below source row in current table 
							else {
								s.borderBottom = REDIPS.drag.hover.borderTr;
							}
						}
						// target is other table (where row will be placed is defined with public property REDIPS.drag.rowDropMode)
						else {
							// highlight top border
							if (REDIPS.drag.rowDropMode === 'before') {
								s.borderTop = REDIPS.drag.hover.borderTr;
							}
							// highlight bottom border
							else {
								s.borderBottom = REDIPS.drag.hover.borderTr;
							}
						}
					}
					// return previous state borderTop and borderBottom (exit from TD)
					else {
						s.borderTopWidth = t.top[i][0];
						s.borderTopStyle = t.top[i][1];
						s.borderTopColor = t.top[i][2];
						s.borderBottomWidth = t.bottom[i][0];
						s.borderBottomStyle = t.bottom[i][1];
						s.borderBottomColor = t.bottom[i][2];

					}
				}
			}
		}
	};


	/**
	 * Method s returns background and border styles as object for the input parameters table index, row index and cell index.
	 * @param {Integer} t Table index.
	 * @param {Integer} r Row index.
	 * @param {Integer} c Cell index.
	 * @return {Object} Object containing background color and border styles (for the row or table cell).
	 * @see <a href="#setTdStyle">setTdStyle</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	getTdStyle = function (ti, ri, ci) {
		var tr, i, c, // reference to the table row, loop variable and td reference
			// define TD style object with background color and border styles: top, right, bottom and left
			t = {color: [], top: [], right: [], bottom: [], left: []},
			// private method gets border styles: top, right, bottom, left
			border = function (c, name) {
				var width = 'border' + name + 'Width',
					style = 'border' + name + 'Style',
					color = 'border' + name + 'Color';			
				return [getStyle(c, width), getStyle(c, style), getStyle(c, color)];
			};
		// if drag mode is "cell" tdStyle.color and tdStyle.border will have only one value
		if (mode === 'cell') {
			// set TD reference
			c = tables[ti].rows[ri].cells[ci];
			// remember background color
			t.color[0] = c.style.backgroundColor;
			// remember top, right, bottom and left TD border styles if hover.borderTd property is set
			if (REDIPS.drag.hover.borderTd !== undefined) {
				t.top[0] = border(c, 'Top');
				t.right[0] = border(c, 'Right');
				t.bottom[0] = border(c, 'Bottom');
				t.left[0] = border(c, 'Left');
			}
		}
		// if drag mode is "row", then color array will contain color for each table cell
		else {
			// set reference to the current table row
			tr = tables[ti].rows[ri];
			// remember styles for each table cell
			for (i = 0; i < tr.cells.length; i++) {
				// set TD reference
				c = tr.cells[i];
				// remember background color
				t.color[i] = c.style.backgroundColor;
				// remember top and bottom TD border styles if hover.borderTr property is set
				if (REDIPS.drag.hover.borderTr !== undefined) {
					t.top[i] = border(c, 'Top');
					t.bottom[i] = border(c, 'Bottom');
				}
			}
		}
		// return TD style object
		return t;
	};


	/**
	 * Method returns array of element bounds (offset) top, right, bottom and left (needed for table grid calculation).
	 * @param {HTMLElement} box HTMLElement for box metrics.
	 * @param {String} [position] HTMLElement "position" style. Elements with style "fixed" will not have included page scroll offset.
	 * @param {Boolean} [box_scroll] If set to "false" then element scroll offset will not be included in calculation (default is "true").
	 * @return {Array} Box offset array: [ top, right, bottom, left ]
	 * @example
	 * // calculate box offset for the div id="drag"
	 * divbox = boxOffset(dragContainer);
	 * @example
	 * // include scroll position in offset
	 * offset = boxOffset(rowObj, 'fixed');
	 * @example
	 * // get DIV offset with or without "page scroll" and excluded element scroll offset
	 * cb = boxOffset(div, position, false);
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	boxOffset = function (box, position, box_scroll) {
		var scrollPosition,	// get scroll position
			oLeft = 0,		// define offset left (take care of horizontal scroll position)
			oTop  = 0,		// define offset top (take care od vertical scroll position)
			boxOld = box;	// remember box object
		// if table_position is undefined, '' or 'page_scroll' then include page scroll offset
		if (position !== 'fixed') {
			scrollPosition = getScrollPosition();	// get scroll position
			oLeft = 0 - scrollPosition[0];			// define offset left (take care of horizontal scroll position)
			oTop  = 0 - scrollPosition[1];			// define offset top (take care od vertical scroll position)
		}
		// climb up through DOM hierarchy (getScrollPosition() takes care about page scroll positions)
		if (box_scroll === undefined || box_scroll === true) {
			do {
				oLeft += box.offsetLeft - box.scrollLeft;
				oTop += box.offsetTop - box.scrollTop;
				box = box.offsetParent;
			}
			while (box && box.nodeName !== 'BODY');
		}
		// climb up to the BODY element but without scroll positions
		else {
			do {
				oLeft += box.offsetLeft;
				oTop += box.offsetTop;
				box = box.offsetParent;
			}
			while (box && box.nodeName !== 'BODY');
		}
		// return box offset array
		//        top                 right,                     bottom           left
		return [ oTop, oLeft + boxOld.offsetWidth, oTop + boxOld.offsetHeight, oLeft ];
	};

 
	/** 
	 * Method is called in every possible case when position or size of table grid could change like: page scrolling, element dropped to the table cell, element start dragging and so on.
	 * It calculates table row offsets (table grid) and saves to the "tables" array.
	 * Table rows with style display='none' are skipped.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	calculateCells = function () {
		var i, j,		// local variables used in loops
			row_offset,	// row box
			position,	// if element (table or table container) has position:fixed then "page scroll" offset should not be added
			cb;			// box offset for container box (cb)
		// open loop for each HTML table inside id=drag (table array is initialized in init() function)
		for (i = 0; i < tables.length; i++) {
			// initialize row_offset array
			row_offset = [];
			// set table style position (to exclude "page scroll" offset from calculation if needed) 
			position = getStyle(tables[i], 'position');
			// if table doesn't have style position:fixed then table container should be tested
			if (position !== 'fixed') {
				position = getStyle(tables[i].parentNode, 'position');
			}
			// backward loop has better perfomance
			for (j = tables[i].rows.length - 1; j >= 0; j--) {
				// add rows to the offset array if row is not hidden 
				if (tables[i].rows[j].style.display !== 'none') {
					row_offset[j] = boxOffset(tables[i].rows[j], position);
				}
			}
			// save table informations (table offset and row offsets)
			tables[i].redips.offset = boxOffset(tables[i], position);
			tables[i].redips.row_offset = row_offset;
		}
		// calculate box offset for the div id=drag
		divBox = boxOffset(dragContainer);
		// update scrollable container areas if needed
		for (i = 0; i < scrollData.container.length; i++) {
			// set container box style position (to exclude page scroll offset from calculation if needed) 
			position = getStyle(scrollData.container[i].div, 'position');
			// get DIV container offset with or without "page scroll" and excluded scroll position of the content
			cb = boxOffset(scrollData.container[i].div, position, false);
			// prepare scrollable container areas
			scrollData.container[i].offset = cb;
			scrollData.container[i].midstX = (cb[1] + cb[3]) / 2;
			scrollData.container[i].midstY = (cb[0] + cb[2]) / 2;
		}
	};


	/**
	 * Method returns current page scroll position as array.
	 * @return {Array} Returns array with two members [ scrollX, scrollY ].
	 * @public
	 * @function
	 * @name REDIPS.drag#getScrollPosition
	 */
	getScrollPosition = function () {
		// define local scroll position variables
		var scrollX, scrollY;
		// Netscape compliant
		if (typeof(window.pageYOffset) === 'number') {
			scrollX = window.pageXOffset;
			scrollY = window.pageYOffset;
		}
		// DOM compliant
		else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
			scrollX = document.body.scrollLeft;
			scrollY = document.body.scrollTop;
		}
		// IE6 standards compliant mode
		else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
			scrollX = document.documentElement.scrollLeft;
			scrollY = document.documentElement.scrollTop;
		}
		// needed for IE6 (when vertical scroll bar was on the top)
		else {
			scrollX = scrollY = 0;
		}
		// return scroll positions
		return [ scrollX, scrollY ];
	};


	/**
	 * Horizontal auto scroll method.
	 * @param {HTMLElement} so Window or DIV element (so - scroll object).
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	autoScrollX = function (so) {
		var pos,			// left style position
			old,			// old window scroll position (needed for window scrolling)
			scrollPosition,	// define current scroll position
			maxsp,			// maximum scroll position
			edgeCrossed,	// crossed edge for window and scrollable container
			X = pointer.x,	// define pointer X position
			Y = pointer.y;	// define pointer Y position
		// if mouseup then stop handling "current cell"
		if (edge.flag.x > 0) {
			// calculate cell (autoscroll is working)
			calculateCells();
			// set current table row and table cell
			setTableRowColumn();
			// set current table, row and cell if mouse pointer is inside DIV id="drag"
			if (X < divBox[1] && X > divBox[3] && Y < divBox[2] && Y > divBox[0]) {
				cellChanged();
			}
		}
		// save scroll object to the global variable for the first call from handlerOnMouseMove
		// recursive calls will not enter this code and reference to the scrollData.obj will be preserved
		if (typeof(so) === 'object') {
			scrollData.obj = so;
		}
		// window autoscroll (define current, old and maximum scroll position)
		if (scrollData.obj === window) {
			scrollPosition = old = getScrollPosition()[0];
			maxsp = scrollData.width - screen.width;
			edgeCrossed = edge.page.x;
		}
		// scrollable container (define current and maximum scroll position)
		else {
			scrollPosition = scrollData.obj.scrollLeft;
			maxsp = scrollData.obj.scrollWidth - scrollData.obj.clientWidth;
			edgeCrossed = edge.div.x;
		}
		// if scrolling is possible
		if (edge.flag.x > 0 && ((edgeCrossed < 0 && scrollPosition > 0) || (edgeCrossed > 0 && scrollPosition < maxsp))) {
			// if object is window
			if (scrollData.obj === window) {
				// scroll window
				window.scrollBy(edgeCrossed, 0);
				// get new window scroll position (after scrolling)
				// because at page top or bottom edgeY can be bigger then the rest of scrolling area
				// it will be nice to know how much was window scrolled after scrollBy command 
				scrollPosition = getScrollPosition()[0];
				// get current object top style
				pos = parseInt(obj.style.left, 10);
				if (isNaN(pos)) {
					pos = 0;
				}
			}
			// or scrollable container
			else {
				scrollData.obj.scrollLeft += edgeCrossed;
			}
			// recursive autoscroll call 
			setTimeout(autoScrollX, REDIPS.drag.scroll.speed);
		}
		// autoscroll is ended: element is out of the page edge or maximum position is reached (left or right)
		else {
			// return onscroll event handler (to window or div element)
			REDIPS.event.add(scrollData.obj, 'scroll', calculateCells);
			// reset auto scroll flag X
			edge.flag.x = 0;
			// reset current cell position
			currentCell = [0, 0, 0, 0];
		}
	};


	/**
	 * Vertical auto scroll method.
	 * @param {HTMLElement} so Window or DIV element (so - scroll object).
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	autoScrollY = function (so) {
		var pos,			// top style position
			old,			// old window scroll position (needed for window scrolling)
			scrollPosition,	// define current scroll position
			maxsp,			// maximum scroll position
			edgeCrossed,	// crossed edge for window and scrollable container
			X = pointer.x,	// define pointer X position
			Y = pointer.y;	// define pointer Y position
		// if mouseup then stop handling "current cell"
		if (edge.flag.y > 0) {
			// calculate cell (autoscroll is working)
			calculateCells();
			// set current table row and table cell
			setTableRowColumn();
			// set current table, row and cell if mouse pointer is inside DIV id="drag"
			if (X < divBox[1] && X > divBox[3] && Y < divBox[2] && Y > divBox[0]) {
				cellChanged();
			}
		}
		// save scroll object to the global variable for the first call from handlerOnMouseMove
		// recursive calls will not enter this code and reference to the scrollData.obj will be preserved
		if (typeof(so) === 'object') {
			scrollData.obj = so;
		}
		// window autoscroll (define current, old and maximum scroll position)
		if (scrollData.obj === window) {
			scrollPosition = old = getScrollPosition()[1];
			maxsp = scrollData.height - screen.height;
			edgeCrossed = edge.page.y;
		}
		// scrollable container (define current and maximum scroll position)
		else {
			scrollPosition = scrollData.obj.scrollTop;
			maxsp = scrollData.obj.scrollHeight - scrollData.obj.clientHeight;
			edgeCrossed = edge.div.y;
		}
		// if scrolling is possible
		if (edge.flag.y > 0 && ((edgeCrossed < 0 && scrollPosition > 0) || (edgeCrossed > 0 && scrollPosition < maxsp))) {
			// if object is window
			if (scrollData.obj === window) {
				// scroll window
				window.scrollBy(0, edgeCrossed);
				// get new window scroll position (after scrolling)
				// because at page top or bottom edgeY can be bigger then the rest of scrolling area
				// it will be nice to know how much was window scrolled after scrollBy command 
				scrollPosition = getScrollPosition()[1];
				// get current object top style
				pos = parseInt(obj.style.top, 10);
				if (isNaN(pos)) {
					pos = 0;
				}
			}
			// or scrollable container
			else {
				scrollData.obj.scrollTop += edgeCrossed;
			}
			// recursive autoscroll call 
			setTimeout(autoScrollY, REDIPS.drag.scroll.speed);
		}
		// autoscroll is ended: element is out of the page edge or maximum position is reached (top or bottom)
		else {
			// return onscroll event handler (to window or div element)
			REDIPS.event.add(scrollData.obj, 'scroll', calculateCells);
			// reset auto scroll flag Y
			edge.flag.y = 0;
			// reset current cell position
			currentCell = [0, 0, 0, 0];
		}
	};
	

	/**
	 * Method clones DIV element and returns cloned element reference.
	 * "clone" class name will not be copied in cloned element (in case if source element contains "clone" class name).
	 * This method is called internally when DIV elements are cloned.
	 * @param {HTMLElement} div DIV element to clone.
	 * @param {Boolean} [drag] If set to true, then cloned DIV element will be ready for dragging (otherwise element will be only cloned).
	 * @return {HTMLElement} Returns cloned DIV element.
	 * @public
	 * @function
	 * @name REDIPS.drag#cloneObject
	 */
	cloneObject = function (div, drag) {
		var divCloned = div.cloneNode(true),	// cloned DIV element
			cname = divCloned.className,		// set class names of cloned DIV element
			offset,								// offset of the original object
			offsetDragged;						// offset of the new object (cloned)
		// if cloned DIV element should be ready for dragging
		if (drag === true) {
			// append cloned element to the DIV id="redips_clone"
			document.getElementById('redips_clone').appendChild(divCloned);
			// set high z-index
			divCloned.style.zIndex = 999;
			// set style to fixed to allow dragging DIV object
			divCloned.style.position = 'fixed';
			// set offset for original and cloned element
			offset = boxOffset(div);
			offsetDragged = boxOffset(divCloned);
			// calculate top and left offset of the new object
			divCloned.style.top   = (offset[0] - offsetDragged[0]) + 'px';
			divCloned.style.left  = (offset[3] - offsetDragged[3]) + 'px';
		}
		// get IE (all versions) to allow dragging outside the window (?!)
		// this was needed here also -  despite setCaputure in onmousedown
		if (divCloned.setCapture) {
			divCloned.setCapture();
		}
		// remove "clone" and "climitX_Y" class names
		cname = cname.replace('clone', '');
		cname = cname.replace(/climit(\d)_(\d+)/, '');
		// set class names with normalized spaces to the cloned DIV element
		divCloned.className = normalize(cname);
		// if counter is undefined, set 0
		if (clonedId[div.id] === undefined) {
			clonedId[div.id] = 0;
		}
		// set id for cloned element (append id of "clone" element - tracking the origin)
		// id is separated with "c" ("_" is already used to compound id, table, row and column)  
		divCloned.id = div.id + 'c' + clonedId[div.id];
		// increment clonedId for cloned element
		clonedId[div.id] += 1;
		// copy custom properties to the DIV element and child DIV elements and register event handlers
		copyProperties(div, divCloned);
		// return reference to the cloned DIV element	
		return (divCloned);
	};


	/**
	 * Method copies custom properties from source element to the cloned element and sets event handlers (onmousedown and ondblclick).
	 * This action will be taken on DIV element itself and all child DIV elements.
	 * Needed in case when DIV element is cloned or ROW is cloned (for dragging mode="row").
	 * @param {HTMLElement} src Source element (DIV or TR element).
	 * @param {HTMLElement} cln Cloned element (DIV or TR element).
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	copyProperties = function (src, cln) {
		var	copy = [],	// copy method
			childs;		// copy properties for child elements (this method calls "copy" method)
		// define copy method for DIV elements (e1 source element, e2 cloned element)
		// http://stackoverflow.com/questions/4094811/javascript-clonenode-and-properties
		copy[0] = function (e1, e2) {
			// if redips property exists in source element
			if (e1.redips) {
				// copy custom properties (redips.enabled,  redips.container ...)
				e2.redips = {};
				e2.redips.enabled = e1.redips.enabled;
				e2.redips.container = e1.redips.container;
				// set onmousedown, ontouchstart and ondblclick event handler if source element is enabled
				if (e1.redips.enabled) {
					registerEvents(e2);
				}
			}
		};
		// define copy method for TR elements
		copy[1] = function (e1, e2) {
			// if redips property exists in source element
			if (e1.redips) {
				// copy custom properties (redips.emptyRow ...)
				e2.redips = {};
				e2.redips.emptyRow = e1.redips.emptyRow;
			}
		};
		// define method to copy properties for child elements (input parameter is element index 0 - DIV, 1 - TR)
		childs = function (e) {
			var	el1, el2,			// collection of DIV/TR elements in source and cloned element
				i,					// loop variable
				tn = ['DIV', 'TR'];	// tag name
			// collect child DIV/TR elements from the source element (possible if div element contains table)
			el1 = src.getElementsByTagName(tn[e]);
			// collect child DIV/TR elements from cloned element
			el2 = cln.getElementsByTagName(tn[e]);
			// copy custom properties (redips.enabled,  redips.container ...) and set event handlers to child DIV elements
			for (i = 0; i < el2.length; i++) {
				copy[e](el1[i], el2[i]);
			}
		};
		// if source element is DIV element then copy custom properties for DIV element
		if (src.nodeName === 'DIV') {
			copy[0](src, cln);
		}
		// if source element is TR element then copy custom properties for TR element
		else if (src.nodeName === 'TR') {
			copy[1](src, cln);
		}
		// copy properties for DIV child elements
		childs(0);
		// copy properties for TR child elements
		childs(1);
	};


	/**
	 * Method updates climit1_X or climit2_X class name (X defines cloning limit).
	 * <ul>
	 * <li>climit1_X - after cloning X elements, last element will be normal drag-able element</li>
	 * <li>climit2_X - after cloning X elements, last element will stay unmovable</li>
	 * </ul>
	 * @param {HTMLElement} el Element on which cname class should be updated.
	 * @param {Integer} value Increment or decrement climit value.
	 * @see <a href="#handlerOnMouseUp">handlerOnMouseUp</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	cloneLimit = function (el, value) {
		// declare local variables 
		var matchArray,	// match array
			limitType,	// limit type (1 - clone becomes "normal" drag element at last; 2 - clone element stays immovable)
			limit,		// limit number
			classes;	// class names of clone element
		// read class name from element
		classes = el.className;
		// match climit class name		
		matchArray = classes.match(/climit(\d)_(\d+)/);
		// if DIV class contains climit
		if (matchArray !== null) {
			// prepare limitType (1 or 2) and limit
			limitType = parseInt(matchArray[1], 10); 
			limit = parseInt(matchArray[2], 10);
			// if current limit is 0 and should be set to 1 then return "cloning" to the DIV element
			if (limit === 0 && value === 1) {
				// add "clone" class to class attribute
				classes += ' clone';
				// enable DIV element for climit2 type
				if (limitType === 2) {
					enableDrag(true, el);
				}
			}
			// update limit value
			limit += value;
			// update climit class name with new limit value
			classes = classes.replace(/climit\d_\d+/g, 'climit' + limitType + '_' + limit);
			// test if limit drops to zero
			if (limit <= 0) {
				// no more cloning, cut out "clone" from class name
				classes = classes.replace('clone', '');
				// if limit type is 2 then disable clone element (it will stay in cell)
				if (limitType === 2) {
					// disable source DIV element
					enableDrag(false, el);
					// call event.clonedEnd2 handler
					REDIPS.drag.event.clonedEnd2(); 
				}
				else {
					// call event.clonedEnd1 handler
					REDIPS.drag.event.clonedEnd1();
				}
			}
			// normalize spaces and return classes to the clone object 
			el.className = normalize(classes);
		}
	};


	/**
	 * Method returns true or false if element needs to have control.
	 * Elements like A, INPUT, SELECT, OPTION, TEXTAREA should have its own control (method returns "true").
	 * If element contains "nodrag" class name then dragging will be skipped (see example11 "Drag handle on titlebar").
	 * <ul>
	 * <li>true - click on element will not start dragging (element has its own control)</li>
	 * <li>false - click on element will start dragging</li>
	 * </ul>
	 * @param {Event} evt Event information.
	 * @return {Boolean} Returns true or false if element needs to have control.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	elementControl = function (evt) {
		// declare elementControl flag, source tag name and element classes
		var flag = false,
			srcName,
			classes,						// class names of DIV element;
			regexNodrag = /\bnodrag\b/i;	// regular expression to search "nodrag" class name 
		// set source tag name and classes for IE and FF
		if (evt.srcElement) {
			srcName = evt.srcElement.nodeName;
			classes = evt.srcElement.className;
		}
		else {
			srcName = evt.target.nodeName;
			classes = evt.target.className;
		}
		// set flag (true or false) for clicked elements
		switch (srcName) {
		case 'A':
		case 'INPUT':
		case 'SELECT':
		case 'OPTION':
		case 'TEXTAREA':
			flag = true;
			break;
		// none of form elements
		default:
			// if element has "nodrag" class name then dragging will be skipped 
			if (regexNodrag.test(classes)) {
				flag = true;
			}
			else {
				flag = false;
			}
		}
		// return true/false flag
		return flag;
	};


	/**
	 * Method attaches / detaches onmousedown, ontouchstart and ondblclick events to DIV elements and attaches onscroll event to the scroll containers in initialization phase.
	 * It also can be used for element initialization after DIV element was manually added to the table.
	 * If class attribute of DIV container contains "noautoscroll" class name then autoScroll option will be disabled.
	 * @param {Boolean|String} enableFlag Enable / disable element (or element subtree like table, dragging container ...).
	 * @param {HTMLElement|String} [el] HTML node or CSS selector to enable / disable. Parameter defines element reference or CSS selector of DIV elements to enable / disable.
	 * @example
	 * // enable element with id="id123"
	 * rd.enableDrag(true, '#id123');
	 *  
	 * // or init manually added element with known id
	 * REDIPS.drag.enableDrag(true, '#id234');
	 *  
	 * // disable all DIV elements in drag1 subtree 
	 * rd.enableDrag(false, '#drag1 div')
	 *  
	 * // init DIV elements in dragging area (including newly added DIV element)
	 * REDIPS.drag.enableDrag('init');
	 *  
	 * // init added element with reference myElement
	 * REDIPS.drag.enableDrag(true, myElement);
	 *  
	 * // disable all DIV elements within TD (td is reference to TD node)
	 * REDIPS.drag.enableDrag(false, td);
	 * @public
	 * @function
	 * @name REDIPS.drag#enableDrag
	 * @see <a href="#enableTable">enableTable</a>
	 */
	enableDrag = function (enable_flag, el) {
		// define local variables
		var i, j, k,		// local variables used in loop
			div = [],		// collection of div elements contained in tables or one div element
			tbls = [],		// collection of tables inside scrollable container
			borderStyle,	// border style (solid or dotted)
			opacity,		// (integer) set opacity for enabled / disabled elements
			cursor,			// cursor style (move or auto)
			overflow,		// css value of overflow property
			autoscroll,		// boolean - if scrollable container will have autoscroll option (default is true)
			enabled,		// enabled property (true or false) 
			cb,				// box offset for container box (cb)
			position,		// if table container has position:fixed then "page scroll" offset should not be added
			regexDrag = /\bdrag\b/i,	// regular expression to search "drag" class name
			regexNoAutoscroll = /\bnoautoscroll\b/i;	// regular expression to search "noautoscroll" class name
		// set opacity for disabled elements from public property "opacityDisabled" 
		opacity = REDIPS.drag.style.opacityDisabled;
		// set styles for enabled DIV element
		if (enable_flag === true || enable_flag === 'init') {
			borderStyle = REDIPS.drag.style.borderEnabled;
			cursor = 'move';
			enabled = true;
		}
		// else set styles for disabled DIV element
		else {
			borderStyle = REDIPS.drag.style.borderDisabled;
			cursor = 'auto';
			enabled = false;
		}
		// collect DIV elements inside current drag area (drag elements and scroll containers)
		// e.g. enableDrag(true)
		if (el === undefined) {
			div = dragContainer.getElementsByTagName('div');
		}
		// "el" is string (CSS selector) - it can collect one DIV element (like "#d12") or many DIV elements (like "#drag1 div")
		else if (typeof(el) === 'string') {
			div = document.querySelectorAll(el);
		}
		// "el" is node reference to element that is not DIV class="drag"
		else if (typeof(el) === 'object' && (el.nodeName !== 'DIV' || el.className.indexOf('drag') === -1)) {
			div = el.getElementsByTagName('div');
		}
		// none of above, el is DIV class="drag", so prepare array with one DIV element
		else {
			div[0] = el;
		}
		// 
		// main loop that goes through all DIV elements
		//
		for (i = 0, j = 0; i < div.length; i++) {
			// if DIV element contains "drag" class name
			if (regexDrag.test(div[i].className)) {
				// add reference to the DIV container (initialization or newly added element to the table)
				// this property should not be changed in later element enable/disable
				if (enable_flag === 'init' || div[i].redips === undefined) {
					// create a "property object" in which all custom properties will be saved
					div[i].redips = {};
					div[i].redips.container = dragContainer;
				}
				// remove opacity mask
				else if (enable_flag === true && typeof(opacity) === 'number') {
					div[i].style.opacity = '';
					div[i].style.filter = '';						
				}
				// set opacity for disabled elements
				else if (enable_flag === false && typeof(opacity) === 'number') {
					div[i].style.opacity = opacity / 100;
					div[i].style.filter = 'alpha(opacity=' + opacity + ')';					
				}
				// register event listener for DIV element
				registerEvents(div[i], enabled);
				// set styles for DIV element
				div[i].style.borderStyle = borderStyle;
				div[i].style.cursor = cursor;
				// add enabled property to the DIV element (true or false)
				div[i].redips.enabled = enabled;
			}
			// attach onscroll event to the DIV element in init phase only if DIV element has overflow other than default value 'visible'
			// and that means scrollable DIV container
			else if (enable_flag === 'init') {
				// ask for overflow style
				overflow = getStyle(div[i], 'overflow');
				// if DIV is scrollable
				if (overflow !== 'visible') {
					// define onscroll event handler for scrollable container
					REDIPS.event.add(div[i], 'scroll', calculateCells);
					// set container box style position (to exclude page scroll offset from calculation if needed) 
					position = getStyle(div[i], 'position');
					// get DIV container offset with or without "page scroll" and excluded scroll position of the content
					cb = boxOffset(div[i], position, false);
					// search for noautoscroll option
					if (regexNoAutoscroll.test(div[i].className)) {
						autoscroll = false;
					}
					else {
						autoscroll = true;
					}
					// prepare scrollable container areas
					scrollData.container[j] = {
						div : div[i],					// reference to the scrollable container
						offset : cb,					// box offset of the scrollable container
						midstX : (cb[1] + cb[3]) / 2,	// middle X
						midstY : (cb[0] + cb[2]) / 2,	// middle Y
						autoscroll : autoscroll			// autoscroll enabled or disabled (true or false)
					};
					// search for tables inside scrollable container
					tbls = div[i].getElementsByTagName('table');
					// loop goes through found tables inside scrollable area 
					for (k = 0; k < tbls.length; k++) {
						// add a reference to the corresponding scrollable area
						tbls[k].sca = scrollData.container[j];
					}
					// increase scrollable container counter
					j++;
				}
			}
		}
	};


	/**
	 * Method deletes DIV element from table.
	 * Input parameter is DIV reference or id of DIV element.
	 * @param {String|HTMLElement} el Id of DIV element or reference of DIV element that should be deleted. 
	 * @example
	 * // delete DIV element in event.dropped() event handler
	 * rd.event.dropped = function () {
	 *     rd.deleteObject(rd.obj);
	 * }
	 *  
	 * // delete DIV element with id="d1"
	 * rd.deleteObject('d1'); 
	 * @public
	 * @function
	 * @name REDIPS.drag#deleteObject
	 */
	deleteObject = function (el) {
		var div, i;
		// if "el" is DIV reference then remove DIV element
		if (typeof(el) === 'object' && el.nodeName === 'DIV') {
			el.parentNode.removeChild(el);
		}
		// else try to delete DIV element with its ID
		else if (typeof(el) === 'string') {
			// search for DIV element inside current drag area (drag elements and scrollable containers)
			div = document.getElementById(el);
			// if div element exists then it will be deleted
			if (div) {
				div.parentNode.removeChild(div);
			}
		}
	};


	/**
	 * This method can select tables by class name and mark them as enabled / disabled.
	 * Instead of class name, it it possible to send table reference for enable / disable.
	 * By default, all tables are enabled to accept dropped elements.
	 * @param {Boolean} enable_flag Enable / disable one or more tables.
	 * @param {String|HTMLElement} el Class name of table(s) to enable/disable or table reference to enable/disable. 
	 * @example
	 * // disable tables with class name 'mini'
	 * enableTable(false, 'mini');
	 * @public
	 * @function
	 * @name REDIPS.drag#enableTable
	 * @see <a href="#enableDrag">enableDrag</a>
	 */
	enableTable = function (enable_flag, el) {
		var i;
		// if "el" is table reference then set enable/disable to the table
		if (typeof(el) === 'object' && el.nodeName === 'TABLE') {
			el.redips.enabled = enable_flag;
		}
		// else "el" is table class name
		else {
			// loop through tables array
			for (i = 0; i < tables.length; i++) {
				// if class name is found then set redips.enabled property to the table (redips_enabled is tested inside setTableRowColumn() method)
				if (tables[i].className.indexOf(el) > -1) {
					tables[i].redips.enabled = enable_flag;
				}
			}
		}
	};


	/**
	 * Method returns style value for requested HTML element and style name.
	 * @param {HTMLElement} el Requested HTML element.
	 * @param {String} style_name Asked style name.
	 * @return {String} Returns style value.
	 * @see <a href="http://www.quirksmode.org/dom/getstyles.html">http://www.quirksmode.org/dom/getstyles.html</a>
	 * @public
	 * @function
	 * @name REDIPS.drag#getStyle
	 */
	getStyle = function (el, style_name) {
		var val; // value of requested object and property
		if (el && el.currentStyle) {
			val = el.currentStyle[style_name];
		}
		else if (el && window.getComputedStyle) {
//			val = document.defaultView.getComputedStyle(el, null).getPropertyValue(style_name);
			val = document.defaultView.getComputedStyle(el, null)[style_name];  
		}
		return val;
	};


	/**
	 * Method returns a reference of the required parent element.
	 * @param {String} tag_name Tag name of parent element.
	 * @param {HTMLElement} el Start position to search.
	 * @param {Integer} [skip] How many found nodes should be skipped. For example when start node is TD in inner table and findParent() should return reference of the outside table.
	 * @example
	 * // find parent TABLE element (from cell reference)
	 * tbl = findParent('TABLE', cell);
	 *  
	 * // find reference of the outside table (start node is TD in inner table - first TABLE node should be skipped)
	 * tbl = findParent('TABLE', cell, 1);
	 * @return {HTMLElement} Returns reference of the found parent element.
	 * @public
	 * @function
	 * @name REDIPS.drag#findParent
	 */
	findParent = function (tag_name, el, skip) {
		// move "el" one level up (to prevent finding node itself)
		el = el.parentNode;
		// if skip is not defined then set it to 0
		if (skip === undefined) {
			skip = 0;
		}
		// loop up until parent element is found 
		while (el && el.nodeName !== tag_name) {
			el = el.parentNode;
			// if node is found and needs to be skipped then decrease skip counter and move pointer to the parent node again
			if (el && el.nodeName === tag_name && skip > 0) {
				skip--;
				el = el.parentNode;
			}
	    }
	    // return found element
	    return el;
	};


	/**
	 * Method returns data (cell reference, row index and column index) for first or last cell in table or row / column.
	 * @param {String} param Parameter defines first or last table cell (values are "first", "firstInColumn", "firstInRow", "last", "lastInColumn", "lastInRow").
	 * @param {HTMLElement} el Table cell reference (td). For "first" or "last" request, el can be any HTMLElement within table.
	 * @example
	 * // find first cell in row (el is table cell reference)
	 * firstInRow = findCell('firstInRow', el);
	 * 
	 * // find last cell in table (el is reference of any cell inside table)
	 * last = findCell('last', el);
	 * 
	 * // find last cell in column (el is table cell reference)
	 * lastInColumn = findCell('lastInColumn', el);
	 * @return {Array} Returns array with row index, column index and cell reference, 
	 * @public
	 * @function
	 * @name REDIPS.drag#findCell
	 */
	findCell = function (param, el) {
		// find parent table (if "el" is already table then "el" reference will not change)
		var tbl = findParent('TABLE', el),
			ri,	// row index
			ci,	// cell index
			c;	// cell reference
		switch (param) {
		// first in column
		case 'firstInColumn':
			ri = 0;
			ci = el.cellIndex;
			break;
		// first in row
		case 'firstInRow':
			ri = el.parentNode.rowIndex;
			ci = 0;
			break;
		// last in column
		case 'lastInColumn':
			ri = tbl.rows.length - 1;
			ci = el.cellIndex;
			break;
		// last in row (cell index for current row)
		case 'lastInRow':
			ri = el.parentNode.rowIndex;
			ci = tbl.rows[ri].cells.length - 1;
			break;
		// last in table (cell index for last row)
		case 'last':
			ri = tbl.rows.length - 1;
			ci = tbl.rows[ri].cells.length - 1;
			break;
		// define cell reference for first table cell (row and column indexes are 0) 
		default:
			ri = ci = 0;
		}
		// set table cell reference
		c = tbl.rows[ri].cells[ci];
	    // return cell data as array: row index, cell index and td reference
	    return [ri, ci, c];
	};



	/**
	 * Method scans table content and prepares query string or JSON format for submitting to the server.
	 * Input parameters are id / table reference and optional output format.
	 * @param {String|HTMLElement} tbl Id or reference of table that will be scanned.
	 * @param {String} [type] Type defines output format. If set to "json" then output will be JSON format otherwise output will be query string.
	 * @return {String} Returns table content as query string or in JSON format.
	 * @example
	 * Query string:
	 * 'p[]='+id+'_'+r+'_'+c+'&p[]='+id+'_'+r+'_'+c + ...
	 *  
	 * JSON:
	 * [["id",r,c],["id",r,c],...]
	 *  
	 * id - element id
	 * r  - row index
	 * c  - cell index
	 *  
	 * Query string example:
	 * p[]=d1_1_0&p[]=d2_1_1&p[]=d3_5_2&p[]=d4_5_3
	 *  
	 * JSON example:
	 * [["d1",1,0],["d2",1,1],["d3",5,2],["d4",5,3]]
	 * @see <a href="#saveParamName">saveParamName</a>
	 * @public
	 * @function
	 * @name REDIPS.drag#saveContent
	 */
	saveContent = function (tbl, type) {
		var query = '',						// define query parameter
			tbl_start,						// table loop starts from tbl_start parameter
			tbl_end,						// table loop ends on tbl_end parameter
			tbl_rows,						// number of table rows
			cells,							// number of cells in the current row
			tbl_cell,						// reference to the table cell
			cn,								// reference to the child node
			id, r, c, d,					// variables used in for loops
			JSONobj = [],					// prepare JSON object
			pname = REDIPS.drag.saveParamName;	// set parameter name (default is 'p')
		// if input parameter is string, then set reference to the table
		if (typeof(tbl) === 'string') {
			tbl = document.getElementById(tbl);
		}
		// tbl should be reference to the TABLE object
		if (tbl !== undefined && typeof(tbl) === 'object' && tbl.nodeName === 'TABLE') {
			// define number of table rows
			tbl_rows = tbl.rows.length;
			// iterate through each table row
			for (r = 0; r < tbl_rows; r++) {
				// set the number of cells in the current row
				cells = tbl.rows[r].cells.length;
				// iterate through each table cell
				for (c = 0; c < cells; c++) {
					// set reference to the table cell
					tbl_cell = tbl.rows[r].cells[c];
					// if cells isn't empty (no matter is it allowed or denied table cell) 
					if (tbl_cell.childNodes.length > 0) {
						// cell can contain many DIV elements
						for (d = 0; d < tbl_cell.childNodes.length; d++) {
							// set reference to the child node
							cn = tbl_cell.childNodes[d];
							// childNode should be DIV with containing "drag" class name
							if (cn.nodeName === 'DIV' && cn.className.indexOf('drag') > -1) { // and yes, it should be uppercase
								// prepare query string
								query += pname + '[]=' + cn.id + '_' + r + '_' + c + '&';
								// push values for DIV element as Array to the Array
								JSONobj.push([cn.id, r, c]);
							}
						}
					}
				}
			}
			// prepare query string in JSON format (only if array isn't empty)
			if (type === 'json' && JSONobj.length > 0) {
				query = JSON.stringify(JSONobj);
			}
			else {
				// cut last '&' from query string
				query = query.substring(0, query.length - 1);
			}
		}
		// return prepared parameters (if tables are empty, returned value could be empty too) 
		return query;
	};


	/**
	 * Method relocates DIV elements from source table cell to the target table cell (with optional animation).
	 * If animation is enabled, then target table will be disabled until animated element reaches destination cell.
	 * In animation mode, event.relocated() will be called after animation is finished.
	 * @param {HTMLElement} from Source table cell.
	 * @param {HTMLElement} to Target table cell.
	 * @param {String} [mode] Relocation mode "instant" or "animation". Default is "instant".
	 * @public
	 * @function
	 * @see <a href="#event:relocateBefore">event.relocateBefore</a>
	 * @see <a href="#event:relocateAfter">event.relocateAfter</a>
	 * @see <a href="#event:relocateEnd">event.relocateEnd</a>
	 * @name REDIPS.drag#relocate
	 */
	relocate = function (from, to, mode) {
		var i, j,	// loop variables
			tbl2,	// target table
			idx2,	// target table index
			cn,		// number of child nodes
			div,	// DIV element (needed in for loop)
			move;	// move object (private function)
		// define private move function (after animation is finished table will be enabled)
		move = function (el, to) {
			// call relocateBefore event handler for this element
			REDIPS.drag.event.relocateBefore(el, to);
			// define target position
			var target = REDIPS.drag.getPosition(to);
			// move object
			REDIPS.drag.moveObject({
				obj: el,
				target: target,
				callback: function (div) {
					// set reference to the table and table index
					var tbl = REDIPS.drag.findParent('TABLE', div),
						idx = tbl.redips.idx;
					// call relocateAfter event handler for this div element
					REDIPS.drag.event.relocateAfter(div, to);
					// decrease animation counter per table
					animationCounter[idx]--;
					// after last element is placed the table then table should be enabled
					if (animationCounter[idx] === 0) {
						// call event handler after relocation is finished
						REDIPS.drag.event.relocateEnd();
						// enable target table
						REDIPS.drag.enableTable(true, tbl);
					}
				}
			});
		};
		// test if "from" cell is equal to "to" cell then do nothing
		if (from === to) {
			return;
		}
		// "from" and "to" should be element nodes, if not then return from method
		if (typeof(from) !== 'object' || typeof(to) !== 'object') {
			return;
		}
		// define childnodes length before loop
		cn = from.childNodes.length;
		// if mode is "animation"
		if (mode === 'animation') {
			// if child nodes exist
			if (cn > 0) {
				// define target table reference and target table index
				tbl2 = findParent('TABLE', to);
				idx2 = tbl2.redips.idx;
				// disable target table
				REDIPS.drag.enableTable(false, tbl2);
				// loop through all child nodes in table cell
				for (i = 0; i < cn; i++) {
					// relocate (with animation) only DIV elements
					if (from.childNodes[i].nodeType === 1 && from.childNodes[i].nodeName === 'DIV') {
						// increase animated counter (counter is initially set to 0)
						animationCounter[idx2]++;
						// move DIV element to the target cell
						move(from.childNodes[i], to);
					}
				}
			}
		}
		// instant mode
		else {
			// loop through all child nodes in table cell
			// 'j', not 'i' because NodeList objects in the DOM are live
			for (i = 0, j = 0; i < cn; i++) {
				// relocate only DIV elements
				if (from.childNodes[j].nodeType === 1 && from.childNodes[j].nodeName === 'DIV') {
					// set DIV element
					div = from.childNodes[j];
					// call relocateBefore event handler for this element
					REDIPS.drag.event.relocateBefore(div, to);
					// append DIV element to the table cell
					to.appendChild(div);
					// register event listeners (FIX for Safari Mobile) if DIV element is not disabled
					if (div.redips && div.redips.enabled !== false) {
						registerEvents(div);
					}
					// call relocateAfter event handler
					REDIPS.drag.event.relocateAfter(div);
				}
				// skip text nodes, attribute nodes ...
				else {
					j++;
				}
			}	
		}
	};


	/**
	 * Method tests TD if is empty or removes elements from table cell.
	 * Cell is considered as empty if does not contain any child nodes or if cell has only one text node.
	 * In other words, if cell contains only text then it will be treated as empty cell.
	 * @param {HTMLElement} td Table cell to test or from which all the elements will be deleted.
	 * @param {String} [mode] If mode is set to "test" then method will only test TD and return true or false.
	 * @example
	 * // set REDIPS.drag reference
	 * var rd = REDIPS.drag;
	 * // search for TABLE element (from cell reference)
	 * tbl = rd.emptyCell(td);
	 *  
	 * // how to test TD if cell is occupied
	 * var empty = rd.emptyCell(td, 'test');  
	 * @return {Boolean|Array} Returns true/false depending on cell content or array with deleted child nodes.
	 * @public
	 * @function
	 * @name REDIPS.drag#emptyCell
	 */
	emptyCell = function (tdElement, mode) {
		var cn,			// number of child nodes
			el = [],	// removed elements will be saved in array
			flag,		// empty cell flag
			i;			// loop variable
		// td should be table cell element
		if (tdElement.nodeName !== 'TD') {
			return;
		}
		// define childnodes length before loop (not in loop because NodeList objects in the DOM are live)
		cn = tdElement.childNodes.length;
		// if mode is set to "test" then check for cell content
		if (mode === 'test') {
			// in case of source cell, return undefined
			if (td.source === tdElement) {
				flag = undefined;
			}
			// cell without child nodes or if cell has only one node and that is text node then cell is empty
			else if (tdElement.childNodes.length === 0 || (tdElement.childNodes.length === 1 && tdElement.firstChild.nodeType === 3)) {
				flag = true;
			}
			// otherwise, cell contain some elements
			else {
				flag = false;
			}
			// return empty flag state
			return flag;
		}
		// otherwise delete all child nodes from td
		else {
			for (i = 0; i < cn; i++) {
				// save node reference
				el.push(tdElement.childNodes[0]);
				// delete node
				tdElement.removeChild(tdElement.childNodes[0]);
			}
			// return array with references od deleted nodes
			return el;
		}
	};


	/**
	 * Method shifts table content horizontally or vertically. REDIPS.drag.shift.mode defines the way of how content will be shifted.
	 * Useful for sorting table content in any direction.
	 * @param {HTMLElement} td1 Source table cell.
	 * @param {HTMLElement} td2 Target table cell.
	 * @example
	 * // define first and last table cell
	 * var firstCell = document.getElementById('firstCellOnTable'),
	 *     lastCell = document.getElementById('lastCellOnTable');
	 * // enable animation
	 * REDIPS.drag.shift.animation = true;
	 * // shift content
	 * REDIPS.drag.shiftCells(lastCell, firstCell);
	 * @public
	 * @function
	 * @name REDIPS.drag#shiftCells
	 * @see <a href="#shift">shift.mode</a>
	 */
	shiftCells = function (td1, td2) {
		var tbl1, tbl2,	// table reference of source and target cell
			pos,		// start cell (source) position
			pos1,		// start position (used for settings of pos variable)
			pos2,		// end cell (target) position
			d,			// direction (1 - left, -1 - right)
			cl,			// cell list in form of [row, cell] -> table_cell (it takes care about rowspan and colspan)
			t1, t2,		// temporary source and target cell needed for relocate
			c1, c2,		// source and target cell needed for relocate
			m1, m2,		// set flags if source or target cell contains "mark" class name
			p2,			// remember last possible cell when marked cell occures
			shiftMode,	// shift.mode read from public parameter
			rows,		// row number
			cols,		// column number (column number is defined from first row)
			x, y,		// column / row
			max,
			overflow = false,	// (boolean) overflow flag (initially is set to false)
			myShift,			// shift method used locally in shiftCells
			handleOverflow;		// overflow method used locally (handler overflowed cells)
		// define myShift local method (content will be shifted with or without animation)
		myShift = function (source, target) {
			if (REDIPS.drag.shift.animation) {
				relocate(source, target, 'animation');
			}
			else {
				relocate(source, target);
			}
		};
		// handleOverflow - how to handle overflow content
		handleOverflow = function (target) {
			if (REDIPS.drag.shift.overflow === 'delete') {
				emptyCell(target);
			}
			// relocate overflowed content
			else if (REDIPS.drag.shift.overflow === 'source') {
				myShift(target, td.source);
			}
			else if (typeof(REDIPS.drag.shift.overflow) === 'object') {
				myShift(target, REDIPS.drag.shift.overflow);
			}
			// set overflow flag to false (overflow could happen only once)
			overflow = false;
			// call shiftOverflow event handler
			REDIPS.drag.event.shiftOverflow(target);
		};
		// if DIV element is dropped to the source cell then there's nothing to do - just return from method
		if (td1 === td2) {
			return;
		}
		// set shift.mode from public property
		shiftMode = REDIPS.drag.shift.mode;
		// set table reference for source and target table cell
		tbl1 = findParent('TABLE', td1);
		tbl2 = findParent('TABLE', td2);
		// prepare cell index (this will take care about rowspan and cellspan cases)
		cl = cellList(tbl2);
		// set source position only if both locations are from the same table
		if (tbl1 === tbl2) {
			pos1 = [td1.redips.rowIndex, td1.redips.cellIndex];
		}
		else {
			pos1 = [-1, -1];
		}
		// set source and target position (pos1 is used for setting pos variable in switch (shiftMode) case)
		pos2 = [td2.redips.rowIndex, td2.redips.cellIndex];
		// define number of rows and columns for target table (it's used as row and column index) 
		rows = tbl2.rows.length;
		cols = maxCols(tbl2);
		// set start position for shifting (depending on shift.mode value)
		switch (shiftMode) {
		case 'vertical2':
			// if source and target are from the same table and from the same column then use pos1 otherwise set last cell in column
			pos = (tbl1 === tbl2 && td1.redips.cellIndex === td2.redips.cellIndex) ? pos1 : [rows, td2.redips.cellIndex];
			break;
		case 'horizontal2':
			// if source and target are from the same table and from the same row then use pos1 otherwise set last cell in row
			pos = (tbl1 === tbl2 && td1.parentNode.rowIndex === td2.parentNode.rowIndex) ? pos1 : [td2.redips.rowIndex, cols];
			break;
		// vertical1 and horizontal1 shift.mode
		default:
			// set start cell if source and target cells are from the same table otherwise set last cell in table
			pos = (tbl1 === tbl2) ? pos1 : [rows, cols];
		}
		//
		// shift direction, max and row / column variables
		//
		// set direction (up/down) for vertical shift.mode
		// if source cell is prior to the target cell then set direction to the "up", otherwise direction is to the "down"
		if (shiftMode === 'vertical1' || shiftMode === 'vertical2') {
			d = (pos[1] * 1000 + pos[0] < pos2[1] * 1000 + pos2[0]) ? 1 : -1;
			max = rows;
			x = 0;
			y = 1;
		}
		// set direction (left/right) for horizontal shift.mode
		// if source cell is prior to the target cell then set direction to the "left", otherwise direction is to the "right"
		else {
			d = (pos[0] * 1000 + pos[1] < pos2[0] * 1000 + pos2[1]) ? 1 : -1;
			max = cols;
			x = 1;
			y = 0;
		}
		//
		// set overflow flag
		//
		// if source and target tables are different or max cell is defined for row, column or table then set possible overflow
		if (pos[0] !== pos1[0] && pos[1] !== pos1[1]) {
			overflow = true;
		}
		//
		// loop
		//
		// while loop - goes from target to source position (backward)
		// imagine row with 5 cells, relocation will go like this: 3->4, 2->3, 1->2 and 0->1 
		while (pos[0] !== pos2[0] || pos[1] !== pos2[1]) {
			// define target cell
			t2 = cl[pos[0] + '-' + pos[1]];
			// increase indexes for row and column to define source cell 
			// increment row index
			pos[x] += d;
			// if row is highest row
			if (pos[x] < 0) {
				pos[x] = max;
				pos[y]--;
			}
			// if cellIndex was most right column
			else if (pos[x] > max) {
				pos[x] = 0;
				pos[y]++;
			}
			// define temp source cell
			t1 = cl[pos[0] + '-' + pos[1]];
			// if temp1 cell source cell exists then remember location to c1
			if (t1 !== undefined) {
				c1 = t1;
			}
			// if temp2 cell source cell exists then remember location to c2
			if (t2 !== undefined) {
				c2 = t2;
			}
			// shift DIV if exists (t1 and c2) or (c1 and t2)
			if ((t1 !== undefined && c2 !== undefined) || (c1 !== undefined && t2 !== undefined)) {
				// set "mark" flags if source or target cell contains "mark" class name
				m1 = c1.className.indexOf(REDIPS.drag.mark.cname) === -1 ? 0 : 1;
				m2 = c2.className.indexOf(REDIPS.drag.mark.cname) === -1 ? 0 : 1;
				// detect overflow (actually this is detection of first allowed cell)
				if (overflow) {
					// if target cell is marked and source cell is not marked handle overflow (overflow flag will be automatically set to false)
					if (m1 === 0 && m2 === 1) {
						handleOverflow(c1);
					}
				}
				// if source cell is forbidden then skip shifting
				if (m1 === 1) {
					// if target cell isn't foribdden then remember this location
					if (m2 === 0) {
						p2 = c2;
					}
					continue;
				}
				// set target cell to be last free cell (remembered in previous step)
				else if (m1 === 0 && m2 === 1) {
					c2 = p2;
				}
				// relocate cell content with or without animation
				myShift(c1, c2);
			}
			// overflow detection (fall off table edge)
			else if (overflow && c1 !== undefined && c2 === undefined) {
				// test for "mark" class name for source cell
				m1 = c1.className.indexOf(REDIPS.drag.mark.cname) === -1 ? 0 : 1;
				// if edge cell is not marked then handle overflow
				if (m1 === 0) {
					handleOverflow(c1);
				}
			}
		}
	};


	/**
	 * Determining a table cell's X and Y position/index.
	 * @see <a href="http://www.javascripttoolbox.com/temp/table_cellindex.html">http://www.javascripttoolbox.com/temp/table_cellindex.html</a>
	 * @see <a href="http://www.barryvan.com.au/2012/03/determining-a-table-cells-x-and-y-positionindex/">http://www.barryvan.com.au/2012/03/determining-a-table-cells-x-and-y-positionindex/</a>
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	cellList = function (table) {
		var matrix = [],
			matrixrow,
			lookup = {},
			c,			// current cell
			ri,			// row index
			rowspan,
			colspan,
			firstAvailCol,
			tr,			// TR collection
			i, j, k, l;	// loop variables
		// set HTML collection of table rows
		tr = table.rows;
		// open loop for each TR element
		for (i = 0; i < tr.length; i++) {
			// open loop for each cell within current row
			for (j = 0; j < tr[i].cells.length; j++) {
				// define current cell
				c = tr[i].cells[j];
				// set row index
				ri = c.parentNode.rowIndex;
				// define cell rowspan and colspan values
				rowspan = c.rowSpan || 1;
				colspan = c.colSpan || 1;
				// if matrix for row index is not defined then initialize array
				matrix[ri] = matrix[ri] || [];
				// find first available column in the first row
				for (k = 0; k < matrix[ri].length + 1; k++) {
					if (typeof(matrix[ri][k]) === 'undefined') {
						firstAvailCol = k;
						break;
					}
				}
				// set cell coordinates and reference to the table cell
				lookup[ri + '-' + firstAvailCol] = c;
				// create a "property object" in which "real" row/cell index will be saved
				if (c.redips === undefined) {
					c.redips = {};
				}
				// save row and cell index to the cell
				c.redips.rowIndex = ri;
				c.redips.cellIndex = firstAvailCol;
				for (k = ri; k < ri + rowspan; k++) {
					matrix[k] = matrix[k] || [];
					matrixrow = matrix[k];
					for (l = firstAvailCol; l < firstAvailCol + colspan; l++) {
						matrixrow[l] = 'x';
					}
				}
			}
		}
		return lookup;
	};


	/**
	 * Method returns number of maximum columns in table (some row may contain merged cells).
	 * @param {HTMLElement|String} table TABLE element.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	maxCols = function (table) {
		var	tr = table.rows,	// define number of rows in current table
			span,				// sum of colSpan values
			max = 0,			// maximum number of columns
			i, j;				// loop variable
		// if input parameter is string then overwrite it with table reference
		if (typeof(table) === 'string') {
			table = document.getElementById(table);
		}
		// open loop for each TR within table
		for (i = 0; i < tr.length; i++) {
			// reset span value
			span = 0;
			// sum colspan value for each table cell
			for (j = 0; j < tr[i].cells.length; j++) {
				span += tr[i].cells[j].colSpan || 1;
			}
			// set maximum value
			if (span > max) {
				max = span;
			}
		}
		// return maximum value
		return max;
	};


	/**
	 * Method will calculate parameters and start animation (DIV element to the target table cell).
	 * "moveObject" will always move DIV element with animation while "relocate" has option to relocate all DIV elements from one TD to another TD with or without animation.
	 * If "target" property is not defined then current location will be used. Here is properties definition of input parameter:
	 * <ul>
	 * <li>{String} id - id of element to animate - DIV element or row handler (div class="drag row")</li>
	 * <li>{String} obj - reference of element to animate - DIV element or row handler (if "id" parameter exists, "obj" parameter will be ignored)</li>
	 * <li>{String} mode - animation mode (if mode="row" then source and target properties should be defined)</li>
	 * <li>{Boolean} clone - if set to true then DIV element will be cloned instead of moving (used only in "cell" mode and default is false)</li>
	 * <li>{Boolean} overwrite - if set to true then elements in target cell will be overwritten (used only in "cell" mode and default is false)</li>
	 * <li>{Array} source - source position (table index and row index)</li>
	 * <li>{Array} target - target position (table, row and cell index (optional for "row" mode)</li>
	 * <li>{Function} callback - callback function executed after animation is finished</li>
	 * </ul>
	 * Method returns array containing reference of two object. In "cell" mode returned objects are:
	 * <ul>
	 * <li>Array[0] - dragged element</li>
	 * <li>Array[1] - dragged element</li>
	 * </ul>
	 * In "row" mode returned objects are:
	 * <ul>
	 * <li>Array[0] - tableMini</li>
	 * <li>Array[1] - source row</li>
	 * </ul>
	 * If "clone" parameter is set to true then event.cloned() event handler will be invoked with input parameter of cloned element.
	 * @param {Object} ip Object with properties: id, mode, source, target and callback.
	 * @return {Array|Boolean} Returns reference of two elements in array or false. In "cell" mode both elements are dragged element, while in "row" mode first element is tableMini and second element is source row or it could be false if "emptyRow" try to move.
	 * @example
	 * // move element with id="a1" to the current location and after
	 * // animation is finished display alert "Finished"  
	 * rd.moveObject({
	 *     id: 'a1',
	 *     callback: function () {
	 *         alert('Finished');
	 *     }
	 * });
	 *  
	 * // move DIV element with reference "mydiv" to the TD with reference td
	 * rd.moveObject({
	 *     obj: mydiv,
	 *     target: td
	 * });
	 *  
	 * // move DIV element with reference "mydiv" to the first table, second row and third cell
	 * rd.moveObject({
	 *     obj: mydiv,
	 *     target: [0, 1, 2]
	 * });
	 *  
	 * // move element with id="a2" to the first table, second row and third cell
	 * rd.moveObject({
	 *     id: 'a2',
	 *     target: [0, 1, 2]
	 * });
	 *  
	 * // clone DIV element with reference "mydiv", move to the first table, second row,
	 * // third cell and overwrite all content in target cell
	 * rd.moveObject({
	 *     obj: mydiv,
	 *     clone: true,
	 *     overwrite: true,
	 *     target: [0, 1, 2]
	 * });
	 *  
	 * // move first row and after animation is finished call "enable_button" function
	 * // "moveObject" returns Array with references of tableMini and source row
	 * row = rd.moveObject({
	 *           mode: 'row',            // animation mode - row
	 *           source: [0, 0],         // source position (table index and row index)
	 *           target: [0, 6],         // target position
	 *           callback: enable_button // function to call after animation is over
	 *        });
	 * @see <a href="#relocate">relocate</a>
	 * @public
	 * @function
	 * @name REDIPS.drag#moveObject
	 */
	moveObject = function (ip) {
		var p = {'direction': 1},	// param object (with default direction)
			x1, y1,	w1, h1,			// coordinates and width/height of object to animate
			x2, y2,	w2, h2,			// coordinates and width/height of target cell
			row, col,				// row and cell indexes
			dx, dy,					// delta x and delta y
			pos, i,					// local variables needed for calculation coordinates and settings the first point
			target;
		// set callback function - it will be called after animation is finished
		p.callback = ip.callback;
		// set overwrite parameter
		p.overwrite = ip.overwrite;
		// define obj and objOld (reference of the object to animate - DIV element or row handler)
		// ip.id - input parameter obj_id
		if (typeof(ip.id) === 'string') {
			p.obj = p.objOld = document.getElementById(ip.id);
		}
		// reference of DIV element to animate
		else if (typeof(ip.obj) === 'object' && ip.obj.nodeName === 'DIV') {
			p.obj = p.objOld = ip.obj;
		}
		// test if animation mode is "row" (mode, source and target properties should be defined)
		if (ip.mode === 'row') {
			p.mode = 'row';
			// find table index for source table (source[0] contains original table index)
			i = getTableIndex(ip.source[0]);
			// define source row index from input parameter object
			row = ip.source[1];
			// set source row
			objOld = p.objOld = tables[i].rows[row];
			// if row is marked as empty row then it will not be moved and method will return false
			if (objOld.redips && objOld.redips.emptyRow === true) {
				return false;
			}
			// set reference to the mini table - cloned from source row (TABLE element)
			p.obj = rowClone(p.objOld, 'animated');
		}
		// test if element is row handler
		else if (p.obj && p.obj.className.indexOf('row') > -1) {
			p.mode = 'row';
			// find TR element and remember reference to the source row (TR element)
			p.obj = p.objOld = objOld = findParent('TR', p.obj);
			// if row is marked as empty row then it will not be moved and method will return false
			if (objOld.redips && objOld.redips.emptyRow === true) {
				return false;
			}
			// set reference to the mini table - cloned from source row (TABLE element)
			p.obj = rowClone(p.objOld, 'animated');
		}
		// animation mode is "cell"
		else {
			p.mode = 'cell';
		}
		// p.obj should be existing object (null or non objects are not allowed)
		if (typeof(p.obj) !== 'object' || p.obj === null) {
			return;
		}
		// set high z-index
		p.obj.style.zIndex = 999;
		// if clicked element doesn't belong to the current container then context should be changed
		// redips property could not be set in case when static DIV is moved (like in example25)
		if (p.obj.redips && dragContainer !== p.obj.redips.container) {
			dragContainer = p.obj.redips.container;
			initTables();
		}
		// set width, height and coordinates for source position of object
		pos = boxOffset(p.obj);
		w1 = pos[1] - pos[3];
		h1 = pos[2] - pos[0];
		x1 = pos[3];
		y1 = pos[0];
		// if input parameter "clone" is true and DIV element is moving then clone DIV element instead of moving original element
		// this should go after definition of start coordinates x1 and y1
		if (ip.clone === true && p.mode === 'cell') {
			// clone object (DIV element)
			p.obj = cloneObject(p.obj, true);
			// and call event.cloned event handler
			REDIPS.drag.event.cloned(p.obj);
		}
		// if target parameted is undefined then use current position in table 
		if (ip.target === undefined) {
			ip.target = getPosition();
		}
		// if target is TD (object) then set position for this TD
		else if (typeof(ip.target) === 'object' && ip.target.nodeName === 'TD') {
			ip.target = getPosition(ip.target);
		}
		// set target table, row and cell indexes (needed for moving table row)
		// table index is index from array not original table index
		p.target = ip.target;
		// find table index because tables array is sorted on every element click (target[0] contains original table index)
		i = getTableIndex(ip.target[0]);
		// set index for row and cell (target input parameter is array)
		row = ip.target[1];
		col = ip.target[2];
		// if target row index is greater then number of rows in target table then set last row index
		if (row > tables[i].rows.length - 1) {
			row = tables[i].rows.length - 1;
		}
		// save reference of target cell
		p.targetCell = tables[i].rows[row].cells[col];
		// set width, height and coordinates of target cell
		if (p.mode === 'cell') {
			pos = boxOffset(p.targetCell);
			w2 = pos[1] - pos[3];
			h2 = pos[2] - pos[0];
			// target coordinates are cell center including object dimensions
			x2 = pos[3] + (w2 - w1) / 2;
			y2 = pos[0] + (h2 - h1) / 2;
		}
		// set width, height and coordinates of target row
		else {
			pos = boxOffset(tables[i].rows[row]);
			w2 = pos[1] - pos[3];
			h2 = pos[2] - pos[0];
			x2 = pos[3];
			y2 = pos[0];
		}
		// calculate delta x and delta y
		dx = x2 - x1;
		dy = y2 - y1;
		// set style to fixed to allow moving DIV object
		p.obj.style.position = 'fixed';
		// if line is more horizontal
		if (Math.abs(dx) > Math.abs(dy)) {
			// set path type
			p.type = 'horizontal';
			// set slope (m) and y-intercept (b)
			// y = m * x + b
			p.m = dy / dx;
			p.b = y1 - p.m * x1;
			// parameters needed for delay calculation (based on parabola)
			p.k1 = (x1 + x2) / (x1 - x2);
			p.k2 = 2 / (x1 - x2);
			// define animation direction
			if (x1 > x2) {
				p.direction = -1;
			}
			// set first and last point
			i = x1;
			p.last = x2;
		}
		// line is more vertical
		else {
			// set path type
			p.type = 'vertical';
			// set slope (m) and y-intercept (b)
			// y = m * x + b
			p.m = dx / dy;
			p.b = x1 - p.m * y1;
			// parameters needed for delay calculation (based on parabola)
			p.k1 = (y1 + y2) / (y1 - y2);
			p.k2 = 2 / (y1 - y2);
			// define animation direction
			if (y1 > y2) {
				p.direction = -1;
			}
			// set first and last point
			i = y1;
			p.last = y2;
		}
		// set attribute "animated" of DIV object to true (to disable dragging od DIV while animation lasts)
		// redips property could not be set in case when static DIV is moved (like in example25)
		if (p.obj.redips) {
			p.obj.redips.animated = true;
		}
		// start animation
		animateObject(i, p);
		// return reference of obj and objOld elements
		// "cell" mode
		// obj - dragged element
		// objOld - dragged element
		// "row" mode
		// obj - tableMini
		// objOld - source row
		return [p.obj, p.objOld];
	};


	/**
	 * Element (DIV or table row) animation.
	 * After "moveObject" calculates parameters, animation is started by calling "animate" method.
	 * Each other animation step is done by recursive calls until element reaches last point.
	 * Input parameters are first (current) point and 'p' object with following properties:
	 * <ul>
	 * <li>obj - object to animate</li>
	 * <li>targetCell - target table cell</li>
	 * <li>last - last point</li>
	 * <li>m, b - slope and y-intercept (needed for y = m * x + b)</li>
	 * <li>k1, k2 - constants needed for calculation 1 -> 0 -> 1 parameter (regarding current position)</li>
	 * <li>direction - animation direction (1 or -1)</li>
	 * <li>type - line type (horizontal or vertical)</li>
	 * <li>overwrite - if set to true then elements in target cell will be overwritten (used only in "cell" mode and default is false)</li>
	 * </ul>
	 * @param {Integer} i First (and lately current) point
	 * @param {Object} p Object with properties: obj, targetCell, last, m, b, k1, k2, direction and type
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	animateObject = function (i, p) {
		// calculate parameter k (k goes 1 -> 0 -> 1 for start and end step)
		var k = (p.k1 - p.k2 * i) * (p.k1 - p.k2 * i),
			f;
		// calculate step and function of step (y = m * x + b)
		i = i + REDIPS.drag.animation.step * (4 - k * 3) * p.direction;
		f = p.m * i + p.b;
		// set element position
		if (p.type === 'horizontal') {
			p.obj.style.left = i + 'px';
			p.obj.style.top  = f + 'px';			
		}
		else {
			p.obj.style.left = f + 'px';
			p.obj.style.top  = i + 'px';
		}
		// if line is not finished then make recursive call
		if ((i < p.last && p.direction > 0) || ((i > p.last) && p.direction < 0)) {
			// recursive call for next step
			setTimeout(function () {
				animateObject(i, p);
			}, REDIPS.drag.animation.pause * k);
		}
		// animation is finished
		else {
			// reset object styles
			resetStyles(p.obj);
			// set animation flag to false to enable DIV dragging
			// redips property could not be set in case when static DIV is moved (like in example25)
			if (p.obj.redips) {
				p.obj.redips.animated = false;
			}
			// if moved element is cell then append element to the target cell
			if (p.mode === 'cell') {
				// if overwrite parameter is set to true then empty targetCell
				if (p.overwrite === true) {
					// empty target cell
					emptyCell(p.targetCell);
				}
				p.targetCell.appendChild(p.obj);
				// register event listeners (FIX for Safari Mobile) if DIV element is not disabled
				if (p.obj.redips && p.obj.redips.enabled !== false) {
					registerEvents(p.obj);
				}
			}
			// else element is row
			else {
				// take care about real table index
				rowDrop(getTableIndex(p.target[0]), p.target[1], p.obj);
			}
			// execute callback function if callback is defined and send reference of moved element
			if (typeof(p.callback) === 'function') {
				p.callback(p.obj);
			}
		}
	};


	/**
	 * Method returns position as array with members tableIndex, rowIndex and cellIndex (array length is 3).
	 * If input parameter is not defined then method will return array with current and source positions (array length will be 6).
	 * @param {String|HTMLElement} [ip] DIV element id / reference or table cell id / reference.
	 * @return {Array} Returns array with members tableIndex, rowIndex and cellIndex. If position is not found then all array members will have value -1.
	 * @example
	 * // set REDIPS.drag reference
	 * var rd = REDIPS.drag;
	 * // display target and source position of dropped element
	 * rd.event.dropped = function () {
	 *    // get target and source position (method returns positions as array)
	 *    // pos[0] - target table index
	 *    // pos[1] - target row index
	 *    // pos[2] - target cell (column) index
	 *    // pos[3] - source table index
	 *    // pos[4] - source row index
	 *    // pos[5] - source cell (column) index
	 *    var pos = rd.getPosition();
	 *    // display element positions
	 *    console.log(pos);
	 * };
	 * @public
	 * @function
	 * @name REDIPS.drag#getPosition
	 */
	getPosition = function (ip) {
		var toi,		// table original index (because tables are sorted on every element click)
			toi_source,	// table original index (source table)
			ci, ri, ti,	// cellIndex, rowIndex and table index (needed for case if input parameter exists)
			el,			// element reference
			tbl,		// table reference
			arr = [];	// array to return
		// set initial values for cell, row and table index
		ci = ri = ti = -1;
		// if input parameter is is undefined, then return current location and source location (array will contain 6 elements)
		if (ip === undefined) {
			// table original index (because tables are sorted on every element click)
			if (table < tables.length) {
				toi = tables[table].redips.idx;
			}
			// if any level of old position is undefined, then use source location
			else if (table_old === null || row_old === null || cell_old === null) {
				toi = tables[table_source].redips.idx;
			}
			// or use the previous location
			else {
				toi = tables[table_old].redips.idx;
			}
			// table source original index
			toi_source = tables[table_source].redips.idx;
			// prepare array to return (row, cell and row_source, cell_source are global variables)
			arr = [toi, row, cell, toi_source, row_source, cell_source];
		}
		// input parameter is defined (id or reference of table cell or any child of table cell) 
		else {
			// if input parameter is string (this should be element id), then set element reference
			if (typeof(ip) === 'string') {
				el = document.getElementById(ip);
			}
			// else, input parameter is reference
			else {
				el = ip;
			}
			// if element exists
			if (el) {
				// find parent TD element (because "ip" could be the child element of table cell - DIV drag or any other inner element)
				if (el.nodeName !== 'TD') {
					el = findParent('TD', el);
				}
				// if node is table cell then set coordinates
				if (el && el.nodeName === 'TD') {
					// define cellIndex and rowIndex 
					ci = el.cellIndex;
					ri = el.parentNode.rowIndex;
					// find table
					tbl = findParent('TABLE', el);
					// define table index
					ti = tbl.redips.idx;
				}
			}
			// prepare array with tableIndex, rowIndex and cellIndex (3 elements)
			arr = [ti, ri, ci];
		}
		// return result array
		return arr;
	};
	

	/**
	 * Find table index - because tables array is sorted on every element click.
	 * @param {Integer} idx Table index of initial table order.
	 * @return {Integer} Returns current index from tables array.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	getTableIndex = function (idx) {
		var i;
		for (i = 0; i < tables.length; i++) {
			if (tables[i].redips.idx === idx) {
				break;
			}
		}
		return i;
	};


	/**
	 * Function returns a string in which all of the preceding and trailing white space has been
	 * removed, and in which all internal sequences of white is replaced with one white space. 
	 * @param {String} str Input string.
	 * @return {String} Returns normalized string.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	normalize = function (str) {
		if (str !== undefined) {
			str = str.replace(/^\s+|\s+$/g, '').replace(/\s{2,}/g, ' ');
		}
		// return normalized string (without preceding and trailing spaces)
		return str;
	};


	/**
	 * Method returns "true" if input element contains child nodes with nodeType === 1.
	 * Other node types (like text node) are ignored.
	 * @param {HTMLElement} el Input element.
	 * @return {Boolean} Returns true or false.
	 * @private
	 * @memberOf REDIPS.drag#
	 */
	hasChilds = function (el) {
		// local variable
		var i;
		// loop goes through all child nodes and search for node with nodeType === 1
		for (i = 0; i < el.childNodes.length; i++) {
			if (el.childNodes[i].nodeType === 1) {
				return true;
			}
		}
		return false;
	};


	/**
	 * Method sets opacity to table row or deletes row content.
	 * Input parameter "el" is reference to the table row or reference to the cloned mini table (when row is moved).
	 * @param {HTMLElement|String} el Id of row handler (div class="drag row") or reference to element (source row or mini table).
	 * @param {Integer|String} opacity Opacity level (from 0 to 100) or "empty" (then content of table cells in row will be deleted - in that case first parameter should be TR).
	 * @param {String} [color] Background color.
	 * @example
	 * // set reference to the REDIPS.drag library
	 * rd = REDIPS.drag; 
	 * 
	 * // make row semi-transparent
	 * rd.rowOpacity(rowObj, 50);
	 * 
	 * // set row as empty and white (content in table cells will be deleted)
	 * rd.rowOpacity(rowObj, 'empty', 'White');
	 * @public
	 * @function
	 * @name REDIPS.drag#rowOpacity
	 */
	rowOpacity = function (el, opacity, color) {
		var	tdNodeList,	// table cells
			i, j;		// loop variables
		// if input parameter is string (this should be element id), then set element reference
		if (typeof(el) === 'string') {
			el = document.getElementById(el);
			// el could be reference of the DIV class="drag row" (row handler)
			el = findParent('TABLE', el);
		}
		// if el is TR, then set background color to each cell (if needed) and apply opacity
		if (el.nodeName === 'TR') {
			// collect table cell from the row
			tdNodeList = el.getElementsByTagName('td');
			// set opacity for DIV element
			for (i = 0; i < tdNodeList.length; i++) {
				// set background color to table cell if needed
				tdNodeList[i].style.backgroundColor = color ? color : '';
				// if opacity is set to "empty" then delete cell content 
				if (opacity === 'empty') {
					tdNodeList[i].innerHTML = '';
				}
				// otherwise set opacity to every child node in table cell
				else {
					// loop through child nodes of every table cell
					for (j = 0; j < tdNodeList[i].childNodes.length; j++) {
						// apply styles only to Element nodes (not text nodes, attributes ...)
						// http://code.stephenmorley.org/javascript/dom-nodetype-constants/
						if (tdNodeList[i].childNodes[j].nodeType === 1) {
							tdNodeList[i].childNodes[j].style.opacity = opacity / 100;
							tdNodeList[i].childNodes[j].style.filter = 'alpha(opacity=' + opacity + ')';
							//td[i].childNodes[j].style.visibility = 'hidden';
						}
					}
				}
			}
		}
		// when row is moved then REDIPS.drag will create mini table with one row
		// all browsers (IE8, Opera11, FF3.6, Chrome10) can set opacity to the table
		else {
			el.style.opacity = opacity / 100;					// set opacity for FF, Chrome, Opera
			el.style.filter = 'alpha(opacity=' + opacity + ')';	// set opacity for IE
			el.style.backgroundColor = color ? color : '';		// set background color 
		}
	};


	/**
	 * Method marks selected row as empty. Could be needed for displaying initially empty table.
	 * Input parameters are table id and row index.
	 * @param {String} tbl_id Table id.
	 * @param {Integer} row_idx Row index (starts from 0).
	 * @param {String} [color] Color of empty row (default is "White" or defined with REDIPS.drag.rowEmptyColor parameter).
	 * @see <a href="#style">style.rowEmptyColor</a>
	 * @example
	 * // set reference to the REDIPS.drag library
	 * rd = REDIPS.drag; 
	 * // mark first row as empty in table with id="tbl1"
	 * rd.rowEmpty('tbl1', 0);
	 * @public
	 * @function
	 * @name REDIPS.drag#rowEmpty
	 */
	rowEmpty = function (tbl_id, row_idx, color) {
		var tbl = document.getElementById(tbl_id),
			row = tbl.rows[row_idx];
		// define color parameter if input parameter "color" is not defined
		if (color === undefined) {
			color = REDIPS.drag.style.rowEmptyColor;
		}
		// create a "property object" in which all custom properties of row will be saved.
		if (row.redips === undefined) {
			row.redips = {};
		}
		// set emptyRow property to true
		row.redips.emptyRow = true;
		// mark row as empty
		rowOpacity(row, 'empty', color);
	};


	return {
		/* public properties */
		/**
		 * Currently moved DIV element. Reference to the REDIPS.drag.obj (dragged DIV element) is visible and can be used in appropriate event handlers.
		 * @type HTMLElement
		 * @name REDIPS.drag#obj
		 */
		obj	: obj,
		/**
		 * Previously moved DIV element (before clicked or cloned). In case when DIV element is cloned, obj is reference of current (cloned) DIV element while objOld is reference of bottom (origin) DIV element.
		 * @type HTMLElement
		 * @name REDIPS.drag#objOld
		 */
		objOld	: objOld,
		/**
		 * Dragging mode "cell" or "row" (readonly).
		 * This is readonly property defined in a moment when DIV element or row handler is clicked.
		 * @type String
		 * @name REDIPS.drag#mode
		 */
		mode : mode,
		/**
		 * Object contains reference to previous, source, current and target table cell. Td references can be used in event handlers. 
		 * <ul>
		 * <li>{HTMLElement} td.source - reference to source table cell (set in onmousedown)</li>
		 * <li>{HTMLElement} td.previous - reference to previous table cell (set in onmousemove and autoscroll)</li>
		 * <li>{HTMLElement} td.current - reference to current table cell (set in onmousemove and autoscroll)</li>
		 * <li>{HTMLElement} td.target - reference to target table cell (target table cell is set in a moment of dropping element to the table cell)</li>
		 * </ul>  
		 * @type Object
		 * @name REDIPS.drag#td
		 */
		td : td,
		/**
		 * Hover object contains 4 properties: colorTd, colorTr, borderTd and borderTr. colorTd and colorTr define hover color for DIV element and table row.
		 * If borderTd is defined, then highlighted cell will have border. If borderTr is defined then highlighted row will have only top or bottom border.
		 * Top border shows that row will be placed above current row, while bottom border shows that current row will be placed below current row.
		 * Some browsers may have problem with "border-collapse:collapse" table style and border highlighting.
		 * In that case try without collapsing TD borders (e.g set "border-spacing:0" and smaller "td.border-width").
		 * @type Object
		 * @name REDIPS.drag#hover
		 * @example
		 * // set "#9BB3DA" as hover color for TD
		 * REDIPS.drag.hover.colorTd = '#9BB3DA';
		 *  
		 * // or set "Lime" as hover color for TR
		 * REDIPS.drag.hover.colorTr = 'Lime';
		 *  
		 * // set red border for highlighted TD
		 * REDIPS.drag.hover.borderTd = '2px solid red';
		 */
		hover : hover,
		/**
		 * Scroll object contains properties needed for autoscroll option.
		 * <ul>
		 * <li>{Boolean} scroll.enable - Enable / disable autoscroll option. By default autoscroll is enabled but it can be usefull in some cases to completely turn off autoscroll (if application doesn't need autoscrolling page nor autoscrolling DIV container). Turning off autoscroll will speed up application because extra calculations will be skipped. Default is true</li>
		 * <li>{Integer} scroll.bound - Bound size for triggering page autoScroll or autoScroll of scrollable DIV container. Default value is 25 (px).</li>
		 * <li>{Integer} scroll.speed - Autoscroll pause in milliseconds. Default value is 20 (milliseconds).</li>
		 * </ul>
		 * @type Object
		 * @name REDIPS.drag#scroll
		 */
		scroll : scroll,
		/**
		 * Table cells marked with "only" class name can accept only defined DIV elements.
		 * Object contains:
		 * <ul>
		 * <li>{Array} div - defined DIV elements can be dropped only to the table cells marked with class name "only" (DIV id -> class name)</li>
		 * <li>{String} cname - class name of marked cells (default is "only")</li>
		 * <li>{String} other - allow / deny dropping DIV elements to other table cells (default is "deny")</li>
		 * </ul>
		 * @example
		 * // only element with Id "a1" can be dropped to the cell with class name "only last"
		 * REDIPS.drag.only.div.a1 = 'last';
		 *  
		 * // DIV elements mentioned in REDIPS.drag.only.div cannot be dropped to other cells
		 * REDIPS.drag.only.other = 'deny';
		 * @type Object
		 * @name REDIPS.drag#only
		 * @see <a href="#mark">mark</a>
		 */
		only : only,
		/**
		 * Table cells marked with "mark" class name can be allowed or forbidden for accessing (with exceptions) - default is "deny".
		 * This is useful to define table cells forbidden for every DIV element with exceptions (or contrary, define table cells allowed for all DIV elements except some).
		 * Object contains:
		 * <ul>
		 * <li>{String} action - allow / deny table cell (default is "deny")</li>
		 * <li>{String} cname - class name of marked cells (default is "mark")</li>
		 * <li>{Array} exception - defined DIV elements can be dropped to the table cells marked with class "mark" (DIV id -> class name)</li>
		 * </ul>
		 * @example
		 * // only element with Id "d8" can be dropped to the cell with class name "mark smile"
		 * REDIPS.drag.mark.exception.d8 = 'smile';
		 * @type Object
		 * @see <a href="#only">only</a>
		 * @name REDIPS.drag#mark
		 */
		mark : mark,
		/**
		 * Object contains styles (colors, opacity levels) for DIV elements and table rows.
		 * <ul>
		 * <li>{String} style.borderEnabled - Border style for enabled DIV elements. Default is "solid".</li>
		 * <li>{String} style.borderDisabled - Border style for disabled DIV elements. Default is "dotted".</li>
		 * <li>{Integer} style.opacityDisabled - Opacity level for disabled elements. Default is empty string.</li>
		 * <li>{String} style.rowEmptyColor - "Empty row" color. When last row from table is moved then this color will be set to "empty row". Default is "white".</li>
		 * </ul>
		 * @example
		 * // define border style for disabled elements
		 * REDIPS.drag.style.borderDisabled = 'dashed';
		 * @type Object
		 * @name REDIPS.drag#style
		 */
		style : style,
		/**
		 * Object contains td class name (where DIV elements can be deleted) and confirmation questions for deleting DIV element or table row.
		 * <ul>
		 * <li>{String} trash.className - Class name of td which will become trash can. Default value is "trash".</li>
		 * <li>{String} trash.question - If trash.question is set then popup will appear and ask to confirm element deletion. Default value is null.</li>
		 * <li>{String} trash.questionRow - If trash.questionRow is set then popup will appear and ask to confirm row deletion. Default value is null.</li>
		 * </ul>
		 * @example
		 * // confirm DIV element delete action 
		 * REDIPS.drag.trash.question = 'Are you sure you want to delete DIV element?';
		 *  
		 * // confirm row delete action 
		 * REDIPS.drag.trash.questionRow = 'Are you sure you want to delete table row?';  
		 * @type Object
		 * @name REDIPS.drag#trash
		 */
		trash : trash,
		/**
		 * Save content parameter name. Parameter name should be short because it will be repeated for every DIV element.
		 * It is irrelevant in case of JSON format.
		 * @type String
		 * @name REDIPS.drag#saveParamName
		 * @default p
		 */
		saveParamName : saveParamName,
		/**
		 * Property defines working types of REDIPS.drag library for dragging DIV elements: multiple, single, switch, switching, overwrite and shift.
		 * @type String
		 * @name REDIPS.drag#dropMode
		 * @default multiple
		 * @example
		 * // elements can be dropped to all table cells (multiple elements in table cell)
		 * REDIPS.drag.dropMode = 'multiple';
		 *  
		 * // elements can be dropped only to the empty table cells
		 * REDIPS.drag.dropMode = 'single';
		 *  
		 * // switch content
		 * REDIPS.drag.dropMode = 'switch';
		 *  
		 * // switching content continuously
		 * REDIPS.drag.dropMode = 'switching';
		 *  
		 * // overwrite content in table cell
		 * REDIPS.drag.dropMode = 'overwrite';
		 *  
		 * // shift table content after element is dropped or moved to trash cell
		 * REDIPS.drag.dropMode = 'shift';
		 */
		dropMode : dropMode,
		/**
		 * Property defines "top" or "bottom" position of dropped element in table cell (if cell already contains DIV elements).
		 * It has affect only in case of dropMode="multiple".
		 * @type String
		 * @name REDIPS.drag#multipleDrop
		 * @default bottom
		 * @example
		 * // place dropped elements to cell top
		 * REDIPS.drag.multipleDrop = 'top';
		 */
		multipleDrop : multipleDrop,
		/**
		 * Object defines several rules related to cloning DIV elements like enable cloning with shift key, enable returning cloned DIV element to its source and so on.
		 * Instead of moving, DIV element / row will be cloned and ready for dragging.
		 * Just press SHIFT key and try to drag DIV element / row.
		 * if clone.sendBack property set to true, cloned DIV element will be deleted when dropped to the cell containing its source clone element.
		 * If exists, "climit" class will be updated (increased by 1).
		 * clone.drop property defines placing cloned DIV element (dropped outside any table) to the last marked position.
		 * If this property is set to true, the cloned DIV element will be always placed to the table cell.  
		 * <ul>
		 * <li>{Boolean} clone.keyDiv - If set to true, all DIV elements on tables could be cloned with pressed SHIFT key. Default is false.</li>
		 * <li>{Boolean} clone.keyRow - If set to true, table rows could be cloned with pressed SHIFT key. Default is false.</li>
		 * <li>{Boolean} clone.sendBack - If set to true, cloned element can be returned to its source. Default is false.</li>
		 * <li>{Boolean} clone.drop - If set to true, cloned element will be always placed to the table (to the last possible cell) no matter if is dropped outside the table. Default is false.</li>
		 * </ul>
		 * @type Object
		 * @name REDIPS.drag#clone
		 */
		clone : clone,
		/**
		 * Object contains animation properties: pause and step.
		 * <ul>
		 * <li>{Integer} animation.pause - Animation pause (lower values means the animation will go faster). Default value is 20 (milliseconds).</li>
		 * <li>{Integer} animation.step - Value defines number of pixels in each step. Higher values means bigger step (faster animation) but with less smoothness. Default value is 2 (px).</li>
		 * </ul>
		 * @type Object
		 * @name REDIPS.drag#animation
		 */
		animation : animation,
		/**
		 * Object contains several properties: shift.after, shift.mode, shift.overflow and shift.animation.
		 * <ul>
		 * <li>{String} shift.after - how to shift table content after DIV element is dropped</li>
		 * <li>{String} shift.mode - shift modes (horizontal / vertical)</li>
		 * <li>{String|HTMLElement} shift.overflow - defines how to behave when DIV element falls off the end</li>
		 * <li>{Boolean} shift.animation - if set to true, table content will be relocated with animation - default is false</li>
		 * </ul>
		 *  
		 * shift.after option has the following values: "default", "delete" and "always" (this property will have effect only if dropMode is set to "shift"). Default value is "default".
		 * <ul>
		 * <li>default - table content will be shifted only if DIV element is dropped to the non empty cell</li>
		 * <li>delete - same as "default" + shift table content after DIV element is moved to trash</li>
		 * <li>always - table content will be always shifted</li>
		 * </ul>
		 *  
		 * shift.mode defines shift modes: "horizontal1", "horizontal2", "vertical1" and "vertical2". Default value is "horizontal1".
		 * <ul>
		 * <li>horizontal1 - horizontal shift (element shift can affect more rows)</li>
		 * <li>horizontal2 - horizontal shift (each row is treated separately)</li>
		 * <li>vertical1 - vertical shift (element shift can affect more columns)</li>
		 * <li>vertical2 - vertical shift (each column is treated separately)</li>
		 * </ul>
		 * 
		 * shift.overflow defines how to behave when DIV element falls off the end. Possible actions are: "bunch", "delete" and "source". Default value is "bunch".
		 * <ul>
		 * <li>bunch - overflow will stay in last cell</li>
		 * <li>delete - overflow will be deleted</li>
		 * <li>source - overflow will be moved to the source TD</li>
		 * <li>{HTMLElement} - overflow will be moved to user defined HTML element</li>
		 * </ul> 
		 * @example
		 * // DIV elements will be shifted vertically (each column is treated separately)
		 * REDIPS.drag.shift.mode = 'vertical2';
		 *  
		 * // delete overflowed DIV element
		 * REDIPS.drag.shift.overflow = 'delete';
		 * @type Object
		 * @see <a href="#dropMode">dropMode</a>
		 * @see <a href="#shiftCells">shiftCells</a>
		 * @name REDIPS.drag#shift
		 */
		shift : shift,
		/**
		 * Property defines working types of REDIPS.drag library for dragging table rows: before, after, switch and overwrite.
		 * <ul>
		 * <li>before - row will be dropped before highlighted row</li>
		 * <li>after - row will be dropped after highlighted row</li>
		 * <li>switch - source and highlighted rows will be switched</li>
		 * <li>overwrite - highlighted row will be overwritten</li>
		 * </ul>  
		 * Values "before" and "after" have effect only if row is dropped to other tables (not in current table).
		 * In case of only one table, after/before is defined relatively to source row position (middle row will be dropped before highlighted row if dragged to the table top or after highlighted row in other case).
		 * @type String
		 * @name REDIPS.drag#rowDropMode
		 * @default before
		 */
		rowDropMode : rowDropMode,
		/**
		 * Table sort is feature where tables inside drop container are sorted on each element click.
		 * Clicked DIV element defines table that should be placed on the array top.
		 * Tables order is important for highlighting current cell in case of nested tables.
		 * But sometimes this feature should be turned off when one table overlays the other using "position" style relative, fixed or absolute.
		 * @type Boolean
		 * @name REDIPS.drag#tableSort
		 * @default true
		 */
		tableSort : tableSort,
		/* public methods (documented in main code) */
		init : init,
		initTables : initTables,
		enableDrag : enableDrag,
		enableTable : enableTable,
		cloneObject : cloneObject,
		saveContent : saveContent,
		relocate : relocate,
		emptyCell : emptyCell,
		moveObject : moveObject,
		shiftCells : shiftCells,
		deleteObject : deleteObject,
		getPosition : getPosition,
		rowOpacity : rowOpacity,
		rowEmpty : rowEmpty,
		getScrollPosition : getScrollPosition,
		getStyle : getStyle,
		findParent : findParent,
		findCell : findCell,

		/* Event Handlers */
		/**
		 * All events are part of REDIPS.drag.event namespace.
		 * @type Object
		 * @ignore
		 */
		event : event

		/* Element Event Handlers */
		/**
		 * Event handler invoked if a mouse button is pressed down while the mouse pointer is over DIV element.
		 * @param {HTMLElement} [currentCell] Reference to the table cell of clicked element.
		 * @name REDIPS.drag#event:clicked
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if a mouse button is clicked twice while the mouse pointer is over DIV element.
		 * @name REDIPS.drag#event:dblClicked
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if element is moved from home position.
		 * @param {Boolean} [cloned] True if moved element is actually a cloned DIV. Needed for cases when obj or objOld should be used.
		 * @name REDIPS.drag#event:moved
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if mouse button is pressed down and released while the mouse pointer is over DIV element (element was not actually moved).
		 * Default threshold value is 7px, so if DIV element is moved within threshold value (background color of cell will not change) the same event handler will be called.
		 * @name REDIPS.drag#event:notMoved
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if element is dropped to the table cell.
		 * @param {HTMLElement} [targetCell] Target cell reference.
		 * @name REDIPS.drag#event:dropped
		 * @function
		 * @event
		 */		
		/**
		 * Event handler invoked if mouse button is released but before element is dropped to the table cell.
		 * If boolen "false" is returned from event handler then element drop will be canceled.
		 * Dragged element will be returned to the start position while cloned element will be deleted.
		 * @param {HTMLElement} [targetCell] Target cell reference.
		 * @name REDIPS.drag#event:droppedBefore
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if DIV elements are switched (dropMode is set to "switch").
		 * @param {HTMLElement} [targetCell] Reference to the target table cell.
		 * @name REDIPS.drag#event:switched
		 * @see <a href="#dropMode">dropMode</a>
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked after all DIV elements are relocated and before table is enabled (DIV elements enabled for dragging).
		 * This event can be triggered after single call of relocate() method or after all DIV elements are shifted in "shift" mode.
		 * It is called only if animation is turned on.
		 * @name REDIPS.drag#event:relocateEnd
		 * @see <a href="#relocate">relocate</a>
		 * @see <a href="#event:relocateBefore">event.relocateBefore</a>
		 * @see <a href="#event:relocateAfter">event.relocateAfter</a>
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked before DIV element will be relocated.
		 * For example, in shift drop mode, this event handler will be called before each DIV element move.
		 * @param {HTMLElement} div Reference of DIV element that will be relocated.
		 * @param {HTMLElement} td Reference of TD where DIV element will be relocated.
		 * @name REDIPS.drag#event:relocateBefore
		 * @see <a href="#relocate">relocate</a>
		 * @see <a href="#event:relocateAfter">event.relocateAfter</a>
		 * @see <a href="#event:relocateEnd">event.relocateEnd</a>
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked after DIV element is relocated.
		 * For example, in shift drop mode, this event handler will be called after each DIV element has been moved.
		 * @param {HTMLElement} div Reference of DIV element that is relocated.
		 * @param {HTMLElement} td Reference of TD where DIV element is relocated.
		 * @name REDIPS.drag#event:relocateAfter
		 * @see <a href="#relocate">relocate</a>
		 * @see <a href="#event:relocateBefore">event.relocateBefore</a>
		 * @see <a href="#event:relocateEnd">event.relocateEnd</a>
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked on every change of current (highlighted) table cell.
		 * @param {HTMLElement} [currentCell] Reference to the current (highlighted) table cell.
		 * @name REDIPS.drag#event:changed
		 * @see <a href="#getPosition">getPosition</a>
		 * @example
		 * // set REDIPS.drag reference
		 * var rd = REDIPS.drag;
		 * // define event.changed handler  
		 * rd.event.changed = function () {
		 *   // get current position (method returns positions as array)
		 *   var pos = rd.getPosition();
		 *   // display current row and current cell
		 *   console.log('Changed: ' + pos[1] + ' ' + pos[2]);
		 * };
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked after DIV element is cloned - interactively by moving DIV element or by calling move_object() in "cell" mode with "clone" option.
		 * If event handler is called from move_object() then reference of cloned element is sent as input parameter.
		 * Otherwise, reference of cloned DIV element is set to REDIPS.drag.obj while reference of original element is set to REDIPS.drag.objOld public property.
		 * @param {HTMLElement} [clonedElement] Cloned element reference.
		 * @see <a href="#moveObject">moveObject</a>
		 * @name REDIPS.drag#event:cloned
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked after cloned DIV element is dropped.
		 * @param {HTMLElement} [targetCell] Reference to the target table cell.
		 * @name REDIPS.drag#event:clonedDropped
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if last element is cloned (type 1).
		 * Element has defined "climit1_X" class name where X defines number of elements to clone. Last element can be dragged.
		 * @name REDIPS.drag#event:clonedEnd1
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if last element is cloned (type 2).
		 * Element has defined "climit2_X" class name where X defines number of elements to clone. Last element can't be dragged and stays static.
		 * @name REDIPS.drag#event:clonedEnd2
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if cloned element is dropped on start position or cloned element is dropped outside current table with "clone.drop" property set to false.
		 * This event handler could be also invoked if "clone" type element is placed inside forbidden table cell.
		 * @see <a href="#clone">clone.drop</a>
		 * @name REDIPS.drag#event:notCloned
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if element is deleted (dropped to the "trash" table cell).
		 * @param {Boolean} [cloned] True if cloned element is directly moved to the trash (in one move). If cloned element is dropped to the table and then moved to the trash then "cloned" parameter will be set to false.
		 * @name REDIPS.drag#event:deleted
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if element is undeleted.
		 * After element is dropped to the "trash" table cell and trash.question property is not null then popup with set question will appear.
		 * Clicking on "Cancel" will undelete element and call this event handler.
		 * @see <a href="#trash">trash</a>
		 * @name REDIPS.drag#event:undeleted 
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked after any DIV element action.
		 * For example, if drop option is set to "multiple" (default drop mode) and DIV element is dropped to the table cell then the following order of event handlers will be performed:
		 * <ol>
		 * <li>event.droppedBefore</li>
		 * <li>event.dropped (only if event.droppedBefore doesn't return false)</li>
		 * <li>event.finish</li>
		 * <ol>
		 * So, event.finish will be called after deleting DIV element, cloning, switching and so on.
		 * Its main purpose is to execute some common code (like "cleaning") after any DIV element action.
		 * @name REDIPS.drag#event:finish
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked in a moment when overflow happen in shift mode.
		 * @param {HTMLElement} td Reference of TD where overflow happen.
		 * @name REDIPS.drag#event:shiftOverflow
		 * @see <a href="#dropMode">dropMode</a>
		 * @function
		 * @event
		 */

		/* Row Event Handlers */
		/**
		 * Event handler invoked if a mouse button is pressed down while the mouse pointer is over row handler (div class="drag row").
		 * @param {HTMLElement} [currentCell] Reference to the table cell of clicked element.
		 * @name REDIPS.drag#event:rowClicked
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if row is moved from home position.
		 * @name REDIPS.drag#event:rowMoved
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if a mouse button is pressed down and released while the mouse pointer is over row handler (row was not actually moved).
		 * @name REDIPS.drag#event:rowNotMoved
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked after dropping row to the table.
		 * @param {HTMLElement} [targetRow] Reference to the target row (dropped row).
		 * @param {HTMLElement} [sourceTable] Source table reference. If row is dropped to the same table then this reference and targetRow will be in correlation (actually "source table" contains targetRow).
		 * @param {Integer} [sourceRowIndex] Source row index.
		 * @name REDIPS.drag#event:rowDropped
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked in the moment when mouse button is released but before row is dropped to the table.
		 * @param {HTMLElement} [sourceTable] Source table reference.
		 * @param {Integer} [sourceRowIndex] Source row index.
		 * If boolen "false" is returned from event handler then row drop will be canceled. 
		 * @name REDIPS.drag#event:rowDroppedBefore
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if row is moved around and dropped to the home position.
		 * @param {HTMLElement} [targetCell] Reference to the target table cell.
		 * @name REDIPS.drag#event:rowDroppedSource
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked on every change of current (highlighted) table row.
		 * @param {HTMLElement} [currentCell] Reference to the current (highlighted) table cell.
		 * @name REDIPS.drag#event:rowChanged
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if table row is cloned.
		 * @name REDIPS.drag#event:rowCloned
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if cloned row is dropped to the source row.
		 * @name REDIPS.drag#event:rowNotCloned
		 * @function
		 * @event
		 */	
		/**
		 * Event handler invoked if row is deleted (dropped to the "trash" table cell).
		 * @name REDIPS.drag#event:rowDeleted
		 * @function
		 * @event
		 */
		/**
		 * Event handler invoked if row is undeleted.
		 * After row is dropped to the "trash" table cell and trash.questionRow property is not null then popup with set question will appear.
		 * Clicking on "Cancel" will undelete row and call this event handler.
		 * @see <a href="#trash">trash</a>
		 * @name REDIPS.drag#event:rowUndeleted 
		 * @function
		 * @event
		 */	

	}; // end of public (return statement)		
}());




// if REDIPS.event isn't already defined (from other REDIPS file) 
if (!REDIPS.event) {
	REDIPS.event = (function () {
		var add,	// add event listener
			remove;	// remove event listener
		
		// http://msdn.microsoft.com/en-us/scriptjunkie/ff728624
		// http://www.javascriptrules.com/2009/07/22/cross-browser-event-listener-with-design-patterns/
		// http://www.quirksmode.org/js/events_order.html

		// add event listener
		add = function (obj, eventName, handler) {
			if (obj.addEventListener) {
				// (false) register event in bubble phase (event propagates from from target element up to the DOM root)
				obj.addEventListener(eventName, handler, false);
			}
			else if (obj.attachEvent) {
				obj.attachEvent('on' + eventName, handler);
			}
			else {
				obj['on' + eventName] = handler;
			}
		};
	
		// remove event listener
		remove = function (obj, eventName, handler) {
			if (obj.removeEventListener) {
				obj.removeEventListener(eventName, handler, false);
			}
			else if (obj.detachEvent) {
				obj.detachEvent('on' + eventName, handler);
			}
			else {
				obj['on' + eventName] = null;
			}
		};
	
		return {
			add		: add,
			remove	: remove
		}; // end of public (return statement)	
		
	}());
}

Zerion Mini Shell 1.0