1if (typeof(PhpDebugBar) == 'undefined') { 2 // namespace 3 var PhpDebugBar = {}; 4 PhpDebugBar.$ = jQuery; 5} 6 7(function($) { 8 9 /** 10 * @namespace 11 */ 12 PhpDebugBar.Widgets = {}; 13 14 var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-'); 15 16 /** 17 * Replaces spaces with and line breaks with <br> 18 * 19 * @param {String} text 20 * @return {String} 21 */ 22 var htmlize = PhpDebugBar.Widgets.htmlize = function(text) { 23 return text.replace(/\n/g, '<br>').replace(/\s/g, " ") 24 }; 25 26 /** 27 * Returns a string representation of value, using JSON.stringify 28 * if it's an object. 29 * 30 * @param {Object} value 31 * @param {Boolean} prettify Uses htmlize() if true 32 * @return {String} 33 */ 34 var renderValue = PhpDebugBar.Widgets.renderValue = function(value, prettify) { 35 if (typeof(value) !== 'string') { 36 if (prettify) { 37 return htmlize(JSON.stringify(value, undefined, 2)); 38 } 39 return JSON.stringify(value); 40 } 41 return value; 42 }; 43 44 /** 45 * Highlights a block of code 46 * 47 * @param {String} code 48 * @param {String} lang 49 * @return {String} 50 */ 51 var highlight = PhpDebugBar.Widgets.highlight = function(code, lang) { 52 if (typeof(code) === 'string') { 53 if (typeof(hljs) === 'undefined') { 54 return htmlize(code); 55 } 56 if (lang && hljs.getLanguage(lang)) { 57 return hljs.highlight(code, {language: lang}).value; 58 } 59 return hljs.highlightAuto(code).value; 60 } 61 62 if (typeof(hljs) === 'object') { 63 code.each(function(i, e) { hljs.highlightElement(e); }); 64 } 65 return code; 66 }; 67 68 /** 69 * Creates a <pre> element with a block of code 70 * 71 * @param {String} code 72 * @param {String} lang 73 * @param {Number} [firstLineNumber] If provided, shows line numbers beginning with the given value. 74 * @param {Number} [highlightedLine] If provided, the given line number will be highlighted. 75 * @return {String} 76 */ 77 var createCodeBlock = PhpDebugBar.Widgets.createCodeBlock = function(code, lang, firstLineNumber, highlightedLine) { 78 var pre = $('<pre />').addClass(csscls('code-block')); 79 // Add a newline to prevent <code> element from vertically collapsing too far if the last 80 // code line was empty: that creates problems with the horizontal scrollbar being 81 // incorrectly positioned - most noticeable when line numbers are shown. 82 var codeElement = $('<code />').text(code + '\n').appendTo(pre); 83 84 // Format the code 85 if (lang) { 86 codeElement.addClass("language-" + lang); 87 } 88 highlight(codeElement).removeClass('hljs'); 89 90 // Show line numbers in a list 91 if (!isNaN(parseFloat(firstLineNumber))) { 92 var lineCount = code.split('\n').length; 93 var $lineNumbers = $('<ul />').prependTo(pre); 94 pre.children().addClass(csscls('numbered-code')); 95 for (var i = firstLineNumber; i < firstLineNumber + lineCount; i++) { 96 var li = $('<li />').text(i).appendTo($lineNumbers); 97 98 // Add a span with a special class if we are supposed to highlight a line. 99 if (highlightedLine === i) { 100 li.addClass(csscls('highlighted-line')).append('<span> </span>'); 101 } 102 } 103 } 104 105 return pre; 106 }; 107 108 var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) { 109 var d = dict, parts = key.split('.'); 110 for (var i = 0; i < parts.length; i++) { 111 if (!d[parts[i]]) { 112 return default_value; 113 } 114 d = d[parts[i]]; 115 } 116 return d; 117 } 118 119 // ------------------------------------------------------------------ 120 // Generic widgets 121 // ------------------------------------------------------------------ 122 123 /** 124 * Displays array element in a <ul> list 125 * 126 * Options: 127 * - data 128 * - itemRenderer: a function used to render list items (optional) 129 */ 130 var ListWidget = PhpDebugBar.Widgets.ListWidget = PhpDebugBar.Widget.extend({ 131 132 tagName: 'ul', 133 134 className: csscls('list'), 135 136 initialize: function(options) { 137 if (!options['itemRenderer']) { 138 options['itemRenderer'] = this.itemRenderer; 139 } 140 this.set(options); 141 }, 142 143 render: function() { 144 this.bindAttr(['itemRenderer', 'data'], function() { 145 this.$el.empty(); 146 if (!this.has('data')) { 147 return; 148 } 149 150 var data = this.get('data'); 151 for (var i = 0; i < data.length; i++) { 152 var li = $('<li />').addClass(csscls('list-item')).appendTo(this.$el); 153 this.get('itemRenderer')(li, data[i]); 154 } 155 }); 156 }, 157 158 /**
159 * Renders the content of a <li> element 160 * 161 * @param {jQuery} li The <li> element as a jQuery Object 162 * @param {Object} value An item from the data array 163 */ 164 itemRenderer: function(li, value) { 165 li.html(renderValue(value)); 166 } 167 168 }); 169 170 // ------------------------------------------------------------------ 171 172 /** 173 * Displays object property/value paris in a <dl> list 174 * 175 * Options: 176 * - data 177 * - itemRenderer: a function used to render list items (optional) 178 */ 179 var KVListWidget = PhpDebugBar.Widgets.KVListWidget = ListWidget.extend({ 180 181 tagName: 'dl', 182 183 className: csscls('kvlist'), 184 185 render: function() { 186 this.bindAttr(['itemRenderer', 'data'], function() { 187 this.$el.empty(); 188 if (!this.has('data')) { 189 return; 190 } 191 192 var self = this; 193 $.each(this.get('data'), function(key, value) { 194 var dt = $('<dt />').addClass(csscls('key')).appendTo(self.$el); 195 var dd = $('<dd />').addClass(csscls('value')).appendTo(self.$el); 196 self.get('itemRenderer')(dt, dd, key, value); 197 }); 198 }); 199 }, 200 201 /** 202 * Renders the content of the <dt> and <dd> elements 203 * 204 * @param {jQuery} dt The <dt> element as a jQuery Object 205 * @param {jQuery} dd The <dd> element as a jQuery Object 206 * @param {String} key Property name 207 * @param {Object} value Property value 208 */ 209 itemRenderer: function(dt, dd, key, value) { 210 dt.text(key); 211 dd.html(htmlize(value)); 212 } 213 214 }); 215 216 // ------------------------------------------------------------------ 217 218 /** 219 * An extension of KVListWidget where the data represents a list 220 * of variables 221 * 222 * Options: 223 * - data 224 */ 225 var VariableListWidget = PhpDebugBar.Widgets.VariableListWidget = KVListWidget.extend({ 226 227 className: csscls('kvlist varlist'), 228 229 itemRenderer: function(dt, dd, key, value) { 230 $('<span />').attr('title', key).text(key).appendTo(dt); 231 232 var v = value && value.value || value; 233 if (v && v.length > 100) { 234 v = v.substr(0, 100) + "..."; 235 } 236 var prettyVal = null; 237 dd.text(v).click(function() { 238 if (window.getSelection().type == "Range") { 239 return ''; 240 } 241 if (dd.hasClass(csscls('pretty'))) { 242 dd.text(v).removeClass(csscls('pretty')); 243 } else { 244 prettyVal = prettyVal || createCodeBlock(value); 245 dd.addClass(csscls('pretty')).empty().append(prettyVal); 246 } 247 }); 248 } 249 250 }); 251 252 // ------------------------------------------------------------------ 253 254 /** 255 * An extension of KVListWidget where the data represents a list 256 * of variables whose contents are HTML; this is useful for showing 257 * variable output from VarDumper's HtmlDumper. 258 * 259 * Options: 260 * - data 261 */ 262 var HtmlVariableListWidget = PhpDebugBar.Widgets.HtmlVariableListWidget = KVListWidget.extend({ 263 264 className: csscls('kvlist htmlvarlist'), 265 266 itemRenderer: function(dt, dd, key, value) { 267 $('<span />').attr('title', $('<i />').html(key ?? '').text()).html(key ?? '').appendTo(dt); 268 dd.html(value && value.value || value); 269 270 if (value && value.xdebug_link) { 271 var header = $('<span />').addClass(csscls('filename')).text(value.xdebug_link.filename + ( value.xdebug_link.line ? "#" + value.xdebug_link.line : '')); 272 if (value.xdebug_link) { 273 if (value.xdebug_link.ajax) { 274 $('<a title="' + value.xdebug_link.url + '"></a>').on('click', function () { 275 $.ajax(value.xdebug_link.url); 276 }).addClass(csscls('editor-link')).appendTo(header); 277 } else { 278 $('<a href="' + value.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 279 } 280 } 281 header.appendTo(dd); 282 } 283 } 284 285 }); 286 287 // ------------------------------------------------------------------ 288 289 /**
290 * Displays array element in a <table> list, columns keys map 291 * useful for showing a multiple values table 292 * 293 * Options: 294 * - data 295 * - key_map: list of keys to be displayed with an optional label 296 * example: {key1: label1, key2: label2} or [key1, key2] 297 */ 298 var TableVariableListWidget = PhpDebugBar.Widgets.TableVariableListWidget = PhpDebugBar.Widget.extend({ 299 300 tagName: 'div', 301 302 className: csscls('tablevarlist'), 303 304 render: function() { 305 this.bindAttr('data', function(data) { 306 this.$el.empty(); 307 308 if (!this.has('data')) { 309 return; 310 } 311 312 this.$table = $('<table />').addClass(csscls('tablevar')).appendTo(this.$el); 313 var $header = $('<tr />').addClass(csscls('header')).append('<td />').appendTo(this.$table); 314 var key_map = data.key_map || {value: 'Value'}; 315 316 if (Array.isArray(key_map)) { 317 key_map = Object.fromEntries(key_map.map(k => [k, null])); 318 } 319 320 $.each(key_map, function(key, label) { 321 var colTitle = $('<td />').text(label ?? key).appendTo($header); 322 323 if (data.badges && data.badges[key]) { 324 $('<span />').text(data.badges[key]).addClass(csscls('badge')).appendTo(colTitle); 325 } 326 }); 327 328 var self = this; 329 $.each(data.data, function(key, values) { 330 var $tr = $('<tr />').addClass(csscls('item')).appendTo(self.$table); 331 $('<td />').addClass(csscls('key')).text(key).appendTo($tr); 332 333 if (typeof values !== 'object' || values === null) { 334 $('<td />').addClass(csscls('value')).text(values ?? '').appendTo($tr); 335 return; 336 } 337 338 $.each(key_map, function(key) { 339 $('<td />').addClass(csscls('value')).text(values[key] ?? '').appendTo($tr); 340 }); 341 342 if (values.xdebug_link) { 343 var filename = $('<span />').addClass(csscls('filename')) 344 .text(values.xdebug_link.filename + ( values.xdebug_link.line ? "#" + values.xdebug_link.line : '')) 345 .appendTo($('<td />').addClass(csscls('editor')).appendTo($tr)); 346 if (values.xdebug_link.ajax) { 347 $('<a title="' + values.xdebug_link.url + '"></a>').on('click', function () { 348 $.ajax(values.xdebug_link.url); 349 }).addClass(csscls('editor-link')).appendTo(filename); 350 } else { 351 $('<a href="' + values.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(filename); 352 } 353 354 if (!data.xdebug_link) { 355 data.xdebug_link = true; 356 $header.append($('<td />')); 357 } 358 } 359 }); 360 361 if (!data.summary) return; 362 363 var $tr = $('<tr />').addClass(csscls('summary')).appendTo(self.$table); 364 $('<td />').addClass(csscls('key')).appendTo($tr); 365 366 if (typeof data.summary !== 'object' || data.summary === null) { 367 $('<td />').addClass(csscls('value')).text(data.summary ?? '').appendTo($tr); 368 } else { 369 $.each(key_map, function(key) { 370 $('<td />').addClass(csscls('value')).text(data.summary[key] ?? '').appendTo($tr); 371 }); 372 } 373 374 if (data.xdebug_link) { 375 $('<td />').appendTo($tr); 376 } 377 }); 378 } 379 }); 380 381 // ------------------------------------------------------------------ 382 383 /** 384 * Iframe widget 385 * 386 * Options: 387 * - data 388 */ 389 var IFrameWidget = PhpDebugBar.Widgets.IFrameWidget = PhpDebugBar.Widget.extend({ 390 391 tagName: 'iframe', 392 393 className: csscls('iframe'), 394 395 render: function() { 396 this.$el.attr({ 397 seamless: "seamless", 398 border: "0", 399 width: "100%", 400 height: "100%" 401 }); 402 this.bindAttr('data', function(url) { this.$el.attr('src', url); }); 403 } 404 405 }); 406 407 408 // ------------------------------------------------------------------ 409 // Collector specific widgets 410 // ------------------------------------------------------------------ 411 412 /** 413 * Widget for the MessagesCollector 414 * 415 * Uses ListWidget under the hood 416 * 417 * Options: 418 * - data 419 */ 420 var MessagesWidget = PhpDebugBar.Widgets.MessagesWidget = PhpDebugBar.Widget.extend({ 421 422 className: csscls('messages'), 423 424 render: function() { 425 var self = this; 426 427 this.$list = new ListWidget({ itemRenderer: function(li, value) { 428 if (value.message_html) { 429 var val = $('<span />').addClass(csscls('value')).html(value.message_html).appendTo(l
429i); 430 } else { 431 var m = value.message; 432 if (m.length > 100) { 433 m = m.substr(0, 100) + "..."; 434 } 435 436 var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li); 437 if (!value.is_string || value.message.length > 100) { 438 var prettyVal = value.message; 439 if (!value.is_string) { 440 prettyVal = null; 441 } 442 li.css('cursor', 'pointer').click(function () { 443 if (window.getSelection().type == "Range") { 444 return'' 445 } 446 if (val.hasClass(csscls('pretty'))) { 447 val.text(m).removeClass(csscls('pretty')); 448 } else { 449 prettyVal = prettyVal || createCodeBlock(value.message, 'php'); 450 val.addClass(csscls('pretty')).empty().append(prettyVal); 451 } 452 }); 453 } 454 } 455 if (value.xdebug_link) { 456 var header = $('<span />').addClass(csscls('filename')).text(value.xdebug_link.filename + ( value.xdebug_link.line ? "#" + value.xdebug_link.line : '')); 457 if (value.xdebug_link) { 458 if (value.xdebug_link.ajax) { 459 $('<a title="' + value.xdebug_link.url + '"></a>').on('click', function () { 460 $.ajax(value.xdebug_link.url); 461 }).addClass(csscls('editor-link')).appendTo(header); 462 } else { 463 $('<a href="' + value.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 464 } 465 } 466 header.prependTo(li); 467 } 468 if (value.collector) { 469 $('<span />').addClass(csscls('collector')).text(value.collector).prependTo(li); 470 } 471 if (value.label) { 472 val.addClass(csscls(value.label)); 473 $('<span />').addClass(csscls('label')).text(value.label).prependTo(li); 474 } 475 }}); 476 477 this.$list.$el.appendTo(this.$el); 478 this.$toolbar = $('<div><i class="phpdebugbar-fa phpdebugbar-fa-search"></i></div>').addClass(csscls('toolbar')).appendTo(this.$el); 479 480 $('<input type="text" name="search" aria-label="Search" placeholder="Search" />') 481 .on('change', function() { self.set('search', this.value); }) 482 .appendTo(this.$toolbar); 483 484 this.bindAttr('data', function(data) { 485 this.set({excludelabel: [], excludecollector: [], search: ''}); 486 this.$toolbar.find(csscls('.filter')).remove(); 487 488 var labels = [], collectors = [], self = this, 489 createFilterItem = function (type, value) { 490 $('<a />') 491 .addClass(csscls('filter')).addClass(csscls(type)) 492 .text(value).attr('rel', value) 493 .on('click', function() { self.onFilterClick(this, type); }) 494 .appendTo(self.$toolbar) 495 }; 496 497 data.forEach(function (item) { 498 if (!labels.includes(item.label || 'none')) { 499 labels.push(item.label || 'none'); 500 } 501 502 if (!collectors.includes(item.collector || 'none')) { 503 collectors.push(item.collector || 'none'); 504 } 505 }); 506 507 if (labels.length > 1) { 508 labels.forEach(label => createFilterItem('label', label)); 509 } 510 511 if (collectors.length === 1) { 512 return; 513 } 514 515 $('<a />').addClass(csscls('filter')).css('visibility', 'hidden').appendTo(self.$toolbar);
516 collectors.forEach(collector => createFilterItem('collector', collector)); 517 }); 518 519 this.bindAttr(['excludelabel', 'excludecollector', 'search'], function() { 520 var excludelabel = this.get('excludelabel') || [], 521 excludecollector = this.get('excludecollector') || [], 522 search = this.get('search'), 523 caseless = false, 524 fdata = []; 525 526 if (search && search === search.toLowerCase()) { 527 caseless = true; 528 } 529 530 this.get('data').forEach(function (item) { 531 var message = caseless ? item.message.toLowerCase() : item.message; 532 533 if ( 534 !excludelabel.includes(item.label || undefined) && 535 !excludecollector.includes(item.collector || undefined) && 536 (!search || message.indexOf(search) > -1) 537 ) { 538 fdata.push(item); 539 } 540 }); 541 542 this.$list.set('data', fdata); 543 }); 544 }, 545 546 onFilterClick: function(el, type) { 547 $(el).toggleClass(csscls('excluded')); 548 549 var excluded = []; 550 this.$toolbar.find(csscls('.filter') + csscls('.excluded') + csscls('.' + type)).each(function() { 551 excluded.push(this.rel === 'none' || !this.rel ? undefined : this.rel); 552 }); 553 554 this.set('exclude' + type, excluded); 555 } 556 557 }); 558 559 // ------------------------------------------------------------------ 560 561 /** 562 * Widget for the TimeDataCollector 563 * 564 * Options: 565 * - data 566 */ 567 var TimelineWidget = PhpDebugBar.Widgets.TimelineWidget = PhpDebugBar.Widget.extend({ 568 569 tagName: 'ul', 570 571 className: csscls('timeline'), 572 573 render: function() { 574 this.bindAttr('data', function(data) { 575 576 // ported from php DataFormatter 577 var formatDuration = function(seconds) { 578 if (seconds < 0.001) 579 return (seconds * 1000000).toFixed() + 'μs'; 580 else if (seconds < 0.1) 581 return (seconds * 1000).toFixed(2) + 'ms'; 582 else if (seconds < 1) 583 return (seconds * 1000).toFixed() + 'ms'; 584 return (seconds).toFixed(2) + 's'; 585 }; 586 587 // ported from php DataFormatter 588 var formatBytes = function formatBytes(size) { 589 if (size === 0 || size === null) { 590 return '0B'; 591 } 592 593 var sign = size < 0 ? '-' : '', 594 size = Math.abs(size), 595 base = Math.log(size) / Math.log(1024), 596 suffixes = ['B', 'KB', 'MB', 'GB', 'TB']; 597 return sign + (Math.round(Math.pow(1024, base - Math.floor(base)) * 100) / 100) + suffixes[Math.floor(base)]; 598 } 599 600 this.$el.empty(); 601 if (data.measures) { 602 var aggregate = {}; 603 604 for (var i = 0; i < data.measures.length; i++) { 605 var measure = data.measures[i]; 606 var group = measure.group || measure.label; 607 608 if(!aggregate[group]) { 609 aggregate[group] = { count: 0, duration: 0, memory : 0 }; 610 } 611 612 aggregate[group]['count'] += 1; 613 aggregate[group]['duration'] += measure.duration; 614 aggregate[group]['memory'] += (measure.memory || 0); 615 616 var m = $('<div />').addClass(csscls('measure')), 617 li = $('<li />'), 618 left = (measure.relative_start * 100 / data.duration).toFixed(2), 619 width = Math.min((measure.duration * 100 / data.duration).toFixed(2), 100 - left); 620 621 m.append($('<span />').addClass(csscls('value')).css({ 622 left: left + "%", 623 width: width + "%" 624 })); 625 m.append($('<span />').addClass(csscls('label')) 626 .text(measure.label.replace(/\s+/g, ' ') + ( measure.duration ? " (" + measure.duration_str +(measure.memory ? '/' + measure.memory_str: '') + ")" : ""))); 627 628 if (measure.collector) { 629 $('<span />').addClass(csscls('collector')).text(measure.collector).appendTo(m); 630 } 631
632 m.appendTo(li); 633 this.$el.append(li); 634 635 if (measure.params && !$.isEmptyObject(measure.params)) { 636 var table = $('<table><tr><th colspan="2">Params</th></tr></table>').hide().addClass(csscls('params')).appendTo(li); 637 for (var key in measure.params) { 638 if (typeof measure.params[key] !== 'function') { 639 table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') + 640 '"><pre><code>' + measure.params[key] + '</code></pre></td></tr>'); 641 } 642 } 643 li.css('cursor', 'pointer').click(function() { 644 if (window.getSelection().type == "Range") { 645 return ''; 646 } 647 var table = $(this).find('table'); 648 if (table.is(':visible')) { 649 table.hide(); 650 } else { 651 table.show(); 652 } 653 }).on('click', '.sf-dump', function(event) { 654 event.stopPropagation(); 655 }); 656 } 657 } 658 659 // convert to array and sort by duration 660 aggregate = $.map(aggregate, function(data, label) { 661 return { 662 label: label, 663 data: data 664 } 665 }).sort(function(a, b) { 666 return b.data.duration - a.data.duration 667 }); 668 669 // build table and add 670 var aggregateTable = $('<table></table>').addClass(csscls('params')); 671 $.each(aggregate, function(i, aggregate) { 672 width = Math.min((aggregate.data.duration * 100 / data.duration).toFixed(2), 100); 673 674 aggregateTable.append('<tr><td class="' + csscls('name') + '">' + 675 aggregate.data.count + ' x ' + $('<i />').text(aggregate.label.replace(/\s+/g, ' ')).html() + ' (' + width + '%)</td><td class="' + csscls('value') + '">' + 676 '<div class="' + csscls('measure') +'">' + 677 '<span class="' + csscls('value') + '"></span>' + 678 '<span class="' + csscls('label') + '">' + formatDuration(aggregate.data.duration) + (aggregate.data.memory ? '/' + formatBytes(aggregate.data.memory) : '') + '</span>' + 679 '</div></td></tr>'); 680 aggregateTable.find('span.' + csscls('value') + ':last').css({width: width + "%" }); 681 }); 682 683 this.$el.append('<li/>').find('li:last').append(aggregateTable); 684 } 685 }); 686 } 687 688 }); 689 690 // ------------------------------------------------------------------ 691 692 /** 693 * Widget for the displaying exceptions 694 * 695 * Options: 696 * - data 697 */ 698 var ExceptionsWidget = PhpDebugBar.Widgets.ExceptionsWidget = PhpDebugBar.Widget.extend({ 699 700 className: csscls('exceptions'), 701 702 render: function() { 703 this.$list = new ListWidget({ itemRenderer: function(li, e) { 704 $('<span />').addClass(csscls('message')).text(e.message) 705 .prepend(e.count>1 ? $('<span />').addClass(csscls('badge')).text(e.count+'x') : '').appendTo(li); 706 if (e.file) { 707 var header = $('<span />').addClass(csscls('filename')).text(e.file + "#" + e.line); 708 if (e.xdebug_link) { 709 if (e.xdebug_link.ajax) { 710 $('<a title="' + e.xdebug_link.url + '"></a>').on('click', function () { 711 fetch(e.xdebug_link.url); 712 }).addClass(csscls('editor-link')).appendTo(header); 713 } else { 714 $('<a href="' + e.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 715 } 716 }
717 header.appendTo(li); 718 } 719 if (e.type) { 720 $('<span />').addClass(csscls('type')).text(e.type).appendTo(li); 721 } 722 if (e.surrounding_lines) { 723 var startLine = (e.line - 3) <= 0 ? 1 : e.line - 3; 724 var pre = createCodeBlock(e.surrounding_lines.join(""), 'php', startLine, e.line).addClass(csscls('file')).appendTo(li); 725 if (!e.stack_trace_html) { 726 // This click event makes the var-dumper hard to use. 727 li.click(function () { 728 if (pre.is(':visible')) { 729 pre.hide(); 730 } else { 731 pre.show(); 732 } 733 }); 734 } 735 } 736 if (e.stack_trace_html) { 737 var $trace = $('<span />').addClass(csscls('filename')).html(e.stack_trace_html); 738 $trace.find('samp[data-depth="1"]').removeClass('sf-dump-expanded').addClass('sf-dump-compact').parent() 739 .find('>.sf-dump-note').html((_, t) => t.replace(/^array:/, '<span class="sf-dump-key">Stack Trace:</span> ') + ' files'); 740 $trace.appendTo(li); 741 } else if (e.stack_trace) { 742 e.stack_trace.split("\n").forEach(function (trace) { 743 var $traceLine = $('<div />'); 744 $('<span />').addClass(csscls('filename')).text(trace).appendTo($traceLine); 745 $traceLine.appendTo(li); 746 }); 747 } 748 }}); 749 this.$list.$el.appendTo(this.$el); 750 751 this.bindAttr('data', function(data) { 752 this.$list.set('data', data); 753 if (data.length == 1) { 754 this.$list.$el.children().first().find(csscls('.file')).show(); 755 } 756 }); 757 758 } 759 760 }); 761 762 /** 763 * Displays datasets in a table 764 * 765 */ 766 var DatasetWidget = PhpDebugBar.Widgets.DatasetWidget = PhpDebugBar.Widget.extend({ 767 768 initialize: function(options) { 769 if (!options['itemRenderer']) { 770 options['itemRenderer'] = this.itemRenderer; 771 } 772 this.set(options); 773 this.set('autoshow', null); 774 this.set('id', null); 775 this.set('sort', localStorage.getItem('debugbar-history-sort') || 'asc'); 776 this.$el.addClass(csscls('dataset-history')) 777 778 this.renderHead(); 779 }, 780 781 renderHead: function() { 782 this.$el.empty(); 783 this.$actions = $('<div />').addClass(csscls('dataset-actions')).appendTo(this.$el); 784 785 var self = this; 786 var debugbar = self.get('debugbar'); 787 788 this.$autoshow = $('<input type=checkbox>') 789 .on('click', function() { 790 if (debugbar.ajaxHandler) { 791 debugbar.ajaxHandler.setAutoShow($(this).is(':checked')); 792 } 793 if (debugbar.controls['__settings']) { 794 debugbar.controls['__settings'].get('widget').set('autoshow', this.autoShow); 795 } 796 }); 797 798 $('<label>Autoshow</label>') 799 .append(this.$autoshow) 800 .appendTo(this.$actions) 801 802 803 this.$clearbtn = $('<a>Clear</a>') 804 .appendTo(this.$actions) 805 .on('click', function() { 806 self.$table.empty(); 807 }); 808 809 this.$showBtn = $('<a>Show all</a>') 810 .appendTo(this.$actions) 811 .on('click', function() { 812 self.searchInput.val(null); 813 self.methodInput.val(null); 814 self.set('search', null); 815 self.set('method', null); 816 }); 817 818 this.methodInput = $('<select name="method" style="width:100px"><option>
818(method)</option><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select>') 819 .on('change', function() { self.set('method', this.value)}) 820 .appendTo(this.$actions) 821 822 this.searchInput = $('<input type="text" name="search" aria-label="Search" placeholder="Search" />') 823 .on('input', function() { self.set('search', this.value); }) 824 .appendTo(this.$actions); 825 826 827 this.$table = $('<tbody />'); 828 829 $('<table/>') 830 .append($('<thead/>') 831 .append($('<tr/>') 832 .append($('<th></th>').css('width', '30px')) 833 .append($('<th>Date â</th>').css('width', '175px').click(function() { 834 self.set('sort', self.get('sort') === 'asc' ? 'desc' : 'asc') 835 localStorage.setItem('debugbar-history-sort', self.get('sort')) 836 })) 837 .append($('<th>Method</th>').css('width', '80px')) 838 .append($('<th>URL</th>')) 839 .append($('<th width="40%">Data</th>'))) 840 ) 841 .append(this.$table) 842 .appendTo(this.$el); 843 844 845 }, 846 847 renderDatasets: function() { 848 this.$table.empty(); 849 var self = this; 850 $.each(this.get('data'), function(key, data) { 851 if (!data.__meta) { 852 return; 853 } 854 855 self.get('itemRenderer')(self, data); 856 }); 857 }, 858 859 render: function() { 860 this.bindAttr('data', function() { 861 if (this.get('autoshow') === null && this.get('debugbar').ajaxHandler) { 862 this.set('autoshow', this.get('debugbar').ajaxHandler.autoShow); 863 } 864 865 if (!this.has('data')) { 866 return; 867 } 868 869 // Render the latest item 870 var datasets = this.get('data'); 871 var data = datasets[Object.keys(datasets)[Object.keys(datasets).length - 1]] 872 if (!data.__meta) { 873 return; 874 } 875 876 this.get('itemRenderer')(this, data); 877 }); 878 this.bindAttr(['itemRenderer', 'search', 'method', 'sort'], function() { 879 this.renderDatasets(); 880 }) 881 this.bindAttr('autoshow', function() { 882 var autoshow = this.get('autoshow'); 883 this.$autoshow.prop('checked', autoshow); 884 }) 885 this.bindAttr('id', function() { 886 var id = this.get('id'); 887 this.$table.find('.' + csscls('active')).removeClass(csscls('active')); 888 this.$table.find('tr[data-id=' + id+']').addClass(csscls('active')); 889 }) 890 }, 891 892 /** 893 * Renders the content of a dataset item 894 * 895 * @param {Object} value An item from the data array 896 */ 897 itemRenderer: function(widget, data) { 898 var meta = data.__meta; 899 900 var $badges = $('<td />'); 901 var tr = $('<tr />'); 902 if (widget.get('sort') === 'asc') { 903 tr.appendTo(widget.$table); 904 } else { 905 tr.prependTo(widget.$table); 906 } 907 908 var clickHandler = function() { 909 var debugbar = widget.get('debugbar'); 910 debugbar.showDataSet(meta.id, debugbar.datesetTitleFormater.format('', data, meta.suffix, meta.nb)); 911 widget.$table.find('.' + csscls('active')).removeClass(csscls('active')); 912 tr.addClass(csscls('active')); 913 914 if ($(this).data('tab')) { 915 debugbar.showTab($(this).data('tab')); 916 } 917 } 918 919 tr.attr('data-id', meta['id']) 920 .append($('<td>#' + meta['nb'] + '</td>').click(clickHandler)) 921 .append($('<td>' + meta['datetime'] + '</td>').click(clickHandler)) 922 .append($('<td>' + meta['method'] + '</td>').click(clickHandler)) 923 .append($('<td />').append(meta['uri'] + (meta['suffix'] ? ' ' + meta['suffix'] : '')).click(clickHandler)) 924 .css('cursor', 'pointer') 925 .addClass(csscls('table-row')) 926 927 var debugbar = widget.get('debugbar'); 928 $.each(debugbar.dataMap, function(key, def) { 929 var d = getDictValue(data, def[0], def[1]); 930 if (key.indexOf(':') != -1) { 931 key = key.split(':'); 932 if (key[1] === 'badge' && d > 0) { 933 var control = debugbar.getControl(key[0]); 934 var $a = $('<a>').attr('title', control.get('title')).data('tab', key[0]); 935 if (control.$icon) { 936 $a.append(debugbar.getControl(key[0]).$icon.clone()); 937 } 938 if (control.$badge) { 939 $a.append(debugbar.getControl(key[0]).$badge.clone().css('display', 'inline-block').text(d)); 940 } 941 $a.appendTo($badges).click(clickHandler); 942 } else if (key[1] === 'tooltip') { 943 debugbar.getControl(key[0]).set('tooltip', d); 944 } 945 } 946 }); 947 tr.append($badges); 948 949 if (debugbar.activeDatasetId === meta['id']) { 950 tr.addClass(csscls('active')); 951 } 952 953 var search = widget.get('search'); 954 var method = widget.get('method'); 955 if ((search && meta['uri'].indexOf(search) == -1) || (method && meta['method'] !== method)) { 956 tr.hide(); 957 } 958 } 959 960 }); 961 962 963 964})(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.