(function() { /** * @namespace Helpers * Helper/Utility functions. */ var Helpers = { /** * Returns an element to be created in the DOM and adds attributes. * * @param {string} tag Tag you want to created, i.e "div", "span", etc... * @param {object} attrs Attributes you want on the tag, i.e. class="test", src="img.jpg", etc... * @return {HTMLElement} */ create: function(element, attrs) { var ele = document.createElement(element); if (attrs) { for (var attr in attrs) { if (attr === 'html') { ele.innerHTML = attrs[attr]; } else { // IE does not support support setting class name with set attribute ([attr] == 'class') ? ele.className = attrs[attr] : ele.setAttribute([attr], attrs[attr]); } } } return ele; }, /** * @function removeClass * Removes a class name from an element. * Thanks to http://blkcreative.com/words/simple-javascript-addclass-removeclass-and-hasclass/ * * @param {HTMLElement} elem - element of class name you want removed. * @param {String} classN - class name to be removed. */ removeClass: function(elem, classN) { var currClass = elem.className.replace(/(^\s+|\s+$)/g, ''); var regex = new RegExp("(^|\\s)" + classN + "(\\s|$)", "g"); elem.className = (currClass.replace(regex, " ")).replace(/(^\s+|\s+$)/g, ''); }, /** * @function hasClass * Returns true or false if an element has a specifc class name. * Thanks to http://blkcreative.com/words/simple-javascript-addclass-removeclass-and-hasclass/ * * @param {HTMLElement} elem - element that may have the class name specified. * @param {String} classN - class name to be checked for. */ hasClass: function(elem, classN) { var regex = new RegExp("(^|\\s)" + classN + "(\\s|$)"); return regex.test(elem.className); }, /** * @function addClass * Adds a class name to a given element. * * @param {HTMLElement} elem - element that you want a class name added to. * @param {String} classN - new class name you want added to element. */ addClass: function(elem, classN) { var currClass = elem.className.replace(/(^\s+|\s+$)/g, ''); var addedClass = (currClass.length === 0) ? classN : currClass + ' ' + classN; if (!this.hasClass(elem, classN)) elem.className = addedClass; }, /** * Returns true or false if we are viewing on a mobile or tablet device. * @return {Boolean} */ isMobile: function() { var devices = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; return (devices.test(navigator.userAgent)) ? true : false; }, /** * Returns an element based on id. * * @param {String} id The id of the element to return. * @return {htmlelement} */ $: function(id) { return document.getElementById(id); }, /** * Returns true/false if a node is an HTML tag. * * @param {object} p The node to test if it is an HTML tag. * @return {Boolean} */ isHtmlEl: function(p) { return ((p.length) ? p[0].nodeType : p.nodeType === 1) ? true : false; }, /** * Returns true or false if the browser is IE8 or IE9 * @credit http://blogs.msdn.com/b/giorgio/archive/2009/04/14/how-to-detect-ie8-using-javascript-client-side.aspx * @return {Boolean} */ isIE8or9: function() { var rv = -1; // Return value assumes failure. if (navigator.appName == 'Microsoft Internet Explorer') { var ua = navigator.userAgent; var re = new RegExp('MSIE ([0-9]{1,}[\.0-9]{0,})'); if (re.exec(ua) !== null) rv = parseFloat(RegExp.$1); } return (rv === 8 || rv === 9) ? true : false; }, /** * Returns the viewport height of the browser window. * @return {Number} */ getWinH: function() { var winH = 0; var win = window; var doc = document; var docEle = doc.documentElement; if (typeof win.innerHeight != 'undefined') { winH = window.innerHeight; } else if (typeof docEle != 'undefined' && typeof docEle.clientHeight != 'undefined' && docEle.clientHeight !== 0) { winH = docEle.clientHeight; } else { winH = doc.getElementsByTagName('body')[0].clientHeight; } return winH; }, /** * Returns an Object as a string * Credits go to https://github.com/douglascrockford/JSON-js * Code was modified to fit certain needs. * * @param {Object} obj Object being passed * @return {String} */ ObjString: function(obj) { var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; var gap = ''; var indent = ''; var meta = { "\b": "\\b", " ": "\\t", "\n": "\\n", "\f": "\\f", "\r": "\\r", '"': '\\"', "\\": "\\\\" }; // Returns the primitive value String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function() { return this.valueOf(); }; /** * Returns string with quotes * * @param {String} string - string being passed to surround with quotes. * @return {String} */ var quote = function(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function(a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; }; // Responsible for indentation, set to 4 spaces for (var i = 0; i < 2; i += 1) { indent += ' '; } // Magic behind parsing objects var str = function(key, holder) { var i = 0; // The loop counter. var k = null; // The member key. var v = null; // The member value. var length; var mind = gap; var partial; var value = holder[key]; if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': case 'function': return String(value); case 'object': if (!value) { return 'null'; } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { /* Removing quotes from key partial.push(quote(k) + (gap ? ': ' : ':') + v);*/ partial.push(k + (gap ? ': ' : ':') + v); } } } v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } }; var j = str('', { '': obj }); return j.replace(/\n/g, '
').replace(/ /g, ' '); } }; /** * @namespace CL * Functionality used for the custom console. * */ var CL = { show: true, height: 0, synColor: { err: '#FF0000', _null: '#808080', obj: '#881391', str: '#C41A16', numBoo: '#1C00CF', tag: '#881280', tagAttr: '#994500', tagVal: '#1A1AA6', time: '#0080FF' }, textareaVal: '', funcs: { log: function() {'[native code]';}, debug: function() {'[native code]';}, time: function() {'[native code]';}, timeEnd: function() {'[native code]';}, clear: function() {'[native code]';}, error: function() {'[native code]';}, warn: function() {'[native code]';}, assert: function() {'[native code]';}, count: function() {'[native code]';} }, _el: null, _liExec: null, _entries: null, /** * Initializes the custom console functions. */ init: function() { this.setup(); this.insertRules(document.styleSheets[document.styleSheets.length - 1], '.CL{position:fixed;bottom:0;width:100%;left:0;border-top:1px solid #a3a3a3;z-index:2;font-size:12px}* html{height:100%}* html body{margin:0;padding:0;height:100%;zoom:1}* html #customconsole{position:absolute;right:auto;bottom:auto;top:expression((0 - customconsole.offsetHeight + (document.documentElement.clientHeight ? document.documentElement.clientHeight:document.body.clientHeight) + (ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop)) + "px")}.CL-header{overflow:auto;background:#ececec;border-bottom:1px solid #a3a3a3;*height:32px}.CL-header,.CL-title{font-family:Lucida Grande;font-size:12px}.CL-title{margin:0 0 0 10px;line-height:15px;border:1px solid #a3a3a3;border-bottom:0;float:left;background:#fff;padding:5px 8px 6px;font-weight:400;*margin:0 0 0 5px}.CL-tog{color:#333;display:block;text-decoration:none;outline:none;padding:4px 0;text-align:right;font-family:Lucida Grande;font-size:12px}.CL-togtxt{background:#a3a3a3;color:#fff;font-size:11px;padding:4px;margin-right:4px;display:inline-block}.CL-menu{background:#fff;overflow:auto;border-bottom:1px solid #e6e6e6;*height:31px}.CL-label{float:left;font-size:11px;padding:4px 0 4px 8px;margin:4px 0;text-transform:uppercase;border-left:1px solid #a3a3a3}.CL-clear{color:#333;display:block;text-decoration:none;outline:none;padding:8px 10px;color:#666;float:left}.CL-inp{width:23px;padding:2px;margin:4px;float:left;border:0}.CL-rad{margin:8px;border:2px solid #a3a3a3}.CL-entries{background:#fff;overflow:auto;position:relative;margin:0;padding:0;list-style-type:none;font-family:Lucida Grande;font-size:12px;width:100%}.CL-entries.show-timestamps .CL-timest{display:block}.CL-entry{clear:both;position:relative;min-height:16px;list-style-type:none;font-size:11px;z-index:1;border-bottom:1px solid #f0f0f0;overflow:auto;*zoom:1}.CL-entry .CL-timest{display:none}.CL-entry.CL-err{color:red;background:#fff0f0;border-top:1px solid #ffd6d6;border-bottom:1px solid #ffd6d6;margin-top:-1px}.CL-entry.CL-err .CL-sym{color:red}.CL-entry.CL-cleared .CL-sym{display:none}.CL-entry.CL-warn{background:#fffbe6;color:#5c3b00}.CL-entry.CL-exec{overflow:hidden;border-bottom:0}.CL-entry.CL-exec .CL-sym{color:#2d7df9;top:0!important}.CL-entry.CL-exec .CL-entrytxt{overflow:auto;padding-right:0;*zoom:0}.CL-sym{border:0;position:absolute;margin-left:10px;font-family:Arial;font-weight:900;color:#939393;font-size:12px;padding:3px 0;left:0}.CL-sym2{color:#bababa}.CL-entrytxt{margin-left:24px;display:block;padding:4px 22px 4px 0;word-wrap:break-word;position:relative;font-family:Menlo,monospace,Lucida Sans Unicode,Courier,Courier New;font-size:11px;*zoom:1}.CL-entrytxt.CL-timest{padding-right:6px;color:gray;font-size:10px;padding-bottom:0;display:none;float:right}.CL-txtarea{width:76%;float:left;padding:3px;height:30px;border:0;font-family:Menlo,monospace,Lucida Sans Unicode,Courier,Courier New;font-size:11px}.CL-execbtn{color:#333;text-decoration:none;outline:none;display:block;float:right;width:21%;text-align:center;text-transform:uppercase;line-height:38px}'); // Replaced by gulp this.scriptParams(); // Added because IE 8 & 9 does support console.log, just needs to be enabled. if (Helpers.isIE8or9()) { alert('IE 8 & 9 support the console. The developer tools need to be opened for the console to work.'); } this.setHeight(this.height, true); this.toggle(); this.bindEvents(); }, /** * Creates the console markup and appends to the document. */ setup: function() { var style = Helpers.create('style', { 'type': 'text/css' }); this._entries = Helpers.create('ul', { 'class': 'CL-entries' }); this._el = Helpers.create('div', { 'class': 'CL', id: 'customconsole', 'html': '
' + '' + '
Console
' + 'Click to hide' + '
' + '
' + '
' + 'CLEAR' + 'Height:' + '' + '' + '' + '
' }); this._liExec = Helpers.create('li', { 'class': 'CL-entry CL-exec', id: 'CLExecute', 'html': '>' + '' + '' + 'Execute' + '' }); document.getElementsByTagName('head')[0].appendChild(style); this._el.appendChild(this._entries); this._entries.appendChild(this._liExec); document.body.appendChild(this._el); }, override: function() { var result = false; var scriptTags = document.getElementsByTagName('script'); var scTagSrc = ''; var queries = ''; // Loop through script tags on page. for (var i = 0, ii = scriptTags.length; i < ii; i++) { scTagSrc = scriptTags[i].src; // Grab all script tags, if its console or console.min then check for param if (/.*console(\.min)?\.js/gi.test(scTagSrc) && scTagSrc.indexOf('?') > 0) { queries = scTagSrc.substring(scTagSrc.indexOf('?') + 1, scTagSrc.length).split('&'); // Implement queries for (var j = 0, jj = queries.length; j < jj; j++) { if (queries[j] === 'override') { result = true; break; } } } } return result; }, /** * Changes the default settings via query parameter in the script tag. */ scriptParams: function() { var scriptTags = document.getElementsByTagName('script'); var scTagSrc = ''; var queries = ''; var query = ''; var self = this; // Loop through script tags on page. for (var i = 0, ii = scriptTags.length; i < ii; i++) { scTagSrc = scriptTags[i].src; // Grab all script tags, if its console or console.min then check for param if (/.*console(\.min)?\.js/gi.test(scTagSrc) && scTagSrc.indexOf('?') > 0) { queries = scTagSrc.substring(scTagSrc.indexOf('?') + 1, scTagSrc.length).split('&'); // Implement queries for (var j = 0, jj = queries.length; j < jj; j++) { query = queries[j]; if (Number(query)) { self.height = query; } else if (query === 'hide') { self.show = false; } else if (query === 'timestamp') { Helpers.$('CLTime').setAttribute('checked', true); Helpers.addClass(self._entries, 'show-timestamps'); } } } } }, /** * Events bound to elements in the custom console. */ bindEvents: function() { var self = this; var textarea = Helpers.$('CLTextarea'); Helpers.$('CLTog').onclick = function() { self.show = self.show ? false : true; self.toggle(); return false; }; Helpers.$('CLHeight').onkeyup = function(e) { self.setHeight(this.value, false); }; Helpers.$('CLClear').onclick = function() { var logs = self._entries.getElementsByTagName('li'); var logsCount = logs.length; if (logsCount !== 1) { while (logsCount--) { if (logs[logsCount].id !== 'CLExecute') { logs[logsCount].parentNode.removeChild(logs[logsCount]); } } } return false; }; Helpers.$('CLTime').onchange = function() { this.checked ? Helpers.addClass(self._entries, 'show-timestamps') : Helpers.removeClass(self._entries, 'show-timestamps'); }; Helpers.$('CLExeBtn').onclick = function() { if (textarea.value !== '') { isExec = true; self.textareaVal = textarea.value; self.executeCode(self.textareaVal); isExec = false; } return false; }; }, /** * WHen executing code from the textarea. * * @param {string} val The textarea value. */ executeCode: function(val) { var reg = /console\.*?.*/g; var reg2 = /console.*?\((.*)\)/; if ((val).match(reg)) { if (val.match(reg2)) { sym = '>'; console.log(val); sym = '<'; console.log(eval(val)); } else { if (val === 'console') { sym = '>'; console.log(val); sym = '<'; console.log(this.funcs); } else { for (var key in this.funcs) { if (val === 'console.' + key) { sym = '>'; console.log(val); sym = '<'; console.log(this.funcs[key]); } } } } } else { sym = '>'; console.log(val); sym = '<'; console.log(eval(val)); } }, /** * Handles the toggling (hiding/showing) of the custom console. */ toggle: function() { var toggleTxt = (this.show) ? 'hide' : 'show'; var toggleElem = Helpers.$('CLTogText'); toggleElem.innerHTML = 'Click to ' + toggleTxt; this._entries.style.display = (this.show) ? 'block' : 'none'; Helpers.$('CLMenu').style.display = (this.show) ? 'block' : 'none'; if (this.show) this.scrl2Btm(); }, /** * Sets the height of the console entries. * * @param {string} h The new height for the entries. * @param {boolean} init Used to determine if height is being set in initialization or via input field. */ setHeight: function(h, init) { var val = Number(h); if (val >= 90 && val <= (Helpers.getWinH() - 150)) { this.height = val; } else { this.height = Helpers.getWinH() / 3; } if (init) { Helpers.$('CLHeight').value = this.height; } this._entries.style.height = this.height + 'px'; this.scrl2Btm(); }, /** * Functionality that occurs when a new entry is added to the console. */ newLog: function() { var textarea = Helpers.$('CLTextarea'); this._entries.appendChild(this._liExec); textarea.value = ''; textarea.focus(); this.scrl2Btm(); isExec = isError = consoleError = consoleAssert = false; entryClass = 'CL-entry'; sym = ''; }, /** * Ensures the entries list is always scrolled to the bottom. */ scrl2Btm: function() { this._entries.scrollTop = this._entries.scrollHeight; }, /** * Returns string with HTML surrounding, used for coloring. * * @param {String} type Type of argument being passed, i.e. number, null, etc.. * @param {String} str String being passed to surround with. * @return {String} */ syntax: function(type, str) { var formattedString = str; var self = this; if (type == 'object') { if (str === null) { formattedString = '' + str + ''; } else { formattedString = str.replace(new RegExp(/(\w+)(\:)/g), '$1$2') // key in object .replace(new RegExp(/( )(-?\d+\D?\d+)|( )(-?\d)|( )(true|false)/g), '$1$3$5$2$4$6') //number or boolean value .replace(new RegExp(/( )(".*?")/g), '$1$2'); // string value } } else if (type == 'html') { var formattedString2 = str.replace(new RegExp(/<(.*?)>/gi), function(x) { // HTML tags return '' + x + ''; }); formattedString = formattedString2.replace(new RegExp(/<(?!\/)(.*?)>/gi), function(y) { // HTML tag attributes var attr = new RegExp(/ (.*?)="(.*?)"/gi); return y.replace(attr, ' $1="$2"'); }); } else if (type == 'number' || type == 'boolean') { formattedString = '' + str + ''; } else if (type === 'undefined') { formattedString = '' + str + ''; } return formattedString; }, /** * Returns HTML as a string. * * @param {HTMLElement/Object} el The element to be parsed. * @return {String} */ printHTML: function(el) { var outputDiv = Helpers.create('div'); var elem = null; var html = ''; var breakLine = null; // Clear each time called. outputDiv.innerHTML = ''; try { elem = el.cloneNode(true); // Needed to use clone because it was removing orginal elements outputDiv.appendChild(elem); } catch (e) { for (var i = 0, ii = el.length; i < ii; i++) { breakLine = document.createTextNode('\n'); elem = el[i].cloneNode(true); outputDiv.appendChild(elem); outputDiv.appendChild(breakLine); } } // Replace entities html = outputDiv.innerHTML.replace(//g, '>'); // Added in for IE html = html.replace(/<\/?([A-Z]+)/g, function(x) { return x.toLowerCase(); }) .replace(/([a-zA-Z]+)=([a-zA-Z]+-?[a-zA-Z]+)/g, '$1="$2"'); return html.replace(/\n/g, '
'); // Add breaking space }, /** * Adds CSS rules into a