if (typeof(PhpDebugBar) == 'undefined') { // namespace var PhpDebugBar = {}; PhpDebugBar.$ = jQuery; } (function($) { if (typeof(localStorage) == 'undefined') { // provide mock localStorage object for dumb browsers localStorage = { setItem: function(key, value) {}, getItem: function(key) { return null; } }; } if (typeof(PhpDebugBar.utils) == 'undefined') { PhpDebugBar.utils = {}; } /** * Returns the value from an object property. * Using dots in the key, it is possible to retrieve nested property values * * @param {Object} dict * @param {String} key * @param {Object} default_value * @return {Object} */ var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) { var d = dict, parts = key.split('.'); for (var i = 0; i < parts.length; i++) { if (!d[parts[i]]) { return default_value; } d = d[parts[i]]; } return d; } /** * Counts the number of properties in an object * * @param {Object} obj * @return {Integer} */ var getObjectSize = PhpDebugBar.utils.getObjectSize = function(obj) { if (Object.keys) { return Object.keys(obj).length; } var count = 0; for (var k in obj) { if (obj.hasOwnProperty(k)) { count++; } } return count; } /** * Returns a prefixed css class name * * @param {String} cls * @return {String} */ PhpDebugBar.utils.csscls = function(cls, prefix) { if (cls.indexOf(' ') > -1) { var clss = cls.split(' '), out = []; for (var i = 0, c = clss.length; i < c; i++) { out.push(PhpDebugBar.utils.csscls(clss[i], prefix)); } return out.join(' '); } if (cls.indexOf('.') === 0) { return '.' + prefix + cls.substr(1); } return prefix + cls; }; /** * Creates a partial function of csscls where the second * argument is already defined * * @param {string} prefix * @return {Function} */ PhpDebugBar.utils.makecsscls = function(prefix) { var f = function(cls) { return PhpDebugBar.utils.csscls(cls, prefix); }; return f; } var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-'); // ------------------------------------------------------------------ /** * Base class for all elements with a visual component * * @param {Object} options * @constructor */ var Widget = PhpDebugBar.Widget = function(options) { this._attributes = $.extend({}, this.defaults); this._boundAttributes = {}; this.$el = $('<' + this.tagName + ' />'); if (this.className) { this.$el.addClass(this.className); } this.initialize.apply(this, [options || {}]); this.render.apply(this); }; $.extend(Widget.prototype, { tagName: 'div', className: null, defaults: {}, /** * Called after the constructor * * @param {Object} options */ initialize: function(options) { this.set(options); }, /** * Called after the constructor to render the element */ render: function() {}, /** * Sets the value of an attribute * * @param {String} attr Can also be an object to set multiple attributes at once * @param {Object} value */ set: function(attr, value) { if (typeof(attr) != 'string') { for (var k in attr) { this.set(k, attr[k]); } return; } this._attributes[attr] = value; if (typeof(this._boundAttributes[attr]) !== 'undefined') { for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) { this._boundAttributes[attr][i].apply(this, [value]); } } }, /** * Checks if an attribute exists and is not null * * @param {String} attr * @return {[type]} [description] */ has: function(attr) { return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null; }, /** * Returns the value of an attribute * * @param {String} attr * @return {Object} */ get: function(attr) { return this._attributes[attr]; }, /** * Registers a callback function that will be called whenever the value of the attribute changes * * If cb is a jQuery element, text() will be used to fill the element * * @param {String} attr * @param {Function} cb */ bindAttr: function(attr, cb) { if ($.isArray(attr)) { for (var i = 0, c = attr.length; i < c; i++) { this.bindAttr(attr[i], cb); } return; } if (typeof(this._boundAttributes[attr]) == 'undefined') { this._boundAttributes[attr] = []; } if (typeof(cb) == 'object') { var el = cb; cb = function(value) { el.text(value || ''); }; } this._boundAttributes[attr].push(cb); if (this.has(attr)) { cb.apply(this, [this._attributes[attr]]); } } }); /** * Creates a subclass * * Code from Backbone.js * * @param {Array} props Prototype properties * @return {Function} */ Widget.extend = function(props) { var parent = this; var child = function() { return parent.apply(this, arguments); }; $.extend(child, parent); var Surrogate = function() { this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; $.extend(child.prototype, props); child.__super__ = parent.prototype; return child; }; // ------------------------------------------------------------------ /** * Tab * * A tab is composed of a tab label which is always visible and * a tab panel which is visible only when the tab is active. * * The panel must contain a widget. A widget is an object which has * an element property containing something appendable to a jQuery object. * * Options: * - title * - badge * - widget * - data: forward data to widget data */ var Tab = Widget.extend({ className: csscls('panel'), render: function() { this.$tab = $('').addClass(csscls('tab')); this.$icon = $('').appendTo(this.$tab); this.bindAttr('icon', function(icon) { if (icon) { this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon); } else { this.$icon.attr('class', ''); } }); this.bindAttr('title', $('').addClass(csscls('text')).appendTo(this.$tab)); this.$badge = $('').addClass(csscls('badge')).appendTo(this.$tab); this.bindAttr('badge', function(value) { if (value !== null) { this.$badge.text(value); this.$badge.addClass(csscls('visible')); } else { this.$badge.removeClass(csscls('visible')); } }); this.bindAttr('widget', function(widget) { this.$el.empty().append(widget.$el); }); this.bindAttr('data', function(data) { if (this.has('widget')) { this.get('widget').set('data', data); } }) } }); // ------------------------------------------------------------------ /** * Indicator * * An indicator is a text and an icon to display single value information * right inside the always visible part of the debug bar * * Options: * - icon * - title * - tooltip * - data: alias of title */ var Indicator = Widget.extend({ tagName: 'span', className: csscls('indicator'), render: function() { this.$icon = $('').appendTo(this.$el); this.bindAttr('icon', function(icon) { if (icon) { this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon); } else { this.$icon.attr('class', ''); } }); this.bindAttr(['title', 'data'], $('').addClass(csscls('text')).appendTo(this.$el)); this.$tooltip = $('').addClass(csscls('tooltip disabled')).appendTo(this.$el); this.bindAttr('tooltip', function(tooltip) { if (tooltip) { this.$tooltip.text(tooltip).removeClass(csscls('disabled')); } else { this.$tooltip.addClass(csscls('disabled')); } }); } }); // ------------------------------------------------------------------ /** * Dataset title formater * * Formats the title of a dataset for the select box */ var DatasetTitleFormater = PhpDebugBar.DatasetTitleFormater = function(debugbar) { this.debugbar = debugbar; }; $.extend(DatasetTitleFormater.prototype, { /** * Formats the title of a dataset * * @this {DatasetTitleFormater} * @param {String} id * @param {Object} data * @param {String} suffix * @return {String} */ format: function(id, data, suffix) { if (suffix) { suffix = ' ' + suffix; } else { suffix = ''; } var nb = getObjectSize(this.debugbar.datasets) + 1; if (typeof(data['__meta']) === 'undefined') { return "#" + nb + suffix; } var uri = data['__meta']['uri'], filename; if (uri.length && uri.charAt(uri.length - 1) === '/') { // URI ends in a trailing /: get the portion before then to avoid returning an empty string filename = uri.substr(0, uri.length - 1); // strip trailing '/' filename = filename.substr(filename.lastIndexOf('/') + 1); // get last path segment filename += '/'; // add the trailing '/' back } else { filename = uri.substr(uri.lastIndexOf('/') + 1); } // truncate the filename in the label, if it's too long var maxLength = 150; if (filename.length > maxLength) { filename = filename.substr(0, maxLength) + '...'; } var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')'; return label; } }); // ------------------------------------------------------------------ /** * DebugBar * * Creates a bar that appends itself to the body of your page * and sticks to the bottom. * * The bar can be customized by adding tabs and indicators. * A data map is used to fill those controls with data provided * from datasets. */ var DebugBar = PhpDebugBar.DebugBar = Widget.extend({ className: "phpdebugbar " + csscls('minimized'), options: { bodyMarginBottom: true, bodyMarginBottomHeight: 0 }, initialize: function() { this.controls = {}; this.dataMap = {}; this.datasets = {}; this.firstTabName = null; this.activePanelName = null; this.datesetTitleFormater = new DatasetTitleFormater(this); this.options.bodyMarginBottomHeight = parseInt($('body').css('margin-bottom')); this.registerResizeHandler(); }, /** * Register resize event, for resize debugbar with reponsive css. * * @this {DebugBar} */ registerResizeHandler: function() { if (typeof this.resize.bind == 'undefined') return; var f = this.resize.bind(this); this.respCSSSize = 0; $(window).resize(f); setTimeout(f, 20); }, /** * Resizes the debugbar to fit the current browser window */ resize: function() { var contentSize = this.respCSSSize; if (this.respCSSSize == 0) { this.$header.find("> div > *:visible").each(function () { contentSize += $(this).outerWidth(); }); } var currentSize = this.$header.width(); var cssClass = "phpdebugbar-mini-design"; var bool = this.$header.hasClass(cssClass); if (currentSize <= contentSize && !bool) { this.respCSSSize = contentSize; this.$header.addClass(cssClass); } else if (contentSize < currentSize && bool) { this.respCSSSize = 0; this.$header.removeClass(cssClass); } // Reset height to ensure bar is still visible this.setHeight(this.$body.height()); }, /** * Initialiazes the UI * * @this {DebugBar} */ render: function() { var self = this; this.$el.appendTo('body'); this.$dragCapture = $('
').addClass(csscls('drag-capture')).appendTo(this.$el); this.$resizehdle = $('
').addClass(csscls('resize-handle')).appendTo(this.$el); this.$header = $('
').addClass(csscls('header')).appendTo(this.$el); this.$headerLeft = $('
').addClass(csscls('header-left')).appendTo(this.$header); this.$headerRight = $('
').addClass(csscls('header-right')).appendTo(this.$header); var $body = this.$body = $('
').addClass(csscls('body')).appendTo(this.$el); this.recomputeBottomOffset(); // dragging of resize handle var pos_y, orig_h; this.$resizehdle.on('mousedown', function(e) { orig_h = $body.height(), pos_y = e.pageY; $body.parents().on('mousemove', mousemove).on('mouseup', mouseup); self.$dragCapture.show(); e.preventDefault(); }); var mousemove = function(e) { var h = orig_h + (pos_y - e.pageY); self.setHeight(h); }; var mouseup = function() { $body.parents().off('mousemove', mousemove).off('mouseup', mouseup); self.$dragCapture.hide(); }; // close button this.$closebtn = $('').addClass(csscls('close-btn')).appendTo(this.$headerRight); this.$closebtn.click(function() { self.close(); }); // minimize button this.$minimizebtn = $('').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight); this.$minimizebtn.click(function() { self.minimize(); }); // maximize button this.$maximizebtn = $('').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight); this.$maximizebtn.click(function() { self.restore(); }); // restore button this.$restorebtn = $('').addClass(csscls('restore-btn')).hide().appendTo(this.$el); this.$restorebtn.click(function() { self.restore(); }); // open button this.$openbtn = $('').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide(); this.$openbtn.click(function() { self.openHandler.show(function(id, dataset) { self.addDataSet(dataset, id, "(opened)"); self.showTab(); }); }); // select box for data sets this.$datasets = $('