dataTables.buttons.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478
  1. /*! Buttons for DataTables 2.2.2
  2. * ©2016-2022 SpryMedia Ltd - datatables.net/license
  3. */
  4. (function( factory ){
  5. if ( typeof define === 'function' && define.amd ) {
  6. // AMD
  7. define( ['jquery', 'datatables.net'], function ( $ ) {
  8. return factory( $, window, document );
  9. } );
  10. }
  11. else if ( typeof exports === 'object' ) {
  12. // CommonJS
  13. module.exports = function (root, $) {
  14. if ( ! root ) {
  15. root = window;
  16. }
  17. if ( ! $ || ! $.fn.dataTable ) {
  18. $ = require('datatables.net')(root, $).$;
  19. }
  20. return factory( $, root, root.document );
  21. };
  22. }
  23. else {
  24. // Browser
  25. factory( jQuery, window, document );
  26. }
  27. }(function( $, window, document, undefined ) {
  28. 'use strict';
  29. var DataTable = $.fn.dataTable;
  30. // Used for namespacing events added to the document by each instance, so they
  31. // can be removed on destroy
  32. var _instCounter = 0;
  33. // Button namespacing counter for namespacing events on individual buttons
  34. var _buttonCounter = 0;
  35. var _dtButtons = DataTable.ext.buttons;
  36. // Allow for jQuery slim
  37. function _fadeIn(el, duration, fn) {
  38. if ($.fn.animate) {
  39. el
  40. .stop()
  41. .fadeIn( duration, fn );
  42. }
  43. else {
  44. el.css('display', 'block');
  45. if (fn) {
  46. fn.call(el);
  47. }
  48. }
  49. }
  50. function _fadeOut(el, duration, fn) {
  51. if ($.fn.animate) {
  52. el
  53. .stop()
  54. .fadeOut( duration, fn );
  55. }
  56. else {
  57. el.css('display', 'none');
  58. if (fn) {
  59. fn.call(el);
  60. }
  61. }
  62. }
  63. /**
  64. * [Buttons description]
  65. * @param {[type]}
  66. * @param {[type]}
  67. */
  68. var Buttons = function( dt, config )
  69. {
  70. // If not created with a `new` keyword then we return a wrapper function that
  71. // will take the settings object for a DT. This allows easy use of new instances
  72. // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
  73. if ( !(this instanceof Buttons) ) {
  74. return function (settings) {
  75. return new Buttons( settings, dt ).container();
  76. };
  77. }
  78. // If there is no config set it to an empty object
  79. if ( typeof( config ) === 'undefined' ) {
  80. config = {};
  81. }
  82. // Allow a boolean true for defaults
  83. if ( config === true ) {
  84. config = {};
  85. }
  86. // For easy configuration of buttons an array can be given
  87. if ( Array.isArray( config ) ) {
  88. config = { buttons: config };
  89. }
  90. this.c = $.extend( true, {}, Buttons.defaults, config );
  91. // Don't want a deep copy for the buttons
  92. if ( config.buttons ) {
  93. this.c.buttons = config.buttons;
  94. }
  95. this.s = {
  96. dt: new DataTable.Api( dt ),
  97. buttons: [],
  98. listenKeys: '',
  99. namespace: 'dtb'+(_instCounter++)
  100. };
  101. this.dom = {
  102. container: $('<'+this.c.dom.container.tag+'/>')
  103. .addClass( this.c.dom.container.className )
  104. };
  105. this._constructor();
  106. };
  107. $.extend( Buttons.prototype, {
  108. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  109. * Public methods
  110. */
  111. /**
  112. * Get the action of a button
  113. * @param {int|string} Button index
  114. * @return {function}
  115. *//**
  116. * Set the action of a button
  117. * @param {node} node Button element
  118. * @param {function} action Function to set
  119. * @return {Buttons} Self for chaining
  120. */
  121. action: function ( node, action )
  122. {
  123. var button = this._nodeToButton( node );
  124. if ( action === undefined ) {
  125. return button.conf.action;
  126. }
  127. button.conf.action = action;
  128. return this;
  129. },
  130. /**
  131. * Add an active class to the button to make to look active or get current
  132. * active state.
  133. * @param {node} node Button element
  134. * @param {boolean} [flag] Enable / disable flag
  135. * @return {Buttons} Self for chaining or boolean for getter
  136. */
  137. active: function ( node, flag ) {
  138. var button = this._nodeToButton( node );
  139. var klass = this.c.dom.button.active;
  140. var jqNode = $(button.node);
  141. if ( flag === undefined ) {
  142. return jqNode.hasClass( klass );
  143. }
  144. jqNode.toggleClass( klass, flag === undefined ? true : flag );
  145. return this;
  146. },
  147. /**
  148. * Add a new button
  149. * @param {object} config Button configuration object, base string name or function
  150. * @param {int|string} [idx] Button index for where to insert the button
  151. * @param {boolean} [draw=true] Trigger a draw. Set a false when adding
  152. * lots of buttons, until the last button.
  153. * @return {Buttons} Self for chaining
  154. */
  155. add: function ( config, idx, draw )
  156. {
  157. var buttons = this.s.buttons;
  158. if ( typeof idx === 'string' ) {
  159. var split = idx.split('-');
  160. var base = this.s;
  161. for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) {
  162. base = base.buttons[ split[i]*1 ];
  163. }
  164. buttons = base.buttons;
  165. idx = split[ split.length-1 ]*1;
  166. }
  167. this._expandButton(
  168. buttons,
  169. config,
  170. config !== undefined ? config.split : undefined,
  171. (config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined,
  172. false,
  173. idx
  174. );
  175. if (draw === undefined || draw === true) {
  176. this._draw();
  177. }
  178. return this;
  179. },
  180. /**
  181. * Clear buttons from a collection and then insert new buttons
  182. */
  183. collectionRebuild: function ( node, newButtons )
  184. {
  185. var button = this._nodeToButton( node );
  186. if(newButtons !== undefined) {
  187. var i;
  188. // Need to reverse the array
  189. for (i=button.buttons.length-1; i>=0; i--) {
  190. this.remove(button.buttons[i].node);
  191. }
  192. for (i=0; i<newButtons.length; i++) {
  193. var newBtn = newButtons[i];
  194. this._expandButton(
  195. button.buttons,
  196. newBtn,
  197. newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined,
  198. true,
  199. newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined,
  200. i,
  201. newBtn.parentConf
  202. );
  203. }
  204. }
  205. this._draw(button.collection, button.buttons);
  206. },
  207. /**
  208. * Get the container node for the buttons
  209. * @return {jQuery} Buttons node
  210. */
  211. container: function ()
  212. {
  213. return this.dom.container;
  214. },
  215. /**
  216. * Disable a button
  217. * @param {node} node Button node
  218. * @return {Buttons} Self for chaining
  219. */
  220. disable: function ( node ) {
  221. var button = this._nodeToButton( node );
  222. $(button.node)
  223. .addClass( this.c.dom.button.disabled )
  224. .attr('disabled', true);
  225. return this;
  226. },
  227. /**
  228. * Destroy the instance, cleaning up event handlers and removing DOM
  229. * elements
  230. * @return {Buttons} Self for chaining
  231. */
  232. destroy: function ()
  233. {
  234. // Key event listener
  235. $('body').off( 'keyup.'+this.s.namespace );
  236. // Individual button destroy (so they can remove their own events if
  237. // needed). Take a copy as the array is modified by `remove`
  238. var buttons = this.s.buttons.slice();
  239. var i, ien;
  240. for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
  241. this.remove( buttons[i].node );
  242. }
  243. // Container
  244. this.dom.container.remove();
  245. // Remove from the settings object collection
  246. var buttonInsts = this.s.dt.settings()[0];
  247. for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) {
  248. if ( buttonInsts.inst === this ) {
  249. buttonInsts.splice( i, 1 );
  250. break;
  251. }
  252. }
  253. return this;
  254. },
  255. /**
  256. * Enable / disable a button
  257. * @param {node} node Button node
  258. * @param {boolean} [flag=true] Enable / disable flag
  259. * @return {Buttons} Self for chaining
  260. */
  261. enable: function ( node, flag )
  262. {
  263. if ( flag === false ) {
  264. return this.disable( node );
  265. }
  266. var button = this._nodeToButton( node );
  267. $(button.node)
  268. .removeClass( this.c.dom.button.disabled )
  269. .removeAttr('disabled');
  270. return this;
  271. },
  272. /**
  273. * Get a button's index
  274. *
  275. * This is internally recursive
  276. * @param {element} node Button to get the index of
  277. * @return {string} Button index
  278. */
  279. index: function ( node, nested, buttons )
  280. {
  281. if ( ! nested ) {
  282. nested = '';
  283. buttons = this.s.buttons;
  284. }
  285. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  286. var inner = buttons[i].buttons;
  287. if (buttons[i].node === node) {
  288. return nested + i;
  289. }
  290. if ( inner && inner.length ) {
  291. var match = this.index(node, i + '-', inner);
  292. if (match !== null) {
  293. return match;
  294. }
  295. }
  296. }
  297. return null;
  298. },
  299. /**
  300. * Get the instance name for the button set selector
  301. * @return {string} Instance name
  302. */
  303. name: function ()
  304. {
  305. return this.c.name;
  306. },
  307. /**
  308. * Get a button's node of the buttons container if no button is given
  309. * @param {node} [node] Button node
  310. * @return {jQuery} Button element, or container
  311. */
  312. node: function ( node )
  313. {
  314. if ( ! node ) {
  315. return this.dom.container;
  316. }
  317. var button = this._nodeToButton( node );
  318. return $(button.node);
  319. },
  320. /**
  321. * Set / get a processing class on the selected button
  322. * @param {element} node Triggering button node
  323. * @param {boolean} flag true to add, false to remove, undefined to get
  324. * @return {boolean|Buttons} Getter value or this if a setter.
  325. */
  326. processing: function ( node, flag )
  327. {
  328. var dt = this.s.dt;
  329. var button = this._nodeToButton( node );
  330. if ( flag === undefined ) {
  331. return $(button.node).hasClass( 'processing' );
  332. }
  333. $(button.node).toggleClass( 'processing', flag );
  334. $(dt.table().node()).triggerHandler( 'buttons-processing.dt', [
  335. flag, dt.button( node ), dt, $(node), button.conf
  336. ] );
  337. return this;
  338. },
  339. /**
  340. * Remove a button.
  341. * @param {node} node Button node
  342. * @return {Buttons} Self for chaining
  343. */
  344. remove: function ( node )
  345. {
  346. var button = this._nodeToButton( node );
  347. var host = this._nodeToHost( node );
  348. var dt = this.s.dt;
  349. // Remove any child buttons first
  350. if ( button.buttons.length ) {
  351. for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) {
  352. this.remove( button.buttons[i].node );
  353. }
  354. }
  355. button.conf.destroying = true;
  356. // Allow the button to remove event handlers, etc
  357. if ( button.conf.destroy ) {
  358. button.conf.destroy.call( dt.button(node), dt, $(node), button.conf );
  359. }
  360. this._removeKey( button.conf );
  361. $(button.node).remove();
  362. var idx = $.inArray( button, host );
  363. host.splice( idx, 1 );
  364. return this;
  365. },
  366. /**
  367. * Get the text for a button
  368. * @param {int|string} node Button index
  369. * @return {string} Button text
  370. *//**
  371. * Set the text for a button
  372. * @param {int|string|function} node Button index
  373. * @param {string} label Text
  374. * @return {Buttons} Self for chaining
  375. */
  376. text: function ( node, label )
  377. {
  378. var button = this._nodeToButton( node );
  379. var buttonLiner = this.c.dom.collection.buttonLiner;
  380. var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
  381. buttonLiner.tag :
  382. this.c.dom.buttonLiner.tag;
  383. var dt = this.s.dt;
  384. var jqNode = $(button.node);
  385. var text = function ( opt ) {
  386. return typeof opt === 'function' ?
  387. opt( dt, jqNode, button.conf ) :
  388. opt;
  389. };
  390. if ( label === undefined ) {
  391. return text( button.conf.text );
  392. }
  393. button.conf.text = label;
  394. if ( linerTag ) {
  395. jqNode
  396. .children( linerTag )
  397. .eq(0)
  398. .filter(':not(.dt-down-arrow)')
  399. .html( text(label) );
  400. }
  401. else {
  402. jqNode.html( text(label) );
  403. }
  404. return this;
  405. },
  406. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  407. * Constructor
  408. */
  409. /**
  410. * Buttons constructor
  411. * @private
  412. */
  413. _constructor: function ()
  414. {
  415. var that = this;
  416. var dt = this.s.dt;
  417. var dtSettings = dt.settings()[0];
  418. var buttons = this.c.buttons;
  419. if ( ! dtSettings._buttons ) {
  420. dtSettings._buttons = [];
  421. }
  422. dtSettings._buttons.push( {
  423. inst: this,
  424. name: this.c.name
  425. } );
  426. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  427. this.add( buttons[i] );
  428. }
  429. dt.on( 'destroy', function ( e, settings ) {
  430. if ( settings === dtSettings ) {
  431. that.destroy();
  432. }
  433. } );
  434. // Global key event binding to listen for button keys
  435. $('body').on( 'keyup.'+this.s.namespace, function ( e ) {
  436. if ( ! document.activeElement || document.activeElement === document.body ) {
  437. // SUse a string of characters for fast lookup of if we need to
  438. // handle this
  439. var character = String.fromCharCode(e.keyCode).toLowerCase();
  440. if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) {
  441. that._keypress( character, e );
  442. }
  443. }
  444. } );
  445. },
  446. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  447. * Private methods
  448. */
  449. /**
  450. * Add a new button to the key press listener
  451. * @param {object} conf Resolved button configuration object
  452. * @private
  453. */
  454. _addKey: function ( conf )
  455. {
  456. if ( conf.key ) {
  457. this.s.listenKeys += $.isPlainObject( conf.key ) ?
  458. conf.key.key :
  459. conf.key;
  460. }
  461. },
  462. /**
  463. * Insert the buttons into the container. Call without parameters!
  464. * @param {node} [container] Recursive only - Insert point
  465. * @param {array} [buttons] Recursive only - Buttons array
  466. * @private
  467. */
  468. _draw: function ( container, buttons )
  469. {
  470. if ( ! container ) {
  471. container = this.dom.container;
  472. buttons = this.s.buttons;
  473. }
  474. container.children().detach();
  475. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  476. container.append( buttons[i].inserter );
  477. container.append( ' ' );
  478. if ( buttons[i].buttons && buttons[i].buttons.length ) {
  479. this._draw( buttons[i].collection, buttons[i].buttons );
  480. }
  481. }
  482. },
  483. /**
  484. * Create buttons from an array of buttons
  485. * @param {array} attachTo Buttons array to attach to
  486. * @param {object} button Button definition
  487. * @param {boolean} inCollection true if the button is in a collection
  488. * @private
  489. */
  490. _expandButton: function ( attachTo, button, split, inCollection, inSplit, attachPoint, parentConf )
  491. {
  492. var dt = this.s.dt;
  493. var buttonCounter = 0;
  494. var isSplit = false;
  495. var buttons = ! Array.isArray( button ) ?
  496. [ button ] :
  497. button;
  498. if(button === undefined ) {
  499. buttons = !Array.isArray(split) ?
  500. [ split ] :
  501. split;
  502. }
  503. if (button !== undefined && button.split !== undefined) {
  504. isSplit = true;
  505. }
  506. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  507. var conf = this._resolveExtends( buttons[i] );
  508. if ( ! conf ) {
  509. continue;
  510. }
  511. if( conf.config !== undefined && conf.config.split) {
  512. isSplit = true;
  513. }
  514. else {
  515. isSplit = false;
  516. }
  517. // If the configuration is an array, then expand the buttons at this
  518. // point
  519. if ( Array.isArray( conf ) ) {
  520. this._expandButton( attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf );
  521. continue;
  522. }
  523. var built = this._buildButton( conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit );
  524. if ( ! built ) {
  525. continue;
  526. }
  527. if ( attachPoint !== undefined && attachPoint !== null ) {
  528. attachTo.splice( attachPoint, 0, built );
  529. attachPoint++;
  530. }
  531. else {
  532. attachTo.push( built );
  533. }
  534. if ( built.conf.buttons || built.conf.split ) {
  535. built.collection = $('<'+(isSplit ? this.c.dom.splitCollection.tag : this.c.dom.collection.tag)+'/>');
  536. built.conf._collection = built.collection;
  537. if(built.conf.split) {
  538. for(var j = 0; j < built.conf.split.length; j++) {
  539. if(typeof built.conf.split[j] === "object") {
  540. built.conf.split[j].parent = parentConf;
  541. if(built.conf.split[j].collectionLayout === undefined) {
  542. built.conf.split[j].collectionLayout = built.conf.collectionLayout;
  543. }
  544. if(built.conf.split[j].dropup === undefined) {
  545. built.conf.split[j].dropup = built.conf.dropup;
  546. }
  547. if(built.conf.split[j].fade === undefined) {
  548. built.conf.split[j].fade = built.conf.fade;
  549. }
  550. }
  551. }
  552. }
  553. else {
  554. $(built.node).append($('<span class="dt-down-arrow">'+this.c.dom.splitDropdown.text+'</span>'))
  555. }
  556. this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf );
  557. }
  558. built.conf.parent = parentConf;
  559. // init call is made here, rather than buildButton as it needs to
  560. // be selectable, and for that it needs to be in the buttons array
  561. if ( conf.init ) {
  562. conf.init.call( dt.button( built.node ), dt, $(built.node), conf );
  563. }
  564. buttonCounter++;
  565. }
  566. },
  567. /**
  568. * Create an individual button
  569. * @param {object} config Resolved button configuration
  570. * @param {boolean} inCollection `true` if a collection button
  571. * @return {jQuery} Created button node (jQuery)
  572. * @private
  573. */
  574. _buildButton: function ( config, inCollection, isSplit, inSplit )
  575. {
  576. var buttonDom = this.c.dom.button;
  577. var linerDom = this.c.dom.buttonLiner;
  578. var collectionDom = this.c.dom.collection;
  579. var splitDom = this.c.dom.split;
  580. var splitCollectionDom = this.c.dom.splitCollection;
  581. var splitDropdownButton = this.c.dom.splitDropdownButton;
  582. var dt = this.s.dt;
  583. var text = function ( opt ) {
  584. return typeof opt === 'function' ?
  585. opt( dt, button, config ) :
  586. opt;
  587. };
  588. // Spacers don't do much other than insert an element into the DOM
  589. if (config.spacer) {
  590. var spacer = $('<span></span>')
  591. .addClass('dt-button-spacer ' + config.style + ' ' + buttonDom.spacerClass)
  592. .html(text(config.text));
  593. return {
  594. conf: config,
  595. node: spacer,
  596. inserter: spacer,
  597. buttons: [],
  598. inCollection: inCollection,
  599. isSplit: isSplit,
  600. inSplit: inSplit,
  601. collection: null
  602. };
  603. }
  604. if ( !isSplit && inSplit && splitCollectionDom ) {
  605. buttonDom = splitDropdownButton;
  606. }
  607. else if ( !isSplit && inCollection && collectionDom.button ) {
  608. buttonDom = collectionDom.button;
  609. }
  610. if ( !isSplit && inSplit && splitCollectionDom.buttonLiner ) {
  611. linerDom = splitCollectionDom.buttonLiner
  612. }
  613. else if ( !isSplit && inCollection && collectionDom.buttonLiner ) {
  614. linerDom = collectionDom.buttonLiner;
  615. }
  616. // Make sure that the button is available based on whatever requirements
  617. // it has. For example, PDF button require pdfmake
  618. if ( config.available && ! config.available( dt, config ) && !config.hasOwnProperty('html') ) {
  619. return false;
  620. }
  621. var button;
  622. if(!config.hasOwnProperty('html')) {
  623. var action = function ( e, dt, button, config ) {
  624. config.action.call( dt.button( button ), e, dt, button, config );
  625. $(dt.table().node()).triggerHandler( 'buttons-action.dt', [
  626. dt.button( button ), dt, button, config
  627. ] );
  628. };
  629. var tag = config.tag || buttonDom.tag;
  630. var clickBlurs = config.clickBlurs === undefined
  631. ? true :
  632. config.clickBlurs;
  633. button = $('<'+tag+'/>')
  634. .addClass( buttonDom.className )
  635. .addClass( inSplit ? this.c.dom.splitDropdownButton.className : '')
  636. .attr( 'tabindex', this.s.dt.settings()[0].iTabIndex )
  637. .attr( 'aria-controls', this.s.dt.table().node().id )
  638. .on( 'click.dtb', function (e) {
  639. e.preventDefault();
  640. if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
  641. action( e, dt, button, config );
  642. }
  643. if( clickBlurs ) {
  644. button.trigger('blur');
  645. }
  646. } )
  647. .on( 'keypress.dtb', function (e) {
  648. if ( e.keyCode === 13 ) {
  649. e.preventDefault();
  650. if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
  651. action( e, dt, button, config );
  652. }
  653. }
  654. } );
  655. // Make `a` tags act like a link
  656. if ( tag.toLowerCase() === 'a' ) {
  657. button.attr( 'href', '#' );
  658. }
  659. // Button tags should have `type=button` so they don't have any default behaviour
  660. if ( tag.toLowerCase() === 'button' ) {
  661. button.attr( 'type', 'button' );
  662. }
  663. if ( linerDom.tag ) {
  664. var liner = $('<'+linerDom.tag+'/>')
  665. .html( text( config.text ) )
  666. .addClass( linerDom.className );
  667. if ( linerDom.tag.toLowerCase() === 'a' ) {
  668. liner.attr( 'href', '#' );
  669. }
  670. button.append( liner );
  671. }
  672. else {
  673. button.html( text( config.text ) );
  674. }
  675. if ( config.enabled === false ) {
  676. button.addClass( buttonDom.disabled );
  677. }
  678. if ( config.className ) {
  679. button.addClass( config.className );
  680. }
  681. if ( config.titleAttr ) {
  682. button.attr( 'title', text( config.titleAttr ) );
  683. }
  684. if ( config.attr ) {
  685. button.attr( config.attr );
  686. }
  687. if ( ! config.namespace ) {
  688. config.namespace = '.dt-button-'+(_buttonCounter++);
  689. }
  690. if ( config.config !== undefined && config.config.split ) {
  691. config.split = config.config.split;
  692. }
  693. }
  694. else {
  695. button = $(config.html)
  696. }
  697. var buttonContainer = this.c.dom.buttonContainer;
  698. var inserter;
  699. if ( buttonContainer && buttonContainer.tag ) {
  700. inserter = $('<'+buttonContainer.tag+'/>')
  701. .addClass( buttonContainer.className )
  702. .append( button );
  703. }
  704. else {
  705. inserter = button;
  706. }
  707. this._addKey( config );
  708. // Style integration callback for DOM manipulation
  709. // Note that this is _not_ documented. It is currently
  710. // for style integration only
  711. if( this.c.buttonCreated ) {
  712. inserter = this.c.buttonCreated( config, inserter );
  713. }
  714. var splitDiv;
  715. if(isSplit) {
  716. splitDiv = $('<div/>').addClass(this.c.dom.splitWrapper.className)
  717. splitDiv.append(button);
  718. var dropButtonConfig = $.extend(config, {
  719. text: this.c.dom.splitDropdown.text,
  720. className: this.c.dom.splitDropdown.className,
  721. closeButton: false,
  722. attr: {
  723. 'aria-haspopup': true,
  724. 'aria-expanded': false
  725. },
  726. align: this.c.dom.splitDropdown.align,
  727. splitAlignClass: this.c.dom.splitDropdown.splitAlignClass
  728. })
  729. this._addKey(dropButtonConfig);
  730. var splitAction = function ( e, dt, button, config ) {
  731. _dtButtons.split.action.call( dt.button($('div.dt-btn-split-wrapper')[0] ), e, dt, button, config );
  732. $(dt.table().node()).triggerHandler( 'buttons-action.dt', [
  733. dt.button( button ), dt, button, config
  734. ] );
  735. button.attr('aria-expanded', true)
  736. };
  737. var dropButton = $('<button class="' + this.c.dom.splitDropdown.className + ' dt-button"><span class="dt-btn-split-drop-arrow">'+this.c.dom.splitDropdown.text+'</span></button>')
  738. .on( 'click.dtb', function (e) {
  739. e.preventDefault();
  740. e.stopPropagation();
  741. if ( ! dropButton.hasClass( buttonDom.disabled )) {
  742. splitAction( e, dt, dropButton, dropButtonConfig );
  743. }
  744. if ( clickBlurs ) {
  745. dropButton.trigger('blur');
  746. }
  747. } )
  748. .on( 'keypress.dtb', function (e) {
  749. if ( e.keyCode === 13 ) {
  750. e.preventDefault();
  751. if ( ! dropButton.hasClass( buttonDom.disabled ) ) {
  752. splitAction( e, dt, dropButton, dropButtonConfig );
  753. }
  754. }
  755. } );
  756. if(config.split.length === 0) {
  757. dropButton.addClass('dtb-hide-drop');
  758. }
  759. splitDiv.append(dropButton).attr(dropButtonConfig.attr);
  760. }
  761. return {
  762. conf: config,
  763. node: isSplit ? splitDiv.get(0) : button.get(0),
  764. inserter: isSplit ? splitDiv : inserter,
  765. buttons: [],
  766. inCollection: inCollection,
  767. isSplit: isSplit,
  768. inSplit: inSplit,
  769. collection: null
  770. };
  771. },
  772. /**
  773. * Get the button object from a node (recursive)
  774. * @param {node} node Button node
  775. * @param {array} [buttons] Button array, uses base if not defined
  776. * @return {object} Button object
  777. * @private
  778. */
  779. _nodeToButton: function ( node, buttons )
  780. {
  781. if ( ! buttons ) {
  782. buttons = this.s.buttons;
  783. }
  784. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  785. if ( buttons[i].node === node ) {
  786. return buttons[i];
  787. }
  788. if ( buttons[i].buttons.length ) {
  789. var ret = this._nodeToButton( node, buttons[i].buttons );
  790. if ( ret ) {
  791. return ret;
  792. }
  793. }
  794. }
  795. },
  796. /**
  797. * Get container array for a button from a button node (recursive)
  798. * @param {node} node Button node
  799. * @param {array} [buttons] Button array, uses base if not defined
  800. * @return {array} Button's host array
  801. * @private
  802. */
  803. _nodeToHost: function ( node, buttons )
  804. {
  805. if ( ! buttons ) {
  806. buttons = this.s.buttons;
  807. }
  808. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  809. if ( buttons[i].node === node ) {
  810. return buttons;
  811. }
  812. if ( buttons[i].buttons.length ) {
  813. var ret = this._nodeToHost( node, buttons[i].buttons );
  814. if ( ret ) {
  815. return ret;
  816. }
  817. }
  818. }
  819. },
  820. /**
  821. * Handle a key press - determine if any button's key configured matches
  822. * what was typed and trigger the action if so.
  823. * @param {string} character The character pressed
  824. * @param {object} e Key event that triggered this call
  825. * @private
  826. */
  827. _keypress: function ( character, e )
  828. {
  829. // Check if this button press already activated on another instance of Buttons
  830. if ( e._buttonsHandled ) {
  831. return;
  832. }
  833. var run = function ( conf, node ) {
  834. if ( ! conf.key ) {
  835. return;
  836. }
  837. if ( conf.key === character ) {
  838. e._buttonsHandled = true;
  839. $(node).click();
  840. }
  841. else if ( $.isPlainObject( conf.key ) ) {
  842. if ( conf.key.key !== character ) {
  843. return;
  844. }
  845. if ( conf.key.shiftKey && ! e.shiftKey ) {
  846. return;
  847. }
  848. if ( conf.key.altKey && ! e.altKey ) {
  849. return;
  850. }
  851. if ( conf.key.ctrlKey && ! e.ctrlKey ) {
  852. return;
  853. }
  854. if ( conf.key.metaKey && ! e.metaKey ) {
  855. return;
  856. }
  857. // Made it this far - it is good
  858. e._buttonsHandled = true;
  859. $(node).click();
  860. }
  861. };
  862. var recurse = function ( a ) {
  863. for ( var i=0, ien=a.length ; i<ien ; i++ ) {
  864. run( a[i].conf, a[i].node );
  865. if ( a[i].buttons.length ) {
  866. recurse( a[i].buttons );
  867. }
  868. }
  869. };
  870. recurse( this.s.buttons );
  871. },
  872. /**
  873. * Remove a key from the key listener for this instance (to be used when a
  874. * button is removed)
  875. * @param {object} conf Button configuration
  876. * @private
  877. */
  878. _removeKey: function ( conf )
  879. {
  880. if ( conf.key ) {
  881. var character = $.isPlainObject( conf.key ) ?
  882. conf.key.key :
  883. conf.key;
  884. // Remove only one character, as multiple buttons could have the
  885. // same listening key
  886. var a = this.s.listenKeys.split('');
  887. var idx = $.inArray( character, a );
  888. a.splice( idx, 1 );
  889. this.s.listenKeys = a.join('');
  890. }
  891. },
  892. /**
  893. * Resolve a button configuration
  894. * @param {string|function|object} conf Button config to resolve
  895. * @return {object} Button configuration
  896. * @private
  897. */
  898. _resolveExtends: function ( conf )
  899. {
  900. var that = this;
  901. var dt = this.s.dt;
  902. var i, ien;
  903. var toConfObject = function ( base ) {
  904. var loop = 0;
  905. // Loop until we have resolved to a button configuration, or an
  906. // array of button configurations (which will be iterated
  907. // separately)
  908. while ( ! $.isPlainObject(base) && ! Array.isArray(base) ) {
  909. if ( base === undefined ) {
  910. return;
  911. }
  912. if ( typeof base === 'function' ) {
  913. base = base.call( that, dt, conf );
  914. if ( ! base ) {
  915. return false;
  916. }
  917. }
  918. else if ( typeof base === 'string' ) {
  919. if ( ! _dtButtons[ base ] ) {
  920. return {html: base}
  921. }
  922. base = _dtButtons[ base ];
  923. }
  924. loop++;
  925. if ( loop > 30 ) {
  926. // Protect against misconfiguration killing the browser
  927. throw 'Buttons: Too many iterations';
  928. }
  929. }
  930. return Array.isArray( base ) ?
  931. base :
  932. $.extend( {}, base );
  933. };
  934. conf = toConfObject( conf );
  935. while ( conf && conf.extend ) {
  936. // Use `toConfObject` in case the button definition being extended
  937. // is itself a string or a function
  938. if ( ! _dtButtons[ conf.extend ] ) {
  939. throw 'Cannot extend unknown button type: '+conf.extend;
  940. }
  941. var objArray = toConfObject( _dtButtons[ conf.extend ] );
  942. if ( Array.isArray( objArray ) ) {
  943. return objArray;
  944. }
  945. else if ( ! objArray ) {
  946. // This is a little brutal as it might be possible to have a
  947. // valid button without the extend, but if there is no extend
  948. // then the host button would be acting in an undefined state
  949. return false;
  950. }
  951. // Stash the current class name
  952. var originalClassName = objArray.className;
  953. if (conf.config !== undefined && objArray.config !== undefined) {
  954. conf.config = $.extend({}, objArray.config, conf.config)
  955. }
  956. conf = $.extend( {}, objArray, conf );
  957. // The extend will have overwritten the original class name if the
  958. // `conf` object also assigned a class, but we want to concatenate
  959. // them so they are list that is combined from all extended buttons
  960. if ( originalClassName && conf.className !== originalClassName ) {
  961. conf.className = originalClassName+' '+conf.className;
  962. }
  963. // Buttons to be added to a collection -gives the ability to define
  964. // if buttons should be added to the start or end of a collection
  965. var postfixButtons = conf.postfixButtons;
  966. if ( postfixButtons ) {
  967. if ( ! conf.buttons ) {
  968. conf.buttons = [];
  969. }
  970. for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) {
  971. conf.buttons.push( postfixButtons[i] );
  972. }
  973. conf.postfixButtons = null;
  974. }
  975. var prefixButtons = conf.prefixButtons;
  976. if ( prefixButtons ) {
  977. if ( ! conf.buttons ) {
  978. conf.buttons = [];
  979. }
  980. for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) {
  981. conf.buttons.splice( i, 0, prefixButtons[i] );
  982. }
  983. conf.prefixButtons = null;
  984. }
  985. // Although we want the `conf` object to overwrite almost all of
  986. // the properties of the object being extended, the `extend`
  987. // property should come from the object being extended
  988. conf.extend = objArray.extend;
  989. }
  990. return conf;
  991. },
  992. /**
  993. * Display (and replace if there is an existing one) a popover attached to a button
  994. * @param {string|node} content Content to show
  995. * @param {DataTable.Api} hostButton DT API instance of the button
  996. * @param {object} inOpts Options (see object below for all options)
  997. */
  998. _popover: function ( content, hostButton, inOpts, e ) {
  999. var dt = hostButton;
  1000. var buttonsSettings = this.c;
  1001. var closed = false;
  1002. var options = $.extend( {
  1003. align: 'button-left', // button-right, dt-container, split-left, split-right
  1004. autoClose: false,
  1005. background: true,
  1006. backgroundClassName: 'dt-button-background',
  1007. closeButton: true,
  1008. contentClassName: buttonsSettings.dom.collection.className,
  1009. collectionLayout: '',
  1010. collectionTitle: '',
  1011. dropup: false,
  1012. fade: 400,
  1013. popoverTitle: '',
  1014. rightAlignClassName: 'dt-button-right',
  1015. tag: buttonsSettings.dom.collection.tag
  1016. }, inOpts );
  1017. var hostNode = hostButton.node();
  1018. var close = function () {
  1019. closed = true;
  1020. _fadeOut(
  1021. $('.dt-button-collection'),
  1022. options.fade,
  1023. function () {
  1024. $(this).detach();
  1025. }
  1026. );
  1027. $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes())
  1028. .attr('aria-expanded', 'false');
  1029. $('div.dt-button-background').off( 'click.dtb-collection' );
  1030. Buttons.background( false, options.backgroundClassName, options.fade, hostNode );
  1031. $(window).off('resize.resize.dtb-collection');
  1032. $('body').off( '.dtb-collection' );
  1033. dt.off( 'buttons-action.b-internal' );
  1034. dt.off( 'destroy' );
  1035. };
  1036. if (content === false) {
  1037. close();
  1038. return;
  1039. }
  1040. var existingExpanded = $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes());
  1041. if ( existingExpanded.length ) {
  1042. // Reuse the current position if the button that was triggered is inside an existing collection
  1043. if (hostNode.closest('div.dt-button-collection').length) {
  1044. hostNode = existingExpanded.eq(0);
  1045. }
  1046. close();
  1047. }
  1048. // Try to be smart about the layout
  1049. var cnt = $('.dt-button', content).length;
  1050. var mod = '';
  1051. if (cnt === 3) {
  1052. mod = 'dtb-b3';
  1053. }
  1054. else if (cnt === 2) {
  1055. mod = 'dtb-b2';
  1056. }
  1057. else if (cnt === 1) {
  1058. mod = 'dtb-b1';
  1059. }
  1060. var display = $('<div/>')
  1061. .addClass('dt-button-collection')
  1062. .addClass(options.collectionLayout)
  1063. .addClass(options.splitAlignClass)
  1064. .addClass(mod)
  1065. .css('display', 'none');
  1066. content = $(content)
  1067. .addClass(options.contentClassName)
  1068. .attr('role', 'menu')
  1069. .appendTo(display);
  1070. hostNode.attr( 'aria-expanded', 'true' );
  1071. if ( hostNode.parents('body')[0] !== document.body ) {
  1072. hostNode = document.body.lastChild;
  1073. }
  1074. if ( options.popoverTitle ) {
  1075. display.prepend('<div class="dt-button-collection-title">'+options.popoverTitle+'</div>');
  1076. }
  1077. else if ( options.collectionTitle ) {
  1078. display.prepend('<div class="dt-button-collection-title">'+options.collectionTitle+'</div>');
  1079. }
  1080. if (options.closeButton) {
  1081. display.prepend('<div class="dtb-popover-close">x</div>').addClass('dtb-collection-closeable')
  1082. }
  1083. _fadeIn( display.insertAfter( hostNode ), options.fade );
  1084. var tableContainer = $( hostButton.table().container() );
  1085. var position = display.css( 'position' );
  1086. if ( options.span === 'container' || options.align === 'dt-container' ) {
  1087. hostNode = hostNode.parent();
  1088. display.css('width', tableContainer.width());
  1089. }
  1090. // Align the popover relative to the DataTables container
  1091. // Useful for wide popovers such as SearchPanes
  1092. if (position === 'absolute') {
  1093. // Align relative to the host button
  1094. var offsetParent = $(hostNode[0].offsetParent);
  1095. var buttonPosition = hostNode.position();
  1096. var buttonOffset = hostNode.offset();
  1097. var tableSizes = offsetParent.offset();
  1098. var containerPosition = offsetParent.position();
  1099. var computed = window.getComputedStyle(offsetParent[0]);
  1100. tableSizes.height = offsetParent.outerHeight();
  1101. tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft);
  1102. tableSizes.right = tableSizes.left + tableSizes.width;
  1103. tableSizes.bottom = tableSizes.top + tableSizes.height;
  1104. // Set the initial position so we can read height / width
  1105. var top = buttonPosition.top + hostNode.outerHeight();
  1106. var left = buttonPosition.left;
  1107. display.css( {
  1108. top: top,
  1109. left: left
  1110. } );
  1111. // Get the popover position
  1112. computed = window.getComputedStyle(display[0]);
  1113. var popoverSizes = display.offset();
  1114. popoverSizes.height = display.outerHeight();
  1115. popoverSizes.width = display.outerWidth();
  1116. popoverSizes.right = popoverSizes.left + popoverSizes.width;
  1117. popoverSizes.bottom = popoverSizes.top + popoverSizes.height;
  1118. popoverSizes.marginTop = parseFloat(computed.marginTop);
  1119. popoverSizes.marginBottom = parseFloat(computed.marginBottom);
  1120. // First position per the class requirements - pop up and right align
  1121. if (options.dropup) {
  1122. top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom;
  1123. }
  1124. if (options.align === 'button-right' || display.hasClass( options.rightAlignClassName )) {
  1125. left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth();
  1126. }
  1127. // Container alignment - make sure it doesn't overflow the table container
  1128. if (options.align === 'dt-container' || options.align === 'container') {
  1129. if (left < buttonPosition.left) {
  1130. left = -buttonPosition.left;
  1131. }
  1132. if (left + popoverSizes.width > tableSizes.width) {
  1133. left = tableSizes.width - popoverSizes.width;
  1134. }
  1135. }
  1136. // Window adjustment
  1137. if (containerPosition.left + left + popoverSizes.width > $(window).width()) {
  1138. // Overflowing the document to the right
  1139. left = $(window).width() - popoverSizes.width - containerPosition.left;
  1140. }
  1141. if (buttonOffset.left + left < 0) {
  1142. // Off to the left of the document
  1143. left = -buttonOffset.left;
  1144. }
  1145. if (containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop()) {
  1146. // Pop up if otherwise we'd need the user to scroll down
  1147. top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom;
  1148. }
  1149. if (containerPosition.top + top < $(window).scrollTop()) {
  1150. // Correction for when the top is beyond the top of the page
  1151. top = buttonPosition.top + hostNode.outerHeight();
  1152. }
  1153. // Calculations all done - now set it
  1154. display.css( {
  1155. top: top,
  1156. left: left
  1157. } );
  1158. }
  1159. else {
  1160. // Fix position - centre on screen
  1161. var position = function () {
  1162. var half = $(window).height() / 2;
  1163. var top = display.height() / 2;
  1164. if ( top > half ) {
  1165. top = half;
  1166. }
  1167. display.css( 'marginTop', top*-1 );
  1168. };
  1169. position();
  1170. $(window).on('resize.dtb-collection', function () {
  1171. position();
  1172. });
  1173. }
  1174. if ( options.background ) {
  1175. Buttons.background(
  1176. true,
  1177. options.backgroundClassName,
  1178. options.fade,
  1179. options.backgroundHost || hostNode
  1180. );
  1181. }
  1182. // This is bonkers, but if we don't have a click listener on the
  1183. // background element, iOS Safari will ignore the body click
  1184. // listener below. An empty function here is all that is
  1185. // required to make it work...
  1186. $('div.dt-button-background').on( 'click.dtb-collection', function () {} );
  1187. if ( options.autoClose ) {
  1188. setTimeout( function () {
  1189. dt.on( 'buttons-action.b-internal', function (e, btn, dt, node) {
  1190. if ( node[0] === hostNode[0] ) {
  1191. return;
  1192. }
  1193. close();
  1194. } );
  1195. }, 0);
  1196. }
  1197. $(display).trigger('buttons-popover.dt');
  1198. dt.on('destroy', close);
  1199. setTimeout(function() {
  1200. closed = false;
  1201. $('body')
  1202. .on( 'click.dtb-collection', function (e) {
  1203. if (closed) {
  1204. return;
  1205. }
  1206. // andSelf is deprecated in jQ1.8, but we want 1.7 compat
  1207. var back = $.fn.addBack ? 'addBack' : 'andSelf';
  1208. var parent = $(e.target).parent()[0];
  1209. if (( ! $(e.target).parents()[back]().filter( content ).length && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background')) {
  1210. close();
  1211. }
  1212. } )
  1213. .on( 'keyup.dtb-collection', function (e) {
  1214. if ( e.keyCode === 27 ) {
  1215. close();
  1216. }
  1217. } );
  1218. }, 0);
  1219. }
  1220. } );
  1221. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1222. * Statics
  1223. */
  1224. /**
  1225. * Show / hide a background layer behind a collection
  1226. * @param {boolean} Flag to indicate if the background should be shown or
  1227. * hidden
  1228. * @param {string} Class to assign to the background
  1229. * @static
  1230. */
  1231. Buttons.background = function ( show, className, fade, insertPoint ) {
  1232. if ( fade === undefined ) {
  1233. fade = 400;
  1234. }
  1235. if ( ! insertPoint ) {
  1236. insertPoint = document.body;
  1237. }
  1238. if ( show ) {
  1239. _fadeIn(
  1240. $('<div/>')
  1241. .addClass( className )
  1242. .css( 'display', 'none' )
  1243. .insertAfter( insertPoint ),
  1244. fade
  1245. );
  1246. }
  1247. else {
  1248. _fadeOut(
  1249. $('div.'+className),
  1250. fade,
  1251. function () {
  1252. $(this)
  1253. .removeClass( className )
  1254. .remove();
  1255. }
  1256. );
  1257. }
  1258. };
  1259. /**
  1260. * Instance selector - select Buttons instances based on an instance selector
  1261. * value from the buttons assigned to a DataTable. This is only useful if
  1262. * multiple instances are attached to a DataTable.
  1263. * @param {string|int|array} Instance selector - see `instance-selector`
  1264. * documentation on the DataTables site
  1265. * @param {array} Button instance array that was attached to the DataTables
  1266. * settings object
  1267. * @return {array} Buttons instances
  1268. * @static
  1269. */
  1270. Buttons.instanceSelector = function ( group, buttons )
  1271. {
  1272. if ( group === undefined || group === null ) {
  1273. return $.map( buttons, function ( v ) {
  1274. return v.inst;
  1275. } );
  1276. }
  1277. var ret = [];
  1278. var names = $.map( buttons, function ( v ) {
  1279. return v.name;
  1280. } );
  1281. // Flatten the group selector into an array of single options
  1282. var process = function ( input ) {
  1283. if ( Array.isArray( input ) ) {
  1284. for ( var i=0, ien=input.length ; i<ien ; i++ ) {
  1285. process( input[i] );
  1286. }
  1287. return;
  1288. }
  1289. if ( typeof input === 'string' ) {
  1290. if ( input.indexOf( ',' ) !== -1 ) {
  1291. // String selector, list of names
  1292. process( input.split(',') );
  1293. }
  1294. else {
  1295. // String selector individual name
  1296. var idx = $.inArray( input.trim(), names );
  1297. if ( idx !== -1 ) {
  1298. ret.push( buttons[ idx ].inst );
  1299. }
  1300. }
  1301. }
  1302. else if ( typeof input === 'number' ) {
  1303. // Index selector
  1304. ret.push( buttons[ input ].inst );
  1305. }
  1306. else if ( typeof input === 'object' ) {
  1307. // Actual instance selector
  1308. ret.push( input );
  1309. }
  1310. };
  1311. process( group );
  1312. return ret;
  1313. };
  1314. /**
  1315. * Button selector - select one or more buttons from a selector input so some
  1316. * operation can be performed on them.
  1317. * @param {array} Button instances array that the selector should operate on
  1318. * @param {string|int|node|jQuery|array} Button selector - see
  1319. * `button-selector` documentation on the DataTables site
  1320. * @return {array} Array of objects containing `inst` and `idx` properties of
  1321. * the selected buttons so you know which instance each button belongs to.
  1322. * @static
  1323. */
  1324. Buttons.buttonSelector = function ( insts, selector )
  1325. {
  1326. var ret = [];
  1327. var nodeBuilder = function ( a, buttons, baseIdx ) {
  1328. var button;
  1329. var idx;
  1330. for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
  1331. button = buttons[i];
  1332. if ( button ) {
  1333. idx = baseIdx !== undefined ?
  1334. baseIdx+i :
  1335. i+'';
  1336. a.push( {
  1337. node: button.node,
  1338. name: button.conf.name,
  1339. idx: idx
  1340. } );
  1341. if ( button.buttons ) {
  1342. nodeBuilder( a, button.buttons, idx+'-' );
  1343. }
  1344. }
  1345. }
  1346. };
  1347. var run = function ( selector, inst ) {
  1348. var i, ien;
  1349. var buttons = [];
  1350. nodeBuilder( buttons, inst.s.buttons );
  1351. var nodes = $.map( buttons, function (v) {
  1352. return v.node;
  1353. } );
  1354. if ( Array.isArray( selector ) || selector instanceof $ ) {
  1355. for ( i=0, ien=selector.length ; i<ien ; i++ ) {
  1356. run( selector[i], inst );
  1357. }
  1358. return;
  1359. }
  1360. if ( selector === null || selector === undefined || selector === '*' ) {
  1361. // Select all
  1362. for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
  1363. ret.push( {
  1364. inst: inst,
  1365. node: buttons[i].node
  1366. } );
  1367. }
  1368. }
  1369. else if ( typeof selector === 'number' ) {
  1370. // Main button index selector
  1371. if (inst.s.buttons[ selector ]) {
  1372. ret.push( {
  1373. inst: inst,
  1374. node: inst.s.buttons[ selector ].node
  1375. } );
  1376. }
  1377. }
  1378. else if ( typeof selector === 'string' ) {
  1379. if ( selector.indexOf( ',' ) !== -1 ) {
  1380. // Split
  1381. var a = selector.split(',');
  1382. for ( i=0, ien=a.length ; i<ien ; i++ ) {
  1383. run( a[i].trim(), inst );
  1384. }
  1385. }
  1386. else if ( selector.match( /^\d+(\-\d+)*$/ ) ) {
  1387. // Sub-button index selector
  1388. var indexes = $.map( buttons, function (v) {
  1389. return v.idx;
  1390. } );
  1391. ret.push( {
  1392. inst: inst,
  1393. node: buttons[ $.inArray( selector, indexes ) ].node
  1394. } );
  1395. }
  1396. else if ( selector.indexOf( ':name' ) !== -1 ) {
  1397. // Button name selector
  1398. var name = selector.replace( ':name', '' );
  1399. for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
  1400. if ( buttons[i].name === name ) {
  1401. ret.push( {
  1402. inst: inst,
  1403. node: buttons[i].node
  1404. } );
  1405. }
  1406. }
  1407. }
  1408. else {
  1409. // jQuery selector on the nodes
  1410. $( nodes ).filter( selector ).each( function () {
  1411. ret.push( {
  1412. inst: inst,
  1413. node: this
  1414. } );
  1415. } );
  1416. }
  1417. }
  1418. else if ( typeof selector === 'object' && selector.nodeName ) {
  1419. // Node selector
  1420. var idx = $.inArray( selector, nodes );
  1421. if ( idx !== -1 ) {
  1422. ret.push( {
  1423. inst: inst,
  1424. node: nodes[ idx ]
  1425. } );
  1426. }
  1427. }
  1428. };
  1429. for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
  1430. var inst = insts[i];
  1431. run( selector, inst );
  1432. }
  1433. return ret;
  1434. };
  1435. /**
  1436. * Default function used for formatting output data.
  1437. * @param {*} str Data to strip
  1438. */
  1439. Buttons.stripData = function ( str, config ) {
  1440. if ( typeof str !== 'string' ) {
  1441. return str;
  1442. }
  1443. // Always remove script tags
  1444. str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' );
  1445. // Always remove comments
  1446. str = str.replace( /<!\-\-.*?\-\->/g, '' );
  1447. if ( ! config || config.stripHtml ) {
  1448. str = str.replace( /<[^>]*>/g, '' );
  1449. }
  1450. if ( ! config || config.trim ) {
  1451. str = str.replace( /^\s+|\s+$/g, '' );
  1452. }
  1453. if ( ! config || config.stripNewlines ) {
  1454. str = str.replace( /\n/g, ' ' );
  1455. }
  1456. if ( ! config || config.decodeEntities ) {
  1457. _exportTextarea.innerHTML = str;
  1458. str = _exportTextarea.value;
  1459. }
  1460. return str;
  1461. };
  1462. /**
  1463. * Buttons defaults. For full documentation, please refer to the docs/option
  1464. * directory or the DataTables site.
  1465. * @type {Object}
  1466. * @static
  1467. */
  1468. Buttons.defaults = {
  1469. buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ],
  1470. name: 'main',
  1471. tabIndex: 0,
  1472. dom: {
  1473. container: {
  1474. tag: 'div',
  1475. className: 'dt-buttons'
  1476. },
  1477. collection: {
  1478. tag: 'div',
  1479. className: ''
  1480. },
  1481. button: {
  1482. tag: 'button',
  1483. className: 'dt-button',
  1484. active: 'active',
  1485. disabled: 'disabled',
  1486. spacerClass: ''
  1487. },
  1488. buttonLiner: {
  1489. tag: 'span',
  1490. className: ''
  1491. },
  1492. split: {
  1493. tag: 'div',
  1494. className: 'dt-button-split',
  1495. },
  1496. splitWrapper: {
  1497. tag: 'div',
  1498. className: 'dt-btn-split-wrapper',
  1499. },
  1500. splitDropdown: {
  1501. tag: 'button',
  1502. text: '&#x25BC;',
  1503. className: 'dt-btn-split-drop',
  1504. align: 'split-right',
  1505. splitAlignClass: 'dt-button-split-left'
  1506. },
  1507. splitDropdownButton: {
  1508. tag: 'button',
  1509. className: 'dt-btn-split-drop-button dt-button',
  1510. },
  1511. splitCollection: {
  1512. tag: 'div',
  1513. className: 'dt-button-split-collection',
  1514. }
  1515. }
  1516. };
  1517. /**
  1518. * Version information
  1519. * @type {string}
  1520. * @static
  1521. */
  1522. Buttons.version = '2.2.2';
  1523. $.extend( _dtButtons, {
  1524. collection: {
  1525. text: function ( dt ) {
  1526. return dt.i18n( 'buttons.collection', 'Collection' );
  1527. },
  1528. className: 'buttons-collection',
  1529. closeButton: false,
  1530. init: function ( dt, button, config ) {
  1531. button.attr( 'aria-expanded', false );
  1532. },
  1533. action: function ( e, dt, button, config ) {
  1534. if ( config._collection.parents('body').length ) {
  1535. this.popover(false, config);
  1536. }
  1537. else {
  1538. this.popover(config._collection, config);
  1539. }
  1540. },
  1541. attr: {
  1542. 'aria-haspopup': true
  1543. }
  1544. // Also the popover options, defined in Buttons.popover
  1545. },
  1546. split: {
  1547. text: function ( dt ) {
  1548. return dt.i18n( 'buttons.split', 'Split' );
  1549. },
  1550. className: 'buttons-split',
  1551. closeButton: false,
  1552. init: function ( dt, button, config ) {
  1553. return button.attr( 'aria-expanded', false );
  1554. },
  1555. action: function ( e, dt, button, config ) {
  1556. this.popover(config._collection, config);
  1557. },
  1558. attr: {
  1559. 'aria-haspopup': true
  1560. }
  1561. // Also the popover options, defined in Buttons.popover
  1562. },
  1563. copy: function ( dt, conf ) {
  1564. if ( _dtButtons.copyHtml5 ) {
  1565. return 'copyHtml5';
  1566. }
  1567. },
  1568. csv: function ( dt, conf ) {
  1569. if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) {
  1570. return 'csvHtml5';
  1571. }
  1572. },
  1573. excel: function ( dt, conf ) {
  1574. if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) {
  1575. return 'excelHtml5';
  1576. }
  1577. },
  1578. pdf: function ( dt, conf ) {
  1579. if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) {
  1580. return 'pdfHtml5';
  1581. }
  1582. },
  1583. pageLength: function ( dt ) {
  1584. var lengthMenu = dt.settings()[0].aLengthMenu;
  1585. var vals = [];
  1586. var lang = [];
  1587. var text = function ( dt ) {
  1588. return dt.i18n( 'buttons.pageLength', {
  1589. "-1": 'Show all rows',
  1590. _: 'Show %d rows'
  1591. }, dt.page.len() );
  1592. };
  1593. // Support for DataTables 1.x 2D array
  1594. if (Array.isArray( lengthMenu[0] )) {
  1595. vals = lengthMenu[0];
  1596. lang = lengthMenu[1];
  1597. }
  1598. else {
  1599. for (var i=0 ; i<lengthMenu.length ; i++) {
  1600. var option = lengthMenu[i];
  1601. // Support for DataTables 2 object in the array
  1602. if ($.isPlainObject(option)) {
  1603. vals.push(option.value);
  1604. lang.push(option.label);
  1605. }
  1606. else {
  1607. vals.push(option);
  1608. lang.push(option);
  1609. }
  1610. }
  1611. }
  1612. return {
  1613. extend: 'collection',
  1614. text: text,
  1615. className: 'buttons-page-length',
  1616. autoClose: true,
  1617. buttons: $.map( vals, function ( val, i ) {
  1618. return {
  1619. text: lang[i],
  1620. className: 'button-page-length',
  1621. action: function ( e, dt ) {
  1622. dt.page.len( val ).draw();
  1623. },
  1624. init: function ( dt, node, conf ) {
  1625. var that = this;
  1626. var fn = function () {
  1627. that.active( dt.page.len() === val );
  1628. };
  1629. dt.on( 'length.dt'+conf.namespace, fn );
  1630. fn();
  1631. },
  1632. destroy: function ( dt, node, conf ) {
  1633. dt.off( 'length.dt'+conf.namespace );
  1634. }
  1635. };
  1636. } ),
  1637. init: function ( dt, node, conf ) {
  1638. var that = this;
  1639. dt.on( 'length.dt'+conf.namespace, function () {
  1640. that.text( conf.text );
  1641. } );
  1642. },
  1643. destroy: function ( dt, node, conf ) {
  1644. dt.off( 'length.dt'+conf.namespace );
  1645. }
  1646. };
  1647. },
  1648. spacer: {
  1649. style: 'empty',
  1650. spacer: true,
  1651. text: function ( dt ) {
  1652. return dt.i18n( 'buttons.spacer', '' );
  1653. }
  1654. }
  1655. } );
  1656. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1657. * DataTables API
  1658. *
  1659. * For complete documentation, please refer to the docs/api directory or the
  1660. * DataTables site
  1661. */
  1662. // Buttons group and individual button selector
  1663. DataTable.Api.register( 'buttons()', function ( group, selector ) {
  1664. // Argument shifting
  1665. if ( selector === undefined ) {
  1666. selector = group;
  1667. group = undefined;
  1668. }
  1669. this.selector.buttonGroup = group;
  1670. var res = this.iterator( true, 'table', function ( ctx ) {
  1671. if ( ctx._buttons ) {
  1672. return Buttons.buttonSelector(
  1673. Buttons.instanceSelector( group, ctx._buttons ),
  1674. selector
  1675. );
  1676. }
  1677. }, true );
  1678. res._groupSelector = group;
  1679. return res;
  1680. } );
  1681. // Individual button selector
  1682. DataTable.Api.register( 'button()', function ( group, selector ) {
  1683. // just run buttons() and truncate
  1684. var buttons = this.buttons( group, selector );
  1685. if ( buttons.length > 1 ) {
  1686. buttons.splice( 1, buttons.length );
  1687. }
  1688. return buttons;
  1689. } );
  1690. // Active buttons
  1691. DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) {
  1692. if ( flag === undefined ) {
  1693. return this.map( function ( set ) {
  1694. return set.inst.active( set.node );
  1695. } );
  1696. }
  1697. return this.each( function ( set ) {
  1698. set.inst.active( set.node, flag );
  1699. } );
  1700. } );
  1701. // Get / set button action
  1702. DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) {
  1703. if ( action === undefined ) {
  1704. return this.map( function ( set ) {
  1705. return set.inst.action( set.node );
  1706. } );
  1707. }
  1708. return this.each( function ( set ) {
  1709. set.inst.action( set.node, action );
  1710. } );
  1711. } );
  1712. // Collection control
  1713. DataTable.Api.registerPlural( 'buttons().collectionRebuild()', 'button().collectionRebuild()', function ( buttons ) {
  1714. return this.each( function ( set ) {
  1715. for(var i = 0; i < buttons.length; i++) {
  1716. if(typeof buttons[i] === 'object') {
  1717. buttons[i].parentConf = set;
  1718. }
  1719. }
  1720. set.inst.collectionRebuild( set.node, buttons );
  1721. } );
  1722. } );
  1723. // Enable / disable buttons
  1724. DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) {
  1725. return this.each( function ( set ) {
  1726. set.inst.enable( set.node, flag );
  1727. } );
  1728. } );
  1729. // Disable buttons
  1730. DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () {
  1731. return this.each( function ( set ) {
  1732. set.inst.disable( set.node );
  1733. } );
  1734. } );
  1735. // Button index
  1736. DataTable.Api.register( 'button().index()', function () {
  1737. var idx = null;
  1738. this.each( function ( set ) {
  1739. var res = set.inst.index( set.node );
  1740. if (res !== null) {
  1741. idx = res;
  1742. }
  1743. } );
  1744. return idx;
  1745. } );
  1746. // Get button nodes
  1747. DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () {
  1748. var jq = $();
  1749. // jQuery will automatically reduce duplicates to a single entry
  1750. $( this.each( function ( set ) {
  1751. jq = jq.add( set.inst.node( set.node ) );
  1752. } ) );
  1753. return jq;
  1754. } );
  1755. // Get / set button processing state
  1756. DataTable.Api.registerPlural( 'buttons().processing()', 'button().processing()', function ( flag ) {
  1757. if ( flag === undefined ) {
  1758. return this.map( function ( set ) {
  1759. return set.inst.processing( set.node );
  1760. } );
  1761. }
  1762. return this.each( function ( set ) {
  1763. set.inst.processing( set.node, flag );
  1764. } );
  1765. } );
  1766. // Get / set button text (i.e. the button labels)
  1767. DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) {
  1768. if ( label === undefined ) {
  1769. return this.map( function ( set ) {
  1770. return set.inst.text( set.node );
  1771. } );
  1772. }
  1773. return this.each( function ( set ) {
  1774. set.inst.text( set.node, label );
  1775. } );
  1776. } );
  1777. // Trigger a button's action
  1778. DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () {
  1779. return this.each( function ( set ) {
  1780. set.inst.node( set.node ).trigger( 'click' );
  1781. } );
  1782. } );
  1783. // Button resolver to the popover
  1784. DataTable.Api.register( 'button().popover()', function (content, options) {
  1785. return this.map( function ( set ) {
  1786. return set.inst._popover( content, this.button(this[0].node), options );
  1787. } );
  1788. } );
  1789. // Get the container elements
  1790. DataTable.Api.register( 'buttons().containers()', function () {
  1791. var jq = $();
  1792. var groupSelector = this._groupSelector;
  1793. // We need to use the group selector directly, since if there are no buttons
  1794. // the result set will be empty
  1795. this.iterator( true, 'table', function ( ctx ) {
  1796. if ( ctx._buttons ) {
  1797. var insts = Buttons.instanceSelector( groupSelector, ctx._buttons );
  1798. for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
  1799. jq = jq.add( insts[i].container() );
  1800. }
  1801. }
  1802. } );
  1803. return jq;
  1804. } );
  1805. DataTable.Api.register( 'buttons().container()', function () {
  1806. // API level of nesting is `buttons()` so we can zip into the containers method
  1807. return this.containers().eq(0);
  1808. } );
  1809. // Add a new button
  1810. DataTable.Api.register( 'button().add()', function ( idx, conf, draw ) {
  1811. var ctx = this.context;
  1812. // Don't use `this` as it could be empty - select the instances directly
  1813. if ( ctx.length ) {
  1814. var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons );
  1815. if ( inst.length ) {
  1816. inst[0].add( conf, idx , draw);
  1817. }
  1818. }
  1819. return this.button( this._groupSelector, idx );
  1820. } );
  1821. // Destroy the button sets selected
  1822. DataTable.Api.register( 'buttons().destroy()', function () {
  1823. this.pluck( 'inst' ).unique().each( function ( inst ) {
  1824. inst.destroy();
  1825. } );
  1826. return this;
  1827. } );
  1828. // Remove a button
  1829. DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () {
  1830. this.each( function ( set ) {
  1831. set.inst.remove( set.node );
  1832. } );
  1833. return this;
  1834. } );
  1835. // Information box that can be used by buttons
  1836. var _infoTimer;
  1837. DataTable.Api.register( 'buttons.info()', function ( title, message, time ) {
  1838. var that = this;
  1839. if ( title === false ) {
  1840. this.off('destroy.btn-info');
  1841. _fadeOut(
  1842. $('#datatables_buttons_info'),
  1843. 400,
  1844. function () {
  1845. $(this).remove();
  1846. }
  1847. );
  1848. clearTimeout( _infoTimer );
  1849. _infoTimer = null;
  1850. return this;
  1851. }
  1852. if ( _infoTimer ) {
  1853. clearTimeout( _infoTimer );
  1854. }
  1855. if ( $('#datatables_buttons_info').length ) {
  1856. $('#datatables_buttons_info').remove();
  1857. }
  1858. title = title ? '<h2>'+title+'</h2>' : '';
  1859. _fadeIn(
  1860. $('<div id="datatables_buttons_info" class="dt-button-info"/>')
  1861. .html( title )
  1862. .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) )
  1863. .css( 'display', 'none' )
  1864. .appendTo( 'body' )
  1865. );
  1866. if ( time !== undefined && time !== 0 ) {
  1867. _infoTimer = setTimeout( function () {
  1868. that.buttons.info( false );
  1869. }, time );
  1870. }
  1871. this.on('destroy.btn-info', function () {
  1872. that.buttons.info(false);
  1873. });
  1874. return this;
  1875. } );
  1876. // Get data from the table for export - this is common to a number of plug-in
  1877. // buttons so it is included in the Buttons core library
  1878. DataTable.Api.register( 'buttons.exportData()', function ( options ) {
  1879. if ( this.context.length ) {
  1880. return _exportData( new DataTable.Api( this.context[0] ), options );
  1881. }
  1882. } );
  1883. // Get information about the export that is common to many of the export data
  1884. // types (DRY)
  1885. DataTable.Api.register( 'buttons.exportInfo()', function ( conf ) {
  1886. if ( ! conf ) {
  1887. conf = {};
  1888. }
  1889. return {
  1890. filename: _filename( conf ),
  1891. title: _title( conf ),
  1892. messageTop: _message(this, conf.message || conf.messageTop, 'top'),
  1893. messageBottom: _message(this, conf.messageBottom, 'bottom')
  1894. };
  1895. } );
  1896. /**
  1897. * Get the file name for an exported file.
  1898. *
  1899. * @param {object} config Button configuration
  1900. * @param {boolean} incExtension Include the file name extension
  1901. */
  1902. var _filename = function ( config )
  1903. {
  1904. // Backwards compatibility
  1905. var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ?
  1906. config.title :
  1907. config.filename;
  1908. if ( typeof filename === 'function' ) {
  1909. filename = filename();
  1910. }
  1911. if ( filename === undefined || filename === null ) {
  1912. return null;
  1913. }
  1914. if ( filename.indexOf( '*' ) !== -1 ) {
  1915. filename = filename.replace( '*', $('head > title').text() ).trim();
  1916. }
  1917. // Strip characters which the OS will object to
  1918. filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");
  1919. var extension = _stringOrFunction( config.extension );
  1920. if ( ! extension ) {
  1921. extension = '';
  1922. }
  1923. return filename + extension;
  1924. };
  1925. /**
  1926. * Simply utility method to allow parameters to be given as a function
  1927. *
  1928. * @param {undefined|string|function} option Option
  1929. * @return {null|string} Resolved value
  1930. */
  1931. var _stringOrFunction = function ( option )
  1932. {
  1933. if ( option === null || option === undefined ) {
  1934. return null;
  1935. }
  1936. else if ( typeof option === 'function' ) {
  1937. return option();
  1938. }
  1939. return option;
  1940. };
  1941. /**
  1942. * Get the title for an exported file.
  1943. *
  1944. * @param {object} config Button configuration
  1945. */
  1946. var _title = function ( config )
  1947. {
  1948. var title = _stringOrFunction( config.title );
  1949. return title === null ?
  1950. null : title.indexOf( '*' ) !== -1 ?
  1951. title.replace( '*', $('head > title').text() || 'Exported data' ) :
  1952. title;
  1953. };
  1954. var _message = function ( dt, option, position )
  1955. {
  1956. var message = _stringOrFunction( option );
  1957. if ( message === null ) {
  1958. return null;
  1959. }
  1960. var caption = $('caption', dt.table().container()).eq(0);
  1961. if ( message === '*' ) {
  1962. var side = caption.css( 'caption-side' );
  1963. if ( side !== position ) {
  1964. return null;
  1965. }
  1966. return caption.length ?
  1967. caption.text() :
  1968. '';
  1969. }
  1970. return message;
  1971. };
  1972. var _exportTextarea = $('<textarea/>')[0];
  1973. var _exportData = function ( dt, inOpts )
  1974. {
  1975. var config = $.extend( true, {}, {
  1976. rows: null,
  1977. columns: '',
  1978. modifier: {
  1979. search: 'applied',
  1980. order: 'applied'
  1981. },
  1982. orthogonal: 'display',
  1983. stripHtml: true,
  1984. stripNewlines: true,
  1985. decodeEntities: true,
  1986. trim: true,
  1987. format: {
  1988. header: function ( d ) {
  1989. return Buttons.stripData( d, config );
  1990. },
  1991. footer: function ( d ) {
  1992. return Buttons.stripData( d, config );
  1993. },
  1994. body: function ( d ) {
  1995. return Buttons.stripData( d, config );
  1996. }
  1997. },
  1998. customizeData: null
  1999. }, inOpts );
  2000. var header = dt.columns( config.columns ).indexes().map( function (idx) {
  2001. var el = dt.column( idx ).header();
  2002. return config.format.header( el.innerHTML, idx, el );
  2003. } ).toArray();
  2004. var footer = dt.table().footer() ?
  2005. dt.columns( config.columns ).indexes().map( function (idx) {
  2006. var el = dt.column( idx ).footer();
  2007. return config.format.footer( el ? el.innerHTML : '', idx, el );
  2008. } ).toArray() :
  2009. null;
  2010. // If Select is available on this table, and any rows are selected, limit the export
  2011. // to the selected rows. If no rows are selected, all rows will be exported. Specify
  2012. // a `selected` modifier to control directly.
  2013. var modifier = $.extend( {}, config.modifier );
  2014. if ( dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined ) {
  2015. if ( dt.rows( config.rows, $.extend( { selected: true }, modifier ) ).any() ) {
  2016. $.extend( modifier, { selected: true } )
  2017. }
  2018. }
  2019. var rowIndexes = dt.rows( config.rows, modifier ).indexes().toArray();
  2020. var selectedCells = dt.cells( rowIndexes, config.columns );
  2021. var cells = selectedCells
  2022. .render( config.orthogonal )
  2023. .toArray();
  2024. var cellNodes = selectedCells
  2025. .nodes()
  2026. .toArray();
  2027. var columns = header.length;
  2028. var rows = columns > 0 ? cells.length / columns : 0;
  2029. var body = [];
  2030. var cellCounter = 0;
  2031. for ( var i=0, ien=rows ; i<ien ; i++ ) {
  2032. var row = [ columns ];
  2033. for ( var j=0 ; j<columns ; j++ ) {
  2034. row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] );
  2035. cellCounter++;
  2036. }
  2037. body[i] = row;
  2038. }
  2039. var data = {
  2040. header: header,
  2041. footer: footer,
  2042. body: body
  2043. };
  2044. if ( config.customizeData ) {
  2045. config.customizeData( data );
  2046. }
  2047. return data;
  2048. };
  2049. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2050. * DataTables interface
  2051. */
  2052. // Attach to DataTables objects for global access
  2053. $.fn.dataTable.Buttons = Buttons;
  2054. $.fn.DataTable.Buttons = Buttons;
  2055. // DataTables creation - check if the buttons have been defined for this table,
  2056. // they will have been if the `B` option was used in `dom`, otherwise we should
  2057. // create the buttons instance here so they can be inserted into the document
  2058. // using the API. Listen for `init` for compatibility with pre 1.10.10, but to
  2059. // be removed in future.
  2060. $(document).on( 'init.dt plugin-init.dt', function (e, settings) {
  2061. if ( e.namespace !== 'dt' ) {
  2062. return;
  2063. }
  2064. var opts = settings.oInit.buttons || DataTable.defaults.buttons;
  2065. if ( opts && ! settings._buttons ) {
  2066. new Buttons( settings, opts ).container();
  2067. }
  2068. } );
  2069. function _init ( settings, options ) {
  2070. var api = new DataTable.Api( settings );
  2071. var opts = options
  2072. ? options
  2073. : api.init().buttons || DataTable.defaults.buttons;
  2074. return new Buttons( api, opts ).container();
  2075. }
  2076. // DataTables `dom` feature option
  2077. DataTable.ext.feature.push( {
  2078. fnInit: _init,
  2079. cFeature: "B"
  2080. } );
  2081. // DataTables 2 layout feature
  2082. if ( DataTable.ext.features ) {
  2083. DataTable.ext.features.register( 'buttons', _init );
  2084. }
  2085. return Buttons;
  2086. }));