app/assets/javascripts/dataTables/rowReorder/dataTables.rowReorder.js in effective_datatables-4.17.4 vs app/assets/javascripts/dataTables/rowReorder/dataTables.rowReorder.js in effective_datatables-4.18.0

- old
+ new

@@ -1,17 +1,64 @@ -/*! RowReorder 1.2.4 - * 2015-2018 SpryMedia Ltd - datatables.net/license +/*! RowReorder 1.4.1 + * © SpryMedia Ltd - datatables.net/license */ +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; + + + /** * @summary RowReorder * @description Row reordering extension for DataTables - * @version 1.2.4 + * @version 1.4.1 * @file dataTables.rowReorder.js - * @author SpryMedia Ltd (www.sprymedia.co.uk) - * @contact www.sprymedia.co.uk/contact - * @copyright Copyright 2015-2018 SpryMedia Ltd. + * @author SpryMedia Ltd + * @contact datatables.net + * @copyright Copyright 2015-2023 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * This source file is distributed in the hope that it will be useful, but @@ -19,40 +66,10 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details please refer to: http://www.datatables.net */ -(function( factory ){ - if ( typeof define === 'function' && define.amd ) { - // AMD - define( ['jquery', 'datatables.net'], function ( $ ) { - return factory( $, window, document ); - } ); - } - else if ( typeof exports === 'object' ) { - // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; - } - - return factory( $, root, root.document ); - }; - } - else { - // Browser - factory( jQuery, window, document ); - } -}(function( $, window, document, undefined ) { -'use strict'; -var DataTable = $.fn.dataTable; - - /** * RowReorder provides the ability in DataTables to click and drag rows to * reorder them. When a row is dropped the data for the rows effected will be * updated to reflect the change. Normally this data point should also be the * column being sorted upon in the DataTable but this does not need to be the @@ -70,749 +87,953 @@ * @param {object} settings DataTables settings object for the host table * @param {object} [opts] Configuration options * @requires jQuery 1.7+ * @requires DataTables 1.10.7+ */ -var RowReorder = function ( dt, opts ) { - // Sanity check that we are using DataTables 1.10 or newer - if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { - throw 'DataTables RowReorder requires DataTables 1.10.8 or newer'; - } +var RowReorder = function (dt, opts) { + // Sanity check that we are using DataTables 1.10 or newer + if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) { + throw 'DataTables RowReorder requires DataTables 1.10.8 or newer'; + } - // User and defaults configuration object - this.c = $.extend( true, {}, - DataTable.defaults.rowReorder, - RowReorder.defaults, - opts - ); + // User and defaults configuration object + this.c = $.extend(true, {}, DataTable.defaults.rowReorder, RowReorder.defaults, opts); - // Internal settings - this.s = { - /** @type {integer} Scroll body top cache */ - bodyTop: null, + // Internal settings + this.s = { + /** @type {integer} Scroll body top cache */ + bodyTop: null, - /** @type {DataTable.Api} DataTables' API instance */ - dt: new DataTable.Api( dt ), + /** @type {DataTable.Api} DataTables' API instance */ + dt: new DataTable.Api(dt), - /** @type {function} Data fetch function */ - getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ), + /** @type {function} Data fetch function */ + getDataFn: DataTable.ext.oApi._fnGetObjectDataFn(this.c.dataSrc), - /** @type {array} Pixel positions for row insertion calculation */ - middles: null, + /** @type {array} Pixel positions for row insertion calculation */ + middles: null, - /** @type {Object} Cached dimension information for use in the mouse move event handler */ - scroll: {}, + /** @type {Object} Cached dimension information for use in the mouse move event handler */ + scroll: {}, - /** @type {integer} Interval object used for smooth scrolling */ - scrollInterval: null, + /** @type {integer} Interval object used for smooth scrolling */ + scrollInterval: null, - /** @type {function} Data set function */ - setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ), + /** @type {function} Data set function */ + setDataFn: DataTable.ext.oApi._fnSetObjectDataFn(this.c.dataSrc), - /** @type {Object} Mouse down information */ - start: { - top: 0, - left: 0, - offsetTop: 0, - offsetLeft: 0, - nodes: [] - }, + /** @type {Object} Mouse down information */ + start: { + top: 0, + left: 0, + offsetTop: 0, + offsetLeft: 0, + nodes: [], + rowIndex: 0 + }, - /** @type {integer} Window height cached value */ - windowHeight: 0, + /** @type {integer} Window height cached value */ + windowHeight: 0, - /** @type {integer} Document outer height cached value */ - documentOuterHeight: 0, + /** @type {integer} Document outer height cached value */ + documentOuterHeight: 0, - /** @type {integer} DOM clone outer height cached value */ - domCloneOuterHeight: 0 - }; + /** @type {integer} DOM clone outer height cached value */ + domCloneOuterHeight: 0, - // DOM items - this.dom = { - /** @type {jQuery} Cloned row being moved around */ - clone: null, + /** @type {integer} Flag used for signing if the drop is enabled or not */ + dropAllowed: true + }; - /** @type {jQuery} DataTables scrolling container */ - dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container()) - }; + // DOM items + this.dom = { + /** @type {jQuery} Cloned row being moved around */ + clone: null, + cloneParent: null, - // Check if row reorder has already been initialised on this table - var settings = this.s.dt.settings()[0]; - var exisiting = settings.rowreorder; - if ( exisiting ) { - return exisiting; - } + /** @type {jQuery} DataTables scrolling container */ + dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container()) + }; - settings.rowreorder = this; - this._constructor(); + // Check if row reorder has already been initialised on this table + var settings = this.s.dt.settings()[0]; + var exisiting = settings.rowreorder; + + if (exisiting) { + return exisiting; + } + + if (!this.dom.dtScroll.length) { + this.dom.dtScroll = $(this.s.dt.table().container(), 'tbody'); + } + + settings.rowreorder = this; + this._constructor(); }; +$.extend(RowReorder.prototype, { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constructor + */ -$.extend( RowReorder.prototype, { - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Constructor - */ + /** + * Initialise the RowReorder instance + * + * @private + */ + _constructor: function () { + var that = this; + var dt = this.s.dt; + var table = $(dt.table().node()); - /** - * Initialise the RowReorder instance - * - * @private - */ - _constructor: function () - { - var that = this; - var dt = this.s.dt; - var table = $( dt.table().node() ); + // Need to be able to calculate the row positions relative to the table + if (table.css('position') === 'static') { + table.css('position', 'relative'); + } - // Need to be able to calculate the row positions relative to the table - if ( table.css('position') === 'static' ) { - table.css( 'position', 'relative' ); - } + // listen for mouse down on the target column - we have to implement + // this rather than using HTML5 drag and drop as drag and drop doesn't + // appear to work on table rows at this time. Also mobile browsers are + // not supported. + // Use `table().container()` rather than just the table node for IE8 - + // otherwise it only works once... + $(dt.table().container()).on( + 'mousedown.rowReorder touchstart.rowReorder', + this.c.selector, + function (e) { + if (!that.c.enable) { + return; + } - // listen for mouse down on the target column - we have to implement - // this rather than using HTML5 drag and drop as drag and drop doesn't - // appear to work on table rows at this time. Also mobile browsers are - // not supported. - // Use `table().container()` rather than just the table node for IE8 - - // otherwise it only works once... - $(dt.table().container()).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) { - if ( ! that.c.enable ) { - return; - } + // Ignore excluded children of the selector + if ($(e.target).is(that.c.excludedChildren)) { + return true; + } - // Ignore excluded children of the selector - if ( $(e.target).is(that.c.excludedChildren) ) { - return true; - } + var tr = $(this).closest('tr'); + var row = dt.row(tr); - var tr = $(this).closest('tr'); - var row = dt.row( tr ); + // Double check that it is a DataTable row + if (row.any()) { + that._emitEvent('pre-row-reorder', { + node: row.node(), + index: row.index() + }); - // Double check that it is a DataTable row - if ( row.any() ) { - that._emitEvent( 'pre-row-reorder', { - node: row.node(), - index: row.index() - } ); + that._mouseDown(e, tr); + return false; + } + } + ); - that._mouseDown( e, tr ); - return false; - } - } ); + dt.on('destroy.rowReorder', function () { + $(dt.table().container()).off('.rowReorder'); + dt.off('.rowReorder'); + }); - dt.on( 'destroy.rowReorder', function () { - $(dt.table().container()).off( '.rowReorder' ); - dt.off( '.rowReorder' ); - } ); - }, + this._keyup = this._keyup.bind(this); + }, + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods + */ - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Private methods - */ + /** + * Cache the measurements that RowReorder needs in the mouse move handler + * to attempt to speed things up, rather than reading from the DOM. + * + * @private + */ + _cachePositions: function () { + var dt = this.s.dt; - /** - * Cache the measurements that RowReorder needs in the mouse move handler - * to attempt to speed things up, rather than reading from the DOM. - * - * @private - */ - _cachePositions: function () - { - var dt = this.s.dt; + // Frustratingly, if we add `position:relative` to the tbody, the + // position is still relatively to the parent. So we need to adjust + // for that + var headerHeight = $(dt.table().node()).find('thead').outerHeight(); - // Frustratingly, if we add `position:relative` to the tbody, the - // position is still relatively to the parent. So we need to adjust - // for that - var headerHeight = $( dt.table().node() ).find('thead').outerHeight(); + // Need to pass the nodes through jQuery to get them in document order, + // not what DataTables thinks it is, since we have been altering the + // order + var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); + var middles = $.map(nodes, function (node, i) { + var top = $(node).position().top - headerHeight; - // Need to pass the nodes through jQuery to get them in document order, - // not what DataTables thinks it is, since we have been altering the - // order - var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); - var tops = $.map( nodes, function ( node, i ) { - return $(node).position().top - headerHeight; - } ); + return (top + top + $(node).outerHeight()) / 2; + }); - var middles = $.map( tops, function ( top, i ) { - return tops.length < i-1 ? - (top + tops[i+1]) / 2 : - (top + top + $( dt.row( ':last-child' ).node() ).outerHeight() ) / 2; - } ); + this.s.middles = middles; + this.s.bodyTop = $(dt.table().body()).offset().top; + this.s.windowHeight = $(window).height(); + this.s.documentOuterHeight = $(document).outerHeight(); + this.s.bodyArea = this._calcBodyArea(); + }, - this.s.middles = middles; - this.s.bodyTop = $( dt.table().body() ).offset().top; - this.s.windowHeight = $(window).height(); - this.s.documentOuterHeight = $(document).outerHeight(); - }, + /** + * Clone a row so it can be floated around the screen + * + * @param {jQuery} target Node to be cloned + * @private + */ + _clone: function (target) { + var dt = this.s.dt; + var clone = $(dt.table().node().cloneNode(false)) + .addClass('dt-rowReorder-float') + .append('<tbody/>') + .append(target.clone(false)); + // Match the table and column widths - read all sizes before setting + // to reduce reflows + var tableWidth = target.outerWidth(); + var tableHeight = target.outerHeight(); + var scrollBody = $($(this.s.dt.table().node()).parent()); + var scrollWidth = scrollBody.width(); + var scrollLeft = scrollBody.scrollLeft(); + var sizes = target.children().map(function () { + return $(this).width(); + }); - /** - * Clone a row so it can be floated around the screen - * - * @param {jQuery} target Node to be cloned - * @private - */ - _clone: function ( target ) - { - var dt = this.s.dt; - var clone = $( dt.table().node().cloneNode(false) ) - .addClass( 'dt-rowReorder-float' ) - .append('<tbody/>') - .append( target.clone( false ) ); + clone + .width(tableWidth) + .height(tableHeight) + .find('tr') + .children() + .each(function (i) { + this.style.width = sizes[i] + 'px'; + }); - // Match the table and column widths - read all sizes before setting - // to reduce reflows - var tableWidth = target.outerWidth(); - var tableHeight = target.outerHeight(); - var sizes = target.children().map( function () { - return $(this).width(); - } ); + var cloneParent = $('<div>') + .addClass('dt-rowReorder-float-parent') + .width(scrollWidth) + .append(clone) + .appendTo('body') + .scrollLeft(scrollLeft); - clone - .width( tableWidth ) - .height( tableHeight ) - .find('tr').children().each( function (i) { - this.style.width = sizes[i]+'px'; - } ); + // Insert into the document to have it floating around - // Insert into the document to have it floating around - clone.appendTo( 'body' ); + this.dom.clone = clone; + this.dom.cloneParent = cloneParent; + this.s.domCloneOuterHeight = clone.outerHeight(); + }, - this.dom.clone = clone; - this.s.domCloneOuterHeight = clone.outerHeight(); - }, + /** + * Update the cloned item's position in the document + * + * @param {object} e Event giving the mouse's position + * @private + */ + _clonePosition: function (e) { + var start = this.s.start; + var topDiff = this._eventToPage(e, 'Y') - start.top; + var leftDiff = this._eventToPage(e, 'X') - start.left; + var snap = this.c.snapX; + var left; + var top = topDiff + start.offsetTop; + if (snap === true) { + left = start.offsetLeft; + } + else if (typeof snap === 'number') { + left = start.offsetLeft + snap; + } + else { + left = leftDiff + start.offsetLeft + this.dom.cloneParent.scrollLeft(); + } - /** - * Update the cloned item's position in the document - * - * @param {object} e Event giving the mouse's position - * @private - */ - _clonePosition: function ( e ) - { - var start = this.s.start; - var topDiff = this._eventToPage( e, 'Y' ) - start.top; - var leftDiff = this._eventToPage( e, 'X' ) - start.left; - var snap = this.c.snapX; - var left; - var top = topDiff + start.offsetTop; + if (top < 0) { + top = 0; + } + else if (top + this.s.domCloneOuterHeight > this.s.documentOuterHeight) { + top = this.s.documentOuterHeight - this.s.domCloneOuterHeight; + } - if ( snap === true ) { - left = start.offsetLeft; - } - else if ( typeof snap === 'number' ) { - left = start.offsetLeft + snap; - } - else { - left = leftDiff + start.offsetLeft; - } + this.dom.cloneParent.css({ + top: top, + left: left + }); + }, - if(top < 0) { - top = 0 - } - else if(top + this.s.domCloneOuterHeight > this.s.documentOuterHeight) { - top = this.s.documentOuterHeight - this.s.domCloneOuterHeight; - } + /** + * Emit an event on the DataTable for listeners + * + * @param {string} name Event name + * @param {array} args Event arguments + * @private + */ + _emitEvent: function ( name, args ) + { + var ret; - this.dom.clone.css( { - top: top, - left: left - } ); - }, + this.s.dt.iterator( 'table', function ( ctx, i ) { + var innerRet = $(ctx.nTable).triggerHandler( name+'.dt', args ); + if (innerRet !== undefined) { + ret = innerRet; + } + } ); - /** - * Emit an event on the DataTable for listeners - * - * @param {string} name Event name - * @param {array} args Event arguments - * @private - */ - _emitEvent: function ( name, args ) - { - this.s.dt.iterator( 'table', function ( ctx, i ) { - $(ctx.nTable).triggerHandler( name+'.dt', args ); - } ); - }, + return ret; + }, + /** + * Get pageX/Y position from an event, regardless of if it is a mouse or + * touch event. + * + * @param {object} e Event + * @param {string} pos X or Y (must be a capital) + * @private + */ + _eventToPage: function (e, pos) { + if (e.type.indexOf('touch') !== -1) { + return e.originalEvent.touches[0]['page' + pos]; + } - /** - * Get pageX/Y position from an event, regardless of if it is a mouse or - * touch event. - * - * @param {object} e Event - * @param {string} pos X or Y (must be a capital) - * @private - */ - _eventToPage: function ( e, pos ) - { - if ( e.type.indexOf( 'touch' ) !== -1 ) { - return e.originalEvent.touches[0][ 'page'+pos ]; - } + return e['page' + pos]; + }, - return e[ 'page'+pos ]; - }, + /** + * Mouse down event handler. Read initial positions and add event handlers + * for the move. + * + * @param {object} e Mouse event + * @param {jQuery} target TR element that is to be moved + * @private + */ + _mouseDown: function (e, target) { + var that = this; + var dt = this.s.dt; + var start = this.s.start; + var cancelable = this.c.cancelable; + var offset = target.offset(); + start.top = this._eventToPage(e, 'Y'); + start.left = this._eventToPage(e, 'X'); + start.offsetTop = offset.top; + start.offsetLeft = offset.left; + start.nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); - /** - * Mouse down event handler. Read initial positions and add event handlers - * for the move. - * - * @param {object} e Mouse event - * @param {jQuery} target TR element that is to be moved - * @private - */ - _mouseDown: function ( e, target ) - { - var that = this; - var dt = this.s.dt; - var start = this.s.start; + this._cachePositions(); + this._clone(target); + this._clonePosition(e); - var offset = target.offset(); - start.top = this._eventToPage( e, 'Y' ); - start.left = this._eventToPage( e, 'X' ); - start.offsetTop = offset.top; - start.offsetLeft = offset.left; - start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); + var bodyY = this._eventToPage(e, 'Y') - this.s.bodyTop; + start.rowIndex = this._calcRowIndexByPos(bodyY); - this._cachePositions(); - this._clone( target ); - this._clonePosition( e ); + this.dom.target = target; + target.addClass('dt-rowReorder-moving'); - this.dom.target = target; - target.addClass( 'dt-rowReorder-moving' ); + $(document) + .on('mouseup.rowReorder touchend.rowReorder', function (e) { + that._mouseUp(e); + }) + .on('mousemove.rowReorder touchmove.rowReorder', function (e) { + that._mouseMove(e); + }); - $( document ) - .on( 'mouseup.rowReorder touchend.rowReorder', function (e) { - that._mouseUp(e); - } ) - .on( 'mousemove.rowReorder touchmove.rowReorder', function (e) { - that._mouseMove(e); - } ); + // Check if window is x-scrolling - if not, disable it for the duration + // of the drag + if ($(window).width() === $(document).width()) { + $(document.body).addClass('dt-rowReorder-noOverflow'); + } - // Check if window is x-scrolling - if not, disable it for the duration - // of the drag - if ( $(window).width() === $(document).width() ) { - $(document.body).addClass( 'dt-rowReorder-noOverflow' ); - } + // Cache scrolling information so mouse move doesn't need to read. + // This assumes that the window and DT scroller will not change size + // during an row drag, which I think is a fair assumption + var scrollWrapper = this.dom.dtScroll; + this.s.scroll = { + windowHeight: $(window).height(), + windowWidth: $(window).width(), + dtTop: scrollWrapper.length ? scrollWrapper.offset().top : null, + dtLeft: scrollWrapper.length ? scrollWrapper.offset().left : null, + dtHeight: scrollWrapper.length ? scrollWrapper.outerHeight() : null, + dtWidth: scrollWrapper.length ? scrollWrapper.outerWidth() : null + }; - // Cache scrolling information so mouse move doesn't need to read. - // This assumes that the window and DT scroller will not change size - // during an row drag, which I think is a fair assumption - var scrollWrapper = this.dom.dtScroll; - this.s.scroll = { - windowHeight: $(window).height(), - windowWidth: $(window).width(), - dtTop: scrollWrapper.length ? scrollWrapper.offset().top : null, - dtLeft: scrollWrapper.length ? scrollWrapper.offset().left : null, - dtHeight: scrollWrapper.length ? scrollWrapper.outerHeight() : null, - dtWidth: scrollWrapper.length ? scrollWrapper.outerWidth() : null - }; - }, + // Add keyup handler if dragging is cancelable + if (cancelable) { + $(document).on('keyup', this._keyup); + } + }, + /** + * Mouse move event handler - move the cloned row and shuffle the table's + * rows if required. + * + * @param {object} e Mouse event + * @private + */ + _mouseMove: function (e) { + this._clonePosition(e); - /** - * Mouse move event handler - move the cloned row and shuffle the table's - * rows if required. - * - * @param {object} e Mouse event - * @private - */ - _mouseMove: function ( e ) - { - this._clonePosition( e ); + var start = this.s.start; + var cancelable = this.c.cancelable; - // Transform the mouse position into a position in the table's body - var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop; - var middles = this.s.middles; - var insertPoint = null; - var dt = this.s.dt; - var body = dt.table().body(); + if (cancelable) { + var bodyArea = this.s.bodyArea; + var cloneArea = this._calcCloneParentArea(); + this.s.dropAllowed = this._rectanglesIntersect(bodyArea, cloneArea); - // Determine where the row should be inserted based on the mouse - // position - for ( var i=0, ien=middles.length ; i<ien ; i++ ) { - if ( bodyY < middles[i] ) { - insertPoint = i; - break; - } - } + this.s.dropAllowed + ? $(this.dom.cloneParent).removeClass('drop-not-allowed') + : $(this.dom.cloneParent).addClass('drop-not-allowed'); + } - if ( insertPoint === null ) { - insertPoint = middles.length; - } + // Transform the mouse position into a position in the table's body + var bodyY = this._eventToPage(e, 'Y') - this.s.bodyTop; + var middles = this.s.middles; + var insertPoint = null; - // Perform the DOM shuffle if it has changed from last time - if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) { - if ( insertPoint === 0 ) { - this.dom.target.prependTo( body ); - } - else { - var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); + // Determine where the row should be inserted based on the mouse + // position + for (var i = 0, ien = middles.length; i < ien; i++) { + if (bodyY < middles[i]) { + insertPoint = i; + break; + } + } - if ( insertPoint > this.s.lastInsert ) { - this.dom.target.insertAfter( nodes[ insertPoint-1 ] ); - } - else { - this.dom.target.insertBefore( nodes[ insertPoint ] ); - } - } + if (insertPoint === null) { + insertPoint = middles.length; + } - this._cachePositions(); + if (cancelable) { + if (!this.s.dropAllowed) { + // Move the row back to its original position becasuse the drop is not allowed + insertPoint = + start.rowIndex > this.s.lastInsert ? start.rowIndex + 1 : start.rowIndex; + } - this.s.lastInsert = insertPoint; - } + this.dom.target.toggleClass('dt-rowReorder-moving', this.s.dropAllowed); + } - this._shiftScroll( e ); - }, + this._moveTargetIntoPosition(insertPoint); + this._shiftScroll(e); + }, - /** - * Mouse up event handler - release the event handlers and perform the - * table updates - * - * @param {object} e Mouse event - * @private - */ - _mouseUp: function ( e ) - { - var that = this; - var dt = this.s.dt; - var i, ien; - var dataSrc = this.c.dataSrc; + /** + * Mouse up event handler - release the event handlers and perform the + * table updates + * + * @param {object} e Mouse event + * @private + */ + _mouseUp: function (e) { + var that = this; + var dt = this.s.dt; + var i, ien; + var dataSrc = this.c.dataSrc; + var dropAllowed = this.s.dropAllowed; - this.dom.clone.remove(); - this.dom.clone = null; + if (!dropAllowed) { + that._cancel(); + return; + } - this.dom.target.removeClass( 'dt-rowReorder-moving' ); - //this.dom.target = null; + // Calculate the difference + var startNodes = this.s.start.nodes; + var endNodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); + var idDiff = {}; + var fullDiff = []; + var diffNodes = []; + var getDataFn = this.s.getDataFn; + var setDataFn = this.s.setDataFn; - $(document).off( '.rowReorder' ); - $(document.body).removeClass( 'dt-rowReorder-noOverflow' ); + for (i = 0, ien = startNodes.length; i < ien; i++) { + if (startNodes[i] !== endNodes[i]) { + var id = dt.row(endNodes[i]).id(); + var endRowData = dt.row(endNodes[i]).data(); + var startRowData = dt.row(startNodes[i]).data(); - clearInterval( this.s.scrollInterval ); - this.s.scrollInterval = null; + if (id) { + idDiff[id] = getDataFn(startRowData); + } - // Calculate the difference - var startNodes = this.s.start.nodes; - var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() ); - var idDiff = {}; - var fullDiff = []; - var diffNodes = []; - var getDataFn = this.s.getDataFn; - var setDataFn = this.s.setDataFn; + fullDiff.push({ + node: endNodes[i], + oldData: getDataFn(endRowData), + newData: getDataFn(startRowData), + newPosition: i, + oldPosition: $.inArray(endNodes[i], startNodes) + }); - for ( i=0, ien=startNodes.length ; i<ien ; i++ ) { - if ( startNodes[i] !== endNodes[i] ) { - var id = dt.row( endNodes[i] ).id(); - var endRowData = dt.row( endNodes[i] ).data(); - var startRowData = dt.row( startNodes[i] ).data(); + diffNodes.push(endNodes[i]); + } + } - if ( id ) { - idDiff[ id ] = getDataFn( startRowData ); - } + // Create event args + var eventArgs = [ + fullDiff, + { + dataSrc: dataSrc, + nodes: diffNodes, + values: idDiff, + triggerRow: dt.row(this.dom.target), + originalEvent: e + } + ]; - fullDiff.push( { - node: endNodes[i], - oldData: getDataFn( endRowData ), - newData: getDataFn( startRowData ), - newPosition: i, - oldPosition: $.inArray( endNodes[i], startNodes ) - } ); + // Emit event + var eventResult = this._emitEvent( 'row-reorder', eventArgs ); - diffNodes.push( endNodes[i] ); - } - } + if (eventResult === false) { + that._cancel(); + return; + } - // Create event args - var eventArgs = [ fullDiff, { - dataSrc: dataSrc, - nodes: diffNodes, - values: idDiff, - triggerRow: dt.row( this.dom.target ) - } ]; + // Remove cloned elements, handlers, etc + this._cleanupDragging(); - // Emit event - this._emitEvent( 'row-reorder', eventArgs ); + var update = function () { + if (that.c.update) { + for (i = 0, ien = fullDiff.length; i < ien; i++) { + var row = dt.row(fullDiff[i].node); + var rowData = row.data(); - var update = function () { - if ( that.c.update ) { - for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) { - var row = dt.row( fullDiff[i].node ); - var rowData = row.data(); + setDataFn(rowData, fullDiff[i].newData); - setDataFn( rowData, fullDiff[i].newData ); + // Invalidate the cell that has the same data source as the dataSrc + dt.columns().every(function () { + if (this.dataSrc() === dataSrc) { + dt.cell(fullDiff[i].node, this.index()).invalidate('data'); + } + }); + } - // Invalidate the cell that has the same data source as the dataSrc - dt.columns().every( function () { - if ( this.dataSrc() === dataSrc ) { - dt.cell( fullDiff[i].node, this.index() ).invalidate( 'data' ); - } - } ); - } + // Trigger row reordered event + that._emitEvent('row-reordered', eventArgs); - // Trigger row reordered event - that._emitEvent( 'row-reordered', eventArgs ); + dt.draw(false); + } + }; - dt.draw( false ); - } - }; + // Editor interface + if (this.c.editor) { + // Disable user interaction while Editor is submitting + this.c.enable = false; - // Editor interface - if ( this.c.editor ) { - // Disable user interaction while Editor is submitting - this.c.enable = false; + this.c.editor + .edit(diffNodes, false, $.extend({ submit: 'changed' }, this.c.formOptions)) + .multiSet(dataSrc, idDiff) + .one('preSubmitCancelled.rowReorder', function () { + that.c.enable = true; + that.c.editor.off('.rowReorder'); + dt.draw(false); + }) + .one('submitUnsuccessful.rowReorder', function () { + dt.draw(false); + }) + .one('submitSuccess.rowReorder', function () { + update(); + }) + .one('submitComplete', function () { + that.c.enable = true; + that.c.editor.off('.rowReorder'); + }) + .submit(); + } + else { + update(); + } + }, - this.c.editor - .edit( - diffNodes, - false, - $.extend( {submit: 'changed'}, this.c.formOptions ) - ) - .multiSet( dataSrc, idDiff ) - .one( 'preSubmitCancelled.rowReorder', function () { - that.c.enable = true; - that.c.editor.off( '.rowReorder' ); - dt.draw( false ); - } ) - .one( 'submitUnsuccessful.rowReorder', function () { - dt.draw( false ); - } ) - .one( 'submitSuccess.rowReorder', function () { - update(); - } ) - .one( 'submitComplete', function () { - that.c.enable = true; - that.c.editor.off( '.rowReorder' ); - } ) - .submit(); - } - else { - update(); - } - }, + /** + * Moves the current target into the given position within the table + * and caches the new positions + * + * @param {integer} insertPoint Position + * @private + */ + _moveTargetIntoPosition: function (insertPoint) { + var dt = this.s.dt; + // Perform the DOM shuffle if it has changed from last time + if (this.s.lastInsert === null || this.s.lastInsert !== insertPoint) { + var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); + var insertPlacement = ''; - /** - * Move the window and DataTables scrolling during a drag to scroll new - * content into view. - * - * This matches the `_shiftScroll` method used in AutoFill, but only - * horizontal scrolling is considered here. - * - * @param {object} e Mouse move event object - * @private - */ - _shiftScroll: function ( e ) - { - var that = this; - var dt = this.s.dt; - var scroll = this.s.scroll; - var runInterval = false; - var scrollSpeed = 5; - var buffer = 65; - var - windowY = e.pageY - document.body.scrollTop, - windowVert, - dtVert; + if (insertPoint > this.s.lastInsert) { + this.dom.target.insertAfter(nodes[insertPoint - 1]); + insertPlacement = 'after'; + } + else { + this.dom.target.insertBefore(nodes[insertPoint]); + insertPlacement = 'before'; + } - // Window calculations - based on the mouse position in the window, - // regardless of scrolling - if ( windowY < buffer ) { - windowVert = scrollSpeed * -1; - } - else if ( windowY > scroll.windowHeight - buffer ) { - windowVert = scrollSpeed; - } + this._cachePositions(); - // DataTables scrolling calculations - based on the table's position in - // the document and the mouse position on the page - if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) { - dtVert = scrollSpeed * -1; - } - else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) { - dtVert = scrollSpeed; - } + this.s.lastInsert = insertPoint; - // This is where it gets interesting. We want to continue scrolling - // without requiring a mouse move, so we need an interval to be - // triggered. The interval should continue until it is no longer needed, - // but it must also use the latest scroll commands (for example consider - // that the mouse might move from scrolling up to scrolling left, all - // with the same interval running. We use the `scroll` object to "pass" - // this information to the interval. Can't use local variables as they - // wouldn't be the ones that are used by an already existing interval! - if ( windowVert || dtVert ) { - scroll.windowVert = windowVert; - scroll.dtVert = dtVert; - runInterval = true; - } - else if ( this.s.scrollInterval ) { - // Don't need to scroll - remove any existing timer - clearInterval( this.s.scrollInterval ); - this.s.scrollInterval = null; - } + this._emitEvent('row-reorder-changed', { + insertPlacement, + insertPoint, + row: dt.row(this.dom.target) + }); + } + }, - // If we need to run the interval to scroll and there is no existing - // interval (if there is an existing one, it will continue to run) - if ( ! this.s.scrollInterval && runInterval ) { - this.s.scrollInterval = setInterval( function () { - // Don't need to worry about setting scroll <0 or beyond the - // scroll bound as the browser will just reject that. - if ( scroll.windowVert ) { - document.body.scrollTop += scroll.windowVert; - } + /** + * Removes the cloned elements, event handlers, scrolling intervals, etc + * + * @private + */ + _cleanupDragging: function () { + var cancelable = this.c.cancelable; - // DataTables scrolling - if ( scroll.dtVert ) { - var scroller = that.dom.dtScroll[0]; + this.dom.clone.remove(); + this.dom.cloneParent.remove(); + this.dom.clone = null; + this.dom.cloneParent = null; - if ( scroll.dtVert ) { - scroller.scrollTop += scroll.dtVert; - } - } - }, 20 ); - } - } -} ); + this.dom.target.removeClass('dt-rowReorder-moving'); + //this.dom.target = null; + $(document).off('.rowReorder'); + $(document.body).removeClass('dt-rowReorder-noOverflow'); + clearInterval(this.s.scrollInterval); + this.s.scrollInterval = null; + if (cancelable) { + $(document).off('keyup', this._keyup); + } + }, + + /** + * Move the window and DataTables scrolling during a drag to scroll new + * content into view. + * + * This matches the `_shiftScroll` method used in AutoFill, but only + * horizontal scrolling is considered here. + * + * @param {object} e Mouse move event object + * @private + */ + _shiftScroll: function (e) { + var that = this; + var dt = this.s.dt; + var scroll = this.s.scroll; + var runInterval = false; + var scrollSpeed = 5; + var buffer = 65; + var windowY = e.pageY - document.body.scrollTop, + windowVert, + dtVert; + + // Window calculations - based on the mouse position in the window, + // regardless of scrolling + if (windowY < $(window).scrollTop() + buffer) { + windowVert = scrollSpeed * -1; + } + else if (windowY > scroll.windowHeight + $(window).scrollTop() - buffer) { + windowVert = scrollSpeed; + } + + // DataTables scrolling calculations - based on the table's position in + // the document and the mouse position on the page + if (scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer) { + dtVert = scrollSpeed * -1; + } + else if (scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer) { + dtVert = scrollSpeed; + } + + // This is where it gets interesting. We want to continue scrolling + // without requiring a mouse move, so we need an interval to be + // triggered. The interval should continue until it is no longer needed, + // but it must also use the latest scroll commands (for example consider + // that the mouse might move from scrolling up to scrolling left, all + // with the same interval running. We use the `scroll` object to "pass" + // this information to the interval. Can't use local variables as they + // wouldn't be the ones that are used by an already existing interval! + if (windowVert || dtVert) { + scroll.windowVert = windowVert; + scroll.dtVert = dtVert; + runInterval = true; + } + else if (this.s.scrollInterval) { + // Don't need to scroll - remove any existing timer + clearInterval(this.s.scrollInterval); + this.s.scrollInterval = null; + } + + // If we need to run the interval to scroll and there is no existing + // interval (if there is an existing one, it will continue to run) + if (!this.s.scrollInterval && runInterval) { + this.s.scrollInterval = setInterval(function () { + // Don't need to worry about setting scroll <0 or beyond the + // scroll bound as the browser will just reject that. + if (scroll.windowVert) { + var top = $(document).scrollTop(); + $(document).scrollTop(top + scroll.windowVert); + + if (top !== $(document).scrollTop()) { + var move = parseFloat(that.dom.cloneParent.css('top')); + that.dom.cloneParent.css('top', move + scroll.windowVert); + } + } + + // DataTables scrolling + if (scroll.dtVert) { + var scroller = that.dom.dtScroll[0]; + + if (scroll.dtVert) { + scroller.scrollTop += scroll.dtVert; + } + } + }, 20); + } + }, + + /** + * Calculates the current area of the table body and returns it as a rectangle + * + * @private + */ + _calcBodyArea: function (e) { + var dt = this.s.dt; + var offset = $(dt.table().body()).offset(); + var area = { + left: offset.left, + top: offset.top, + right: offset.left + $(dt.table().body()).width(), + bottom: offset.top + $(dt.table().body()).height() + }; + + return area; + }, + + /** + * Calculates the current area of the cloned parent element and returns it as a rectangle + * + * @private + */ + _calcCloneParentArea: function (e) { + var dt = this.s.dt; + var offset = $(this.dom.cloneParent).offset(); + var area = { + left: offset.left, + top: offset.top, + right: offset.left + $(this.dom.cloneParent).width(), + bottom: offset.top + $(this.dom.cloneParent).height() + }; + + return area; + }, + + /** + * Returns whether the given reactangles intersect or not + * + * @private + */ + _rectanglesIntersect: function (a, b) { + var noOverlap = + a.left >= b.right || b.left >= a.right || a.top >= b.bottom || b.top >= a.bottom; + + return !noOverlap; + }, + + /** + * Calculates the index of the row which lays under the given Y position or + * returns -1 if no such row + * + * @param {integer} insertPoint Position + * @private + */ + _calcRowIndexByPos: function (bodyY) { + // Determine where the row is located based on the mouse + // position + + var dt = this.s.dt; + var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); + var rowIndex = -1; + var headerHeight = $(dt.table().node()).find('thead').outerHeight(); + + $.each(nodes, function (i, node) { + var top = $(node).position().top - headerHeight; + var bottom = top + $(node).outerHeight(); + + if (bodyY >= top && bodyY <= bottom) { + rowIndex = i; + } + }); + + return rowIndex; + }, + + /** + * Handles key up events and cancels the dragging if ESC key is pressed + * + * @param {object} e Mouse move event object + * @private + */ + _keyup: function (e) { + var cancelable = this.c.cancelable; + + if (cancelable && e.which === 27) { + // ESC key is up + e.preventDefault(); + this._cancel(); + } + }, + + /** + * Cancels the dragging, moves target back into its original position + * and cleans up the dragging + * + * @param {object} e Mouse move event object + * @private + */ + _cancel: function () { + var start = this.s.start; + var insertPoint = start.rowIndex > this.s.lastInsert ? start.rowIndex + 1 : start.rowIndex; + + this._moveTargetIntoPosition(insertPoint); + + this._cleanupDragging(); + + // Emit event + this._emitEvent('row-reorder-canceled', [this.s.start.rowIndex]); + } +}); + /** * RowReorder default settings for initialisation * * @namespace * @name RowReorder.defaults * @static */ RowReorder.defaults = { - /** - * Data point in the host row's data source object for where to get and set - * the data to reorder. This will normally also be the sorting column. - * - * @type {Number} - */ - dataSrc: 0, + /** + * Data point in the host row's data source object for where to get and set + * the data to reorder. This will normally also be the sorting column. + * + * @type {Number} + */ + dataSrc: 0, - /** - * Editor instance that will be used to perform the update - * - * @type {DataTable.Editor} - */ - editor: null, + /** + * Editor instance that will be used to perform the update + * + * @type {DataTable.Editor} + */ + editor: null, - /** - * Enable / disable RowReorder's user interaction - * @type {Boolean} - */ - enable: true, + /** + * Enable / disable RowReorder's user interaction + * @type {Boolean} + */ + enable: true, - /** - * Form options to pass to Editor when submitting a change in the row order. - * See the Editor `from-options` object for details of the options - * available. - * @type {Object} - */ - formOptions: {}, + /** + * Form options to pass to Editor when submitting a change in the row order. + * See the Editor `from-options` object for details of the options + * available. + * @type {Object} + */ + formOptions: {}, - /** - * Drag handle selector. This defines the element that when dragged will - * reorder a row. - * - * @type {String} - */ - selector: 'td:first-child', + /** + * Drag handle selector. This defines the element that when dragged will + * reorder a row. + * + * @type {String} + */ + selector: 'td:first-child', - /** - * Optionally lock the dragged row's x-position. This can be `true` to - * fix the position match the host table's, `false` to allow free movement - * of the row, or a number to define an offset from the host table. - * - * @type {Boolean|number} - */ - snapX: false, + /** + * Optionally lock the dragged row's x-position. This can be `true` to + * fix the position match the host table's, `false` to allow free movement + * of the row, or a number to define an offset from the host table. + * + * @type {Boolean|number} + */ + snapX: false, - /** - * Update the table's data on drop - * - * @type {Boolean} - */ - update: true, + /** + * Update the table's data on drop + * + * @type {Boolean} + */ + update: true, - /** - * Selector for children of the drag handle selector that mouseDown events - * will be passed through to and drag will not activate - * - * @type {String} - */ - excludedChildren: 'a' + /** + * Selector for children of the drag handle selector that mouseDown events + * will be passed through to and drag will not activate + * + * @type {String} + */ + excludedChildren: 'a', + + /** + * Enable / disable the canceling of the drag & drop interaction + * + * @type {Boolean} + */ + cancelable: false }; - /* * API */ var Api = $.fn.dataTable.Api; // Doesn't do anything - work around for a bug in DT... Not documented -Api.register( 'rowReorder()', function () { - return this; -} ); +Api.register('rowReorder()', function () { + return this; +}); -Api.register( 'rowReorder.enable()', function ( toggle ) { - if ( toggle === undefined ) { - toggle = true; - } +Api.register('rowReorder.enable()', function (toggle) { + if (toggle === undefined) { + toggle = true; + } - return this.iterator( 'table', function ( ctx ) { - if ( ctx.rowreorder ) { - ctx.rowreorder.c.enable = toggle; - } - } ); -} ); + return this.iterator('table', function (ctx) { + if (ctx.rowreorder) { + ctx.rowreorder.c.enable = toggle; + } + }); +}); -Api.register( 'rowReorder.disable()', function () { - return this.iterator( 'table', function ( ctx ) { - if ( ctx.rowreorder ) { - ctx.rowreorder.c.enable = false; - } - } ); -} ); +Api.register('rowReorder.disable()', function () { + return this.iterator('table', function (ctx) { + if (ctx.rowreorder) { + ctx.rowreorder.c.enable = false; + } + }); +}); - /** * Version information * * @name RowReorder.version * @static */ -RowReorder.version = '1.2.4'; +RowReorder.version = '1.4.1'; - $.fn.dataTable.RowReorder = RowReorder; $.fn.DataTable.RowReorder = RowReorder; // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise -$(document).on( 'init.dt.dtr', function (e, settings, json) { - if ( e.namespace !== 'dt' ) { - return; - } +$(document).on('init.dt.dtr', function (e, settings, json) { + if (e.namespace !== 'dt') { + return; + } - var init = settings.oInit.rowReorder; - var defaults = DataTable.defaults.rowReorder; + var init = settings.oInit.rowReorder; + var defaults = DataTable.defaults.rowReorder; - if ( init || defaults ) { - var opts = $.extend( {}, init, defaults ); + if (init || defaults) { + var opts = $.extend({}, init, defaults); - if ( init !== false ) { - new RowReorder( settings, opts ); - } - } -} ); + if (init !== false) { + new RowReorder(settings, opts); + } + } +}); -return RowReorder; +return DataTable; }));