--- /dev/null
+/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+(function() {
+
+ /**
+ * The ProfilerViewer module provides a graphical display for viewing
+ * the output of the YUI Profiler <http://developer.yahoo.com/yui/profiler>.
+ * @module profilerviewer
+ * @requires yahoo, dom, event, element, profiler, yuiloader
+ */
+
+ /**
+ * A widget to view YUI Profiler output.
+ * @namespace YAHOO.widget
+ * @class ProfilerViewer
+ * @extends YAHOO.util.Element
+ * @constructor
+ * @param {HTMLElement | String | Object} el(optional) The html
+ * element into which the ProfileViewer should be rendered.
+ * An element will be created if none provided.
+ * @param {Object} attr (optional) A key map of the ProfilerViewer's
+ * initial attributes. Ignored if first arg is an attributes object.
+ */
+ YAHOO.widget.ProfilerViewer = function(el, attr) {
+ attr = attr || {};
+ if (arguments.length == 1 && !YAHOO.lang.isString(el) && !el.nodeName) {
+ attr = el;
+ el = attr.element || null;
+ }
+ if (!el && !attr.element) {
+ el = this._createProfilerViewerElement();
+ }
+
+ YAHOO.widget.ProfilerViewer.superclass.constructor.call(this, el, attr);
+
+ this._init();
+
+ YAHOO.log("ProfilerViewer instantiated.", "info", "ProfilerViewer");
+ };
+
+ YAHOO.extend(YAHOO.widget.ProfilerViewer, YAHOO.util.Element);
+
+ // Static members of YAHOO.widget.ProfilerViewer:
+ YAHOO.lang.augmentObject(YAHOO.widget.ProfilerViewer, {
+ /**
+ * Classname for ProfilerViewer containing element.
+ * @static
+ * @property CLASS
+ * @type string
+ * @public
+ * @default "yui-pv"
+ */
+ CLASS: 'yui-pv',
+
+ /**
+ * Classname for ProfilerViewer button dashboard.
+ * @static
+ * @property CLASS_DASHBOARD
+ * @type string
+ * @public
+ * @default "yui-pv-dashboard"
+ */
+ CLASS_DASHBOARD: 'yui-pv-dashboard',
+
+ /**
+ * Classname for the "refresh data" button.
+ * @static
+ * @property CLASS_REFRESH
+ * @type string
+ * @public
+ * @default "yui-pv-refresh"
+ */
+ CLASS_REFRESH: 'yui-pv-refresh',
+
+ /**
+ * Classname for busy indicator in the dashboard.
+ * @static
+ * @property CLASS_BUSY
+ * @type string
+ * @public
+ * @default "yui-pv-busy"
+ */
+ CLASS_BUSY: 'yui-pv-busy',
+
+ /**
+ * Classname for element containing the chart and chart
+ * legend elements.
+ * @static
+ * @property CLASS_CHART_CONTAINER
+ * @type string
+ * @public
+ * @default "yui-pv-chartcontainer"
+ */
+ CLASS_CHART_CONTAINER: 'yui-pv-chartcontainer',
+
+ /**
+ * Classname for element containing the chart.
+ * @static
+ * @property CLASS_CHART
+ * @type string
+ * @public
+ * @default "yui-pv-chart"
+ */
+ CLASS_CHART: 'yui-pv-chart',
+
+ /**
+ * Classname for element containing the chart's legend.
+ * @static
+ * @property CLASS_CHART_LEGEND
+ * @type string
+ * @public
+ * @default "yui-pv-chartlegend"
+ */
+ CLASS_CHART_LEGEND: 'yui-pv-chartlegend',
+
+ /**
+ * Classname for element containing the datatable.
+ * @static
+ * @property CLASS_TABLE
+ * @type string
+ * @public
+ * @default "yui-pv-table"
+ */
+ CLASS_TABLE: 'yui-pv-table',
+
+ /**
+ * Strings used in the UI.
+ * @static
+ * @property STRINGS
+ * @object
+ * @public
+ * @default English language strings for UI.
+ */
+ STRINGS: {
+ title: "YUI Profiler (beta)",
+ buttons: {
+ viewprofiler: "View Profiler Data",
+ hideprofiler: "Hide Profiler Report",
+ showchart: "Show Chart",
+ hidechart: "Hide Chart",
+ refreshdata: "Refresh Data"
+ },
+ colHeads: {
+ //key: [column head label, width in pixels]
+ fn: ["Function/Method", null], //must auto-size
+ calls: ["Calls", 40],
+ avg: ["Average", 80],
+ min: ["Shortest", 70],
+ max: ["Longest", 70],
+ total: ["Total Time", 70],
+ pct: ["Percent", 70]
+ },
+ millisecondsAbbrev: "ms",
+ initMessage: "initialiazing chart...",
+ 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>."
+ },
+
+ /**
+ * Function used to format numbers in milliseconds
+ * for chart; must be publicly accessible, per Charts spec.
+ * @static
+ * @property timeAxisLabelFunction
+ * @type function
+ * @private
+ */
+ timeAxisLabelFunction: function(n) {
+ var a = (n === Math.floor(n)) ? n : (Math.round(n*1000))/1000;
+ return (a + " " + YAHOO.widget.ProfilerViewer.STRINGS.millisecondsAbbrev);
+ },
+
+ /**
+ * Function used to format percent numbers for chart; must
+ * be publicly accessible, per Charts spec.
+ * @static
+ * @property percentAxisLabelFunction
+ * @type function
+ * @private
+ */
+ percentAxisLabelFunction: function(n) {
+ var a = (n === Math.floor(n)) ? n : (Math.round(n*100))/100;
+ return (a + "%");
+ }
+
+
+ },true);
+
+
+ //
+ // STANDARD SHORTCUTS
+ //
+ var Dom = YAHOO.util.Dom;
+ var Event = YAHOO.util.Event;
+ var Profiler = YAHOO.tool.Profiler;
+ var PV = YAHOO.widget.ProfilerViewer;
+ var proto = PV.prototype;
+
+
+ //
+ // PUBLIC METHODS
+ //
+
+ /**
+ * Refreshes the data displayed in the ProfilerViewer. When called,
+ * this will invoke a refresh of the DataTable and (if displayed)
+ * the Chart.
+ * @method refreshData
+ * @return void
+ * @public
+ */
+ proto.refreshData = function() {
+ YAHOO.log("Data refresh requested via refreshData method.", "info", "ProfilerViewer");
+ this.fireEvent("dataRefreshEvent");
+ };
+
+ /**
+ * Returns the element containing the console's header.
+ * @method getHeadEl
+ * @return HTMLElement
+ * @public
+ */
+ proto.getHeadEl = function() {
+ YAHOO.log("Head element requested via getHeadEl.", "info", "ProfilerViewer");
+ return (this._headEl) ? Dom.get(this._headEl) : false;
+ };
+
+ /**
+ * Returns the element containing the console's body, including
+ * the chart and the datatable..
+ * @method getBodyEl
+ * @return HTMLElement
+ * @public
+ */
+ proto.getBodyEl = function() {
+ YAHOO.log("Body element requested via getBodyEl.", "info", "ProfilerViewer");
+ return (this._bodyEl) ? Dom.get(this._bodyEl) : false;
+ };
+
+ /**
+ * Returns the element containing the console's chart.
+ * @method getChartEl
+ * @return HTMLElement
+ * @public
+ */
+ proto.getChartEl = function() {
+ YAHOO.log("Chart element requested via getChartEl.", "info", "ProfilerViewer");
+ return (this._chartEl) ? Dom.get(this._chartEl) : false;
+ };
+
+ /**
+ * Returns the element containing the console's dataTable.
+ * @method getTableEl
+ * @return HTMLElement
+ * @public
+ */
+ proto.getTableEl = function() {
+ YAHOO.log("DataTable element requested via getTableEl.", "info", "ProfilerViewer");
+ return (this._tableEl) ? Dom.get(this._tableEl) : false;
+ };
+
+ /**
+ * Returns the element containing the console's DataTable
+ * instance.
+ * @method getDataTable
+ * @return YAHOO.widget.DataTable
+ * @public
+ */
+ proto.getDataTable = function() {
+ YAHOO.log("DataTable instance requested via getDataTable.", "info", "ProfilerViewer");
+ return this._dataTable;
+ };
+
+ /**
+ * Returns the element containing the console's Chart instance.
+ * @method getChart
+ * @return YAHOO.widget.BarChart
+ * @public
+ */
+ proto.getChart = function() {
+ YAHOO.log("Chart instance requested via getChart.", "info", "ProfilerViewer");
+ return this._chart;
+ };
+
+
+ //
+ // PRIVATE PROPERTIES
+ //
+ proto._rendered = false;
+ proto._headEl = null;
+ proto._bodyEl = null;
+ proto._toggleVisibleEl = null;
+ proto._busyEl = null;
+ proto._busy = false;
+
+ proto._tableEl = null;
+ proto._dataTable = null;
+
+ proto._chartEl = null;
+ proto._chartLegendEl = null;
+ proto._chartElHeight = 250;
+ proto._chart = null;
+ proto._chartInitialized = false;
+
+ //
+ // PRIVATE METHODS
+ //
+
+ proto._init = function() {
+ /**
+ * CUSTOM EVENTS
+ **/
+
+ /**
+ * Fired when a data refresh is requested. No arguments are passed
+ * with this event.
+ *
+ * @event refreshDataEvent
+ */
+ this.createEvent("dataRefreshEvent");
+
+ /**
+ * Fired when the viewer canvas first renders. No arguments are passed
+ * with this event.
+ *
+ * @event renderEvent
+ */
+ this.createEvent("renderEvent");
+
+ this.on("dataRefreshEvent", this._refreshDataTable, this, true);
+
+ this._initLauncherDOM();
+
+ if(this.get("showChart")) {
+ this.on("sortedByChange", this._refreshChart);
+ }
+
+ YAHOO.log("ProfilerViewer instance initialization complete.", "info", "ProfilerViewer");
+ };
+
+ /**
+ * If no element is passed in, create it as the first element
+ * in the document.
+ * @method _createProfilerViewerElement
+ * @return HTMLElement
+ * @private
+ */
+ proto._createProfilerViewerElement = function() {
+ YAHOO.log("Creating root element...", "info", "ProfilerViewer");
+
+ var el = document.createElement("div");
+ document.body.insertBefore(el, document.body.firstChild);
+ Dom.addClass(el, this.SKIN_CLASS);
+ Dom.addClass(el, PV.CLASS);
+ YAHOO.log(el);
+ return el;
+ };
+
+ /**
+ * Provides a readable name for the ProfilerViewer instance.
+ * @method toString
+ * @return String
+ * @private
+ */
+ proto.toString = function() {
+ return "ProfilerViewer " + (this.get('id') || this.get('tagName'));
+ };
+
+ /**
+ * Toggles visibility of the viewer canvas.
+ * @method _toggleVisible
+ * @return void
+ * @private
+ */
+ proto._toggleVisible = function() {
+ YAHOO.log("Toggling visibility to " + !this.get("visible") + ".", "info", "ProfilerViewer");
+
+ var newVis = (this.get("visible")) ? false : true;
+ this.set("visible", newVis);
+ };
+
+ /**
+ * Shows the viewer canvas.
+ * @method show
+ * @return void
+ * @private
+ */
+ proto._show = function() {
+ if(!this._busy) {
+ this._setBusyState(true);
+ if(!this._rendered) {
+ var loader = new YAHOO.util.YUILoader();
+ if (this.get("base")) {
+ loader.base = this.get("base");
+ }
+
+ var modules = ["datatable"];
+ if(this.get("showChart")) {
+ modules.push("charts");
+ }
+
+ loader.insert({ require: modules,
+ onSuccess: function() {
+ this._render();
+ },
+ scope: this});
+ } else {
+ var el = this.get("element");
+ Dom.removeClass(el, "yui-pv-minimized");
+ this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
+
+ //The Flash Charts component can't be set to display:none,
+ //and even after positioning it offscreen the screen
+ //may fail to repaint in some browsers. Adding an empty
+ //style rule to the console body can help force a repaint:
+ Dom.addClass(el, "yui-pv-null");
+ Dom.removeClass(el, "yui-pv-null");
+
+ //Always refresh data when changing to visible:
+ this.refreshData();
+ }
+ }
+ };
+
+ /**
+ * Hides the viewer canvas.
+ * @method hide
+ * @return void
+ * @private
+ */
+ proto._hide = function() {
+ this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.viewprofiler;
+ Dom.addClass(this.get("element"), "yui-pv-minimized");
+ };
+
+ /**
+ * Render the viewer canvas
+ * @method _render
+ * @return void
+ * @private
+ */
+ proto._render = function() {
+ YAHOO.log("Beginning to render ProfilerViewer canvas...", "info", "ProfilerViewer");
+
+ Dom.removeClass(this.get("element"), "yui-pv-minimized");
+
+ this._initViewerDOM();
+ this._initDataTable();
+ if(this.get("showChart")) {
+ this._initChartDOM();
+ this._initChart();
+ }
+ this._rendered = true;
+ this._toggleVisibleEl.innerHTML = PV.STRINGS.buttons.hideprofiler;
+
+ this.fireEvent("renderEvent");
+
+ YAHOO.log("ProfilerViewer rendering complete...", "info", "ProfilerViewer");
+ };
+
+ /**
+ * Set up the DOM structure for the ProfilerViewer launcher.
+ * @method _initLauncherDOM
+ * @private
+ */
+ proto._initLauncherDOM = function() {
+ YAHOO.log("Creating the launcher...", "info", "ProfilerViewer");
+
+ var el = this.get("element");
+ Dom.addClass(el, PV.CLASS);
+ Dom.addClass(el, "yui-pv-minimized");
+
+ this._headEl = document.createElement("div");
+ Dom.addClass(this._headEl, "hd");
+
+ var s = PV.STRINGS.buttons;
+ var b = (this.get("visible")) ? s.hideprofiler : s.viewprofiler;
+
+ this._toggleVisibleEl = this._createButton(b, this._headEl);
+
+ this._refreshEl = this._createButton(s.refreshdata, this._headEl);
+ Dom.addClass(this._refreshEl, PV.CLASS_REFRESH);
+
+ this._busyEl = document.createElement("span");
+ this._headEl.appendChild(this._busyEl);
+
+ var title = document.createElement("h4");
+ title.innerHTML = PV.STRINGS.title;
+ this._headEl.appendChild(title);
+
+ el.appendChild(this._headEl);
+
+ Event.on(this._toggleVisibleEl, "click", this._toggleVisible, this, true);
+ Event.on(this._refreshEl, "click", function() {
+ if(!this._busy) {
+ this._setBusyState(true);
+ this.fireEvent("dataRefreshEvent");
+ }
+ }, this, true);
+ };
+
+ /**
+ * Set up the DOM structure for the ProfilerViewer canvas,
+ * including the holder for the DataTable.
+ * @method _initViewerDOM
+ * @private
+ */
+ proto._initViewerDOM = function() {
+ YAHOO.log("Creating DOM structure for viewer...", "info", "ProfilerViewer");
+
+ var el = this.get("element");
+ this._bodyEl = document.createElement("div");
+ Dom.addClass(this._bodyEl, "bd");
+ this._tableEl = document.createElement("div");
+ Dom.addClass(this._tableEl, PV.CLASS_TABLE);
+ this._bodyEl.appendChild(this._tableEl);
+ el.appendChild(this._bodyEl);
+ };
+
+ /**
+ * Set up the DOM structure for the ProfilerViewer canvas.
+ * @method _initChartDOM
+ * @private
+ */
+ proto._initChartDOM = function() {
+ YAHOO.log("Adding DOM structure for chart...", "info", "ProfilerViewer");
+
+ this._chartContainer = document.createElement("div");
+ Dom.addClass(this._chartContainer, PV.CLASS_CHART_CONTAINER);
+
+ var chl = document.createElement("div");
+ Dom.addClass(chl, PV.CLASS_CHART_LEGEND);
+
+ var chw = document.createElement("div");
+
+ this._chartLegendEl = document.createElement("dl");
+ this._chartLegendEl.innerHTML = "<dd>" + PV.STRINGS.initMessage + "</dd>";
+
+ this._chartEl = document.createElement("div");
+ Dom.addClass(this._chartEl, PV.CLASS_CHART);
+
+ var msg = document.createElement("p");
+ msg.innerHTML = PV.STRINGS.installFlashMessage;
+ this._chartEl.appendChild(msg);
+
+ this._chartContainer.appendChild(chl);
+ chl.appendChild(chw);
+ chw.appendChild(this._chartLegendEl);
+ this._chartContainer.appendChild(this._chartEl);
+ this._bodyEl.insertBefore(this._chartContainer,this._tableEl);
+ };
+
+
+ /**
+ * Create anchor elements for use as buttons. Args: label
+ * is text to appear on the face of the button, parentEl
+ * is the el to which the anchor will be attached, position
+ * is true for inserting as the first node and false for
+ * inserting as the last node of the parentEl.
+ * @method _createButton
+ * @private
+ */
+ proto._createButton = function(label, parentEl, position) {
+ var b = document.createElement("a");
+ b.innerHTML = b.title = label;
+ if(parentEl) {
+ if(!position) {
+ parentEl.appendChild(b);
+ } else {
+ parentEl.insertBefore(b, parentEl.firstChild);
+ }
+ }
+ return b;
+ };
+
+ /**
+ * Set's console busy state.
+ * @method _setBusyState
+ * @private
+ **/
+ proto._setBusyState = function(b) {
+ if(b) {
+ Dom.addClass(this._busyEl, PV.CLASS_BUSY);
+ this._busy = true;
+ } else {
+ Dom.removeClass(this._busyEl, PV.CLASS_BUSY);
+ this._busy = false;
+ }
+ };
+
+ /**
+ * Generages a sorting function based on current sortedBy
+ * values.
+ * @method _createProfilerViewerElement
+ * @private
+ **/
+ proto._genSortFunction = function(key, dir) {
+ var by = key;
+ var direction = dir;
+ return function(a, b) {
+ if (direction == YAHOO.widget.DataTable.CLASS_ASC) {
+ return a[by] - b[by];
+ } else {
+ return ((a[by] - b[by]) * -1);
+ }
+ };
+ };
+
+ /**
+ * Utility function for array sums.
+ * @method _arraySum
+ * @private
+ **/
+ var _arraySum = function(arr){
+ var ct = 0;
+ for(var i = 0; i < arr.length; ct+=arr[i++]){}
+ return ct;
+ };
+
+ /**
+ * Retrieves data from Profiler, filtering and sorting as needed
+ * based on current widget state. Adds calculated percentage
+ * column and function name to data returned by Profiler.
+ * @method _getProfilerData
+ * @private
+ **/
+ proto._getProfilerData = function() {
+ YAHOO.log("Profiler data requested from function DataSource.", "info", "ProfilerViewer");
+
+ var obj = Profiler.getFullReport();
+ var arr = [];
+ var totalTime = 0;
+ for (name in obj) {
+ if (YAHOO.lang.hasOwnProperty(obj, name)) {
+ var r = obj[name];
+ var o = {};
+ o.fn = name; //add function name to record
+ o.points = r.points.slice(); //copy live array
+ o.calls = r.calls;
+ o.min = r.min;
+ o.max = r.max;
+ o.avg = r.avg;
+ o.total = _arraySum(o.points);
+ o.points = r.points;
+ var f = this.get("filter");
+ if((!f) || (f(o))) {
+ arr.push(o);
+ totalTime += o.total;
+ }
+ }
+ }
+
+ //add calculated percentage column
+ for (var i = 0, j = arr.length; i < j; i++) {
+ arr[i].pct = (totalTime) ? (arr[i].total * 100) / totalTime : 0;
+ }
+
+ var sortedBy = this.get("sortedBy");
+ var key = sortedBy.key;
+ var dir = sortedBy.dir;
+
+ arr.sort(this._genSortFunction(key, dir));
+
+ YAHOO.log("Returning data from DataSource: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
+
+ return arr;
+ };
+
+ /**
+ * Set up the DataTable.
+ * @method _initDataTable
+ * @private
+ */
+ proto._initDataTable = function() {
+ YAHOO.log("Creating DataTable instance...", "info", "ProfilerViewer");
+
+ var self = this;
+
+ //Set up the JS Function DataSource, pulling data from
+ //the Profiler.
+ this._dataSource = new YAHOO.util.DataSource(
+ function() {
+ return self._getProfilerData.call(self);
+ },
+ {
+ responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
+ maxCacheEntries: 0
+ }
+ );
+ var ds = this._dataSource;
+
+ ds.responseSchema =
+ {
+ fields: [ "fn", "avg", "calls", "max", "min", "total", "pct", "points"]
+ };
+
+ //Set up the DataTable.
+ var formatTimeValue = function(elCell, oRecord, oColumn, oData) {
+ var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*1000))/1000;
+ elCell.innerHTML = a + " " + PV.STRINGS.millisecondsAbbrev;
+ };
+
+ var formatPercent = function(elCell, oRecord, oColumn, oData) {
+ var a = (oData === Math.floor(oData)) ? oData : (Math.round(oData*100))/100;
+ elCell.innerHTML = a + "%";
+ };
+
+ var a = YAHOO.widget.DataTable.CLASS_ASC;
+ var d = YAHOO.widget.DataTable.CLASS_DESC;
+ var c = PV.STRINGS.colHeads;
+ var f = formatTimeValue;
+
+ var cols = [
+ {key:"fn", sortable:true, label: c.fn[0],
+ sortOptions: {defaultDir:a},
+ resizeable: (YAHOO.util.DragDrop) ? true : false,
+ minWidth:c.fn[1]},
+ {key:"calls", sortable:true, label: c.calls[0],
+ sortOptions: {defaultDir:d},
+ width:c.calls[1]},
+ {key:"avg", sortable:true, label: c.avg[0],
+ sortOptions: {defaultDir:d},
+ formatter:f,
+ width:c.avg[1]},
+ {key:"min", sortable:true, label: c.min[0],
+ sortOptions: {defaultDir:a},
+ formatter:f,
+ width:c.min[1]},
+ {key:"max", sortable:true, label: c.max[0],
+ sortOptions: {defaultDir:d},
+ formatter:f,
+ width:c.max[1]},
+ {key:"total", sortable:true, label: c.total[0],
+ sortOptions: {defaultDir:d},
+ formatter:f,
+ width:c.total[1]},
+ {key:"pct", sortable:true, label: c.pct[0],
+ sortOptions: {defaultDir:d},
+ formatter:formatPercent,
+ width:c.pct[1]}
+ ];
+
+ this._dataTable = new YAHOO.widget.DataTable(this._tableEl, cols, ds, {
+ scrollable:true,
+ height:this.get("tableHeight"),
+ initialRequest:null,
+ sortedBy: {
+ key: "total",
+ dir: YAHOO.widget.DataTable.CLASS_DESC
+ }
+ });
+ var dt = this._dataTable;
+
+ //Wire up DataTable events to drive the rest of the UI.
+ dt.subscribe("sortedByChange", this._sortedByChange, this, true);
+ dt.subscribe("renderEvent", this._dataTableRenderHandler, this, true);
+ dt.subscribe("initEvent", this._dataTableRenderHandler, this, true);
+ Event.on(this._tableEl.getElementsByTagName("th"), "click", this._thClickHandler, this, true);
+ YAHOO.log("DataTable initialized.", "info", "ProfilerViewer");
+ };
+
+ /**
+ * Proxy the sort event in DataTable into the ProfilerViewer
+ * attribute.
+ * @method _sortedByChange
+ * @private
+ **/
+ proto._sortedByChange = function(o) {
+ if(o.newValue && o.newValue.key) {
+ YAHOO.log("Relaying DataTable sortedBy value change; new key: " + o.newValue.key + "; new direction: " + o.newValue.dir + ".", "info", "ProfilerViewer");
+ this.set("sortedBy", {key: o.newValue.key, dir:o.newValue.dir});
+ }
+ };
+
+ /**
+ * Proxy the render event in DataTable into the ProfilerViewer
+ * attribute.
+ * @method _dataTableRenderHandler
+ * @private
+ **/
+ proto._dataTableRenderHandler = function(o) {
+ YAHOO.log("DataTable's render event has fired.", "info", "ProfilerViewer");
+ this._setBusyState(false);
+ };
+
+ /**
+ * Event handler for clicks on the DataTable's sortable column
+ * heads.
+ * @method _thClickHandler
+ * @private
+ **/
+ proto._thClickHandler = function(o) {
+ YAHOO.log("DataTable's header row was clicked for sorting.", "info", "ProfilerViewer");
+ this._setBusyState(true);
+ };
+
+ /**
+ * Refresh DataTable, getting new data from Profiler.
+ * @method _refreshDataTable
+ * @private
+ **/
+ proto._refreshDataTable = function(args) {
+ YAHOO.log("Beginning to refresh DataTable contents...", "info", "ProfilerViewer");
+ var dt = this._dataTable;
+ dt.getDataSource().sendRequest("", dt.onDataReturnInitializeTable, dt);
+ YAHOO.log("DataTable refresh complete.", "info", "ProfilerViewer");
+ };
+
+ /**
+ * Refresh chart, getting new data from table.
+ * @method _refreshChart
+ * @private
+ **/
+ proto._refreshChart = function() {
+ YAHOO.log("Beginning to refresh Chart contents...", "info", "ProfilerViewer");
+
+ switch (this.get("sortedBy").key) {
+ case "fn":
+ /*Keep the same data on the chart, but force update to
+ reflect new sort order on function/method name: */
+ this._chart.set("dataSource", this._chart.get("dataSource"));
+ /*no further action necessary; chart redraws*/
+ return;
+ case "calls":
+ /*Null out the xAxis formatting before redrawing chart.*/
+ this._chart.set("xAxis", this._chartAxisDefinitionPlain);
+ break;
+ case "pct":
+ this._chart.set("xAxis", this._chartAxisDefinitionPercent);
+ break;
+ default:
+ /*Set the default xAxis; redraw legend; set the new series definition.*/
+ this._chart.set("xAxis", this._chartAxisDefinitionTime);
+ break;
+ }
+
+ this._drawChartLegend();
+ this._chart.set("series", this._getSeriesDef(this.get("sortedBy").key));
+
+ YAHOO.log("Chart refresh complete.", "info", "ProfilerViewer");
+ };
+
+ /**
+ * Get data for the Chart from DataTable recordset
+ * @method _getChartData
+ * @private
+ */
+ proto._getChartData = function() {
+ YAHOO.log("Getting data for chart from function DataSource.", "info", "ProfilerViewer");
+ //var records = this._getProfilerData();
+ var records = this._dataTable.getRecordSet().getRecords(0, this.get("maxChartFunctions"));
+ var arr = [];
+ for (var i = 0, j = records.length; i<j; i++) {
+ arr.push(records[i].getData());
+ }
+ YAHOO.log("Returning data to Chart: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
+ return arr;
+ };
+
+ /**
+ * Build series definition based on current configuration attributes.
+ * @method _getSeriesDef
+ * @private
+ */
+ proto._getSeriesDef = function(field) {
+ var sd = this.get("chartSeriesDefinitions")[field];
+ var arr = [];
+ for(var i = 0, j = sd.group.length; i<j; i++) {
+ var c = this.get("chartSeriesDefinitions")[sd.group[i]];
+ arr.push(
+ {displayName:c.displayName,
+ xField:c.xField,
+ style: {color:c.style.color, size:c.style.size}
+ }
+ );
+ }
+
+ YAHOO.log("Returning new series definition to chart: " + YAHOO.lang.dump(arr), "info", "ProfilerViewer");
+ return arr;
+ };
+
+ /**
+ * Set up the Chart.
+ * @method _initChart
+ * @private
+ */
+ proto._initChart = function() {
+ YAHOO.log("Initializing chart...", "info", "ProfilerViewer");
+
+ this._sizeChartCanvas();
+
+ YAHOO.widget.Chart.SWFURL = this.get("swfUrl");
+
+ var self = this;
+
+ //Create DataSource based on records currently displayed
+ //at the top of the sort list in the DataTable.
+ var ds = new YAHOO.util.DataSource(
+ //force the jsfunction DataSource to run in the scope of
+ //the ProfilerViewer, not in the YAHOO.util.DataSource scope:
+ function() {
+ return self._getChartData.call(self);
+ },
+ {
+ responseType: YAHOO.util.DataSource.TYPE_JSARRAY,
+ maxCacheEntries: 0
+ }
+ );
+
+ ds.responseSchema =
+ {
+ fields: [ "fn", "avg", "calls", "max", "min", "total", "pct" ]
+ };
+
+ ds.subscribe('responseEvent', this._sizeChartCanvas, this, true);
+
+ //Set up the chart itself.
+ this._chartAxisDefinitionTime = new YAHOO.widget.NumericAxis();
+ this._chartAxisDefinitionTime.labelFunction = "YAHOO.widget.ProfilerViewer.timeAxisLabelFunction";
+
+ this._chartAxisDefinitionPercent = new YAHOO.widget.NumericAxis();
+ this._chartAxisDefinitionPercent.labelFunction = "YAHOO.widget.ProfilerViewer.percentAxisLabelFunction";
+
+ this._chartAxisDefinitionPlain = new YAHOO.widget.NumericAxis();
+
+ this._chart = new YAHOO.widget.BarChart( this._chartEl, ds,
+ {
+ yField: "fn",
+ series: this._getSeriesDef(this.get("sortedBy").key),
+ style: this.get("chartStyle"),
+ xAxis: this._chartAxisDefinitionTime
+ } );
+
+ this._drawChartLegend();
+ this._chartInitialized = true;
+ this._dataTable.unsubscribe("initEvent", this._initChart, this);
+ this._dataTable.subscribe("initEvent", this._refreshChart, this, true);
+
+ YAHOO.log("Chart initialization complete.", "info", "ProfilerViewer");
+ };
+
+ /**
+ * Set up the Chart's legend
+ * @method _drawChartLegend
+ * @private
+ **/
+ proto._drawChartLegend = function() {
+ YAHOO.log("Drawing chart legend...", "info", "ProfilerViewer");
+ var seriesDefs = this.get("chartSeriesDefinitions");
+ var currentDef = seriesDefs[this.get("sortedBy").key];
+ var l = this._chartLegendEl;
+ l.innerHTML = "";
+ for(var i = 0, j = currentDef.group.length; i<j; i++) {
+ var c = seriesDefs[currentDef.group[i]];
+ var dt = document.createElement("dt");
+ Dom.setStyle(dt, "backgroundColor", "#" + c.style.color);
+ var dd = document.createElement("dd");
+ dd.innerHTML = c.displayName;
+ l.appendChild(dt);
+ l.appendChild(dd);
+ }
+ };
+
+ /**
+ * Resize the chart's canvas if based on number of records
+ * returned from the chart's datasource.
+ * @method _sizeChartCanvas
+ * @private
+ **/
+ proto._sizeChartCanvas = function(o) {
+ YAHOO.log("Resizing chart canvas...", "info", "ProfilerViewer");
+ var bars = (o) ? o.response.length : this.get("maxChartFunctions");
+ var s = (bars * 36) + 34;
+ if (s != parseInt(this._chartElHeight, 10)) {
+ this._chartElHeight = s;
+ Dom.setStyle(this._chartEl, "height", s + "px");
+ }
+ };
+
+ /**
+ * setAttributeConfigs TabView specific properties.
+ * @method initAttributes
+ * @param {Object} attr Hash of initial attributes
+ * @method initAttributes
+ * @private
+ */
+ proto.initAttributes = function(attr) {
+ YAHOO.log("Initializing attributes...", "info", "ProfilerViewer");
+ YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
+ /**
+ * The YUI Loader base path from which to pull YUI files needed
+ * in the rendering of the ProfilerViewer canvas. Passed directly
+ * to YUI Loader. Leave blank to draw files from
+ * yui.yahooapis.com.
+ * @attribute base
+ * @type string
+ * @default ""
+ */
+ this.setAttributeConfig('base', {
+ value: attr.base
+ });
+
+ /**
+ * The height of the DataTable. The table will scroll
+ * vertically if the content overflows the specified
+ * height.
+ * @attribute tableHeight
+ * @type string
+ * @default "15em"
+ */
+ this.setAttributeConfig('tableHeight', {
+ value: attr.tableHeight || "15em",
+ method: function(s) {
+ if(this._dataTable) {
+ this._dataTable.set("height", s);
+ }
+ }
+ });
+
+ /**
+ * The default column key to sort by. Valid keys are: fn, calls,
+ * avg, min, max, total. Valid dir values are:
+ * YAHOO.widget.DataTable.CLASS_ASC and
+ * YAHOO.widget.DataTable.CLASS_DESC (or their
+ * string equivalents).
+ * @attribute sortedBy
+ * @type string
+ * @default {key:"total", dir:"yui-dt-desc"}
+ */
+ this.setAttributeConfig('sortedBy', {
+ value: attr.sortedBy || {key:"total", dir:"yui-dt-desc"}
+ });
+
+ /**
+ * A filter function to use in selecting functions that will
+ * appear in the ProfilerViewer report. The function is passed
+ * a function report object and should return a boolean indicating
+ * whether that function should be included in the ProfilerViewer
+ * display. The argument is structured as follows:
+ *
+ * {
+ * fn: <str function name>,
+ * calls : <n number of calls>,
+ * avg : <n average call duration>,
+ * max: <n duration of longest call>,
+ * min: <n duration of shortest call>,
+ * total: <n total time of all calls>
+ * points : <array time in ms of each call>
+ * }
+ *
+ * For example, you would use the follwing filter function to
+ * return only functions that have been called at least once:
+ *
+ * function(o) {
+ * return (o.calls > 0);
+ * }
+ *
+ * @attribute filter
+ * @type function
+ * @default null
+ */
+ this.setAttributeConfig('filter', {
+ value: attr.filter || null,
+ validator: YAHOO.lang.isFunction
+ });
+
+ /**
+ * The path to the YUI Charts swf file; must be a full URI
+ * or a path relative to the page being profiled. Changes at runtime
+ * not supported; pass this value in at instantiation.
+ * @attribute swfUrl
+ * @type string
+ * @default "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
+ */
+ this.setAttributeConfig('swfUrl', {
+ value: attr.swfUrl || "http://yui.yahooapis.com/2.5.0/build/charts/assets/charts.swf"
+ });
+
+ /**
+ * The maximum number of functions to profile in the chart. The
+ * greater the number of functions, the greater the height of the
+ * chart canvas.
+ * height.
+ * @attribute maxChartFunctions
+ * @type int
+ * @default 6
+ */
+ this.setAttributeConfig('maxChartFunctions', {
+ value: attr.maxChartFunctions || 6,
+ method: function(s) {
+ if(this._rendered) {
+ this._sizeChartCanvas();
+ }
+ },
+ validator: YAHOO.lang.isNumber
+ });
+
+ /**
+ * The style object that defines the chart's visual presentation.
+ * Conforms to the style attribute passed to the Charts Control
+ * constructor. See Charts Control User's Guide for more information
+ * on how to format this object.
+ * @attribute chartStyle
+ * @type obj
+ * @default See JS source for default definitions.
+ */
+ this.setAttributeConfig('chartStyle', {
+ value: attr.chartStyle || {
+ font:
+ {
+ name: "Arial",
+ color: 0xeeee5c,
+ size: 12
+ },
+ background:
+ {
+ color: "6e6e63"
+ }
+ },
+ method: function() {
+ if(this._rendered && this.get("showChart")) {
+ this._refreshChart();
+ }
+ }
+ });
+
+ /**
+ * The series definition information to use when charting
+ * specific fields on the chart. displayName, xField,
+ * and style members are used to construct the series
+ * definition; the "group" member is the array of fields
+ * that should be charted when the table is sorted by a
+ * given field.
+ * @attribute chartSeriesDefinitions
+ * @type obj
+ * @default See JS source for full default definitions.
+ */
+ this.setAttributeConfig('chartSeriesDefinitions', {
+ value: attr.chartSeriesDefinitions || {
+ total: {
+ displayName: PV.STRINGS.colHeads.total[0],
+ xField: "total",
+ style: {color:"4d95dd", size:20},
+ group: ["total"]
+ },
+ calls: {
+ displayName: PV.STRINGS.colHeads.calls[0],
+ xField: "calls",
+ style: {color:"edff9f", size:20},
+ group: ["calls"]
+ },
+ avg: {
+ displayName: PV.STRINGS.colHeads.avg[0],
+ xField: "avg",
+ style: {color:"209daf", size:9},
+ group: ["avg", "min", "max"]
+ },
+ min: {
+ displayName: PV.STRINGS.colHeads.min[0],
+ xField: "min",
+ style: {color:"b6ecf4", size:9},
+ group: ["avg", "min", "max"]
+ },
+ max: {
+ displayName: PV.STRINGS.colHeads.max[0],
+ xField: "max",
+ style: {color:"29c7de", size:9},
+ group: ["avg", "min", "max"]
+ },
+ pct: {
+ displayName: PV.STRINGS.colHeads.pct[0],
+ xField: "pct",
+ style: {color:"C96EDB", size:20},
+ group: ["pct"]
+ }
+ },
+ method: function() {
+ if(this._rendered && this.get("showChart")) {
+ this._refreshChart();
+ }
+ }
+ });
+
+ /**
+ * The default visibility setting for the viewer canvas. If true,
+ * the viewer will load all necessary files and render itself
+ * immediately upon instantiation; otherwise, the viewer will
+ * load only minimal resources until the user toggles visibility
+ * via the UI.
+ * @attribute visible
+ * @type boolean
+ * @default false
+ */
+ this.setAttributeConfig('visible', {
+ value: attr.visible || false,
+ validator: YAHOO.lang.isBoolean,
+ method: function(b) {
+ if(b) {
+ this._show();
+ } else {
+ if (this._rendered) {
+ this._hide();
+ }
+ }
+ }
+ });
+
+ /**
+ * The default visibility setting for the chart.
+ * @attribute showChart
+ * @type boolean
+ * @default true
+ */
+ this.setAttributeConfig('showChart', {
+ value: attr.showChart || true,
+ validator: YAHOO.lang.isBoolean,
+ writeOnce: true
+
+ });
+
+ YAHOO.widget.ProfilerViewer.superclass.initAttributes.call(this, attr);
+
+ YAHOO.log("Attributes initialized.", "info", "ProfilerViewer");
+ };
+
+})();
+YAHOO.register("profilerviewer", YAHOO.widget.ProfilerViewer, {version: "2.7.0", build: "1799"});