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