1/*! Buttons for DataTables 3.0.2 2 * © 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 var jq = require('jquery'); 15 var cjsRequires = function (root, $) { 16 if ( ! $.fn.dataTable ) { 17 require('datatables.net')(root, $); 18 } 19 }; 20 21 if (typeof window === 'undefined') { 22 module.exports = function (root, $) { 23 if ( ! root ) { 24 // CommonJS environments without a window global must pass a 25 // root. This will give an error otherwise 26 root = window; 27 } 28 29 if ( ! $ ) { 30 $ = jq( root ); 31 } 32 33 cjsRequires( root, $ ); 34 return factory( $, root, root.document ); 35 }; 36 } 37 else { 38 cjsRequires( window, jq ); 39 module.exports = factory( jq, window, window.document ); 40 } 41 } 42 else { 43 // Browser 44 factory( jQuery, window, document ); 45 } 46}(function( $, window, document ) { 47'use strict'; 48var DataTable = $.fn.dataTable; 49 50 51 52// Used for namespacing events added to the document by each instance, so they 53// can be removed on destroy 54var _instCounter = 0; 55 56// Button namespacing counter for namespacing events on individual buttons 57var _buttonCounter = 0; 58 59var _dtButtons = DataTable.ext.buttons; 60 61// Custom entity decoder for data export 62var _entityDecoder = null; 63 64// Allow for jQuery slim 65function _fadeIn(el, duration, fn) { 66 if ($.fn.animate) { 67 el.stop().fadeIn(duration, fn); 68 } 69 else { 70 el.css('display', 'block'); 71 72 if (fn) { 73 fn.call(el); 74 } 75 } 76} 77 78function _fadeOut(el, duration, fn) { 79 if ($.fn.animate) { 80 el.stop().fadeOut(duration, fn); 81 } 82 else { 83 el.css('display', 'none'); 84 85 if (fn) { 86 fn.call(el); 87 } 88 } 89} 90 91/** 92 * [Buttons description] 93 * @param {[type]} 94 * @param {[type]} 95 */ 96var Buttons = function (dt, config) { 97 if (!DataTable.versionCheck('2')) { 98 throw 'Warning: Buttons requires DataTables 2 or newer'; 99 } 100 101 // If not created with a `new` keyword then we return a wrapper function that 102 // will take the settings object for a DT. This allows easy use of new instances 103 // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`. 104 if (!(this instanceof Buttons)) { 105 return function (settings) { 106 return new Buttons(settings, dt).container(); 107 }; 108 } 109 110 // If there is no config set it to an empty object 111 if (typeof config === 'undefined') { 112 config = {}; 113 } 114 115 // Allow a boolean true for defaults 116 if (config === true) { 117 config = {}; 118 } 119 120 // For easy configuration of buttons an array can be given 121 if (Array.isArray(config)) { 122 config = { buttons: config }; 123 } 124 125 this.c = $.extend(true, {}, Buttons.defaults, config); 126 127 // Don't want a deep copy for the buttons 128 if (config.buttons) { 129 this.c.buttons = config.buttons; 130 } 131 132 this.s = { 133 dt: new DataTable.Api(dt), 134 buttons: [], 135 listenKeys: '', 136 namespace: 'dtb' + _instCounter++ 137 }; 138 139 this.dom = { 140 container: $('<' + this.c.dom.container.tag + '/>').addClass( 141 this.c.dom.container.className 142 ) 143 }; 144 145 this._constructor(); 146}; 147 148$.extend(Buttons.prototype, { 149 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 150 * Public methods 151 */ 152 153 /** 154 * Get the action of a button 155 * @param {int|string} Button index 156 * @return {function} 157 */ /** 158 * Set the action of a button 159 * @param {node} node Button element 160 * @param {function} action Function to set 161 * @return {Buttons} Self for chaining 162 */ 163 action: function (node, action) { 164 var button = this._nodeToButton(node); 165 166 if (action === undefined) { 167 return button.conf.action; 168 } 169 170 button.conf.action = action; 171 172 return this; 173 }, 174 175 /** 176 * Add an active class to the button to make to look active or get current 177 * active state. 178 * @param {node} node Button element 179 * @param {boolean} [flag] Enable / disable flag 180 * @return {Buttons} Self for chaining or boolean for getter 181 */ 182 active: function (node, flag) { 183 var button = this._nodeToButton(node); 184 var klass = this.c.dom.button.active; 185 var jqNode = $(button.node); 186 187 if ( 188 button.inCollection && 189 this.c.dom.collection.button && 190 this.c.dom.collection.button.active !== undefined 191 ) { 192 klass = this.c.dom.collection.button.active; 193 } 194 195 if (flag === undefined) { 196 return jqNode.hasClass(klass); 197 } 198 199 jqNode.toggleClass(klass, flag === undefined ? true : flag); 200 201 return this; 202 }, 203 204 /** 205 * Add a new button 206 * @param {object} config Button configuration object, base string name or function 207 * @param {int|string} [idx] Button index for where to insert the button 208 * @param {boolean} [draw=true] Trigger a draw. Set a false when adding 209 * lots of buttons, until the last button. 210 * @return {Buttons} Self for chaining 211 */ 212 add: function (config, idx, draw) { 213 var buttons = this.s.buttons; 214 215 if (typeof idx === 'string') { 216 var split = idx.split('-'); 217 var base = this.s; 218 219 for (var i = 0, ien = split.length - 1; i < ien; i++) { 220 base = base.buttons[split[i] * 1]; 221 } 222 223 buttons = base.buttons; 224 idx = split[split.length - 1] * 1; 225 } 226 227 this._expandButton( 228 buttons, 229 config,
230 config !== undefined ? config.split : undefined, 231 (config === undefined || 232 config.split === undefined || 233 config.split.length === 0) && 234 base !== undefined, 235 false, 236 idx 237 ); 238 239 if (draw === undefined || draw === true) { 240 this._draw(); 241 } 242 243 return this; 244 }, 245 246 /** 247 * Clear buttons from a collection and then insert new buttons 248 */ 249 collectionRebuild: function (node, newButtons) { 250 var button = this._nodeToButton(node); 251 252 if (newButtons !== undefined) { 253 var i; 254 // Need to reverse the array 255 for (i = button.buttons.length - 1; i >= 0; i--) { 256 this.remove(button.buttons[i].node); 257 } 258 259 // If the collection has prefix and / or postfix buttons we need to add them in 260 if (button.conf.prefixButtons) { 261 newButtons.unshift.apply(newButtons, button.conf.prefixButtons); 262 } 263 264 if (button.conf.postfixButtons) { 265 newButtons.push.apply(newButtons, button.conf.postfixButtons); 266 } 267 268 for (i = 0; i < newButtons.length; i++) { 269 var newBtn = newButtons[i]; 270 271 this._expandButton( 272 button.buttons, 273 newBtn, 274 newBtn !== undefined && 275 newBtn.config !== undefined && 276 newBtn.config.split !== undefined, 277 true, 278 newBtn.parentConf !== undefined && 279 newBtn.parentConf.split !== undefined, 280 null, 281 newBtn.parentConf 282 ); 283 } 284 } 285 286 this._draw(button.collection, button.buttons); 287 }, 288 289 /** 290 * Get the container node for the buttons 291 * @return {jQuery} Buttons node 292 */ 293 container: function () { 294 return this.dom.container; 295 }, 296 297 /** 298 * Disable a button 299 * @param {node} node Button node 300 * @return {Buttons} Self for chaining 301 */ 302 disable: function (node) { 303 var button = this._nodeToButton(node); 304 305 $(button.node) 306 .addClass(this.c.dom.button.disabled) 307 .prop('disabled', true); 308 309 return this; 310 }, 311 312 /** 313 * Destroy the instance, cleaning up event handlers and removing DOM 314 * elements 315 * @return {Buttons} Self for chaining 316 */ 317 destroy: function () { 318 // Key event listener 319 $('body').off('keyup.' + this.s.namespace); 320 321 // Individual button destroy (so they can remove their own events if 322 // needed). Take a copy as the array is modified by `remove` 323 var buttons = this.s.buttons.slice(); 324 var i, ien; 325 326 for (i = 0, ien = buttons.length; i < ien; i++) { 327 this.remove(buttons[i].node); 328 } 329 330 // Container 331 this.dom.container.remove(); 332 333 // Remove from the settings object collection 334 var buttonInsts = this.s.dt.settings()[0]; 335 336 for (i = 0, ien = buttonInsts.length; i < ien; i++) { 337 if (buttonInsts.inst === this) { 338 buttonInsts.splice(i, 1); 339 break; 340 } 341 } 342 343 return this; 344 }, 345 346 /** 347 * Enable / disable a button 348 * @param {node} node Button node 349 * @param {boolean} [flag=true] Enable / disable flag 350 * @return {Buttons} Self for chaining 351 */ 352 enable: function (node, flag) { 353 if (flag === false) { 354 return this.disable(node); 355 } 356 357 var button = this._nodeToButton(node); 358 $(button.node) 359 .removeClass(this.c.dom.button.disabled) 360 .prop('disabled', false); 361 362 return this; 363 }, 364 365 /** 366 * Get a button's index 367 * 368 * This is internally recursive 369 * @param {element} node Button to get the index of 370 * @return {string} Button index 371 */ 372 index: function (node, nested, buttons) { 373 if (!nested) { 374 nested = ''; 375 buttons = this.s.buttons; 376 } 377 378 for (var i = 0, ien = buttons.length; i < ien; i++) { 379 var inner = buttons[i].buttons; 380 381 if (buttons[i].node === node) { 382 return nested + i; 383 } 384 385 if (inner && inner.length) { 386 var match = this.index(node, i + '-', inner); 387 388 if (match !== null) { 389 return match; 390 } 391 } 392 } 393 394 return null; 395 }, 396 397 /** 398 * Get the instance name for the button set selector 399 * @return {string} Instance name 400 */ 401 name: function () { 402 return this.c.name; 403 }, 404 405 /** 406 * Get a button's node of the buttons container if no button is given 407 * @param {node} [node] Button node 408 * @return {jQuery} Button element, or container 409 */ 410 node: function (node) { 411 if (!node) { 412 return this.dom.container; 413 } 414 415 var button = this._nodeToButton(node); 416 return $(button.node); 417 }, 418 419 /** 420 * Set / get a processing class on the selected button 421 * @param {element} node Triggering button node 422 * @param {boolean} flag true to add, false to remove, undefined to get 423 * @return {boolean|Buttons} Getter value or this if a setter. 424 */ 425 processing: function (node, flag) { 426 var dt = this.s.dt; 427 var button = this._nodeToButton(node); 428 429 if (flag === undefined) { 430 return $(button.node).hasClass('processing'); 431 } 432 433 $(button.node).toggleClass('processing', flag); 434 435 $(dt.table().node()).triggerHandler('buttons-processing.dt', [ 436 flag, 437 dt.button(node), 438 dt, 439 $(node), 440 button.conf 441 ]); 442 443 return this; 444 }, 445 446 /** 447 * Remove a button. 448 * @param {node} node Button node 449 * @return {Buttons} Self for chaining 450 */ 451 remove: function (node) { 452 var button = this._nodeToButton(node); 453 var host = this._nodeToHost(node); 454 var dt = this.s.dt; 455 456 // Remove any child buttons first 457 if (button.buttons.length) { 458 for (var i = button.buttons.length - 1; i >= 0; i--) { 459 this.remove(button.buttons[i].node); 460 } 461 } 462 463 button.conf.destroying = true; 464 465 // Allow the button to remove event handlers, etc 466 if (button.conf.destroy) { 467 button.conf.destroy.call(dt.button(node), dt, $(node), button.conf); 468 } 469 470 this._removeKey(button.conf); 471 472 $(button.node).remove(); 473 474 var idx = $.inArray(button, host); 475 host.splice(idx, 1); 476 477 return this; 478 }, 479 480 /** 481 * Get the text for a button 482 * @param {int|string}
482 node Button index 483 * @return {string} Button text 484 */ /** 485 * Set the text for a button 486 * @param {int|string|function} node Button index 487 * @param {string} label Text 488 * @return {Buttons} Self for chaining 489 */ 490 text: function (node, label) { 491 var button = this._nodeToButton(node); 492 var textNode = button.textNode; 493 var dt = this.s.dt; 494 var jqNode = $(button.node); 495 var text = function (opt) { 496 return typeof opt === 'function' 497 ? opt(dt, jqNode, button.conf) 498 : opt; 499 }; 500 501 if (label === undefined) { 502 return text(button.conf.text); 503 } 504 505 button.conf.text = label; 506 textNode.html(text(label)); 507 508 return this; 509 }, 510 511 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 512 * Constructor 513 */ 514 515 /** 516 * Buttons constructor 517 * @private 518 */ 519 _constructor: function () { 520 var that = this; 521 var dt = this.s.dt; 522 var dtSettings = dt.settings()[0]; 523 var buttons = this.c.buttons; 524 525 if (!dtSettings._buttons) { 526 dtSettings._buttons = []; 527 } 528 529 dtSettings._buttons.push({ 530 inst: this, 531 name: this.c.name 532 }); 533 534 for (var i = 0, ien = buttons.length; i < ien; i++) { 535 this.add(buttons[i]); 536 } 537 538 dt.on('destroy', function (e, settings) { 539 if (settings === dtSettings) { 540 that.destroy(); 541 } 542 }); 543 544 // Global key event binding to listen for button keys 545 $('body').on('keyup.' + this.s.namespace, function (e) { 546 if ( 547 !document.activeElement || 548 document.activeElement === document.body 549 ) { 550 // SUse a string of characters for fast lookup of if we need to 551 // handle this 552 var character = String.fromCharCode(e.keyCode).toLowerCase(); 553 554 if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) { 555 that._keypress(character, e); 556 } 557 } 558 }); 559 }, 560 561 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 562 * Private methods 563 */ 564 565 /** 566 * Add a new button to the key press listener 567 * @param {object} conf Resolved button configuration object 568 * @private 569 */ 570 _addKey: function (conf) { 571 if (conf.key) { 572 this.s.listenKeys += $.isPlainObject(conf.key) 573 ? conf.key.key 574 : conf.key; 575 } 576 }, 577 578 /** 579 * Insert the buttons into the container. Call without parameters! 580 * @param {node} [container] Recursive only - Insert point 581 * @param {array} [buttons] Recursive only - Buttons array 582 * @private 583 */ 584 _draw: function (container, buttons) { 585 if (!container) { 586 container = this.dom.container; 587 buttons = this.s.buttons; 588 } 589 590 container.children().detach(); 591 592 for (var i = 0, ien = buttons.length; i < ien; i++) { 593 container.append(buttons[i].inserter); 594 container.append(' '); 595 596 if (buttons[i].buttons && buttons[i].buttons.length) { 597 this._draw(buttons[i].collection, buttons[i].buttons); 598 } 599 } 600 }, 601 602 /** 603 * Create buttons from an array of buttons 604 * @param {array} attachTo Buttons array to attach to 605 * @param {object} button Button definition 606 * @param {boolean} inCollection true if the button is in a collection 607 * @private 608 */ 609 _expandButton: function ( 610 attachTo, 611 button, 612 split, 613 inCollection, 614 inSplit, 615 attachPoint, 616 parentConf 617 ) { 618 var dt = this.s.dt; 619 var isSplit = false; 620 var domCollection = this.c.dom.collection; 621 var buttons = !Array.isArray(button) ? [button] : button; 622 623 if (button === undefined) { 624 buttons = !Array.isArray(split) ? [split] : split; 625 } 626 627 for (var i = 0, ien = buttons.length; i < ien; i++) { 628 var conf = this._resolveExtends(buttons[i]); 629 630 if (!conf) { 631 continue; 632 } 633 634 isSplit = conf.config && conf.config.split ? true : false; 635 636 // If the configuration is an array, then expand the buttons at this 637 // point 638 if (Array.isArray(conf)) { 639 this._expandButton( 640 attachTo, 641 conf, 642 built !== undefined && built.conf !== undefined 643 ? built.conf.split 644 : undefined, 645 inCollection, 646 parentConf !== undefined && parentConf.split !== undefined, 647 attachPoint, 648 parentConf 649 ); 650 continue; 651 } 652 653 var built = this._buildButton( 654 conf, 655 inCollection, 656 conf.split !== undefined || 657 (conf.config !== undefined && 658 conf.config.split !== undefined), 659 inSplit 660 ); 661 if (!built) { 662 continue; 663 } 664 665 if (attachPoint !== undefined && attachPoint !== null) { 666 attachTo.splice(attachPoint, 0, built); 667 attachPoint++; 668 } 669 else { 670 attachTo.push(built); 671 } 672 673 // Create the dropdown for a collection 674 if (built.conf.buttons) { 675 built.collection = $( 676 '<' + domCollection.container.content.tag + '/>' 677 ); 678 built.conf._collection = built.collection; 679 680 $(built.node).append(domCollection.action.dropHtml); 681 682 this._expandButton( 683 built.buttons, 684 built.conf.buttons, 685 built.conf.split, 686 !isSplit, 687 isSplit, 688 attachPoint, 689 built.conf 690 ); 691 } 692 693 // And the split collection 694 if (built.conf.split) { 695 built.collection = $('<' + domCollection.container.tag + '/>'); 696 built.conf._collection = built.collection; 697 698 for (var j = 0; j < built.conf.split.length; j++) { 699 var item = built.conf.split[j]; 700 701 if (typeof item === 'object') { 702 item.parent = parentConf; 703 704 if (item.collectionLayout === undefined) { 705 item.collectionLayout = built.conf.collectionLayout; 706 } 707 708 if (item.dropup === undefined) { 709 item.dropup = built.conf.dropup; 710 } 711 712 if (item.fade === undefined) { 713 item.fade = built.conf.fade; 714 } 715 } 716 } 717 718 this._expandButton( 719 built.buttons, 720 built.conf.buttons, 721 built.conf.split, 722 !isSplit, 723 isSplit, 724 attachPoint, 725 built.conf 726 ); 727 } 728 729 built.conf.parent = parentConf; 730 731 // init call is made here, rather than buildButton as it needs to 732 // be selectable, and for that it needs to be in the buttons array 733 if (conf.init) { 734 conf.init.call(dt.button(built.node), dt, $(built.node), conf); 735 } 736 } 737 }, 738 739 /** 740 * Create an individual button 741 * @param {object} config Resolved button configuration 742 * @param {boolean} inCollection `true` if a collection button 743 * @return {object} Completed button description object 744 * @private 745 */ 746 _buildButton: function (config, inCollection, isSplit, inSplit) { 747 var that = this; 748 var configDom = this.c.dom; 749 var textNode; 750 var dt = this.s.dt; 751 var text = function (opt) { 752 return typeof opt === 'function' ? opt(dt, button, config) : opt; 753 }; 754 755 // Create an object that describes the button which can be in `dom.button`, or 756 // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`! 757 // Each should extend from `dom.button`. 758 var dom = $.extend(true, {}, configDom.button); 759 760 if (inCollection && isSplit && configDom.collection.split) { 761 $.extend(true, dom, configDom.collection.split.action); 762 } 763 else if (inSplit || inCollection) { 764 $.extend(true, dom, configDom.collection.button); 765 } 766 else if (isSplit) { 767 $.extend(true, dom, configDom.split.button); 768 } 769
770 // Spacers don't do much other than insert an element into the DOM 771 if (config.spacer) { 772 var spacer = $('<' + dom.spacer.tag + '/>') 773 .addClass( 774 'dt-button-spacer ' + 775 config.style + 776 ' ' + 777 dom.spacer.className 778 ) 779 .html(text(config.text)); 780 781 return { 782 conf: config, 783 node: spacer, 784 inserter: spacer, 785 buttons: [], 786 inCollection: inCollection, 787 isSplit: isSplit, 788 collection: null, 789 textNode: spacer 790 }; 791 } 792 793 // Make sure that the button is available based on whatever requirements 794 // it has. For example, PDF button require pdfmake 795 if ( 796 config.available && 797 !config.available(dt, config) && 798 !config.html 799 ) { 800 return false; 801 } 802 803 var button; 804 805 if (!config.html) { 806 var run = function (e, dt, button, config, done) { 807 config.action.call(dt.button(button), e, dt, button, config, done); 808 809 $(dt.table().node()).triggerHandler('buttons-action.dt', [ 810 dt.button(button), 811 dt, 812 button, 813 config 814 ]); 815 }; 816 817 var action = function(e, dt, button, config) { 818 if (config.async) { 819 that.processing(button[0], true); 820 821 setTimeout(function () { 822 run(e, dt, button, config, function () { 823 that.processing(button[0], false); 824 }); 825 }, config.async); 826 } 827 else { 828 run(e, dt, button, config, function () {}); 829 } 830 } 831 832 var tag = config.tag || dom.tag; 833 var clickBlurs = 834 config.clickBlurs === undefined ? true : config.clickBlurs; 835 836 button = $('<' + tag + '/>') 837 .addClass(dom.className) 838 .attr('tabindex', this.s.dt.settings()[0].iTabIndex) 839 .attr('aria-controls', this.s.dt.table().node().id) 840 .on('click.dtb', function (e) { 841 e.preventDefault(); 842 843 if (!button.hasClass(dom.disabled) && config.action) { 844 action(e, dt, button, config); 845 } 846 847 if (clickBlurs) { 848 button.trigger('blur'); 849 } 850 }) 851 .on('keypress.dtb', function (e) { 852 if (e.keyCode === 13) { 853 e.preventDefault(); 854 855 if (!button.hasClass(dom.disabled) && config.action) { 856 action(e, dt, button, config); 857 } 858 } 859 }); 860 861 // Make `a` tags act like a link 862 if (tag.toLowerCase() === 'a') { 863 button.attr('href', '#'); 864 } 865 866 // Button tags should have `type=button` so they don't have any default behaviour 867 if (tag.toLowerCase() === 'button') { 868 button.attr('type', 'button'); 869 } 870 871 if (dom.liner.tag) { 872 var liner = $('<' + dom.liner.tag + '/>') 873 .html(text(config.text)) 874 .addClass(dom.liner.className); 875 876 if (dom.liner.tag.toLowerCase() === 'a') { 877 liner.attr('href', '#'); 878 } 879 880 button.append(liner); 881 textNode = liner; 882 } 883 else { 884 button.html(text(config.text)); 885 textNode = button; 886 } 887 888 if (config.enabled === false) { 889 button.addClass(dom.disabled); 890 } 891 892 if (config.className) { 893 button.addClass(config.className); 894 } 895 896 if (config.titleAttr) { 897 button.attr('title', text(config.titleAttr)); 898 } 899 900 if (config.attr) { 901 button.attr(config.attr); 902 } 903 904 if (!config.namespace) { 905 config.namespace = '.dt-button-' + _buttonCounter++; 906 } 907 908 if (config.config !== undefined && config.config.split) { 909 config.split = config.config.split; 910 } 911 } 912 else { 913 button = $(config.html); 914 } 915 916 var buttonContainer = this.c.dom.buttonContainer; 917 var inserter; 918 if (buttonContainer && buttonContainer.tag) { 919 inserter = $('<' + buttonContainer.tag + '/>') 920 .addClass(buttonContainer.className) 921 .append(button); 922 } 923 else { 924 inserter = button; 925 } 926 927 this._addKey(config); 928 929 // Style integration callback for DOM manipulation 930 // Note that this is _not_ documented. It is currently 931 // for style integration only 932 if (this.c.buttonCreated) { 933 inserter = this.c.buttonCreated(config, inserter); 934 } 935 936 var splitDiv; 937 938 if (isSplit) { 939 var dropdownConf = inCollection 940 ? $.extend(true, this.c.dom.split, this.c.dom.collection.split) 941 : this.c.dom.split; 942 var wrapperConf = dropdownConf.wrapper; 943 944 splitDiv = $('<' + wrapperConf.tag + '/>') 945 .addClass(wrapperConf.className) 946 .append(button); 947 948 var dropButtonConfig = $.extend(config, { 949 align: dropdownConf.dropdown.align, 950 attr: { 951 'aria-haspopup': 'dialog', 952 'aria-expanded': false 953 }, 954 className: dropdownConf.dropdown.className,
955 closeButton: false, 956 splitAlignClass: dropdownConf.dropdown.splitAlignClass, 957 text: dropdownConf.dropdown.text 958 }); 959 960 this._addKey(dropButtonConfig); 961 962 var splitAction = function (e, dt, button, config) { 963 _dtButtons.split.action.call( 964 dt.button(splitDiv), 965 e, 966 dt, 967 button, 968 config 969 ); 970 971 $(dt.table().node()).triggerHandler('buttons-action.dt', [ 972 dt.button(button), 973 dt, 974 button, 975 config 976 ]); 977 button.attr('aria-expanded', true); 978 }; 979 980 var dropButton = $( 981 '<button class="' + 982 dropdownConf.dropdown.className + 983 ' dt-button"></button>' 984 ) 985 .html(dropdownConf.dropdown.dropHtml) 986 .on('click.dtb', function (e) { 987 e.preventDefault(); 988 e.stopPropagation(); 989 990 if (!dropButton.hasClass(dom.disabled)) { 991 splitAction(e, dt, dropButton, dropButtonConfig); 992 } 993 if (clickBlurs) { 994 dropButton.trigger('blur'); 995 } 996 }) 997 .on('keypress.dtb', function (e) { 998 if (e.keyCode === 13) { 999 e.preventDefault(); 1000 1001 if (!dropButton.hasClass(dom.disabled)) { 1002 splitAction(e, dt, dropButton, dropButtonConfig); 1003 } 1004 } 1005 }); 1006 1007 if (config.split.length === 0) { 1008 dropButton.addClass('dtb-hide-drop'); 1009 } 1010 1011 splitDiv.append(dropButton).attr(dropButtonConfig.attr); 1012 } 1013 1014 return { 1015 conf: config, 1016 node: isSplit ? splitDiv.get(0) : button.get(0), 1017 inserter: isSplit ? splitDiv : inserter, 1018 buttons: [], 1019 inCollection: inCollection, 1020 isSplit: isSplit, 1021 inSplit: inSplit, 1022 collection: null, 1023 textNode: textNode 1024 }; 1025 }, 1026 1027 /** 1028 * Get the button object from a node (recursive) 1029 * @param {node} node Button node 1030 * @param {array} [buttons] Button array, uses base if not defined 1031 * @return {object} Button object 1032 * @private 1033 */ 1034 _nodeToButton: function (node, buttons) { 1035 if (!buttons) { 1036 buttons = this.s.buttons; 1037 } 1038 1039 for (var i = 0, ien = buttons.length; i < ien; i++) { 1040 if (buttons[i].node === node) { 1041 return buttons[i]; 1042 } 1043 1044 if (buttons[i].buttons.length) { 1045 var ret = this._nodeToButton(node, buttons[i].buttons); 1046 1047 if (ret) { 1048 return ret; 1049 } 1050 } 1051 } 1052 }, 1053 1054 /** 1055 * Get container array for a button from a button node (recursive) 1056 * @param {node} node Button node 1057 * @param {array} [buttons] Button array, uses base if not defined 1058 * @return {array} Button's host array 1059 * @private 1060 */ 1061 _nodeToHost: function (node, buttons) { 1062 if (!buttons) { 1063 buttons = this.s.buttons; 1064 } 1065 1066 for (var i = 0, ien = buttons.length; i < ien; i++) { 1067 if (buttons[i].node === node) { 1068 return buttons; 1069 } 1070 1071 if (buttons[i].buttons.length) { 1072 var ret = this._nodeToHost(node, buttons[i].buttons); 1073 1074 if (ret) { 1075 return ret; 1076 } 1077 } 1078 } 1079 }, 1080 1081 /** 1082 * Handle a key press - determine if any button's key configured matches 1083 * what was typed and trigger the action if so. 1084 * @param {string} character The character pressed 1085 * @param {object} e Key event that triggered this call 1086 * @private 1087 */ 1088 _keypress: function (character, e) { 1089 // Check if this button press already activated on another instance of Buttons 1090 if (e._buttonsHandled) { 1091 return; 1092 } 1093 1094 var run = function (conf, node) { 1095 if (!conf.key) { 1096 return; 1097 } 1098 1099 if (conf.key === character) { 1100 e._buttonsHandled = true; 1101 $(node).click(); 1102 } 1103 else if ($.isPlainObject(conf.key)) { 1104 if (conf.key.key !== character) { 1105 return; 1106 } 1107 1108 if (conf.key.shiftKey && !e.shiftKey) { 1109 return; 1110 } 1111 1112 if (conf.key.altKey && !e.altKey) { 1113 return; 1114 } 1115 1116 if (conf.key.ctrlKey && !e.ctrlKey) { 1117 return; 1118 } 1119 1120 if (conf.key.metaKey && !e.metaKey) { 1121 return; 1122 } 1123 1124 // Made it this far - it is good 1125 e._buttonsHandled = true; 1126 $(node).click(); 1127 } 1128 }; 1129 1130 var recurse = function (a) { 1131 for (var i = 0, ien = a.length; i < ien; i++) { 1132 run(a[i].conf, a[i].node); 1133 1134 if (a[i].buttons.length) { 1135 recurse(a[i].buttons); 1136 } 1137 } 1138 }; 1139 1140 recurse(this.s.buttons); 1141 }, 1142 1143 /** 1144 * Remove a key from the key listener for this instance (to be used when a 1145 * button is removed) 1146 * @param {object} conf Button configuration 1147 * @private 1148 */ 1149 _removeKey: function (conf) { 1150 if (conf.key) { 1151 var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key; 1152 1153 // Remove only one character, as multiple buttons could have the 1154 // same listening key 1155 var a = this.s.listenKeys.split(''); 1156 var idx = $.inArray(character, a); 1157 a.splice(idx, 1); 1158 this.s.listenKeys = a.join(''); 1159 } 1160 }, 1161 1162 /** 1163 * Resolve a button configuration 1164 * @param {string|function|object} conf Button config to resolve 1165 * @return {object} Button configuration 1166 * @private 1167 */ 1168 _resolveExtends: function (conf) { 1169 var that = this; 1170 var dt = this.s.dt; 1171 var i, ien; 1172 var toConfObject = function (base) { 1173 var loop = 0; 1174 1175 // Loop until we have resolved to a button configuration, or an 1176 // array of button configurations (which will be iterated 1177 // separately) 1178 while (!$.isPlainObject(base) && !Array.isArray(base)) { 1179 if (base === undefined) { 1180 return; 1181 } 1182 1183 if (typeof base === 'function') { 1184 base = base.call(that, dt, conf); 1185 1186 if (!base) { 1187 return false; 1188 } 1189 } 1190 else if (typeof base === 'string') { 1191 if (!_dtButtons[base]) { 1192 return { html: base }; 1193 } 1194 1195 base = _dtButtons[base]; 1196 } 1197 1198 loop++; 1199 if (loop > 30) { 1200 // Protect against misconfiguration killing the browser 1201 throw 'Buttons: Too many iterations'; 1202 } 1203 } 1204 1205 return Array.isArray(base) ? base : $.extend({}, base); 1206 }; 1207 1208 conf = toConfObject(conf); 1209 1210 while (conf && conf.extend) { 1211 // Use `toConfObject` in case the button definition being extended 1212 // is itself a string or a function 1213 if (!_dtButtons[conf.extend]) { 1214 throw 'Cannot extend unknown button type: ' + conf.extend; 1215 } 1216 1217 var objArray = toConfObject(_dtButtons[conf.extend]); 1218 if (Array.isArray(objArray)) { 1219 return objArray; 1220 } 1221 else if (!objArray) { 1222 // This is a little brutal as it might be possible to have a 1223 // valid button without the extend, but if there is no extend 1224 // then the host button would be acting in an undefined state 1225 return false; 1226 } 1227 1228 // Stash the current class name 1229 var originalClassName = objArray.className; 1230 1231 if (conf.config !== undefined && objArray.config !== undefined) { 1232 conf.config = $.extend({}, objArray.config, conf.config); 1233 } 1234 1235 conf = $.extend({}, objArray, conf); 1236 1237 // The extend will have overwritten the original class name if the 1238 // `conf` object also assigned a class, but we want to concatenate 1239 // them so they are list that is combined from all extended buttons 1240 if (originalClassName && conf.className !== originalClassName) { 1241 conf.className = originalClassName + ' ' + conf.className; 1242 } 1243 1244 // Although we want the `conf` object to overwrite almost all of 1245 // the properties of the object being extended, the `extend` 1246 // property should come from the object being extended 1247 conf.extend = objArray.extend; 1248 } 1249 1250 // Buttons to be added to a collection -gives the ability to define 1251 // if buttons should be added to the start or end of a collection 1252 var postfixButtons = conf.postfixButtons; 1253 if (postfixButtons) { 1254 if (!conf.buttons) { 1255 conf.buttons = []; 1256 } 1257 1258 for (i = 0, ien = postfixButtons.length; i < ien; i++) { 1259 conf.buttons.push(postfixButtons[i]); 1260 } 1261 } 1262 1263 var prefixButtons = conf.prefixButtons; 1264 if (prefixButtons) { 1265 if (!conf.buttons) { 1266 conf.buttons = []; 1267 } 1268 1269 for (i = 0, ien = prefixButtons.length; i < ien; i++) { 1270 conf.buttons.splice(i, 0, prefixButtons[i]); 1271 } 1272 } 1273 1274 return conf; 1275 }, 1276 1277 /** 1278 * Display (and replace if there is an existing one) a popover attached to a button 1279 * @param {string|node} content Content to show 1280 * @param {DataTable.Api} hostButton DT API instance of the button 1281 * @param {object} inOpts Options (see object below for all options) 1282 */ 1283 _popover: function (content, hostButton, inOpts) { 1284 var dt = hostButton; 1285 var c = this.c; 1286 var closed = false; 1287 var options = $.extend( 1288 { 1289 align: 'button-left', // button-right, dt-container, split-left, split-right 1290 autoClose: false, 1291 background: true, 1292 backgroundClassName: 'dt-button-background', 1293 closeButton: true, 1294 containerClassName: c.dom.collection.container.className, 1295 contentClassName: c.dom.collection.container.content.className, 1296 collectionLayout: '', 1297 collectionTitle: '', 1298 dropup: false, 1299 fade: 400, 1300 popoverTitle: '', 1301 rightAlignClassName: 'dt-button-right', 1302 tag: c.dom.collection.container.tag 1303 }, 1304 inOpts 1305 ); 1306 1307 var containerSelector = 1308 options.tag + '.' + options.containerClassName.replace(/ /g, '.'); 1309 var hostNode = hostButton.node(); 1310 1311 var close = function () { 1312 closed = true; 1313 1314 _fadeOut($(containerSelector), options.fade, function () { 1315 $(this).detach(); 1316 }); 1317 1318 $( 1319 dt 1320 .buttons('[aria-haspopup="dialog"][aria-expanded="true"]') 1321 .nodes() 1322 ).attr('aria-expanded', 'false'); 1323 1324 $('div.dt-button-background').off('click.dtb-collection'); 1325 Buttons.background( 1326 false, 1327 options.backgroundClassName, 1328 options.fade, 1329 hostNode 1330 ); 1331 1332 $(window).off('resize.resize.dtb-collection'); 1333 $('body').off('.dtb-collection'); 1334 dt.off('buttons-action.b-internal'); 1335 dt.off('destroy'); 1336 }; 1337 1338 if (content === false) { 1339 close(); 1340 return; 1341 } 1342 1343 var existingExpanded = $( 1344 dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes() 1345 ); 1346 if (existingExpanded.length) { 1347 // Reuse the current position if the button that was triggered is inside an existing collection 1348 if (hostNode.closest(containerSelector).length) { 1349 hostNode = existingExpanded.eq(0); 1350 } 1351 1352 close(); 1353 } 1354 1355 // Try to be smart about the layout 1356 var cnt = $('.dt-button', content).length; 1357 var mod = ''; 1358 1359 if (cnt === 3) { 1360 mod = 'dtb-b3'; 1361 } 1362 else if (cnt === 2) { 1363 mod = 'dtb-b2'; 1364 } 1365 else if (cnt === 1) { 1366 mod = 'dtb-b1'; 1367 } 1368 1369 var display = $('<' + options.tag + '/>') 1370 .addClass(options.containerClassName) 1371 .addClass(options.collectionLayout) 1372 .addClass(options.splitAlignClass) 1373 .addClass(mod) 1374 .css('display', 'none') 1375 .attr({
1376 'aria-modal': true, 1377 role: 'dialog' 1378 }); 1379 1380 content = $(content) 1381 .addClass(options.contentClassName) 1382 .attr('role', 'menu') 1383 .appendTo(display); 1384 1385 hostNode.attr('aria-expanded', 'true'); 1386 1387 if (hostNode.parents('body')[0] !== document.body) { 1388 hostNode = document.body.lastChild; 1389 } 1390 1391 if (options.popoverTitle) { 1392 display.prepend( 1393 '<div class="dt-button-collection-title">' + 1394 options.popoverTitle + 1395 '</div>' 1396 ); 1397 } 1398 else if (options.collectionTitle) { 1399 display.prepend( 1400 '<div class="dt-button-collection-title">' + 1401 options.collectionTitle + 1402 '</div>' 1403 ); 1404 } 1405 1406 if (options.closeButton) { 1407 display 1408 .prepend('<div class="dtb-popover-close">×</div>') 1409 .addClass('dtb-collection-closeable'); 1410 } 1411 1412 _fadeIn(display.insertAfter(hostNode), options.fade); 1413 1414 var tableContainer = $(hostButton.table().container()); 1415 var position = display.css('position'); 1416 1417 if (options.span === 'container' || options.align === 'dt-container') { 1418 hostNode = hostNode.parent(); 1419 display.css('width', tableContainer.width()); 1420 } 1421 1422 // Align the popover relative to the DataTables container 1423 // Useful for wide popovers such as SearchPanes 1424 if (position === 'absolute') { 1425 // Align relative to the host button 1426 var offsetParent = $(hostNode[0].offsetParent); 1427 var buttonPosition = hostNode.position(); 1428 var buttonOffset = hostNode.offset(); 1429 var tableSizes = offsetParent.offset(); 1430 var containerPosition = offsetParent.position(); 1431 var computed = window.getComputedStyle(offsetParent[0]); 1432 1433 tableSizes.height = offsetParent.outerHeight(); 1434 tableSizes.width = 1435 offsetParent.width() + parseFloat(computed.paddingLeft); 1436 tableSizes.right = tableSizes.left + tableSizes.width; 1437 tableSizes.bottom = tableSizes.top + tableSizes.height; 1438 1439 // Set the initial position so we can read height / width 1440 var top = buttonPosition.top + hostNode.outerHeight(); 1441 var left = buttonPosition.left; 1442 1443 display.css({ 1444 top: top, 1445 left: left 1446 }); 1447 1448 // Get the popover position 1449 computed = window.getComputedStyle(display[0]); 1450 var popoverSizes = display.offset(); 1451 1452 popoverSizes.height = display.outerHeight(); 1453 popoverSizes.width = display.outerWidth(); 1454 popoverSizes.right = popoverSizes.left + popoverSizes.width; 1455 popoverSizes.bottom = popoverSizes.top + popoverSizes.height; 1456 popoverSizes.marginTop = parseFloat(computed.marginTop); 1457 popoverSizes.marginBottom = parseFloat(computed.marginBottom); 1458 1459 // First position per the class requirements - pop up and right align 1460 if (options.dropup) { 1461 top = 1462 buttonPosition.top - 1463 popoverSizes.height - 1464 popoverSizes.marginTop - 1465 popoverSizes.marginBottom; 1466 } 1467 1468 if ( 1469 options.align === 'button-right' || 1470 display.hasClass(options.rightAlignClassName) 1471 ) { 1472 left = 1473 buttonPosition.left - 1474 popoverSizes.width + 1475 hostNode.outerWidth(); 1476 } 1477 1478 // Container alignment - make sure it doesn't overflow the table container 1479 if ( 1480 options.align === 'dt-container' || 1481 options.align === 'container' 1482 ) { 1483 if (left < buttonPosition.left) { 1484 left = -buttonPosition.left; 1485 } 1486 } 1487 1488 // Window adjustment 1489 if ( 1490 containerPosition.left + left + popoverSizes.width > 1491 $(window).width() 1492 ) { 1493 // Overflowing the document to the right 1494 left = 1495 $(window).width() - 1496 popoverSizes.width - 1497 containerPosition.left; 1498 } 1499 1500 if (buttonOffset.left + left < 0) { 1501 // Off to the left of the document 1502 left = -buttonOffset.left; 1503 } 1504 1505 if ( 1506 containerPosition.top + top + popoverSizes.height > 1507 $(window).height() + $(window).scrollTop() 1508 ) { 1509 // Pop up if otherwise we'd need the user to scroll down 1510 top = 1511 buttonPosition.top - 1512 popoverSizes.height - 1513 popoverSizes.marginTop - 1514 popoverSizes.marginBottom; 1515 } 1516 1517 if (containerPosition.top + top < $(window).scrollTop()) { 1518 // Correction for when the top is beyond the top of the page 1519 top = buttonPosition.top + hostNode.outerHeight(); 1520 } 1521 1522 // Calculations all done - now set it 1523 display.css({ 1524 top: top, 1525 left: left 1526 }); 1527 } 1528 else { 1529 // Fix position - centre on screen 1530 var place = function () { 1531 var half = $(window).height() / 2; 1532 1533 var top = display.height() / 2; 1534 if (top > half) { 1535 top = half; 1536 } 1537 1538 display.css('marginTop', top * -1); 1539 }; 1540 1541 place(); 1542 1543 $(window).on('resize.dtb-collection', function () { 1544 place(); 1545 }); 1546 } 1547 1548 if (options.background) { 1549 Buttons.background( 1550 true, 1551 options.backgroundClassName, 1552 options.fade, 1553 options.backgroundHost || hostNode 1554 ); 1555 } 1556 1557 // This is bonkers, but if we don't have a click listener on the 1558 // background element, iOS Safari will ignore the body click 1559 // listener below. An empty function here is all that is 1560 // required to make it work... 1561 $('div.dt-button-background').on( 1562 'click.dtb-collection', 1563 function () {} 1564 ); 1565 1566 if (options.autoClose) { 1567 setTimeout(function () { 1568 dt.on('buttons-action.b-internal', function (e, btn, dt, node) { 1569 if (node[0] === hostNode[0]) { 1570 return; 1571 } 1572 close(); 1573 }); 1574 }, 0); 1575 } 1576 1577 $(display).trigger('buttons-popover.dt'); 1578 1579 dt.on('destroy', close); 1580 1581 setTimeout(function () { 1582 closed = false; 1583 $('body') 1584 .on('click.dtb-collection', function (e) { 1585 if (closed) { 1586 return; 1587 } 1588
1589 // andSelf is deprecated in jQ1.8, but we want 1.7 compat 1590 var back = $.fn.addBack ? 'addBack' : 'andSelf'; 1591 var parent = $(e.target).parent()[0]; 1592 1593 if ( 1594 (!$(e.target).parents()[back]().filter(content) 1595 .length && 1596 !$(parent).hasClass('dt-buttons')) || 1597 $(e.target).hasClass('dt-button-background') 1598 ) { 1599 close(); 1600 } 1601 }) 1602 .on('keyup.dtb-collection', function (e) { 1603 if (e.keyCode === 27) { 1604 close(); 1605 } 1606 }) 1607 .on('keydown.dtb-collection', function (e) { 1608 // Focus trap for tab key 1609 var elements = $('a, button', content); 1610 var active = document.activeElement; 1611 1612 if (e.keyCode !== 9) { 1613 // tab 1614 return; 1615 } 1616 1617 if (elements.index(active) === -1) { 1618 // If current focus is not inside the popover 1619 elements.first().focus(); 1620 e.preventDefault(); 1621 } 1622 else if (e.shiftKey) { 1623 // Reverse tabbing order when shift key is pressed 1624 if (active === elements[0]) { 1625 elements.last().focus(); 1626 e.preventDefault(); 1627 } 1628 } 1629 else { 1630 if (active === elements.last()[0]) { 1631 elements.first().focus(); 1632 e.preventDefault(); 1633 } 1634 } 1635 }); 1636 }, 0); 1637 } 1638}); 1639 1640/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1641 * Statics 1642 */ 1643 1644/** 1645 * Show / hide a background layer behind a collection 1646 * @param {boolean} Flag to indicate if the background should be shown or 1647 * hidden 1648 * @param {string} Class to assign to the background 1649 * @static 1650 */ 1651Buttons.background = function (show, className, fade, insertPoint) { 1652 if (fade === undefined) { 1653 fade = 400; 1654 } 1655 if (!insertPoint) { 1656 insertPoint = document.body; 1657 } 1658 1659 if (show) { 1660 _fadeIn( 1661 $('<div/>') 1662 .addClass(className) 1663 .css('display', 'none') 1664 .insertAfter(insertPoint), 1665 fade 1666 ); 1667 } 1668 else { 1669 _fadeOut($('div.' + className), fade, function () { 1670 $(this).removeClass(className).remove(); 1671 }); 1672 } 1673}; 1674 1675/** 1676 * Instance selector - select Buttons instances based on an instance selector 1677 * value from the buttons assigned to a DataTable. This is only useful if 1678 * multiple instances are attached to a DataTable. 1679 * @param {string|int|array} Instance selector - see `instance-selector` 1680 * documentation on the DataTables site 1681 * @param {array} Button instance array that was attached to the DataTables 1682 * settings object 1683 * @return {array} Buttons instances 1684 * @static 1685 */ 1686Buttons.instanceSelector = function (group, buttons) { 1687 if (group === undefined || group === null) { 1688 return $.map(buttons, function (v) { 1689 return v.inst; 1690 }); 1691 } 1692 1693 var ret = []; 1694 var names = $.map(buttons, function (v) { 1695 return v.name; 1696 }); 1697 1698 // Flatten the group selector into an array of single options 1699 var process = function (input) { 1700 if (Array.isArray(input)) { 1701 for (var i = 0, ien = input.length; i < ien; i++) { 1702 process(input[i]); 1703 } 1704 return; 1705 } 1706 1707 if (typeof input === 'string') { 1708 if (input.indexOf(',') !== -1) { 1709 // String selector, list of names 1710 process(input.split(',')); 1711 } 1712 else { 1713 // String selector individual name 1714 var idx = $.inArray(input.trim(), names); 1715 1716 if (idx !== -1) { 1717 ret.push(buttons[idx].inst); 1718 } 1719 } 1720 } 1721 else if (typeof input === 'number') { 1722 // Index selector 1723 ret.push(buttons[input].inst); 1724 } 1725 else if (typeof input === 'object' && input.nodeName) { 1726 // Element selector 1727 for (var j = 0; j < buttons.length; j++) { 1728 if (buttons[j].inst.dom.container[0] === input) { 1729 ret.push(buttons[j].inst); 1730 } 1731 } 1732 } 1733 else if (typeof input === 'object') { 1734 // Actual instance selector 1735 ret.push(input); 1736 } 1737 }; 1738 1739 process(group); 1740 1741 return ret; 1742}; 1743 1744/** 1745 * Button selector - select one or more buttons from a selector input so some 1746 * operation can be performed on them. 1747 * @param {array} Button instances array that the selector should operate on 1748 * @param {string|int|node|jQuery|array} Button selector - see 1749 * `button-selector` documentation on the DataTables site 1750 * @return {array} Array of objects containing `inst` and `idx` properties of
1751 * the selected buttons so you know which instance each button belongs to. 1752 * @static 1753 */ 1754Buttons.buttonSelector = function (insts, selector) { 1755 var ret = []; 1756 var nodeBuilder = function (a, buttons, baseIdx) { 1757 var button; 1758 var idx; 1759 1760 for (var i = 0, ien = buttons.length; i < ien; i++) { 1761 button = buttons[i]; 1762 1763 if (button) { 1764 idx = baseIdx !== undefined ? baseIdx + i : i + ''; 1765 1766 a.push({ 1767 node: button.node, 1768 name: button.conf.name, 1769 idx: idx 1770 }); 1771 1772 if (button.buttons) { 1773 nodeBuilder(a, button.buttons, idx + '-'); 1774 } 1775 } 1776 } 1777 }; 1778 1779 var run = function (selector, inst) { 1780 var i, ien; 1781 var buttons = []; 1782 nodeBuilder(buttons, inst.s.buttons); 1783 1784 var nodes = $.map(buttons, function (v) { 1785 return v.node; 1786 }); 1787 1788 if (Array.isArray(selector) || selector instanceof $) { 1789 for (i = 0, ien = selector.length; i < ien; i++) { 1790 run(selector[i], inst); 1791 } 1792 return; 1793 } 1794 1795 if (selector === null || selector === undefined || selector === '*') { 1796 // Select all 1797 for (i = 0, ien = buttons.length; i < ien; i++) { 1798 ret.push({ 1799 inst: inst, 1800 node: buttons[i].node 1801 }); 1802 } 1803 } 1804 else if (typeof selector === 'number') { 1805 // Main button index selector 1806 if (inst.s.buttons[selector]) { 1807 ret.push({ 1808 inst: inst, 1809 node: inst.s.buttons[selector].node 1810 }); 1811 } 1812 } 1813 else if (typeof selector === 'string') { 1814 if (selector.indexOf(',') !== -1) { 1815 // Split 1816 var a = selector.split(','); 1817 1818 for (i = 0, ien = a.length; i < ien; i++) { 1819 run(a[i].trim(), inst); 1820 } 1821 } 1822 else if (selector.match(/^\d+(\-\d+)*$/)) { 1823 // Sub-button index selector 1824 var indexes = $.map(buttons, function (v) { 1825 return v.idx; 1826 }); 1827 1828 ret.push({ 1829 inst: inst, 1830 node: buttons[$.inArray(selector, indexes)].node 1831 }); 1832 } 1833 else if (selector.indexOf(':name') !== -1) { 1834 // Button name selector 1835 var name = selector.replace(':name', ''); 1836 1837 for (i = 0, ien = buttons.length; i < ien; i++) { 1838 if (buttons[i].name === name) { 1839 ret.push({ 1840 inst: inst, 1841 node: buttons[i].node 1842 }); 1843 } 1844 } 1845 } 1846 else { 1847 // jQuery selector on the nodes 1848 $(nodes) 1849 .filter(selector) 1850 .each(function () { 1851 ret.push({ 1852 inst: inst, 1853 node: this 1854 }); 1855 }); 1856 } 1857 } 1858 else if (typeof selector === 'object' && selector.nodeName) { 1859 // Node selector 1860 var idx = $.inArray(selector, nodes); 1861 1862 if (idx !== -1) { 1863 ret.push({ 1864 inst: inst, 1865 node: nodes[idx] 1866 }); 1867 } 1868 } 1869 }; 1870 1871 for (var i = 0, ien = insts.length; i < ien; i++) { 1872 var inst = insts[i]; 1873 1874 run(selector, inst); 1875 } 1876 1877 return ret; 1878}; 1879 1880/** 1881 * Default function used for formatting output data. 1882 * @param {*} str Data to strip 1883 */ 1884Buttons.stripData = function (str, config) { 1885 if (typeof str !== 'string') { 1886 return str; 1887 } 1888 1889 // Always remove script tags 1890 str = Buttons.stripHtmlScript(str); 1891 1892 // Always remove comments 1893 str = Buttons.stripHtmlComments(str); 1894 1895 if (!config || config.stripHtml) { 1896 str = DataTable.util.stripHtml(str); 1897 } 1898 1899 if (!config || config.trim) { 1900 str = str.trim(); 1901 } 1902 1903 if (!config || config.stripNewlines) { 1904 str = str.replace(/\n/g, ' '); 1905 } 1906 1907 if (!config || config.decodeEntities) { 1908 if (_entityDecoder) { 1909 str = _entityDecoder(str); 1910 } 1911 else { 1912 _exportTextarea.innerHTML = str; 1913 str = _exportTextarea.value; 1914 } 1915 } 1916 1917 return str; 1918}; 1919 1920/** 1921 * Provide a custom entity decoding function - e.g. a regex one, which can be 1922 * much faster than the built in DOM option, but also larger code size. 1923 * @param {function} fn 1924 */ 1925Buttons.entityDecoder = function (fn) { 1926 _entityDecoder = fn; 1927}; 1928 1929/** 1930 * Common function for stripping HTML comments 1931 * 1932 * @param {*} input 1933 * @returns 1934 */ 1935Buttons.stripHtmlComments = function (input) { 1936 var previous; 1937 1938 do { 1939 previous = input; 1940 input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, ''); 1941 } while (input !== previous); 1942 1943 return input; 1944}; 1945 1946/** 1947 * Common function for stripping HTML script tags 1948 * 1949 * @param {*} input 1950 * @returns 1951 */ 1952Buttons.stripHtmlScript = function (input) { 1953 var previous; 1954 1955 do { 1956 previous = input; 1957 input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, ''); 1958 } while (input !== previous); 1959 1960 return input; 1961}; 1962 1963/** 1964 * Buttons defaults. For full documentation, please refer to the docs/option 1965 * directory or the DataTables site. 1966 * @type {Object} 1967 * @static 1968 */ 1969Buttons.defaults = { 1970 buttons: ['copy', 'excel', 'csv', 'pdf', 'print'], 1971 name: 'main', 1972 tabIndex: 0, 1973 dom: { 1974 container: { 1975 tag: 'div', 1976 className: 'dt-buttons' 1977 }, 1978 collection: { 1979 action: { 1980 // action button 1981 dropHtml: '<span class="dt-button-down-arrow">▼</span>' 1982 }, 1983 container: { 1984 // The element used for the dropdown 1985 className: 'dt-button-collection', 1986 content: { 1987 className: '', 1988 tag: 'div' 1989 }, 1990 tag: 'div' 1991 } 1992 // optionally 1993 // , button: IButton - buttons inside the collection container 1994 // , split: ISplit - splits inside the collection container 1995 }, 1996 button: { 1997 tag: 'button', 1998 className: 'dt-button',
1999 active: 'dt-button-active', // class name 2000 disabled: 'disabled', // class name 2001 spacer: { 2002 className: 'dt-button-spacer', 2003 tag: 'span' 2004 }, 2005 liner: { 2006 tag: 'span', 2007 className: '' 2008 } 2009 }, 2010 split: { 2011 action: { 2012 // action button 2013 className: 'dt-button-split-drop-button dt-button', 2014 tag: 'button' 2015 }, 2016 dropdown: { 2017 // button to trigger the dropdown 2018 align: 'split-right', 2019 className: 'dt-button-split-drop', 2020 dropHtml: '<span class="dt-button-down-arrow">▼</span>', 2021 splitAlignClass: 'dt-button-split-left', 2022 tag: 'button' 2023 }, 2024 wrapper: { 2025 // wrap around both 2026 className: 'dt-button-split', 2027 tag: 'div' 2028 } 2029 } 2030 } 2031}; 2032 2033/** 2034 * Version information 2035 * @type {string} 2036 * @static 2037 */ 2038Buttons.version = '3.0.2'; 2039 2040$.extend(_dtButtons, { 2041 collection: { 2042 text: function (dt) { 2043 return dt.i18n('buttons.collection', 'Collection'); 2044 }, 2045 className: 'buttons-collection', 2046 closeButton: false, 2047 init: function (dt, button) { 2048 button.attr('aria-expanded', false); 2049 }, 2050 action: function (e, dt, button, config) { 2051 if (config._collection.parents('body').length) { 2052 this.popover(false, config); 2053 } 2054 else { 2055 this.popover(config._collection, config); 2056 } 2057 2058 // When activated using a key - auto focus on the 2059 // first item in the popover 2060 if (e.type === 'keypress') { 2061 $('a, button', config._collection).eq(0).focus(); 2062 } 2063 }, 2064 attr: { 2065 'aria-haspopup': 'dialog' 2066 } 2067 // Also the popover options, defined in Buttons.popover 2068 }, 2069 split: { 2070 text: function (dt) { 2071 return dt.i18n('buttons.split', 'Split'); 2072 }, 2073 className: 'buttons-split', 2074 closeButton: false, 2075 init: function (dt, button) { 2076 return button.attr('aria-expanded', false); 2077 }, 2078 action: function (e, dt, button, config) { 2079 this.popover(config._collection, config); 2080 }, 2081 attr: { 2082 'aria-haspopup': 'dialog' 2083 } 2084 // Also the popover options, defined in Buttons.popover 2085 }, 2086 copy: function () { 2087 if (_dtButtons.copyHtml5) { 2088 return 'copyHtml5'; 2089 } 2090 }, 2091 csv: function (dt, conf) { 2092 if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) { 2093 return 'csvHtml5'; 2094 } 2095 }, 2096 excel: function (dt, conf) { 2097 if ( 2098 _dtButtons.excelHtml5 && 2099 _dtButtons.excelHtml5.available(dt, conf) 2100 ) { 2101 return 'excelHtml5'; 2102 } 2103 }, 2104 pdf: function (dt, conf) { 2105 if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) { 2106 return 'pdfHtml5'; 2107 } 2108 }, 2109 pageLength: function (dt) { 2110 var lengthMenu = dt.settings()[0].aLengthMenu; 2111 var vals = []; 2112 var lang = []; 2113 var text = function (dt) { 2114 return dt.i18n( 2115 'buttons.pageLength', 2116 { 2117 '-1': 'Show all rows', 2118 _: 'Show %d rows' 2119 }, 2120 dt.page.len() 2121 ); 2122 }; 2123 2124 // Support for DataTables 1.x 2D array 2125 if (Array.isArray(lengthMenu[0])) { 2126 vals = lengthMenu[0]; 2127 lang = lengthMenu[1]; 2128 } 2129 else { 2130 for (var i = 0; i < lengthMenu.length; i++) { 2131 var option = lengthMenu[i]; 2132 2133 // Support for DataTables 2 object in the array 2134 if ($.isPlainObject(option)) { 2135 vals.push(option.value); 2136 lang.push(option.label); 2137 } 2138 else { 2139 vals.push(option); 2140 lang.push(option); 2141 } 2142 } 2143 } 2144 2145 return { 2146 extend: 'collection', 2147 text: text, 2148 className: 'buttons-page-length', 2149 autoClose: true, 2150 buttons: $.map(vals, function (val, i) { 2151 return { 2152 text: lang[i], 2153 className: 'button-page-length', 2154 action: function (e, dt) { 2155 dt.page.len(val).draw(); 2156 }, 2157 init: function (dt, node, conf) { 2158 var that = this; 2159 var fn = function () { 2160 that.active(dt.page.len() === val); 2161 }; 2162 2163 dt.on('length.dt' + conf.namespace, fn); 2164 fn(); 2165 }, 2166 destroy: function (dt, node, conf) { 2167 dt.off('length.dt' + conf.namespace); 2168 } 2169 }; 2170 }), 2171 init: function (dt, node, conf) { 2172 var that = this; 2173 dt.on('length.dt' + conf.namespace, function () { 2174 that.text(conf.text); 2175 }); 2176 }, 2177 destroy: function (dt, node, conf) { 2178 dt.off('length.dt' + conf.namespace); 2179 } 2180 }; 2181 }, 2182 spacer: { 2183 style: 'empty', 2184 spacer: true, 2185 text: function (dt) { 2186 return dt.i18n('buttons.spacer', ''); 2187 } 2188 } 2189}); 2190 2191/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2192 * DataTables API 2193 * 2194 * For complete documentation, please refer to the docs/api directory or the 2195 * DataTables site 2196 */ 2197 2198// Buttons group and individual button selector 2199DataTable.Api.register('buttons()', function (group, selector) { 2200 // Argument shifting 2201 if (selector === undefined) { 2202 selector = group; 2203 group = undefined; 2204 } 2205 2206 this.selector.buttonGroup = group; 2207 2208 var res = this.iterator( 2209 true, 2210 'table', 2211 function (ctx) { 2212 if (ctx._buttons) { 2213 return Buttons.buttonSelector( 2214 Buttons.instanceSelector(group, ctx._buttons), 2215 selector 2216 ); 2217 } 2218 }, 2219 true 2220 ); 2221 2222 res._groupSelector = group; 2223 return res; 2224}); 2225 2226// Individual button selector 2227DataTable.Api.register('button()', function (group, selector) { 2228 // just run buttons() and truncate 2229 var buttons = this.buttons(group, selector); 2230 2231 if (buttons.length > 1) { 2232 buttons.splice(1, buttons.length); 2233 } 2234 2235 return buttons; 2236}); 2237 2238// Active buttons 2239DataTable.Api.registerPlural( 2240 'buttons().active()', 2241 'button().active()', 2242 function (flag) { 2243 if (flag === undefined) { 2244 return this.map(function (set) { 2245 return set.inst.active(set.node); 2246 }); 2247 } 2248 2249 return this.each(function (set) { 2250 set.inst.active(set.node, flag); 2251 }); 2252 } 2253); 2254 2255// Get / set button action 2256DataTable.Api.registerPlural( 2257 'buttons().action()', 2258 'button().action()', 2259 function (action) { 2260 if (action === undefined) { 2261 return this.map(function (set) { 2262 return set.inst.action(set.node); 2263 }); 2264 } 2265 2266 return this.each(function (set) { 2267 set.inst.action(set.node, action); 2268 }); 2269 } 2270); 2271 2272// Collection control 2273DataTable.Api.registerPlural( 2274 'buttons().collectionRebuild()', 2275 'button().collectionRebuild()', 2276 function (buttons) { 2277 return this.each(function (set) { 2278 for (var i = 0; i < buttons.length; i++) { 2279 if (typeof buttons[i] === 'object') { 2280 buttons[i].parentConf = set; 2281 } 2282 } 2283 set.inst.collectionRebuild(set.node, buttons); 2284 }); 2285 } 2286); 2287 2288// Enable / disable buttons 2289DataTable.Api.register( 2290 ['buttons().enable()', 'button().enable()'], 2291 function (flag) { 2292 return this.each(function (set) { 2293 set.inst.enable(set.node, flag); 2294 }); 2295 } 2296); 2297 2298// Disable buttons 2299DataTable.Api.register( 2300 ['buttons().disable()', 'button().disable()'], 2301 function () { 2302 return this.each(function (set) { 2303 set.inst.disable(set.node); 2304 }); 2305 } 2306); 2307 2308// Button index 2309DataTable.Api.register('button().index()', function () { 2310 var idx = null; 2311 2312 this.each(function (set) { 2313 var res = set.inst.index(set.node); 2314 2315 if (res !== null) { 2316 idx = res; 2317 } 2318 }); 2319 2320 return idx; 2321}); 2322 2323// Get button nodes 2324DataTable.Api.registerPlural( 2325 'buttons().nodes()', 2326 'button().node()', 2327 function () { 2328 var jq = $(); 2329 2330 // jQuery will automatically reduce duplicates to a single entry 2331 $( 2332 this.each(function (set) { 2333 jq = jq.add(set.inst.node(set.node)); 2334 }) 2335 ); 2336 2337 return jq; 2338 } 2339); 2340 2341// Get / set button processing state 2342DataTable.Api.registerPlural( 2343 'buttons().processing()', 2344 'button().processing()', 2345 function (flag) { 2346 if (flag === undefined) { 2347 return this.map(function (set) { 2348 return set.inst.processing(set.node); 2349 }); 2350 } 2351 2352 return this.each(function (set) { 2353 set.inst.processing(set.node, flag); 2354 }); 2355 } 2356); 2357 2358// Get / set button text (i.e. the button labels) 2359DataTable.Api.registerPlural( 2360 'buttons().text()', 2361 'button().text()', 2362 function (label) { 2363 if (label === undefined) { 2364 return this.map(function (set) { 2365 return set.inst.text(set.node); 2366 }); 2367 } 2368 2369 return this.each(function (set) { 2370 set.inst.text(set.node, label); 2371 }); 2372 } 2373); 2374 2375// Trigger a button's action 2376DataTable.Api.registerPlural( 2377 'buttons().trigger()', 2378 'button().trigger()', 2379 function () { 2380 return this.each(function (set) { 2381 set.inst.node(set.node).trigger('click'); 2382 }); 2383 } 2384); 2385 2386// Button resolver to the popover 2387DataTable.Api.register('button().popover()', function (content, options) { 2388 return this.map(function (set) { 2389 return set.inst._popover(content, this.button(this[0].node), options); 2390 }); 2391}); 2392 2393// Get the container elements 2394DataTable.Api.register('buttons().containers()', function () { 2395 var jq = $(); 2396 var groupSelector = this._groupSelector; 2397 2398 // We need to use the group selector directly, since if there are no buttons 2399 // the result set will be empty 2400 this.iterator(true, 'table', function (ctx) { 2401 if (ctx._buttons) { 2402 var insts = Buttons.instanceSelector(groupSelector, ctx._buttons); 2403 2404 for (var i = 0, ien = insts.length; i < ien; i++) { 2405 jq = jq.add(insts[i].container()); 2406 } 2407 } 2408 }); 2409 2410 return jq; 2411}); 2412 2413DataTable.Api.register('buttons().container()', function () { 2414 // API level of nesting is `buttons()` so we can zip into the containers method 2415 return this.containers().eq(0); 2416}); 2417 2418// Add a new button 2419DataTable.Api.register('button().add()', function (idx, conf, draw) { 2420 var ctx = this.context; 2421 2422 // Don't use `this` as it could be empty - select the instances directly 2423 if (ctx.length) { 2424 var inst = Buttons.instanceSelector( 2425 this._groupSelector, 2426 ctx[0]._buttons 2427 ); 2428 2429 if (inst.length) { 2430 inst[0].add(conf, idx, draw);
2431 } 2432 } 2433 2434 return this.button(this._groupSelector, idx); 2435}); 2436 2437// Destroy the button sets selected 2438DataTable.Api.register('buttons().destroy()', function () { 2439 this.pluck('inst') 2440 .unique() 2441 .each(function (inst) { 2442 inst.destroy(); 2443 }); 2444 2445 return this; 2446}); 2447 2448// Remove a button 2449DataTable.Api.registerPlural( 2450 'buttons().remove()', 2451 'buttons().remove()', 2452 function () { 2453 this.each(function (set) { 2454 set.inst.remove(set.node); 2455 }); 2456 2457 return this; 2458 } 2459); 2460 2461// Information box that can be used by buttons 2462var _infoTimer; 2463DataTable.Api.register('buttons.info()', function (title, message, time) { 2464 var that = this; 2465 2466 if (title === false) { 2467 this.off('destroy.btn-info'); 2468 _fadeOut($('#datatables_buttons_info'), 400, function () { 2469 $(this).remove(); 2470 }); 2471 clearTimeout(_infoTimer); 2472 _infoTimer = null; 2473 2474 return this; 2475 } 2476 2477 if (_infoTimer) { 2478 clearTimeout(_infoTimer); 2479 } 2480 2481 if ($('#datatables_buttons_info').length) { 2482 $('#datatables_buttons_info').remove(); 2483 } 2484 2485 title = title ? '<h2>' + title + '</h2>' : ''; 2486 2487 _fadeIn( 2488 $('<div id="datatables_buttons_info" class="dt-button-info"/>') 2489 .html(title) 2490 .append( 2491 $('<div/>')[typeof message === 'string' ? 'html' : 'append']( 2492 message 2493 ) 2494 ) 2495 .css('display', 'none') 2496 .appendTo('body') 2497 ); 2498 2499 if (time !== undefined && time !== 0) { 2500 _infoTimer = setTimeout(function () { 2501 that.buttons.info(false); 2502 }, time); 2503 } 2504 2505 this.on('destroy.btn-info', function () { 2506 that.buttons.info(false); 2507 }); 2508 2509 return this; 2510}); 2511 2512// Get data from the table for export - this is common to a number of plug-in 2513// buttons so it is included in the Buttons core library 2514DataTable.Api.register('buttons.exportData()', function (options) { 2515 if (this.context.length) { 2516 return _exportData(new DataTable.Api(this.context[0]), options); 2517 } 2518}); 2519 2520// Get information about the export that is common to many of the export data 2521// types (DRY) 2522DataTable.Api.register('buttons.exportInfo()', function (conf) { 2523 if (!conf) { 2524 conf = {}; 2525 } 2526 2527 return { 2528 filename: _filename(conf, this), 2529 title: _title(conf, this), 2530 messageTop: _message(this, conf, conf.message || conf.messageTop, 'top'), 2531 messageBottom: _message(this, conf, conf.messageBottom, 'bottom') 2532 }; 2533}); 2534 2535/** 2536 * Get the file name for an exported file. 2537 * 2538 * @param {object} config Button configuration 2539 * @param {object} dt DataTable instance 2540 */ 2541var _filename = function (config, dt) { 2542 // Backwards compatibility 2543 var filename = 2544 config.filename === '*' && 2545 config.title !== '*' && 2546 config.title !== undefined && 2547 config.title !== null && 2548 config.title !== '' 2549 ? config.title 2550 : config.filename; 2551 2552 if (typeof filename === 'function') { 2553 filename = filename(config, dt); 2554 } 2555 2556 if (filename === undefined || filename === null) { 2557 return null; 2558 } 2559 2560 if (filename.indexOf('*') !== -1) { 2561 filename = filename.replace(/\*/g, $('head > title').text()).trim(); 2562 } 2563 2564 // Strip characters which the OS will object to 2565 filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ''); 2566 2567 var extension = _stringOrFunction(config.extension, config, dt); 2568 if (!extension) { 2569 extension = ''; 2570 } 2571 2572 return filename + extension; 2573}; 2574 2575/** 2576 * Simply utility method to allow parameters to be given as a function 2577 * 2578 * @param {undefined|string|function} option Option 2579 * @return {null|string} Resolved value 2580 */ 2581var _stringOrFunction = function (option, config, dt) { 2582 if (option === null || option === undefined) { 2583 return null; 2584 } 2585 else if (typeof option === 'function') { 2586 return option(config, dt); 2587 } 2588 return option; 2589}; 2590 2591/** 2592 * Get the title for an exported file. 2593 * 2594 * @param {object} config Button configuration 2595 */ 2596var _title = function (config, dt) { 2597 var title = _stringOrFunction(config.title, config, dt); 2598 2599 return title === null 2600 ? null 2601 : title.indexOf('*') !== -1 2602 ? title.replace(/\*/g, $('head > title').text() || 'Exported data') 2603 : title; 2604}; 2605 2606var _message = function (dt, config, option, position) { 2607 var message = _stringOrFunction(option, config, dt); 2608 if (message === null) { 2609 return null; 2610 } 2611 2612 var caption = $('caption', dt.table().container()).eq(0); 2613 if (message === '*') { 2614 var side = caption.css('caption-side'); 2615 if (side !== position) { 2616 return null; 2617 } 2618 2619 return caption.length ? caption.text() : ''; 2620 } 2621 2622 return message; 2623}; 2624 2625var _exportTextarea = $('<textarea/>')[0]; 2626var _exportData = function (dt, inOpts) { 2627 var config = $.extend( 2628 true, 2629 {}, 2630 { 2631 rows: null,
2632 columns: '', 2633 modifier: { 2634 search: 'applied', 2635 order: 'applied' 2636 }, 2637 orthogonal: 'display', 2638 stripHtml: true, 2639 stripNewlines: true, 2640 decodeEntities: true, 2641 trim: true, 2642 format: { 2643 header: function (d) { 2644 return Buttons.stripData(d, config); 2645 }, 2646 footer: function (d) { 2647 return Buttons.stripData(d, config); 2648 }, 2649 body: function (d) { 2650 return Buttons.stripData(d, config); 2651 } 2652 }, 2653 customizeData: null, 2654 customizeZip: null 2655 }, 2656 inOpts 2657 ); 2658 2659 var header = dt 2660 .columns(config.columns) 2661 .indexes() 2662 .map(function (idx) { 2663 var col = dt.column(idx); 2664 return config.format.header(col.title(), idx, col.header()); 2665 }) 2666 .toArray(); 2667 2668 var footer = dt.table().footer() 2669 ? dt 2670 .columns(config.columns) 2671 .indexes() 2672 .map(function (idx) { 2673 var el = dt.column(idx).footer(); 2674 var val = ''; 2675 2676 if (el) { 2677 var inner = $('.dt-column-title', el); 2678 2679 val = inner.length 2680 ? inner.html() 2681 : $(el).html(); 2682 } 2683 2684 return config.format.footer(val, idx, el); 2685 }) 2686 .toArray() 2687 : null; 2688 2689 // If Select is available on this table, and any rows are selected, limit the export 2690 // to the selected rows. If no rows are selected, all rows will be exported. Specify 2691 // a `selected` modifier to control directly. 2692 var modifier = $.extend({}, config.modifier); 2693 if ( 2694 dt.select && 2695 typeof dt.select.info === 'function' && 2696 modifier.selected === undefined 2697 ) { 2698 if ( 2699 dt.rows(config.rows, $.extend({ selected: true }, modifier)).any() 2700 ) { 2701 $.extend(modifier, { selected: true }); 2702 } 2703 } 2704 2705 var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray(); 2706 var selectedCells = dt.cells(rowIndexes, config.columns, { 2707 order: modifier.order 2708 }); 2709 var cells = selectedCells.render(config.orthogonal).toArray(); 2710 var cellNodes = selectedCells.nodes().toArray(); 2711 var cellIndexes = selectedCells.indexes().toArray(); 2712 2713 var columns = dt.columns(config.columns).count(); 2714 var rows = columns > 0 ? cells.length / columns : 0; 2715 var body = []; 2716 var cellCounter = 0; 2717 2718 for (var i = 0, ien = rows; i < ien; i++) { 2719 var row = [columns]; 2720 2721 for (var j = 0; j < columns; j++) { 2722 row[j] = config.format.body( 2723 cells[cellCounter], 2724 cellIndexes[cellCounter].row, 2725 cellIndexes[cellCounter].column, 2726 cellNodes[cellCounter] 2727 ); 2728 cellCounter++; 2729 } 2730 2731 body[i] = row; 2732 } 2733 2734 var data = { 2735 header: header, 2736 headerStructure: _headerFormatter( 2737 config.format.header, 2738 dt.table().header.structure(config.columns) 2739 ), 2740 footer: footer, 2741 footerStructure: _headerFormatter( 2742 config.format.footer, 2743 dt.table().footer.structure(config.columns) 2744 ), 2745 body: body 2746 }; 2747 2748 if (config.customizeData) { 2749 config.customizeData(data); 2750 } 2751 2752 return data; 2753}; 2754 2755function _headerFormatter(formatter, struct) { 2756 for (var i=0 ; i<struct.length ; i++) { 2757 for (var j=0 ; j<struct[i].length ; j++) { 2758 var item = struct[i][j]; 2759 2760 if (item) { 2761 item.title = formatter( 2762 item.title, 2763 j, 2764 item.cell 2765 ); 2766 } 2767 } 2768 } 2769 2770 return struct; 2771} 2772 2773/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2774 * DataTables interface 2775 */ 2776 2777// Attach to DataTables objects for global access 2778$.fn.dataTable.Buttons = Buttons; 2779$.fn.DataTable.Buttons = Buttons; 2780 2781// DataTables creation - check if the buttons have been defined for this table, 2782// they will have been if the `B` option was used in `dom`, otherwise we should 2783// create the buttons instance here so they can be inserted into the document 2784// using the API. Listen for `init` for compatibility with pre 1.10.10, but to 2785// be removed in future. 2786$(document).on('init.dt plugin-init.dt', function (e, settings) { 2787 if (e.namespace !== 'dt') { 2788 return; 2789 } 2790 2791 var opts = settings.oInit.buttons || DataTable.defaults.buttons; 2792
2793 if (opts && !settings._buttons) { 2794 new Buttons(settings, opts).container(); 2795 } 2796}); 2797 2798function _init(settings, options) { 2799 var api = new DataTable.Api(settings); 2800 var opts = options 2801 ? options 2802 : api.init().buttons || DataTable.defaults.buttons; 2803 2804 return new Buttons(api, opts).container(); 2805} 2806 2807// DataTables 1 `dom` feature option 2808DataTable.ext.feature.push({ 2809 fnInit: _init, 2810 cFeature: 'B' 2811}); 2812 2813// DataTables 2 layout feature 2814if (DataTable.feature) { 2815 DataTable.feature.register('buttons', _init); 2816} 2817 2818 2819return DataTable; 2820}));
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.