Skip to content

Commit

Permalink
v0.1 - get full CSS selector path of any element
Browse files Browse the repository at this point in the history
  • Loading branch information
Joss Crowcroft committed Mar 27, 2011
1 parent f243e27 commit 6e12d2d
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 83 deletions.
26 changes: 23 additions & 3 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -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
225 changes: 145 additions & 80 deletions inspector.js
Original file line number Diff line number Diff line change
@@ -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 <html>/<body>;
// 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);

0 comments on commit 6e12d2d

Please sign in to comment.