Skip to content

Instantly share code, notes, and snippets.

@PseudoSky
Created October 31, 2018 13:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PseudoSky/2832ed2f0d4731f8431caaa38aee5bb5 to your computer and use it in GitHub Desktop.
Save PseudoSky/2832ed2f0d4731f8431caaa38aee5bb5 to your computer and use it in GitHub Desktop.
Extract a dom nodes css selector

SmartSelect

Compute and minify an element's css selector

gistFileNameInput = '#gists > div:nth-child(4) > div > div.file-header > div.input-group.gist-filename-input > input.form-control.filename.js-gist-filename.js-blob-filename'
gistFileNameInput.length
> 151

newSelect = SmartSelect.cssPath(gistFileNameInput)
newSelect
> "div:nth-of-type(3) > div > div:nth-of-type(1) > div:nth-of-type(2) > input:nth-of-type(2)"

newSelect.length
> 89

document.querySelectorAll(newSelect)
> [input.form-control.filename.js-gist-filename.js-blob-filename]
/* UTIL: generate all possible combos */
/* ex: 'div > div.maybe-useful > p:nth-child(1)' -> tokenize -> permute -> test */
var powerSet = ( list ) => {
var set = [],
listSize = list.length,
combinationsCount = (1 << listSize);
for (var i = 1; i < combinationsCount ; i++ , set.push(combination) )
for (var j=0, combination = [];j<listSize;j++)
if ((i & (1 << j)))
combination.push(list[j]);
return set;
}
/* generate a universally unique selector for the desired element */
var cssPath = (el) => {
if (typeof el === 'string') el = document.querySelector(el)
if (!(el instanceof Element))
return -1;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector = '#' + el.id;
path.unshift(selector);
break;
}
if(true) {
var sib = el;
var nth = 1;
while (sib = sib.previousElementSibling) {
if (sib.nodeName.toLowerCase() == selector){
nth++;
}
}
if (nth != 1 || el.nextElementSibling) selector += ":nth-of-type(" + nth + ")";
}
// Pretty significant impact on performance...
// if(el.className){
// selector += '.' + el.className.split(' ').filter(e => e.trim())[1].join('.');
// }
path.unshift(selector);
if(document.querySelectorAll(path.join(" > ")).length===1) {
return path.join(" > ")
}
el = el.parentNode;
}
return path.join(" > ");
}
var splitPermute = (s) => s.split(/ > /).map( r => powerSet(r.split(/(?=[#\.:])/)).map(sel => sel.join('')))
var shortestPath = (agg, e=[], i) => ((!agg || e.length<agg.length) ? e : agg)
var checkVs = (target) => {
return el => {
return $$(el).length === 1 && $$(el)[0] === target
}
}
var reducePath = (pre="", opts=[], target, max=1000, join=' > ') => {
res = []
if(pre.length >= max) return [];
if(opts.length===0) return [pre.trim()];
if (opts.length) {
y = opts[0]
res = y.flatMap((yi) => {
if(pre){
prior = [pre, yi.trim()].filter(t => t.trim()).join(join)
} else {
prior = yi.trim()
}
res = reducePath(prior, opts.slice(1), target, max, ' > ') || []
if(res && res.length) return res
return []
})
return res.filter(e => e && e.trim())
} else {
return []
}
return res.flat()
}
var findSelector = (elem) => {
node = typeof elem === 'string' ? $$(elem)[0] : elem;
selector = cssPath(node);
if(selector===-1) return -1;
perms = splitPermute(selector).filter(s => s.length <= selector.length).sort();
paths = reducePath('', perms, node, selector.length).filter(checkVs(node))
return paths.length ? paths.reduce(shortestPath) : selector
}
var SmartSelect = {
cssPath,
findSelector,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment