1if (typeof(PhpDebugBar) == 'undefined') { 2 // namespace 3 var PhpDebugBar = {}; 4 PhpDebugBar.$ = jQuery; 5} 6 7(function($) { 8 9 if (typeof(localStorage) == 'undefined') { 10 // provide mock localStorage object for dumb browsers 11 localStorage = { 12 setItem: function(key, value) {}, 13 getItem: function(key) { return null; } 14 }; 15 } 16 17 if (typeof(PhpDebugBar.utils) == 'undefined') { 18 PhpDebugBar.utils = {}; 19 } 20 21 /** 22 * Returns the value from an object property. 23 * Using dots in the key, it is possible to retrieve nested property values 24 * 25 * @param {Object} dict 26 * @param {String} key 27 * @param {Object} default_value 28 * @return {Object} 29 */ 30 var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) { 31 var d = dict, parts = key.split('.'); 32 for (var i = 0; i < parts.length; i++) { 33 if (!d[parts[i]]) { 34 return default_value; 35 } 36 d = d[parts[i]]; 37 } 38 return d; 39 } 40 41 /** 42 * Counts the number of properties in an object 43 * 44 * @param {Object} obj 45 * @return {Integer} 46 */ 47 var getObjectSize = PhpDebugBar.utils.getObjectSize = function(obj) { 48 if (Object.keys) { 49 return Object.keys(obj).length; 50 } 51 var count = 0; 52 for (var k in obj) { 53 if (obj.hasOwnProperty(k)) { 54 count++; 55 } 56 } 57 return count; 58 } 59 60 /** 61 * Returns a prefixed css class name 62 * 63 * @param {String} cls 64 * @return {String} 65 */ 66 PhpDebugBar.utils.csscls = function(cls, prefix) { 67 if (cls.indexOf(' ') > -1) { 68 var clss = cls.split(' '), out = []; 69 for (var i = 0, c = clss.length; i < c; i++) { 70 out.push(PhpDebugBar.utils.csscls(clss[i], prefix)); 71 } 72 return out.join(' '); 73 } 74 if (cls.indexOf('.') === 0) { 75 return '.' + prefix + cls.substr(1); 76 } 77 return prefix + cls; 78 }; 79 80 /** 81 * Creates a partial function of csscls where the second 82 * argument is already defined 83 * 84 * @param {string} prefix 85 * @return {Function} 86 */ 87 PhpDebugBar.utils.makecsscls = function(prefix) { 88 var f = function(cls) { 89 return PhpDebugBar.utils.csscls(cls, prefix); 90 }; 91 return f; 92 } 93 94 var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-'); 95 96 97 // ------------------------------------------------------------------ 98 99 /** 100 * Base class for all elements with a visual component 101 * 102 * @param {Object} options 103 * @constructor 104 */ 105 var Widget = PhpDebugBar.Widget = function(options) { 106 this._attributes = $.extend({}, this.defaults); 107 this._boundAttributes = {}; 108 this.$el = $('<' + this.tagName + ' />'); 109 if (this.className) { 110 this.$el.addClass(this.className); 111 } 112 this.initialize.apply(this, [options || {}]); 113 this.render.apply(this); 114 }; 115 116 $.extend(Widget.prototype, { 117 118 tagName: 'div', 119 120 className: null, 121 122 defaults: {}, 123 124 /** 125 * Called after the constructor 126 * 127 * @param {Object} options 128 */ 129 initialize: function(options) { 130 this.set(options); 131 }, 132 133 /** 134 * Called after the constructor to render the element 135 */ 136 render: function() {}, 137 138 /** 139 * Sets the value of an attribute 140 * 141 * @param {String} attr Can also be an object to set multiple attributes at once 142 * @param {Object} value 143 */ 144 set: function(attr, value) { 145 if (typeof(attr) != 'string') { 146 for (var k in attr) { 147 this.set(k, attr[k]); 148 } 149 return; 150 } 151 152 this._attributes[attr] = value; 153 if (typeof(this._boundAttributes[attr]) !== 'undefined') { 154 for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) { 155 this._boundAttributes[attr][i].apply(this, [value]); 156 } 157 } 158 }, 159 160 /** 161 * Checks if an attribute exists and is not null 162 * 163 * @param {String} attr 164 * @return {[type]} [description] 165 */ 166 has: function(attr) { 167 return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null; 168 }, 169 170 /** 171 * Returns the value of an attribute 172 * 173 * @param {String} attr 174 * @return {Object} 175 */ 176 get: function(attr) { 177 return this._attributes[attr]; 178 }, 179 180 /** 181 * Registers a callback function that will be called whenever the value of the attribute changes 182 * 183 * If cb is a jQuery element, text() will be used to fill the element 184 * 185 * @param {String} attr 186 * @param {Function} cb 187 */ 188 bindAttr: function(attr, cb) { 189 if ($.isArray(attr)) { 190 for (var i = 0, c = attr.length; i < c; i++) { 191 this.bindAttr(attr[i], cb); 192 } 193 return; 194 } 195 196 if (typeof(this._boundAttributes[attr]) == 'undefined') { 197 this._boundAttributes[attr] = []; 198 } 199 if (typeof(cb) == 'object') { 200 var el = cb; 201 cb = function(value) { el.text(value || ''); }; 202 } 203 this._boundAttributes[attr].push(cb); 204 if (this.has(attr)) { 205 cb.apply(this, [this._attributes[attr]]); 206 } 207 } 208 209 }); 210 211 212 /** 213 * Creates a subclass 214 * 215 * Code from Backbone.js 216 * 217 * @param {Array} props Prototype properties 218 * @return {Function} 219 */ 220 Widget.extend = function(props) { 221 var parent = this; 222 223 var child = function() { return parent.apply(this, arguments); }; 224 $.extend(child, parent); 225 226 var Surrogate = function() { this.constructor = child; }; 227 Surrogate.prototype = parent.prototype; 228 child.prototype = new Surrogate; 229 $.extend(child.prototype, props); 230 231 child.__super__ = parent.prototype; 232 233 return child; 234 }; 235 236 // ------------------------------------------------------------------ 237 238 /** 239 * Tab 240 * 241 * A tab is composed of a tab label which is always visible and 242 * a tab panel which is visible only when the tab is active. 243 * 244 * The panel must contain a widget. A widget is an object which has 245 * an element property containing something appendable to a jQuery object. 246 * 247 * Options: 248 * - title 249 * - badge 250 * - widget 251 * - data: forward data to widget data 252 */ 253 var Tab = Widget.extend({ 254 255 className: csscls('panel'), 256 257 render: function() { 258 this.$tab = $('<a />').addClass(csscls('tab')); 259 260 this.$icon = $('<i />').appendTo(this.$tab); 261 this.bindAttr('icon', function(icon) { 262 if (icon) { 263 this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon); 264 } else { 265 this.$icon.attr('class', ''); 266 } 267 }); 268 269 this.bindAttr('title', $('<span />').addClass(csscls('text')).appendTo(this.$tab)); 270 271 this.$badge = $('<span />').addClass(csscls('badge')).appendTo(this.$tab); 272 this.bindAttr('badge', function(value) { 273 if (value !== null) { 274 this.$badge.text(value); 275 this.$badge.addClass(csscls('visible')); 276 } else { 277 this.$badge.removeClass(csscls('visible')); 278 } 279 }); 280 281 this.bindAttr('widget', function(widget) { 282 this.$el.empty().append(widget.$el); 283 }); 284 285 this.bindAttr('data', function(data) { 286 if (this.has('widget')) { 287 this.get('widget').set('data', data); 288 } 289 }) 290 } 291 292 }); 293 294 // ------------------------------------------------------------------ 295 296 /** 297 * Indicator 298 * 299 * An indicator is a text and an icon to display single value information 300 * right inside the always visible part of the debug bar 301 * 302 * Options: 303 * - icon 304 * - title 305 * - tooltip 306 * - data: alias of title 307 */ 308 var Indicator = Widget.extend({ 309 310 tagName: 'span', 311 312 className: csscls('indicator'), 313 314 render: function() { 315 this.$icon = $('<i />').appendTo(this.$el); 316 this.bindAttr('icon', function(icon) { 317 if (icon) { 318 this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon); 319 } else { 320 this.$icon.attr('class', ''); 321 } 322 }); 323 324 this.bindAttr(['title', 'data'], $('<span />').addClass(csscls('text')).appendTo(this.$el)); 325 326 this.$tooltip = $('<span />').addClass(csscls('tooltip disabled')).appendTo(this.$el); 327 this.bindAttr('tooltip', function(tooltip) { 328 if (tooltip) { 329 this.$tooltip.text(tooltip).removeClass(csscls('disabled')); 330 } else { 331 this.$tooltip.addClass(csscls('disabled')); 332 } 333 }); 334 } 335 336 }); 337 338 // ------------------------------------------------------------------ 339 340 /** 341 * Dataset title formater 342 * 343 * Formats the title of a dataset for the select box 344 */ 345 var DatasetTitleFormater = PhpDebugBar.DatasetTitleFormater = function(debugbar) { 346 this.debugbar = debugbar; 347 }; 348 349 $.extend(DatasetTitleFormater.prototype, { 350 351 /** 352 * Formats the title of a dataset 353 * 354 * @this {DatasetTitleFormater} 355 * @param {String} id 356 * @param {Object} data 357 * @param {String} suffix 358 * @return {String} 359 */ 360 format: function(id, data, suffix) { 361 if (suffix) { 362 suffix = ' ' + suffix; 363 } else { 364 suffix = ''; 365 } 366 367 var nb = getObjectSize(this.debugbar.datasets) + 1; 368 369 if (typeof(data['__meta']) === 'undefined') { 370 return "#" + nb + suffix; 371 } 372 373 var uri = data['__meta']['uri'], filename; 374 if (uri.length && uri.charAt(uri.length - 1) === '/') { 375 // URI ends in a trailing /: get the portion before then to avoid returning an empty string 376 filename = uri.substr(0, uri.length - 1); // strip trailing '/' 377 filename = filename.substr(filename.lastIndexOf('/') + 1); // get last path segment 378 filename += '/'; // add the trailing '/' back 379 } else { 380 filename = uri.substr(uri.lastIndexOf('/') + 1); 381 } 382 383 // truncate the filename in the label, if it's too long 384 var maxLength = 150; 385 if (filename.length > maxLength) { 386 filename = filename.substr(0, maxLength) + '...'; 387 } 388
389 var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')'; 390 return label; 391 } 392 393 }); 394 395 // ------------------------------------------------------------------ 396 397 398 /** 399 * DebugBar 400 * 401 * Creates a bar that appends itself to the body of your page 402 * and sticks to the bottom. 403 * 404 * The bar can be customized by adding tabs and indicators. 405 * A data map is used to fill those controls with data provided 406 * from datasets. 407 */ 408 var DebugBar = PhpDebugBar.DebugBar = Widget.extend({ 409 410 className: "phpdebugbar " + csscls('minimized'), 411 412 options: { 413 bodyMarginBottom: true, 414 bodyMarginBottomHeight: 0 415 }, 416 417 initialize: function() { 418 this.controls = {}; 419 this.dataMap = {}; 420 this.datasets = {}; 421 this.firstTabName = null; 422 this.activePanelName = null; 423 this.datesetTitleFormater = new DatasetTitleFormater(this); 424 this.options.bodyMarginBottomHeight = parseInt($('body').css('margin-bottom')); 425 this.registerResizeHandler(); 426 }, 427 428 /** 429 * Register resize event, for resize debugbar with reponsive css. 430 * 431 * @this {DebugBar} 432 */ 433 registerResizeHandler: function() { 434 if (typeof this.resize.bind == 'undefined') return; 435 436 var f = this.resize.bind(this); 437 this.respCSSSize = 0; 438 $(window).resize(f); 439 setTimeout(f, 20); 440 }, 441 442 /** 443 * Resizes the debugbar to fit the current browser window 444 */ 445 resize: function() { 446 var contentSize = this.respCSSSize; 447 if (this.respCSSSize == 0) { 448 this.$header.find("> div > *:visible").each(function () { 449 contentSize += $(this).outerWidth(); 450 }); 451 } 452 453 var currentSize = this.$header.width(); 454 var cssClass = "phpdebugbar-mini-design"; 455 var bool = this.$header.hasClass(cssClass); 456 457 if (currentSize <= contentSize && !bool) { 458 this.respCSSSize = contentSize; 459 this.$header.addClass(cssClass); 460 } else if (contentSize < currentSize && bool) { 461 this.respCSSSize = 0; 462 this.$header.removeClass(cssClass); 463 } 464 465 // Reset height to ensure bar is still visible 466 this.setHeight(this.$body.height()); 467 }, 468 469 /** 470 * Initialiazes the UI 471 * 472 * @this {DebugBar} 473 */ 474 render: function() { 475 var self = this; 476 this.$el.appendTo('body'); 477 this.$dragCapture = $('<div />').addClass(csscls('drag-capture')).appendTo(this.$el); 478 this.$resizehdle = $('<div />').addClass(csscls('resize-handle')).appendTo(this.$el); 479 this.$header = $('<div />').addClass(csscls('header')).appendTo(this.$el); 480 this.$headerBtn = $('<a />').addClass(csscls('restore-btn')).appendTo(this.$header); 481 this.$headerBtn.click(function() { 482 self.close(); 483 }); 484 this.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header); 485 this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header); 486 var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el); 487 this.recomputeBottomOffset(); 488 489 // dragging of resize handle 490 var pos_y, orig_h; 491 this.$resizehdle.on('mousedown', function(e) { 492 orig_h = $body.height(), pos_y = e.pageY; 493 $body.parents().on('mousemove', mousemove).on('mouseup', mouseup); 494 self.$dragCapture.show(); 495 e.preventDefault(); 496 }); 497 var mousemove = function(e) { 498 var h = orig_h + (pos_y - e.pageY); 499 self.setHeight(h); 500 }; 501 var mouseup = function() { 502 $body.parents().off('mousemove', mousemove).off('mouseup', mouseup); 503 self.$dragCapture.hide(); 504 }; 505 506 // close button 507 this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight); 508 this.$closebtn.click(function() { 509 self.close(); 510 }); 511 512 // minimize button 513 this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight); 514 this.$minimizebtn.click(function() { 515 self.minimize(); 516 }); 517 518 // maximize button 519 this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight); 520 this.$maximizebtn.click(function() {
521 self.restore(); 522 }); 523 524 // restore button 525 this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el); 526 this.$restorebtn.click(function() { 527 self.restore(); 528 }); 529 530 // open button 531 this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide(); 532 this.$openbtn.click(function() { 533 self.openHandler.show(function(id, dataset) { 534 self.addDataSet(dataset, id, "(opened)"); 535 self.showTab(); 536 }); 537 }); 538 539 // select box for data sets 540 this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).appendTo(this.$headerRight); 541 this.$datasets.change(function() { 542 self.dataChangeHandler(self.datasets[this.value]); 543 self.showTab(); 544 }); 545 }, 546 547 /** 548 * Sets the height of the debugbar body section 549 * Forces the height to lie within a reasonable range 550 * Stores the height in local storage so it can be restored 551 * Resets the document body bottom offset 552 * 553 * @this {DebugBar} 554 */ 555 setHeight: function(height) { 556 var min_h = 40; 557 var max_h = $(window).innerHeight() - this.$header.height() - 10; 558 height = Math.min(height, max_h); 559 height = Math.max(height, min_h); 560 this.$body.css('height', height); 561 localStorage.setItem('phpdebugbar-height', height); 562 this.recomputeBottomOffset(); 563 }, 564 565 /** 566 * Restores the state of the DebugBar using localStorage 567 * This is not called by default in the constructor and 568 * needs to be called by subclasses in their init() method 569 * 570 * @this {DebugBar} 571 */ 572 restoreState: function() { 573 // bar height 574 var height = localStorage.getItem('phpdebugbar-height'); 575 this.setHeight(height || this.$body.height()); 576 577 // bar visibility 578 var open = localStorage.getItem('phpdebugbar-open'); 579 if (open && open == '0') { 580 this.close(); 581 } else { 582 var visible = localStorage.getItem('phpdebugbar-visible'); 583 if (visible && visible == '1') { 584 var tab = localStorage.getItem('phpdebugbar-tab'); 585 if (this.isTab(tab)) { 586 this.showTab(tab); 587 } 588 } 589 } 590 }, 591 592 /** 593 * Creates and adds a new tab 594 * 595 * @this {DebugBar} 596 * @param {String} name Internal name 597 * @param {Object} widget A widget object with an element property 598 * @param {String} title The text in the tab, if not specified, name will be used 599 * @return {Tab} 600 */ 601 createTab: function(name, widget, title) { 602 var tab = new Tab({ 603 title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), 604 widget: widget 605 }); 606 return this.addTab(name, tab); 607 }, 608 609 /** 610 * Adds a new tab 611 * 612 * @this {DebugBar} 613 * @param {String} name Internal name 614 * @param {Tab} tab Tab object 615 * @return {Tab} 616 */ 617 addTab: function(name, tab) { 618 if (this.isControl(name)) { 619 throw new Error(name + ' already exists'); 620 } 621 622 var self = this; 623 tab.$tab.appendTo(this.$headerLeft).click(function() { 624 if (!self.isMinimized() && self.activePanelName == name) { 625 self.minimize(); 626 } else { 627 self.showTab(name); 628 } 629 }); 630 tab.$el.appendTo(this.$body); 631 632 this.controls[name] = tab; 633 if (this.firstTabName == null) { 634 this.firstTabName = name; 635 } 636 return tab; 637 }, 638 639 /** 640 * Creates and adds an indicator 641 * 642 * @this {DebugBar} 643 * @param {String} name Internal name 644 * @param {String} icon 645 * @param {String} tooltip 646 * @param {String} position "right" or "left", default is "right" 647 * @return {Indicator} 648 */ 649 createIndicator: function(name, icon, tooltip, position) { 650 var indicator = new Indicator({ 651 icon: icon, 652 tooltip: tooltip 653 }); 654 return this.addIndicator(name, indicator, position); 655 }, 656 657 /** 658 * Adds an indicator 659 * 660 * @this {DebugBar} 661 * @param {String} name Internal name 662 * @param {Indicator} indicator Indicator object 663 * @return {Indicator} 664 */ 665 addIndicator: function(name, indicator, position) { 666 if (this.isControl(name)) { 667 throw new Error(name + ' already exists'); 668 } 669 670 if (position == 'left') { 671 indicator.$el.insertBefore(this.$headerLeft.children().first()); 672 } else { 673 indicator.$el.appendTo(this.$headerRight); 674 } 675 676 this.controls[name] = indicator; 677 return indicator; 678 }, 679 680 /** 681 * Returns a control 682 * 683 * @param {String} name 684 * @return {Object} 685 */ 686 getControl: function(name) { 687 if (this.isControl(name)) { 688 return this.controls[name]; 689 } 690 }, 691 692 /** 693 * Checks if there's a control under the specified name 694 * 695 * @this {DebugBar} 696 * @param {String} name 697 * @return {Boolean} 698 */ 699 isControl: function(name) { 700 return typeof(this.controls[name]) != 'undefined'; 701 }, 702 703 /** 704 * Checks if a tab with the specified name exists 705 * 706 * @this {DebugBar} 707 * @param {String} name 708 * @return {Boolean} 709 */ 710 isTab: function(name) { 711 return this.isControl(name) && this.controls[name] instanceof Tab; 712 }, 713 714 /** 715 * Checks if an indicator with the specified name exists 716 * 717 * @this {DebugBar} 718 * @param {String} name 719 * @return {Boolean} 720 */ 721 isIndicator: function(name) { 722 return this.isControl(name) && this.controls[name] instanceof Indicator; 723 }, 724 725 /** 726 * Removes all tabs and indicators from the debug bar and hides it 727 * 728 * @this {DebugBar} 729 */ 730 reset: function() { 731 this.minimize(); 732 var self = this; 733 $.each(this.controls, function(name, control) { 734 if (self.isTab(name)) { 735 control.$tab.remove(); 736 } 737 control.$el.remove(); 738 }); 739 this.controls = {}; 740 }, 741 742 /** 743 * Open the debug bar and display the specified tab 744 * 745 * @this {DebugBar} 746 * @param {String} name If not specified, display the first tab 747 */ 748 showTab: function(name) { 749 if (!name) { 750 if (this.activePanelName) { 751 name = this.activePanelName; 752 } else { 753 name = this.firstTabName; 754 } 755 } 756 757 if (!this.isTab(name)) {
758 throw new Error("Unknown tab '" + name + "'"); 759 } 760 761 this.$resizehdle.show(); 762 this.$body.show(); 763 this.recomputeBottomOffset(); 764 765 $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active')); 766 $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active')); 767 768 this.controls[name].$tab.addClass(csscls('active')); 769 this.controls[name].$el.addClass(csscls('active')); 770 this.activePanelName = name; 771 772 this.$el.removeClass(csscls('minimized')); 773 localStorage.setItem('phpdebugbar-visible', '1'); 774 localStorage.setItem('phpdebugbar-tab', name); 775 this.resize(); 776 }, 777 778 /** 779 * Hide panels and minimize the debug bar 780 * 781 * @this {DebugBar} 782 */ 783 minimize: function() { 784 this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active')); 785 this.$body.hide(); 786 this.$resizehdle.hide(); 787 this.recomputeBottomOffset(); 788 localStorage.setItem('phpdebugbar-visible', '0'); 789 this.$el.addClass(csscls('minimized')); 790 this.resize(); 791 }, 792 793 /** 794 * Checks if the panel is minimized 795 * 796 * @return {Boolean} 797 */ 798 isMinimized: function() { 799 return this.$el.hasClass(csscls('minimized')); 800 }, 801 802 /** 803 * Close the debug bar 804 * 805 * @this {DebugBar} 806 */ 807 close: function() { 808 this.$resizehdle.hide(); 809 this.$header.hide(); 810 this.$body.hide(); 811 this.$restorebtn.show(); 812 localStorage.setItem('phpdebugbar-open', '0'); 813 this.$el.addClass(csscls('closed')); 814 this.recomputeBottomOffset(); 815 }, 816 817 /** 818 * Checks if the panel is closed 819 * 820 * @return {Boolean} 821 */ 822 isClosed: function() { 823 return this.$el.hasClass(csscls('closed')); 824 }, 825 826 /** 827 * Restore the debug bar 828 * 829 * @this {DebugBar} 830 */ 831 restore: function() { 832 this.$resizehdle.show(); 833 this.$header.show(); 834 this.$restorebtn.hide(); 835 localStorage.setItem('phpdebugbar-open', '1'); 836 var tab = localStorage.getItem('phpdebugbar-tab'); 837 if (this.isTab(tab)) { 838 this.showTab(tab); 839 } else { 840 this.showTab(); 841 } 842 this.$el.removeClass(csscls('closed')); 843 this.resize(); 844 }, 845 846 /** 847 * Recomputes the margin-bottom css property of the body so 848 * that the debug bar never hides any content 849 */ 850 recomputeBottomOffset: function() { 851 if (this.options.bodyMarginBottom) { 852 if (this.isClosed()) { 853 return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || ''); 854 } 855 856 var offset = parseInt(this.$el.height()) + (this.options.bodyMarginBottomHeight || 0); 857 $('body').css('margin-bottom', offset); 858 } 859 }, 860 861 /** 862 * Sets the data map used by dataChangeHandler to populate 863 * indicators and widgets 864 * 865 * A data map is an object where properties are control names. 866 * The value of each property should be an array where the first 867 * item is the name of a property from the data object (nested properties 868 * can be specified) and the second item the default value. 869 * 870 * Example: 871 * {"memory": ["memory.peak_usage_str", "0B"]} 872 * 873 * @this {DebugBar} 874 * @param {Object} map 875 */ 876 setDataMap: function(map) { 877 this.dataMap = map; 878 }, 879 880 /** 881 * Same as setDataMap() but appends to the existing map 882 * rather than replacing it 883 * 884 * @this {DebugBar} 885 * @param {Object} map 886 */ 887 addDataMap: function(map) { 888 $.extend(this.dataMap, map); 889 }, 890 891 /** 892 * Resets datasets and add one set of data 893 * 894 * For this method to be usefull, you need to specify 895 * a dataMap using setDataMap() 896 * 897 * @this {DebugBar} 898 * @param {Object} data 899 * @return {String} Dataset's id 900 */ 901 setData: function(data) { 902 this.datasets = {}; 903 return this.addDataSet(data); 904 }, 905 906 /** 907 * Adds a dataset 908 * 909 * If more than one dataset are added, the dataset selector 910 * will be displayed. 911 * 912 * For this method to be usefull, you need to specify 913 * a dataMap using setDataMap() 914 * 915 * @this {DebugBar} 916 * @param {Object} data 917 * @param {String} id The name of this set, optional 918 * @param {String} suffix 919 * @param {Bool} show Whether to show the new dataset, optional (default: true) 920 * @return {String} Dataset's id 921 */ 922 addDataSet: function(data, id, suffix, show) { 923 var label = this.datesetTitleFormater.format(id, data, suffix); 924 id = id || (getObjectSize(this.datasets) + 1); 925 this.datasets[id] = data; 926 927 this.$datasets.append($('<option value="' + id + '">' + label + '</option>')); 928 if (this.$datasets.children().length > 1) { 929 this.$datasets.show(); 930 } 931 932 if (typeof(show) == 'undefined' || show) { 933 this.showDataSet(id); 934 } 935 return id; 936 }, 937 938 /** 939 * Loads a dataset using the open handler 940 * 941 * @param {String} id 942 * @param {Bool} show Whether to show the new dataset, optional (default: true) 943 */
944 loadDataSet: function(id, suffix, callback, show) { 945 if (!this.openHandler) { 946 throw new Error('loadDataSet() needs an open handler'); 947 } 948 var self = this; 949 this.openHandler.load(id, function(data) { 950 self.addDataSet(data, id, suffix, show); 951 self.resize(); 952 callback && callback(data); 953 }); 954 }, 955 956 /** 957 * Returns the data from a dataset 958 * 959 * @this {DebugBar} 960 * @param {String} id 961 * @return {Object} 962 */ 963 getDataSet: function(id) { 964 return this.datasets[id]; 965 }, 966 967 /** 968 * Switch the currently displayed dataset 969 * 970 * @this {DebugBar} 971 * @param {String} id 972 */ 973 showDataSet: function(id) { 974 this.dataChangeHandler(this.datasets[id]); 975 this.$datasets.val(id); 976 }, 977 978 /** 979 * Called when the current dataset is modified. 980 * 981 * @this {DebugBar} 982 * @param {Object} data 983 */ 984 dataChangeHandler: function(data) { 985 var self = this; 986 $.each(this.dataMap, function(key, def) { 987 var d = getDictValue(data, def[0], def[1]); 988 if (key.indexOf(':') != -1) { 989 key = key.split(':'); 990 self.getControl(key[0]).set(key[1], d); 991 } else { 992 self.getControl(key).set('data', d); 993 } 994 }); 995 }, 996 997 /** 998 * Sets the handler to open past dataset 999 * 1000 * @this {DebugBar} 1001 * @param {object} handler 1002 */ 1003 setOpenHandler: function(handler) { 1004 this.openHandler = handler; 1005 if (handler !== null) { 1006 this.$openbtn.show(); 1007 } else { 1008 this.$openbtn.hide(); 1009 } 1010 }, 1011 1012 /** 1013 * Returns the handler to open past dataset 1014 * 1015 * @this {DebugBar} 1016 * @return {object} 1017 */ 1018 getOpenHandler: function() { 1019 return this.openHandler; 1020 } 1021 1022 }); 1023 1024 DebugBar.Tab = Tab; 1025 DebugBar.Indicator = Indicator; 1026 1027 // ------------------------------------------------------------------ 1028 1029 /** 1030 * AjaxHandler 1031 * 1032 * Extract data from headers of an XMLHttpRequest and adds a new dataset 1033 * 1034 * @param {Bool} autoShow Whether to immediately show new datasets, optional (default: true) 1035 */ 1036 var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName, autoShow) { 1037 this.debugbar = debugbar; 1038 this.headerName = headerName || 'phpdebugbar'; 1039 this.autoShow = typeof(autoShow) == 'undefined' ? true : autoShow; 1040 }; 1041 1042 $.extend(AjaxHandler.prototype, { 1043 1044 /** 1045 * Handles a Fetch API Response or an XMLHttpRequest 1046 * 1047 * @this {AjaxHandler} 1048 * @param {Response|XMLHttpRequest} response 1049 * @return {Bool} 1050 */ 1051 handle: function(response) { 1052 // Check if the debugbar header is available 1053 if (this.isFetch(response) && !response.headers.has(this.headerName + '-id')) { 1054 return true; 1055 } else if (this.isXHR(response) && response.getAllResponseHeaders().indexOf(this.headerName) === -1) { 1056 return true; 1057 } 1058 if (!this.loadFromId(response)) { 1059 return this.loadFromData(response); 1060 } 1061 return true; 1062 }, 1063 1064 getHeader: function(response, header) { 1065 if (this.isFetch(response)) { 1066 return response.headers.get(header) 1067 } 1068 1069 return response.getResponseHeader(header) 1070 }, 1071 1072 isFetch: function(response) { 1073 return Object.prototype.toString.call(response) == '[object Response]' 1074 }, 1075 1076 isXHR: function(response) { 1077 return Object.prototype.toString.call(response) == '[object XMLHttpRequest]' 1078 }, 1079 1080 /** 1081 * Checks if the HEADER-id exists and loads the dataset using the open handler 1082 * 1083 * @param {Response|XMLHttpRequest} response 1084 * @return {Bool} 1085 */ 1086 loadFromId: function(response) { 1087 var id = this.extractIdFromHeaders(response); 1088 if (id && this.debugbar.openHandler) { 1089 this.debugbar.loadDataSet(id, "(ajax)", undefined, this.autoShow); 1090 return true; 1091 } 1092 return false;
1093 }, 1094 1095 /** 1096 * Extracts the id from the HEADER-id 1097 * 1098 * @param {Response|XMLHttpRequest} response 1099 * @return {String} 1100 */ 1101 extractIdFromHeaders: function(response) { 1102 return this.getHeader(response, this.headerName + '-id'); 1103 }, 1104 1105 /** 1106 * Checks if the HEADER exists and loads the dataset 1107 * 1108 * @param {Response|XMLHttpRequest} response 1109 * @return {Bool} 1110 */ 1111 loadFromData: function(response) { 1112 var raw = this.extractDataFromHeaders(response); 1113 if (!raw) { 1114 return false; 1115 } 1116 1117 var data = this.parseHeaders(raw); 1118 if (data.error) { 1119 throw new Error('Error loading debugbar data: ' + data.error); 1120 } else if(data.data) { 1121 this.debugbar.addDataSet(data.data, data.id, "(ajax)", this.autoShow); 1122 } 1123 return true; 1124 }, 1125 1126 /** 1127 * Extract the data as a string from headers of an XMLHttpRequest 1128 * 1129 * @this {AjaxHandler} 1130 * @param {Response|XMLHttpRequest} response 1131 * @return {string} 1132 */ 1133 extractDataFromHeaders: function(response) { 1134 var data = this.getHeader(response, this.headerName); 1135 if (!data) { 1136 return; 1137 } 1138 for (var i = 1;; i++) { 1139 var header = this.getHeader(response, this.headerName + '-' + i); 1140 if (!header) { 1141 break; 1142 } 1143 data += header; 1144 } 1145 return decodeURIComponent(data); 1146 }, 1147 1148 /** 1149 * Parses the string data into an object 1150 * 1151 * @this {AjaxHandler} 1152 * @param {string} data 1153 * @return {string} 1154 */ 1155 parseHeaders: function(data) { 1156 return JSON.parse(data); 1157 }, 1158 1159 /** 1160 * Attaches an event listener to fetch 1161 * 1162 * @this {AjaxHandler} 1163 */ 1164 bindToFetch: function() { 1165 var self = this; 1166 var proxied = window.fetch; 1167 1168 if (proxied !== undefined && proxied.polyfill !== undefined) { 1169 return; 1170 } 1171 1172 window.fetch = function () { 1173 var promise = proxied.apply(this, arguments); 1174 1175 promise.then(function (response) { 1176 self.handle(response); 1177 }); 1178 1179 return promise; 1180 }; 1181 }, 1182 1183 /** 1184 * Attaches an event listener to jQuery.ajaxComplete() 1185 * 1186 * @this {AjaxHandler} 1187 * @param {jQuery} jq Optional 1188 */ 1189 bindToJquery: function(jq) { 1190 var self = this;
1191 jq(document).ajaxComplete(function(e, xhr, settings) { 1192 if (!settings.ignoreDebugBarAjaxHandler) { 1193 self.handle(xhr); 1194 } 1195 }); 1196 }, 1197 1198 /** 1199 * Attaches an event listener to XMLHttpRequest 1200 * 1201 * @this {AjaxHandler} 1202 */ 1203 bindToXHR: function() { 1204 var self = this; 1205 var proxied = XMLHttpRequest.prototype.open; 1206 XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { 1207 var xhr = this; 1208 this.addEventListener("readystatechange", function() { 1209 var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null; 1210 var href = (typeof url === 'string') ? url : url.href; 1211 1212 if (xhr.readyState == 4 && href.indexOf(skipUrl) !== 0) { 1213 self.handle(xhr); 1214 } 1215 }, false); 1216 proxied.apply(this, Array.prototype.slice.call(arguments)); 1217 }; 1218 } 1219 1220 }); 1221 1222})(PhpDebugBar.$);
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.