/** * jQuery dump plugin. Inspect object properties in a popup window. * * Copyright (c) 2012 Block Alexander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ (function ($) { var __jqdump__dump_OFF = false; // global OFF switch var position = { left: 100, top: 120 }; // popup window position var defaultDepth = 2; // default dump depth /** * Dump object content in popup window with prettyprint and subnavigation * * * // dump navigator properties * $.dump(window.navigator, 1, "window.navigator"); * * // this may be used to disable all following function calls * $.dump("off"); * * * @param(obj): object to dump, or "off" string to disable all dump calls * @param(iDepth): number, optional dumping depth, default 2 * @param(sHistoryPath): string, optional global properties path relative to the initial dump object value, used for display only. * @returns: null */ $.dump = function (obj, iDepth, sHistoryPath) { if (typeof(obj) == "string" && /^off$/i.test(obj)) { __jqdump__dump_OFF = true; } return __jqdump__dump(obj, iDepth, sHistoryPath); }; /** * Dump object content in popup window with prettyprint and subnavigation * * * $("#element").dump(1); * // same as * $.dump($("#element"), 1, "(#element)"); * * * Call type: window.dump/$.dump( ... ) * @param(iDepth): number, optional dumping depth, default 2 * @returns: null */ $.fn.dump = function (iDepth) { return __jqdump__dump(this, iDepth, this.selector? "("+ this.selector +")" : ""); }; /** * Dump object content in popup window with prettyprint and subnavigation * * @param(obj): object to dump * @param(iDepth): number, optional dumping depth, default 2 * @param(sHistoryPath): string, optional global properties path relative to the initial dump object value, used for display only. * @returns: null */ function __jqdump__dump (obj, iDepth, sHistoryPath) { if (__jqdump__dump_OFF) { return null; } // store current object value to allow continious/recursive dump browsing via window.opener.__jqdump__ __jqdump__ = {data: obj, dump: __jqdump__dump }; // provide defaults as needed iDepth = (typeof(iDepth) == "number" && iDepth > 0? parseInt(iDepth, 10) : defaultDepth); sHistoryPath = (typeof(sHistoryPath) == "string" && sHistoryPath.length > 0? sHistoryPath : "OBJECT"); // adjust new window position position = { top: (position.top - 30) % 120, left: (position.left - 10) % 100 }; // try to popup blank page var dumpWindow = window.open("about:blank", "_blank" , "width=600,height=800,menubar=0,left="+ position.left +",top="+ position.top +",location=0,toolbar=0,status=0,scrollbars=1,resizable=1", true); // popup blocked? if (!dumpWindow || dumpWindow.closed == true) { if (confirm("Dump Window couldn't showup cause of popup blocker.\nContinue using current window?")) { dumpWindow = window; } else { return null; } } // fill the page with dump content dumpWindow.document.write(""+ sHistoryPath +" @ "+ DateToHMSMString(new Date()) +"" +"" +"" +"
" +" " +" " +" " +"
" +"
"+ sHistoryPath +" = "
      + __jqdump__next(obj, "  ", iDepth, "__jqdump__.data", sHistoryPath)
      +";
" // provide data and code to the parent window +"" ); // finalize writings dumpWindow.document.close(); // ensure new window has data and code to continue further dumps dumpWindow.__jqdump__ = { data: __jqdump__.data , dump: __jqdump__.dump //*dbg*/, history: sHistoryPath }; /** * @param(obj): object to dump * @param(sIndent): string, used for object properties alignment indentation * @param(iDepth): number, dumping depth, defaults to 2 * @param(sContextPath): string, evaluable object properties path relative to the current obj value, used onclick event * @param(sHistoryPath): string, global properties path relative to the initial dump obj value, used for display only */ function __jqdump__next (obj, sIndent, iDepth, sContextPath, sHistoryPath) { var objType = typeof(obj); if (null == obj && objType != "undefined") { return "null"; } switch (objType) { case "object": break; case "undefined": return "undefined"; case "string": return obj.length? "\""+ htmlEscape(obj) +"\"" : "\"\""; case "number": return ""+ obj.toString() +""; case "boolean": return ""+ obj.toString() +""; // allow dumping of function return value (simple function call without arguments) case "function": return format( "func"+"tion({2})" , sContextPath , sHistoryPath , (obj.length? ""+ obj.length +"" : "") ); default: return "/* Unknown object type: {"+ objType +"}*/"; } if (obj instanceof Date) { return "new Date(\""+ obj.toUTCString() +"\")"; } if (iDepth == 0) { // stop here and allow deeper dumping by a click return format("{ ... }" , sContextPath , sHistoryPath.replace(/"/g, "\\\"")); } var bIsArray = (obj instanceof Array) , sNewContextPath, sNewHistoryPath , obja = [], prop; var rv = [ " "+ (bIsArray? "[" : "{") +" \n" ]; try { // making sorted array of object property names if (bIsArray) { for (var i = 0; i < obj.length; i++) { obja.push(i); } } else { for (prop in obj) { obja.push(prop); } obja.sort(function (a, b) { return (isNaN(a)? (a.toLowerCase() >= b.toLowerCase()? 1 : -1) : (Number(a) >= Number(b))? 1 : -1); }); } // quering object with names via sorted array for (var c = 0, length = obja.length; c < length; c++) { try { prop = obja[c]; // skip self properties if (/__jqdump__/.test(prop)) { continue; } if (bIsArray) { // array index: sNewContextPath = format("{0}[\"{1}\"]", sContextPath, prop); sNewHistoryPath = format("{0}[\"{1}\"]", sHistoryPath, prop); rv.push(format( "{0}/*{2}*/ " , sIndent, sNewHistoryPath, prop )); } else {// object property: if (/(\W)|(^\d)/.test(prop)) {//- as string sNewContextPath = format("{0}[\"{1}\"]", sContextPath, prop); sNewHistoryPath = format("{0}[\"{1}\"]", sHistoryPath, htmlEscape(prop)); rv.push(format( "{0}\"{2}\" : " , sIndent, sNewHistoryPath, htmlEscape(prop) )); } else {//- as conventional variable name sNewContextPath = format("{0}.{1}", sContextPath, prop); sNewHistoryPath = format("{0}.{1}", sHistoryPath, htmlEscape(prop)); rv.push(format( "{0}{2} : " , sIndent, sNewHistoryPath, htmlEscape(prop) )); } } rv.push(__jqdump__next(obj[prop], sIndent +" ", iDepth - 1, sNewContextPath, sNewHistoryPath)); } catch (xcp) { rv.push(format("/*{0} - {1}*/", xcp.name, xcp.message)); } rv.push((c < (obja.length-1)? ",\n" : "\n")); } } catch (xcp) { rv.push(format("/*{0} - {1}*/", xcp.name, xcp.message)); } rv.push(format("{0}{1}", sIndent.replace(" ", ""), (bIsArray? "]" : "}"))); return rv.join(""); }; /** * Escape native string characters before writing it as html text */ function htmlEscape (str) { // trying to speedup the process by checking the length return str.length? str.replace(/&/g, "&").replace(/\/g, ">") : str; }; /** * Replaces each format item "{n}" in `this` string with the string equivalent of a corresponding * parameters object's value. For example "{0}" reffers to the second argument, "{1}" to the third. * * @note: format identifier "{i}" may be repeated multiple times in any order as long as it reffers * to corresponding position of the argument * @param(0): first parameter is a format-string, containing meta information of insertion positions * @param(...): any type, other arguments reffered by the format-string will be evaluated to string * @return: string, formating result */ function format (/* ... */) { var match = null, rv = arguments[0]; for (var c = 1, length = arguments.length; c < length; c++) { match = new RegExp("\\{"+ (c - 1) +"\\}", "g"); if (match.test(rv)) { rv = rv.replace(match, typeof(arguments[c]) == "undefined" ? "(undefined)" : arguments[c].toString()); } } match = null; return rv; }; /** * Time to string in HH:MM:SS.mmm format * @param(d): date object */ function DateToHMSMString (d) { var iHours = d.getHours(), iMinutes = d.getMinutes(), iSeconds = d.getSeconds(), iMSeconds = d.getMilliseconds(); return (iHours < 10? "0" : "") + iHours +":"+ (iMinutes < 10? "0" : "") + iMinutes +":"+ (iSeconds < 10? "0" : "") + iSeconds +"."+ (iMSeconds < 100? "0" : "") + (iMSeconds < 10? "0" : "") + iMSeconds; } return null; }; })(jQuery);