Skip to content

Instantly share code, notes, and snippets.

@c-kick
Last active January 14, 2024 18:24
Show Gist options
  • Star 71 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save c-kick/2d717790aadd3aa86884ee0b07c3119f to your computer and use it in GitHub Desktop.
Save c-kick/2d717790aadd3aa86884ee0b07c3119f to your computer and use it in GitHub Desktop.
NOTE: V2 Released! Seehttps://github.com/c-kick/mobileConsole 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/
/*!
*
* NEW VERSION AT https://github.com/c-kick/mobileConsole
*
* hnl.mobileConsole - javascript mobile console - v1.3.8 - 04/01/2021
* 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
*
* Changelog:
* 1.3.8
* - fixed bug when logging numbers
*
* Original author: @hnldesign
* Further changes, comments: @hnldesign
* Copyright (c) 2014-2020 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
*/
//Polyfills
//Date.now polyfill
if (!Date.now) {
Date.now = function now() {
return new Date().getTime();
};
}
//Array.isArray polyfill
if (typeof Array.isArray === 'undefined') {
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
}
//Array.slice polyfill
if (!Uint8Array.prototype.slice) {
Object.defineProperty(Uint8Array.prototype, 'slice', {
value: function (begin, end)
{
return new Uint8Array(Array.prototype.slice.call(this, begin, end));
}
});
}
//Array.filter polyfill
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
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];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
//Function.bind polyfill
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
//Array.prototype.indexOf polyfill
// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Referentie: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
if (len === 0) {
return -1;
}
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
if (n >= len) {
return -1;
}
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (k in o && o[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
//String.prototype.trim polyfill
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
//Array.prototype.map polyfill
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback/*, thisArg*/) {
var T, A, k;
if (this == null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = arguments[1];
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
// 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));
}
//define console variable
var console = window.console;
var mobileConsole = (function () {
'use strict';
//options and other variable containers
var options = {
overrideAutorun: false, //set this to true to skip mobile-detection and run the console no matter what.
startMinimized: false,
version: '1.3.7',
baseClass: 'mobileConsole_',
animParams: 'all 200ms ease',
browserinfo: {
isMobile: (function (a) {
return (/(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)));
}(navigator.userAgent || navigator.vendor || window.opera)),
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'
},
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, //screen/console-ratio that determines the height of the console (reevaluated on every minimize/maximize).
paddingLeft: 0, //used when grouping, no need to change as it will be reset.
groupDepth: 0, //used when grouping, no need to change as it will be reset.
truncate: 400 //hard limit for large strings. For speed/mem issues with consecutive logging of large strings
},
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
missingMethod = function() { return true; }, //method is not supported on this device's original console, return dummy
originalConsole = {
log: (console && typeof console.log === 'function') ? console.log.bind(console) : missingMethod,
info: (console && typeof console.info === 'function') ? console.info.bind(console) : missingMethod,
dir: (console && typeof console.dir === 'function') ? console.dir.bind(console) : missingMethod,
group: (console && typeof console.group === 'function') ? console.group.bind(console) : missingMethod,
groupEnd: (console && typeof console.groupEnd === 'function') ? console.groupEnd.bind(console) : missingMethod,
warn: (console && typeof console.warn === 'function') ? console.warn.bind(console) : missingMethod,
error: (console && typeof console.error === 'function') ? console.error.bind(console) : missingMethod,
trace: (console && typeof console.trace === 'function') ? console.trace.bind(console) : missingMethod,
clear: (console && typeof console.clear === 'function') ? console.clear.bind(console) : missingMethod
},
// reference variables
mobileConsole, consoleElement, commandLine;
//helpers for all sub functions
function setCSS(el, css) {
var i;
for (i in css) {
if (css.hasOwnProperty(i)) {
el.style[i] = css[i];
}
}
return el;
}
function htmlToString(html) {
var string;
try { string = String(html); } catch(e) { string = JSON.stringify(html); } //this should be done differently, but works for now
return string.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) { 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);
}
// elements
var elements = {
lines: [],
acItems: [],
base: createElem('div', 'base', {
boxSizing: 'border-box',
position: 'fixed',
resize: 'none',
fontSize: '10px',
lineHeight: '10px',
bottom: 0,
top: 'auto',
right: 0,
width: '100%',
zIndex: 20000,
padding: 0,
paddingBottom: options.browserinfo.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: options.browserinfo.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: options.browserinfo.isMobile ? '8px 12px' : '4px 8px'
}),
tdRight : createElem('td', 'table_row_data', {
border: '0 none',
textAlign: 'left',
padding: options.browserinfo.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: options.browserinfo.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: options.browserinfo.isMobile ? '34px' : '28px',
height: options.browserinfo.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: options.browserinfo.isMobile ? '16px' : 'inherit', //prevents ios safari's zoom on focus
fontFamily: 'Consolas, monaco, monospace',
paddingLeft: '25px',
margin: 0,
height: options.browserinfo.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: options.browserinfo.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: options.browserinfo.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 = (typeof 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 = typeof 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 destroyConsole() {
//conan the destroyer. Very basic; just removes the console element. mobileConsole will still 'pipe' console logging
//don't see any reason for now to reverse that.
elements.base.parentNode.removeChild(elements.base);
status.initialized = false;
console.warn(
'--==## Mobile Console DESTROYED ##==--' + '\n' +
'To enable again: reload the page. Tip: use the minimize button instead of closing.'
);
}
function assemble() {
var i = options.methods.length, key;
//add close button
elements.buttons.closer = elements.button.cloneNode(false);
elements.buttons.closer.innerHTML = '&#10005;';
elements.buttons.closer.setAttribute('title', 'Close (destroy) console');
setCSS(elements.buttons.closer, { float: 'right', margin: '0'});
//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(e) {
var button = e.currentTarget || e.srcElement;
var logType = button.innerHTML.toLowerCase();
var elems = elements.lines[logType], i = elems.length;
button.toggled = (typeof 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) { //hide buttons that we don't want
setCSS(elements.buttons[methods[i]], { display: 'none' });
}
}
if (options.browserinfo.evtLstn) {
elements.buttons.toggler.addEventListener('click', toggleHeight, false);
elements.buttons.closer.addEventListener('click', destroyConsole, false);
elements.buttons.clear.addEventListener('click', console.clear, false);
} else {
elements.buttons.toggler.attachEvent('onclick', toggleHeight);
elements.buttons.closer.attachEvent('onclick', destroyConsole);
elements.buttons.clear.attachEvent('onclick', console.clear);
}
}
//init
function init() {
var element = assemble();
docReady(function () {
setBinds();
attach(element);
if (options.startMinimized) {
toggleHeight();
}
});
//expose Public methods and variables
return {
toggleHeight : toggleHeight,
toggleScroll : toggleScroll,
destroy: destroyConsole
};
}
if (!ref) {
ref = init();
}
return ref;
}
//initializes the new console logger
function initConsole() {
//reference
var ref;
//sub helpers
function isEmpty(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return JSON.stringify(obj) === JSON.stringify({});
}
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 prop, output = '';
if (!isElement(object)) {
for (prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
} else if (typeof (object[prop]) === 'object') {
output += prop + ((Array.isArray(object[prop])) ? ': Array(' + object[prop].length + ')' : ': {&hellip;}');
} else if (typeof (object[prop]) === 'function') {
output += 'func: f';
} else {
output += prop + ': <span style="color:#c54300;">"' + object[prop] + '"</span>';
}
output += ', ';
}
return '<em>{' + output.slice(0, -2) + '}</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 = (typeof trace !== 'undefined' && typeof 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 (typeof trace === 'undefined') {
return;
}
trace.reverse(); //reverse order of trace, just like 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 (typeof value === 'string') {
valueColor = '#c54300';
if (value.length > options.truncate) {
value = '"' + String(value).substring(0, options.truncate) + '" [...] <br/><span style="color:#FF0000;text-decoration: underline;">Note: string was truncated to ' + options.truncate + ' chars</span>';
} else {
value = '"' + value + '"';
}
} else if (value === null) {
valueColor = '#808080';
value = 'null';
} else if (typeof value === 'undefined' || value === undefined) {
valueColor = '#808080';
value = 'undefined';
} else if (typeof value === 'object') {
if (isEmpty(value)) {
value = '{}';
} else {
valueColor = '';
//iterate over object to create another table inside
var tempTable = createElem('table', 'stackTraceSubTable', {
border: '0 none',
margin: 0,
display: 'none',
marginLeft: '10px',
marginTop: options.browserinfo.isMobile ? '8px' : '4px',
tableLayout: 'auto',
maxWidth: '100%',
color: '#333333'
}),
wrap = document.createElement('div');
wrap.appendChild(objectToTable(tempTable, value).cloneNode(true));
if (Array.isArray(value)) {
value = 'Array(' + value.length + ')' + wrap.innerHTML;
} else {
value = wrap.innerHTML;
}
}
}
return '<span style="color:' + keyColor + ';">' + key + ':</span> <span style="color:' + valueColor + ';">' + value + '</span>';
}
function objectToTable(table, object) {
var i, tdLeft, tr;
if (isElement(object)){
tdLeft = elements.td.cloneNode(false); tr = elements.tr.cloneNode(false);
tdLeft.innerHTML = htmlToString(object.outerHTML);
tr.appendChild(tdLeft);
table.appendChild(tr);
} else {
for (i in object) {
if (object.hasOwnProperty(i)) {
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(e) {
var button = e.currentTarget || e.srcElement, 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, isHTMLElement,
message = (typeof arguments[1].newMessage !== 'undefined') ? arguments[1].newMessage : undefined,
stackTrace = (typeof arguments[1].newStackTrace !== 'undefined') ? arguments[1].newStackTrace : undefined,
colors = (typeof arguments[1].slice !== 'undefined') ? arguments[1].slice(1) : [];
//if message emtpy or undefined, show empty message-message
if (message === '' || typeof message === 'undefined' || message === undefined) { 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') {
message = isElement(message) ?
htmlToString(message.outerHTML.match(/<(.*?)>/g)[0] + '...' + message.outerHTML.match(/<(.*?)>/g).pop()) : //gets e.g. <div>...</div>
objectToString(message);
} else if (method !== 'assert' && method.indexOf('time') === -1) {
//handle colors
var parts = message.toString().split('%c'); parts.shift();
var l = parts.length;
if (l > 0 && colors.length > 0) {
while(l--) {
parts[l] = '<span style="' + colors[l] + '">' + htmlToString(parts[l]) + '</span>';
}
message = parts.join('');
} else {
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 && typeof 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 (options.browserinfo.isMobile) { alert(e); }
originalConsole.error('mobileConsole generated an error logging this event! (type: ' + typeof message + ')');
originalConsole.error(arguments);
originalConsole.error(e);
//try to re-log it as an error
newConsole('error', e);
}
}
function interceptConsole(method) {
var original = console ? console[method] : missingMethod(), i, stackTraceOrig;
if (!console) { console = {}; } //create empty console if we have no console (IE?)
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') {
try {
elements.table.innerHTML = '';
} catch (e) {
console.log('This browser does not allow clearing tables, the console cannot be cleared.');
return;
}
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);
};
return {
//nothing 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 (typeof e === 'undefined') { return; }
//unset any already hovered elements
var hovered = getFromArrayById('hover').element, target = e.target || e.srcElement, over;
if (typeof hovered !== 'undefined') {
setCSS(hovered, {
color: '',
backgroundColor: ''
}).id = '';
}
if (e.type === 'mouseover') {
status.acHovered = true;
over = true;
} else {
over = false;
}
setCSS(target, {
color: over ? '#FFFFFF' : '',
backgroundColor: over ? 'rgb(66,139,202)' : ''
}).id = over ? 'hover' : '';
}
function toggleAutoComplete(show) {
var hidden = (elements.autocomplete.currentStyle ? elements.autocomplete.currentStyle.display : window.getComputedStyle(elements.autocomplete, null).display) === 'none';
show = (typeof show === 'undefined') ? hidden : show;
setCSS(elements.autocomplete, {display: (show) ? 'inherit' : 'none'});
status.acActive = show;
if (!show) { status.acHovered = false; }
}
function clickAutoComplete(e) {
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; }
var tgt = e.target || e.srcElement;
elements.consoleinput.value = tgt.innerHTML;
elements.consoleinput.focus();
toggleAutoComplete();
}
function autoComplete(command) {
if (command.length < 1) {
toggleAutoComplete(false);
return;
}
var searchString = encodeURI(command), matches, match, row, i, maxAmount = options.browserinfo.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) {
e = e || window.event;
var tgt = e.target || e.srcElement;
if (tgt === elements.consoleinput) {
if ((e.key === 'Enter' || e.keyCode === 13)) { //enter
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; }
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
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; }
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 = typeof hist[history.input.commandIdx] === 'undefined' ? '' : decodeURI(hist[history.input.commandIdx]);
}
}
}
if (e.keyCode === 27 && status.acActive) {
toggleAutoComplete(false);
}
};
document.onkeyup = function (e) {
e = e || window.event;
var tgt = e.target || e.srcElement;
if (tgt === 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() {
assemble();
setBinds();
return {
//nothing to expose
};
}
//return
if (!ref) {
ref = init();
}
return ref;
}
function init() {
if (!status.initialized) {
if (consoleElement && mobileConsole) {
console.error( 'Mobile Console cannot be reconstructed! Reload the page to enable Mobile Console again.' + '\n' +
'Tip: use the minimize button instead of closing.' );
return;
} else {
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' ) + ' ##==--' );
}
//autorun if mobile
if (options.browserinfo.isMobile || options.overrideAutorun) {
init();
}
//expose the mobileConsole's methods
return {
init : init,
about: about,
toggle : toggleHeight,
status : status,
options : options
};
}());
@Chrom69
Copy link

Chrom69 commented Oct 20, 2017

Works great, very easy to use. Thanks!

@ehaab
Copy link

ehaab commented Jan 1, 2018

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

@daveamato
Copy link

Very useful; Great job!

@jkusachi
Copy link

love this! thank you!!

@rayluo
Copy link

rayluo commented Jan 19, 2019

@ehaab, based on some conversation at the end of this page, you can try to explicitly mobileConsole.init() it. That trick helps in my case. YMMV.

@tobenpark
Copy link

thanks!!

@jrDev1
Copy link

jrDev1 commented Jul 16, 2020

Hello,

I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.

Thanks,
jrDev

@c-kick
Copy link
Author

c-kick commented Jul 16, 2020

Hello,

I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.

Thanks,
jrDev

I have no experience with AR.js, but if my script breaks it, I take it that it throws errors you can see/trace on a desktop browser (using the regular console)

@jrDev1
Copy link

jrDev1 commented Jul 17, 2020

Hello,
I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.
Thanks,
jrDev

I have no experience with AR.js, but if my script breaks it, I take it that it throws errors you can see/trace on a desktop browser (using the regular console)

I’ll check and get back to you, I appreciate the reply!

Thanks,
jrDev

@vietnux
Copy link

vietnux commented Aug 28, 2021

it has error when run html on webview

Uncaught SecurityError: Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document. -- From line 281 of about:blank

my code:
`webView = (WebView) findViewById(R.id.webView1);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().getDisplayZoomControls();
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setDatabaseEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
// Create database folder
// String databasePath = webView.getContext().getDir("databases", Context.MODE_PRIVATE).getPath();
// webView.getSettings().setDatabasePath(databasePath);//
// webView.getContext().getPackageName() + "/databases/";
String cache_path = getApplicationContext().getFilesDir().getAbsolutePath() + "/cache";

    webView.getSettings().setAppCacheEnabled(true);
    webView.getSettings().setSaveFormData(true);
    webView.getSettings().setAppCachePath(cache_path);

    fragmentSave = MainActivity.getActivity().getFragmentSave();
    setInitialConfiguration();

    // LOADING THE HTML DOCUMENT
    String js = loadAssetTextAsString("hnl.mobileConsole.js").toString();//loadAssetTextAsString("3DObjectMaker.htm");
    String fragmentHTML = fragmentSave.getTabEditor().getText().toString();//loadAssetTextAsString("3DObjectMaker.htm");
    int enbody = fragmentHTML.indexOf("</body>");
    String resultHTML = "";
    if( enbody >= 0 ) {
        String resultHTML_begin = fragmentHTML.substring( 0, enbody);
        String resultHTML_end = fragmentHTML.substring( enbody);
        resultHTML = resultHTML_begin+"<h1>Sao ko được</h1><script>"+js+ "</script>"+
                "  <script>alert('sbc');  window.onload = function() {  if (!mobileConsole.status.initialized) {" +
                "        mobileConsole.init();" +
                "    } };</script>"
                +resultHTML_end;
    } else {
        resultHTML = fragmentHTML+"<h1>Sao ko được</h1><script>"+js+ "</script>"+
                "  <script>alert('sbc'); window.addEventListener('load', function() {" +
                "if (!mobileConsole.status.initialized) { " +
                "alert('111');" +
                "        mobileConsole.init(); " +
                "    }; " +
                "}, false); </script>";
    }

    webView.loadDataWithBaseURL(null, resultHTML, "text/html", "utf-8", null);`

and AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.DELETE_CACHE_FILES" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

@c-kick
Copy link
Author

c-kick commented Sep 2, 2021

Hi vietnux, thanks for your feedback!

The error you are experiencing is casused by the "Block third-party cookies and site data" setting for the website in question. See: https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document

I'll see if I can write a check for it (to see if sessionstorage is available) some time later, to circumvent the error, but it sounds like an edge-case to me.

@youssefso
Copy link

youssefso commented Sep 16, 2021

hello when i run this script in my webapp i get this error:
Html Webpack Plugin:
Error: /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:284
})('docReady', window);
-----------------^
ReferenceError: window is not defined

  • index.html:284 Object../src/hnl.MobileConsole.js
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:284:18

  • index.html:1878 webpack_require
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1878:41

  • index.html:1850 Object../node_modules/html-webpack-plugin/lib/loader.js!./src/index.html
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1850:34

  • index.html:1878 webpack_require
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1878:41

  • index.html:1888
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1888:18

  • index.html:1889
    /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1889:12

  • node:vm:139 Script.runInContext
    node:vm:139:12

  • index.js:121 HtmlWebpackPlugin.evaluateCompilationResult
    [web_ar_playground]/[html-webpack-plugin]/index.js:121:28

  • index.js:297
    [web_ar_playground]/[html-webpack-plugin]/index.js:297:26

  • async Promise.all

@c-kick
Copy link
Author

c-kick commented Sep 16, 2021

Hi Youssef,

You are probably building a webapp using something like React or NodeJs. Webapps are not browsers, so they dont have a window available. As MobileConsole relies on some window events/methods (like the original console, which is an integral part of window), I'm afraid it just won't work without some rewriting, sorry.

@Heath123
Copy link

Heath123 commented Oct 9, 2021

Really nice but why a gist instead of a repo?

@c-kick
Copy link
Author

c-kick commented Oct 11, 2021

Really nice but why a gist instead of a repo?

Good question. Couple of reasons: 1) no experience setting up a repo 2) no time to handle all issues 3) not time to maintain it

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