/** * Simple JavaScript DOM Inspector v0.1.2 * * Highlights hovered elements with a 2px red outline, and then provides the hovered element * on click to the callback function, which can do anything with it. * * By default, the inspector just console.logs a jQuery-style CSS selector path for the element. * * The CSS path-building code works up the given element's parent nodes to create an optimised * selector string, stopping at the first parent with an #id, but can also create an * ultra-specific full CSS path, right down to 'html'. * * Optionally, it can check to see if any part of the CSS path matches multiple elements, or if * any element in the CSS path has no #id or .class, and add specific ":nth-child()" * pseudo-selector to match the element, eg. "html body #content p img:nth-child(1)" * * Hit escape key to cancel the inspector. * * NB: XPath code removed as it didn't really work very well, need to write from scratch. * * Started putting in IE support, but won't work in IE just yet, check back next week for that * (so far, tested in FF4, Chrome, Safari, Opera 11.) * * No warranty; probably won't break the internet. Improvements and linkbacks welcome! * * - Joss */ (function(document) { var last; /** * Get full CSS path of any element * * Returns a jQuery-style CSS path, with IDs, classes and ':nth-child' pseudo-selectors. * * Can either build a full CSS path, from 'html' all the way to ':nth-child()', or a * more optimised short path, stopping at the first parent with a specific ID, * eg. "#content .top p" instead of "html body #main #content .top p:nth-child(3)" */ function cssPath(el) { var fullPath = 0, // Set to 1 to build ultra-specific full CSS-path, or 0 for optimised selector useNthChild = 0, // Set to 1 to use ":nth-child()" pseudo-selectors to match the given element cssPathStr = '', testPath = '', parents = [], parentSelectors = [], tagName, cssId, cssClass, tagSelector, vagueMatch, nth, i, c; // Go up the list of parent nodes and build unique identifier for each: while ( el ) { vagueMatch = 0; // Get the node's HTML tag name in lowercase: tagName = el.nodeName.toLowerCase(); // Get node's ID attribute, adding a '#': cssId = ( el.id ) ? ( '#' + el.id ) : false; // Get node's CSS classes, replacing spaces with '.': cssClass = ( el.className ) ? ( '.' + el.className.replace(/\s+/g,".") ) : ''; // Build a unique identifier for this parent node: if ( cssId ) { // Matched by ID: tagSelector = tagName + cssId + cssClass; } else if ( cssClass ) { // Matched by class (will be checked for multiples afterwards): tagSelector = tagName + cssClass; } else { // Couldn't match by ID or class, so use ":nth-child()" instead: vagueMatch = 1; tagSelector = tagName; } // Add this full tag selector to the parentSelectors array: parentSelectors.unshift( tagSelector ) // If doing short/optimised CSS paths and this element has an ID, stop here: if ( cssId && !fullPath ) break; // Go up to the next parent node: el = el.parentNode !== document ? el.parentNode : false; } // endwhile // Build the CSS path string from the parent tag selectors: for ( i = 0; i < parentSelectors.length; i++ ) { cssPathStr += ' ' + parentSelectors[i];// + ' ' + cssPathStr; // If using ":nth-child()" selectors and this selector has no ID / isn't the html or body tag: if ( useNthChild && !parentSelectors[i].match(/#/) && !parentSelectors[i].match(/^(html|body)$/) ) { // If there's no CSS class, or if the semi-complete CSS selector path matches multiple elements: if ( !parentSelectors[i].match(/\./) || $( cssPathStr ).length > 1 ) { // Count element's previous siblings for ":nth-child" pseudo-selector: for ( nth = 1, c = el; c.previousElementSibling; c = c.previousElementSibling, nth++ ); // Append ":nth-child()" to CSS path: cssPathStr += ":nth-child(" + nth + ")"; } } } // Return trimmed full CSS path: return cssPathStr.replace(/^[ \t]+|[ \t]+$/, ''); } /** * MouseOver action for all elements on the page: */ function inspectorMouseOver(e) { // NB: this doesn't work in IE (needs fix): var element = e.target; // Set outline: element.style.outline = '2px solid #f00'; // Set last selected element so it can be 'deselected' on cancel. last = element; } /** * MouseOut event action for all elements */ function inspectorMouseOut(e) { // Remove outline from element: e.target.style.outline = ''; } /** * Click action for hovered element */ function inspectorOnClick(e) { e.preventDefault(); // These are the default actions (the XPath code might be a bit janky) // Really, these could do anything: console.log( cssPath(e.target) ); /* console.log( getXPath(e.target).join('/') ); */ return false; } /** * Function to cancel inspector: */ function inspectorCancel(e) { // Unbind inspector mouse and click events: if (e === null && event.keyCode === 27) { // IE (won't work yet): document.detachEvent("mouseover", inspectorMouseOver); document.detachEvent("mouseout", inspectorMouseOut); document.detachEvent("click", inspectorOnClick); document.detachEvent("keydown", inspectorCancel); last.style.outlineStyle = 'none'; } else if(e.which === 27) { // Better browsers: document.removeEventListener("mouseover", inspectorMouseOver, true); document.removeEventListener("mouseout", inspectorMouseOut, true); document.removeEventListener("click", inspectorOnClick, true); document.removeEventListener("keydown", inspectorCancel, true); // Remove outline on last-selected element: last.style.outline = 'none'; } } /** * Add event listeners for DOM-inspectorey actions */ if ( document.addEventListener ) { document.addEventListener("mouseover", inspectorMouseOver, true); document.addEventListener("mouseout", inspectorMouseOut, true); document.addEventListener("click", inspectorOnClick, true); document.addEventListener("keydown", inspectorCancel, true); } else if ( document.attachEvent ) { document.attachEvent("mouseover", inspectorMouseOver); document.attachEvent("mouseout", inspectorMouseOut); document.attachEvent("click", inspectorOnClick); document.attachEvent("keydown", inspectorCancel); } })(document);