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