Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dataTables.rowReorder.js 15KB

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