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

2530 lines
61 KiB

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