How to retrieve first H1 from an Url? - javascript

I am preparing for interview questions from a site , and couldn't think of solution for following, can some body help?
Write a function to retrieve the value of the first H1 element from a given Url.
H1 that contains attributes
H1 that contains nested tags
No H1 found in the HTML

Have a look at the Html agility pack http://htmlagilitypack.codeplex.com/. You can then use linq to answer the question.

So I searched it up online and I found a script and modified it.
I took the file_get_contents function from a project on GitHub.
I hope it will work, tell me, if it's not I will try to fix it.
get.js (var H1 is the first title):
function file_get_contents(url, flags, context, offset, maxLen) {
// discuss at: http://phpjs.org/functions/file_get_contents/
// original by: Legaev Andrey
// input by: Jani Hartikainen
// input by: Raphael (Ao) RUDLER
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Brett Zamir (http://brett-zamir.me)
// bugfixed by: Brett Zamir (http://brett-zamir.me)
// note: This function uses XmlHttpRequest and cannot retrieve resource from different domain without modifications.
// note: Synchronous by default (as in PHP) so may lock up browser. Can
// note: get async by setting a custom "phpjs.async" property to true and "notification" for an
// note: optional callback (both as context params, with responseText, and other JS-specific
// note: request properties available via 'this'). Note that file_get_contents() will not return the text
// note: in such a case (use this.responseText within the callback). Or, consider using
// note: jQuery's: $('#divId').load('http://url') instead.
// note: The context argument is only implemented for http, and only partially (see below for
// note: "Presently unimplemented HTTP context options"); also the arguments passed to
// note: notification are incomplete
// test: skip
// example 1: var buf file_get_contents('http://google.com');
// example 1: buf.indexOf('Google') !== -1
// returns 1: true
var tmp, headers = [],
newTmp = [],
k = 0,
i = 0,
href = '',
pathPos = -1,
flagNames = 0,
content = null,
http_stream = false;
var func = function(value) {
return value.substring(1) !== '';
};
// BEGIN REDUNDANT
this.php_js = this.php_js || {};
this.php_js.ini = this.php_js.ini || {};
// END REDUNDANT
var ini = this.php_js.ini;
context = context || this.php_js.default_streams_context || null;
if (!flags) {
flags = 0;
}
var OPTS = {
FILE_USE_INCLUDE_PATH: 1,
FILE_TEXT: 32,
FILE_BINARY: 64
};
if (typeof flags === 'number') { // Allow for a single string or an array of string flags
flagNames = flags;
} else {
flags = [].concat(flags);
for (i = 0; i < flags.length; i++) {
if (OPTS[flags[i]]) {
flagNames = flagNames | OPTS[flags[i]];
}
}
}
if (flagNames & OPTS.FILE_BINARY && (flagNames & OPTS.FILE_TEXT)) { // These flags shouldn't be together
throw 'You cannot pass both FILE_BINARY and FILE_TEXT to file_get_contents()';
}
if ((flagNames & OPTS.FILE_USE_INCLUDE_PATH) && ini.include_path && ini.include_path.local_value) {
var slash = ini.include_path.local_value.indexOf('/') !== -1 ? '/' : '\\';
url = ini.include_path.local_value + slash + url;
} else if (!/^(https?|file):/.test(url)) { // Allow references within or below the same directory (should fix to allow other relative references or root reference; could make dependent on parse_url())
href = this.window.location.href;
pathPos = url.indexOf('/') === 0 ? href.indexOf('/', 8) - 1 : href.lastIndexOf('/');
url = href.slice(0, pathPos + 1) + url;
}
var http_options;
if (context) {
http_options = context.stream_options && context.stream_options.http;
http_stream = !! http_options;
}
if (!context || http_stream) {
var req = this.window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
if (!req) {
throw new Error('XMLHttpRequest not supported');
}
var method = http_stream ? http_options.method : 'GET';
var async = !! (context && context.stream_params && context.stream_params['phpjs.async']);
if (ini['phpjs.ajaxBypassCache'] && ini['phpjs.ajaxBypassCache'].local_value) {
url += (url.match(/\?/) == null ? '?' : '&') + (new Date())
.getTime(); // Give optional means of forcing bypass of cache
}
req.open(method, url, async);
if (async) {
var notification = context.stream_params.notification;
if (typeof notification === 'function') {
// Fix: make work with req.addEventListener if available: https://developer.mozilla.org/En/Using_XMLHttpRequest
if (0 && req.addEventListener) { // Unimplemented so don't allow to get here
/*
req.addEventListener('progress', updateProgress, false);
req.addEventListener('load', transferComplete, false);
req.addEventListener('error', transferFailed, false);
req.addEventListener('abort', transferCanceled, false);
*/
} else {
req.onreadystatechange = function(aEvt) { // aEvt has stopPropagation(), preventDefault(); see https://developer.mozilla.org/en/NsIDOMEvent
// Other XMLHttpRequest properties: multipart, responseXML, status, statusText, upload, withCredentials
/*
PHP Constants:
STREAM_NOTIFY_RESOLVE 1 A remote address required for this stream has been resolved, or the resolution failed. See severity for an indication of which happened.
STREAM_NOTIFY_CONNECT 2 A connection with an external resource has been established.
STREAM_NOTIFY_AUTH_REQUIRED 3 Additional authorization is required to access the specified resource. Typical issued with severity level of STREAM_NOTIFY_SEVERITY_ERR.
STREAM_NOTIFY_MIME_TYPE_IS 4 The mime-type of resource has been identified, refer to message for a description of the discovered type.
STREAM_NOTIFY_FILE_SIZE_IS 5 The size of the resource has been discovered.
STREAM_NOTIFY_REDIRECTED 6 The external resource has redirected the stream to an alternate location. Refer to message .
STREAM_NOTIFY_PROGRESS 7 Indicates current progress of the stream transfer in bytes_transferred and possibly bytes_max as well.
STREAM_NOTIFY_COMPLETED 8 There is no more data available on the stream.
STREAM_NOTIFY_FAILURE 9 A generic error occurred on the stream, consult message and message_code for details.
STREAM_NOTIFY_AUTH_RESULT 10 Authorization has been completed (with or without success).
STREAM_NOTIFY_SEVERITY_INFO 0 Normal, non-error related, notification.
STREAM_NOTIFY_SEVERITY_WARN 1 Non critical error condition. Processing may continue.
STREAM_NOTIFY_SEVERITY_ERR 2 A critical error occurred. Processing cannot continue.
*/
var objContext = {
responseText: req.responseText,
responseXML: req.responseXML,
status: req.status,
statusText: req.statusText,
readyState: req.readyState,
evt: aEvt
}; // properties are not available in PHP, but offered on notification via 'this' for convenience
// notification args: notification_code, severity, message, message_code, bytes_transferred, bytes_max (all int's except string 'message')
// Need to add message, etc.
var bytes_transferred;
switch (req.readyState) {
case 0:
// UNINITIALIZED open() has not been called yet.
notification.call(objContext, 0, 0, '', 0, 0, 0);
break;
case 1:
// LOADING send() has not been called yet.
notification.call(objContext, 0, 0, '', 0, 0, 0);
break;
case 2:
// LOADED send() has been called, and headers and status are available.
notification.call(objContext, 0, 0, '', 0, 0, 0);
break;
case 3:
// INTERACTIVE Downloading; responseText holds partial data.
bytes_transferred = req.responseText.length * 2; // One character is two bytes
notification.call(objContext, 7, 0, '', 0, bytes_transferred, 0);
break;
case 4:
// COMPLETED The operation is complete.
if (req.status >= 200 && req.status < 400) {
bytes_transferred = req.responseText.length * 2; // One character is two bytes
notification.call(objContext, 8, 0, '', req.status, bytes_transferred, 0);
} else if (req.status === 403) { // Fix: These two are finished except for message
notification.call(objContext, 10, 2, '', req.status, 0, 0);
} else { // Errors
notification.call(objContext, 9, 2, '', req.status, 0, 0);
}
break;
default:
throw 'Unrecognized ready state for file_get_contents()';
}
};
}
}
}
if (http_stream) {
var sendHeaders = http_options.header && http_options.header.split(/\r?\n/);
var userAgentSent = false;
for (i = 0; i < sendHeaders.length; i++) {
var sendHeader = sendHeaders[i];
var breakPos = sendHeader.search(/:\s*/);
var sendHeaderName = sendHeader.substring(0, breakPos);
req.setRequestHeader(sendHeaderName, sendHeader.substring(breakPos + 1));
if (sendHeaderName === 'User-Agent') {
userAgentSent = true;
}
}
if (!userAgentSent) {
var user_agent = http_options.user_agent || (ini.user_agent && ini.user_agent.local_value);
if (user_agent) {
req.setRequestHeader('User-Agent', user_agent);
}
}
content = http_options.content || null;
/*
// Presently unimplemented HTTP context options
var request_fulluri = http_options.request_fulluri || false; // When set to TRUE, the entire URI will be used when constructing the request. (i.e. GET http://www.example.com/path/to/file.html HTTP/1.0). While this is a non-standard request format, some proxy servers require it.
var max_redirects = http_options.max_redirects || 20; // The max number of redirects to follow. Value 1 or less means that no redirects are followed.
var protocol_version = http_options.protocol_version || 1.0; // HTTP protocol version
var timeout = http_options.timeout || (ini.default_socket_timeout && ini.default_socket_timeout.local_value); // Read timeout in seconds, specified by a float
var ignore_errors = http_options.ignore_errors || false; // Fetch the content even on failure status codes.
*/
}
if (flagNames & OPTS.FILE_TEXT) { // Overrides how encoding is treated (regardless of what is returned from the server)
var content_type = 'text/html';
if (http_options && http_options['phpjs.override']) { // Fix: Could allow for non-HTTP as well
content_type = http_options['phpjs.override']; // We use this, e.g., in gettext-related functions if character set
// overridden earlier by bind_textdomain_codeset()
} else {
var encoding = (ini['unicode.stream_encoding'] && ini['unicode.stream_encoding'].local_value) ||
'UTF-8';
if (http_options && http_options.header && (/^content-type:/im)
.test(http_options.header)) { // We'll assume a content-type expects its own specified encoding if present
content_type = http_options.header.match(/^content-type:\s*(.*)$/im)[1]; // We let any header encoding stand
}
if (!(/;\s*charset=/)
.test(content_type)) { // If no encoding
content_type += '; charset=' + encoding;
}
}
req.overrideMimeType(content_type);
}
// Default is FILE_BINARY, but for binary, we apparently deviate from PHP in requiring the flag, since many if not
// most people will also want a way to have it be auto-converted into native JavaScript text instead
else if (flagNames & OPTS.FILE_BINARY) { // Trick at https://developer.mozilla.org/En/Using_XMLHttpRequest to get binary
req.overrideMimeType('text/plain; charset=x-user-defined');
// Getting an individual byte then requires:
// responseText.charCodeAt(x) & 0xFF; // throw away high-order byte (f7) where x is 0 to responseText.length-1 (see notes in our substr())
}
try {
if (http_options && http_options['phpjs.sendAsBinary']) { // For content sent in a POST or PUT request (use with file_put_contents()?)
req.sendAsBinary(content); // In Firefox, only available FF3+
} else {
req.send(content);
}
} catch (e) {
// catches exception reported in issue #66
return false;
}
tmp = req.getAllResponseHeaders();
if (tmp) {
tmp = tmp.split('\n');
for (k = 0; k < tmp.length; k++) {
if (func(tmp[k])) {
newTmp.push(tmp[k]);
}
}
tmp = newTmp;
for (i = 0; i < tmp.length; i++) {
headers[i] = tmp[i];
}
this.$http_response_header = headers; // see http://php.net/manual/en/reserved.variables.httpresponseheader.php
}
if (offset || maxLen) {
if (maxLen) {
return req.responseText.substr(offset || 0, maxLen);
}
return req.responseText.substr(offset);
}
return req.responseText;
}
return false;
}
var filecontent = file_get_contents("www.example.com");
var H1 = $( "h1" ).first().val();

if you would do it on the client side using jquery you could do it like this
$('#mydiv').load("http://localhost/mypage.html");
var firstH1 = $('#mydiv').children('h1:first-child');
console.log(firstH1);

Related

Domo.js is trying to JSON.parse a png

While developing a custom app for my organization, I am trying to request the name and the avatar of the individual accessing the card. I am able to get the name of the individual without any problems, but when requesting the avatar image I get the following console error:
Uncaught (in promise) Error: Invalid JSON response at XMLHttpRequest.d.onload (domo.ts:309:18)
I have looked into the domo.js code, and after making some limited sense of things, I found that it tries to JSON.parse the .png that is returned.
When checking the network dev tools tab I can see the correct image getting returned, but it doesn't get passed to the app.
Here is the function that returns the error:
d.onload = function() {
var e;
if( u(d.status) ) {
!["csv","excel"].includes(r.format) && d.response || i(d.response), "blob" === r.responseType && i(new Blob([d.response], { type:d.getResponseHeader("content-type") }));
var t = d.response;
try{
e = JSON.parse(t)
}
catch(e){
return void c(Error("Invalid JSON response"))
}i(e)
}else c(Error(d.statusText))
}
As far as I can tell, e refers to the Domo environment, although I am not 100% sure of that.
Note: I am turning to stackoverflow because my organization still has open support tickets with Domo that are more than 2 years old with no response, so I have little faith in getting a timely response from Domo regarding this issue.
UPDATE: Here is the full function that is called-
function i(e,t,r,n,a) {
return r = r || {}, new Promise((function(i,c) {
var d = new XMLHttpRequest;
if (n?d.open(e,t,n):d.open(e,t), p(d,t,r), function(e,t) {
t.contentType ?
"multipart" !== t.contentType && e.setRequestHeader("Content-Type", t.contentType)
: e.setRequestHeader("Content-Type", o.DataFormats.JSON)
} (d,r), function(e) {
s && e.setRequestHeader("X-DOMO-Ryuu-Token", s)
} (d), function(e,t) {
void 0 !== t.responseType && (e.responseType = t.responseType)
} (d,r),
d.onload = function() {
var e;
if( u(d.status) ) {
!["csv","excel"].includes(r.format) && d.response || i(d.response), "blob" === r.responseType && i(new Blob([d.response], { type:d.getResponseHeader("content-type") }));
var t = d.response;
try{
e = JSON.parse(t)
}
catch(e){
return void c(Error("Invalid JSON response"))
}i(e)
}else c(Error(d.statusText))
},
d.onerror = function() {
c(Error("Network Error"))
}, a)
if (r.contentType && r.contentType !== o.DataFormats.JSON) d.send(a);
else {
var f = JSON.stringify(a);
d.send(f)
}
else d.send()
}))
Here is the domo.js method that is being called to get the image:
e.get = function(e, t) {
return i(o.RequestMethods.GET, e, t)
},
#Skousini you can get the avatar for a user by providing this URL directly to the src property of the <img> tag (obviously replacing the query params with the relevant information):
<img src="/domo/avatars/v2/USER/846578099?size=300&defaultForeground=fff&defaultBackground=000&defaultText=D" />
This documentation is available on developer.domo.com: https://developer.domo.com/docs/dev-studio-references/user-api#User%20Avatar
If you want to pull down data from endpoints, you don't have to use domo.js. You could use axios or any other HTTP tool. domo.js is trying to make HTTP requests simpler by automatically parsing json (since most requests are json based). There are a few other options for what data format that domo.get can support provided in this documentation: https://developer.domo.com/docs/dev-studio-tools/domo-js#domo.get

How to maintain browser history while doing a location.reload() on popstate event of Javascript

So, I have a scenario where I am generating all my URL's using history.pushState function. I am NOT using any server side technique for this. I am fetching all the data only once from the server and then generating the URL using Javascript.
Now the problem that I am encountering is using the popstate function which basically mimics the back button behavior of a browser.
Everything is working fine for ONLY one back button click.
$(window).on('popstate', function (event) {
location.reload(true);
//window.location.href = document.location;
//console.log("location: " + document.location);
});
After one back button event, I am unable to see any previous history of the browser. I am assuming it is happening because of location.reload method. I tried storing all my location also and then doing a redirect but same thing, the browser history gets lost after one refresh.
Is there a way where I can achieve this without the browser losing it's history for multiple back button clicks?
If there is a JQuery option for this, then it would be great if someone could share their knowledge on this aspect.
I wrote a simple plugin built around the History API that I use in my projects. It will do what you are asking.
// instantiate the QueryString Class.
qsObj=new QueryString({
onPopstate: function(qsParams, data, dataAge, e) {
// This function is called on any window.popstate event
// -- triggered when user click browser's back or forward button.
// -- triggered when JS manipulates history.back() or history.forward()
// -- NOT triggered when any of the QueryString methods are called (per the native behavior of the HTML5.history API which this class uses.)
// -- Might be triggered on page load in some browsers. You can handle this by checking for the existence of data, eg: if(data){ 'do something with the data' }else{ 'move along nothing to do here' }
console.log('executing onPopstate function');
console.log('page query string parameters are: ', qsParams);
console.log('stored page data is: ', data);
console.log('dataAge is:', dataAge, ' seconds old');
// do stuff..
if(data) {
if(dataAge && dataAge<=20) {
// use the stored data..
} else {
// it's old data get new..
}
} else {
// the current page has no stored data..
}
},
onDocumentReady: function(qsParams) {
// Document ready is only called when a page is first browsed to or the page is refreshed.
// Navigating the history stack (clicking back/forward) does NOT refire document ready.
// Use this function to handle any parameters given in the URL and sent as an object of key/value pairs as the first and only parameter to this function.
console.log('executing onDocumentReady function');
console.log('page query string parameters are:',qsParams);
// do stuff..
}
});
Plugin Usage:
// == QueryString class =============================================================================================
//
// Modifies the query string portion of a browsers URL, updates the browsers history stack - saving page data
// along with it, all without reloading the page.
// Can be used to simply obtain querystring parameter values as well.
//
// == Instantiate: ================================
//
// var qsObj=new QueryString(
//
// onPopstate: function(qsParams, data, dataAge, e) {
//
// // This function is called on any window.popstate event
// // -- triggered when user click browser's back or forward button.
// // -- triggered when JS manipulates history.back() or history.forward()
// // -- NOT triggered when any of the QueryString methods are called (per the native behavior of the
// // HTML5.history API which this class uses.)
// // -- Might be triggered on page load in some browsers. You can handle this by checking for the
// // existence of data, eg:
// // if(data){ 'do something with the data' }else{ 'move along nothing to do here' }
//
// // -- qsParams: is an object that contains the current pages query string paramters as key:value pairs.
// // -- data: is an object that contains any page data that was stored at the time that this page was added
// // to the history stack via this class (or any HTML5 history API method), otherwise this value is NULL!
// // -- dataAge: null if data is null or the page data was not added via the methods in this class,
// // otherwise the value is the age of the data in seconds.
// // -- e: the event object if you want it.
//
// if(data){
// if(dataAge && dataAge <= 600){ // do it this way otherwise you'll error out if dataAge is null.
// // There is data and it is less than 10 minutes old.
// // do stuff with it..
// }
// }
// },
//
// onDocumentReady: function(qsParams){
// // Document ready is only called when a page is first browsed to or the page is refreshed.
// // Navigating the history stack (clicking back/forward) does NOT refire document ready.
// // Use this function to handle any parameters given in the URL and sent as an object of key/value pairs as
// // the first and only parameter to this function.
//
// // do stuff with any qsParams given in the URL..
//
// }
//
// });
//
//
// == The following methods are available: =======================================
//
//
// var qsParams = qsObj.parseQueryString(); // returns an object that contains the key/value pairs of all the query
// // string parameter/values contained in the current URL, or an empty object
// // if there are none.
//
//
// qsObj.update({
//
// // Use this method to add/remove query string parameters from the URL and at the same time update, or add to, the
// browser history stack with the ability to also save page data in with the history.
//
// opType: 'auto',
// // -- Optional. Allowed values: ['replace'|'push'|'auto'], Default is 'auto' unless 'push' or 'replace' is
// // specifically given.
// // -- 'push': Adds the new URL and any page data onto the browser history stack.
// // -- 'replace': Overwrites the current page history in the stack with the new URL and/or page data
// // -- 'auto': compares the initial qs parameters with the updated qs parameters and if they are the same
// // does a 'replace', if they are different does a 'push'.
//
// qsParams: {
// hello: 'world',
// another: 'pair'
// },
// // -- Optional. Object that contains key/value pairs to add to the query string portion of the URL.
// // -- Will entirely replace what is/isn't currently in the query string with the given key/value pairs.
// // -- The parameters contained in the url querystring will be made, or unmade, based on the key/value pairs
// // included here so be sure to include all of the pairs that you want to show in the URL each time.
//
//
// data: {
// key1: 'value1',
// key2: 'value2'
// }
// // Optional, Object that contains key/value pairs to store as page data in the browser history stack for this page.
//
// // ** If qsParams and data are ommitted then nothing silently happens. (This is not the same as given but empty,
// // in which case something happens.)
//
// });
//
//
// qsObj.Clear({
//
// // Use this method to remove all query string parameters from the URL and at the same time update, or add to, the
// // browser history stack with the ability to also save page data in with the history.
//
// optype: 'auto' // optional, defaults to auto.
//
// data: {} // Optional, defaults to empty object {}.
// });
//
// =========================================================================================================================
Plugin Code:
; (function () {
var Def = function () { return constructor.apply(this, arguments); };
var attr = Def.prototype;
//== list attributes
attr.popstateCallback = null;
attr.docreadyCallback = null;
attr.skipParseOnInit = false;
attr.currentQS;
//== Construct
function constructor(settings) {
if (typeof settings === 'object') {
if ('onPopstate' in settings && typeof settings.onPopstate === 'function') {
this.popstateCallback = settings.onPopstate;
}
if ('onDocumentReady' in settings && typeof settings.onDocumentReady === 'function') {
this.docreadyCallback = settings.onDocumentReady;
}
}
if (this.skipParseOnInit !== true) {
this.currentQS = this.parseQueryString();
}
var self = this;
if (typeof this.popstateCallback === 'function') {
$(window).on('popstate', function (e) {
var data = null;
var dataAge = null;
if (typeof e === 'object' && 'originalEvent' in e && typeof e.originalEvent === 'object' && 'state' in e.originalEvent && e.originalEvent.state && typeof e.originalEvent.state === 'object') {
data = e.originalEvent.state;
if ('_qst_' in data) {
dataAge = ((new Date).getTime() - e.originalEvent.state['_qst_']) / 1000; // determine how old the data is, in seconds
delete data['_qst_'];
}
}
var qsparams = self.parseQueryString();
self.popstateCallback(qsparams, data, dataAge, e);
});
}
if (typeof this.docreadyCallback === 'function') {
$(document).ready(function () {
self.docreadyCallback(self.currentQS);
});
}
}
//== Define methods ============================================================================
attr.parseQueryString = function (url) {
var pairs, t, i, l;
var qs = '';
if (url === undefined) {
var loc = window.history.location || window.location;
qs = loc.search.replace(/^\?/, '');
} else {
var p = url.split('?');
if (p.length === 2) {
qs = p[1];
}
}
var r = {};
if (qs === '') {
return r;
}
// Split into key/value pairs
pairs = qs.split("&");
// Convert the array of strings into an object
for (i = 0, l = pairs.length; i < l; i++) {
t = pairs[i].split('=');
var x = decodeURI(t[1]);
r[t[0]] = x;
}
return r;
};
//-- Get a querystring value from it's key name
attr.getValueFromKey = function (key) {
var qs = this.parseQueryString();
if (key in qs) {
return decodeURIComponent(qs[key].replace(/\+/g, " "));
} else {
return null;
}
};
//-- if urlValue is given then qsParams are ignored.
attr.update = function (params) {
if (typeof params !== 'object') { return; }
var urlValue = null;
var data = {};
if ('data' in params) {
data = params.data;
urlValue = '';
}
var opType = 'opType' in params ? params.opType : 'auto';
if ('urlValue' in params && typeof params.urlValue === 'string') {
urlValue = params.urlValue;
if (opType === 'auto') {
var loc = window.history.location || window.location;
if (loc.protocol + '//' + loc.host + loc.pathname + loc.search === urlValue || loc.pathname + loc.search === urlValue) {
opType = 'replace'; // same URL, replace
} else {
opType = 'push'; // different URL, push
}
}
} else if ('qsParams' in params && typeof params.qsParams === 'object') {
var pairs = [];
for (var key in params.qsParams) {
pairs.push(key + '=' + params.qsParams[key]);
}
urlValue = '?' + pairs.join('&', pairs);
if (opType === 'auto') {
if (this.compareQsObjects(params.qsParams, this.currentQS) === false) { // different
this.currentQS = params.qsParams;
opType = 'push';
} else { // same
opType = 'replace';
}
}
}
this.replaceOrPush(urlValue, data, opType);
};
//== Add querystring
//-- just an alias to update
attr.add = function (params) {
return this.update(params);
};
//== Remove specified querystring parameters
// -- Use clear() method to remove ALL query string parameters
attr.remove = function (params) {
var urlValue = null;
var qsChanged = false;
if ('qsParams' in params && params.qsParams.length > 0) {
var qs = this.parseQueryString();
var key;
for (var i = 0, l = params.qsParams.length; i < l; ++i) {
key = params.qsParams[i];
if (key in qs) {
delete qs[key];
qsChanged = true;
}
}
}
if (qsChanged === true) {
var pairs = [];
for (key in qs) {
pairs.push(key + '=' + qs[key]);
}
urlValue = '?' + pairs.join('&', pairs);
var data = 'data' in params ? params.data : {};
var opType = 'opType' in params ? params.opType : '';
this.replaceOrPush(urlValue, data, opType);
}
return;
};
//== Delete querystring
//-- just an alias to remove
attr.delete = function (params) {
return this.remove(params);
};
//== Removes all query string parameters.
// Use remove() method to remove just the given parameters
attr.clear = function (params) {
params = typeof params === 'undefined' ? {} : params;
var urlValue = window.history.location || window.location;
urlValue = urlValue.protocol + '//' + urlValue.host + urlValue.pathname;
var data = 'data' in params ? params.data : {};
var opType = 'opType' in params ? params.opType : '';
this.replaceOrPush(urlValue, data, opType);
return;
};
//== Simple wrapper to the HTML5 history API's replaceState() method.
// --also used internally
attr.replaceState = function (urlValue, data) {
if (typeof urlValue !== 'string') {
return;
}
if (typeof data !== 'object') {
data = {};
}
data['_qst_'] = (new Date).getTime(); // store a timestamp value
history.replaceState(data, '', urlValue);
};
//== Simple wrapper to the HTML5 history API's pushState() method.
// --also used internally
attr.pushState = function (urlValue, data) {
if (typeof urlValue !== 'string') {
return;
}
if (typeof data !== 'object') {
data = {};
}
data['_qst_'] = (new Date).getTime(); // store a timestamp value
history.pushState(data, '', urlValue);
};
//-- internal use - simple gatekeeper to decide if there is anything to do and will default to 'replace' opType if this value is not given.
attr.replaceOrPush = function (urlValue, data, opType) {
// is there anything to do?
if (typeof urlValue === 'string' || typeof data === 'object') {
// yes, what type of operation are we going to do?
if (opType === 'push') {
this.pushState(urlValue, data);
} else {
this.replaceState(urlValue, data);
}
}
return;
};
// == internal use - compares the existing qs with a potentially updated one to see if they are the same (returns true) or not (returns false)
attr.compareQsObjects = function (a, b) {
if (typeof a === 'object' && typeof b === 'object') {
var aa = [];
var bb = [];
for (k in a) {
aa.push(k + a[k]);
}
aa.sort();
for (k in b) {
bb.push(k + b[k]);
}
bb.sort();
if (aa.join('') !== bb.join('')) { return false; }
return true;
}
return null;
};
//unleash your class
window.QueryString = Def;
})();
Well, this is not really possible.
The only trick I'm thinking about is to change window.location.href when the popstate event is fired, and to pass your history as URL parameters.
$(window).on('popstate', function (event) {
window.location.href = 'https://here.com?history = ' + yourHistory;
});
You need to figure out how you can send your history with this method, and handle it when you arrive on the new page to be able to send it again when the user clicks back button again.
You also have to make sure the URL generated is not too long.

A proper wrapper for console.log with correct line number?

I'm now developing an application, and place a global isDebug switch. I would like to wrap console.log for more convenient usage.
//isDebug controls the entire site.
var isDebug = true;
//debug.js
function debug(msg, level){
var Global = this;
if(!(Global.isDebug && Global.console && Global.console.log)){
return;
}
level = level||'info';
Global.console.log(level + ': '+ msg);
}
//main.js
debug('Here is a msg.');
Then I get this result in Firefox console.
info: Here is a msg. debug.js (line 8)
What if I want to log with line number where debug() gets called, like info: Here is a msg. main.js (line 2)?
This is an old question and All the answers provided are overly hackey, have MAJOR cross browser issues, and don't provide anything super useful. This solution works in every browser and reports all console data exactly as it should. No hacks required and one line of code Check out the codepen.
var debug = console.log.bind(window.console)
Create the switch like this:
isDebug = true // toggle this to turn on / off for global controll
if (isDebug) var debug = console.log.bind(window.console)
else var debug = function(){}
Then simply call as follows:
debug('This is happening.')
You can even take over the console.log with a switch like this:
if (!isDebug) console.log = function(){}
If you want to do something useful with that.. You can add all the console methods and wrap it up in a reusable function that gives not only global control, but class level as well:
var Debugger = function(gState, klass) {
this.debug = {}
if (gState && klass.isDebug) {
for (var m in console)
if (typeof console[m] == 'function')
this.debug[m] = console[m].bind(window.console, klass.toString()+": ")
}else{
for (var m in console)
if (typeof console[m] == 'function')
this.debug[m] = function(){}
}
return this.debug
}
isDebug = true //global debug state
debug = Debugger(isDebug, this)
debug.log('Hello log!')
debug.trace('Hello trace!')
Now you can add it to your classes:
var MyClass = function() {
this.isDebug = true //local state
this.debug = Debugger(isDebug, this)
this.debug.warn('It works in classses')
}
You can maintain line numbers and output the log level with some clever use of Function.prototype.bind:
function setDebug(isDebug) {
if (window.isDebug) {
window.debug = window.console.log.bind(window.console, '%s: %s');
} else {
window.debug = function() {};
}
}
setDebug(true);
// ...
debug('level', 'This is my message.'); // --> level: This is my message. (line X)
Taking it a step further, you could make use of the console's error/warning/info distinctions and still have custom levels. Try it!
function setDebug(isDebug) {
if (isDebug) {
window.debug = {
log: window.console.log.bind(window.console, 'log: %s'),
error: window.console.error.bind(window.console, 'error: %s'),
info: window.console.info.bind(window.console, 'info: %s'),
warn: window.console.warn.bind(window.console, 'warn: %s')
};
} else {
var __no_op = function() {};
window.debug = {
log: __no_op,
error: __no_op,
warn: __no_op,
info: __no_op
}
}
}
setDebug(true);
// ...
debug.log('wat', 'Yay custom levels.'); // -> log: wat: Yay custom levels. (line X)
debug.info('This is info.'); // -> info: This is info. (line Y)
debug.error('Bad stuff happened.'); // -> error: Bad stuff happened. (line Z)
I liked #fredrik's answer, so I rolled it up with another answer which splits the Webkit stacktrace, and merged it with #PaulIrish's safe console.log wrapper. "Standardizes" the filename:line to a "special object" so it stands out and looks mostly the same in FF and Chrome.
Testing in fiddle: http://jsfiddle.net/drzaus/pWe6W/
_log = (function (undefined) {
var Log = Error; // does this do anything? proper inheritance...?
Log.prototype.write = function (args) {
/// <summary>
/// Paulirish-like console.log wrapper. Includes stack trace via #fredrik SO suggestion (see remarks for sources).
/// </summary>
/// <param name="args" type="Array">list of details to log, as provided by `arguments`</param>
/// <remarks>Includes line numbers by calling Error object -- see
/// * http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
/// * https://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number
/// * https://stackoverflow.com/a/3806596/1037948
/// </remarks>
// via #fredrik SO trace suggestion; wrapping in special construct so it stands out
var suffix = {
"#": (this.lineNumber
? this.fileName + ':' + this.lineNumber + ":1" // add arbitrary column value for chrome linking
: extractLineNumberFromStack(this.stack)
)
};
args = args.concat([suffix]);
// via #paulirish console wrapper
if (console && console.log) {
if (console.log.apply) { console.log.apply(console, args); } else { console.log(args); } // nicer display in some browsers
}
};
var extractLineNumberFromStack = function (stack) {
/// <summary>
/// Get the line/filename detail from a Webkit stack trace. See https://stackoverflow.com/a/3806596/1037948
/// </summary>
/// <param name="stack" type="String">the stack string</param>
if(!stack) return '?'; // fix undefined issue reported by #sigod
// correct line number according to how Log().write implemented
var line = stack.split('\n')[2];
// fix for various display text
line = (line.indexOf(' (') >= 0
? line.split(' (')[1].substring(0, line.length - 1)
: line.split('at ')[1]
);
return line;
};
return function (params) {
/// <summary>
/// Paulirish-like console.log wrapper
/// </summary>
/// <param name="params" type="[...]">list your logging parameters</param>
// only if explicitly true somewhere
if (typeof DEBUGMODE === typeof undefined || !DEBUGMODE) return;
// call handler extension which provides stack trace
Log().write(Array.prototype.slice.call(arguments, 0)); // turn into proper array
};//-- fn returned
})();//--- _log
This also works in node, and you can test it with:
// no debug mode
_log('this should not appear');
// turn it on
DEBUGMODE = true;
_log('you should', 'see this', {a:1, b:2, c:3});
console.log('--- regular log ---');
_log('you should', 'also see this', {a:4, b:8, c:16});
// turn it off
DEBUGMODE = false;
_log('disabled, should not appear');
console.log('--- regular log2 ---');
I found a simple solution to combine the accepted answer (binding to console.log/error/etc) with some outside logic to filter what is actually logged.
// or window.log = {...}
var log = {
ASSERT: 1, ERROR: 2, WARN: 3, INFO: 4, DEBUG: 5, VERBOSE: 6,
set level(level) {
if (level >= this.ASSERT) this.a = console.assert.bind(window.console);
else this.a = function() {};
if (level >= this.ERROR) this.e = console.error.bind(window.console);
else this.e = function() {};
if (level >= this.WARN) this.w = console.warn.bind(window.console);
else this.w = function() {};
if (level >= this.INFO) this.i = console.info.bind(window.console);
else this.i = function() {};
if (level >= this.DEBUG) this.d = console.debug.bind(window.console);
else this.d = function() {};
if (level >= this.VERBOSE) this.v = console.log.bind(window.console);
else this.v = function() {};
this.loggingLevel = level;
},
get level() { return this.loggingLevel; }
};
log.level = log.DEBUG;
Usage:
log.e('Error doing the thing!', e); // console.error
log.w('Bonus feature failed to load.'); // console.warn
log.i('Signed in.'); // console.info
log.d('Is this working as expected?'); // console.debug
log.v('Old debug messages, output dominating messages'); // console.log; ignored because `log.level` is set to `DEBUG`
log.a(someVar == 2) // console.assert
Note that console.assert uses conditional logging.
Make sure your browser's dev tools shows all message levels!
Listen McFly, this was the only thing that worked for me:
let debug = true;
Object.defineProperty(this, "log", {get: function () {
return debug ? console.log.bind(window.console, '['+Date.now()+']', '[DEBUG]')
: function(){};}
});
// usage:
log('Back to the future');
// outputs:
[1624398754679] [DEBUG] Back to the future
The beauty is to avoid another function call like log('xyz')()
or to create a wrapper object or even class. Its also ES5 safe.
If you don't want a prefix, just delete the param.
update included current timestamp to prefix every log output.
Chrome Devtools lets you achieve this with Blackboxing. You can create console.log wrapper that can have side effects, call other functions, etc, and still retain the line number that called the wrapper function.
Just put a small console.log wrapper into a separate file, e.g.
(function() {
var consolelog = console.log
console.log = function() {
// you may do something with side effects here.
// log to a remote server, whatever you want. here
// for example we append the log message to the DOM
var p = document.createElement('p')
var args = Array.prototype.slice.apply(arguments)
p.innerText = JSON.stringify(args)
document.body.appendChild(p)
// call the original console.log function
consolelog.apply(console,arguments)
}
})()
Name it something like log-blackbox.js
Then go to Chrome Devtools settings and find the section "Blackboxing", add a pattern for the filename you want to blackbox, in this case log-blackbox.js
From: How to get JavaScript caller function line number? How to get JavaScript caller source URL?
the Error object has a line number property(in FF). So something like this should work:
var err = new Error();
Global.console.log(level + ': '+ msg + 'file: ' + err.fileName + ' line:' + err.lineNumber);
In Webkit browser you have err.stack that is a string representing the current call stack. It will display the current line number and more information.
UPDATE
To get the correct linenumber you need to invoke the error on that line. Something like:
var Log = Error;
Log.prototype.write = function () {
var args = Array.prototype.slice.call(arguments, 0),
suffix = this.lineNumber ? 'line: ' + this.lineNumber : 'stack: ' + this.stack;
console.log.apply(console, args.concat([suffix]));
};
var a = Log().write('monkey' + 1, 'test: ' + 2);
var b = Log().write('hello' + 3, 'test: ' + 4);
A way to keep line number is here: https://gist.github.com/bgrins/5108712. It more or less boils down to this:
if (Function.prototype.bind) {
window.log = Function.prototype.bind.call(console.log, console);
}
else {
window.log = function() {
Function.prototype.apply.call(console.log, console, arguments);
};
}
You could wrap this with isDebug and set window.log to function() { } if you aren't debugging.
You can pass the line number to your debug method, like this :
//main.js
debug('Here is a msg.', (new Error).lineNumber);
Here, (new Error).lineNumber would give you the current line number in your javascript code.
If you simply want to control whether debug is used and have the correct line number, you can do this instead:
if(isDebug && window.console && console.log && console.warn && console.error){
window.debug = {
'log': window.console.log,
'warn': window.console.warn,
'error': window.console.error
};
}else{
window.debug = {
'log': function(){},
'warn': function(){},
'error': function(){}
};
}
When you need access to debug, you can do this:
debug.log("log");
debug.warn("warn");
debug.error("error");
If isDebug == true, The line numbers and filenames shown in the console will be correct, because debug.log etc is actually an alias of console.log etc.
If isDebug == false, no debug messages are shown, because debug.log etc simply does nothing (an empty function).
As you already know, a wrapper function will mess up the line numbers and filenames, so it's a good idea to prevent using wrapper functions.
Stack trace solutions display the line number but do not allow to click to go to source, which is a major problem. The only solution to keep this behaviour is to bind to the original function.
Binding prevents to include intermediate logic, because this logic would mess with line numbers. However, by redefining bound functions and playing with console string substitution, some additional behaviour is still possible.
This gist shows a minimalistic logging framework that offers modules, log levels, formatting, and proper clickable line numbers in 34 lines. Use it as a basis or inspiration for your own needs.
var log = Logger.get("module").level(Logger.WARN);
log.error("An error has occured", errorObject);
log("Always show this.");
EDIT: gist included below
/*
* Copyright 2016, Matthieu Dumas
* This work is licensed under the Creative Commons Attribution 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/
*/
/* Usage :
* var log = Logger.get("myModule") // .level(Logger.ALL) implicit
* log.info("always a string as first argument", then, other, stuff)
* log.level(Logger.WARN) // or ALL, DEBUG, INFO, WARN, ERROR, OFF
* log.debug("does not show")
* log("but this does because direct call on logger is not filtered by level")
*/
var Logger = (function() {
var levels = {
ALL:100,
DEBUG:100,
INFO:200,
WARN:300,
ERROR:400,
OFF:500
};
var loggerCache = {};
var cons = window.console;
var noop = function() {};
var level = function(level) {
this.error = level<=levels.ERROR ? cons.error.bind(cons, "["+this.id+"] - ERROR - %s") : noop;
this.warn = level<=levels.WARN ? cons.warn.bind(cons, "["+this.id+"] - WARN - %s") : noop;
this.info = level<=levels.INFO ? cons.info.bind(cons, "["+this.id+"] - INFO - %s") : noop;
this.debug = level<=levels.DEBUG ? cons.log.bind(cons, "["+this.id+"] - DEBUG - %s") : noop;
this.log = cons.log.bind(cons, "["+this.id+"] %s");
return this;
};
levels.get = function(id) {
var res = loggerCache[id];
if (!res) {
var ctx = {id:id,level:level}; // create a context
ctx.level(Logger.ALL); // apply level
res = ctx.log; // extract the log function, copy context to it and returns it
for (var prop in ctx)
res[prop] = ctx[prop];
loggerCache[id] = res;
}
return res;
};
return levels; // return levels augmented with "get"
})();
Here's a way to keep your existing console logging statements while adding a file name and line number or other stack trace info onto the output:
(function () {
'use strict';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !!window.chrome.webstore;
var isIE = /*#cc_on!#*/false || !!document.documentMode;
var isEdge = !isIE && !!window.StyleMedia;
var isPhantom = (/PhantomJS/).test(navigator.userAgent);
Object.defineProperties(console, ['log', 'info', 'warn', 'error'].reduce(function (props, method) {
var _consoleMethod = console[method].bind(console);
props[method] = {
value: function MyError () {
var stackPos = isOpera || isChrome ? 2 : 1;
var err = new Error();
if (isIE || isEdge || isPhantom) { // Untested in Edge
try { // Stack not yet defined until thrown per https://learn.microsoft.com/en-us/scripting/javascript/reference/stack-property-error-javascript
throw err;
} catch (e) {
err = e;
}
stackPos = isPhantom ? 1 : 2;
}
var a = arguments;
if (err.stack) {
var st = err.stack.split('\n')[stackPos]; // We could utilize the whole stack after the 0th index
var argEnd = a.length - 1;
[].slice.call(a).reverse().some(function(arg, i) {
var pos = argEnd - i;
if (typeof a[pos] !== 'string') {
return false;
}
if (typeof a[0] === 'string' && a[0].indexOf('%') > -1) { pos = 0 } // If formatting
a[pos] += ' \u00a0 (' + st.slice(0, st.lastIndexOf(':')) // Strip out character count
.slice(st.lastIndexOf('/') + 1) + ')'; // Leave only path and line (which also avoids ":" changing Safari console formatting)
return true;
});
}
return _consoleMethod.apply(null, a);
}
};
return props;
}, {}));
}());
Then use it like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="console-log.js"></script>
</head>
<body>
<script>
function a () {
console.log('xyz'); // xyz (console-log.html:10)
}
console.info('abc'); // abc (console-log.html:12)
console.log('%cdef', "color:red;"); // (IN RED:) // def (console-log.html:13)
a();
console.warn('uuu'); // uuu (console-log.html:15)
console.error('yyy'); // yyy (console-log.html:16)
</script>
</body>
</html>
This works in Firefox, Opera, Safari, Chrome, and IE 10 (not yet tested on IE11 or Edge).
The idea with bind Function.prototype.bind is brilliant. You can also use npm library lines-logger. It shows origin source files:
Create logger anyone once in your project:
var LoggerFactory = require('lines-logger').LoggerFactory;
var loggerFactory = new LoggerFactory();
var logger = loggerFactory.getLoggerColor('global', '#753e01');
Print logs:
logger.log('Hello world!')();
With modern javascript and the use of getters, you could write something like this:
window.Logger = {
debugMode: true,
get info() {
if ( window.Logger.debugMode ) {
return window.console.info.bind( window.console );
} else {
return () => {};
}
}
}
The nice part about it is that you can have both static and computed values printed out, together with correct line numbers. You could even define multiple logger with different settings:
class LoggerClz {
name = null;
debugMode = true;
constructor( name ) { this.name = name; }
get info() {
if ( this.debugMode ) {
return window.console.info.bind( window.console, 'INFO', new Date().getTime(), this.name );
} else {
return () => {};
}
}
}
const Logger1 = new LoggerClz( 'foo' );
const Logger2 = new LoggerClz( 'bar' );
function test() {
Logger1.info( '123' ); // INFO 1644750929128 foo 123 [script.js:18]
Logger2.info( '456' ); // INFO 1644750929128 bar 456 [script.js:19]
}
test();
A little variation is to to have debug() return a function, which is then executed where you need it - debug(message)(); and so properly shows the correct line number and calling script in the console window, while allowing for variations like redirecting as an alert, or saving to file.
var debugmode='console';
var debugloglevel=3;
function debug(msg, type, level) {
if(level && level>=debugloglevel) {
return(function() {});
}
switch(debugmode) {
case 'alert':
return(alert.bind(window, type+": "+msg));
break;
case 'console':
return(console.log.bind(window.console, type+": "+msg));
break;
default:
return (function() {});
}
}
Since it returns a function, that function needs to be executed at the debug line with ();. Secondly, the message is sent to the debug function, rather than into the returned function allowing pre-processing or checking that you might need, such as checking log-level state, making the message more readable, skipping different types, or only reporting items meeting the log level criteria;
debug(message, "serious", 1)();
debug(message, "minor", 4)();
You can use optional chaining to really simplify this. You get full access to the console object with no hacks and concise syntax.
const debug = (true) ? console : null;
debug?.log('test');
debug?.warn('test');
debug?.error('test')
If debug == null, everything after the ? is ignored with no error thrown about inaccessible properties.
const debug = (false) ? console : null;
debug?.error('not this time');
This also lets you use the debug object directly as a conditional for other debug related processes besides logging.
const debug = (true) ? console : null;
let test = false;
function doSomething() {
test = true;
debug?.log('did something');
}
debug && doSomething();
if (debug && test == false) {
debug?.warn('uh-oh');
} else {
debug?.info('perfect');
}
if (!debug) {
// set up production
}
If you want, you can override the various methods with a no-op based on your desired log level.
const debug = (true) ? console : null;
const quiet = true; const noop = ()=>{};
if (debug && quiet) {
debug.info = noop;
debug.warn = noop;
}
debug?.log('test');
debug?.info('ignored in quiet mode');
debug?.warn('me too');
//isDebug controls the entire site.
var isDebug = true;
//debug.js
function debug(msg, level){
var Global = this;
if(!(Global.isDebug && Global.console && Global.console.log)){
return;
}
level = level||'info';
return 'console.log(\'' + level + ': '+ JSON.stringify(msg) + '\')';
}
//main.js
eval(debug('Here is a msg.'));
This will give me info: "Here is a msg." main.js(line:2).
But the extra eval is needed, pity.
I have been looking at this issue myself lately. Needed something very straight forward to control logging, but also to retain line numbers. My solution is not looking as elegant in code, but provides what is needed for me. If one is careful enough with closures and retaining.
I've added a small wrapper to the beginning of the application:
window.log = {
log_level: 5,
d: function (level, cb) {
if (level < this.log_level) {
cb();
}
}
};
So that later I can simply do:
log.d(3, function(){console.log("file loaded: utils.js");});
I've tested it of firefox and crome, and both browsers seem to show console log as intended. If you fill like that, you can always extend the 'd' method and pass other parameters to it, so that it can do some extra logging.
Haven't found any serious drawbacks for my approach yet, except the ugly line in code for logging.
This implementation is based on the selected answer and helps reduce the amount of noise in the error console: https://stackoverflow.com/a/32928812/516126
var Logging = Logging || {};
const LOG_LEVEL_ERROR = 0,
LOG_LEVEL_WARNING = 1,
LOG_LEVEL_INFO = 2,
LOG_LEVEL_DEBUG = 3;
Logging.setLogLevel = function (level) {
const NOOP = function () { }
Logging.logLevel = level;
Logging.debug = (Logging.logLevel >= LOG_LEVEL_DEBUG) ? console.log.bind(window.console) : NOOP;
Logging.info = (Logging.logLevel >= LOG_LEVEL_INFO) ? console.log.bind(window.console) : NOOP;
Logging.warning = (Logging.logLevel >= LOG_LEVEL_WARNING) ? console.log.bind(window.console) : NOOP;
Logging.error = (Logging.logLevel >= LOG_LEVEL_ERROR) ? console.log.bind(window.console) : NOOP;
}
Logging.setLogLevel(LOG_LEVEL_INFO);
Here's my logger function (based on some of the answers). Hope someone can make use of it:
const DEBUG = true;
let log = function ( lvl, msg, fun ) {};
if ( DEBUG === true ) {
log = function ( lvl, msg, fun ) {
const d = new Date();
const timestamp = '[' + d.getHours() + ':' + d.getMinutes() + ':' +
d.getSeconds() + '.' + d.getMilliseconds() + ']';
let stackEntry = new Error().stack.split( '\n' )[2];
if ( stackEntry === 'undefined' || stackEntry === null ) {
stackEntry = new Error().stack.split( '\n' )[1];
}
if ( typeof fun === 'undefined' || fun === null ) {
fun = stackEntry.substring( stackEntry.indexOf( 'at' ) + 3,
stackEntry.lastIndexOf( ' ' ) );
if ( fun === 'undefined' || fun === null || fun.length <= 1 ) {
fun = 'anonymous';
}
}
const idx = stackEntry.lastIndexOf( '/' );
let file;
if ( idx !== -1 ) {
file = stackEntry.substring( idx + 1, stackEntry.length - 1 );
} else {
file = stackEntry.substring( stackEntry.lastIndexOf( '\\' ) + 1,
stackEntry.length - 1 );
}
if ( file === 'undefined' || file === null ) {
file = '<>';
}
const m = timestamp + ' ' + file + '::' + fun + '(): ' + msg;
switch ( lvl ) {
case 'log': console.log( m ); break;
case 'debug': console.log( m ); break;
case 'info': console.info( m ); break;
case 'warn': console.warn( m ); break;
case 'err': console.error( m ); break;
default: console.log( m ); break;
}
};
}
Examples:
log( 'warn', 'log message', 'my_function' );
log( 'info', 'log message' );
For Angular / Typescript console logger with correct line number you can do the following:
example file: console-logger.ts
export class Log {
static green(title: string): (...args: any) => void {
return console.log.bind(console, `%c${title}`, `background: #222; color: #31A821`);
}
static red(title: string): (...args: any) => void {
return console.log.bind(console, `%c${title}`, `background: #222; color: #DA5555`);
}
static blue(title: string): (...args: any) => void {
return console.log.bind(console, `%c${title}`, `background: #222; color: #5560DA`);
}
static purple(title: string): (...args: any) => void {
return console.log.bind(console, `%c${title}`, `background: #222; color: #A955DA`);
}
static yellow(title: string): (...args: any) => void {
return console.log.bind(console, `%c${title}`, `background: #222; color: #EFEC47`);
}
}
Then call it from any other file:
example file: auth.service.ts
import { Log } from 'path to console-logger.ts';
const user = { user: '123' }; // mock data
Log.green('EXAMPLE')();
Log.red('EXAMPLE')(user);
Log.blue('EXAMPLE')(user);
Log.purple('EXAMPLE')(user);
Log.yellow('EXAMPLE')(user);
It will look like this in the console:
Stackblitz example
I found some of the answers to this problem a little too complex for my needs. Here is a simple solution, rendered in Coffeescript. It'a adapted from Brian Grinstead's version here
It assumes the global console object.
# exposes a global 'log' function that preserves line numbering and formatting.
(() ->
methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeStamp', 'trace', 'warn']
noop = () ->
# stub undefined methods.
for m in methods when !console[m]
console[m] = noop
if Function.prototype.bind?
window.log = Function.prototype.bind.call(console.log, console);
else
window.log = () ->
Function.prototype.apply.call(console.log, console, arguments)
)()
Code from http://www.briangrinstead.com/blog/console-log-helper-function:
// Full version of `log` that:
// * Prevents errors on console methods when no console present.
// * Exposes a global 'log' function that preserves line numbering and formatting.
(function () {
var method;
var noop = function () { };
var methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeStamp', 'trace', 'warn'
];
var length = methods.length;
var console = (window.console = window.console || {});
while (length--) {
method = methods[length];
// Only stub undefined methods.
if (!console[method]) {
console[method] = noop;
}
}
if (Function.prototype.bind) {
window.log = Function.prototype.bind.call(console.log, console);
}
else {
window.log = function() {
Function.prototype.apply.call(console.log, console, arguments);
};
}
})();
var a = {b:1};
var d = "test";
log(a, d);
window.line = function () {
var error = new Error(''),
brower = {
ie: !-[1,], // !!window.ActiveXObject || "ActiveXObject" in window
opera: ~window.navigator.userAgent.indexOf("Opera"),
firefox: ~window.navigator.userAgent.indexOf("Firefox"),
chrome: ~window.navigator.userAgent.indexOf("Chrome"),
safari: ~window.navigator.userAgent.indexOf("Safari"), // /^((?!chrome).)*safari/i.test(navigator.userAgent)?
},
todo = function () {
// TODO:
console.error('a new island was found, please told the line()\'s author(roastwind)');
},
line = (function(error, origin){
// line, column, sourceURL
if(error.stack){
var line,
baseStr = '',
stacks = error.stack.split('\n');
stackLength = stacks.length,
isSupport = false;
// mac版本chrome(55.0.2883.95 (64-bit))
if(stackLength == 11 || brower.chrome){
line = stacks[3];
isSupport = true;
// mac版本safari(10.0.1 (12602.2.14.0.7))
}else if(brower.safari){
line = stacks[2];
isSupport = true;
}else{
todo();
}
if(isSupport){
line = ~line.indexOf(origin) ? line.replace(origin, '') : line;
line = ~line.indexOf('/') ? line.substring(line.indexOf('/')+1, line.lastIndexOf(':')) : line;
}
return line;
}else{
todo();
}
return '😭';
})(error, window.location.origin);
return line;
}
window.log = function () {
var _line = window.line.apply(arguments.callee.caller),
args = Array.prototype.slice.call(arguments, 0).concat(['\t\t\t#'+_line]);
window.console.log.apply(window.console, args);
}
log('hello');
here was my solution about this question. when you call the method: log, it will print the line number where you print your log
The way I solved it was to create an object, then create a new property on the object using Object.defineProperty() and return the console property, which was then used as the normal function, but now with the extended abilty.
var c = {};
var debugMode = true;
var createConsoleFunction = function(property) {
Object.defineProperty(c, property, {
get: function() {
if(debugMode)
return console[property];
else
return function() {};
}
});
};
Then, to define a property you just do...
createConsoleFunction("warn");
createConsoleFunction("log");
createConsoleFunction("trace");
createConsoleFunction("clear");
createConsoleFunction("error");
createConsoleFunction("info");
And now you can use your function just like
c.error("Error!");
Based on other answers (mainly #arctelix one) I created this for Node ES6, but a quick test showed good results in the browser as well. I'm just passing the other function as a reference.
let debug = () => {};
if (process.argv.includes('-v')) {
debug = console.log;
// debug = console; // For full object access
}
Nothing here really had what I needed so I add my own approach: Overriding the console and reading the original error line from a synthetic Error. The example stores the console warns and errors to console.appTrace, having errors very detailed and verbose; in such way that simple (console.appTrace.join("") tells me all I need from the user session.
Note that this works as of now only on Chrome
(function () {
window.console.appTrace = [];
const defaultError = console.error;
const timestamp = () => {
let ts = new Date(), pad = "000", ms = ts.getMilliseconds().toString();
return ts.toLocaleTimeString("cs-CZ") + "." + pad.substring(0, pad.length - ms.length) + ms + " ";
};
window.console.error = function () {
window.console.appTrace.push("ERROR ",
(new Error().stack.split("at ")[1]).trim(), " ",
timestamp(), ...arguments, "\n");
defaultError.apply(window.console, arguments);
};
const defaultWarn = console.warn;
window.console.warn = function () {
window.console.appTrace.push("WARN ", ...arguments, "\n");
defaultWarn.apply(window.console, arguments);
};
})();
inspired by Get name and line of calling function in node.js and date formatting approach in this thread.
This is what worked for me. It creates a new object with all the functionality of the original console object and retains the console object.
let debugOn=true; // we can set this from the console or from a query parameter, for example (&debugOn=true)
const noop = () => { }; // dummy function.
const debug = Object.create(console); // creates a deep copy of the console object. This retains the console object so we can use it when we need to and our new debug object has all the properties and methods of the console object.
let quiet = false;
debug.log = (debugOn) ? debug.log : noop;
if (debugOn&&quiet) {
debug.info = noop;
debug.warn = noop;
debug.assert = noop;
debug.error = noop;
debug.debug = noop;
}
console.log(`we should see this with debug off or on`);
debug.log(`we should not see this with debugOn=false`);
All the solutions here dance around the real problem -- the debugger should be able to ignore part of the stack to give meaningful lines. VSCode's js debugger can now do this. At the time of this edit, the feature is available via the nightly build of the js-debug extension. See the link in the following paragraph.
I proposed a feature for VSCode's debugger here that ignores the top of the stack that resides in any of the skipFiles file paths of the launch configuration. The property is in the launch.json config of your vscode workspace. So you can create a file/module responsible for wrapping console.log, add it to the skipFiles and the debugger will show the line that called into your skipped file rather than console.log itself.
It is in the nightly build of the js-debug extension. It looks like it could be in the next minor release of visual studio code. I've verified it works using the nightly build. No more hacky workarounds, yay!
let debug = console.log.bind(console);
let error = console.error.bind(console);
debug('debug msg');
error('more important message');
Reading for noobs:
bind (function): https://www.w3schools.com/js/js_function_bind.asp
chrome console (object): https://developer.mozilla.org/en-US/docs/Web/API/console
function: https://www.w3schools.com/js/js_functions.asp
object: https://www.w3schools.com/js/js_objects.asp

How to iterate through file system directories and files using javascript?

I'm using Javascript to write an application that will be used with Phonegap to make an Android application. I'm using the Phonegap File API to read directories and files. The relevant code is shown below:
document.addEventListener("deviceready", onDeviceReady, false);
// PhoneGap is ready
//
function onDeviceReady() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail);
}
function onFileSystemSuccess(fileSystem) {
fileSystem.root.getDirectory("/sdcard", {create: false, exclusive: false}, getDirSuccess, fail);
}
function getDirSuccess(dirEntry) {
// Get a directory reader
var directoryReader = dirEntry.createReader();
// Get a list of all the entries in the directory
directoryReader.readEntries(readerSuccess,fail);
}
var numDirs = 0;
var numFiles = 0;
function readerSuccess(entries) {
var i;
for (i=0; i<entries.length; i++)
{
if(entries[i].isFile === true)
{
numFiles++;
entries[i].file(fileSuccess,fail);
}
else if (entries[i].isDirectory === true)
{
numDirs++;
getDirSuccess(entries[i]);
}
}
}
So as of now, the program works fine. The reader will read the contents of the /sdcard directory..if it encounters a file, it will call fileSuccess (which I've excluded in the code for brevity), and if it encounters another directory, it will call getDirSuccess again. My question is this: How can I know when the entire /sdcard directory is read? I can't think of a good way of accomplishing this without going through the /sdcard directory more than one time. Any ideas are appreciated, and thank you in advance!
+1 on a good question since I have to do this anyway myself. I would use the old setTimeout trick. Once the cancel doesn't occur anymore, you know you are done and can fire your event, but just ensure its only fired once.
Here's what I mean and I've named the variables long simply to be more readable (not my style)...
// create timeout var outside your "readerSuccess" function scope
var readerTimeout = null, millisecondsBetweenReadSuccess = 100;
function readerSuccess(entries) {
var i = 0, len = entries.length;
for (; i < len; i++) {
if (entries[i].isFile) {
numFiles++;
entries[i].file(fileSuccess,fail);
} else if (entries[i].isDirectory) {
numDirs++;
getDirSuccess(entries[i]);
}
if (readerTimeout) {
window.clearTimeout(readerTimeout);
}
}
if (readerTimeout) {
window.clearTimeout(readerTimeout);
}
readerTimeout = window.setTimeout(weAreDone, millisecondsBetweenReadSuccess);
}
// additional event to call when totally done
function weAreDone() {
// do something
}
So the logic in this is you keep cancelling the "weAreDone" function from being called as you are reading through stuff. Not sure if this is the best way or more efficient but it would not result in more than one loop given the appropriate "millisecondsBetweenReadSuccess".
Instead of using a setTimeout, which can fail if you have a very slow device, you can use a counter to see how many callbacks still need to be called. If the counter reaches zero, you're all done :)
This is the recursive code:
var fileSync = new function(){
this.filesystem = null;
this.getFileSystem = function(callback){
var rfs = window.requestFileSystem || window.webkitRequestFileSystem;
rfs(
1// '1' means PERSISTENT
, 0// '0' is about max. storage size: 0==we don't know yet
, function(filesystem){
fileSync.filesystem = filesystem;
callback(filesystem);
}
, function(e){
alert('An error occured while requesting the fileSystem:\n\n'+ e.message);
}
);
}
this.readFilesFromReader = function(reader, callback, recurse, recurseFinishedCallback, recurseCounter)
{
if (recurse && !recurseCounter)
recurseCounter = [1];
reader.readEntries(function(res){
callback(res);
if (recurse)
{
for (var i=0; i<res.length; i++) {
/* only handle directories */
if (res[i].isDirectory == true)
{
recurseCounter[0]++;
fileSync.readFilesFromReader(res[i].createReader(), callback, recurse, recurseFinishedCallback, recurseCounter);
}
}
}
/* W3C specs say: Continue calling readEntries() until an empty array is returned.
* You have to do this because the API might not return all entries in a single call.
* But... Phonegap doesn't seem to accommodate this, and instead always returns the same dir-entries... OMG, an infinite loop is created :-/
*/
//if (res.length)
// fileSync.readFilesFromReader(reader, callback, recurse, recurseFinishedCallback, recurseCounter);
//else
if (recurse && --recurseCounter[0] == 0)
{
recurseFinishedCallback();
}
}
, function(e){
fileSync.onError(e);
if (recurse && --recurseCounter[0] == 0)
recurseFinishedCallback();
});
};
this.onError = function(e){
utils.log('onError in fileSync: ' + JSON.stringify(e));
if (utils.isDebugEnvironment())
alert('onError in fileSync: '+JSON.stringify(e));
}
}
var utils = new function(){
this.log = function(){
for (var i=0;i<arguments.length;i++)
console.log(arguments[i]);
}
this.isDebugEnvironment = function(){ return true }// simplified
}
Example code to test this:
var myFiles = [];
var directoryCount = 0;
window.onerror = function(){ alert('window.onerror=\n\n' + arguments.join('\n')) }
var gotFilesCallback = function(entries)
{
for (var i=0;i<entries.length;i++)
{
if (entries[i].isFile == true)
myFiles.push(entries[i].fullPath)
else
++directoryCount;
}
}
var allDoneCallback = function(){
alert('All files and directories were read.\nWe found '+myFiles.length+' files and '+directoryCount+' directories, shown on-screen now...');
var div = document.createElement('div');
div.innerHTML = '<div style="border: 1px solid red; position: absolute;top:10px;left:10%;width:80%; background: #eee;">'
+ '<b>Filesystem root:</b><i>' + fileSync.filesystem.root.fullPath + '</i><br><br>'
+ myFiles.join('<br>').split(fileSync.filesystem.root.fullPath).join('')
+ '</div>';
document.body.appendChild(div);
}
/* on-device-ready / on-load, get the filesystem, and start reading files */
var docReadyEvent = window.cordova ? 'deviceready':'load';
document.addEventListener(docReadyEvent, function()
{
fileSync.getFileSystem(function(filesystem){
var rootDirReader = filesystem.root.createReader();
fileSync.readFilesFromReader(rootDirReader, gotFilesCallback, true, allDoneCallback);
})
}, false);

Create Firefox Addon to Watch and modify XHR requests & reponses

Update: I guess the subject gave a wrong notion that I'm looking for an existing addon. This is a custom problem and I do NOT want an existing solution.
I wish to WRITE (or more appropriately, modify and existing) Addon.
Here's my requirement:
I want my addon to work for a particular site only
The data on the pages are encoded using a 2 way hash
A good deal of info is loaded by XHR requests, and sometimes
displayed in animated bubbles etc.
The current version of my addon parses the page via XPath
expressions, decodes the data, and replaces them
The issue comes in with those bubblified boxes that are displayed
on mouse-over event
Thus, I realized that it might be a good idea to create an XHR
bridge that could listen to all the data and decode/encode on the fly
After a couple of searches, I came across nsITraceableInterface[1][2][3]
Just wanted to know if I am on the correct path. If "yes", then kindly
provide any extra pointers and suggestions that may be appropriate;
and if "No", then.. well, please help with correct pointers :)
Thanks,
Bipin.
[1]. https://developer.mozilla.org/en/NsITraceableChannel
[2]. http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
[3]. http://www.ashita.org/howto-xhr-listening-by-a-firefox-addon/
nsITraceableChannel is indeed the way to go here. the blog posts by Jan Odvarko (softwareishard.com) and myself (ashita.org) show how to do this. You may also want to see http://www.ashita.org/implementing-an-xpcom-firefox-interface-and-creating-observers/, however it isn't really necessary to do this in an XPCOM component.
The steps are basically:
Create Object prototype implementing nsITraceableChannel; and create observer to listen to http-on-modify-request and http-on-examine-response
register observer
observer listening to the two request types adds our nsITraceableChannel object into the chain of listeners and make sure that our nsITC knows who is next in the chain
nsITC object provides three callbacks and each will be called at the appropriate stage: onStartRequest, onDataAvailable, and onStopRequest
in each of the callbacks above, our nsITC object must pass on the data to the next item in the chain
Below is actual code from a site-specific add-on I wrote that behaves very similarly to yours from what I can tell.
function TracingListener() {
//this.receivedData = [];
}
TracingListener.prototype =
{
originalListener: null,
receivedData: null, // array for incoming data.
onDataAvailable: function(request, context, inputStream, offset, count)
{
var binaryInputStream = CCIN("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
var storageStream = CCIN("#mozilla.org/storagestream;1", "nsIStorageStream");
binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
var binaryOutputStream = CCIN("#mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream");
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Copy received data as they come.
var data = binaryInputStream.readBytes(count);
//var data = inputStream.readBytes(count);
this.receivedData.push(data);
binaryOutputStream.writeBytes(data, count);
this.originalListener.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count);
},
onStartRequest: function(request, context) {
this.receivedData = [];
this.originalListener.onStartRequest(request, context);
},
onStopRequest: function(request, context, statusCode)
{
try
{
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0)
{
var data = null;
if (request.requestMethod.toLowerCase() == "post")
{
var postText = this.readPostTextFromRequest(request, context);
if (postText)
data = ((String)(postText)).parseQuery();
}
var date = Date.parse(request.getResponseHeader("Date"));
var responseSource = this.receivedData.join('');
//fix leading spaces bug
responseSource = responseSource.replace(/^\s+(\S[\s\S]+)/, "$1");
piratequesting.ProcessRawResponse(request.originalURI.spec, responseSource, date, data);
}
}
catch (e)
{
dumpError(e);
}
this.originalListener.onStopRequest(request, context, statusCode);
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
readPostTextFromRequest : function(request, context) {
try
{
var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream;
if (is)
{
var ss = is.QueryInterface(Ci.nsISeekableStream);
var prevOffset;
if (ss)
{
prevOffset = ss.tell();
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
// Seek locks the file so, seek to the beginning only if necko hasn't read it yet,
// since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed).
if (ss && prevOffset == 0)
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
return text;
}
else {
dump("Failed to Query Interface for upload stream.\n");
}
}
catch(exc)
{
dumpError(exc);
}
return null;
},
readFromStream : function(stream, charset, noClose) {
var sis = CCSV("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
sis.setInputStream(stream);
var segments = [];
for (var count = stream.available(); count; count = stream.available())
segments.push(sis.readBytes(count));
if (!noClose)
sis.close();
var text = segments.join("");
return text;
}
}
hRO = {
observe: function(request, aTopic, aData){
try {
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aTopic == "http-on-examine-response") {
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) {
var newListener = new TracingListener();
request.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = request.setNewListener(newListener);
}
}
} catch (e) {
dump("\nhRO error: \n\tMessage: " + e.message + "\n\tFile: " + e.fileName + " line: " + e.lineNumber + "\n");
}
},
QueryInterface: function(aIID){
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
};
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(hRO,
"http-on-examine-response", false);
In the above code, originalListener is the listener we are inserting ourselves before in the chain. It is vital that you keep that info when creating the Tracing Listener and pass on the data in all three callbacks. Otherwise nothing will work (pages won't even load. Firefox itself is last in the chain).
Note: there are some functions called in the code above which are part of the piratequesting add-on, e.g.: parseQuery() and dumpError()
Tamper Data Add-on. See also the How to Use it page
You could try making a Greasemonkey script and overwriting the XMLHttpRequest.
The code would look something like:
function request () {
};
request.prototype.open = function (type, path, block) {
GM_xmlhttpRequest({
method: type,
url: path,
onload: function (response) {
// some code here
}
});
};
unsafeWindow.XMLHttpRequest = request;
Also note that you can turn a GM script into an addon for Firefox.

Categories

Resources