| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- /**
- * TableDnD plug-in for JQuery, allows you to drag and drop table rows
- * You can set up various options to control how the system will work
- * Copyright (c) Denis Howlett <denish@isocra.com>
- * License: MIT.
- * See https://github.com/isocra/TableDnD
- */
- /*jshint -W054 */
- /*jshint laxbreak: true */
- /*jshint expr: true */
- !function ($, window, document, undefined) {
- // Determine if this is a touch device
- var hasTouch = 'ontouchstart' in document.documentElement,
- startEvent = 'touchstart mousedown',
- moveEvent = 'touchmove mousemove',
- endEvent = 'touchend mouseup';
- $(document).ready(function () {
- function parseStyle(css) {
- var objMap = {},
- parts = css.match(/([^;:]+)/g) || [];
- while (parts.length)
- objMap[parts.shift()] = parts.shift().trim();
- return objMap;
- }
- $('table').each(function () {
- if ($(this).data('table') === 'dnd') {
- $(this).tableDnD({
- onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null,
- onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null,
- onDragClass: $(this).data('ondragclass') === undefined && "tDnD_whileDrag" || $(this).data('ondragclass'),
- onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null,
- onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null,
- onDragStop: $(this).data('ondragstop') && new Function('table', 'row' ,$(this).data('ondragstop')),
- scrollAmount: $(this).data('scrollamount') || 5,
- sensitivity: $(this).data('sensitivity') || 10,
- hierarchyLevel: $(this).data('hierarchylevel') || 0,
- indentArtifact: $(this).data('indentartifact') || '<div class="indent"> </div>',
- autoWidthAdjust: $(this).data('autowidthadjust') || true,
- autoCleanRelations: $(this).data('autocleanrelations') || true,
- jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t',
- serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/,
- serializeParamName: $(this).data('serializeparamname') || false,
- dragHandle: $(this).data('draghandle') || null
- });
- }
- });
- });
- jQuery.tableDnD = {
- /** Keep hold of the current table being dragged */
- currentTable: null,
- /** Keep hold of the current drag object if any */
- dragObject: null,
- /** The current mouse offset */
- mouseOffset: null,
- /** Remember the old value of X and Y so that we don't do too much processing */
- oldX: 0,
- oldY: 0,
- /** Actually build the structure */
- build: function(options) {
- // Set up the defaults if any
- this.each(function() {
- // This is bound to each matching table, set up the defaults and override with user options
- this.tableDnDConfig = $.extend({
- onDragStyle: null,
- onDropStyle: null,
- // Add in the default class for whileDragging
- onDragClass: "tDnD_whileDrag",
- onDrop: null,
- onDragStart: null,
- onDragStop: null,
- scrollAmount: 5,
- /** Sensitivity setting will throttle the trigger rate for movement detection */
- sensitivity: 10,
- /** Hierarchy level to support parent child. 0 switches this functionality off */
- hierarchyLevel: 0,
- /** The html artifact to prepend the first cell with as indentation */
- indentArtifact: '<div class="indent"> </div>',
- /** Automatically adjust width of first cell */
- autoWidthAdjust: true,
- /** Automatic clean-up to ensure relationship integrity */
- autoCleanRelations: true,
- /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */
- jsonPretifySeparator: '\t',
- /** The regular expression to use to trim row IDs */
- serializeRegexp: /[^\-]*$/,
- /** If you want to specify another parameter name instead of the table ID */
- serializeParamName: false,
- /** If you give the name of a class here, then only Cells with this class will be draggable */
- dragHandle: null
- }, options || {});
- // Now make the rows draggable
- $.tableDnD.makeDraggable(this);
- // Prepare hierarchy support
- this.tableDnDConfig.hierarchyLevel
- && $.tableDnD.makeIndented(this);
- });
- // Don't break the chain
- return this;
- },
- makeIndented: function (table) {
- var config = table.tableDnDConfig,
- rows = table.rows,
- firstCell = $(rows).first().find('td:first')[0],
- indentLevel = 0,
- cellWidth = 0,
- longestCell,
- tableStyle;
- if ($(table).hasClass('indtd'))
- return null;
- tableStyle = $(table).addClass('indtd').attr('style');
- $(table).css({whiteSpace: "nowrap"});
- for (var w = 0; w < rows.length; w++) {
- if (cellWidth < $(rows[w]).find('td:first').text().length) {
- cellWidth = $(rows[w]).find('td:first').text().length;
- longestCell = w;
- }
- }
- $(firstCell).css({width: 'auto'});
- for (w = 0; w < config.hierarchyLevel; w++)
- $(rows[longestCell]).find('td:first').prepend(config.indentArtifact);
- firstCell && $(firstCell).css({width: firstCell.offsetWidth});
- tableStyle && $(table).css(tableStyle);
- for (w = 0; w < config.hierarchyLevel; w++)
- $(rows[longestCell]).find('td:first').children(':first').remove();
- config.hierarchyLevel
- && $(rows).each(function () {
- indentLevel = $(this).data('level') || 0;
- indentLevel <= config.hierarchyLevel
- && $(this).data('level', indentLevel)
- || $(this).data('level', 0);
- for (var i = 0; i < $(this).data('level'); i++)
- $(this).find('td:first').prepend(config.indentArtifact);
- });
- return this;
- },
- /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
- makeDraggable: function(table) {
- var config = table.tableDnDConfig;
- config.dragHandle
- // We only need to add the event to the specified cells
- && $(config.dragHandle, table).each(function() {
- // The cell is bound to "this"
- $(this).bind(startEvent, function(e) {
- $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config);
- return false;
- });
- })
- // For backwards compatibility, we add the event to the whole row
- // get all the rows as a wrapped set
- || $(table.rows).each(function() {
- // Iterate through each row, the row is bound to "this"
- if (! $(this).hasClass("nodrag")) {
- $(this).bind(startEvent, function(e) {
- if (e.target.tagName === "TD") {
- $.tableDnD.initialiseDrag(this, table, this, e, config);
- return false;
- }
- }).css("cursor", "move"); // Store the tableDnD object
- } else {
- $(this).css("cursor", ""); // Remove the cursor if we don't have the nodrag class
- }
- });
- },
- currentOrder: function() {
- var rows = this.currentTable.rows;
- return $.map(rows, function (val) {
- return ($(val).data('level') + val.id).replace(/\s/g, '');
- }).join('');
- },
- initialiseDrag: function(dragObject, table, target, e, config) {
- this.dragObject = dragObject;
- this.currentTable = table;
- this.mouseOffset = this.getMouseOffset(target, e);
- this.originalOrder = this.currentOrder();
- // Now we need to capture the mouse up and mouse move event
- // We can use bind so that we don't interfere with other event handlers
- $(document)
- .bind(moveEvent, this.mousemove)
- .bind(endEvent, this.mouseup);
- // Call the onDragStart method if there is one
- config.onDragStart
- && config.onDragStart(table, target);
- },
- updateTables: function() {
- this.each(function() {
- // this is now bound to each matching table
- if (this.tableDnDConfig)
- $.tableDnD.makeDraggable(this);
- });
- },
- /** Get the mouse coordinates from the event (allowing for browser differences) */
- mouseCoords: function(e) {
- if (e.originalEvent.changedTouches)
- return {
- x: e.originalEvent.changedTouches[0].clientX,
- y: e.originalEvent.changedTouches[0].clientY
- };
- if(e.pageX || e.pageY)
- return {
- x: e.pageX,
- y: e.pageY
- };
- return {
- x: e.clientX + document.body.scrollLeft - document.body.clientLeft,
- y: e.clientY + document.body.scrollTop - document.body.clientTop
- };
- },
- /** Given a target element and a mouse eent, get the mouse offset from that element.
- To do this we need the element's position and the mouse position */
- getMouseOffset: function(target, e) {
- var mousePos,
- docPos;
- e = e || window.event;
- docPos = this.getPosition(target);
- mousePos = this.mouseCoords(e);
- return {
- x: mousePos.x - docPos.x,
- y: mousePos.y - docPos.y
- };
- },
- /** Get the position of an element by going up the DOM tree and adding up all the offsets */
- getPosition: function(element) {
- var left = 0,
- top = 0;
- // Safari fix -- thanks to Luis Chato for this!
- // Safari 2 doesn't correctly grab the offsetTop of a table row
- // this is detailed here:
- // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
- // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
- // note that firefox will return a text node as a first child, so designing a more thorough
- // solution may need to take that into account, for now this seems to work in firefox, safari, ie
- if (element.offsetHeight === 0)
- element = element.firstChild; // a table cell
- while (element.offsetParent) {
- left += element.offsetLeft;
- top += element.offsetTop;
- element = element.offsetParent;
- }
- left += element.offsetLeft;
- top += element.offsetTop;
- return {
- x: left,
- y: top
- };
- },
- autoScroll: function (mousePos) {
- var config = this.currentTable.tableDnDConfig,
- yOffset = window.pageYOffset,
- windowHeight = window.innerHeight
- ? window.innerHeight
- : document.documentElement.clientHeight
- ? document.documentElement.clientHeight
- : document.body.clientHeight;
- // Windows version
- // yOffset=document.body.scrollTop;
- if (document.all)
- if (typeof document.compatMode !== 'undefined'
- && document.compatMode !== 'BackCompat')
- yOffset = document.documentElement.scrollTop;
- else if (typeof document.body !== 'undefined')
- yOffset = document.body.scrollTop;
- mousePos.y - yOffset < config.scrollAmount
- && window.scrollBy(0, - config.scrollAmount)
- || windowHeight - (mousePos.y - yOffset) < config.scrollAmount
- && window.scrollBy(0, config.scrollAmount);
- },
- moveVerticle: function (moving, currentRow) {
- if (0 !== moving.vertical
- // If we're over a row then move the dragged row to there so that the user sees the
- // effect dynamically
- && currentRow
- && this.dragObject !== currentRow
- && this.dragObject.parentNode === currentRow.parentNode)
- 0 > moving.vertical
- && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling)
- || 0 < moving.vertical
- && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow);
- },
- moveHorizontal: function (moving, currentRow) {
- var config = this.currentTable.tableDnDConfig,
- currentLevel;
- if (!config.hierarchyLevel
- || 0 === moving.horizontal
- // We only care if moving left or right on the current row
- || !currentRow
- || this.dragObject !== currentRow)
- return null;
- currentLevel = $(currentRow).data('level');
- 0 < moving.horizontal
- && currentLevel > 0
- && $(currentRow).find('td:first').children(':first').remove()
- && $(currentRow).data('level', --currentLevel);
- 0 > moving.horizontal
- && currentLevel < config.hierarchyLevel
- && $(currentRow).prev().data('level') >= currentLevel
- && $(currentRow).children(':first').prepend(config.indentArtifact)
- && $(currentRow).data('level', ++currentLevel);
- },
- mousemove: function(e) {
- var dragObj = $($.tableDnD.dragObject),
- config = $.tableDnD.currentTable.tableDnDConfig,
- currentRow,
- mousePos,
- moving,
- x,
- y;
- e && e.preventDefault();
- if (!$.tableDnD.dragObject)
- return false;
- // prevent touch device screen scrolling
- e.type === 'touchmove'
- && event.preventDefault(); // TODO verify this is event and not really e
- // update the style to show we're dragging
- config.onDragClass
- && dragObj.addClass(config.onDragClass)
- || dragObj.css(config.onDragStyle);
- mousePos = $.tableDnD.mouseCoords(e);
- x = mousePos.x - $.tableDnD.mouseOffset.x;
- y = mousePos.y - $.tableDnD.mouseOffset.y;
- // auto scroll the window
- $.tableDnD.autoScroll(mousePos);
- currentRow = $.tableDnD.findDropTargetRow(dragObj, y);
- moving = $.tableDnD.findDragDirection(x, y);
- $.tableDnD.moveVerticle(moving, currentRow);
- $.tableDnD.moveHorizontal(moving, currentRow);
- return false;
- },
- findDragDirection: function (x,y) {
- var sensitivity = this.currentTable.tableDnDConfig.sensitivity,
- oldX = this.oldX,
- oldY = this.oldY,
- xMin = oldX - sensitivity,
- xMax = oldX + sensitivity,
- yMin = oldY - sensitivity,
- yMax = oldY + sensitivity,
- moving = {
- horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1,
- vertical : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1
- };
- // update the old value
- if (moving.horizontal !== 0)
- this.oldX = x;
- if (moving.vertical !== 0)
- this.oldY = y;
- return moving;
- },
- /** We're only worried about the y position really, because we can only move rows up and down */
- findDropTargetRow: function(draggedRow, y) {
- var rowHeight = 0,
- rows = this.currentTable.rows,
- config = this.currentTable.tableDnDConfig,
- rowY = 0,
- row = null;
- for (var i = 0; i < rows.length; i++) {
- row = rows[i];
- rowY = this.getPosition(row).y;
- rowHeight = parseInt(row.offsetHeight) / 2;
- if (row.offsetHeight === 0) {
- rowY = this.getPosition(row.firstChild).y;
- rowHeight = parseInt(row.firstChild.offsetHeight) / 2;
- }
- // Because we always have to insert before, we need to offset the height a bit
- if (y > (rowY - rowHeight) && y < (rowY + rowHeight))
- // that's the row we're over
- // If it's the same as the current row, ignore it
- if (draggedRow.is(row)
- || (config.onAllowDrop
- && !config.onAllowDrop(draggedRow, row))
- // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
- || $(row).hasClass("nodrop"))
- return null;
- else
- return row;
- }
- return null;
- },
- processMouseup: function() {
- if (!this.currentTable || !this.dragObject)
- return null;
- var config = this.currentTable.tableDnDConfig,
- droppedRow = this.dragObject,
- parentLevel = 0,
- myLevel = 0;
- // Unbind the event handlers
- $(document)
- .unbind(moveEvent, this.mousemove)
- .unbind(endEvent, this.mouseup);
- config.hierarchyLevel
- && config.autoCleanRelations
- && $(this.currentTable.rows).first().find('td:first').children().each(function () {
- myLevel = $(this).parents('tr:first').data('level');
- myLevel
- && $(this).parents('tr:first').data('level', --myLevel)
- && $(this).remove();
- })
- && config.hierarchyLevel > 1
- && $(this.currentTable.rows).each(function () {
- myLevel = $(this).data('level');
- if (myLevel > 1) {
- parentLevel = $(this).prev().data('level');
- while (myLevel > parentLevel + 1) {
- $(this).find('td:first').children(':first').remove();
- $(this).data('level', --myLevel);
- }
- }
- });
- // If we have a dragObject, then we need to release it,
- // The row will already have been moved to the right place so we just reset stuff
- config.onDragClass
- && $(droppedRow).removeClass(config.onDragClass)
- || $(droppedRow).css(config.onDropStyle);
- this.dragObject = null;
- // Call the onDrop method if there is one
- config.onDrop
- && this.originalOrder !== this.currentOrder()
- && $(droppedRow).hide().fadeIn('fast')
- && config.onDrop(this.currentTable, droppedRow);
- // Call the onDragStop method if there is one
- config.onDragStop
- && config.onDragStop(this.currentTable, droppedRow);
- this.currentTable = null; // let go of the table too
- },
- mouseup: function(e) {
- e && e.preventDefault();
- $.tableDnD.processMouseup();
- return false;
- },
- jsonize: function(pretify) {
- var table = this.currentTable;
- if (pretify)
- return JSON.stringify(
- this.tableData(table),
- null,
- table.tableDnDConfig.jsonPretifySeparator
- );
- return JSON.stringify(this.tableData(table));
- },
- serialize: function() {
- return $.param(this.tableData(this.currentTable));
- },
- serializeTable: function(table) {
- var result = "";
- var paramName = table.tableDnDConfig.serializeParamName || table.id;
- var rows = table.rows;
- for (var i=0; i<rows.length; i++) {
- if (result.length > 0) result += "&";
- var rowId = rows[i].id;
- if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
- rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
- result += paramName + '[]=' + rowId;
- }
- }
- return result;
- },
- serializeTables: function() {
- var result = [];
- $('table').each(function() {
- this.id && result.push($.param($.tableDnD.tableData(this)));
- });
- return result.join('&');
- },
- tableData: function (table) {
- var config = table.tableDnDConfig,
- previousIDs = [],
- currentLevel = 0,
- indentLevel = 0,
- rowID = null,
- data = {},
- getSerializeRegexp,
- paramName,
- currentID,
- rows;
- if (!table)
- table = this.currentTable;
- if (!table || !table.rows || !table.rows.length)
- return {error: { code: 500, message: "Not a valid table."}};
- if (!table.id && !config.serializeParamName)
- return {error: { code: 500, message: "No serializable unique id provided."}};
-
- rows = config.autoCleanRelations
- && table.rows
- || $.makeArray(table.rows);
- paramName = config.serializeParamName || table.id;
- currentID = paramName;
- getSerializeRegexp = function (rowId) {
- if (rowId && config && config.serializeRegexp)
- return rowId.match(config.serializeRegexp)[0];
- return rowId;
- };
- data[currentID] = [];
- !config.autoCleanRelations
- && $(rows[0]).data('level')
- && rows.unshift({id: 'undefined'});
- for (var i=0; i < rows.length; i++) {
- if (config.hierarchyLevel) {
- indentLevel = $(rows[i]).data('level') || 0;
- if (indentLevel === 0) {
- currentID = paramName;
- previousIDs = [];
- }
- else if (indentLevel > currentLevel) {
- previousIDs.push([currentID, currentLevel]);
- currentID = getSerializeRegexp(rows[i-1].id);
- }
- else if (indentLevel < currentLevel) {
- for (var h = 0; h < previousIDs.length; h++) {
- if (previousIDs[h][1] === indentLevel)
- currentID = previousIDs[h][0];
- if (previousIDs[h][1] >= currentLevel)
- previousIDs[h][1] = 0;
- }
- }
- currentLevel = indentLevel;
- if (!$.isArray(data[currentID]))
- data[currentID] = [];
- rowID = getSerializeRegexp(rows[i].id);
- rowID && data[currentID].push(rowID);
- }
- else {
- rowID = getSerializeRegexp(rows[i].id);
- rowID && data[currentID].push(rowID);
- }
- }
- return data;
- }
- };
- jQuery.fn.extend(
- {
- tableDnD : $.tableDnD.build,
- tableDnDUpdate : $.tableDnD.updateTables,
- tableDnDSerialize : $.proxy($.tableDnD.serialize, $.tableDnD),
- tableDnDSerializeAll : $.tableDnD.serializeTables,
- tableDnDData : $.proxy($.tableDnD.tableData, $.tableDnD)
- }
- );
- }(jQuery, window, window.document);
|