debug.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /**
  2. * debugData
  3. *
  4. * Pass me a data structure {} and I'll output all the key/value pairs - recursively
  5. *
  6. * @example var HTML = debugData( oElem.style, "Element.style", { keys: "top,left,width,height", recurse: true, sort: true, display: true, returnHTML: true });
  7. *
  8. * @param Object o_Data A JSON-style data structure
  9. * @param String s_Title Title for dialog (optional)
  10. * @param Hash options Pass additional options in a hash
  11. */
  12. function debugData (o_Data, s_Title, options) {
  13. options = options || {};
  14. var
  15. str=(s_Title||s_Title==='' ? s_Title : 'DATA')
  16. , dType=$.type(o_Data)
  17. // maintain backward compatibility with OLD 'recurseData' param
  18. , recurse=($.type(options)==='boolean' ? options : options.recurse !==false)
  19. , keys=(options.keys?','+options.keys+',':false)
  20. , display=options.display !==false
  21. , html=options.returnHTML !==false
  22. , sort=!!options.sort
  23. , prefix=options.indent ? ' ' : ''
  24. , D=[], i=0 // Array to hold data, i=counter
  25. , hasSubKeys = false
  26. , k, t, skip, x, type // loop vars
  27. ;
  28. if (dType!=='object' && dType!=='array') {
  29. if (options.display) alert( (s_Title || 'debugData') +': '+ o_Data );
  30. return o_Data;
  31. }
  32. if (dType==='object' && $.isPlainObject(o_Data))
  33. dType='hash';
  34. if (o_Data.jquery) {
  35. str=s_Title+'jQuery Collection ('+ o_Data.length +')\n context="'+ o_Data.context +'"';
  36. }
  37. else if (o_Data.nodeName) {
  38. str=s_Title+o_Data.tagName;
  39. var id = o_Data.id, cls=o_Data.className, src=o_Data.src, hrf=o_Data.href;
  40. if (id) str+='\n id="'+ id+'"';
  41. if (cls) str+='\n class="'+ cls+'"';
  42. if (src) str+='\n src="'+ src+'"';
  43. if (hrf) str+='\n href="'+ hrf+'"';
  44. }
  45. else {
  46. parse(o_Data,prefix,dType); // recursive parsing
  47. if (sort && !hasSubKeys) D.sort(); // sort by keyName - but NOT if has subKeys!
  48. if (str) str += '\n***'+ '****************************'.substr(0,str.length) +'\n';
  49. str += D.join('\n'); // add line-breaks
  50. }
  51. if (display) alert(str); // display data
  52. if (html) str=str.replace(/\n/g, ' <br>').replace(/ /g, ' &nbsp;'); // format as HTML
  53. return str;
  54. function parse ( data, prefix, parentType ) {
  55. var first = true;
  56. try {
  57. $.each( data, function (key, val) {
  58. skip = (keys && keys.indexOf(','+key+',') === -1);
  59. type = $.type(val);
  60. if (type==='object' && $.isPlainObject(val))
  61. type = 'hash';
  62. k = prefix + (first ? ' ' : ', ');
  63. first = false;
  64. if (parentType!=='array') // no key-names for array items
  65. k += key+': '; // NOT an array
  66. if (type==="date" || type==="regexp") {
  67. val = val.toString();
  68. type = "string";
  69. }
  70. if (type==="string") { // STRING
  71. if (!skip) D[i++] = k +'"'+ val +'"';
  72. }
  73. // NULL, UNDEFINED, NUMBER or BOOLEAN
  74. else if (/^(null|undefined|number|boolean)/.test(type)) {
  75. if (!skip) D[i++] = k + val;
  76. }
  77. else if (type==="function") { // FUNCTION
  78. if (!skip) D[i++] = k +'function()';
  79. }
  80. else if (type==="array") { // ARRAY
  81. if (!skip) {
  82. D[i++] = k +'[';
  83. parse( val, prefix+' ',type); // RECURSE
  84. D[i++] = prefix +' ]';
  85. }
  86. }
  87. else if (val.jquery) { // JQUERY OBJECT
  88. if (!skip) D[i++] = k +'jQuery ('+ val.length +') context="'+ val.context +'"';
  89. }
  90. else if (val.nodeName) { // DOM ELEMENT
  91. var id = val.id, cls=val.className, src=val.src, hrf=val.href;
  92. if (skip) D[i++] = k +' '+
  93. id ? 'id="'+ id+'"' :
  94. src ? 'src="'+ src+'"' :
  95. hrf ? 'href="'+ hrf+'"' :
  96. cls ? 'class="'+cls+'"' :
  97. '';
  98. }
  99. else if (type==="hash") { // JSON
  100. if (!recurse || $.isEmptyObject(val)) { // show an empty hash
  101. if (!skip) D[i++] = k +'{ }';
  102. }
  103. else { // recurse into JSON hash - indent output
  104. D[i++] = k +'{';
  105. parse( val, prefix+' ',type); // RECURSE
  106. D[i++] = prefix +' }';
  107. }
  108. }
  109. else { // OBJECT
  110. if (!skip) D[i++] = k +'OBJECT'; // NOT a hash
  111. }
  112. });
  113. } catch (e) {}
  114. }
  115. };
  116. function debugStackTrace (s_Title, options) {
  117. var
  118. callstack = []
  119. , isCallstackPopulated = false
  120. ;
  121. try {
  122. i.dont.exist += 0; // doesn't exist- that's the point
  123. } catch(e) {
  124. if (e.stack) { // Firefox
  125. var lines = e.stack.split('\n');
  126. for (var i=0, len=lines.length; i<len; i++) {
  127. if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
  128. callstack.push(lines[i]);
  129. }
  130. }
  131. //Remove call to printStackTrace()
  132. callstack.shift();
  133. isCallstackPopulated = true;
  134. }
  135. else if (window.opera && e.message) { // Opera
  136. var lines = e.message.split('\n');
  137. for (var i=0, len=lines.length; i<len; i++) {
  138. if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
  139. var entry = lines[i];
  140. //Append next line also since it has the file info
  141. if (lines[i+1]) {
  142. entry += ' at ' + lines[i+1];
  143. i++;
  144. }
  145. callstack.push(entry);
  146. }
  147. }
  148. //Remove call to printStackTrace()
  149. callstack.shift();
  150. isCallstackPopulated = true;
  151. }
  152. }
  153. if (!isCallstackPopulated) { // IE and Safari
  154. var currentFunction = arguments.callee.caller;
  155. while (currentFunction) {
  156. var fn = currentFunction.toString();
  157. var fname = fn.substring(fn.indexOf('function') + 8, fn.indexOf('')) || 'anonymous';
  158. callstack.push(fname);
  159. currentFunction = currentFunction.caller;
  160. }
  161. }
  162. debugData( callstack, s_Title, options );
  163. };
  164. if (!window.console) window.console = { log: debugData };
  165. if (!window.console.trace)
  166. window.console.trace = function (s_Title) {
  167. window.console.log( debugStackTrace(s_Title, { display: false, returnHTML: false, sort: false }) );
  168. };
  169. // add method to output 'hash data' inside an string
  170. window.console.data = function (data, title) {
  171. var w = { array: ['[',']'], object: ['{','}'], string: ['"','"'], number: ['',''], function: ['','()'] }
  172. , x = $.type( data )
  173. , obj = x.match(/(object|array)/)
  174. , delim = !obj ? ['',''] : x === 'object' ? ['{\n','\n}'] : ['[\n','\n]']
  175. , opts = { display: false, returnHTML: false, sort: false }
  176. , debug = debugData( data, '', opts)
  177. ;
  178. console.log(
  179. (title ? title +' = ' : '')
  180. + delim[0]
  181. + ($.type(debug) === 'string' ? debug.replace(/ /g, '\t') : debug)
  182. + delim[1]
  183. );
  184. };
  185. /**
  186. * timer
  187. *
  188. * Utility for debug timing of events
  189. * Can track multiple timers and returns either a total time or interval from last event
  190. * @param String timerName Name of the timer - defaults to debugTimer
  191. * @param String action Keyword for action or return-value...
  192. * action: 'reset' = reset; 'clear' = delete; 'total' = ms since init; 'step' or '' = ms since last event
  193. */
  194. /**
  195. * timer
  196. *
  197. * Utility method for timing performance
  198. * Can track multiple timers and returns either a total time or interval from last event
  199. *
  200. * returns time-data: {
  201. * start: Date Object
  202. * , last: Date Object
  203. * , step: 99 // time since 'last'
  204. * , total: 99 // time since 'start'
  205. * }
  206. *
  207. * USAGE SAMPLES
  208. * =============
  209. * timer('name'); // create/init timer
  210. * timer('name', 'reset'); // re-init timer
  211. * timer('name', 'clear'); // clear/remove timer
  212. * var i = timer('name'); // how long since last timer request?
  213. * var i = timer('name', 'total'); // how long since timer started?
  214. *
  215. * @param String timerName Name of the timer - defaults to debugTimer
  216. * @param String action Keyword for action or return-value...
  217. * @param Hash options Options to customize return data
  218. * action: 'reset' = reset; 'clear' = delete; 'total' = ms since init; 'step' or '' = ms since last event
  219. */
  220. function timer (timerName, action, options) {
  221. var
  222. name = timerName || 'debugTimer'
  223. , Timer = window[ name ]
  224. , defaults = {
  225. returnString: true
  226. , padNumbers: true
  227. , timePrefix: ''
  228. , timeSuffix: ''
  229. }
  230. ;
  231. // init the timer first time called
  232. if (!Timer || action == 'reset') { // init timer
  233. Timer = window[ name ] = {
  234. start: new Date()
  235. , last: new Date()
  236. , step: 0 // time since 'last'
  237. , total: 0 // time since 'start'
  238. , options: $.extend({}, defaults, options)
  239. };
  240. }
  241. else if (action == 'clear') { // remove timer
  242. window[ name ] = null;
  243. return null;
  244. }
  245. else { // update existing timer
  246. Timer.step = (new Date()) - Timer.last; // time since 'last'
  247. Timer.total = (new Date()) - Timer.start; // time since 'start'
  248. Timer.last = new Date();
  249. }
  250. var
  251. time = (action == 'total') ? Timer.total : Timer.step
  252. , o = Timer.options // alias
  253. ;
  254. if (o.returnString) {
  255. time += ""; // convert integer to string
  256. // pad time to 4 chars with underscores
  257. if (o.padNumbers)
  258. switch (time.length) {
  259. case 1: time = "&ensp;&ensp;&ensp;"+ time; break;
  260. case 2: time = "&ensp;&ensp;"+ time; break;
  261. case 3: time = "&ensp;"+ time; break;
  262. }
  263. // add prefix and suffix
  264. if (o.timePrefix || o.timeSuffix)
  265. time = o.timePrefix + time + o.timeSuffix;
  266. }
  267. return time;
  268. };
  269. /**
  270. * showOptions
  271. *
  272. * Pass a layout-options object, and the pane/key you want to display
  273. */
  274. function showOptions (Layout, key, debugOpts) {
  275. var data = Layout.options;
  276. $.each(key.split("."), function() {
  277. data = data[this]; // recurse through multiple key-levels
  278. });
  279. debugData( data, 'options.'+key, debugOpts );
  280. };
  281. /**
  282. * showState
  283. *
  284. * Pass a layout-options object, and the pane/key you want to display
  285. */
  286. function showState (Layout, key, debugOpts) {
  287. var data = Layout.state;
  288. $.each(key.split("."), function() {
  289. data = data[this]; // recurse through multiple key-levels
  290. });
  291. debugData( data, 'state.'+key, debugOpts );
  292. };
  293. function debugWindow ( content, options ) {
  294. var defaults = {
  295. css: {
  296. position: 'fixed'
  297. , top: 0
  298. }
  299. };
  300. $.extend( true, (options || {}), defaults );
  301. var $W = $('<div></div>')
  302. .html( content.replace(/\n/g, '<br>').replace(/ /g, ' &nbsp;') ) // format as HTML
  303. .css( options.css )
  304. ;
  305. };