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.select.js 33KB

2 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. /*! Select for DataTables 1.3.4-dev
  2. * 2015-2021 SpryMedia Ltd - datatables.net/license/mit
  3. */
  4. /**
  5. * @summary Select for DataTables
  6. * @description A collection of API methods, events and buttons for DataTables
  7. * that provides selection options of the items in a DataTable
  8. * @version 1.3.4-dev
  9. * @file dataTables.select.js
  10. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  11. * @contact datatables.net/forums
  12. * @copyright Copyright 2015-2021 SpryMedia Ltd.
  13. *
  14. * This source file is free software, available under the following license:
  15. * MIT license - http://datatables.net/license/mit
  16. *
  17. * This source file is distributed in the hope that it will be useful, but
  18. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  19. * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
  20. *
  21. * For details please refer to: http://www.datatables.net/extensions/select
  22. */
  23. (function( factory ){
  24. if ( typeof define === 'function' && define.amd ) {
  25. // AMD
  26. define( ['jquery', 'datatables.net'], function ( $ ) {
  27. return factory( $, window, document );
  28. } );
  29. }
  30. else if ( typeof exports === 'object' ) {
  31. // CommonJS
  32. module.exports = function (root, $) {
  33. if ( ! root ) {
  34. root = window;
  35. }
  36. if ( ! $ || ! $.fn.dataTable ) {
  37. $ = require('datatables.net')(root, $).$;
  38. }
  39. return factory( $, root, root.document );
  40. };
  41. }
  42. else {
  43. // Browser
  44. factory( jQuery, window, document );
  45. }
  46. }(function( $, window, document, undefined ) {
  47. 'use strict';
  48. var DataTable = $.fn.dataTable;
  49. // Version information for debugger
  50. DataTable.select = {};
  51. DataTable.select.version = '1.3.4-dev';
  52. DataTable.select.init = function ( dt ) {
  53. var ctx = dt.settings()[0];
  54. if (ctx._select) {
  55. return;
  56. }
  57. var savedSelected = dt.state.loaded();
  58. var selectAndSave = function(e, settings, data) {
  59. if(data === null || data.select === undefined) {
  60. return;
  61. }
  62. dt.rows().deselect();
  63. dt.columns().deselect();
  64. dt.cells().deselect();
  65. if (data.select.rows !== undefined) {
  66. dt.rows(data.select.rows).select();
  67. }
  68. if (data.select.columns !== undefined) {
  69. dt.columns(data.select.columns).select();
  70. }
  71. if (data.select.cells !== undefined) {
  72. for(var i = 0; i < data.select.cells.length; i++) {
  73. dt.cell(data.select.cells[i].row, data.select.cells[i].column).select();
  74. }
  75. }
  76. dt.state.save();
  77. }
  78. dt.one('init', function() {
  79. dt.on('stateSaveParams', function(e, settings, data) {
  80. data.select = {};
  81. data.select.rows = dt.rows({selected:true}).ids(true).toArray();
  82. data.select.columns = dt.columns({selected:true})[0];
  83. data.select.cells = dt.cells({selected:true})[0].map(function(coords) {
  84. return {row: dt.row(coords.row).id(true), column: coords.column}
  85. });
  86. })
  87. selectAndSave(undefined, undefined, savedSelected)
  88. dt.on('stateLoaded stateLoadParams', selectAndSave)
  89. })
  90. var init = ctx.oInit.select;
  91. var defaults = DataTable.defaults.select;
  92. var opts = init === undefined ?
  93. defaults :
  94. init;
  95. // Set defaults
  96. var items = 'row';
  97. var style = 'api';
  98. var blurable = false;
  99. var toggleable = true;
  100. var info = true;
  101. var selector = 'td, th';
  102. var className = 'selected';
  103. var setStyle = false;
  104. ctx._select = {};
  105. // Initialisation customisations
  106. if ( opts === true ) {
  107. style = 'os';
  108. setStyle = true;
  109. }
  110. else if ( typeof opts === 'string' ) {
  111. style = opts;
  112. setStyle = true;
  113. }
  114. else if ( $.isPlainObject( opts ) ) {
  115. if ( opts.blurable !== undefined ) {
  116. blurable = opts.blurable;
  117. }
  118. if ( opts.toggleable !== undefined ) {
  119. toggleable = opts.toggleable;
  120. }
  121. if ( opts.info !== undefined ) {
  122. info = opts.info;
  123. }
  124. if ( opts.items !== undefined ) {
  125. items = opts.items;
  126. }
  127. if ( opts.style !== undefined ) {
  128. style = opts.style;
  129. setStyle = true;
  130. }
  131. else {
  132. style = 'os';
  133. setStyle = true;
  134. }
  135. if ( opts.selector !== undefined ) {
  136. selector = opts.selector;
  137. }
  138. if ( opts.className !== undefined ) {
  139. className = opts.className;
  140. }
  141. }
  142. dt.select.selector( selector );
  143. dt.select.items( items );
  144. dt.select.style( style );
  145. dt.select.blurable( blurable );
  146. dt.select.toggleable( toggleable );
  147. dt.select.info( info );
  148. ctx._select.className = className;
  149. // Sort table based on selected rows. Requires Select Datatables extension
  150. $.fn.dataTable.ext.order['select-checkbox'] = function ( settings, col ) {
  151. return this.api().column( col, {order: 'index'} ).nodes().map( function ( td ) {
  152. if ( settings._select.items === 'row' ) {
  153. return $( td ).parent().hasClass( settings._select.className );
  154. } else if ( settings._select.items === 'cell' ) {
  155. return $( td ).hasClass( settings._select.className );
  156. }
  157. return false;
  158. });
  159. };
  160. // If the init options haven't enabled select, but there is a selectable
  161. // class name, then enable
  162. if ( ! setStyle && $( dt.table().node() ).hasClass( 'selectable' ) ) {
  163. dt.select.style( 'os' );
  164. }
  165. };
  166. /*
  167. Select is a collection of API methods, event handlers, event emitters and
  168. buttons (for the `Buttons` extension) for DataTables. It provides the following
  169. features, with an overview of how they are implemented:
  170. ## Selection of rows, columns and cells. Whether an item is selected or not is
  171. stored in:
  172. * rows: a `_select_selected` property which contains a boolean value of the
  173. DataTables' `aoData` object for each row
  174. * columns: a `_select_selected` property which contains a boolean value of the
  175. DataTables' `aoColumns` object for each column
  176. * cells: a `_selected_cells` property which contains an array of boolean values
  177. of the `aoData` object for each row. The array is the same length as the
  178. columns array, with each element of it representing a cell.
  179. This method of using boolean flags allows Select to operate when nodes have not
  180. been created for rows / cells (DataTables' defer rendering feature).
  181. ## API methods
  182. A range of API methods are available for triggering selection and de-selection
  183. of rows. Methods are also available to configure the selection events that can
  184. be triggered by an end user (such as which items are to be selected). To a large
  185. extent, these of API methods *is* Select. It is basically a collection of helper
  186. functions that can be used to select items in a DataTable.
  187. Configuration of select is held in the object `_select` which is attached to the
  188. DataTables settings object on initialisation. Select being available on a table
  189. is not optional when Select is loaded, but its default is for selection only to
  190. be available via the API - so the end user wouldn't be able to select rows
  191. without additional configuration.
  192. The `_select` object contains the following properties:
  193. ```
  194. {
  195. items:string - Can be `rows`, `columns` or `cells`. Defines what item
  196. will be selected if the user is allowed to activate row
  197. selection using the mouse.
  198. style:string - Can be `none`, `single`, `multi` or `os`. Defines the
  199. interaction style when selecting items
  200. blurable:boolean - If row selection can be cleared by clicking outside of
  201. the table
  202. toggleable:boolean - If row selection can be cancelled by repeated clicking
  203. on the row
  204. info:boolean - If the selection summary should be shown in the table
  205. information elements
  206. }
  207. ```
  208. In addition to the API methods, Select also extends the DataTables selector
  209. options for rows, columns and cells adding a `selected` option to the selector
  210. options object, allowing the developer to select only selected items or
  211. unselected items.
  212. ## Mouse selection of items
  213. Clicking on items can be used to select items. This is done by a simple event
  214. handler that will select the items using the API methods.
  215. */
  216. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  217. * Local functions
  218. */
  219. /**
  220. * Add one or more cells to the selection when shift clicking in OS selection
  221. * style cell selection.
  222. *
  223. * Cell range is more complicated than row and column as we want to select
  224. * in the visible grid rather than by index in sequence. For example, if you
  225. * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
  226. * should also be selected (and not 1-3, 1-4. etc)
  227. *
  228. * @param {DataTable.Api} dt DataTable
  229. * @param {object} idx Cell index to select to
  230. * @param {object} last Cell index to select from
  231. * @private
  232. */
  233. function cellRange( dt, idx, last )
  234. {
  235. var indexes;
  236. var columnIndexes;
  237. var rowIndexes;
  238. var selectColumns = function ( start, end ) {
  239. if ( start > end ) {
  240. var tmp = end;
  241. end = start;
  242. start = tmp;
  243. }
  244. var record = false;
  245. return dt.columns( ':visible' ).indexes().filter( function (i) {
  246. if ( i === start ) {
  247. record = true;
  248. }
  249. if ( i === end ) { // not else if, as start might === end
  250. record = false;
  251. return true;
  252. }
  253. return record;
  254. } );
  255. };
  256. var selectRows = function ( start, end ) {
  257. var indexes = dt.rows( { search: 'applied' } ).indexes();
  258. // Which comes first - might need to swap
  259. if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) {
  260. var tmp = end;
  261. end = start;
  262. start = tmp;
  263. }
  264. var record = false;
  265. return indexes.filter( function (i) {
  266. if ( i === start ) {
  267. record = true;
  268. }
  269. if ( i === end ) {
  270. record = false;
  271. return true;
  272. }
  273. return record;
  274. } );
  275. };
  276. if ( ! dt.cells( { selected: true } ).any() && ! last ) {
  277. // select from the top left cell to this one
  278. columnIndexes = selectColumns( 0, idx.column );
  279. rowIndexes = selectRows( 0 , idx.row );
  280. }
  281. else {
  282. // Get column indexes between old and new
  283. columnIndexes = selectColumns( last.column, idx.column );
  284. rowIndexes = selectRows( last.row , idx.row );
  285. }
  286. indexes = dt.cells( rowIndexes, columnIndexes ).flatten();
  287. if ( ! dt.cells( idx, { selected: true } ).any() ) {
  288. // Select range
  289. dt.cells( indexes ).select();
  290. }
  291. else {
  292. // Deselect range
  293. dt.cells( indexes ).deselect();
  294. }
  295. }
  296. /**
  297. * Disable mouse selection by removing the selectors
  298. *
  299. * @param {DataTable.Api} dt DataTable to remove events from
  300. * @private
  301. */
  302. function disableMouseSelection( dt )
  303. {
  304. var ctx = dt.settings()[0];
  305. var selector = ctx._select.selector;
  306. $( dt.table().container() )
  307. .off( 'mousedown.dtSelect', selector )
  308. .off( 'mouseup.dtSelect', selector )
  309. .off( 'click.dtSelect', selector );
  310. $('body').off( 'click.dtSelect' + _safeId(dt.table().node()) );
  311. }
  312. /**
  313. * Attach mouse listeners to the table to allow mouse selection of items
  314. *
  315. * @param {DataTable.Api} dt DataTable to remove events from
  316. * @private
  317. */
  318. function enableMouseSelection ( dt )
  319. {
  320. var container = $( dt.table().container() );
  321. var ctx = dt.settings()[0];
  322. var selector = ctx._select.selector;
  323. var matchSelection;
  324. container
  325. .on( 'mousedown.dtSelect', selector, function(e) {
  326. // Disallow text selection for shift clicking on the table so multi
  327. // element selection doesn't look terrible!
  328. if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
  329. container
  330. .css( '-moz-user-select', 'none' )
  331. .one('selectstart.dtSelect', selector, function () {
  332. return false;
  333. } );
  334. }
  335. if ( window.getSelection ) {
  336. matchSelection = window.getSelection();
  337. }
  338. } )
  339. .on( 'mouseup.dtSelect', selector, function() {
  340. // Allow text selection to occur again, Mozilla style (tested in FF
  341. // 35.0.1 - still required)
  342. container.css( '-moz-user-select', '' );
  343. } )
  344. .on( 'click.dtSelect', selector, function ( e ) {
  345. var items = dt.select.items();
  346. var idx;
  347. // If text was selected (click and drag), then we shouldn't change
  348. // the row's selected state
  349. if ( matchSelection ) {
  350. var selection = window.getSelection();
  351. // If the element that contains the selection is not in the table, we can ignore it
  352. // This can happen if the developer selects text from the click event
  353. if ( ! selection.anchorNode || $(selection.anchorNode).closest('table')[0] === dt.table().node() ) {
  354. if ( selection !== matchSelection ) {
  355. return;
  356. }
  357. }
  358. }
  359. var ctx = dt.settings()[0];
  360. var wrapperClass = dt.settings()[0].oClasses.sWrapper.trim().replace(/ +/g, '.');
  361. // Ignore clicks inside a sub-table
  362. if ( $(e.target).closest('div.'+wrapperClass)[0] != dt.table().container() ) {
  363. return;
  364. }
  365. var cell = dt.cell( $(e.target).closest('td, th') );
  366. // Check the cell actually belongs to the host DataTable (so child
  367. // rows, etc, are ignored)
  368. if ( ! cell.any() ) {
  369. return;
  370. }
  371. var event = $.Event('user-select.dt');
  372. eventTrigger( dt, event, [ items, cell, e ] );
  373. if ( event.isDefaultPrevented() ) {
  374. return;
  375. }
  376. var cellIndex = cell.index();
  377. if ( items === 'row' ) {
  378. idx = cellIndex.row;
  379. typeSelect( e, dt, ctx, 'row', idx );
  380. }
  381. else if ( items === 'column' ) {
  382. idx = cell.index().column;
  383. typeSelect( e, dt, ctx, 'column', idx );
  384. }
  385. else if ( items === 'cell' ) {
  386. idx = cell.index();
  387. typeSelect( e, dt, ctx, 'cell', idx );
  388. }
  389. ctx._select_lastCell = cellIndex;
  390. } );
  391. // Blurable
  392. $('body').on( 'click.dtSelect' + _safeId(dt.table().node()), function ( e ) {
  393. if ( ctx._select.blurable ) {
  394. // If the click was inside the DataTables container, don't blur
  395. if ( $(e.target).parents().filter( dt.table().container() ).length ) {
  396. return;
  397. }
  398. // Ignore elements which have been removed from the DOM (i.e. paging
  399. // buttons)
  400. if ( $(e.target).parents('html').length === 0 ) {
  401. return;
  402. }
  403. // Don't blur in Editor form
  404. if ( $(e.target).parents('div.DTE').length ) {
  405. return;
  406. }
  407. clear( ctx, true );
  408. }
  409. } );
  410. }
  411. /**
  412. * Trigger an event on a DataTable
  413. *
  414. * @param {DataTable.Api} api DataTable to trigger events on
  415. * @param {boolean} selected true if selected, false if deselected
  416. * @param {string} type Item type acting on
  417. * @param {boolean} any Require that there are values before
  418. * triggering
  419. * @private
  420. */
  421. function eventTrigger ( api, type, args, any )
  422. {
  423. if ( any && ! api.flatten().length ) {
  424. return;
  425. }
  426. if ( typeof type === 'string' ) {
  427. type = type +'.dt';
  428. }
  429. args.unshift( api );
  430. $(api.table().node()).trigger( type, args );
  431. }
  432. /**
  433. * Update the information element of the DataTable showing information about the
  434. * items selected. This is done by adding tags to the existing text
  435. *
  436. * @param {DataTable.Api} api DataTable to update
  437. * @private
  438. */
  439. function info ( api )
  440. {
  441. var ctx = api.settings()[0];
  442. if ( ! ctx._select.info || ! ctx.aanFeatures.i ) {
  443. return;
  444. }
  445. if ( api.select.style() === 'api' ) {
  446. return;
  447. }
  448. var rows = api.rows( { selected: true } ).flatten().length;
  449. var columns = api.columns( { selected: true } ).flatten().length;
  450. var cells = api.cells( { selected: true } ).flatten().length;
  451. var add = function ( el, name, num ) {
  452. el.append( $('<span class="select-item"/>').append( api.i18n(
  453. 'select.'+name+'s',
  454. { _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
  455. num
  456. ) ) );
  457. };
  458. // Internal knowledge of DataTables to loop over all information elements
  459. $.each( ctx.aanFeatures.i, function ( i, el ) {
  460. el = $(el);
  461. var output = $('<span class="select-info"/>');
  462. add( output, 'row', rows );
  463. add( output, 'column', columns );
  464. add( output, 'cell', cells );
  465. var exisiting = el.children('span.select-info');
  466. if ( exisiting.length ) {
  467. exisiting.remove();
  468. }
  469. if ( output.text() !== '' ) {
  470. el.append( output );
  471. }
  472. } );
  473. }
  474. /**
  475. * Initialisation of a new table. Attach event handlers and callbacks to allow
  476. * Select to operate correctly.
  477. *
  478. * This will occur _after_ the initial DataTables initialisation, although
  479. * before Ajax data is rendered, if there is ajax data
  480. *
  481. * @param {DataTable.settings} ctx Settings object to operate on
  482. * @private
  483. */
  484. function init ( ctx ) {
  485. var api = new DataTable.Api( ctx );
  486. ctx._select_init = true;
  487. // Row callback so that classes can be added to rows and cells if the item
  488. // was selected before the element was created. This will happen with the
  489. // `deferRender` option enabled.
  490. //
  491. // This method of attaching to `aoRowCreatedCallback` is a hack until
  492. // DataTables has proper events for row manipulation If you are reviewing
  493. // this code to create your own plug-ins, please do not do this!
  494. ctx.aoRowCreatedCallback.push( {
  495. fn: function ( row, data, index ) {
  496. var i, ien;
  497. var d = ctx.aoData[ index ];
  498. // Row
  499. if ( d._select_selected ) {
  500. $( row ).addClass( ctx._select.className );
  501. }
  502. // Cells and columns - if separated out, we would need to do two
  503. // loops, so it makes sense to combine them into a single one
  504. for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) {
  505. if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) {
  506. $(d.anCells[i]).addClass( ctx._select.className );
  507. }
  508. }
  509. },
  510. sName: 'select-deferRender'
  511. } );
  512. // On Ajax reload we want to reselect all rows which are currently selected,
  513. // if there is an rowId (i.e. a unique value to identify each row with)
  514. api.on( 'preXhr.dt.dtSelect', function (e, settings) {
  515. if (settings !== api.settings()[0]) {
  516. // Not triggered by our DataTable!
  517. return;
  518. }
  519. // note that column selection doesn't need to be cached and then
  520. // reselected, as they are already selected
  521. var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) {
  522. return d !== undefined;
  523. } );
  524. var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) {
  525. var id = api.row( cellIdx.row ).id( true );
  526. return id ?
  527. { row: id, column: cellIdx.column } :
  528. undefined;
  529. } ).filter( function ( d ) {
  530. return d !== undefined;
  531. } );
  532. // On the next draw, reselect the currently selected items
  533. api.one( 'draw.dt.dtSelect', function () {
  534. api.rows( rows ).select();
  535. // `cells` is not a cell index selector, so it needs a loop
  536. if ( cells.any() ) {
  537. cells.each( function ( id ) {
  538. api.cells( id.row, id.column ).select();
  539. } );
  540. }
  541. } );
  542. } );
  543. // Update the table information element with selected item summary
  544. api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () {
  545. info( api );
  546. api.state.save();
  547. } );
  548. // Clean up and release
  549. api.on( 'destroy.dtSelect', function () {
  550. api.rows({selected: true}).deselect();
  551. disableMouseSelection( api );
  552. api.off( '.dtSelect' );
  553. $('body').off('.dtSelect' + _safeId(api.table().node()));
  554. } );
  555. }
  556. /**
  557. * Add one or more items (rows or columns) to the selection when shift clicking
  558. * in OS selection style
  559. *
  560. * @param {DataTable.Api} dt DataTable
  561. * @param {string} type Row or column range selector
  562. * @param {object} idx Item index to select to
  563. * @param {object} last Item index to select from
  564. * @private
  565. */
  566. function rowColumnRange( dt, type, idx, last )
  567. {
  568. // Add a range of rows from the last selected row to this one
  569. var indexes = dt[type+'s']( { search: 'applied' } ).indexes();
  570. var idx1 = $.inArray( last, indexes );
  571. var idx2 = $.inArray( idx, indexes );
  572. if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) {
  573. // select from top to here - slightly odd, but both Windows and Mac OS
  574. // do this
  575. indexes.splice( $.inArray( idx, indexes )+1, indexes.length );
  576. }
  577. else {
  578. // reverse so we can shift click 'up' as well as down
  579. if ( idx1 > idx2 ) {
  580. var tmp = idx2;
  581. idx2 = idx1;
  582. idx1 = tmp;
  583. }
  584. indexes.splice( idx2+1, indexes.length );
  585. indexes.splice( 0, idx1 );
  586. }
  587. if ( ! dt[type]( idx, { selected: true } ).any() ) {
  588. // Select range
  589. dt[type+'s']( indexes ).select();
  590. }
  591. else {
  592. // Deselect range - need to keep the clicked on row selected
  593. indexes.splice( $.inArray( idx, indexes ), 1 );
  594. dt[type+'s']( indexes ).deselect();
  595. }
  596. }
  597. /**
  598. * Clear all selected items
  599. *
  600. * @param {DataTable.settings} ctx Settings object of the host DataTable
  601. * @param {boolean} [force=false] Force the de-selection to happen, regardless
  602. * of selection style
  603. * @private
  604. */
  605. function clear( ctx, force )
  606. {
  607. if ( force || ctx._select.style === 'single' ) {
  608. var api = new DataTable.Api( ctx );
  609. api.rows( { selected: true } ).deselect();
  610. api.columns( { selected: true } ).deselect();
  611. api.cells( { selected: true } ).deselect();
  612. }
  613. }
  614. /**
  615. * Select items based on the current configuration for style and items.
  616. *
  617. * @param {object} e Mouse event object
  618. * @param {DataTables.Api} dt DataTable
  619. * @param {DataTable.settings} ctx Settings object of the host DataTable
  620. * @param {string} type Items to select
  621. * @param {int|object} idx Index of the item to select
  622. * @private
  623. */
  624. function typeSelect ( e, dt, ctx, type, idx )
  625. {
  626. var style = dt.select.style();
  627. var toggleable = dt.select.toggleable();
  628. var isSelected = dt[type]( idx, { selected: true } ).any();
  629. if ( isSelected && ! toggleable ) {
  630. return;
  631. }
  632. if ( style === 'os' ) {
  633. if ( e.ctrlKey || e.metaKey ) {
  634. // Add or remove from the selection
  635. dt[type]( idx ).select( ! isSelected );
  636. }
  637. else if ( e.shiftKey ) {
  638. if ( type === 'cell' ) {
  639. cellRange( dt, idx, ctx._select_lastCell || null );
  640. }
  641. else {
  642. rowColumnRange( dt, type, idx, ctx._select_lastCell ?
  643. ctx._select_lastCell[type] :
  644. null
  645. );
  646. }
  647. }
  648. else {
  649. // No cmd or shift click - deselect if selected, or select
  650. // this row only
  651. var selected = dt[type+'s']( { selected: true } );
  652. if ( isSelected && selected.flatten().length === 1 ) {
  653. dt[type]( idx ).deselect();
  654. }
  655. else {
  656. selected.deselect();
  657. dt[type]( idx ).select();
  658. }
  659. }
  660. } else if ( style == 'multi+shift' ) {
  661. if ( e.shiftKey ) {
  662. if ( type === 'cell' ) {
  663. cellRange( dt, idx, ctx._select_lastCell || null );
  664. }
  665. else {
  666. rowColumnRange( dt, type, idx, ctx._select_lastCell ?
  667. ctx._select_lastCell[type] :
  668. null
  669. );
  670. }
  671. }
  672. else {
  673. dt[ type ]( idx ).select( ! isSelected );
  674. }
  675. }
  676. else {
  677. dt[ type ]( idx ).select( ! isSelected );
  678. }
  679. }
  680. function _safeId( node ) {
  681. return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-');
  682. }
  683. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  684. * DataTables selectors
  685. */
  686. // row and column are basically identical just assigned to different properties
  687. // and checking a different array, so we can dynamically create the functions to
  688. // reduce the code size
  689. $.each( [
  690. { type: 'row', prop: 'aoData' },
  691. { type: 'column', prop: 'aoColumns' }
  692. ], function ( i, o ) {
  693. DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) {
  694. var selected = opts.selected;
  695. var data;
  696. var out = [];
  697. if ( selected !== true && selected !== false ) {
  698. return indexes;
  699. }
  700. for ( var i=0, ien=indexes.length ; i<ien ; i++ ) {
  701. data = settings[ o.prop ][ indexes[i] ];
  702. if ( (selected === true && data._select_selected === true) ||
  703. (selected === false && ! data._select_selected )
  704. ) {
  705. out.push( indexes[i] );
  706. }
  707. }
  708. return out;
  709. } );
  710. } );
  711. DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
  712. var selected = opts.selected;
  713. var rowData;
  714. var out = [];
  715. if ( selected === undefined ) {
  716. return cells;
  717. }
  718. for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
  719. rowData = settings.aoData[ cells[i].row ];
  720. if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) ||
  721. (selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) )
  722. ) {
  723. out.push( cells[i] );
  724. }
  725. }
  726. return out;
  727. } );
  728. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  729. * DataTables API
  730. *
  731. * For complete documentation, please refer to the docs/api directory or the
  732. * DataTables site
  733. */
  734. // Local variables to improve compression
  735. var apiRegister = DataTable.Api.register;
  736. var apiRegisterPlural = DataTable.Api.registerPlural;
  737. apiRegister( 'select()', function () {
  738. return this.iterator( 'table', function ( ctx ) {
  739. DataTable.select.init( new DataTable.Api( ctx ) );
  740. } );
  741. } );
  742. apiRegister( 'select.blurable()', function ( flag ) {
  743. if ( flag === undefined ) {
  744. return this.context[0]._select.blurable;
  745. }
  746. return this.iterator( 'table', function ( ctx ) {
  747. ctx._select.blurable = flag;
  748. } );
  749. } );
  750. apiRegister( 'select.toggleable()', function ( flag ) {
  751. if ( flag === undefined ) {
  752. return this.context[0]._select.toggleable;
  753. }
  754. return this.iterator( 'table', function ( ctx ) {
  755. ctx._select.toggleable = flag;
  756. } );
  757. } );
  758. apiRegister( 'select.info()', function ( flag ) {
  759. if ( flag === undefined ) {
  760. return this.context[0]._select.info;
  761. }
  762. return this.iterator( 'table', function ( ctx ) {
  763. ctx._select.info = flag;
  764. } );
  765. } );
  766. apiRegister( 'select.items()', function ( items ) {
  767. if ( items === undefined ) {
  768. return this.context[0]._select.items;
  769. }
  770. return this.iterator( 'table', function ( ctx ) {
  771. ctx._select.items = items;
  772. eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] );
  773. } );
  774. } );
  775. // Takes effect from the _next_ selection. None disables future selection, but
  776. // does not clear the current selection. Use the `deselect` methods for that
  777. apiRegister( 'select.style()', function ( style ) {
  778. if ( style === undefined ) {
  779. return this.context[0]._select.style;
  780. }
  781. return this.iterator( 'table', function ( ctx ) {
  782. if ( ! ctx._select ) {
  783. DataTable.select.init( new DataTable.Api(ctx) );
  784. }
  785. if ( ! ctx._select_init ) {
  786. init(ctx);
  787. }
  788. ctx._select.style = style;
  789. // Add / remove mouse event handlers. They aren't required when only
  790. // API selection is available
  791. var dt = new DataTable.Api( ctx );
  792. disableMouseSelection( dt );
  793. if ( style !== 'api' ) {
  794. enableMouseSelection( dt );
  795. }
  796. eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] );
  797. } );
  798. } );
  799. apiRegister( 'select.selector()', function ( selector ) {
  800. if ( selector === undefined ) {
  801. return this.context[0]._select.selector;
  802. }
  803. return this.iterator( 'table', function ( ctx ) {
  804. disableMouseSelection( new DataTable.Api( ctx ) );
  805. ctx._select.selector = selector;
  806. if ( ctx._select.style !== 'api' ) {
  807. enableMouseSelection( new DataTable.Api( ctx ) );
  808. }
  809. } );
  810. } );
  811. apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) {
  812. var api = this;
  813. if ( select === false ) {
  814. return this.deselect();
  815. }
  816. this.iterator( 'row', function ( ctx, idx ) {
  817. clear( ctx );
  818. ctx.aoData[ idx ]._select_selected = true;
  819. $( ctx.aoData[ idx ].nTr ).addClass( ctx._select.className );
  820. } );
  821. this.iterator( 'table', function ( ctx, i ) {
  822. eventTrigger( api, 'select', [ 'row', api[i] ], true );
  823. } );
  824. return this;
  825. } );
  826. apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) {
  827. var api = this;
  828. if ( select === false ) {
  829. return this.deselect();
  830. }
  831. this.iterator( 'column', function ( ctx, idx ) {
  832. clear( ctx );
  833. ctx.aoColumns[ idx ]._select_selected = true;
  834. var column = new DataTable.Api( ctx ).column( idx );
  835. $( column.header() ).addClass( ctx._select.className );
  836. $( column.footer() ).addClass( ctx._select.className );
  837. column.nodes().to$().addClass( ctx._select.className );
  838. } );
  839. this.iterator( 'table', function ( ctx, i ) {
  840. eventTrigger( api, 'select', [ 'column', api[i] ], true );
  841. } );
  842. return this;
  843. } );
  844. apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) {
  845. var api = this;
  846. if ( select === false ) {
  847. return this.deselect();
  848. }
  849. this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
  850. clear( ctx );
  851. var data = ctx.aoData[ rowIdx ];
  852. if ( data._selected_cells === undefined ) {
  853. data._selected_cells = [];
  854. }
  855. data._selected_cells[ colIdx ] = true;
  856. if ( data.anCells ) {
  857. $( data.anCells[ colIdx ] ).addClass( ctx._select.className );
  858. }
  859. } );
  860. this.iterator( 'table', function ( ctx, i ) {
  861. eventTrigger( api, 'select', [ 'cell', api.cells(api[i]).indexes().toArray() ], true );
  862. } );
  863. return this;
  864. } );
  865. apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () {
  866. var api = this;
  867. this.iterator( 'row', function ( ctx, idx ) {
  868. ctx.aoData[ idx ]._select_selected = false;
  869. ctx._select_lastCell = null;
  870. $( ctx.aoData[ idx ].nTr ).removeClass( ctx._select.className );
  871. } );
  872. this.iterator( 'table', function ( ctx, i ) {
  873. eventTrigger( api, 'deselect', [ 'row', api[i] ], true );
  874. } );
  875. return this;
  876. } );
  877. apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () {
  878. var api = this;
  879. this.iterator( 'column', function ( ctx, idx ) {
  880. ctx.aoColumns[ idx ]._select_selected = false;
  881. var api = new DataTable.Api( ctx );
  882. var column = api.column( idx );
  883. $( column.header() ).removeClass( ctx._select.className );
  884. $( column.footer() ).removeClass( ctx._select.className );
  885. // Need to loop over each cell, rather than just using
  886. // `column().nodes()` as cells which are individually selected should
  887. // not have the `selected` class removed from them
  888. api.cells( null, idx ).indexes().each( function (cellIdx) {
  889. var data = ctx.aoData[ cellIdx.row ];
  890. var cellSelected = data._selected_cells;
  891. if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) {
  892. $( data.anCells[ cellIdx.column ] ).removeClass( ctx._select.className );
  893. }
  894. } );
  895. } );
  896. this.iterator( 'table', function ( ctx, i ) {
  897. eventTrigger( api, 'deselect', [ 'column', api[i] ], true );
  898. } );
  899. return this;
  900. } );
  901. apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () {
  902. var api = this;
  903. this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
  904. var data = ctx.aoData[ rowIdx ];
  905. if(data._selected_cells !== undefined) {
  906. data._selected_cells[ colIdx ] = false;
  907. }
  908. // Remove class only if the cells exist, and the cell is not column
  909. // selected, in which case the class should remain (since it is selected
  910. // in the column)
  911. if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) {
  912. $( data.anCells[ colIdx ] ).removeClass( ctx._select.className );
  913. }
  914. } );
  915. this.iterator( 'table', function ( ctx, i ) {
  916. eventTrigger( api, 'deselect', [ 'cell', api[i] ], true );
  917. } );
  918. return this;
  919. } );
  920. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  921. * Buttons
  922. */
  923. function i18n( label, def ) {
  924. return function (dt) {
  925. return dt.i18n( 'buttons.'+label, def );
  926. };
  927. }
  928. // Common events with suitable namespaces
  929. function namespacedEvents ( config ) {
  930. var unique = config._eventNamespace;
  931. return 'draw.dt.DT'+unique+' select.dt.DT'+unique+' deselect.dt.DT'+unique;
  932. }
  933. function enabled ( dt, config ) {
  934. if ( $.inArray( 'rows', config.limitTo ) !== -1 && dt.rows( { selected: true } ).any() ) {
  935. return true;
  936. }
  937. if ( $.inArray( 'columns', config.limitTo ) !== -1 && dt.columns( { selected: true } ).any() ) {
  938. return true;
  939. }
  940. if ( $.inArray( 'cells', config.limitTo ) !== -1 && dt.cells( { selected: true } ).any() ) {
  941. return true;
  942. }
  943. return false;
  944. }
  945. var _buttonNamespace = 0;
  946. $.extend( DataTable.ext.buttons, {
  947. selected: {
  948. text: i18n( 'selected', 'Selected' ),
  949. className: 'buttons-selected',
  950. limitTo: [ 'rows', 'columns', 'cells' ],
  951. init: function ( dt, node, config ) {
  952. var that = this;
  953. config._eventNamespace = '.select'+(_buttonNamespace++);
  954. // .DT namespace listeners are removed by DataTables automatically
  955. // on table destroy
  956. dt.on( namespacedEvents(config), function () {
  957. that.enable( enabled(dt, config) );
  958. } );
  959. this.disable();
  960. },
  961. destroy: function ( dt, node, config ) {
  962. dt.off( config._eventNamespace );
  963. }
  964. },
  965. selectedSingle: {
  966. text: i18n( 'selectedSingle', 'Selected single' ),
  967. className: 'buttons-selected-single',
  968. init: function ( dt, node, config ) {
  969. var that = this;
  970. config._eventNamespace = '.select'+(_buttonNamespace++);
  971. dt.on( namespacedEvents(config), function () {
  972. var count = dt.rows( { selected: true } ).flatten().length +
  973. dt.columns( { selected: true } ).flatten().length +
  974. dt.cells( { selected: true } ).flatten().length;
  975. that.enable( count === 1 );
  976. } );
  977. this.disable();
  978. },
  979. destroy: function ( dt, node, config ) {
  980. dt.off( config._eventNamespace );
  981. }
  982. },
  983. selectAll: {
  984. text: i18n( 'selectAll', 'Select all' ),
  985. className: 'buttons-select-all',
  986. action: function () {
  987. var items = this.select.items();
  988. this[ items+'s' ]().select();
  989. }
  990. },
  991. selectNone: {
  992. text: i18n( 'selectNone', 'Deselect all' ),
  993. className: 'buttons-select-none',
  994. action: function () {
  995. clear( this.settings()[0], true );
  996. },
  997. init: function ( dt, node, config ) {
  998. var that = this;
  999. config._eventNamespace = '.select'+(_buttonNamespace++);
  1000. dt.on( namespacedEvents(config), function () {
  1001. var count = dt.rows( { selected: true } ).flatten().length +
  1002. dt.columns( { selected: true } ).flatten().length +
  1003. dt.cells( { selected: true } ).flatten().length;
  1004. that.enable( count > 0 );
  1005. } );
  1006. this.disable();
  1007. },
  1008. destroy: function ( dt, node, config ) {
  1009. dt.off( config._eventNamespace );
  1010. }
  1011. }
  1012. } );
  1013. $.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) {
  1014. var lc = item.toLowerCase();
  1015. DataTable.ext.buttons[ 'select'+item+'s' ] = {
  1016. text: i18n( 'select'+item+'s', 'Select '+lc+'s' ),
  1017. className: 'buttons-select-'+lc+'s',
  1018. action: function () {
  1019. this.select.items( lc );
  1020. },
  1021. init: function ( dt ) {
  1022. var that = this;
  1023. dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {
  1024. that.active( items === lc );
  1025. } );
  1026. }
  1027. };
  1028. } );
  1029. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1030. * Initialisation
  1031. */
  1032. // DataTables creation - check if select has been defined in the options. Note
  1033. // this required that the table be in the document! If it isn't then something
  1034. // needs to trigger this method unfortunately. The next major release of
  1035. // DataTables will rework the events and address this.
  1036. $(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
  1037. if ( e.namespace !== 'dt' ) {
  1038. return;
  1039. }
  1040. DataTable.select.init( new DataTable.Api( ctx ) );
  1041. } );
  1042. return DataTable.select;
  1043. }));