You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-panel/share/static/js/jquery.dump.js

312 lines
14 KiB

/**
* 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
*
* <example>
* // dump navigator properties
* $.dump(window.navigator, 1, "window.navigator");
*
* // this may be used to disable all following function calls
* $.dump("off");
* </example>
*
* @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
*
* <example>
* $("#element").dump(1);
* // same as
* $.dump($("#element"), 1, "(#element)");
* </example>
*
* 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("<html><head><title>"+ sHistoryPath +" @ "+ DateToHMSMString(new Date()) +"</title>"
+"<meta http-equiv='Content-Type' content='text/html;charset=utf-8'/>"
+"<style type='text/css'>"
+" body{background-color:#fff;color:#000;font-size:12px;margin-top:24px;}"
+" #toolbar{position:fixed;top:0px;right:0px;z-index:9999;}"
+" span.p.a:hover,span.p.a:hover+span {background-color: #B5F5FF;}"
+" a{text-decoration:none;}"
+" a:hover{text-decoration:underline;}"
+" .h{display:none;}" // hidden element
+" .a{cursor:pointer;}" // link like element
+" .s{color:#740;}" // string
+" .k{color:#427;font-weight:bold;font-style:italic;}" // key-word
+" .c{color:#666;font-style:italic;}" // comment
+" .u{color:#259;}" // reserved value
+" .p{color:#155;font-weight:bold;}" // punctuation
+" .d{color:#800;font-weight:bold;}" // diggit
+" .e{color:#900;font-style:italic;background-color:#FAA;}" // exception
+" .t{color:#080;font-weight:bold;}" // true boolean value
+" .f{color:#800;font-weight:bold;}" // false boolean value
+" .arg{color:#000;font-weight:normal;}" // number of function arguments
+"</style></head><body>"
+"<div id='toolbar'>"
+" <input type='button' id='btnClose' value='Close' onclick='window.close();'/>"
+" <input type='button' value='Collapse All' onclick='ToggleCollapse(true);'/>"
+" <input type='button' value='Expand All' onclick='ToggleCollapse(false);'/>"
+"</div>"
+"<code><pre>"+ sHistoryPath +" <span class='p'>=</span> "
+ __jqdump__next(obj, " ", iDepth, "__jqdump__.data", sHistoryPath)
+"<span class='p'>;</span></pre></code>"
// provide data and code to the parent window
+"<sc"+"ript type='text/javascript'><!-"+"-"
+"\n __jqdump__ = (window.opener? window.opener.__jqdump__ : window.__jqdump__);"
+"\n if (!__jqdump__) { __jqdump__ = {data: null, dump: function (o) { if (JSON) { alert(JSON.stringify(o)); } } }; }"
+"\n "
// focus the close button to allow fast window close by pressing button [space]
+"\n window.onload = function () {"
+"\n window.focus();"
+"\n document.getElementById('btnClose').focus();"
+"\n }"
+"\n "
+"\n function ToggleCollapse (bCollapse) {"
+"\n var span = document.getElementsByTagName('span');"
+"\n for (var i = 0; i < span.length; i++) {"
+"\n if (span[i].getAttribute('title') != 'collapse/expand' || (i == 1 && bCollapse)) { continue; }"
+"\n span[i].nextSibling.className = bCollapse? 'h' : '';"
+"\n }"
+"\n }"
+"\n-"+"-></sc"+"ript></body></html>"
);
// 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 "<span class='u'>null</span>"; }
switch (objType) {
case "object": break;
case "undefined": return "<span class='u'>undefined</span>";
case "string": return obj.length? "\"<span class='s'>"+ htmlEscape(obj) +"</span>\"" : "\"\"";
case "number": return "<span class='d'>"+ obj.toString() +"</span>";
case "boolean": return "<span class='"+ (obj? "t" : "f") +"'>"+ obj.toString() +"</span>";
// allow dumping of function return value (simple function call without arguments)
case "function": return format(
"<a href='javascript:;' class='k'"
+" onclick='__jqdump__.dump((function(){try{return {0}();}catch(xcp){return {EXCEPTION_WRAPPER:xcp.toString()};}})()"
+","+ defaultDepth +",this.title);' title='{1}()'>func"+"tion({2})</a>"
, sContextPath
, sHistoryPath
, (obj.length? "<span class='arg'>"+ obj.length +"</span>" : "")
);
default: return "<span class='e'>/* Unknown object type: {"+ objType +"}*/</span>";
}
if (obj instanceof Date) { return "new Date(\""+ obj.toUTCString() +"\")"; }
if (iDepth == 0) { // stop here and allow deeper dumping by a click
return format("<a href='javascript:;' class='p' title='show more'"
+" onclick='__jqdump__.dump((function(){try{return {0};}catch(xcp){return {EXCEPTION_WRAPPER:xcp.toString()};}})(),"
+ defaultDepth +",\"{1}\");'"
+">{ ... }</a>"
, sContextPath
, sHistoryPath.replace(/"/g, "\\\""));
}
var bIsArray = (obj instanceof Array)
, sNewContextPath, sNewHistoryPath
, obja = [], prop;
var rv = [
"<span class='p a' title='collapse/expand'"
+" onclick='this.nextSibling.className=(this.nextSibling.className==\"h\"?\"\":\"h\")'"
+">&#160;"+ (bIsArray? "[" : "{") +"&#160;</span><span>\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}<span class='a c' onclick='alert(this.title);' title='{1}'>/*{2}*/</span> "
, 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}<span class='s a' onclick='alert(this.title);' title='{1}'>\"{2}\"</span> <span class='p'>:</span> "
, 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}<span class='a' onclick='alert(this.title);' title='{1}'>{2}</span> <span class='p'>:</span> "
, sIndent, sNewHistoryPath, htmlEscape(prop)
));
}
}
rv.push(__jqdump__next(obj[prop], sIndent +" ", iDepth - 1, sNewContextPath, sNewHistoryPath));
} catch (xcp) {
rv.push(format("<span class='e'>/*{0} - {1}*/</span>", xcp.name, xcp.message));
}
rv.push((c < (obja.length-1)? "<span class='p'>,</span>\n" : "\n"));
}
} catch (xcp) {
rv.push(format("<span class='e'>/*{0} - {1}*/</span>", xcp.name, xcp.message));
}
rv.push(format("{0}</span><span class='p'>{1}</span>", 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, "&amp;").replace(/\</g, "&lt;").replace(/\>/g, "&gt;") : 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);