%PDF- %PDF-
Direktori : /proc/3163975/root/home/infra/glpiinventory/lib/REDIPS_drag/ |
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) }()); }