Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

dataTables.buttons.js 68KB

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