/*
* Functionality for the CodeIgniter Debug Toolbar.
*/
var ciDebugBar = {
toolbarContainer : null,
toolbar : null,
icon : null,
//--------------------------------------------------------------------
init : function () {
this.toolbarContainer = document.getElementById('toolbarContainer');
this.toolbar = document.getElementById('debug-bar');
this.icon = document.getElementById('debug-icon');
ciDebugBar.createListeners();
ciDebugBar.setToolbarState();
ciDebugBar.setToolbarPosition();
ciDebugBar.setToolbarTheme();
ciDebugBar.toggleViewsHints();
ciDebugBar.routerLink();
document.getElementById('debug-bar-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
// Allows to highlight the row of the current history request
var btn = document.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]');
ciDebugBar.addClass(btn.parentNode.parentNode, 'current');
historyLoad = document.getElementsByClassName('ci-history-load');
for (var i = 0; i < historyLoad.length; i++)
{
historyLoad[i].addEventListener('click', function () {
loadDoc(this.getAttribute('data-time'));
}, true);
}
// Display the active Tab on page load
var tab = ciDebugBar.readCookie('debug-bar-tab');
if (document.getElementById(tab))
{
var el = document.getElementById(tab);
el.style.display = 'block';
ciDebugBar.addClass(el, 'active');
tab = document.querySelector('[data-tab=' + tab + ']');
if (tab)
{
ciDebugBar.addClass(tab.parentNode, 'active');
}
}
},
//--------------------------------------------------------------------
createListeners : function () {
var buttons = [].slice.call(document.querySelectorAll('#debug-bar .ci-label a'));
for (var i = 0; i < buttons.length; i++)
{
buttons[i].addEventListener('click', ciDebugBar.showTab, true);
}
},
//--------------------------------------------------------------------
showTab: function () {
// Get the target tab, if any
var tab = document.getElementById(this.getAttribute('data-tab'));
// If the label have not a tab stops here
if (! tab)
{
return;
}
// Remove debug-bar-tab cookie
ciDebugBar.createCookie('debug-bar-tab', '', -1);
// Check our current state.
var state = tab.style.display;
// Hide all tabs
var tabs = document.querySelectorAll('#debug-bar .tab');
for (var i = 0; i < tabs.length; i++)
{
tabs[i].style.display = 'none';
}
// Mark all labels as inactive
var labels = document.querySelectorAll('#debug-bar .ci-label');
for (var i = 0; i < labels.length; i++)
{
ciDebugBar.removeClass(labels[i], 'active');
}
// Show/hide the selected tab
if (state != 'block')
{
tab.style.display = 'block';
ciDebugBar.addClass(this.parentNode, 'active');
// Create debug-bar-tab cookie to persistent state
ciDebugBar.createCookie('debug-bar-tab', this.getAttribute('data-tab'), 365);
}
},
//--------------------------------------------------------------------
addClass : function (el, className) {
if (el.classList)
{
el.classList.add(className);
}
else
{
el.className += ' ' + className;
}
},
//--------------------------------------------------------------------
removeClass : function (el, className) {
if (el.classList)
{
el.classList.remove(className);
}
else
{
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
},
//--------------------------------------------------------------------
/**
* Toggle display of a data table
*
* @param obj
*/
toggleDataTable : function (obj) {
if (typeof obj == 'string')
{
obj = document.getElementById(obj + '_table');
}
if (obj)
{
obj.style.display = obj.style.display == 'none' ? 'block' : 'none';
}
},
//--------------------------------------------------------------------
/**
* Toggle tool bar from full to icon and icon to full
*/
toggleToolbar : function () {
var open = ciDebugBar.toolbar.style.display != 'none';
ciDebugBar.icon.style.display = open == true ? 'inline-block' : 'none';
ciDebugBar.toolbar.style.display = open == false ? 'inline-block' : 'none';
// Remember it for other page loads on this site
ciDebugBar.createCookie('debug-bar-state', '', -1);
ciDebugBar.createCookie('debug-bar-state', open == true ? 'minimized' : 'open' , 365);
},
//--------------------------------------------------------------------
/**
* Sets the initial state of the toolbar (open or minimized) when
* the page is first loaded to allow it to remember the state between refreshes.
*/
setToolbarState: function () {
var open = ciDebugBar.readCookie('debug-bar-state');
ciDebugBar.icon.style.display = open != 'open' ? 'inline-block' : 'none';
ciDebugBar.toolbar.style.display = open == 'open' ? 'inline-block' : 'none';
},
//--------------------------------------------------------------------
toggleViewsHints: function () {
// Avoid toggle hints on history requests that are not the initial
if (localStorage.getItem('debugbar-time') != localStorage.getItem('debugbar-time-new'))
{
var a = document.querySelector('a[data-tab="ci-views"]');
a.href = '#';
return;
}
var nodeList = []; // [ Element, NewElement( 1 )/OldElement( 0 ) ]
var sortedComments = [];
var comments = [];
var getComments = function () {
var nodes = [];
var result = [];
var xpathResults = document.evaluate( "//comment()[starts-with(., ' DEBUG-VIEW')]", document, null, XPathResult.ANY_TYPE, null);
var nextNode = xpathResults.iterateNext();
while ( nextNode )
{
nodes.push( nextNode );
nextNode = xpathResults.iterateNext();
}
// sort comment by opening and closing tags
for (var i = 0; i < nodes.length; ++i)
{
// get file path + name to use as key
var path = nodes[i].nodeValue.substring( 18, nodes[i].nodeValue.length - 1 );
if ( nodes[i].nodeValue[12] === 'S' ) // simple check for start comment
{
// create new entry
result[path] = [ nodes[i], null ];
}
else if (result[path])
{
// add to existing entry
result[path][1] = nodes[i];
}
}
return result;
};
// find node that has TargetNode as parentNode
var getParentNode = function ( node, targetNode ) {
if ( node.parentNode === null )
{
return null;
}
if ( node.parentNode !== targetNode )
{
return getParentNode( node.parentNode, targetNode );
}
return node;
};
// define invalid & outer ( also invalid ) elements
const INVALID_ELEMENTS = [ 'NOSCRIPT', 'SCRIPT', 'STYLE' ];
const OUTER_ELEMENTS = [ 'HTML', 'BODY', 'HEAD' ];
var getValidElementInner = function ( node, reverse ) {
// handle invalid tags
if ( OUTER_ELEMENTS.indexOf( node.nodeName ) !== -1 )
{
for (var i = 0; i < document.body.children.length; ++i)
{
var index = reverse ? document.body.children.length - ( i + 1 ) : i;
var element = document.body.children[index];
// skip invalid tags
if ( INVALID_ELEMENTS.indexOf( element.nodeName ) !== -1 )
{
continue;
}
return [ element, reverse ];
}
return null;
}
// get to next valid element
while ( node !== null && INVALID_ELEMENTS.indexOf( node.nodeName ) !== -1 )
{
node = reverse ? node.previousElementSibling : node.nextElementSibling;
}
// return non array if we couldnt find something
if ( node === null )
{
return null;
}
return [ node, reverse ];
};
// get next valid element ( to be safe to add divs )
// @return [ element, skip element ] or null if we couldnt find a valid place
var getValidElement = function ( nodeElement ) {
if (nodeElement)
{
if ( nodeElement.nextElementSibling !== null )
{
return getValidElementInner( nodeElement.nextElementSibling, false )
|| getValidElementInner( nodeElement.previousElementSibling, true );
}
if ( nodeElement.previousElementSibling !== null )
{
return getValidElementInner( nodeElement.previousElementSibling, true );
}
}
// something went wrong! -> element is not in DOM
return null;
};
function showHints()
{
// Had AJAX? Reset view blocks
sortedComments = getComments();
for (var key in sortedComments)
{
var startElement = getValidElement( sortedComments[key][0] );
var endElement = getValidElement( sortedComments[key][1] );
// skip if we couldnt get a valid element
if ( startElement === null || endElement === null )
{
continue;
}
// find element which has same parent as startelement
var jointParent = getParentNode( endElement[0], startElement[0].parentNode );
if ( jointParent === null )
{
// find element which has same parent as endelement
jointParent = getParentNode( startElement[0], endElement[0].parentNode );
if ( jointParent === null )
{
// both tries failed
continue;
}
else
{
startElement[0] = jointParent;
}
}
else
{
endElement[0] = jointParent;
}
var debugDiv = document.createElement( 'div' ); // holder
var debugPath = document.createElement( 'div' ); // path
var childArray = startElement[0].parentNode.childNodes; // target child array
var parent = startElement[0].parentNode;
var start, end;
// setup container
debugDiv.classList.add( 'debug-view' );
debugDiv.classList.add( 'show-view' );
debugPath.classList.add( 'debug-view-path' );
debugPath.innerText = key;
debugDiv.appendChild( debugPath );
// calc distance between them
// start
for (var i = 0; i < childArray.length; ++i)
{
// check for comment ( start & end ) -> if its before valid start element
if ( childArray[i] === sortedComments[key][1] ||
childArray[i] === sortedComments[key][0] ||
childArray[i] === startElement[0] )
{
start = i;
if ( childArray[i] === sortedComments[key][0] )
{
start++; // increase to skip the start comment
}
break;
}
}
// adjust if we want to skip the start element
if ( startElement[1] )
{
start++;
}
// end
for (var i = start; i < childArray.length; ++i)
{
if ( childArray[i] === endElement[0] )
{
end = i;
// dont break to check for end comment after end valid element
}
else if ( childArray[i] === sortedComments[key][1] )
{
// if we found the end comment, we can break
end = i;
break;
}
}
// move elements
var number = end - start;
if ( endElement[1] )
{
number++;
}
for (var i = 0; i < number; ++i)
{
if ( INVALID_ELEMENTS.indexOf( childArray[start] ) !== -1 )
{
// skip invalid childs that can cause problems if moved
start++;
continue;
}
debugDiv.appendChild( childArray[start] );
}
// add container to DOM
nodeList.push( parent.insertBefore( debugDiv, childArray[start] ) );
}
ciDebugBar.createCookie('debug-view', 'show', 365);
ciDebugBar.addClass(btn, 'active');
}
function hideHints()
{
for (var i = 0; i < nodeList.length; ++i)
{
var index;
// find index
for (var j = 0; j < nodeList[i].parentNode.childNodes.length; ++j)
{
if ( nodeList[i].parentNode.childNodes[j] === nodeList[i] )
{
index = j;
break;
}
}
// move child back
while ( nodeList[i].childNodes.length !== 1 )
{
nodeList[i].parentNode.insertBefore( nodeList[i].childNodes[1], nodeList[i].parentNode.childNodes[index].nextSibling );
index++;
}
nodeList[i].parentNode.removeChild( nodeList[i] );
}
nodeList.length = 0;
ciDebugBar.createCookie('debug-view', '', -1);
ciDebugBar.removeClass(btn, 'active');
}
var btn = document.querySelector('[data-tab=ci-views]');
// If the Views Collector is inactive stops here
if (! btn)
{
return;
}
btn.parentNode.onclick = function () {
if (ciDebugBar.readCookie('debug-view'))
{
hideHints();
}
else
{
showHints();
}
};
// Determine Hints state on page load
if (ciDebugBar.readCookie('debug-view'))
{
showHints();
}
},
//--------------------------------------------------------------------
setToolbarPosition: function () {
var btnPosition = document.getElementById('toolbar-position');
if (ciDebugBar.readCookie('debug-bar-position') === 'top')
{
ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top');
ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top');
}
btnPosition.addEventListener('click', function () {
var position = ciDebugBar.readCookie('debug-bar-position');
ciDebugBar.createCookie('debug-bar-position', '', -1);
if (!position || position === 'bottom')
{
ciDebugBar.createCookie('debug-bar-position', 'top', 365);
ciDebugBar.addClass(ciDebugBar.icon, 'fixed-top');
ciDebugBar.addClass(ciDebugBar.toolbar, 'fixed-top');
}
else
{
ciDebugBar.createCookie('debug-bar-position', 'bottom', 365);
ciDebugBar.removeClass(ciDebugBar.icon, 'fixed-top');
ciDebugBar.removeClass(ciDebugBar.toolbar, 'fixed-top');
}
}, true);
},
//--------------------------------------------------------------------
setToolbarTheme: function () {
var btnTheme = document.getElementById('toolbar-theme');
var isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
var isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches;
// If a cookie is set with a value, we force the color scheme
if (ciDebugBar.readCookie('debug-bar-theme') === 'dark')
{
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light');
ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark');
}
else if (ciDebugBar.readCookie('debug-bar-theme') === 'light')
{
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark');
ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light');
}
btnTheme.addEventListener('click', function () {
var theme = ciDebugBar.readCookie('debug-bar-theme');
if (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches)
{
// If there is no cookie, and "prefers-color-scheme" is set to "dark"
// It means that the user wants to switch to light mode
ciDebugBar.createCookie('debug-bar-theme', 'light', 365);
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark');
ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light');
}
else
{
if (theme === 'dark')
{
ciDebugBar.createCookie('debug-bar-theme', 'light', 365);
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark');
ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light');
}
else
{
// In any other cases: if there is no cookie, or the cookie is set to
// "light", or the "prefers-color-scheme" is "light"...
ciDebugBar.createCookie('debug-bar-theme', 'dark', 365);
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light');
ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark');
}
}
}, true);
},
//--------------------------------------------------------------------
/**
* Helper to create a cookie.
*
* @param name
* @param value
* @param days
*/
createCookie : function (name,value,days) {
if (days)
{
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else
{
var expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/; samesite=Lax";
},
//--------------------------------------------------------------------
readCookie : function (name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
while (c.charAt(0) == ' ')
{
c = c.substring(1,c.length);
}
if (c.indexOf(nameEQ) == 0)
{
return c.substring(nameEQ.length,c.length);
}
}
return null;
},
//--------------------------------------------------------------------
trimSlash: function (text) {
return text.replace(/^\/|\/$/g, '');
},
routerLink: function () {
var row, _location;
var rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"]');
var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/;
for (var i = 0; i < rowGet.length; i++)
{
row = rowGet[i];
if (!/\/\(.+?\)/.test(rowGet[i].innerText))
{
row.style = 'cursor: pointer;';
row.setAttribute('title', location.origin + '/' + ciDebugBar.trimSlash(row.innerText));
row.addEventListener('click', function (ev) {
_location = location.origin + '/' + ciDebugBar.trimSlash(ev.target.innerText);
var redirectWindow = window.open(_location, '_blank');
redirectWindow.location;
});
}
else
{
row.innerHTML = '<div>' + row.innerText + '</div>'
+ '<form data-debugbar-route-tpl="' + ciDebugBar.trimSlash(row.innerText.replace(patt, '?')) + '">'
+ row.innerText.replace(patt, '<input type="text" placeholder="$1">')
+ '<input type="submit" value="Go" style="margin-left: 4px;">'
+ '</form>';
}
}
rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"] form');
for (var i = 0; i < rowGet.length; i++)
{
row = rowGet[i];
row.addEventListener('submit', function (event) {
event.preventDefault()
var inputArray = [], t = 0;
var input = event.target.querySelectorAll('input[type=text]');
var tpl = event.target.getAttribute('data-debugbar-route-tpl');
for (var n = 0; n < input.length; n++)
{
if (input[n].value.length > 0)
{
inputArray.push(input[n].value);
}
}
if (inputArray.length > 0)
{
_location = location.origin + '/' + tpl.replace(/\?/g, function () {
return inputArray[t++]
});
var redirectWindow = window.open(_location, '_blank');
redirectWindow.location;
}
})
}
}
};
|