[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 if (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.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header); 481 this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header); 482 var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el); 483 this.recomputeBottomOffset(); 484 485 // dragging of resize handle 486 var pos_y, orig_h; 487 this.$resizehdle.on('mousedown', function(e) { 488 orig_h = $body.height(), pos_y = e.pageY; 489 $body.parents().on('mousemove', mousemove).on('mouseup', mouseup); 490 self.$dragCapture.show(); 491 e.preventDefault(); 492 }); 493 var mousemove = function(e) { 494 var h = orig_h + (pos_y - e.pageY); 495 self.setHeight(h); 496 }; 497 var mouseup = function() { 498 $body.parents().off('mousemove', mousemove).off('mouseup', mouseup); 499 self.$dragCapture.hide(); 500 }; 501 502 // close button 503 this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight); 504 this.$closebtn.click(function() { 505 self.close(); 506 }); 507 508 // minimize button 509 this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight); 510 this.$minimizebtn.click(function() { 511 self.minimize(); 512 }); 513 514 // maximize button 515 this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight); 516 this.$maximizebtn.click(function() { 517 self.restore(); 518 }); 519 520 // restore button 521 this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el); 522 this.$restorebtn.click(function() { 523 self.restore(); 524 }); 525 526 // open button 527 this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide(); 528 this.$openbtn.click(function() { 529 self.openHandler.show(function(id, dataset) { 530 self.addDataSet(dataset, id, "(opened)"); 531 self.showTab(); 532 }); 533 }); 534 535 // select box for data sets 536 this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).appendTo(this.$headerRight); 537 this.$datasets.change(function() { 538 self.dataChangeHandler(self.datasets[this.value]); 539 self.showTab(); 540 }); 541 }, 542 543 /** 544 * Sets the height of the debugbar body section 545 * Forces the height to lie within a reasonable range 546 * Stores the height in local storage so it can be restored 547 * Resets the document body bottom offset 548 * 549 * @this {DebugBar} 550 */ 551 setHeight: function(height) { 552 var min_h = 40; 553 var max_h = $(window).innerHeight() - this.$header.height() - 10; 554 height = Math.min(height, max_h); 555 height = Math.max(height, min_h); 556 this.$body.css('height', height); 557 localStorage.setItem('phpdebugbar-height', height); 558 this.recomputeBottomOffset(); 559 }, 560 561 /** 562 * Restores the state of the DebugBar using localStorage 563 * This is not called by default in the constructor and 564 * needs to be called by subclasses in their init() method 565 * 566 * @this {DebugBar} 567 */ 568 restoreState: function() { 569 // bar height 570 var height = localStorage.getItem('phpdebugbar-height'); 571 this.setHeight(height || this.$body.height()); 572 573 // bar visibility 574 var open = localStorage.getItem('phpdebugbar-open'); 575 if (open && open == '0') { 576 this.close(); 577 } else { 578 var visible = localStorage.getItem('phpdebugbar-visible'); 579 if (visible && visible == '1') { 580 var tab = localStorage.getItem('phpdebugbar-tab'); 581 if (this.isTab(tab)) { 582 this.showTab(tab); 583 } 584 } 585 } 586 }, 587 588 /** 589 * Creates and adds a new tab 590 * 591 * @this {DebugBar} 592 * @param {String} name Internal name 593 * @param {Object} widget A widget object with an element property 594 * @param {String} title The text in the tab, if not specified, name will be used 595 * @return {Tab} 596 */ 597 createTab: function(name, widget, title) { 598 var tab = new Tab({ 599 title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), 600 widget: widget 601 }); 602 return this.addTab(name, tab); 603 }, 604 605 /** 606 * Adds a new tab 607 * 608 * @this {DebugBar} 609 * @param {String} name Internal name 610 * @param {Tab} tab Tab object 611 * @return {Tab} 612 */ 613 addTab: function(name, tab) { 614 if (this.isControl(name)) { 615 throw new Error(name + ' already exists'); 616 } 617 618 var self = this; 619 tab.$tab.appendTo(this.$headerLeft).click(function() { 620 if (!self.isMinimized() && self.activePanelName == name) { 621 self.minimize(); 622 } else { 623 self.showTab(name); 624 } 625 }); 626 tab.$el.appendTo(this.$body); 627 628 this.controls[name] = tab; 629 if (this.firstTabName == null) { 630 this.firstTabName = name; 631 } 632 return tab; 633 }, 634 635 /** 636 * Creates and adds an indicator 637 * 638 * @this {DebugBar} 639 * @param {String} name Internal name 640 * @param {String} icon 641 * @param {String} tooltip 642 * @param {String} position "right" or "left", default is "right" 643 * @return {Indicator} 644 */ 645 createIndicator: function(name, icon, tooltip, position) { 646 var indicator = new Indicator({ 647 icon: icon, 648 tooltip: tooltip 649 }); 650 return this.addIndicator(name, indicator, position); 651 }, 652 653 /** 654 * Adds an indicator 655 * 656 * @this {DebugBar} 657 * @param {String} name Internal name 658 * @param {Indicator} indicator Indicator object 659 * @return {Indicator} 660 */ 661 addIndicator: function(name, indicator, position) { 662 if (this.isControl(name)) { 663 throw new Error(name + ' already exists'); 664 } 665 666 if (position == 'left') { 667 indicator.$el.insertBefore(this.$headerLeft.children().first()); 668 } else { 669 indicator.$el.appendTo(this.$headerRight); 670 } 671 672 this.controls[name] = indicator; 673 return indicator; 674 }, 675 676 /** 677 * Returns a control 678 * 679 * @param {String} name 680 * @return {Object} 681 */ 682 getControl: function(name) { 683 if (this.isControl(name)) { 684 return this.controls[name]; 685 } 686 }, 687 688 /** 689 * Checks if there's a control under the specified name 690 * 691 * @this {DebugBar} 692 * @param {String} name 693 * @return {Boolean} 694 */ 695 isControl: function(name) { 696 return typeof(this.controls[name]) != 'undefined'; 697 }, 698 699 /** 700 * Checks if a tab with the specified name exists 701 * 702 * @this {DebugBar} 703 * @param {String} name 704 * @return {Boolean} 705 */ 706 isTab: function(name) { 707 return this.isControl(name) && this.controls[name] instanceof Tab; 708 }, 709 710 /** 711 * Checks if an indicator with the specified name exists 712 * 713 * @this {DebugBar} 714 * @param {String} name 715 * @return {Boolean} 716 */ 717 isIndicator: function(name) { 718 return this.isControl(name) && this.controls[name] instanceof Indicator; 719 }, 720 721 /** 722 * Removes all tabs and indicators from the debug bar and hides it 723 * 724 * @this {DebugBar} 725 */ 726 reset: function() { 727 this.minimize(); 728 var self = this; 729 $.each(this.controls, function(name, control) { 730 if (self.isTab(name)) { 731 control.$tab.remove(); 732 } 733 control.$el.remove(); 734 }); 735 this.controls = {}; 736 }, 737 738 /** 739 * Open the debug bar and display the specified tab 740 * 741 * @this {DebugBar} 742 * @param {String} name If not specified, display the first tab 743 */ 744 showTab: function(name) { 745 if (!name) { 746 if (this.activePanelName) { 747 name = this.activePanelName; 748 } else { 749 name = this.firstTabName; 750 } 751 } 752 753 if (!this.isTab(name)) { 754 throw new Error("Unknown tab '" + name + "'"); 755 } 756 757 this.$resizehdle.show(); 758 this.$body.show(); 759 this.recomputeBottomOffset(); 760 761 $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active')); 762 $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active')); 763 764 this.controls[name].$tab.addClass(csscls('active')); 765 this.controls[name].$el.addClass(csscls('active')); 766 this.activePanelName = name; 767 768 this.$el.removeClass(csscls('minimized')); 769 localStorage.setItem('phpdebugbar-visible', '1'); 770 localStorage.setItem('phpdebugbar-tab', name); 771 this.resize(); 772 }, 773 774 /** 775 * Hide panels and minimize the debug bar 776 * 777 * @this {DebugBar} 778 */ 779 minimize: function() { 780 this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active')); 781 this.$body.hide(); 782 this.$resizehdle.hide(); 783 this.recomputeBottomOffset(); 784 localStorage.setItem('phpdebugbar-visible', '0'); 785 this.$el.addClass(csscls('minimized')); 786 this.resize(); 787 }, 788 789 /** 790 * Checks if the panel is minimized 791 * 792 * @return {Boolean} 793 */ 794 isMinimized: function() { 795 return this.$el.hasClass(csscls('minimized')); 796 }, 797 798 /** 799 * Close the debug bar 800 * 801 * @this {DebugBar} 802 */ 803 close: function() { 804 this.$resizehdle.hide(); 805 this.$header.hide(); 806 this.$body.hide(); 807 this.$restorebtn.show(); 808 localStorage.setItem('phpdebugbar-open', '0'); 809 this.$el.addClass(csscls('closed')); 810 this.recomputeBottomOffset(); 811 }, 812 813 /** 814 * Checks if the panel is closed 815 * 816 * @return {Boolean} 817 */ 818 isClosed: function() { 819 return this.$el.hasClass(csscls('closed')); 820 }, 821 822 /** 823 * Restore the debug bar 824 * 825 * @this {DebugBar} 826 */ 827 restore: function() { 828 this.$resizehdle.show(); 829 this.$header.show(); 830 this.$restorebtn.hide(); 831 localStorage.setItem('phpdebugbar-open', '1'); 832 var tab = localStorage.getItem('phpdebugbar-tab'); 833 if (this.isTab(tab)) { 834 this.showTab(tab); 835 } else { 836 this.showTab(); 837 } 838 this.$el.removeClass(csscls('closed')); 839 this.resize(); 840 }, 841 842 /** 843 * Recomputes the margin-bottom css property of the body so 844 * that the debug bar never hides any content 845 */ 846 recomputeBottomOffset: function() { 847 if (this.options.bodyMarginBottom) { 848 if (this.isClosed()) { 849 return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || ''); 850 } 851 852 var offset = parseInt(this.$el.height()) + (this.options.bodyMarginBottomHeight || 0); 853 $('body').css('margin-bottom', offset); 854 } 855 }, 856 857 /** 858 * Sets the data map used by dataChangeHandler to populate 859 * indicators and widgets 860 * 861 * A data map is an object where properties are control names. 862 * The value of each property should be an array where the first 863 * item is the name of a property from the data object (nested properties 864 * can be specified) and the second item the default value. 865 * 866 * Example: 867 * {"memory": ["memory.peak_usage_str", "0B"]} 868 * 869 * @this {DebugBar} 870 * @param {Object} map 871 */ 872 setDataMap: function(map) { 873 this.dataMap = map; 874 }, 875 876 /** 877 * Same as setDataMap() but appends to the existing map 878 * rather than replacing it 879 * 880 * @this {DebugBar} 881 * @param {Object} map 882 */ 883 addDataMap: function(map) { 884 $.extend(this.dataMap, map); 885 }, 886 887 /** 888 * Resets datasets and add one set of data 889 * 890 * For this method to be usefull, you need to specify 891 * a dataMap using setDataMap() 892 * 893 * @this {DebugBar} 894 * @param {Object} data 895 * @return {String} Dataset's id 896 */ 897 setData: function(data) { 898 this.datasets = {}; 899 return this.addDataSet(data); 900 }, 901 902 /** 903 * Adds a dataset 904 * 905 * If more than one dataset are added, the dataset selector 906 * will be displayed. 907 * 908 * For this method to be usefull, you need to specify 909 * a dataMap using setDataMap() 910 * 911 * @this {DebugBar} 912 * @param {Object} data 913 * @param {String} id The name of this set, optional 914 * @param {String} suffix 915 * @param {Bool} show Whether to show the new dataset, optional (default: true) 916 * @return {String} Dataset's id 917 */ 918 addDataSet: function(data, id, suffix, show) { 919 var label = this.datesetTitleFormater.format(id, data, suffix); 920 id = id || (getObjectSize(this.datasets) + 1); 921 this.datasets[id] = data; 922 923 this.$datasets.append($('<option value="' + id + '">' + label + '</option>')); 924 if (this.$datasets.children().length > 1) { 925 this.$datasets.show(); 926 } 927 928 if (typeof(show) == 'undefined' || show) { 929 this.showDataSet(id); 930 } 931 return id; 932 }, 933 934 /** 935 * Loads a dataset using the open handler 936 * 937 * @param {String} id 938 * @param {Bool} show Whether to show the new dataset, optional (default: true) 939 */ 940 loadDataSet: function(id, suffix, callback, show) { 941 if (!this.openHandler) { 942 throw new Error('loadDataSet() needs an open handler'); 943 } 944 var self = this; 945 this.openHandler.load(id, function(data) { 946 self.addDataSet(data, id, suffix, show); 947 self.resize(); 948 callback && callback(data); 949 }); 950 }, 951 952 /** 953 * Returns the data from a dataset 954 * 955 * @this {DebugBar} 956 * @param {String} id 957 * @return {Object} 958 */ 959 getDataSet: function(id) { 960 return this.datasets[id]; 961 }, 962 963 /** 964 * Switch the currently displayed dataset 965 * 966 * @this {DebugBar} 967 * @param {String} id 968 */ 969 showDataSet: function(id) { 970 this.dataChangeHandler(this.datasets[id]); 971 this.$datasets.val(id); 972 }, 973 974 /** 975 * Called when the current dataset is modified. 976 * 977 * @this {DebugBar} 978 * @param {Object} data 979 */ 980 dataChangeHandler: function(data) { 981 var self = this; 982 $.each(this.dataMap, function(key, def) { 983 var d = getDictValue(data, def[0], def[1]); 984 if (key.indexOf(':') != -1) { 985 key = key.split(':'); 986 self.getControl(key[0]).set(key[1], d); 987 } else { 988 self.getControl(key).set('data', d); 989 } 990 }); 991 }, 992 993 /** 994 * Sets the handler to open past dataset 995 * 996 * @this {DebugBar} 997 * @param {object} handler 998 */ 999 setOpenHandler: function(handler) { 1000 this.openHandler = handler; 1001 if (handler !== null) { 1002 this.$openbtn.show(); 1003 } else { 1004 this.$openbtn.hide(); 1005 } 1006 }, 1007 1008 /** 1009 * Returns the handler to open past dataset 1010 * 1011 * @this {DebugBar} 1012 * @return {object} 1013 */ 1014 getOpenHandler: function() { 1015 return this.openHandler; 1016 } 1017 1018 }); 1019 1020 DebugBar.Tab = Tab; 1021 DebugBar.Indicator = Indicator; 1022 1023 // ------------------------------------------------------------------ 1024 1025 /** 1026 * AjaxHandler 1027 * 1028 * Extract data from headers of an XMLHttpRequest and adds a new dataset 1029 * 1030 * @param {Bool} autoShow Whether to immediately show new datasets, optional (default: true) 1031 */ 1032 var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName, autoShow) { 1033 this.debugbar = debugbar; 1034 this.headerName = headerName || 'phpdebugbar'; 1035 this.autoShow = typeof(autoShow) == 'undefined' ? true : autoShow; 1036 }; 1037 1038 $.extend(AjaxHandler.prototype, { 1039 1040 /** 1041 * Handles a Fetch API Response or an XMLHttpRequest 1042 * 1043 * @this {AjaxHandler} 1044 * @param {Response|XMLHttpRequest} response 1045 * @return {Bool} 1046 */ 1047 handle: function(response) { 1048 // Check if the debugbar header is available 1049 if (this.isFetch(response) && !response.headers.has(this.headerName + '-id')) { 1050 return true; 1051 } else if (this.isXHR(response) && response.getAllResponseHeaders().indexOf(this.headerName) === -1) { 1052 return true; 1053 } 1054 if (!this.loadFromId(response)) { 1055 return this.loadFromData(response); 1056 } 1057 return true; 1058 }, 1059 1060 getHeader: function(response, header) { 1061 if (this.isFetch(response)) { 1062 return response.headers.get(header) 1063 } 1064 1065 return response.getResponseHeader(header) 1066 }, 1067 1068 isFetch: function(response) { 1069 return Object.prototype.toString.call(response) == '[object Response]' 1070 }, 1071 1072 isXHR: function(response) { 1073 return Object.prototype.toString.call(response) == '[object XMLHttpRequest]' 1074 }, 1075 1076 /** 1077 * Checks if the HEADER-id exists and loads the dataset using the open handler 1078 * 1079 * @param {Response|XMLHttpRequest} response 1080 * @return {Bool} 1081 */ 1082 loadFromId: function(response) { 1083 var id = this.extractIdFromHeaders(response); 1084 if (id && this.debugbar.openHandler) { 1085 this.debugbar.loadDataSet(id, "(ajax)", undefined, this.autoShow); 1086 return true; 1087 } 1088 return false; 1089 }, 1090 1091 /** 1092 * Extracts the id from the HEADER-id 1093 * 1094 * @param {Response|XMLHttpRequest} response 1095 * @return {String} 1096 */ 1097 extractIdFromHeaders: function(response) { 1098 return this.getHeader(response, this.headerName + '-id'); 1099 }, 1100 1101 /** 1102 * Checks if the HEADER exists and loads the dataset 1103 * 1104 * @param {Response|XMLHttpRequest} response 1105 * @return {Bool} 1106 */ 1107 loadFromData: function(response) { 1108 var raw = this.extractDataFromHeaders(response); 1109 if (!raw) { 1110 return false; 1111 } 1112 1113 var data = this.parseHeaders(raw); 1114 if (data.error) { 1115 throw new Error('Error loading debugbar data: ' + data.error); 1116 } else if(data.data) { 1117 this.debugbar.addDataSet(data.data, data.id, "(ajax)", this.autoShow); 1118 } 1119 return true; 1120 }, 1121 1122 /** 1123 * Extract the data as a string from headers of an XMLHttpRequest 1124 * 1125 * @this {AjaxHandler} 1126 * @param {Response|XMLHttpRequest} response 1127 * @return {string} 1128 */ 1129 extractDataFromHeaders: function(response) { 1130 var data = this.getHeader(response, this.headerName); 1131 if (!data) { 1132 return; 1133 } 1134 for (var i = 1;; i++) { 1135 var header = this.getHeader(response, this.headerName + '-' + i); 1136 if (!header) { 1137 break; 1138 } 1139 data += header; 1140 } 1141 return decodeURIComponent(data); 1142 }, 1143 1144 /** 1145 * Parses the string data into an object 1146 * 1147 * @this {AjaxHandler} 1148 * @param {string} data 1149 * @return {string} 1150 */ 1151 parseHeaders: function(data) { 1152 return JSON.parse(data); 1153 }, 1154 1155 /** 1156 * Attaches an event listener to fetch 1157 * 1158 * @this {AjaxHandler} 1159 */ 1160 bindToFetch: function() { 1161 var self = this; 1162 var proxied = window.fetch; 1163 1164 if (proxied !== undefined && proxied.polyfill !== undefined) { 1165 return; 1166 } 1167 1168 window.fetch = function () { 1169 var promise = proxied.apply(this, arguments); 1170 1171 promise.then(function (response) { 1172 self.handle(response); 1173 }); 1174 1175 return promise; 1176 }; 1177 }, 1178 1179 /** 1180 * Attaches an event listener to jQuery.ajaxComplete() 1181 * 1182 * @this {AjaxHandler} 1183 * @param {jQuery} jq Optional 1184 */ 1185 bindToJquery: function(jq) { 1186 var self = this; 1187 jq(document).ajaxComplete(function(e, xhr, settings) { 1188 if (!settings.ignoreDebugBarAjaxHandler) { 1189 self.handle(xhr); 1190 } 1191 }); 1192 }, 1193 1194 /** 1195 * Attaches an event listener to XMLHttpRequest 1196 * 1197 * @this {AjaxHandler} 1198 */ 1199 bindToXHR: function() { 1200 var self = this; 1201 var proxied = XMLHttpRequest.prototype.open; 1202 XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { 1203 var xhr = this; 1204 this.addEventListener("readystatechange", function() { 1205 var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null; 1206 if (xhr.readyState == 4 && url.indexOf(skipUrl) !== 0) { 1207 self.handle(xhr); 1208 } 1209 }, false); 1210 proxied.apply(this, Array.prototype.slice.call(arguments)); 1211 }; 1212 } 1213 1214 }); 1215 1216 })(PhpDebugBar.$);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |