From 6e12d2db4e51af7661f251f3712016ebde01aed4 Mon Sep 17 00:00:00 2001 From: Joss Crowcroft Date: Mon, 28 Mar 2011 00:56:03 +0800 Subject: [PATCH] v0.1 - get full CSS selector path of any element --- README.markdown | 26 +++++- inspector.js | 225 +++++++++++++++++++++++++++++++----------------- 2 files changed, 168 insertions(+), 83 deletions(-) diff --git a/README.markdown b/README.markdown index 0527f82..3371ccf 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,26 @@ -Simple JavaScript DOM Inspector -=============================== +Simple JavaScript DOM Inspector v0.1 +==================================== -Really hacked-together, doesn't do much yet, but might be interesting! +Highlights hovered elements with a 2px red outline, and then logs the element's full +CSS selector path when clicked. You can also display the selected element's XPath, if +that's your kinda thing, or do anything with it in the callback function. +The CSS selector path-getting code tries to be as specific as possible, but could be +cut down to make an optimised selector. It works its way up the element's parentNodes +to build a full jQuery-style selector string with each element's ID and CSS class. + +It also checks to see whether any part of the CSS path matches multiple elements, or if +any element has no ID or CSS class, and adds specific "nth-child" pseudo-selectors +where needed, eg. "p:nth-of-type(3)". + +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 \ No newline at end of file diff --git a/inspector.js b/inspector.js index 7cc3d7e..0165008 100644 --- a/inspector.js +++ b/inspector.js @@ -1,115 +1,180 @@ /** - * Simple JavaScript DOM Inspector v0.0.1 - Joss Crowcroft + * Simple JavaScript DOM Inspector v0.1 * - * Hacked together in 20 minutes so no guarantees it'll work, but putting it in a repo as I think it could become quite useful with some tidying up! - * Default functionality is to highlight elements with a 2px red outline, and alert out the element's XPath on click. + * Highlights hovered elements with a 2px red outline, and then logs the element's full + * CSS selector path when clicked. You can also display the selected element's XPath, if + * that's your kinda thing, or do anything with it in the callback function. * - * Could be configured to display a Sizzle/jQuery selector instead of XPath, or to do pretty much anything. - * - * 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) + * The CSS selector path-getting code tries to be as specific as possible, but could be + * cut down to make an optimised selector. It works its way up the element's parentNodes + * to build a full jQuery-style selector string with each element's ID and CSS class. + * + * It also checks to see whether any part of the CSS path matches multiple elements, or if + * any element has no ID or CSS class, and adds specific "nth-child" pseudo-selectors + * where needed, eg. "p:nth-of-type(3)". + * + * 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! */ -(function(d) { - var last; - - // XPath finder (modified from http://snippets.dzone.com/posts/show/4349) - // JSHinted, but not double-checked for coolness or accuracy (yet): - function getXPath(node, path) { - path = path || []; - var count, sibling; - if(node.parentNode) { - path = getXPath(node.parentNode, path); +(function(document) { + var last; + + /** + * Get full CSS path of any element + * + * Returns a jQuery-style full CSS selector path, including IDs, classes and + * ":nth-of-type()" child pseudo-selectors (when required): + */ + function cssPath(el) { + var cssPathStr = '', + parents = [], + tagName, + cssId, + cssClass, + vagueMatch, + nth, + i, + c, + tagSelector; + + // Build array of parent nodes: + while ( el.parentNode ) { + parents.push( el ); + el = el.parentNode; } - if(node.previousSibling) { - count = 1; - sibling = node.previousSibling; - do { - if(sibling.nodeType === 1 && sibling.nodeName === node.nodeName) { - count++; - } - sibling = sibling.previousSibling; - } while(sibling); - if(count === 1) { - count = null; + + // Go down the list of parent nodes and build unique identifier for each: + for ( i = parents.length-1; i >= 0; i-- ) { + el = parents[i]; + vagueMatch = 0; + + // Get the node's tag name, ID and CSS classes: + tagName = el.nodeName.toLowerCase(); + cssId = ( el.id ) ? ( '#' + el.id ) : false; + cssClass = ( el.className ) ? ( '.' + el.className.replace(/ /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; } - } else if(node.nextSibling) { - sibling = node.nextSibling; - do { - if(sibling.nodeType === 1 && sibling.nodeName === node.nodeName) { - count = 1; - sibling = null; - } else { - count = null; - sibling = sibling.previousSibling; + + // Add this tag's CSS selector to CSS path string: + cssPathStr += ' '+tagSelector; + + // If the complete/semi-complete cssPathStr matches multiple child nodes, add ":nth-child()" pseudo-selector to match: + // NB: Only do this if element has no #id and is not /; + // Always do this if element has no CSS class (vague match) + if ( !tagSelector.match(/(html|body)/) && !cssId && ( vagueMatch || $( cssPathStr ).length > 1 ) ) { + el = parents[i]; + nth = 1; + + // Cycle through elements siblings to match same tags for ":nth-of-type" selector: + while ( el = el.previousElementSibling ) { + // Is this the same type of HTML tag as the selected element?: + if ( el.nodeName.toLowerCase() === tagName ) + nth++; } - } while(sibling); - } - if(node.nodeType === 1) { - path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : '')); + + // Append nth-child pseudo-selector to CSS path: + cssPathStr += ":nth-of-type(" + nth + ")"; + } } - return path; + + // Return full CSS path: + return cssPathStr.replace(' ', ''); } - - // Mouseover any element, this happens: + + + /** + * MouseOver action for all elements on the page: + */ function inspectorMouseOver(e) { - var element = e.target; // NB: not IE (needs fix) + // NB: this doesn't work in IE (needs fix): + var element = e.target; - element.style.outlineWidth = '2px'; - element.style.outlineStyle = 'solid'; - element.style.outlineColor = '#f00'; + // Set outline: + element.style.outline = '2px solid #f00'; + // Set last selected element so it can be 'deselected' on cancel. last = element; } - // Mouseout of any element, do this: + + /** + * MouseOut event action for all elements + */ function inspectorMouseOut(e) { - e.target.style.outlineStyle = 'none'; + // Remove outline from element: + e.target.style.outline = ''; } - // Clicked something? You know what to do: + + /** + * Click action for hovered element + */ function inspectorOnClick(e) { e.preventDefault(); - - /* Maybe: - var selection = e.target.innerHTML; - alert('Element: ' + evt.target.toString() + '\n\nSelection:\n\n' + selection); - */ - alert( 'xpath: '+ getXPath( e.target ) ); + // 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 ) { + + + /** + * Function to cancel inspector: + */ + function inspectorCancel(e) { + // Unbind inspector mouse and click events: if (e === null && event.keyCode === 27) { // IE (won't work yet): - d.detachEvent("mouseover", inspectorMouseOver); - d.detachEvent("mouseout", inspectorMouseOut); - d.detachEvent("click", inspectorOnClick); - d.detachEvent("keydown", inspectorCancel); + 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: - d.removeEventListener("mouseover", inspectorMouseOver, true); - d.removeEventListener("mouseout", inspectorMouseOut, true); - d.removeEventListener("click", inspectorOnClick, true); - d.removeEventListener("keydown", inspectorCancel, true); + 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.outlineStyle = 'none'; + last.style.outline = 'none'; } } - - // Add event listeners for DOM-inspectorey actions - if ( d.addEventListener ) { - d.addEventListener("mouseover", inspectorMouseOver, true); - d.addEventListener("mouseout", inspectorMouseOut, true); - d.addEventListener("click", inspectorOnClick, true); - d.addEventListener("keydown", inspectorCancel, true); - } else if ( d.attachEvent ) { - d.attachEvent("mouseover", inspectorMouseOver); - d.attachEvent("mouseout", inspectorMouseOut); - d.attachEvent("click", inspectorOnClick); - d.attachEvent("keydown", inspectorCancel); + + /** + * 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); \ No newline at end of file