Merge remote branch 'kc/master' into new/enh/bug_5917
[koha.git] / koha-tt / intranet-tmpl / prog / en / lib / yui / profilerviewer / profilerviewer-debug.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 (function() {
8
9     /**
10      * The ProfilerViewer module provides a graphical display for viewing
11          * the output of the YUI Profiler <http://developer.yahoo.com/yui/profiler>.
12      * @module profilerviewer
13      * @requires yahoo, dom, event, element, profiler, yuiloader
14      */
15
16     /**
17      * A widget to view YUI Profiler output.
18      * @namespace YAHOO.widget
19      * @class ProfilerViewer
20      * @extends YAHOO.util.Element
21      * @constructor
22      * @param {HTMLElement | String | Object} el(optional) The html 
23      * element into which the ProfileViewer should be rendered. 
24      * An element will be created if none provided.
25      * @param {Object} attr (optional) A key map of the ProfilerViewer's 
26      * initial attributes.  Ignored if first arg is an attributes object.
27      */
28     YAHOO.widget.ProfilerViewer = function(el, attr) {
29         attr = attr || {};
30         if (arguments.length == 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
31             attr = el;
32             el = attr.element || null;
33         }
34         if (!el && !attr.element) {
35             el = this._createProfilerViewerElement();
36         }
37
38         YAHOO.widget.ProfilerViewer.superclass.constructor.call(this, el, attr); 
39                 
40                 this._init();
41                 
42                 YAHOO.log("ProfilerViewer instantiated.", "info", "ProfilerViewer");
43     };
44
45     YAHOO.extend(YAHOO.widget.ProfilerViewer, YAHOO.util.Element);
46         
47         // Static members of YAHOO.widget.ProfilerViewer:
48         YAHOO.lang.augmentObject(YAHOO.widget.ProfilerViewer, {
49                 /**
50                  * Classname for ProfilerViewer containing element.
51                  * @static
52                  * @property CLASS
53                  * @type string
54                  * @public
55                  * @default "yui-pv"
56                  */
57                 CLASS: 'yui-pv',
58         
59                 /**
60                  * Classname for ProfilerViewer button dashboard. 
61                  * @static
62                  * @property CLASS_DASHBOARD
63                  * @type string
64                  * @public
65                  * @default "yui-pv-dashboard"
66                  */
67                 CLASS_DASHBOARD: 'yui-pv-dashboard',
68
69                 /**
70                  * Classname for the "refresh data" button. 
71                  * @static
72                  * @property CLASS_REFRESH
73                  * @type string
74                  * @public
75                  * @default "yui-pv-refresh"
76                  */
77                 CLASS_REFRESH: 'yui-pv-refresh',
78
79                 /**
80                  * Classname for busy indicator in the dashboard. 
81                  * @static
82                  * @property CLASS_BUSY
83                  * @type string
84                  * @public
85                  * @default "yui-pv-busy"
86                  */
87                 CLASS_BUSY: 'yui-pv-busy',
88         
89                 /**
90                  * Classname for element containing the chart and chart
91                  * legend elements.
92                  * @static
93                  * @property CLASS_CHART_CONTAINER
94                  * @type string
95                  * @public
96                  * @default "yui-pv-chartcontainer"
97                  */
98                 CLASS_CHART_CONTAINER: 'yui-pv-chartcontainer',
99         
100                 /**
101                  * Classname for element containing the chart.
102                  * @static
103                  * @property CLASS_CHART
104                  * @type string
105                  * @public
106                  * @default "yui-pv-chart"
107                  */
108                 CLASS_CHART: 'yui-pv-chart',
109                 
110                 /**
111                  * Classname for element containing the chart's legend. 
112                  * @static
113                  * @property CLASS_CHART_LEGEND
114                  * @type string
115                  * @public
116                  * @default "yui-pv-chartlegend"
117                  */
118                 CLASS_CHART_LEGEND: 'yui-pv-chartlegend',
119                 
120                 /**
121                  * Classname for element containing the datatable. 
122                  * @static
123                  * @property CLASS_TABLE
124                  * @type string
125                  * @public
126                  * @default "yui-pv-table"
127                  */
128                 CLASS_TABLE: 'yui-pv-table',
129                 
130                 /**
131                  * Strings used in the UI.
132                  * @static
133                  * @property STRINGS
134                  * @object
135                  * @public
136                  * @default English language strings for UI.
137                  */
138                 STRINGS: {
139                         title: "YUI Profiler (beta)",
140                         buttons: {
141                                 viewprofiler: "View Profiler Data",
142                                 hideprofiler: "Hide Profiler Report",
143                                 showchart: "Show Chart",
144                                 hidechart: "Hide Chart",
145                                 refreshdata: "Refresh Data"
146                         },
147                         colHeads: {
148                                 //key: [column head label, width in pixels]
149                                 fn: ["Function/Method", null], //must auto-size
150                                 calls: ["Calls", 40],
151                                 avg: ["Average", 80],
152                                 min: ["Shortest", 70],
153                                 max: ["Longest", 70],
154                                 total: ["Total Time", 70],
155                                 pct: ["Percent", 70]
156                         },
157                         millisecondsAbbrev: "ms",
158                         initMessage: "initialiazing chart...",
159                         installFlashMessage: "Unable to load Flash content. The YUI Charts Control requires Flash Player 9.0.45 or higher. You can download the latest version of Flash Player from the <a href='http://www.adobe.com/go/getflashplayer'>Adobe Flash Player Download Center</a>."
160                 },
161
162                 /**
163                  * Function used to format numbers in milliseconds
164                  * for chart; must be publicly accessible, per Charts spec.
165                  * @static
166                  * @property timeAxisLabelFunction
167                  * @type function
168                  * @private
169                  */
170                 timeAxisLabelFunction: function(n) {
171                         var a = (n === Math.floor(n)) ? n : (Math.round(n*1000))/1000;
172                         return (a + " " + YAHOO.widget.ProfilerViewer.STRINGS.millisecondsAbbrev);
173                 },
174
175                 /**
176                  * Function used to format percent numbers for chart; must
177                  * be publicly accessible, per Charts spec.
178                  * @static
179                  * @property percentAxisLabelFunction
180                  * @type function
181                  * @private
182                  */
183                 percentAxisLabelFunction: function(n) {
184                         var a = (n === Math.floor(n)) ? n : (Math.round(n*100))/100;
185                         return (a + "%");
186                 }
187                 
188         
189         },true);
190         
191
192         //
193         // STANDARD SHORTCUTS
194         //
195     var Dom = YAHOO.util.Dom;
196     var Event = YAHOO.util.Event;
197         var Profiler = YAHOO.tool.Profiler;
198         var PV = YAHOO.widget.ProfilerViewer;
199         var proto = PV.prototype;
200
201
202         //
203         // PUBLIC METHODS
204         //
205         
206          /**
207      * Refreshes the data displayed in the ProfilerViewer. When called,
208          * this will invoke a refresh of the DataTable and (if displayed)
209          * the Chart.
210      * @method refreshData
211      * @return void
212          * @public
213      */ 
214         proto.refreshData = function() {
215                 YAHOO.log("Data refresh requested via refreshData method.", "info", "ProfilerViewer");
216                 this.fireEvent("dataRefreshEvent");
217         };
218
219          /**
220      * Returns the element containing the console's header.
221      * @method getHeadEl
222      * @return HTMLElement
223          * @public
224      */ 
225         proto.getHeadEl = function() {
226                 YAHOO.log("Head element requested via getHeadEl.", "info", "ProfilerViewer");
227                 return (this._headEl) ? Dom.get(this._headEl) : false;
228         };
229
230          /**
231      * Returns the element containing the console's body, including
232          * the chart and the datatable..
233      * @method getBodyEl
234      * @return HTMLElement
235          * @public
236      */ 
237         proto.getBodyEl = function() {
238                 YAHOO.log("Body element requested via getBodyEl.", "info", "ProfilerViewer");
239                 return (this._bodyEl) ? Dom.get(this._bodyEl) : false;
240         };
241
242          /**
243      * Returns the element containing the console's chart.
244      * @method getChartEl
245      * @return HTMLElement
246          * @public
247      */ 
248         proto.getChartEl = function() {
249                 YAHOO.log("Chart element requested via getChartEl.", "info", "ProfilerViewer");
250                 return (this._chartEl) ? Dom.get(this._chartEl) : false;
251         };
252
253          /**
254      * Returns the element containing the console's dataTable.
255      * @method getTableEl
256      * @return HTMLElement
257          * @public
258      */ 
259         proto.getTableEl = function() {
260                 YAHOO.log("DataTable element requested via getTableEl.", "info", "ProfilerViewer");
261                 return (this._tableEl) ? Dom.get(this._tableEl) : false;
262         };
263
264          /**
265      * Returns the element containing the console's DataTable
266          * instance.
267      * @method getDataTable
268      * @return YAHOO.widget.DataTable
269          * @public
270      */ 
271         proto.getDataTable = function() {
272                 YAHOO.log("DataTable instance requested via getDataTable.", "info", "ProfilerViewer");
273                 return this._dataTable;
274         };
275
276          /**
277      * Returns the element containing the console's Chart instance.
278      * @method getChart
279      * @return YAHOO.widget.BarChart
280          * @public
281      */ 
282         proto.getChart = function() {
283                 YAHOO.log("Chart instance requested via getChart.", "info", "ProfilerViewer");
284                 return this._chart;
285         };
286
287
288     //
289     // PRIVATE PROPERTIES
290     //
291     proto._rendered = false;
292         proto._headEl = null;
293         proto._bodyEl = null;
294         proto._toggleVisibleEl = null;
295         proto._busyEl = null;
296         proto._busy = false;
297         
298         proto._tableEl = null;
299         proto._dataTable = null;
300
301         proto._chartEl = null;
302         proto._chartLegendEl = null;
303         proto._chartElHeight = 250;
304         proto._chart = null;
305         proto._chartInitialized = false;
306
307     //
308     // PRIVATE METHODS
309     //
310
311         proto._init = function() {
312                 /**
313                  * CUSTOM EVENTS
314                  **/
315                 
316                 /**
317                  * Fired when a data refresh is requested. No arguments are passed
318                  * with this event.
319                  *
320                  * @event refreshDataEvent
321                  */
322                 this.createEvent("dataRefreshEvent");
323                 
324                 /**
325                  * Fired when the viewer canvas first renders. No arguments are passed
326                  * with this event.
327                  *
328                  * @event renderEvent
329                  */
330                 this.createEvent("renderEvent");
331
332                 this.on("dataRefreshEvent", this._refreshDataTable, this, true);
333                 
334                 this._initLauncherDOM();
335                 
336                 if(this.get("showChart")) {
337                         this.on("sortedByChange", this._refreshChart);
338                 }
339
340                 YAHOO.log("ProfilerViewer instance initialization complete.", "info", "ProfilerViewer");
341         };
342
343         /**
344          * If no element is passed in, create it as the first element
345          * in the document.
346          * @method _createProfilerViewerElement
347          * @return HTMLElement
348          * @private
349          */
350         proto._createProfilerViewerElement = function() {
351                 YAHOO.log("Creating root element...", "info", "ProfilerViewer");
352
353                 var el = document.createElement("div");
354                 document.body.insertBefore(el, document.body.firstChild);
355                 Dom.addClass(el, this.SKIN_CLASS);
356                 Dom.addClass(el, PV.CLASS);
357                 YAHOO.log(el);
358                 return el;
359         };
360                         
361     /**
362      * Provides a readable name for the ProfilerViewer instance.
363      * @method toString
364      * @return String
365          * @private
366          */
367     proto.toString = function() {
368         return "ProfilerViewer " + (this.get('id') || this.get('tagName'));
369     };
370
371     /**
372      * Toggles visibility of the viewer canvas.
373      * @method _toggleVisible
374      * @return void
375          * @private
376      */ 
377         proto._toggleVisible = function() {
378                 YAHOO.log("Toggling visibility to " + !this.get("visible") + ".", "info", "ProfilerViewer");
379                 
380                 var newVis = (this.get("visible")) ? false : true;
381                 this.set("visible", newVis);
382     };
383
384     /**
385      * Shows the viewer canvas.
386      * @method show
387      * @return void
388          * @private
389      */ 
390          proto._show = function() {
391                 if(!this._busy) {
392                         this._setBusyState(true);
393                         if(!this._rendered) {
394                                 var loader = new YAHOO.util.YUILoader();
395                                 if (this.get("base")) {
396                                         loader.base = this.get("base");
397                                 }
398                                 
399                                 var modules = ["datatable"];
400                                 if(this.get("showChart")) {
401                                         modules.push("charts");
402                                 }
403                                 
404                                 loader.insert({ require: modules,
405                                                                 onSuccess: function() {
406                                                                         this._render();
407                                                                 },
408                                                                 scope: this});
409                         } else {
410                                 var el = this.get("element");
411                                 Dom.removeClass(el, "yui-pv-minimized");
412                                 this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
413                                 
414                                 //The Flash Charts component can't be set to display:none,
415                                 //and even after positioning it offscreen the screen
416                                 //may fail to repaint in some browsers.  Adding an empty
417                                 //style rule to the console body can help force a repaint:
418                                 Dom.addClass(el, "yui-pv-null");
419                                 Dom.removeClass(el, "yui-pv-null");
420                                 
421                                 //Always refresh data when changing to visible:
422                                 this.refreshData();
423                         }
424                 }
425     };
426
427     /**
428      * Hides the viewer canvas.
429      * @method hide
430      * @return void
431          * @private
432      */ 
433         proto._hide = function() {
434                 this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.viewprofiler;
435                 Dom.addClass(this.get("element"), "yui-pv-minimized");
436     };
437         
438         /**
439          * Render the viewer canvas
440          * @method _render
441          * @return void
442          * @private
443          */
444         proto._render = function() {
445                 YAHOO.log("Beginning to render ProfilerViewer canvas...", "info", "ProfilerViewer");
446                 
447                 Dom.removeClass(this.get("element"), "yui-pv-minimized");
448                 
449                 this._initViewerDOM();
450                 this._initDataTable();
451                 if(this.get("showChart")) {
452                         this._initChartDOM();
453                         this._initChart();
454                 }
455                 this._rendered = true;
456                 this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
457                 
458                 this.fireEvent("renderEvent");
459
460                 YAHOO.log("ProfilerViewer rendering complete...", "info", "ProfilerViewer");
461         };
462         
463         /**
464          * Set up the DOM structure for the ProfilerViewer launcher.
465          * @method _initLauncherDOM
466          * @private
467          */
468         proto._initLauncherDOM = function() {
469                 YAHOO.log("Creating the launcher...", "info", "ProfilerViewer");
470                 
471                 var el = this.get("element");
472                 Dom.addClass(el, PV.CLASS);
473                 Dom.addClass(el, "yui-pv-minimized");
474
475                 this._headEl = document.createElement("div");
476                 Dom.addClass(this._headEl, "hd");
477                 
478                 var s = PV.STRINGS.buttons;
479                 var b = (this.get("visible")) ? s.hideprofiler : s.viewprofiler;
480                 
481                 this._toggleVisibleEl = this._createButton(b, this._headEl);
482                 
483                 this._refreshEl = this._createButton(s.refreshdata, this._headEl);
484                 Dom.addClass(this._refreshEl, PV.CLASS_REFRESH);
485                 
486                 this._busyEl = document.createElement("span");
487                 this._headEl.appendChild(this._busyEl);
488
489                 var title = document.createElement("h4");
490                 title.innerHTML = PV.STRINGS.title;
491                 this._headEl.appendChild(title);
492                 
493                 el.appendChild(this._headEl);
494                 
495                 Event.on(this._toggleVisibleEl, "click", this._toggleVisible, this, true);
496                 Event.on(this._refreshEl, "click", function() {
497                         if(!this._busy) {
498                                 this._setBusyState(true);
499                                 this.fireEvent("dataRefreshEvent");
500                         }
501                 }, this, true);
502         };
503
504         /**
505          * Set up the DOM structure for the ProfilerViewer canvas,
506          * including the holder for the DataTable.
507          * @method _initViewerDOM
508          * @private
509          */
510         proto._initViewerDOM = function() {
511                 YAHOO.log("Creating DOM structure for viewer...", "info", "ProfilerViewer");
512                 
513                 var el = this.get("element");
514                 this._bodyEl = document.createElement("div");
515                 Dom.addClass(this._bodyEl, "bd");
516                 this._tableEl = document.createElement("div");
517                 Dom.addClass(this._tableEl, PV.CLASS_TABLE);
518                 this._bodyEl.appendChild(this._tableEl);
519                 el.appendChild(this._bodyEl);
520         };
521
522         /**
523          * Set up the DOM structure for the ProfilerViewer canvas.
524          * @method _initChartDOM
525          * @private
526          */
527         proto._initChartDOM = function() {
528                 YAHOO.log("Adding DOM structure for chart...", "info", "ProfilerViewer");
529                 
530                 this._chartContainer = document.createElement("div");
531                 Dom.addClass(this._chartContainer, PV.CLASS_CHART_CONTAINER);
532                 
533                 var chl = document.createElement("div");
534                 Dom.addClass(chl, PV.CLASS_CHART_LEGEND);
535                 
536                 var chw = document.createElement("div");
537
538                 this._chartLegendEl = document.createElement("dl");
539                 this._chartLegendEl.innerHTML = "<dd>" + PV.STRINGS.initMessage + "</dd>";
540                 
541                 this._chartEl = document.createElement("div");
542                 Dom.addClass(this._chartEl, PV.CLASS_CHART);
543                 
544                 var msg = document.createElement("p");
545                 msg.innerHTML = PV.STRINGS.installFlashMessage;
546                 this._chartEl.appendChild(msg);
547                 
548                 this._chartContainer.appendChild(chl);
549                 chl.appendChild(chw);
550                 chw.appendChild(this._chartLegendEl);
551                 this._chartContainer.appendChild(this._chartEl);
552                 this._bodyEl.insertBefore(this._chartContainer,this._tableEl);
553         };
554
555
556         /**
557          * Create anchor elements for use as buttons. Args: label
558          * is text to appear on the face of the button, parentEl
559          * is the el to which the anchor will be attached, position
560          * is true for inserting as the first node and false for
561          * inserting as the last node of the parentEl.
562          * @method _createButton
563          * @private
564          */     
565         proto._createButton = function(label, parentEl, position) {
566                 var b = document.createElement("a");
567                 b.innerHTML = b.title = label;
568                 if(parentEl) {
569                         if(!position) {
570                                 parentEl.appendChild(b);
571                         } else {
572                                 parentEl.insertBefore(b, parentEl.firstChild);  
573                         }
574                 }
575                 return b;
576         };
577         
578         /**
579          * Set's console busy state.
580          * @method _setBusyState
581          * @private
582          **/
583         proto._setBusyState = function(b) {
584                 if(b) {
585                         Dom.addClass(this._busyEl, PV.CLASS_BUSY);
586                         this._busy = true;
587                 } else {
588                         Dom.removeClass(this._busyEl, PV.CLASS_BUSY);
589                         this._busy = false;
590                 }
591         };
592
593         /**
594          * Generages a sorting function based on current sortedBy
595          * values.
596          * @method _createProfilerViewerElement
597          * @private
598          **/
599         proto._genSortFunction = function(key, dir) {
600                 var by = key;
601                 var direction = dir;
602                 return function(a, b) {
603                         if (direction == YAHOO.widget.DataTable.CLASS_ASC) {
604                                 return a[by] - b[by];   
605                         } else {
606                                 return ((a[by] - b[by]) * -1);
607                         }
608                 };
609         };
610
611         /**
612          * Utility function for array sums.
613          * @method _arraySum
614          * @private
615          **/    
616          var _arraySum = function(arr){
617                 var ct = 0;
618                 for(var i = 0; i < arr.length; ct+=arr[i++]){}
619                 return ct;
620         };
621         
622         /**
623          * Retrieves data from Profiler, filtering and sorting as needed
624          * based on current widget state.  Adds calculated percentage
625          * column and function name to data returned by Profiler.
626          * @method _getProfilerData
627          * @private
628          **/
629         proto._getProfilerData = function() {
630                 YAHOO.log("Profiler data requested from function DataSource.", "info", "ProfilerViewer");
631                 
632                 var obj = Profiler.getFullReport();
633                 var arr = [];
634                 var totalTime = 0;
635                 for (name in obj) {
636                 if (YAHOO.lang.hasOwnProperty(obj, name)) {
637                                 var r = obj[name];
638                                 var o = {};
639                                 o.fn = name; //add function name to record
640                                 o.points = r.points.slice(); //copy live array
641                                 o.calls = r.calls;
642                                 o.min = r.min;
643                                 o.max = r.max;
644                                 o.avg = r.avg;
645                                 o.total = _arraySum(o.points);
646                                 o.points = r.points;
647                                 var f = this.get("filter");
648                                 if((!f) || (f(o))) {
649                                         arr.push(o);
650                                         totalTime += o.total;
651                                 }
652                         }
653                 }
654                 
655                 //add calculated percentage column
656                 for (var i = 0, j = arr.length; i < j; i++) {
657                         arr[i].pct = (totalTime) ? (arr[i].total * 100) / totalTime : 0;        
658                 }
659
660                 var sortedBy = this.get("sortedBy");
661                 var key = sortedBy.key;
662                 var dir = sortedBy.dir;         
663
664                 arr.sort(this._genSortFunction(key, dir));
665                 
666                 YAHOO.log("Returning data from DataSource: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
667                 
668                 return arr;
669         };
670         
671         /**
672          * Set up the DataTable.
673          * @method _initDataTable
674          * @private
675          */
676         proto._initDataTable = function() {
677                 YAHOO.log("Creating DataTable instance...", "info", "ProfilerViewer");
678                 
679                 var self = this;
680                 
681                 //Set up the JS Function DataSource, pulling data from
682                 //the Profiler.
683                 this._dataSource = new YAHOO.util.DataSource(
684                         function() {
685                                 return self._getProfilerData.call(self);        
686                         },
687                         {
688                                 responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
689                                 maxCacheEntries: 0
690                         }
691                 );
692                 var ds = this._dataSource;
693
694                 ds.responseSchema =
695                 {
696                         fields: [ "fn", "avg", "calls", "max", "min", "total", "pct", "points"]
697                 };
698                 
699                 //Set up the DataTable.
700                 var formatTimeValue = function(elCell, oRecord, oColumn, oData) {
701                         var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*1000))/1000;
702                         elCell.innerHTML = a + " " + PV.STRINGS.millisecondsAbbrev;
703                 };
704
705                 var formatPercent = function(elCell, oRecord, oColumn, oData) {
706                         var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*100))/100;
707                         elCell.innerHTML = a + "%";
708                 };
709                 
710                 var a = YAHOO.widget.DataTable.CLASS_ASC;
711                 var d = YAHOO.widget.DataTable.CLASS_DESC;
712                 var c = PV.STRINGS.colHeads;
713                 var f = formatTimeValue;
714                 
715                 var cols = [
716                         {key:"fn", sortable:true, label: c.fn[0],
717                                 sortOptions: {defaultDir:a}, 
718                                 resizeable: (YAHOO.util.DragDrop) ? true : false,
719                                 minWidth:c.fn[1]},
720                         {key:"calls", sortable:true, label: c.calls[0],
721                                 sortOptions: {defaultDir:d},
722                                 width:c.calls[1]},
723                         {key:"avg", sortable:true, label: c.avg[0],
724                                 sortOptions: {defaultDir:d},
725                                 formatter:f,
726                                 width:c.avg[1]},
727                         {key:"min", sortable:true, label: c.min[0],
728                                 sortOptions: {defaultDir:a},
729                                 formatter:f,
730                                 width:c.min[1]}, 
731                         {key:"max", sortable:true, label: c.max[0],
732                                 sortOptions: {defaultDir:d},
733                                 formatter:f,
734                                 width:c.max[1]},
735                         {key:"total", sortable:true, label: c.total[0],
736                                 sortOptions: {defaultDir:d},
737                                 formatter:f,
738                                 width:c.total[1]},
739                         {key:"pct", sortable:true, label: c.pct[0],
740                                 sortOptions: {defaultDir:d}, 
741                                 formatter:formatPercent,
742                                 width:c.pct[1]}
743                 ];
744
745                 this._dataTable = new YAHOO.widget.DataTable(this._tableEl, cols, ds, {
746                         scrollable:true,
747                         height:this.get("tableHeight"),
748                         initialRequest:null,
749                         sortedBy: {
750                                 key: "total",
751                                 dir: YAHOO.widget.DataTable.CLASS_DESC
752                         }
753                 });
754                 var dt = this._dataTable;
755
756                 //Wire up DataTable events to drive the rest of the UI.
757                 dt.subscribe("sortedByChange", this._sortedByChange, this, true);
758                 dt.subscribe("renderEvent", this._dataTableRenderHandler, this, true);
759                 dt.subscribe("initEvent", this._dataTableRenderHandler, this, true);
760                 Event.on(this._tableEl.getElementsByTagName("th"), "click", this._thClickHandler, this, true);
761                 YAHOO.log("DataTable initialized.", "info", "ProfilerViewer");
762         };
763                 
764         /**
765          * Proxy the sort event in DataTable into the ProfilerViewer
766          * attribute.
767          * @method _sortedByChange
768          * @private
769          **/
770         proto._sortedByChange = function(o) {
771                 if(o.newValue && o.newValue.key) {
772                         YAHOO.log("Relaying DataTable sortedBy value change; new key: " + o.newValue.key + "; new direction: " + o.newValue.dir + ".", "info", "ProfilerViewer");
773                         this.set("sortedBy", {key: o.newValue.key, dir:o.newValue.dir});
774                 }
775         };
776         
777         /**
778          * Proxy the render event in DataTable into the ProfilerViewer
779          * attribute.
780          * @method _dataTableRenderHandler
781          * @private
782          **/
783         proto._dataTableRenderHandler = function(o) {
784                 YAHOO.log("DataTable's render event has fired.", "info", "ProfilerViewer");
785                 this._setBusyState(false);
786         };
787         
788         /**
789          * Event handler for clicks on the DataTable's sortable column
790          * heads.
791          * @method _thClickHandler
792          * @private
793          **/
794         proto._thClickHandler = function(o) {
795                 YAHOO.log("DataTable's header row was clicked for sorting.", "info", "ProfilerViewer");
796                 this._setBusyState(true);
797         };
798
799         /**
800          * Refresh DataTable, getting new data from Profiler.
801          * @method _refreshDataTable
802          * @private
803          **/
804         proto._refreshDataTable = function(args) {
805                 YAHOO.log("Beginning to refresh DataTable contents...", "info", "ProfilerViewer");
806                 var dt = this._dataTable;
807                 dt.getDataSource().sendRequest("", dt.onDataReturnInitializeTable, dt);
808                 YAHOO.log("DataTable refresh complete.", "info", "ProfilerViewer");
809         };
810
811         /**
812          * Refresh chart, getting new data from table.
813          * @method _refreshChart
814          * @private
815          **/
816         proto._refreshChart = function() {
817                 YAHOO.log("Beginning to refresh Chart contents...", "info", "ProfilerViewer");
818                 
819                 switch (this.get("sortedBy").key) {
820                         case "fn":
821                                 /*Keep the same data on the chart, but force update to 
822                                   reflect new sort order on function/method name: */
823                                 this._chart.set("dataSource", this._chart.get("dataSource"));
824                                 /*no further action necessary; chart redraws*/
825                                 return;
826                         case "calls":
827                                 /*Null out the xAxis formatting before redrawing chart.*/
828                                 this._chart.set("xAxis", this._chartAxisDefinitionPlain);
829                                 break;
830                         case "pct":
831                                 this._chart.set("xAxis", this._chartAxisDefinitionPercent);
832                                 break;
833                         default:
834                                 /*Set the default xAxis; redraw legend; set the new series definition.*/
835                                 this._chart.set("xAxis", this._chartAxisDefinitionTime);
836                                 break;
837                 }
838                 
839                 this._drawChartLegend();
840                 this._chart.set("series", this._getSeriesDef(this.get("sortedBy").key));
841
842                 YAHOO.log("Chart refresh complete.", "info", "ProfilerViewer");
843         };
844         
845         /**
846          * Get data for the Chart from DataTable recordset
847          * @method _getChartData
848          * @private
849          */
850         proto._getChartData = function() {
851                 YAHOO.log("Getting data for chart from function DataSource.", "info", "ProfilerViewer");
852                 //var records = this._getProfilerData();
853                 var records = this._dataTable.getRecordSet().getRecords(0, this.get("maxChartFunctions"));
854                 var arr = [];
855                 for (var i = 0, j = records.length; i<j; i++) {
856                         arr.push(records[i].getData()); 
857                 }
858                 YAHOO.log("Returning data to Chart: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
859                 return arr;
860         };
861         
862         /**
863          * Build series definition based on current configuration attributes.
864          * @method _getSeriesDef
865          * @private
866          */
867         proto._getSeriesDef = function(field) {
868                 var sd = this.get("chartSeriesDefinitions")[field];
869                 var arr = [];
870                 for(var i = 0, j = sd.group.length; i<j; i++) {
871                         var c = this.get("chartSeriesDefinitions")[sd.group[i]];
872                         arr.push(
873                                 {displayName:c.displayName,
874                                  xField:c.xField,
875                                  style: {color:c.style.color, size:c.style.size}
876                                 }
877                         );
878                 }
879                 
880                 YAHOO.log("Returning new series definition to chart: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
881                 return arr;
882         };
883         
884         /**
885          * Set up the Chart.
886          * @method _initChart
887          * @private
888          */
889         proto._initChart = function() {
890                 YAHOO.log("Initializing chart...", "info", "ProfilerViewer");
891                 
892                 this._sizeChartCanvas();
893                 
894                 YAHOO.widget.Chart.SWFURL = this.get("swfUrl");
895
896                 var self = this;
897
898                 //Create DataSource based on records currently displayed
899                 //at the top of the sort list in the DataTable.
900                 var ds = new YAHOO.util.DataSource(
901                         //force the jsfunction DataSource to run in the scope of
902                         //the ProfilerViewer, not in the YAHOO.util.DataSource scope:
903                         function() {
904                                 return self._getChartData.call(self);
905                         }, 
906                         {
907                                 responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
908                                 maxCacheEntries: 0
909                         }
910                 );
911
912                 ds.responseSchema =
913                 {
914                         fields: [ "fn", "avg", "calls", "max", "min", "total", "pct" ]
915                 };
916                 
917                 ds.subscribe('responseEvent', this._sizeChartCanvas, this, true);
918                 
919                 //Set up the chart itself.
920                 this._chartAxisDefinitionTime = new YAHOO.widget.NumericAxis();
921                 this._chartAxisDefinitionTime.labelFunction = "YAHOO.widget.ProfilerViewer.timeAxisLabelFunction";
922                 
923                 this._chartAxisDefinitionPercent = new YAHOO.widget.NumericAxis();
924                 this._chartAxisDefinitionPercent.labelFunction = "YAHOO.widget.ProfilerViewer.percentAxisLabelFunction";
925
926                 this._chartAxisDefinitionPlain = new YAHOO.widget.NumericAxis();
927                 
928                 this._chart = new YAHOO.widget.BarChart( this._chartEl, ds,
929                 {
930                         yField: "fn",
931                         series: this._getSeriesDef(this.get("sortedBy").key),
932                         style: this.get("chartStyle"),
933                         xAxis: this._chartAxisDefinitionTime
934                 } );
935                 
936                 this._drawChartLegend();
937                 this._chartInitialized = true;
938                 this._dataTable.unsubscribe("initEvent", this._initChart, this);
939                 this._dataTable.subscribe("initEvent", this._refreshChart, this, true);
940                 
941                 YAHOO.log("Chart initialization complete.", "info", "ProfilerViewer");
942         };
943         
944         /**
945          * Set up the Chart's legend
946          * @method _drawChartLegend
947          * @private
948          **/
949         proto._drawChartLegend = function() {
950                 YAHOO.log("Drawing chart legend...", "info", "ProfilerViewer");
951                 var seriesDefs = this.get("chartSeriesDefinitions");
952                 var currentDef = seriesDefs[this.get("sortedBy").key];
953                 var l = this._chartLegendEl;
954                 l.innerHTML = "";
955                 for(var i = 0, j = currentDef.group.length; i<j; i++) {
956                         var c = seriesDefs[currentDef.group[i]];
957                         var dt = document.createElement("dt");
958                         Dom.setStyle(dt, "backgroundColor", "#" + c.style.color);
959                         var dd = document.createElement("dd");
960                         dd.innerHTML = c.displayName;
961                         l.appendChild(dt);
962                         l.appendChild(dd);
963                 }
964         };
965         
966         /**
967          * Resize the chart's canvas if based on number of records
968          * returned from the chart's datasource.
969          * @method _sizeChartCanvas
970          * @private
971          **/
972         proto._sizeChartCanvas = function(o) {
973                 YAHOO.log("Resizing chart canvas...", "info", "ProfilerViewer");
974                 var bars = (o) ? o.response.length : this.get("maxChartFunctions");
975                 var s = (bars * 36) + 34;
976                 if (s != parseInt(this._chartElHeight, 10)) {
977                         this._chartElHeight = s;
978                         Dom.setStyle(this._chartEl, "height", s + "px");
979                 }
980         };
981
982     /**
983      * setAttributeConfigs TabView specific properties.
984      * @method initAttributes
985      * @param {Object} attr Hash of initial attributes
986          * @method initAttributes
987          * @private
988      */
989     proto.initAttributes = function(attr) {
990                 YAHOO.log("Initializing attributes...", "info", "ProfilerViewer");
991         YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
992         /**
993          * The YUI Loader base path from which to pull YUI files needed
994                  * in the rendering of the ProfilerViewer canvas.  Passed directly
995                  * to YUI Loader.  Leave blank to draw files from
996                  * yui.yahooapis.com.
997          * @attribute base
998          * @type string
999                  * @default ""
1000          */
1001         this.setAttributeConfig('base', {
1002             value: attr.base
1003         });
1004
1005         /**
1006          * The height of the DataTable.  The table will scroll
1007                  * vertically if the content overflows the specified
1008                  * height.
1009          * @attribute tableHeight
1010          * @type string
1011                  * @default "15em"
1012          */
1013         this.setAttributeConfig('tableHeight', {
1014             value: attr.tableHeight || "15em",
1015                         method: function(s) {
1016                                 if(this._dataTable) {
1017                                         this._dataTable.set("height", s);
1018                                 }
1019                         }
1020         });
1021                 
1022         /**
1023          * The default column key to sort by.  Valid keys are: fn, calls,
1024                  * avg, min, max, total.  Valid dir values are: 
1025                  * YAHOO.widget.DataTable.CLASS_ASC and
1026                  * YAHOO.widget.DataTable.CLASS_DESC (or their
1027                  * string equivalents).
1028          * @attribute sortedBy
1029          * @type string
1030                  * @default {key:"total", dir:"yui-dt-desc"}
1031          */
1032         this.setAttributeConfig('sortedBy', {
1033             value: attr.sortedBy || {key:"total", dir:"yui-dt-desc"}
1034         });
1035
1036         /**
1037          * A filter function to use in selecting functions that will
1038                  * appear in the ProfilerViewer report.  The function is passed
1039                  * a function report object and should return a boolean indicating
1040                  * whether that function should be included in the ProfilerViewer
1041                  * display.  The argument is structured as follows:
1042                  *
1043                  * {
1044                  *              fn: <str function name>,
1045                  *              calls : <n number of calls>,
1046                  *              avg : <n average call duration>,
1047                  *              max: <n duration of longest call>,
1048                  *              min: <n duration of shortest call>,
1049                  *              total: <n total time of all calls>
1050                  *              points : <array time in ms of each call>
1051                  *      }
1052                  *
1053                  * For example, you would use the follwing filter function to 
1054                  * return only functions that have been called at least once:
1055                  * 
1056                  *      function(o) {
1057                  *              return (o.calls > 0);
1058                  *      }
1059                  *
1060          * @attribute filter
1061          * @type function
1062                  * @default null
1063          */
1064         this.setAttributeConfig('filter', {
1065             value: attr.filter || null,
1066                         validator: YAHOO.lang.isFunction
1067         });
1068
1069                 /**
1070                  * The path to the YUI Charts swf file; must be a full URI
1071                  * or a path relative to the page being profiled. Changes at runtime
1072                  * not supported; pass this value in at instantiation.
1073                  * @attribute swfUrl
1074                  * @type string
1075                  * @default "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
1076                  */
1077                 this.setAttributeConfig('swfUrl', {
1078                         value: attr.swfUrl || "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
1079                 });
1080
1081         /**
1082          * The maximum number of functions to profile in the chart. The
1083                  * greater the number of functions, the greater the height of the
1084                  * chart canvas.
1085                  * height.
1086          * @attribute maxChartFunctions
1087          * @type int
1088                  * @default 6
1089          */
1090         this.setAttributeConfig('maxChartFunctions', {
1091             value: attr.maxChartFunctions || 6,
1092                         method: function(s) {
1093                                 if(this._rendered) {
1094                                         this._sizeChartCanvas();
1095                                 }
1096                         },
1097                         validator: YAHOO.lang.isNumber
1098         });
1099                 
1100         /**
1101          * The style object that defines the chart's visual presentation.
1102                  * Conforms to the style attribute passed to the Charts Control
1103                  * constructor.  See Charts Control User's Guide for more information
1104                  * on how to format this object.
1105          * @attribute chartStyle
1106          * @type obj
1107                  * @default See JS source for default definitions.
1108          */
1109         this.setAttributeConfig('chartStyle', {
1110             value:      attr.chartStyle || {
1111                                 font:
1112                                         {
1113                                                 name: "Arial",
1114                                                 color: 0xeeee5c,
1115                                                 size: 12
1116                                         },
1117                                         background:
1118                                         {
1119                                                 color: "6e6e63"
1120                                         }
1121                                 },
1122                         method: function() {
1123                                         if(this._rendered && this.get("showChart")) {
1124                                                 this._refreshChart();
1125                                         }
1126                                 }
1127         });
1128                 
1129         /**
1130          * The series definition information to use when charting
1131                  * specific fields on the chart.  displayName, xField,
1132                  * and style members are used to construct the series
1133                  * definition; the "group" member is the array of fields
1134                  * that should be charted when the table is sorted by a
1135                  * given field.
1136          * @attribute chartSeriesDefinitions
1137          * @type obj
1138                  * @default See JS source for full default definitions.
1139          */
1140         this.setAttributeConfig('chartSeriesDefinitions', {
1141             value:      attr.chartSeriesDefinitions ||  {
1142                                                 total: {
1143                                                         displayName: PV.STRINGS.colHeads.total[0],
1144                                                         xField: "total",
1145                                                         style: {color:"4d95dd", size:20},
1146                                                         group: ["total"]
1147                                                 },
1148                                                 calls: {                
1149                                                         displayName: PV.STRINGS.colHeads.calls[0],
1150                                                         xField: "calls",
1151                                                         style: {color:"edff9f", size:20},
1152                                                         group: ["calls"]
1153                                                 },
1154                                                 avg: {
1155                                                         displayName: PV.STRINGS.colHeads.avg[0],
1156                                                         xField: "avg",
1157                                                         style: {color:"209daf", size:9},
1158                                                         group: ["avg", "min", "max"]
1159                                                 },
1160                                                 min: {
1161                                                         displayName: PV.STRINGS.colHeads.min[0],
1162                                                         xField: "min",
1163                                                         style: {color:"b6ecf4", size:9},
1164                                                         group: ["avg", "min", "max"]
1165                                                 },
1166                                                 max: {
1167                                                         displayName: PV.STRINGS.colHeads.max[0],
1168                                                         xField: "max",
1169                                                         style: {color:"29c7de", size:9},
1170                                                         group: ["avg", "min", "max"]
1171                                                 },
1172                                                 pct: {
1173                                                         displayName: PV.STRINGS.colHeads.pct[0],
1174                                                         xField: "pct",
1175                                                         style: {color:"C96EDB", size:20},
1176                                                         group: ["pct"]
1177                                                 }
1178                                 },
1179                         method: function() {
1180                                         if(this._rendered && this.get("showChart")) {
1181                                                 this._refreshChart();
1182                                         }
1183                                 }
1184         });
1185                 
1186         /**
1187          * The default visibility setting for the viewer canvas. If true,
1188                  * the viewer will load all necessary files and render itself
1189                  * immediately upon instantiation; otherwise, the viewer will
1190                  * load only minimal resources until the user toggles visibility
1191                  * via the UI.
1192          * @attribute visible
1193          * @type boolean
1194                  * @default false
1195          */
1196         this.setAttributeConfig('visible', {
1197             value: attr.visible || false,
1198                         validator: YAHOO.lang.isBoolean,
1199                         method: function(b) {
1200                                 if(b) {
1201                                         this._show();
1202                                 } else {
1203                                         if (this._rendered) {
1204                                                 this._hide();
1205                                         }
1206                                 }
1207                         }
1208         });
1209
1210         /**
1211          * The default visibility setting for the chart.
1212          * @attribute showChart
1213          * @type boolean
1214                  * @default true
1215          */
1216         this.setAttributeConfig('showChart', {
1217             value: attr.showChart || true,
1218                         validator: YAHOO.lang.isBoolean,
1219                         writeOnce: true
1220                         
1221         });
1222                 
1223                 YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
1224                 
1225                 YAHOO.log("Attributes initialized.", "info", "ProfilerViewer");
1226     };
1227         
1228 })();
1229 YAHOO.register("profilerviewer", YAHOO.widget.ProfilerViewer, {version: "2.8.0r4", build: "2449"});