Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
hnl.mobileConsole.js - extends JavaScript's console to display a visual console inside the webpage. Very usefull for debugging JS on mobile devices with no real console. Info and demo: http://www.hnldesign.nl/work/code/mobileconsole-javascript-console-for-mobile-devices/
/*!
* hnl.mobileConsole - javascript mobile console - v1.2.6 - 26/10/2016
* Adds html console to webpage. Especially useful for debugging JS on mobile devices.
* Supports 'log', 'trace', 'info', 'warn', 'error', 'group', 'groupEnd', 'table', 'assert', 'clear'
* Inspired by code by jakub fiala (https://gist.github.com/jakubfiala/8fe3461ab6508f46003d)
* Licensed under the MIT license
*
* Original author: @hnldesign
* Further changes, comments: @hnldesign
* Copyright (c) 2014-2016 HN Leussink
* Dual licensed under the MIT and GPL licenses.
*
* Info: http://www.hnldesign.nl/work/code/javascript-mobile-console/
* Demo: http://code.hnldesign.nl/demo/hnl.MobileConsole.html
*/
var console = window.console;
var mobileConsole = (function () {
'use strict';
//stop if there is no console in this browser
if (!console) {
alert('mobileConsole not supported on this browser');
return;
}
//polyfills
if (!Date.now) {
Date.now = function now() {
return new Date().getTime();
};
}
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
// NOTE: Technically this should Object.defineProperty at
// the next index, as push can be affected by
// properties on Object.prototype and Array.prototype.
// But that method's new, and collisions should be
// rare, so use the more-compatible alternative.
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
//options and other variable containers
var options = {
overrideAutorun: false,
version : '1.2.6',
baseClass : 'mobileConsole_',
animParams: 'all 200ms ease',
browserinfo: {
browserChrome: /chrome/.test(navigator.userAgent.toLowerCase()),
ffox: /firefox/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()),
safari: /safari/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()),
trident: /trident/.test(navigator.userAgent.toLowerCase()),
evtLstn: typeof window.addEventListener === 'function',
isCrap: document.querySelectorAll === undefined
},
methods : ['log', 'trace', 'info', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd', 'clear'],
hideButtons : ['group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd'],
ratio: 0.4,
paddingLeft: 0,
groupDepth: 0
},
messages = {
clear : 'Console was cleared',
empty: '(Empty string)'
},
status = {
initialized: false,
acActive : false,
acHovered : false,
acInput : '',
timers : {}
},
history = {
output : {
prevMsg : '',
prevMethod : '',
counter : 0
},
input : {
commands : window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')) : []) : [],
commandIdx: window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')).length : 0) : 0,
acIdx: 0,
acHovered: false
}
},
//'backup' original console for reference & internal debugging
originalConsole = {
log: (typeof console.log === 'function') ? console.log.bind(console) : null,
info: (typeof console.info === 'function') ? console.info.bind(console) : null,
dir: (typeof console.dir === 'function') ? console.dir.bind(console) : null,
group: (typeof console.group === 'function') ? console.group.bind(console) : null,
groupEnd: (typeof console.groupEnd === 'function') ? console.groupEnd.bind(console) : null,
warn: (typeof console.warn === 'function') ? console.warn.bind(console) : null,
error: (typeof console.error === 'function') ? console.error.bind(console) : null,
trace: (typeof console.trace === 'function') ? console.trace.bind(console) : null,
clear: (typeof console.clear === 'function') ? console.clear.bind(console) : null
},
// reference variables
mobileConsole, consoleElement, commandLine;
if(options.browserinfo.isCrap) {
console.error(
'--==## Error: Browser not supported by Mobile Console ##==--' + '\n' +
'MobileConsole v' + options.version + ', running on ' + navigator.userAgent.toLowerCase()
);
return false;
}
//helpers for all sub functions
function isMobile() {
var check = false;
(function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
check = true;
}
}(navigator.userAgent || navigator.vendor || window.opera));
return check;
}
function setCSS(el, css) {
var i;
for (i in css) {
if (css.hasOwnProperty(i)) {
el.style[i] = css[i];
}
}
return el;
}
function htmlToString(html) {
return String(html).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/ /g, '\u00a0').replace(/(?:\r\n|\r|\n)/g, '<br />').trim();
}
function createElem(type, className, css) {
if (!type || typeof setCSS !== 'function') { return; }
var element = setCSS(document.createElement(type), css);
if (className) { element.className = options.baseClass + className; }
return setCSS(element, css);
}
function storeCommand(command) {
if (history) {
history.input.commands.push(encodeURI(command.trim()));
history.input.commandIdx = history.input.commands.length;
if (window.sessionStorage) { sessionStorage.setItem('mobileConsoleCommandHistory', JSON.stringify(history.input.commands)); }
}
}
function valBetween(val, min, max) {
return (Math.min(max, Math.max(min, val)));
}
function getMaxHeight() {
return valBetween(Math.floor((window.innerHeight || document.documentElement.clientHeight) * options.ratio), 55, 300);
}
function getClass(item) {
var returnVal = '';
if (item && item.constructor) {
returnVal = item.constructor.name;
} else {
returnVal = Object.prototype.toString.call(item);
}
return String(returnVal);
}
// DocReady - Fires supplied function when document is ready
if (typeof 'docReady' !== 'function') {
(function (funcName, baseObj) {
// The public function name defaults to window.docReady
// but you can pass in your own object and own function name and those will be used
// if you want to put them in a different namespace
funcName = funcName || 'docReady';
baseObj = baseObj || window;
var i, len, readyList = [], readyFired = false, readyEventHandlersInstalled = false;
// call this when the document is ready
// this function protects itself against being called more than once
function ready() {
if (!readyFired) {
// this must be set to true before we start calling callbacks
readyFired = true;
for (i = 0, len = readyList.length; i < len; i = i + 1) {
// if a callback here happens to add new ready handlers,
// the docReady() function will see that it already fired
// and will schedule the callback to run right after
// this event loop finishes so all handlers will still execute
// in order and no new ones will be added to the readyList
// while we are processing the list
readyList[i].fn.call(window, readyList[i].ctx);
}
// allow any closures held by these functions to free
readyList = [];
}
}
function readyStateChange() {
if (document.readyState === 'complete') {
ready();
}
}
// This is the one public interface
// docReady(fn, context);
// the context argument is optional - if present, it will be passed
// as an argument to the callback
baseObj[funcName] = function (callback, context) {
// if ready has already fired, then just schedule the callback
// to fire asynchronously, but right away
if (readyFired) {
setTimeout(function () {callback(context); }, 1);
return;
}
// add the function and context to the list
readyList.push({fn: callback, ctx: context});
// if document already ready to go, schedule the ready function to run
if (document.readyState === 'complete') {
setTimeout(ready, 1);
} else if (!readyEventHandlersInstalled) {
// otherwise if we don't have event handlers installed, install them
if (document.addEventListener) {
// first choice is DOMContentLoaded event
document.addEventListener('DOMContentLoaded', ready, false);
// backup is window load event
window.addEventListener('load', ready, false);
} else {
// must be IE
document.attachEvent('onreadystatechange', readyStateChange);
window.attachEvent('onload', ready);
}
readyEventHandlersInstalled = true;
}
};
}('docReady', window));
}
// elements
var elements = {
lines: [],
acItems: [],
base: createElem('div', 'base', {
boxSizing: 'border-box',
position: 'fixed',
resize: 'none',
fontSize: '12px',
lineHeight: '14px',
bottom: 0,
top: 'auto',
right: 0,
width: '100%',
zIndex: 10000,
padding: 0,
paddingBottom: isMobile() ? '35px' : '25px',
margin: 0,
border: '0 none',
borderTop: '1px solid #808080',
backgroundColor: '#ffffff'
}),
topbar : createElem('div', 'topbar', {
boxSizing: 'border-box',
position: 'absolute',
height: '28px',
left: 0,
right: 0,
display: 'block',
padding: '0 2px',
overflow: 'hidden',
webkitOverflowScrolling: 'touch',
color: '#444444',
backgroundColor: '#f3f3f3',
border: '0 none',
borderTop: '1px solid #a3a3a3',
borderBottom: '1px solid #a3a3a3',
whiteSpace: 'nowrap',
overflowX: 'auto'
}),
scrollcontainer : createElem('div', 'scroller', {
boxSizing: 'border-box',
border: '0 none',
fontFamily: 'Consolas, monaco, monospace',
position: 'relative',
display: 'block',
height: getMaxHeight() + 'px',
overflow: 'auto',
webkitOverflowScrolling: 'touch',
'-webkit-transition': options.animParams,
'-moz-transition': options.animParams,
'-o-transition': options.animParams,
'transition': options.animParams
}),
table : createElem('table', 'table', {
border: '0 none',
margin: 0,
position: 'relative',
tableLayout: 'auto',
width: '100%',
borderCollapse: 'collapse'
}),
stackTraceTable : createElem('table', 'stackTraceTable', {
border: '0 none',
margin: 0,
display: 'none',
marginLeft: '10px',
marginTop: isMobile() ? '8px' : '4px',
tableLayout: 'auto',
maxWidth: '100%',
color: '#333333'
}),
tr : createElem('tr', 'table_row', {
verticalAlign: 'top'
}),
td : createElem('td', 'table_row', {
border: '0 none',
padding: '2px 4px',
verticalAlign: 'top'
}),
msgContainer : createElem('span', 'msgContainer', {
border: '0 none',
margin: 0,
display: 'inline',
overflow: 'hidden'
}),
tdLeft : createElem('td', 'table_row_data', {
border: '0 none',
textAlign: 'left',
padding: isMobile() ? '8px 12px' : '4px 8px'
}),
tdRight : createElem('td', 'table_row_data', {
border: '0 none',
textAlign: 'left',
padding: isMobile() ? '8px 12px' : '4px 8px',
whiteSpace: 'nowrap',
overflow: 'hidden'
}),
link : createElem('a', 'link', {
color: '#1155cc',
textDecoration: 'underline'
}),
dot : createElem('div', 'table_row_data_dot', {
display: 'inline',
borderRadius: '50%',
fontSize: '80%',
fontWeight: 'bold',
padding: '2px 5px',
textAlign: 'center',
marginRight: '5px',
backgroundColor: '#333333',
color: '#ffffff'
}),
button : createElem('button', 'button', {
display: 'inline-block',
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
fontWeight: 'normal',
textTransform: 'capitalize',
fontSize: '12px',
lineHeight: '26px',
height: '26px',
padding: '0 8px',
margin: 0,
textAlign: 'center',
marginRight: '5px',
border: '0 none',
backgroundColor: 'transparent',
color: 'inherit',
cursor: 'pointer'
}),
buttons : {
},
input : createElem('div', 'input', {
boxSizing: 'border-box',
height: isMobile() ? '35px' : '29px',
fontFamily: 'Consolas, monaco, monospace',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
margin: 0,
border: '0 none',
borderTop: '1px solid #EEEEEE'
}),
gt : createElem('DIV', 'gt', {
position: 'absolute',
bottom: 0,
width: '25px',
lineHeight: isMobile() ? '34px' : '28px',
height: isMobile() ? '34px' : '28px',
textAlign: 'center',
fontSize: '16px',
fontFamily: 'Consolas, monaco, monospace',
fontWeight: 'bold',
color: '#3577B1',
zIndex: 2
}),
consoleinput : createElem('input', 'consoleinput', {
boxSizing: 'border-box',
position: 'absolute',
bottom: 0,
width : '100%',
fontSize: isMobile() ? '16px' : 'inherit', //prevents ios safari's zoom on focus
fontFamily: 'Consolas, monaco, monospace',
paddingLeft: '25px',
margin: 0,
height: isMobile() ? '35px' : '25px',
border: '0 none',
outline: 'none',
outlineWidth: 0,
boxShadow: 'none',
'-moz-appearance': 'none',
'-webkit-appearance': 'none',
backgroundColor: 'transparent',
color: '#000000',
zIndex: 1
}),
autocomplete : createElem('div', 'autocomplete', {
display: 'none',
position: 'absolute',
bottom: isMobile() ? '35px' : '28px',
left: 0,
boxShadow: '1px 2px 5px rgba(0,0,0,0.1)',
color: '#000000',
backgroundColor: '#FFFFFF',
border: '1px solid #b5b5b5'
}),
autocompleteItem : createElem('a', 'autocompleteitem', {
display: 'block',
textDecoration: 'none',
fontSize: isMobile() ? '16px' : 'inherit',
padding: '5px 8px',
wordWrap: 'break-word',
whiteSpace: 'nowrap'
}),
arrowUp: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAD///93d3eMZ/YKAAAAAnRSTlMAAHaTzTgAAAAdSURBVHgBY2CEAAYgYAJiEMXEBKHADCYIgKmD0QAFdAA2OHJXEwAAAABJRU5ErkJggg==">',
arrowDown: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAG0lEQVR42mNgwAfKy8v/48I4FeA0AacVDFQBAP9wJkE/KhUMAAAAAElFTkSuQmCC">',
arrowRight: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAJUlEQVR42mNgAILy8vL/DLgASBKnApgkVgXIkhgKiNKJ005s4gDLbCZBiSxfygAAAABJRU5ErkJggg==">'
};
//shared functions
var setLineStyle = (function () {
var lineStyles = function (style) {
switch (style) {
case 'log':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#000000'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#8097bd'
}
};
case 'info':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#1f3dc4'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#367AB4'
}
};
case 'warn':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#CE8724',
backgroundColor : '#fff6e0'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#e8a400'
}
};
case 'error':
case 'table':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#FF0000',
backgroundColor : '#ffe5e5'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#FF0000'
}
};
case 'assert':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#FF0000',
backgroundColor : '#ffe5e5'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#FF0000'
}
};
case 'trace':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#000000'
},
dot : {
//will not happen
}
};
case 'time':
case 'timeEnd':
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#0000ff'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#0000ff'
}
};
default:
return {
text : {
borderBottom: '1px solid #DDDDDD',
color: '#000000'
},
dot : {
color: '#FFFFFF',
backgroundColor: '#8097bd'
}
};
}
};
var color, dot;
return function (element, type, msg) {
if (status.initialized) {
color = (msg === 'undefined' || msg === htmlToString(messages.empty)) ? {color: '#808080'} : ((msg === htmlToString(messages.clear)) ? {color: '#808080', fontStyle: 'italic'} : (lineStyles(type) !== undefined ? lineStyles(type).text : lineStyles.log.text));
dot = lineStyles(type) !== undefined ? lineStyles(type).dot : lineStyles.log.dot;
setCSS(element, color);
//has dot?
if (element.childNodes[0].childNodes[0].className.indexOf('dot') !== -1) {
setCSS(element.childNodes[0].childNodes[0], lineStyles(type).dot);
}
}
};
}()),
getLink = function (href, textString) {
var HTMLurl = elements.link.cloneNode(false);
if (href) {
HTMLurl.setAttribute('href', href);
HTMLurl.setAttribute('target', '_blank');
}
HTMLurl.innerHTML = textString || href.split('\\').pop().split('/').filter(Boolean).pop();
return HTMLurl;
},
toggleHeight = function () {
if (status.initialized) {
var existingPadding = parseInt(document.body.style.paddingBottom, 10) - Math.abs(elements.base.offsetHeight + elements.topbar.offsetHeight);
var newHeight = (elements.base.minimized) ? getMaxHeight() + 'px' : '0px';
setCSS(elements.scrollcontainer, {
height: newHeight
});
setCSS(document.body, {
paddingBottom: existingPadding + Math.abs(parseInt(newHeight, 10) + elements.topbar.offsetHeight) + 'px'
});
elements.buttons.toggler.innerHTML = (elements.base.minimized) ? elements.arrowDown : elements.arrowUp;
elements.buttons.toggler.setAttribute('title', (elements.base.minimized) ? 'Minimize console' : 'Maximize console');
elements.base.minimized = !elements.base.minimized;
return elements.base.minimized;
}
return 'Not built!';
},
about = (function () {
return function () {
console.info(
'--==## Mobile Console ' + (status.initialized ? 'active' : 'inactive') + ' ##==--' + '\n' +
'--===============================--' + '\n' +
'MobileConsole v' + options.version + ', running on ' + navigator.userAgent.toLowerCase()
);
};
}());
// --==** sub functions start here **==--
//initializes the console HTML element
function initConsoleElement() {
//reference
var ref;
//core
function toggleScroll() {
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight;
elements.scrollcontainer.scrollLeft = 0;
}
function assemble() {
var i = options.methods.length, key;
//add buttons
while (i--) {
elements.buttons[options.methods[i]] = elements.button.cloneNode(false);
elements.buttons[options.methods[i]].innerHTML = options.methods[i].charAt(0).toUpperCase() + options.methods[i].slice(1);
elements.buttons[options.methods[i]].setAttribute('title', (options.methods[i] !== 'clear') ? 'Toggle the display of ' + options.methods[i] + ' messages' : 'Clear the console');
}
//add min/maximize button
elements.buttons.toggler = elements.button.cloneNode(false);
elements.buttons.toggler.innerHTML = elements.arrowDown;
elements.buttons.toggler.setAttribute('title', 'Minimize console');
//assemble everything
for (key in elements.buttons) {
if (elements.buttons.hasOwnProperty(key)) {
elements.topbar.insertBefore(elements.buttons[key], elements.topbar.firstChild);
}
}
elements.scrollcontainer.appendChild(elements.table);
elements.base.appendChild(elements.topbar);
elements.base.appendChild(elements.scrollcontainer);
status.initialized = true;
return elements.base;
}
function attach(console) {
document.body.appendChild(console);
setCSS(elements.topbar, {
top: -Math.abs(elements.topbar.offsetHeight) + 'px'
});
var existingPadding = isNaN(parseInt(document.body.style.paddingBottom, 10)) ? 0 : parseInt(document.body.style.paddingBottom, 10);
setCSS(document.body, {
paddingBottom: existingPadding + Math.abs(console.offsetHeight + elements.topbar.offsetHeight) + 'px'
});
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight;
return elements.base;
}
function toggleLogType() {
//togglelogtype is a click handler; 'this' is the button that was clicked
var button = this;
var logType = button.innerHTML.toLowerCase();
var elems = elements.lines[logType], i = elems.length;
button.toggled = (button.toggled === undefined) ? true : !button.toggled;
setCSS(button, { opacity: (button.toggled) ? '0.5' : '' });
while (i--) {
setCSS(elems[i], { display: (button.toggled) ? 'none' : '' });
}
toggleScroll();
button.blur();
return button;
}
function setBinds() {
var methods = options.methods, i = methods.length;
while (i--) {
if (methods[i] !== 'clear') {
if (options.browserinfo.evtLstn) {
elements.buttons[methods[i]].addEventListener('click', toggleLogType, false);
} else {
elements.buttons[methods[i]].attachEvent('onclick', toggleLogType);
}
}
if (options.hideButtons.indexOf(methods[i]) !== -1) {
setCSS(elements.buttons[methods[i]], { display: 'none' });
}
}
if (options.browserinfo.evtLstn) {
elements.buttons.toggler.addEventListener('click', toggleHeight, false);
elements.buttons.clear.addEventListener('click', console.clear, false);
} else {
elements.buttons.toggler.attachEvent('onclick', toggleHeight);
elements.buttons.clear.attachEvent('onclick', console.clear);
}
}
//init
function init() {
var element = assemble();
docReady(function () {
setBinds();
attach(element);
});
//expose Public methods and variables
return {
toggleHeight : toggleHeight,
toggleScroll : toggleScroll
};
}
if (!ref) {
ref = init();
}
return ref;
}
//initializes the new console logger
function initConsole() {
//reference
var ref;
//sub helpers
function isElement(o) {
return (
typeof HTMLElement === 'object' ? o instanceof HTMLElement : //DOM2
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
);
}
function objectToString(object) {
var simpleObject = {}, prop, classname = getClass(object);
if (!isElement(object)) {
for (prop in object) {
if (!object.hasOwnProperty(prop) || (typeof (object[prop]) === 'object') || (typeof (object[prop]) === 'function')) {
continue;
}
simpleObject[prop] = object[prop];
}
return '<em>' + classname + ' ' + JSON.stringify(simpleObject) + '</em>'; // returns cleaned up JSON
}
return htmlToString(object.outerHTML);
}
function urlFromString(string) {
string = String(string);
//searches for url in string, returns url as string
var match, uriPattern = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
try {
match = string.match(uriPattern)[0];
return match;
} catch (e) {
return '';
}
}
function filterOut(array, match) {
return array.filter(function(item){
return typeof item === 'string' && item.indexOf(match) === -1;
});
}
function preFilterTrace(array) {
var newArray = array.split('\n').filter(Boolean), //filter cleans out empty values
isCommandLine = false, stealthThese, i;
if (newArray[0].indexOf('http') === -1) { newArray.shift(); } //remove first line if contains no 'http' (Chrome starts with 'Error', Firefox doesn't..)
if (newArray[0].indexOf('console.') !== -1 || newArray[0].indexOf('console[method]') !== -1) { newArray.shift(); }
if (newArray.length > 0) {
isCommandLine = newArray[newArray.length - 1].indexOf('keydown') !== -1;
newArray = newArray.filter(function(item){ return item !== ''; });
if (isCommandLine) {
stealthThese = ['submitCommand', 'eval', 'setBinds', 'interceptConsole', 'newConsole'];
newArray.pop(); //remove last index, as it is the keydown event.
i = stealthThese.length;
while(i--) {
newArray = filterOut(newArray, stealthThese[i]);
}
}
}
if (isCommandLine || newArray.length === 0) {
newArray.push('(anonymous function) console:1:1');
}
return newArray;
}
//core
function formatStackTrace(trace, origtrace) {
var callStack = [];
//original stack is hidden inside trace object, if specified
var stackTraceOrig = (trace !== undefined && trace[4] !== undefined) ? trace[4].stack : undefined;
//if the first line contains this, skip it. Meant for browsers that begin the stack with the error message itself (already captured before formatStackTrace)
var traceToProcess = (origtrace && origtrace !== '') ? origtrace : stackTraceOrig,
i,
lines,
url,
txt,
thisLine,
lineAndColumn,
caller,
separator = options.browserinfo.ffox ? '@' : '()';
//stop if no source trace can be determined
if (!traceToProcess) { return; }
lines = preFilterTrace(traceToProcess); //pre filters all lines by filtering out all mobileConsole's own methods so mobileConsole runs Stealth and unobtrusive
i = lines.length;
while (i--) {
thisLine = lines[i].trim();
lineAndColumn = thisLine.match(/(?::)(\d+)(?::)(\d+)/);
url = urlFromString(thisLine).replace(lineAndColumn[0], '').split('#')[0] || '';
caller = htmlToString(thisLine.replace(urlFromString(thisLine), '').replace(separator, '').replace('at ', '').trim());
if (caller === '' || caller === lineAndColumn[0]) { continue; }
if (url[url.length - 1] === '/') {
txt = '(index)';
} else {
txt = url.split('\\').pop().split('/').filter(Boolean).pop() || caller;
}
callStack.push({
caller: caller,
url: url ? url.split(':')[0] + ':' + url.split(':')[1] : caller,
linkText: txt + lineAndColumn[0],
line: lineAndColumn[1],
col: lineAndColumn[2],
originalLine: thisLine
});
}
return callStack;
}
function traceToTable(table, trace) {
var i, tdLeft, tdRight, tr;
if (trace === undefined) {
return;
}
trace.reverse(); //reverse order of trace, as it is in a browser's console
i = trace.length;
while (i--) {
tdLeft = elements.td.cloneNode(false);
tdRight = elements.td.cloneNode(false);
tr = elements.tr.cloneNode(false);
tdLeft.innerHTML = trace[i].caller;
tdRight.innerHTML = '&nbsp;@&nbsp;';
tdRight.appendChild(getLink((trace[i].url || ''), trace[i].linkText));
tr.appendChild(tdLeft);
tr.appendChild(tdRight);
table.insertBefore(tr, table.firstChild);
}
return table;
}
function colorizeData(key, value) {
var valueColor = '#3c53da', keyColor = '#ae33b7', classname = getClass(value);
if (value && classname.indexOf('HTML') !== -1) {
value = htmlToString(value.outerHTML);
valueColor = '#ad8200';
} else if (key === 'innerHTML' || key === 'outerHTML') {
value = htmlToString(value);
valueColor = '#ad8200';
}
if (value === null) {
valueColor = '#808080';
}
if (typeof value === 'string') {
valueColor = '#c54300';
//HARD limit, for speed/mem issues with consecutive logging of large strings
if (value.length > 400) {
value = '"' + String(value).substring(0, 400) + '" [...] <br/><span style="color:#FF0000;text-decoration: underline;">Note: string was truncated to 400 chars</span>';
} else {
value = '"' + value + '"';
}
}
return '<span style="color:' + keyColor + ';">' + key + ':</span> <span style="color:' + valueColor + ';">' + value + '</span>';
}
function objectToTable(table, object) {
var i;
for (i in object) {
var tdLeft = elements.td.cloneNode(false), tr = elements.tr.cloneNode(false);
tdLeft.innerHTML = colorizeData(i, object[i]);
tr.appendChild(tdLeft);
table.appendChild(tr);
}
return table;
}
function toggleDetails() {
//toggleDetails is a click handler; 'this' is the button that was clicked
var button = this, i, hidden;
if (button.getAttribute('toggles') === 'table') {
var tables = button.parentElement.getElementsByTagName('table');
i = tables.length;
while (i--) {
hidden = (tables[i].currentStyle ? tables[i].currentStyle.display : window.getComputedStyle(tables[i], null).display) === 'none';
button.innerHTML = button.innerHTML.replace((hidden ? elements.arrowRight : elements.arrowDown), (hidden ? elements.arrowDown : elements.arrowRight));
setCSS(tables[i], { display: hidden ? 'table' : 'none' });
}
}
}
function isRepeat(message, method) {
return (history.output.prevMsg === message && history.output.prevMethod === method) && (typeof message !== 'object') && (method !== 'trace') && (method !== 'group') && (method !== 'groupCollapsed') && (method !== 'groupEnd');
}
function newConsole() {
try {
//get arguments, set vars
var method = arguments[0], className,
message = (arguments[1].newMessage !== undefined) ? arguments[1].newMessage : undefined,
stackTrace = (arguments[1].newStackTrace !== undefined) ? arguments[1].newStackTrace : undefined;
//if message emtpy, show empty message-message
if (message === '') { message = messages.empty; }
if (isRepeat(message, method) && method.indexOf('time') === -1) {
// up the counter and add the dot
history.output.counter = history.output.counter + 1;
elements.table.lastChild.countDot = elements.table.lastChild.countDot || elements.dot.cloneNode(false);
elements.table.lastChild.firstChild.insertBefore(elements.table.lastChild.countDot, elements.table.lastChild.firstChild.firstChild).innerHTML = history.output.counter;
setLineStyle(elements.table.lastChild, method, message);
} else {
history.output.prevMsg = message;
history.output.prevMethod = method;
history.output.counter = 1;
//an object requires some more handling
if (typeof message === 'object' && method !== 'assert' && method !== 'timeEnd') {
className = getClass(message);
if (className.indexOf('HTML') !== -1 && className !== 'HTMLDocument') {
message = htmlToString(message.outerHTML.match(/<(.*?)>/g)[0] + '...' + message.outerHTML.match(/<(.*?)>/g).pop()); //gets first and last tag, adds '...' in middle. e.g. <div>...</div>
} else {
message = objectToString(message);
}
} else if (method !== 'assert' && method.indexOf('time') === -1) {
message = htmlToString(message);
}
var detailTable,
stackTable,
msgContainer = elements.msgContainer.cloneNode(false),
lineContainer = elements.tr.cloneNode(false),
leftContainer = elements.tdLeft.cloneNode(true),
rightContainer = elements.tdRight.cloneNode(false),
arrows = stackTrace ? elements.arrowRight + '&nbsp;' : '';
switch (method) {
case 'assert':
if (message[0] === false) {
msgContainer.innerHTML = arrows + 'Assertion failed: ' + message[1];
}
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace);
method = 'error'; //groups it under 'error' and is thus toggleable in view
break;
case 'log':
case 'debug':
case 'info':
case 'warn':
if (typeof arguments[1].newMessage === 'object') {
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage);
msgContainer.innerHTML = elements.arrowRight + '&nbsp;' + message;
} else {
msgContainer.innerHTML = message;
}
break;
case 'error':
case 'trace':
case 'dir':
case 'table':
//left side
if (method === 'table' || typeof arguments[1].newMessage === 'object') {
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage);
msgContainer.innerHTML = elements.arrowRight + '&nbsp;' + message;
} else if (method === 'trace') {
message = 'console.trace()';
msgContainer.innerHTML = arrows + message;
} else {
msgContainer.innerHTML = arrows + message;
}
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace);
break;
case 'group':
case 'groupCollapsed':
case 'groupEnd':
if (method !== 'groupEnd') {
options.groupDepth = options.groupDepth + 1;
msgContainer.innerHTML = '<strong>' + message + '</strong>';
msgContainer.setAttribute('toggles', 'group_' + options.groupDepth);
} else {
options.groupDepth = valBetween(options.groupDepth - 1, 0, 99);
history.output.prevMsg = '';
}
if (options.groupDepth > 0) {
options.paddingLeft = (options.groupDepth * 23) + 'px';
} else {
options.paddingLeft = 0;
}
break;
case 'time':
case 'timeEnd':
var timerName = arguments[1].newMessage || 'default', now, passed;
if (method === 'time') {
status.timers[timerName] = Date.now();
if (typeof arguments[1].original === 'function') {
arguments[1].original.apply(console, arguments[1].originalArguments); //make sure we still call the original console.time to start the browser's console timer
}
return;
}
now = Date.now();
if (!status.timers[timerName]) {
console.warn('Timer "' + timerName + '" does not exist.');
return;
}
passed = now - (status.timers[timerName] || 0);
message = timerName + ': ' + passed + 'ms';
msgContainer.innerHTML = message;
delete status.timers[timerName];
break;
default:
msgContainer.innerHTML = message;
}
if (!msgContainer.innerHTML) { return; }
leftContainer.appendChild(msgContainer);
if (detailTable || stackTable) {
setCSS(msgContainer, {cursor : 'pointer'});
leftContainer.appendChild(detailTable || stackTable);
msgContainer.setAttribute('toggles', 'table');
}
//populate right side
if (stackTrace && stackTrace[stackTrace.length - 1] !== undefined) {
rightContainer.appendChild(setCSS(getLink(stackTrace[0].url, stackTrace[0].linkText), {color: '#808080'}));
}
//add to line
lineContainer.appendChild(leftContainer);
lineContainer.appendChild(rightContainer);
//set colors
setCSS(lineContainer, { display: (elements.buttons[method].toggled ? 'none' : '') });
setLineStyle(lineContainer, method, message);
//set binds
if (options.browserinfo.evtLstn) {
msgContainer.addEventListener('click', toggleDetails, false);
} else {
msgContainer.attachEvent('onclick', toggleDetails);
}
//store the lines in the object corresponding to the method used
elements.lines[method].push(lineContainer);
//handle grouping (group and groupEnd
if (options.paddingLeft !== 0) {
setCSS(leftContainer, {paddingLeft: options.paddingLeft});
setCSS(msgContainer, {borderLeft: '1px solid #808080', paddingLeft: '5px'});
}
//add the line to the table
elements.table.appendChild(lineContainer);
}
//scroll
consoleElement.toggleScroll();
//==========================================================
//make sure we still call the original method, if applicable (not window.onerror)
if (typeof arguments[1].original === 'function') {
arguments[1].original.apply(console, arguments[1].originalArguments);
}
} catch (e) {
//not logging. why? throw error
if (isMobile()) { alert(e); }
originalConsole.error('mobileConsole generated an error logging this event!');
originalConsole.error(arguments);
originalConsole.error(e);
//try to re-log it as an error
newConsole('error', e);
}
}
function interceptConsole(method) {
var original = console[method], i, stackTraceOrig;
console[method] = function () {
var args = Array.prototype.slice.call(arguments);
args.original = original;
args.originalArguments = arguments;
args.newMessage = (method === 'assert') ? [args[0], args[1]] : args[0];
//create an Error and get its stack trace and format it
try { throw new Error(); } catch (e) { stackTraceOrig = e.stack; }
args.newStackTrace = formatStackTrace(args.newStackTrace, stackTraceOrig);
if (method === 'clear') {
elements.table.innerHTML = '';
history.output.prevMethod = '';
i = options.methods.length;
while (i--) {
elements.lines[options.methods[i]] = [];
}
options.groupDepth = 0;
options.paddingLeft = 0;
console.log(messages.clear);
originalConsole.clear();
return;
}
//Handle the new console logging
newConsole(method, args);
};
}
//init
function init() {
//Intercept all original console methods including trace. Register the event type as a line type.
var i = options.methods.length;
while (i--) {
elements.lines[options.methods[i]] = [];
interceptConsole(options.methods[i]);
}
//Bind to window.onerror
window.onerror = function() {
var args = Array.prototype.slice.call(arguments);
args.newMessage = args[0];
args.newStackTrace = formatStackTrace(arguments);
newConsole('error', args);
};
//expose Public methods and variables
return {
//nothing yet to expose
};
}
//return
if (!ref) {
ref = init();
}
return ref;
}
//initialize the console commandline
function initCommandLine() {
//reference
var ref;
//sub helpers
function getFromArrayById(id) {
var pos = elements.acItems.map(function(x) {return x.id; }).indexOf(id);
return {
position: pos,
element: (pos !== -1) ? elements.acItems[pos] : undefined
};
}
function findInArray(array, match) {
return array.filter(function(item, index, self){
return (typeof item === 'string' && item.indexOf(match) > -1) && (index === self.indexOf(item));
});
}
//core
function assemble() {
elements.consoleinput.setAttribute('type', 'text');
elements.consoleinput.setAttribute('autocapitalize', 'off');
elements.consoleinput.setAttribute('autocorrect', 'off');
elements.autocompleteItem.setAttribute('href', '#');
elements.gt.innerHTML = '&gt;';
elements.input.appendChild(elements.gt);
elements.input.appendChild(elements.consoleinput);
elements.input.appendChild(elements.autocomplete);
elements.base.appendChild(elements.input);
return elements.base;
}
function submitCommand(command) {
if (command !== '') {
storeCommand(command);
var result;
try {
result = eval.call(window, command.trim());
console.log.call(window, result);
} catch(e) {
console.error(e.message);
} finally {
elements.consoleinput.value = '';
}
}
}
function hoverAutoComplete(e) {
if (e === undefined) { return; }
//unset any already hovered elements
var hovered = getFromArrayById('hover').element, target = e.target, over;
if (hovered !== undefined) {
setCSS(hovered, {
color: '',
backgroundColor: 'rgba(0, 0, 0, 0)'
}).id = '';
}
if (e.type === 'mouseover') {
status.acHovered = true;
over = true;
} else {
over = false;
}
setCSS(target, {
color: over ? '#FFFFFF' : '',
backgroundColor: over ? 'rgba(66, 139, 202, 1)' : 'rgba(0, 0, 0, 0)'
}).id = over ? 'hover' : '';
}
function toggleAutoComplete(show) {
var hidden = (elements.autocomplete.currentStyle ? elements.autocomplete.currentStyle.display : window.getComputedStyle(elements.autocomplete, null).display) === 'none';
show = (show === undefined) ? hidden : show;
setCSS(elements.autocomplete, {display: (show) ? 'inherit' : 'none'});
status.acActive = show;
if (!show) { status.acHovered = false; }
}
function clickAutoComplete(e) {
e.preventDefault();
elements.consoleinput.value = e.target.innerHTML;
elements.consoleinput.focus();
toggleAutoComplete();
}
function autoComplete(command) {
if (command.length < 1) {
toggleAutoComplete(false);
return;
}
var searchString = encodeURI(command), matches, match, row, i, maxAmount = isMobile() ? 3 : 5;
elements.autocomplete.innerHTML = '';
elements.acItems = [];
matches = findInArray(history.input.commands, searchString);
matches = matches.slice(Math.max(matches.length - maxAmount, 0));
i = matches.length;
while (i--) {
match = decodeURI(matches[i]);
row = elements.autocompleteItem.cloneNode(false);
row.innerHTML = match;
row.onmouseover = hoverAutoComplete;
elements.autocomplete.insertBefore(row, elements.autocomplete.firstChild);
elements.acItems.unshift(row);
}
toggleAutoComplete(matches.length > 0);
}
function setBinds() {
if (options.browserinfo.evtLstn) {
elements.autocomplete.addEventListener('click', clickAutoComplete, false);
} else {
elements.autocomplete.attachEvent('onclick', clickAutoComplete);
}
document.onkeydown = function (e) {
if (e.target === elements.consoleinput) {
if ((e.key === 'Enter' || e.keyCode === 13)) { //enter
e.preventDefault();
if(!status.acHovered) {
submitCommand(elements.consoleinput.value);
} else {
elements.consoleinput.value = getFromArrayById('hover').element.innerHTML;
elements.consoleinput.focus();
}
toggleAutoComplete(false);
status.acInput = '';
} else if ((e.keyCode === 38 || e.keyCode === 40)) { //up and down arrows for history browsing
e.preventDefault();
var up = (e.keyCode === 40);
if(status.acActive) {
//autocomplete window is opened
//get id of currently hovered element
var hovered = getFromArrayById('hover').position;
var counter = (hovered === -1) ? elements.acItems.length : hovered;
//hover new (in- or decreased number) one
counter = valBetween((counter += (up) ? 1 : -1), 0, elements.acItems.length - 1);
hoverAutoComplete({target : elements.acItems[counter], type : 'mouseover'});
} else {
//autocompete window not opened
var hist = history.input.commands;
history.input.commandIdx += (up) ? 1 : -1;
history.input.commandIdx = valBetween(history.input.commandIdx, 0, hist.length);
elements.consoleinput.value = hist[history.input.commandIdx] === undefined ? '' : decodeURI(hist[history.input.commandIdx]);
}
}
}
if (e.keyCode === 27 && status.acActive) {
toggleAutoComplete(false);
}
};
document.onkeyup = function (e) {
if (e.target === elements.consoleinput && status.acInput !== elements.consoleinput.value && (e.keyCode !== 38 && e.keyCode !== 40 && e.keyCode !== 27 && e.key !== 'Enter' && e.keyCode !== 13)) {
status.acInput = elements.consoleinput.value.trim();
autoComplete(elements.consoleinput.value);
}
};
}
//init
function init() {
var element = assemble();
setBinds();
//expose Public methods and variables
return {
//nothing yet to expose
};
}
//return
if (!ref) {
ref = init();
}
return ref;
}
function init() {
if (!status.initialized) {
status.initialized = true;
//populate references
if (!mobileConsole) {
//taps into native console and adds new functionality
mobileConsole = initConsole();
}
if (!consoleElement && mobileConsole) {
//creates the new HTML console element and attaches it to document
consoleElement = initConsoleElement();
}
if (!commandLine && consoleElement && mobileConsole) {
//creates an HTML commandline and attaches it to existing console element
commandLine = initCommandLine();
}
//log a 'welcome' message
console.info( '--==## Mobile Console v' + options.version + ' ' + (status.initialized ? 'active' : 'inactive' ) + ' ##==--' );
} else if (options.browserinfo.isCrap) {
console.error(
'--==## Error: Browser not supported by Mobile Console ##==--' + '\n' +
'--===============================--' + '\n' +
'MobileConsole v' + options.version + ', running on ' + navigator.userAgent.toLowerCase()
);
}
}
//autorun if mobile
if (isMobile() || options.overrideAutorun) {
init();
}
//expose the mobileConsole
return {
init : init,
about: about,
toggle : toggleHeight,
status : status,
options : options
};
}());

Chrom69 commented Oct 20, 2017

Works great, very easy to use. Thanks!

ehaab commented Jan 1, 2018

Could you tell me how to set up this js ?Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment