Customized Stack Traces in Google Chrome Developer Tools? - javascript

I'm looking to customize the items that show up in the strack trace panel in the Scripts tab of Google Chrome's developers tools. Specifically, I want to filter out items in the stack trace and to add more descriptive names to some of the items on the stack trace without having to rename my objects and functions.
I found V8's Stack Trace API at http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi but overriding Error.prepareStackTrace doesn't seem to have any effect.

The description on that page is definitely a little hard to follow, here's how it's done:
Error.prepareStackTrace = function(error, stack) {
return stack;
};
var someObj = {
someMethod : function () {
crash();
}
}
function bar(barArg) { someObj.someMethod(); };
function foo(fooArg) { bar("barArgString"); };
function getTrace(e) {
var stack = e.stack;
var trace = "";
for (var i = 0; i < stack.length; i++) {
var frame = stack[i],
func = frame.getFunction();
trace += "\r" + frame.getThis() + "." + frame.getFunctionName();
}
return trace;
}
try {
foo("fooArgString");
} catch (e) {
alert("trace from catch(): " + getTrace(e));
}
This will show:
trace from catch():
[object Object].someObj.someMethod
[object Window].bar
[object Window].foo
[object Window].
The last frame is global scope (no function name).
Essentially your override of prepareStackTrace() causes error.stack to become whatever you return from prepareStackTrace(). The trick is that the second argument to prepareStackTrace() is an Array of CallSite objects - the objects that support getThis(), getFunctionName() etc.
The code above overrides prepareStackTrace() so that it returns the Array of CallSite objects ("stack" parameter above), so this means when you try..catch an Error, Error.stack is going to contain the Array of CallSite objects instead of the usual stack trace in String form. Another approach would be to process the CallSite objects inside of your replacement prepareStackTrace() function and return your alternative stack trace as a String.
Note the CallSite objects are really finicky. Try to do frame.toString(), or just try to alert(frame) (implicitly this involves toString()) and it crashes and Chrome's developer tools show no error.

Here's the code that did the trick for me:
<head>
<script>
Error.prepareStackTrace = function()
{
return "MyStackObject";
}
try {
throw new Error();
} catch (e) {
console.log(e.stack);
}
</script>
</head>

The documentation has moved here:
https://github.com/v8/v8/wiki/Stack-Trace-API
Just put this at the beginning of your javascript code, it formats a nice stack trace:
Error.prepareStackTrace = function(error, stack) {
var trace = '';
var max_width = 0;
for (var i = 0; i < stack.length; i++){
var frame = stack[i];
var typeLength = 0;
typeLength = (frame.getTypeName() !== null && frame.getTypeName() !== '[object global]') ? frame.getTypeName().length : 0;
typeLength = typeLength.length > 50 ? 50 : typeLength;
functionlength = frame.getFunctionName() !== null ? frame.getFunctionName().length : '<anonymous>'.length;
functionlength = functionlength > 50 ? 50 : functionlength;
if (typeLength + functionlength > max_width)
max_width = typeLength + functionlength;
}
for (var i = 0; i < stack.length; i++) {
var frame = stack[i];
var filepath = frame.getFileName();
var typeName = '';
if (frame.getTypeName() !== null && frame.getTypeName() !== '[object global]')
typeName = frame.getTypeName().substring(0, 50) + '.';
var functionName = '<anonymous>';
if (frame.getFunctionName() !== null)
functionName = frame.getFunctionName().substring(0, 50);
var space = '';
var width = max_width - (typeName.length + functionName.length) + 2;
space = Array(width).join(' ');
var line = ' at ' + typeName + functionName + space + filepath +
' (' + frame.getLineNumber() +
':' + frame.getColumnNumber() + ')\n';
trace += line;
}
return trace;
};
Here's an example to the test the code:
function A() { B(); }
function B() { C(); }
function C() { throw new Error('asd'); }
try {
A();
} catch (e) { print(e + '\n' + e.stack); }

Related

Creating another instance of the javascript console

Is it possible to create another instance of the javascript console and use it at one's own discretion, such as placing content filed to console.log() in a div on your page)?
You can override it, but that's not a good practice. Like this:
console.log = function() {
for(var i = 0; i < arguments.length; i++)
document.getElementById("logs").textContent += JSON.stringify(arguments[i], null, 4) + '\n\n';
}
var object = {a: 45, b: "hh"};
console.log(object);
console.log("Hello world!");
console.log(45 + 5);
<pre id="logs"></pre>
Note 1: console.log is a great tool for debugging, it should not be overriden. I don't recommend you override it but instead, I'll recommend you make a function (called for example myConsoleLog) that does what you need.
Note 2: The example I provided is just a basic example to give you insight, you'll need a lot of other stuff (the folding of objects and arrays, the logging of functions for example...).
See this answer, which you could implement by grabbing the value of the stack and writing it to a div on your page.
<div id="log"></div>
var logBackup = console.log;
var logMessages = [];
console.log = function() {
logMessages.push.apply(logMessages, arguments);
document.getElementById('log').innerHTML = "";
for(var i = 0; i < logMessages.length; i++){
var pre = document.createElement("pre");
pre.innerHTML = logMessages[i];
document.getElementById('log').appendChild(pre);
}
logBackup.apply(console, arguments);
};
console.log("My name is joe.")
console.log("My name is tom")
https://jsfiddle.net/yak613/7g6kchox/
You can override console.log. Something like this:
console.log = function(objToLog) {
var myLog = document.getElementById('myLog');
var str = '';
if (typeof objToLog === 'string') {
str = objToLog;
} else if (typeof objToLog === 'object') { //includes array print
str = JSON.stringify(objToLog);
}
myLog.innerText = myLog.innerText + '\n> ' + str;
}
console.log('log this line');
console.log('log this other line');
console.log([1,2,3]);
console.log({ key: 'value' });
Will print to a div:
> log this line
> log this other line
> [1,2,3]
> {"key":"value"}
Fiddle: https://jsfiddle.net/mrlew/a08qL18d/

Profiling Javascript in PyV8

I have a JS codebase running within PyV8. Now I'd like to improve its performance, but there don't seem to be any hooks to enable the V8 profiler. In an older trunk version of PyV8 there are some options referencing the profiler but I don't find any documentation on it. Do you have any idea on how to profile in PyV8 without me having to rewrite the Python-to-JS wrapper?
Do you know of any JS-only-framework that uses monkey patching in order to generate a timing profile? It's not a big deal if there is some overhead involved - better than not having a profile at all.
At the end I've found the hint I needed in the book 'Pro Javascript Design Patterns': Use a closure together with func.apply to apply instrumentation on functions. Unfortunately, the JS way of decorating functions is not quite as clean as Python's - but hey, it works and I get the information I need to drill down into the code's performance characteristics.
profile.js
function mod_profiler() {
var profile_by_function_name = {};
var total_time = 0;
var time_counting_for_function = null;
function get_function_name(func) {
var result = func.toString();
result = result.substr('function '.length);
result = result.substr(0, result.indexOf('('));
return result;
}
function update_profile(function_name, elapsed_time) {
var profile = profile_by_function_name[function_name];
if (profile === undefined) {
profile = {calls:0, elapsed_time:0};
profile_by_function_name[function_name] = profile;
}
profile.calls += 1;
profile.elapsed_time += elapsed_time;
if (time_counting_for_function === function_name) {
total_time += elapsed_time;
}
}
function profile(func) {
function profiled() {
var function_name = get_function_name(func);
if (time_counting_for_function === null) {
time_counting_for_function = function_name;
}
var start_time = new Date().getTime()
var result = func.apply(undefined, arguments);
var elapsed_time = new Date().getTime() - start_time;
update_profile(function_name, elapsed_time);
if (time_counting_for_function === function_name) {
time_counting_for_function = null;
}
return result;
}
return profiled;
}
function get_formatted_result() {
function get_whitespace(length) {
var result = "";
for (var i = 0; i < length; i++) {
result += " ";
}
return result;
}
var function_names = Object.keys(profile_by_function_name);
var function_names_sorted_by_elapsed_time = function_names.sort(function (a,b) {
var elapsed_a = profile_by_function_name[a].elapsed_time;
var elapsed_b = profile_by_function_name[b].elapsed_time;
if (elapsed_a < elapsed_b) {
return 1;
}
if (elapsed_a > elapsed_b) {
return -1;
}
return 0;
});
var max_name_length = 0;
for (var i = 0; i < function_names_sorted_by_elapsed_time.length; i++) {
if (function_names_sorted_by_elapsed_time[i].length > max_name_length) {
max_name_length = function_names_sorted_by_elapsed_time[i].length;
}
}
var result = "\n" + get_whitespace(max_name_length) + " " + "#calls\telapsed\t% of total\n";
for (var i = 0; i < function_names_sorted_by_elapsed_time.length; i++) {
if (total_time === 0) {
break;
}
var function_name = function_names_sorted_by_elapsed_time[i];
var percentage_elapsed = profile_by_function_name[function_name].elapsed_time * 100 / total_time;
if (percentage_elapsed < 0.3) {
break;
}
result += function_name + ":" + get_whitespace(max_name_length - 1 - function_name.length) + profile_by_function_name[function_name].calls + "\t" + profile_by_function_name[function_name].elapsed_time + "\t" + percentage_elapsed.toFixed(2) + "\n";
}
result += "==========\n";
result += "total time accounted for [ms]: " + total_time;
return result;
}
return {
profile: profile,
get_formatted_result: get_formatted_result
}
}
my_module_1.js
function mod_1(profiler_param) {
var profiler = profiler_param;
function my_private_func() {
return "hello world2";
}
if (typeof(profiler) === 'object' && profiler !== null) {
render_user_menu = profiler.profile(render_user_menu);
} //private functions need the instrumentation to be added manually or else they're not included in the profiling.
function my_public_func1() {
return "hello world";
}
function my_public_func2(input1, input2) {
return my_private_func() + input1 + input2;
}
//public functions get the instrumentations automatically as long as they're called from outside the module
var public_function_by_names = {
my_public_func1: my_public_func1
my_public_func2: my_public_func2
}
var result = {};
var public_function_names = Object.keys(public_function_by_names);
for (var i = 0; i < public_function_names.length; i++) {
var func = public_function_by_names[public_function_names[i]];
if (typeof(profiler) === 'object' && profiler !== null) {
result[public_function_names[i]] = profiler.profile(func);
}
else {
result[public_function_names[i]] = func;
}
}
return result;
}
PyV8 side
with X4GEJSContext(extensions=['profile', 'my_module_1']) as ctx:
if self.enable_profiling == True:
ctx.eval("var profiler = mod_profiler();")
ctx.eval("var mod1 = mod_1(profiler);")
#note: you can pass profiler to as many modules as you want and they get instrumented together.
logging.info(ctx.eval("mod1.my_public_func_1() + mod1.my_public_func_2('a', 3);"))
logging.info(ctx.eval("profiler.get_formatted_result();"))
else:
ctx.eval("var mod1 = mod_1();") #it still works without the profiler
Output
"hello worldhelloworld2a3"
#calls elapsed % of total
my_public_func1: 1 31 50.00
my_public_func2: 1 31 50.00
my_private_func: 1 31 50.00
==========
total time accounted for [ms]: 62

Debugging Javascript in IE 8

I'm trying to debug an issue that I'm only having in IE8. It works fine in IE 9+, and chrome. I'm using Aspera to select a file, and am calling a custom function on a callback. the function is as follows;
function uploadPathsRecieved(pathsArray) {
var file_path_selector = '#file_path';
...
$(file_path_selector).text(''); // (*)
...
}
On the (*) line, I get an error that file_path_selector is undefined. This didn't make much sense to me, so after some playing around to get a feel for the problem, I wound up with the following code:
function uploadPathsRecieved(pathsArray) {
var x = 3;
var y = 4;
var z = x + y;
z += 2;
$('#file_path').text(''); // (*)
...
}
When I run the program with this code, I still get the error "file_path_selector is undefined" at the (*) line. I'm out of ideas on what the next steps I should take to try and hunt down this problem are.
My gut feeling tells me that there's something being cached, but if I move the (*) line around, the error follows it, and the script window reflects the changes that I make to it.
Here's the Aspera code that's calling my function:
function wrapCallbacks(callbacks) {
return wrapCallback(function() {
var args, i;
try {
args = Array.prototype.slice.call(arguments);
for ( i = 0; i < args.length; i++) {
if (isObjectAndNotNull(args[i]) && isDefined(args[i].error)) {
// error found
if (isDefined(callbacks.error)) {
callbacks.error.apply(null, args);
}
return;
}
}
// success
if (isDefined(callbacks.success)) {
callbacks.success.apply(null, args);
}
} catch (e) {
AW.utils.console.error(e.name + ": " + e.message);
AW.utils.console.trace();
}
});
}
And here's the entirety of my function, as it exists right now:
var uploadPathsRecieved = function uploadPathsRecieved(pathsArray) {
//var file_path_selector = '#file_path';
var x = 3;
var y = 4;
var z = x + y;
z += 2;
$('#file_path').text('');
var button_selector = '#select_aspera_file';
var textbox_selector = '.aspera_textbox';
/*if (uploadPathsRecieved.fileSelecting == 'cc_file') {
file_path_selector = '#cc_file_path';
button_selector = '#select_cc_file';
textbox_selector = '.cc_aspera_textbox';
} else if (uploadPathsRecieved.fileSelecting == 'preview_file') {
file_path_selector = '#preview_file_path';
button_selector = '#select_preview_file';
textbox_selector = '.preview_aspera_textbox';
}*/
App.AsperaUploadPaths = [];
if (pathsArray.length == 1) {
$(button_selector).text("Clear File");
App.AsperaUploadPaths = pathsArray;
var error_message = pathsArray[0];
$(button_selector).parent().children(textbox_selector).text(error_message).removeClass('error');
//$(file_path_selector).attr('value', pathsArray[0]);
}
else
{
var error_message = 'Please select a single file';
$(button_selector).parent().children(textbox_selector).text(error_message).addClass('error');
}
}
Solved it. file_path was an <input>, and IE 8 and below doesn't allow you to add text or html to inputs.
I fixed it by changing $(file_path_selector).text(''); to $(file_path_selector).attr('value', '');

Google Closure introducing errors

EDIT
The lesson, learned with the help of #Alex, is that you should never put function declarations in block scope. Not that I intended to do this, but if you slip up, it can cause big problems.
I have a script file that seems to be getting compressed via Google Closure incorrectly. When I run my app with the original code, all works fine. But when I try to compress it with Google Closure, some errors get introduced.
I am NOT using the advanced option; I'm using the basic, default mode
Obviously I can't expect anyone to debug the compressed file, but I'm hoping someone can look at the uncompressed code and let me know if I'm somehow doing something insanely stupid that would trick Closure.
Some notes on the minified code:
Closure is inlining BEFramework.prototype.hstreamLoad and BEFramework.prototype.hstreamEvalJson, and seems to be utterly removing the helper functions getDeleteValue, getValueToDisplay, getDisplayForLabel and likely others.
Uncompressed file is below.
This code can manually be compiled by closure here, which should reproduce the symptoms described above.
(function() {
var $ = jQuery;
// Load and display the messages ("healthstream") for a given module.
// This requires that the module's HTML have specific features, see
// dashboard.htm and contactsManager/details/default.htm for examples.
// This also requires that the `request` support `pageIndex` and `pageSize`,
// so we can handle paging.
//
// Args: `options` An options object with these keys:
// `channelId` The channel ID of the module (for transmitRequest)
// `translationId` Optional alternate ID for translation (if not given,
// `channelId` is used).
// `action` The action (for transmitRequest)
// - Must support `pageIndex` and `pageSize`
// `request` The request (for transmitRequest)
// - Must include `pageIndex` and `pageSize`
// `complete` Optional callback triggered when the load is complete.
// `showOptions` Optional callback if an options menu is supported
// by the calling module. Receives a raw event instance
// and the item on which the options were triggered:
// function showOptions(event, item)
// `context` Optional context (`this` value) for the call to
// `complete` and/or `showOptions`
BEFramework.prototype.hstreamLoad = hstreamLoad;
function hstreamLoad(options) {
var inst = this;
var channelId, translationId, action, request, complete, showOptions, context,
pageIndex, pageCount, pageSize, pageCount,
btnPrevious, btnNext,
dataShownFlags;
// Get our arguments (with defaults)
channelId = options.channelId;
translationId = options.translationId || options.channelId;
action = options.action;
request = $.extend({}, options.request); // Create a *copy*, because we modify it when doing paging
complete = options.complete;
if (typeof complete !== "function") {
complete = undefined;
}
showOptions = options.showOptions;
if (typeof showOptions !== "function") {
showOptions = undefined;
}
context = options.context; // (undefined will automatically become the global object)
// Grab the initial pageIndex and pageSize
pageIndex = request.pageIndex || 1;
pageSize = request.pageSize || 100;
// Disable the button and show "searching" label
$('#healthStreamSearchButton')
.button("disable")
.button("option", "label", BETranslate(translationId, 'HealthStreamSearching'));
// Hook up the buttons; be a bit paranoid that they've been hooked before and clear previous handlers
btnPrevious = $('#healthStreamPagePrevious');
btnNext = $('#healthStreamPageNext');
btnPrevious.hide().unbind("click.paging").bind("click.paging", goToPreviousPage);
btnNext.hide().unbind("click.paging").bind("click.paging", goToNextPage);
// Do it
doLoad();
// === Support functions
// Trigger a load request
function doLoad() {
request.pageIndex = pageIndex;
request.pageSize = pageSize;
inst._transport.transmitRequest(channelId, action, request, hstreamLoaded);
}
// Hndle the load response
function hstreamLoaded(objResponse) {
var healthStream = objResponse.items;
var total = objResponse.total;
var tbody = $('#healthStreamList');
// Need to make this update optional
$('#pageHeaderName').html(BETranslate(translationId, 'HeaderActivity') + ' (' + String(total) + ')');
$('#healthStreamSearchButton')
.button("enable")
.button("option", "label", BETranslate(translationId, 'HealthStreamSearch'));
tbody.empty();
btnPrevious.hide();
btnNext.hide();
if (healthStream.length > 0) {
pageCount = Math.ceil(total / pageSize);
if (pageCount > 1) {
if (pageIndex > 1) {
btnPrevious.show();
}
if (pageIndex < pageCount) {
btnNext.show();
}
}
var item;
var tr;
var tdMain;
var daysHash = {};
var creationDate;
var key;
var today = new Date();
var yesterday = new Date();
var msg;
yesterday.setDate(yesterday.getDate() - 1);
dataShownFlags = {};
for (var x = 0; x < healthStream.length; x++) {
item = healthStream[x];
msg = inst.hstreamEvalJson(item);
if (msg.length > 0) {
creationDate = new Date(item.CreationDate);
key = [creationDate.getYear(), creationDate.getMonth(), creationDate.getDate()].join('-');
if (!daysHash[key]) {
if (isDateEqual(creationDate, today)) {
addRowHeader(tbody, BETranslate(inst._channelId, 'HSToday'));
}
else if (isDateEqual(creationDate, yesterday)) {
addRowHeader(tbody, BETranslate(inst._channelId, 'HSYesterday'));
}
else {
addRowHeader(tbody, creationDate.toString('MM/dd/yyyy'));
}
daysHash[key] = true;
}
tr = $(
"<tr>" +
"<td class='date' style='white-space:nowrap;'>" + new Date(item.CreationDate).toString('h:mm tt') + "</td>" +
"<td class='main'><span class='name'>" + msg + "</span>" +
"</tr>"
);
tbody.append(tr);
if (showOptions) {
tr.find("td.main").prepend($("<em rel='opt'> </em>").click(makeShowOptionsHandler(item)));
}
}
}
// If any of the templates created links with a `data` attribute, hook them up
$('#healthStreamList a[data]').click(showTitle).each(function (index) {
this.id = 'data' + index;
});
}
else {
tbody.html('<tr><td colspan="2">' + BETranslate(inst._channelId, 'HSNoActivity') + '</td></tr>');
}
// Trigger completion callback
if (complete) {
complete.call(context, objResponse);
}
}
function makeShowOptionsHandler(item) {
// Our event comes to us from jQuery, but we pass on the raw
// event to the callback
return function (event) {
showOptions.call(context, event.originalEvent || event, item);
};
}
function addRowHeader(listRef, name) {
listRef.append(
"<tr>" +
"<td colspan='2' class='divider'>" + name + "</td>" +
"</tr>"
);
}
function showTitle(event) {
$.stopEvent(event);
var link = this;
var $link = $(this);
var href = $link.attr("href"); // We want the attribute, not the property (the property is usually expanded)
var hrefTitle = $link.attr('hreftitle') || BETranslate(inst._channelId, 'HSMoreInfo');
var data = $link.attr('data') || "";
var linkId = link.id;
if (!dataShownFlags[linkId]) {
dataShownFlags[linkId] = true;
if (data) {
var div = $(
"<div class='data'>" +
"<span data-linkId='" + linkId + "' class='close'>x</span>" +
"<table><thead></thead></table>" +
"</div>"
);
$link.parent().append(div);
var thead = div.find("thead");
var arr = data.split('~');
var splitEntry;
for (var x = 0; x < arr.length; x++) {
splitEntry = arr[x].split('|');
if (splitEntry[0] === 'Changed length') {
splitEntry[1] = splitEntry[1].replace(/\d+/g, BEFramework.prettyTime);
}
if (splitEntry.length > 1 && splitEntry[1].length > 0) {
thead.append(
"<tr>" +
"<td class='hslabel'>" + splitEntry[0] + ":</td>" +
"<td>" + splitEntry[1] + "</td>" +
"</tr>"
);
}
}
div.find("span:first").click(hideTitle);
if (href && href !== "#") {
$("<a target='_blank'>" + hrefTitle + "</a>").attr("href", href).appendTo(div);
}
}
}
}
function hideTitle(event) {
var $this = $(this),
linkId = $this.attr("data-linkId");
delete dataShownFlags[linkId];
$this.parent().remove();
return false;
}
function goToPreviousPage(event) {
--pageIndex;
doLoad();
return false;
}
function goToNextPage(event) {
++pageIndex;
doLoad();
return false;
}
}
var ___x = false;
var __i = 0;
BEFramework.prototype.hstreamEvalJson = hstreamEvalJson;
function hstreamEvalJson(item) {
var inst = this;
if (item.Action === 'saveinsurance' && !___x && __i != 0){
var start = +new Date();
__i = 1;
}
var userId = inst._BEUser ? inst._BEUser.getId() : -1;
var json = eval('(' + item.JSON + ')');
var key = 'HS' + item.Module + '_' + item.Action;
var msg = BETranslate(inst._channelId, key);
var fromIsMe = item.CreatedByContactId == userId;
var toIsMe = item.ContactId == userId;
var fromString = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou') + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
var toString = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour') + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
var fromString2 = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
var toString2 = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
var subFormat, subProps;
var configObject = (BEFramework.healthStreamConfig[item.Module] && BEFramework.healthStreamConfig[item.Module][item.Action]) || {};
var standardCase = configObject.standardCase;
var suppress = configObject.suppress || [];
var propertiesInOrder = configObject.displayOrder || [];
if (msg.indexOf('not found in module') != -1) {
try {
switch (item.Module) {
case 'contacts':
if (item.Action == 'setpermission' || item.Action == 'deleterelationship' || item.Action == 'addinvite') {
msg = BETranslate(inst._channelId, key + json.type.toString());
}
break;
case 'tasks':
if (item.Action == 'savetask') {
msg = BETranslate(inst._channelId, key + json.type.toString());
}
break;
default:
msg = '';
}
} catch (ex) {
msg = '';
}
}
for (var prop in json) {
if (typeof (json[prop]) == 'object') {
if (prop === 'changes' || prop === 'deleted'){
subProps = json[prop];
for (var propName in subProps) {
if (indexInArrayCI(propName, propertiesInOrder) === -1 && indexInArrayCI(propName, suppress) === -1){
propertiesInOrder.push(propName);
}
}
}
if (prop == 'changes') {
var changes = '';
var changeFrom = BETranslate(inst._channelId, 'HSChangedFrom');
var changeTo = BETranslate(inst._channelId, 'HSChangedTo');
for (var i = 0; i < propertiesInOrder.length; i++) {
var subprop = propertiesInOrder[i];
if (getObjectValCI(subProps, subprop) == null) continue;
var subSplit = stripHtml(getObjectValCI(subProps, subprop)).split('|');
if (subSplit.length === 1) {
subFormat = BETranslate(inst._channelId, 'HS' + item.Module + '_changes_' + subprop);
if (subFormat.indexOf('not found in module') < 0) {
changes += $.sandr(subFormat, '#{value}', subSplit[0]);
}
else {
changes += "*|" + subprop + " " + subSplit[0] + "~";
}
}
else {
var fromValue = stripHtml(subSplit[0]);
var toValue = stripHtml(subSplit[1]);
var packetInfo = processChangedValues(subprop, fromValue, toValue);
if (packetInfo.skip) continue;
changes = changes + changeFrom + packetInfo.display + '|' + packetInfo.fromValue + '<b>' + changeTo + '</b>' + packetInfo.toValue + '~';
}
}
msg = $.sandr(msg, '#{' + prop + '}', changes);
} else if (prop == 'deleted') {
var deleted = '';
for (var i = 0; i < propertiesInOrder.length; i++) {
var subprop = propertiesInOrder[i];
var currentValue = getObjectValCI(subProps, subprop);
if (currentValue == null || currentValue.toString().length === 0) continue;
deleted = deleted + getDisplayForLabel(subprop) + '|' + getDeleteValue(subprop, currentValue) + '~';
}
msg = $.sandr(msg, '#{' + prop + '}', deleted);
}
} else {
msg = $.sandr(msg, '#{' + prop + '}', $.sandr(json[prop], '"', ' '));
}
function processChangedValues(label, fromValue, toValue){
var typeFormat = (getObjectValCI(configObject, label) || {}).type;
var result = {};
if (typeFormat === 'date'){
var d1 = new Date(fromValue);
var d2 = new Date(toValue);
if (isDateEqual(d1, d2)) result.skip = true;
}
result.fromValue = getValueToDisplay(fromValue, typeFormat);
result.toValue = getValueToDisplay(toValue, typeFormat);
result.display = getDisplayForLabel(label)
return result;
}
function getDeleteValue(label, value){
var typeFormat = (getObjectValCI(configObject, label) || {}).type;
return getValueToDisplay(value, typeFormat);
}
function getValueToDisplay(rawValue, typeFormat){
if (typeFormat === 'date'){
var d = new Date(rawValue);
return isNaN(d.getTime()) ? rawValue : d.toString('MM/dd/yyyy');
} else if (typeof typeFormat === 'function') {
return typeFormat(rawValue)
} else {
return rawValue;
}
}
function getDisplayForLabel(label){
var fixCaseOfProperty = standardCase === '*' || indexInArrayCI(label, standardCase) > -1;
var rawConfigForLabel = getObjectValCI(configObject, label) || {};
return (rawConfigForLabel && rawConfigForLabel.display)
|| (fixCaseOfProperty ? fixCase(label) : null)
|| label;
}
}
msg = $.sandr(msg, '#{contactId}', item.ContactId);
msg = $.sandr(msg, '#{from}', fromString);
msg = $.sandr(msg, '#{to}', toString);
msg = $.sandr(msg, '#{from2}', fromString2);
msg = $.sandr(msg, '#{to2}', toString2);
msg = $.sandr(msg, '#{recordId}', item.RecordId);
msg = msg.replace(/#{[\S]*}/g, '');
if (item.Action === 'saveinsurance' && !___x && __i == 1){
var end = +new Date();
___x = true;
//alert(end - start);
}
if (item.Action === 'saveinsurance') __i++;
if (msg.indexOf('not found in module') == -1) {
return msg;
} else {
return '';
}
}
function stripHtml(html) {
var tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText;
}
function isDateEqual(date1, date2) {
if (date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getYear() === date2.getYear()) {
return true;
}
else {
return false;
}
}
function getObjectValCI(obj, key){
for (var k in obj){
if (k.toLowerCase() === key.toLowerCase()){
return obj[k];
}
}
}
function indexInArrayCI(item, arr){
if (!$.isArray(arr)) arr = [];
var target = item.toString().toLowerCase();
for (var i = 0; i < arr.length; i++){
if (target === arr[i].toLowerCase()) return i;
}
return -1;
}
function fixCase(str){
return str.replace(/[a-z][A-Z]/g, function(match) { return match.charAt(0) + ' ' + match.charAt(1).toLowerCase(); }).toLowerCase()
.replace(/\sid\s/g, ' ID ')
.replace(/\sid$/g, ' ID')
.replace(/^id$/g, 'ID');
}
})();
When you use closure compiler you're giving up some control over your code. It will do all sorts of tricks, and potentially remove unused code.
It appears as though your functions are not removed, but are renamed.
For example, your call to getDeleteValue...
getDeleteValue(subprop, currentValue)
is now...
l(g,r)
Because getDeleteValue was not exported, Closure renamed it.
Working with Closure Compiler takes a bit of finesse and quite a bit of documentation scouring until you're familiar with how it works.
Well, there are too many errors to think of. First of all, I don't understand if you want static reference or instantiated values. You are not using jsDoc tags or anything like that. The Compiler does it's best work only with the corresponding jsDoc tag. You're logic is very weird and ill formulated. Prototype alternations, etc, all happening in an IIFE(immediately invoked function expression). Are your functions static? Are they constructors? Are we human or are we dancer?
an IIFE executes before the DOMContentLoaded event is fired by the browser. The most you can do is a jQuery IIFE equivalent $(function() {})(); which binds that to the DOMReady or DOMContentLoaded callback. You are defining inline functions inside blocks, which is not even in the ECMA Language.
While most script engines support Function Declarations within blocks it is not part of ECMAScript (see ECMA-262, clause 13 and 14). Worse implementations are inconsistent with each other and with future EcmaScript proposals. ECMAScript only allows for Function Declarations in the root statement list of a script or function. Instead use a variable initialized with a Function Expression to define a function within a block.
var myFunctionName = function (params) {};
You are also missing loads of semi-colons. Automatic semi-colon insertion on interpretation of your JS is not exactly flawless, so make a habit out of it.
Relying on implicit insertion can cause subtle, hard to debug problems. Don't do it. You're better than that.
There are a couple places where missing semicolons are particularly dangerous:
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // No semicolon here.
(function() {
// Some initialization code wrapped in a function to create a scope for locals.
})();
var x = {
'i': 1,
'j': 2
} // No semicolon here.
// 2. Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[normalVersion, ffVersion][isFF]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
// 3. conditional execution a la bash
-1 == resultOfOperation() || die();
So what happens?
JavaScript error - first the function returning 42 is called with the second function as a parameter, then the number 42 is "called" resulting in an error.
You will most likely get a 'no such property in undefined' error at runtime as it tries to call x[ffVersion][isIE]().
die is called unless resultOfOperation() is NaN and THINGS_TO_EAT gets assigned the result of die().
Why?
JavaScript requires statements to end with a semicolon, except when it thinks it can safely infer their existence. In each of these examples, a function declaration or object or array literal is used inside a statement. The closing brackets are not enough to signal the end of the statement. Javascript never ends a statement if the next token is an infix or bracket operator.
This has really surprised people, so make sure your assignments end with semicolons.

javascript abstract console logging

I want to make a function, like this.
For example:
function Logger() {
this.log = function(msg) {
console.log(msg);
}
}
And I want to use it in functions/modules etc, and that all works fine.
But the default console in my browser normally give the fileName + lineNumber.
Now when I abstract this functionality, the fileName and lineNumber is not where I put my instance.log(). Because it will say from where the console.log is being called, not the function itself.
So my question:
How can I get the correct information from where I want to use my logger?
Or give me, please, any tips to improve this functionality.
function Logger() {
this.log = console.log.bind(console);
}
I asked about this some time ago: Create shortcut to console.log() in Chrome.
Try using backtrace function like this one :
function printStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i & lt; len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
callstack.push(lines[i]);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
else if (window.opera & amp; & amp; e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i & lt; len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += ' at ' + lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf( & amp; quot;
function & amp; quot;) + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
output(callstack);
}
function output(arr) {
//Optput however you want
alert(arr.join('\n\n'));
}
Try assigning the function:
(function () {
window.log = (console && console.log
? console.log
: function () {
// Alternative log
});
})();
Later just call log('Message') in your code.

Categories

Resources