Last active
March 15, 2016 07:42
-
-
Save colllin/5409954 to your computer and use it in GitHub Desktop.
Use jQuery to find the shortest selector for a given element in the DOM.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jQuery(function($) { | |
// returns an array of the potential selector components for the first element in the jQuery object. IDs, classes, and tagNames only. | |
var getSelectorComponents = function($el) { | |
var components = []; | |
var id = $el.attr('id'); | |
if (typeof(id)!='undefined' && /[^\s]/.test(id)) { | |
components.push('#'+id); | |
} | |
var classes = $el.attr('class'); | |
if (typeof(classes)!='undefined' && /[^\s]/.test(classes)) { | |
classes = '.' + classes.split(/\s+/).join(' .'); | |
components = components.concat( classes.split(' ') ); | |
} | |
components.push($el[0].tagName); | |
return components; | |
}; | |
var getSelectorFromComponents = function(components) { | |
return components[components.length-1] + components.slice(0, -1).join(''); | |
}; | |
$(document.body).on('click', function(event) { | |
event.preventDefault(); | |
// handle deselection of any previously selected elements | |
// (for now, only allows selection of one element at a time) | |
var selectedTag = 'cdo-selected'; | |
$('.'+selectedTag).removeClass(selectedTag).css('box-shadow', 'none'); | |
var $target = $(event.target).css('box-shadow', '0 0 0 4px rgba(255,100,0,.5)'); | |
// 2 step process | |
// Step 1 - find the closest unique context selector, i.e. `#main .special-container ul` | |
// Step 2 - invent a creative selector within that context (if necessary), i.e. `li:nth-child(2) a` | |
// Step 1. | |
// path will be an array of arrays. for each level of the DOM hierarchy between the body and our target element (inclusive), | |
// there will be an array containing the potential selector components we can use for that element | |
// i.e. for this container within the DOM path: <div id="main" class="column left blue"></div> | |
// we will have this element in our path array: ['#main', '.column', '.left', '.blue', 'DIV'] | |
// from which we can construct various selectors to test for uniqueness | |
// NOTE: because the jQuery .parents() method naturally returns the element ancestors ordered from the inside out, the path array is reversed from the norm | |
var path = []; | |
$target.parents().each(function() { | |
path.push( getSelectorComponents( $(this) ) ); | |
}); | |
path.splice(0, 0, getSelectorComponents($target)); | |
var uniqueContextPath = []; | |
// Find the first element | |
// repeat this step to construct a path of unique contexts | |
var i = 0; | |
var $context = $(document.body); | |
while (path.length > 0 && i < path.length) { | |
var $testQuery; | |
// find the nearest parent that has a unique selector (within any previously determined context, otherwise within the DOM) | |
for (i = 0; i < path.length; i++) { | |
$testQuery = $(getSelectorFromComponents(path[i]), $context); | |
if ($testQuery.length == 1) { | |
break; | |
} | |
} | |
if (i < path.length) { | |
$context = $testQuery; | |
uniqueContextPath.push(path[i]); | |
path.splice(i, path.length); | |
// reset i to make sure we repeat the loop | |
i = 0; | |
} | |
} | |
var contextSelector = $(uniqueContextPath).map(function() { return getSelectorFromComponents(this); }).get().join(' '); | |
console.log( contextSelector ); | |
console.log( $(contextSelector).length ); | |
var $message = $('#message'); | |
if ($message.length == 0) { | |
$message = $('<div id="message" style="position:fixed; left: 0; right: 0; bottom: 0; background:rgba(0,0,0,.7); color: #fff; font-size:15px; text-align: center; padding:20px; font-family:Helvetica;"/>'); | |
$(document.body).append($message); | |
} | |
var message = 'Closest selector using only ID, class, tagName: <strong style="color:#f60;">'+contextSelector+'</strong>'; | |
message += '<br>100% match? <strong style="color:#f60;">'+(path.length == 0 ? 'YES' : 'NO')+'</strong>, Remaining DOM levels to narrow the match: <strong style="color:#f60;">'+path.length+'</strong>'; | |
$message.html(message); | |
// Step 2. | |
// Find a creative selector using :nth-child(). Other kinds of selectors could be included later. | |
if (path.length > 0) { | |
// TODO | |
} | |
// tag the element so we can find it later | |
$target.addClass(selectedTag); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment