PageSourceSearch

https://meresofarabia.com/media/vendor/debugbar/debugbar.js

js meresofarabia.com collected 2026-10-07 23:03:36 UTC 38,345 bytes, 1,222 lines download raw bytes

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.