PageRenderTime 27ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/files/jquery.datatables/1.10.9/plugins/rowreorder/js/dataTables.rowReorder.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 598 lines | 304 code | 99 blank | 195 comment | 43 complexity | 69004cc43a4ec76e32286ae7e8a83860 MD5 | raw file
  1. /*! RowReorder 1.0.0
  2. * 2015 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary RowReorder
  6. * @description Row reordering extension for DataTables
  7. * @version 1.0.0
  8. * @file dataTables.rowReorder.js
  9. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  10. * @contact www.sprymedia.co.uk/contact
  11. * @copyright Copyright 2015 SpryMedia Ltd.
  12. *
  13. * This source file is free software, available under the following license:
  14. * MIT license - http://datatables.net/license/mit
  15. *
  16. * This source file is distributed in the hope that it will be useful, but
  17. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  18. * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
  19. *
  20. * For details please refer to: http://www.datatables.net
  21. */
  22. (function(window, document, undefined) {
  23. var factory = function( $, DataTable ) {
  24. "use strict";
  25. /**
  26. * RowReorder provides the ability in DataTables to click and drag rows to
  27. * reorder them. When a row is dropped the data for the rows effected will be
  28. * updated to reflect the change. Normally this data point should also be the
  29. * column being sorted upon in the DataTable but this does not need to be the
  30. * case. RowReorder implements a "data swap" method - so the rows being
  31. * reordered take the value of the data point from the row that used to occupy
  32. * the row's new position.
  33. *
  34. * Initialisation is done by either:
  35. *
  36. * * `rowReorder` parameter in the DataTable initialisation object
  37. * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables
  38. * initialisation.
  39. *
  40. * @class
  41. * @param {object} settings DataTables settings object for the host table
  42. * @param {object} [opts] Configuration options
  43. * @requires jQuery 1.7+
  44. * @requires DataTables 1.10.7+
  45. */
  46. var RowReorder = function ( dt, opts ) {
  47. // Sanity check that we are using DataTables 1.10 or newer
  48. if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
  49. throw 'DataTables RowReorder requires DataTables 1.10.8 or newer';
  50. }
  51. // User and defaults configuration object
  52. this.c = $.extend( true, {},
  53. DataTable.defaults.rowReorder,
  54. RowReorder.defaults,
  55. opts
  56. );
  57. // Internal settings
  58. this.s = {
  59. /** @type {integer} Scroll body top cache */
  60. bodyTop: null,
  61. /** @type {DataTable.Api} DataTables' API instance */
  62. dt: new DataTable.Api( dt ),
  63. /** @type {function} Data fetch function */
  64. getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ),
  65. /** @type {array} Pixel positions for row insertion calculation */
  66. middles: null,
  67. /** @type {function} Data set function */
  68. setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ),
  69. /** @type {Object} Mouse down information */
  70. start: {
  71. top: 0,
  72. left: 0,
  73. offsetTop: 0,
  74. offsetLeft: 0,
  75. nodes: []
  76. },
  77. /** @type {integer} Window height cached value */
  78. windowHeight: 0
  79. };
  80. // DOM items
  81. this.dom = {
  82. /** @type {jQuery} Cloned row being moved around */
  83. clone: null
  84. };
  85. // Check if row reorder has already been initialised on this table
  86. var settings = this.s.dt.settings()[0];
  87. var exisiting = settings.rowreorder;
  88. if ( exisiting ) {
  89. return exisiting;
  90. }
  91. settings.rowreorder = this;
  92. this._constructor();
  93. };
  94. RowReorder.prototype = {
  95. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  96. * Constructor
  97. */
  98. /**
  99. * Initialise the RowReorder instance
  100. *
  101. * @private
  102. */
  103. _constructor: function ()
  104. {
  105. var that = this;
  106. var dt = this.s.dt;
  107. var table = $( dt.table().node() );
  108. // Need to be able to calculate the row positions relative to the table
  109. if ( table.css('position') === 'static' ) {
  110. table.css( 'position', 'relative' );
  111. }
  112. // listen for mouse down on the target column - we have to implement
  113. // this rather than using HTML5 drag and drop as drag and drop doesn't
  114. // appear to work on table rows at this time. Also mobile browsers are
  115. // not supported
  116. $( table ).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) {
  117. var tr = $(this).closest('tr');
  118. // Double check that it is a DataTable row
  119. if ( dt.row( tr ).any() ) {
  120. that._mouseDown( e, tr );
  121. return false;
  122. }
  123. } );
  124. dt.on( 'destroy', function () {
  125. table.off( 'mousedown.rowReorder' );
  126. } );
  127. },
  128. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  129. * Private methods
  130. */
  131. /**
  132. * Cache the measurements that RowReorder needs in the mouse move handler
  133. * to attempt to speed things up, rather than reading from the DOM.
  134. *
  135. * @private
  136. */
  137. _cachePositions: function ()
  138. {
  139. var dt = this.s.dt;
  140. // Frustratingly, if we add `position:relative` to the tbody, the
  141. // position is still relatively to the parent. So we need to adjust
  142. // for that
  143. var headerHeight = $( dt.table().node() ).find('thead').outerHeight();
  144. // Need to pass the nodes through jQuery to get them in document order,
  145. // not what DataTables thinks it is, since we have been altering the
  146. // order
  147. var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
  148. var tops = $.map( nodes, function ( node, i ) {
  149. return $(node).position().top - headerHeight;
  150. } );
  151. var middles = $.map( tops, function ( top, i ) {
  152. return tops.length < i-1 ?
  153. (top + tops[i+1]) / 2 :
  154. (top + top + $( dt.row( ':last-child' ).node() ).outerHeight() ) / 2;
  155. } );
  156. this.s.middles = middles;
  157. this.s.bodyTop = $( dt.table().body() ).offset().top;
  158. this.s.windowHeight = $(window).height();
  159. },
  160. /**
  161. * Clone a row so it can be floated around the screen
  162. *
  163. * @param {jQuery} target Node to be cloned
  164. * @private
  165. */
  166. _clone: function ( target )
  167. {
  168. var dt = this.s.dt;
  169. var clone = $( dt.table().node().cloneNode(false) )
  170. .addClass( 'dt-rowReorder-float' )
  171. .append('<tbody/>')
  172. .append( target.clone( false ) );
  173. // Match the table and column widths - read all sizes before setting
  174. // to reduce reflows
  175. var tableWidth = target.outerWidth();
  176. var tableHeight = target.outerHeight();
  177. var sizes = target.children().map( function () {
  178. return $(this).width();
  179. } );
  180. clone
  181. .width( tableWidth )
  182. .height( tableHeight )
  183. .find('tr').children().each( function (i) {
  184. this.style.width = sizes[i]+'px';
  185. } );
  186. // Insert into the document to have it floating around
  187. clone.appendTo( 'body' );
  188. this.dom.clone = clone;
  189. },
  190. /**
  191. * Update the cloned item's position in the document
  192. *
  193. * @param {object} e Event giving the mouse's position
  194. * @private
  195. */
  196. _clonePosition: function ( e )
  197. {
  198. var start = this.s.start;
  199. var topDiff = this._eventToPage( e, 'Y' ) - start.top;
  200. var leftDiff = this._eventToPage( e, 'X' ) - start.left;
  201. var snap = this.c.snapX;
  202. var left;
  203. if ( snap === true ) {
  204. left = start.offsetLeft;
  205. }
  206. else if ( typeof snap === 'number' ) {
  207. left = start.offsetLeft + snap;
  208. }
  209. else {
  210. left = leftDiff + start.offsetLeft;
  211. }
  212. this.dom.clone.css( {
  213. top: topDiff + start.offsetTop,
  214. left: left
  215. } );
  216. },
  217. /**
  218. * Emit an event on the DataTable for listeners
  219. *
  220. * @param {string} name Event name
  221. * @param {array} args Event arguments
  222. * @private
  223. */
  224. _emitEvent: function ( name, args )
  225. {
  226. this.s.dt.iterator( 'table', function ( ctx, i ) {
  227. $(ctx.nTable).triggerHandler( name+'.dt', args );
  228. } );
  229. },
  230. /**
  231. * Get pageX/Y position from an event, regardless of if it is a mouse or
  232. * touch event.
  233. *
  234. * @param {object} e Event
  235. * @param {string} pos X or Y (must be a capital)
  236. * @private
  237. */
  238. _eventToPage: function ( e, pos )
  239. {
  240. if ( e.type.indexOf( 'touch' ) !== -1 ) {
  241. return e.originalEvent.touches[0][ 'page'+pos ];
  242. }
  243. return e[ 'page'+pos ];
  244. },
  245. /**
  246. * Mouse down event handler. Read initial positions and add event handlers
  247. * for the move.
  248. *
  249. * @param {object} e Mouse event
  250. * @param {jQuery} target TR element that is to be moved
  251. * @private
  252. */
  253. _mouseDown: function ( e, target )
  254. {
  255. var that = this;
  256. var dt = this.s.dt;
  257. var start = this.s.start;
  258. var offset = target.offset();
  259. start.top = this._eventToPage( e, 'Y' );
  260. start.left = this._eventToPage( e, 'X' );
  261. start.offsetTop = offset.top;
  262. start.offsetLeft = offset.left;
  263. start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
  264. this._cachePositions();
  265. this._clone( target );
  266. this._clonePosition( e );
  267. this.dom.target = target;
  268. target.addClass( 'dt-rowReorder-moving' );
  269. $( document )
  270. .on( 'mouseup.rowReorder touchend.rowReorder', function (e) {
  271. that._mouseUp(e);
  272. } )
  273. .on( 'mousemove.rowReorder touchmove.rowReorder', function (e) {
  274. that._mouseMove(e);
  275. } );
  276. // Check if window is x-scrolling - if not, disable it for the duration
  277. // of the drag
  278. if ( $(window).width() === $(document).width() ) {
  279. $(document.body).addClass( 'dt-rowReorder-noOverflow' );
  280. }
  281. },
  282. /**
  283. * Mouse move event handler - move the cloned row and shuffle the table's
  284. * rows if required.
  285. *
  286. * @param {object} e Mouse event
  287. * @private
  288. */
  289. _mouseMove: function ( e )
  290. {
  291. this._clonePosition( e );
  292. // Transform the mouse position into a position in the table's body
  293. var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop;
  294. var middles = this.s.middles;
  295. var insertPoint = null;
  296. var dt = this.s.dt;
  297. var body = dt.table().body();
  298. // Determine where the row should be inserted based on the mouse
  299. // position
  300. for ( var i=0, ien=middles.length ; i<ien ; i++ ) {
  301. if ( bodyY < middles[i] ) {
  302. insertPoint = i;
  303. break;
  304. }
  305. }
  306. if ( insertPoint === null ) {
  307. insertPoint = middles.length;
  308. }
  309. // Perform the DOM shuffle if it has changed from last time
  310. if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) {
  311. if ( insertPoint === 0 ) {
  312. this.dom.target.prependTo( body );
  313. }
  314. else {
  315. var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
  316. if ( insertPoint > this.s.lastInsert ) {
  317. this.dom.target.before( nodes[ insertPoint-1 ] );
  318. }
  319. else {
  320. this.dom.target.after( nodes[ insertPoint ] );
  321. }
  322. }
  323. this._cachePositions();
  324. this.s.lastInsert = insertPoint;
  325. }
  326. // scroll window up and down when reaching the edges
  327. var windowY = this._eventToPage( e, 'Y' ) - document.body.scrollTop;
  328. var scrollInterval = this.s.scrollInterval;
  329. if ( windowY < 65 ) {
  330. if ( ! scrollInterval ) {
  331. this.s.scrollInterval = setInterval( function () {
  332. document.body.scrollTop -= 5;
  333. }, 15 );
  334. }
  335. }
  336. else if ( this.s.windowHeight - windowY < 65 ) {
  337. if ( ! scrollInterval ) {
  338. this.s.scrollInterval = setInterval( function () {
  339. document.body.scrollTop += 5;
  340. }, 15 );
  341. }
  342. }
  343. else {
  344. clearInterval( scrollInterval );
  345. this.s.scrollInterval = null;
  346. }
  347. },
  348. /**
  349. * Mouse up event handler - release the event handlers and perform the
  350. * table updates
  351. *
  352. * @param {object} e Mouse event
  353. * @private
  354. */
  355. _mouseUp: function ( e )
  356. {
  357. var dt = this.s.dt;
  358. var i, ien;
  359. this.dom.clone.remove();
  360. this.dom.clone = null;
  361. this.dom.target.removeClass( 'dt-rowReorder-moving' );
  362. //this.dom.target = null;
  363. $(document).off( '.rowReorder' );
  364. $(document.body).removeClass( 'dt-rowReorder-noOverflow' );
  365. // Calculate the difference
  366. var startNodes = this.s.start.nodes;
  367. var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
  368. var idDiff = {};
  369. var fullDiff = [];
  370. var diffNodes = [];
  371. var getDataFn = this.s.getDataFn;
  372. var setDataFn = this.s.setDataFn;
  373. for ( i=0, ien=startNodes.length ; i<ien ; i++ ) {
  374. if ( startNodes[i] !== endNodes[i] ) {
  375. var id = dt.row( endNodes[i] ).id();
  376. var endRowData = dt.row( endNodes[i] ).data();
  377. var startRowData = dt.row( startNodes[i] ).data();
  378. if ( id ) {
  379. idDiff[ id ] = getDataFn( startRowData );
  380. }
  381. fullDiff.push( {
  382. node: endNodes[i],
  383. oldData: getDataFn( endRowData ),
  384. newData: getDataFn( startRowData ),
  385. newPosition: i,
  386. oldPosition: $.inArray( endNodes[i], startNodes )
  387. } );
  388. diffNodes.push( endNodes[i] );
  389. }
  390. }
  391. // Emit event
  392. this._emitEvent( 'row-reorder', [ fullDiff, {
  393. dataSrc: this.c.dataSrc,
  394. nodes: diffNodes,
  395. values: idDiff
  396. } ] );
  397. // Editor interface
  398. if ( this.c.editor ) {
  399. this.c.editor
  400. .edit( diffNodes, false, {
  401. submit: 'changed'
  402. } )
  403. .multiSet( this.c.dataSrc, idDiff )
  404. .submit();
  405. }
  406. // Do update if required
  407. if ( this.c.update ) {
  408. for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) {
  409. var row = dt.row( fullDiff[i].node );
  410. var rowData = row.data();
  411. setDataFn( rowData, fullDiff[i].newData );
  412. row.invalidate( 'data' );
  413. }
  414. dt.draw( false );
  415. }
  416. }
  417. };
  418. /**
  419. * RowReorder default settings for initialisation
  420. *
  421. * @namespace
  422. * @name RowReorder.defaults
  423. * @static
  424. */
  425. RowReorder.defaults = {
  426. /**
  427. * Data point in the host row's data source object for where to get and set
  428. * the data to reorder. This will normally also be the sorting column.
  429. *
  430. * @type {Number}
  431. */
  432. dataSrc: 0,
  433. /**
  434. * Editor instance that will be used to perform the update
  435. *
  436. * @type {DataTable.Editor}
  437. */
  438. editor: null,
  439. /**
  440. * Drag handle selector. This defines the element that when dragged will
  441. * reorder a row.
  442. *
  443. * @type {String}
  444. */
  445. selector: 'td:first-child',
  446. /**
  447. * Optionally lock the dragged row's x-position. This can be `true` to
  448. * fix the position match the host table's, `false` to allow free movement
  449. * of the row, or a number to define an offset from the host table.
  450. *
  451. * @type {Boolean|number}
  452. */
  453. snapX: false,
  454. /**
  455. * Update the table's data on drop
  456. *
  457. * @type {Boolean}
  458. */
  459. update: true
  460. };
  461. /**
  462. * Version information
  463. *
  464. * @name RowReorder.version
  465. * @static
  466. */
  467. RowReorder.version = '1.0.0';
  468. $.fn.dataTable.RowReorder = RowReorder;
  469. $.fn.DataTable.RowReorder = RowReorder;
  470. // Attach a listener to the document which listens for DataTables initialisation
  471. // events so we can automatically initialise
  472. $(document).on( 'init.dt.dtr', function (e, settings, json) {
  473. if ( e.namespace !== 'dt' ) {
  474. return;
  475. }
  476. var init = settings.oInit.rowReorder;
  477. var defaults = DataTable.defaults.rowReorder;
  478. if ( init || defaults ) {
  479. var opts = $.extend( {}, init, defaults );
  480. if ( init !== false ) {
  481. new RowReorder( settings, opts );
  482. }
  483. }
  484. } );
  485. return RowReorder;
  486. }; // /factory
  487. // Define as an AMD module if possible
  488. if ( typeof define === 'function' && define.amd ) {
  489. define( ['jquery', 'datatables'], factory );
  490. }
  491. else if ( typeof exports === 'object' ) {
  492. // Node/CommonJS
  493. factory( require('jquery'), require('datatables') );
  494. }
  495. else if ( jQuery && !jQuery.fn.dataTable.RowReorder ) {
  496. // Otherwise simply initialise as normal, stopping multiple evaluation
  497. factory( jQuery, jQuery.fn.dataTable );
  498. }
  499. })(window, document);