Conditions | 3 |
Paths | 128 |
Total Lines | 1138 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | if (typeof(PhpDebugBar) == 'undefined') { |
||
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.show(); |
||
276 | } else { |
||
277 | this.$badge.hide(); |
||
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 filename = data['__meta']['uri'].substr(data['__meta']['uri'].lastIndexOf('/') + 1); |
||
374 | var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')'; |
||
375 | return label; |
||
376 | } |
||
377 | |||
378 | }); |
||
379 | |||
380 | // ------------------------------------------------------------------ |
||
381 | |||
382 | |||
383 | /** |
||
384 | * DebugBar |
||
385 | * |
||
386 | * Creates a bar that appends itself to the body of your page |
||
387 | * and sticks to the bottom. |
||
388 | * |
||
389 | * The bar can be customized by adding tabs and indicators. |
||
390 | * A data map is used to fill those controls with data provided |
||
391 | * from datasets. |
||
392 | */ |
||
393 | var DebugBar = PhpDebugBar.DebugBar = Widget.extend({ |
||
394 | |||
395 | className: "phpdebugbar " + csscls('minimized'), |
||
396 | |||
397 | options: { |
||
398 | bodyMarginBottom: true, |
||
399 | bodyMarginBottomHeight: parseInt($('body').css('margin-bottom')) |
||
400 | }, |
||
401 | |||
402 | initialize: function() { |
||
403 | this.controls = {}; |
||
404 | this.dataMap = {}; |
||
405 | this.datasets = {}; |
||
406 | this.firstTabName = null; |
||
407 | this.activePanelName = null; |
||
408 | this.datesetTitleFormater = new DatasetTitleFormater(this); |
||
409 | this.registerResizeHandler(); |
||
410 | }, |
||
411 | |||
412 | /** |
||
413 | * Register resize event, for resize debugbar with reponsive css. |
||
414 | * |
||
415 | * @this {DebugBar} |
||
416 | */ |
||
417 | registerResizeHandler: function() { |
||
418 | if (typeof this.resize.bind == 'undefined') return; |
||
419 | |||
420 | var f = this.resize.bind(this); |
||
421 | this.respCSSSize = 0; |
||
422 | $(window).resize(f); |
||
423 | setTimeout(f, 20); |
||
424 | }, |
||
425 | |||
426 | /** |
||
427 | * Resizes the debugbar to fit the current browser window |
||
428 | */ |
||
429 | resize: function() { |
||
430 | var contentSize = this.respCSSSize; |
||
431 | if (this.respCSSSize == 0) { |
||
432 | this.$header.find("> div > *:visible").each(function () { |
||
433 | contentSize += $(this).outerWidth(); |
||
434 | }); |
||
435 | } |
||
436 | |||
437 | var currentSize = this.$header.width(); |
||
438 | var cssClass = "phpdebugbar-mini-design"; |
||
439 | var bool = this.$header.hasClass(cssClass); |
||
440 | |||
441 | if (currentSize <= contentSize && !bool) { |
||
442 | this.respCSSSize = contentSize; |
||
443 | this.$header.addClass(cssClass); |
||
444 | } else if (contentSize < currentSize && bool) { |
||
445 | this.respCSSSize = 0; |
||
446 | this.$header.removeClass(cssClass); |
||
447 | } |
||
448 | |||
449 | // Reset height to ensure bar is still visible |
||
450 | this.setHeight(this.$body.height()); |
||
451 | }, |
||
452 | |||
453 | /** |
||
454 | * Initialiazes the UI |
||
455 | * |
||
456 | * @this {DebugBar} |
||
457 | */ |
||
458 | render: function() { |
||
459 | var self = this; |
||
460 | this.$el.appendTo('body'); |
||
461 | this.$dragCapture = $('<div />').addClass(csscls('drag-capture')).appendTo(this.$el); |
||
462 | this.$resizehdle = $('<div />').addClass(csscls('resize-handle')).appendTo(this.$el); |
||
463 | this.$header = $('<div />').addClass(csscls('header')).appendTo(this.$el); |
||
464 | this.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header); |
||
465 | this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header); |
||
466 | var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el); |
||
467 | this.recomputeBottomOffset(); |
||
468 | |||
469 | // dragging of resize handle |
||
470 | var pos_y, orig_h; |
||
471 | this.$resizehdle.on('mousedown', function(e) { |
||
472 | orig_h = $body.height(), pos_y = e.pageY; |
||
473 | $body.parents().on('mousemove', mousemove).on('mouseup', mouseup); |
||
474 | self.$dragCapture.show(); |
||
475 | e.preventDefault(); |
||
476 | }); |
||
477 | var mousemove = function(e) { |
||
478 | var h = orig_h + (pos_y - e.pageY); |
||
479 | self.setHeight(h); |
||
480 | }; |
||
481 | var mouseup = function() { |
||
482 | $body.parents().off('mousemove', mousemove).off('mouseup', mouseup); |
||
483 | self.$dragCapture.hide(); |
||
484 | }; |
||
485 | |||
486 | // close button |
||
487 | this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight); |
||
488 | this.$closebtn.click(function() { |
||
489 | self.close(); |
||
490 | }); |
||
491 | |||
492 | // minimize button |
||
493 | this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight); |
||
494 | this.$minimizebtn.click(function() { |
||
495 | self.minimize(); |
||
496 | }); |
||
497 | |||
498 | // maximize button |
||
499 | this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight); |
||
500 | this.$maximizebtn.click(function() { |
||
501 | self.restore(); |
||
502 | }); |
||
503 | |||
504 | // restore button |
||
505 | this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el); |
||
506 | this.$restorebtn.click(function() { |
||
507 | self.restore(); |
||
508 | }); |
||
509 | |||
510 | // open button |
||
511 | this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide(); |
||
512 | this.$openbtn.click(function() { |
||
513 | self.openHandler.show(function(id, dataset) { |
||
514 | self.addDataSet(dataset, id, "(opened)"); |
||
515 | self.showTab(); |
||
516 | }); |
||
517 | }); |
||
518 | |||
519 | // select box for data sets |
||
520 | this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).appendTo(this.$headerRight); |
||
521 | this.$datasets.change(function() { |
||
522 | self.dataChangeHandler(self.datasets[this.value]); |
||
523 | self.showTab(); |
||
524 | }); |
||
525 | }, |
||
526 | |||
527 | /** |
||
528 | * Sets the height of the debugbar body section |
||
529 | * Forces the height to lie within a reasonable range |
||
530 | * Stores the height in local storage so it can be restored |
||
531 | * Resets the document body bottom offset |
||
532 | * |
||
533 | * @this {DebugBar} |
||
534 | */ |
||
535 | setHeight: function(height) { |
||
536 | var min_h = 40; |
||
537 | var max_h = $(window).innerHeight() - this.$header.height() - 10; |
||
538 | height = Math.min(height, max_h); |
||
539 | height = Math.max(height, min_h); |
||
540 | this.$body.css('height', height); |
||
541 | localStorage.setItem('phpdebugbar-height', height); |
||
542 | this.recomputeBottomOffset(); |
||
543 | }, |
||
544 | |||
545 | /** |
||
546 | * Restores the state of the DebugBar using localStorage |
||
547 | * This is not called by default in the constructor and |
||
548 | * needs to be called by subclasses in their init() method |
||
549 | * |
||
550 | * @this {DebugBar} |
||
551 | */ |
||
552 | restoreState: function() { |
||
553 | // bar height |
||
554 | var height = localStorage.getItem('phpdebugbar-height'); |
||
555 | this.setHeight(height || this.$body.height()); |
||
556 | |||
557 | // bar visibility |
||
558 | var open = localStorage.getItem('phpdebugbar-open'); |
||
559 | if (open && open == '0') { |
||
560 | this.close(); |
||
561 | } else { |
||
562 | var visible = localStorage.getItem('phpdebugbar-visible'); |
||
563 | if (visible && visible == '1') { |
||
564 | var tab = localStorage.getItem('phpdebugbar-tab'); |
||
565 | if (this.isTab(tab)) { |
||
566 | this.showTab(tab); |
||
567 | } |
||
568 | } |
||
569 | } |
||
570 | }, |
||
571 | |||
572 | /** |
||
573 | * Creates and adds a new tab |
||
574 | * |
||
575 | * @this {DebugBar} |
||
576 | * @param {String} name Internal name |
||
577 | * @param {Object} widget A widget object with an element property |
||
578 | * @param {String} title The text in the tab, if not specified, name will be used |
||
579 | * @return {Tab} |
||
580 | */ |
||
581 | createTab: function(name, widget, title) { |
||
582 | var tab = new Tab({ |
||
583 | title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), |
||
584 | widget: widget |
||
585 | }); |
||
586 | return this.addTab(name, tab); |
||
587 | }, |
||
588 | |||
589 | /** |
||
590 | * Adds a new tab |
||
591 | * |
||
592 | * @this {DebugBar} |
||
593 | * @param {String} name Internal name |
||
594 | * @param {Tab} tab Tab object |
||
595 | * @return {Tab} |
||
596 | */ |
||
597 | addTab: function(name, tab) { |
||
598 | if (this.isControl(name)) { |
||
599 | throw new Error(name + ' already exists'); |
||
600 | } |
||
601 | |||
602 | var self = this; |
||
603 | tab.$tab.appendTo(this.$headerLeft).click(function() { |
||
604 | if (!self.isMinimized() && self.activePanelName == name) { |
||
605 | self.minimize(); |
||
606 | } else { |
||
607 | self.showTab(name); |
||
608 | } |
||
609 | }); |
||
610 | tab.$el.appendTo(this.$body); |
||
611 | |||
612 | this.controls[name] = tab; |
||
613 | if (this.firstTabName == null) { |
||
614 | this.firstTabName = name; |
||
615 | } |
||
616 | return tab; |
||
617 | }, |
||
618 | |||
619 | /** |
||
620 | * Creates and adds an indicator |
||
621 | * |
||
622 | * @this {DebugBar} |
||
623 | * @param {String} name Internal name |
||
624 | * @param {String} icon |
||
625 | * @param {String} tooltip |
||
626 | * @param {String} position "right" or "left", default is "right" |
||
627 | * @return {Indicator} |
||
628 | */ |
||
629 | createIndicator: function(name, icon, tooltip, position) { |
||
630 | var indicator = new Indicator({ |
||
631 | icon: icon, |
||
632 | tooltip: tooltip |
||
633 | }); |
||
634 | return this.addIndicator(name, indicator, position); |
||
635 | }, |
||
636 | |||
637 | /** |
||
638 | * Adds an indicator |
||
639 | * |
||
640 | * @this {DebugBar} |
||
641 | * @param {String} name Internal name |
||
642 | * @param {Indicator} indicator Indicator object |
||
643 | * @return {Indicator} |
||
644 | */ |
||
645 | addIndicator: function(name, indicator, position) { |
||
646 | if (this.isControl(name)) { |
||
647 | throw new Error(name + ' already exists'); |
||
648 | } |
||
649 | |||
650 | if (position == 'left') { |
||
651 | indicator.$el.insertBefore(this.$headerLeft.children().first()); |
||
652 | } else { |
||
653 | indicator.$el.appendTo(this.$headerRight); |
||
654 | } |
||
655 | |||
656 | this.controls[name] = indicator; |
||
657 | return indicator; |
||
658 | }, |
||
659 | |||
660 | /** |
||
661 | * Returns a control |
||
662 | * |
||
663 | * @param {String} name |
||
664 | * @return {Object} |
||
665 | */ |
||
666 | getControl: function(name) { |
||
667 | if (this.isControl(name)) { |
||
668 | return this.controls[name]; |
||
669 | } |
||
670 | }, |
||
671 | |||
672 | /** |
||
673 | * Checks if there's a control under the specified name |
||
674 | * |
||
675 | * @this {DebugBar} |
||
676 | * @param {String} name |
||
677 | * @return {Boolean} |
||
678 | */ |
||
679 | isControl: function(name) { |
||
680 | return typeof(this.controls[name]) != 'undefined'; |
||
681 | }, |
||
682 | |||
683 | /** |
||
684 | * Checks if a tab with the specified name exists |
||
685 | * |
||
686 | * @this {DebugBar} |
||
687 | * @param {String} name |
||
688 | * @return {Boolean} |
||
689 | */ |
||
690 | isTab: function(name) { |
||
691 | return this.isControl(name) && this.controls[name] instanceof Tab; |
||
692 | }, |
||
693 | |||
694 | /** |
||
695 | * Checks if an indicator with the specified name exists |
||
696 | * |
||
697 | * @this {DebugBar} |
||
698 | * @param {String} name |
||
699 | * @return {Boolean} |
||
700 | */ |
||
701 | isIndicator: function(name) { |
||
702 | return this.isControl(name) && this.controls[name] instanceof Indicator; |
||
703 | }, |
||
704 | |||
705 | /** |
||
706 | * Removes all tabs and indicators from the debug bar and hides it |
||
707 | * |
||
708 | * @this {DebugBar} |
||
709 | */ |
||
710 | reset: function() { |
||
711 | this.minimize(); |
||
712 | var self = this; |
||
713 | $.each(this.controls, function(name, control) { |
||
714 | if (self.isTab(name)) { |
||
715 | control.$tab.remove(); |
||
716 | } |
||
717 | control.$el.remove(); |
||
718 | }); |
||
719 | this.controls = {}; |
||
720 | }, |
||
721 | |||
722 | /** |
||
723 | * Open the debug bar and display the specified tab |
||
724 | * |
||
725 | * @this {DebugBar} |
||
726 | * @param {String} name If not specified, display the first tab |
||
727 | */ |
||
728 | showTab: function(name) { |
||
729 | if (!name) { |
||
730 | if (this.activePanelName) { |
||
731 | name = this.activePanelName; |
||
732 | } else { |
||
733 | name = this.firstTabName; |
||
734 | } |
||
735 | } |
||
736 | |||
737 | if (!this.isTab(name)) { |
||
738 | throw new Error("Unknown tab '" + name + "'"); |
||
739 | } |
||
740 | |||
741 | this.$resizehdle.show(); |
||
742 | this.$body.show(); |
||
743 | this.recomputeBottomOffset(); |
||
744 | |||
745 | $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active')); |
||
746 | $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active')); |
||
747 | |||
748 | this.controls[name].$tab.addClass(csscls('active')); |
||
749 | this.controls[name].$el.addClass(csscls('active')); |
||
750 | this.activePanelName = name; |
||
751 | |||
752 | this.$el.removeClass(csscls('minimized')); |
||
753 | localStorage.setItem('phpdebugbar-visible', '1'); |
||
754 | localStorage.setItem('phpdebugbar-tab', name); |
||
755 | this.resize(); |
||
756 | }, |
||
757 | |||
758 | /** |
||
759 | * Hide panels and minimize the debug bar |
||
760 | * |
||
761 | * @this {DebugBar} |
||
762 | */ |
||
763 | minimize: function() { |
||
764 | this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active')); |
||
765 | this.$body.hide(); |
||
766 | this.$resizehdle.hide(); |
||
767 | this.recomputeBottomOffset(); |
||
768 | localStorage.setItem('phpdebugbar-visible', '0'); |
||
769 | this.$el.addClass(csscls('minimized')); |
||
770 | this.resize(); |
||
771 | }, |
||
772 | |||
773 | /** |
||
774 | * Checks if the panel is minimized |
||
775 | * |
||
776 | * @return {Boolean} |
||
777 | */ |
||
778 | isMinimized: function() { |
||
779 | return this.$el.hasClass(csscls('minimized')); |
||
780 | }, |
||
781 | |||
782 | /** |
||
783 | * Close the debug bar |
||
784 | * |
||
785 | * @this {DebugBar} |
||
786 | */ |
||
787 | close: function() { |
||
788 | this.$resizehdle.hide(); |
||
789 | this.$header.hide(); |
||
790 | this.$body.hide(); |
||
791 | this.$restorebtn.show(); |
||
792 | localStorage.setItem('phpdebugbar-open', '0'); |
||
793 | this.$el.addClass(csscls('closed')); |
||
794 | this.recomputeBottomOffset(); |
||
795 | }, |
||
796 | |||
797 | /** |
||
798 | * Checks if the panel is closed |
||
799 | * |
||
800 | * @return {Boolean} |
||
801 | */ |
||
802 | isClosed: function() { |
||
803 | return this.$el.hasClass(csscls('closed')); |
||
804 | }, |
||
805 | |||
806 | /** |
||
807 | * Restore the debug bar |
||
808 | * |
||
809 | * @this {DebugBar} |
||
810 | */ |
||
811 | restore: function() { |
||
812 | this.$resizehdle.show(); |
||
813 | this.$header.show(); |
||
814 | this.$restorebtn.hide(); |
||
815 | localStorage.setItem('phpdebugbar-open', '1'); |
||
816 | var tab = localStorage.getItem('phpdebugbar-tab'); |
||
817 | if (this.isTab(tab)) { |
||
818 | this.showTab(tab); |
||
819 | } |
||
820 | this.$el.removeClass(csscls('closed')); |
||
821 | this.resize(); |
||
822 | }, |
||
823 | |||
824 | /** |
||
825 | * Recomputes the margin-bottom css property of the body so |
||
826 | * that the debug bar never hides any content |
||
827 | */ |
||
828 | recomputeBottomOffset: function() { |
||
829 | if (this.options.bodyMarginBottom) { |
||
830 | if (this.isClosed()) { |
||
831 | return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || ''); |
||
832 | } |
||
833 | |||
834 | var offset = parseInt(this.$el.height()) + this.options.bodyMarginBottomHeight; |
||
835 | $('body').css('margin-bottom', offset); |
||
836 | } |
||
837 | }, |
||
838 | |||
839 | /** |
||
840 | * Sets the data map used by dataChangeHandler to populate |
||
841 | * indicators and widgets |
||
842 | * |
||
843 | * A data map is an object where properties are control names. |
||
844 | * The value of each property should be an array where the first |
||
845 | * item is the name of a property from the data object (nested properties |
||
846 | * can be specified) and the second item the default value. |
||
847 | * |
||
848 | * Example: |
||
849 | * {"memory": ["memory.peak_usage_str", "0B"]} |
||
850 | * |
||
851 | * @this {DebugBar} |
||
852 | * @param {Object} map |
||
853 | */ |
||
854 | setDataMap: function(map) { |
||
855 | this.dataMap = map; |
||
856 | }, |
||
857 | |||
858 | /** |
||
859 | * Same as setDataMap() but appends to the existing map |
||
860 | * rather than replacing it |
||
861 | * |
||
862 | * @this {DebugBar} |
||
863 | * @param {Object} map |
||
864 | */ |
||
865 | addDataMap: function(map) { |
||
866 | $.extend(this.dataMap, map); |
||
867 | }, |
||
868 | |||
869 | /** |
||
870 | * Resets datasets and add one set of data |
||
871 | * |
||
872 | * For this method to be usefull, you need to specify |
||
873 | * a dataMap using setDataMap() |
||
874 | * |
||
875 | * @this {DebugBar} |
||
876 | * @param {Object} data |
||
877 | * @return {String} Dataset's id |
||
878 | */ |
||
879 | setData: function(data) { |
||
880 | this.datasets = {}; |
||
881 | return this.addDataSet(data); |
||
882 | }, |
||
883 | |||
884 | /** |
||
885 | * Adds a dataset |
||
886 | * |
||
887 | * If more than one dataset are added, the dataset selector |
||
888 | * will be displayed. |
||
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 | * @param {String} id The name of this set, optional |
||
896 | * @param {String} suffix |
||
897 | * @return {String} Dataset's id |
||
898 | */ |
||
899 | addDataSet: function(data, id, suffix) { |
||
900 | var label = this.datesetTitleFormater.format(id, data, suffix); |
||
901 | id = id || (getObjectSize(this.datasets) + 1); |
||
902 | this.datasets[id] = data; |
||
903 | |||
904 | this.$datasets.append($('<option value="' + id + '">' + label + '</option>')); |
||
905 | if (this.$datasets.children().length > 1) { |
||
906 | this.$datasets.show(); |
||
907 | } |
||
908 | |||
909 | this.showDataSet(id); |
||
910 | return id; |
||
911 | }, |
||
912 | |||
913 | /** |
||
914 | * Loads a dataset using the open handler |
||
915 | * |
||
916 | * @param {String} id |
||
917 | */ |
||
918 | loadDataSet: function(id, suffix, callback) { |
||
919 | if (!this.openHandler) { |
||
920 | throw new Error('loadDataSet() needs an open handler'); |
||
921 | } |
||
922 | var self = this; |
||
923 | this.openHandler.load(id, function(data) { |
||
924 | self.addDataSet(data, id, suffix); |
||
925 | callback && callback(data); |
||
926 | }); |
||
927 | }, |
||
928 | |||
929 | /** |
||
930 | * Returns the data from a dataset |
||
931 | * |
||
932 | * @this {DebugBar} |
||
933 | * @param {String} id |
||
934 | * @return {Object} |
||
935 | */ |
||
936 | getDataSet: function(id) { |
||
937 | return this.datasets[id]; |
||
938 | }, |
||
939 | |||
940 | /** |
||
941 | * Switch the currently displayed dataset |
||
942 | * |
||
943 | * @this {DebugBar} |
||
944 | * @param {String} id |
||
945 | */ |
||
946 | showDataSet: function(id) { |
||
947 | this.dataChangeHandler(this.datasets[id]); |
||
948 | this.$datasets.val(id); |
||
949 | }, |
||
950 | |||
951 | /** |
||
952 | * Called when the current dataset is modified. |
||
953 | * |
||
954 | * @this {DebugBar} |
||
955 | * @param {Object} data |
||
956 | */ |
||
957 | dataChangeHandler: function(data) { |
||
958 | var self = this; |
||
959 | $.each(this.dataMap, function(key, def) { |
||
960 | var d = getDictValue(data, def[0], def[1]); |
||
961 | if (key.indexOf(':') != -1) { |
||
962 | key = key.split(':'); |
||
963 | self.getControl(key[0]).set(key[1], d); |
||
964 | } else { |
||
965 | self.getControl(key).set('data', d); |
||
966 | } |
||
967 | }); |
||
968 | }, |
||
969 | |||
970 | /** |
||
971 | * Sets the handler to open past dataset |
||
972 | * |
||
973 | * @this {DebugBar} |
||
974 | * @param {object} handler |
||
975 | */ |
||
976 | setOpenHandler: function(handler) { |
||
977 | this.openHandler = handler; |
||
978 | if (handler !== null) { |
||
979 | this.$openbtn.show(); |
||
980 | } else { |
||
981 | this.$openbtn.hide(); |
||
982 | } |
||
983 | }, |
||
984 | |||
985 | /** |
||
986 | * Returns the handler to open past dataset |
||
987 | * |
||
988 | * @this {DebugBar} |
||
989 | * @return {object} |
||
990 | */ |
||
991 | getOpenHandler: function() { |
||
992 | return this.openHandler; |
||
993 | } |
||
994 | |||
995 | }); |
||
996 | |||
997 | DebugBar.Tab = Tab; |
||
998 | DebugBar.Indicator = Indicator; |
||
999 | |||
1000 | // ------------------------------------------------------------------ |
||
1001 | |||
1002 | /** |
||
1003 | * AjaxHandler |
||
1004 | * |
||
1005 | * Extract data from headers of an XMLHttpRequest and adds a new dataset |
||
1006 | */ |
||
1007 | var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName) { |
||
1008 | this.debugbar = debugbar; |
||
1009 | this.headerName = headerName || 'phpdebugbar'; |
||
1010 | }; |
||
1011 | |||
1012 | $.extend(AjaxHandler.prototype, { |
||
1013 | |||
1014 | /** |
||
1015 | * Handles an XMLHttpRequest |
||
1016 | * |
||
1017 | * @this {AjaxHandler} |
||
1018 | * @param {XMLHttpRequest} xhr |
||
1019 | * @return {Bool} |
||
1020 | */ |
||
1021 | handle: function(xhr) { |
||
1022 | if (!this.loadFromId(xhr)) { |
||
1023 | return this.loadFromData(xhr); |
||
1024 | } |
||
1025 | return true; |
||
1026 | }, |
||
1027 | |||
1028 | /** |
||
1029 | * Checks if the HEADER-id exists and loads the dataset using the open handler |
||
1030 | * |
||
1031 | * @param {XMLHttpRequest} xhr |
||
1032 | * @return {Bool} |
||
1033 | */ |
||
1034 | loadFromId: function(xhr) { |
||
1035 | var id = this.extractIdFromHeaders(xhr); |
||
1036 | if (id && this.debugbar.openHandler) { |
||
1037 | this.debugbar.loadDataSet(id, "(ajax)"); |
||
1038 | return true; |
||
1039 | } |
||
1040 | return false; |
||
1041 | }, |
||
1042 | |||
1043 | /** |
||
1044 | * Extracts the id from the HEADER-id |
||
1045 | * |
||
1046 | * @param {XMLHttpRequest} xhr |
||
1047 | * @return {String} |
||
1048 | */ |
||
1049 | extractIdFromHeaders: function(xhr) { |
||
1050 | return xhr.getResponseHeader(this.headerName + '-id'); |
||
1051 | }, |
||
1052 | |||
1053 | /** |
||
1054 | * Checks if the HEADER exists and loads the dataset |
||
1055 | * |
||
1056 | * @param {XMLHttpRequest} xhr |
||
1057 | * @return {Bool} |
||
1058 | */ |
||
1059 | loadFromData: function(xhr) { |
||
1060 | var raw = this.extractDataFromHeaders(xhr); |
||
1061 | if (!raw) { |
||
1062 | return false; |
||
1063 | } |
||
1064 | |||
1065 | var data = this.parseHeaders(raw); |
||
1066 | if (data.error) { |
||
1067 | throw new Error('Error loading debugbar data: ' + data.error); |
||
1068 | } else if(data.data) { |
||
1069 | this.debugbar.addDataSet(data.data, data.id, "(ajax)"); |
||
1070 | } |
||
1071 | return true; |
||
1072 | }, |
||
1073 | |||
1074 | /** |
||
1075 | * Extract the data as a string from headers of an XMLHttpRequest |
||
1076 | * |
||
1077 | * @this {AjaxHandler} |
||
1078 | * @param {XMLHttpRequest} xhr |
||
1079 | * @return {string} |
||
1080 | */ |
||
1081 | extractDataFromHeaders: function(xhr) { |
||
1082 | var data = xhr.getResponseHeader(this.headerName); |
||
1083 | if (!data) { |
||
1084 | return; |
||
1085 | } |
||
1086 | for (var i = 1;; i++) { |
||
1087 | var header = xhr.getResponseHeader(this.headerName + '-' + i); |
||
1088 | if (!header) { |
||
1089 | break; |
||
1090 | } |
||
1091 | data += header; |
||
1092 | } |
||
1093 | return decodeURIComponent(data); |
||
1094 | }, |
||
1095 | |||
1096 | /** |
||
1097 | * Parses the string data into an object |
||
1098 | * |
||
1099 | * @this {AjaxHandler} |
||
1100 | * @param {string} data |
||
1101 | * @return {string} |
||
1102 | */ |
||
1103 | parseHeaders: function(data) { |
||
1104 | return JSON.parse(data); |
||
1105 | }, |
||
1106 | |||
1107 | /** |
||
1108 | * Attaches an event listener to jQuery.ajaxComplete() |
||
1109 | * |
||
1110 | * @this {AjaxHandler} |
||
1111 | * @param {jQuery} jq Optional |
||
1112 | */ |
||
1113 | bindToJquery: function(jq) { |
||
1114 | var self = this; |
||
1115 | jq(document).ajaxComplete(function(e, xhr, settings) { |
||
1116 | if (!settings.ignoreDebugBarAjaxHandler) { |
||
1117 | self.handle(xhr); |
||
1118 | } |
||
1119 | }); |
||
1120 | }, |
||
1121 | |||
1122 | /** |
||
1123 | * Attaches an event listener to XMLHttpRequest |
||
1124 | * |
||
1125 | * @this {AjaxHandler} |
||
1126 | */ |
||
1127 | bindToXHR: function() { |
||
1128 | var self = this; |
||
1129 | var proxied = XMLHttpRequest.prototype.open; |
||
1130 | XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { |
||
1131 | var xhr = this; |
||
1132 | this.addEventListener("readystatechange", function() { |
||
1133 | var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null; |
||
1134 | if (xhr.readyState == 4 && url.indexOf(skipUrl) !== 0) { |
||
1135 | self.handle(xhr); |
||
1136 | } |
||
1137 | }, false); |
||
1138 | proxied.apply(this, Array.prototype.slice.call(arguments)); |
||
1139 | }; |
||
1140 | } |
||
1141 | |||
1142 | }); |
||
1143 | |||
1144 | })(PhpDebugBar.$); |
||
1145 |