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