Last active
January 3, 2017 19:44
-
-
Save danemacaulay/3c719af04bdfa39cb41339477f39204f to your computer and use it in GitHub Desktop.
UniqueSelector
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
'use strict'; | |
function attributeIterator(node, extractorFunction) { | |
var selector = ''; | |
var attributes = node.attributes; | |
for (var i = 0; i < attributes.length; i++) { | |
var attr = attributes[i]; | |
var attrSelector = extractorFunction(attr); | |
if (attrSelector) { | |
selector += attrSelector; | |
} | |
} | |
return selector; | |
} | |
function getNthChild(node) { | |
var nthChild; | |
var siblings = node.parentElement.children; | |
for (var i = 0; i < siblings.length; i++) { | |
var sibling = siblings[i]; | |
if (sibling.isEqualNode(node)) { | |
nthChild = i + 1; | |
} | |
} | |
return nthChild; | |
} | |
function byClass(node) { | |
return attributeIterator(node, function (attribute) { | |
if (attribute.name === 'class') { | |
return '.' + attribute.value.replace(' ', '.'); | |
} | |
}); | |
} | |
function byId(node) { | |
return attributeIterator(node, function (attribute) { | |
if (attribute.name === 'id') { | |
return '#' + attribute.value; | |
} | |
}); | |
} | |
function byAttributes(node) { | |
return attributeIterator(node, function (attribute) { | |
if (attribute.name === 'id' || attribute.name === 'class') { | |
return; | |
} | |
return '[' + attribute.name + '="' + attribute.value + '"]'; | |
}); | |
} | |
function byNthChild(node) { | |
if (node.parentElement) { | |
var nthChild = getNthChild(node); | |
return ':nth-child(' + nthChild + ')'; | |
} | |
return ''; | |
} | |
function byTag(node) { | |
return node.tagName; | |
} | |
function getSelectorByStrategies(node, strategies) { | |
var selector = ''; | |
strategies.forEach(function (strategy) { | |
selector += strategy(node); | |
}); | |
return selector; | |
} | |
function uniqueSelector(node, strategies, unusedStrategies, context = []) { | |
if (node === null) { | |
throw new Error('No node provided'); | |
} | |
strategies = strategies || [byTag]; | |
unusedStrategies = unusedStrategies || [ | |
byId, | |
byClass, | |
byAttributes, | |
byNthChild, | |
]; | |
var selector = getSelectorByStrategies(node, strategies); | |
var fullSelector = context.length ? selector + ' ' + context.join(' ') : selector; | |
var results = document.querySelectorAll(fullSelector); | |
if (results.length > 1 && unusedStrategies.length === 0 && node.parentElement) { | |
context.unshift(selector); | |
return uniqueSelector(node.parentElement, null, null, context); | |
} | |
else if (results.length > 1) { | |
strategies.push(unusedStrategies.shift()); | |
return uniqueSelector(node, strategies, unusedStrategies, context); | |
} | |
else if (results.length === 0) { | |
throw new Error('unable to get unique selector'); | |
} | |
return fullSelector; | |
} | |
// tests | |
console.clear(); | |
function isSameNode(originalNode, selectedNode) { | |
if (originalNode.isEqualNode(selectedNode)) { | |
console.log('it worked'); | |
} | |
else { | |
throw new Error('it failed'); | |
} | |
} | |
function writeHTML(html) { | |
document.open(); | |
document.write(html); // jshint ignore:line | |
} | |
function testByTag() { | |
var html = '<body><div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('div'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testById() { | |
var html = '<body><div class="kung"><div id="foo"></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('#foo'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testByClass() { | |
var html = '<body><div class="kung"><div class="panda"></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('.panda'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testByArbitraryAttribute() { | |
var html = '<body><div data-bar="foo"></div><div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('[data-bar="foo"]'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testByNthChild() { | |
var html = '<body><div><div class="monkey">1</div><div class="monkey">2</div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('.monkey:nth-child(2)'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testByOneNestedNthChild() { | |
var html = '<body><div class="kung"><div class="foo">1<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div><div class="foo">2<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('.kung .foo:nth-child(2) .monkey:nth-child(3)'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testCombinedNestedNthChild() { | |
var html = '<body><div class="kung"><div class="foo">1<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div><div class="foo" data-bar="foo">2<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('.kung .foo:nth-child(2) .monkey:nth-child(3)'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
function testByTwoNestedNthChild() { | |
var html = '<body><div class="kung"><div class="foo">1<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div><div class="foo">2<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div></div><div class="kung"><div class="foo">1<div class="panda"></div><div class="monkey">1</div><div class="monkey">2</div></div><div class="foo">2<div class="panda"></div><div class="monkey">1</div><div class="monkey">3</div></div></div></body>'; | |
writeHTML(html); | |
var node = document.querySelector('.kung:nth-child(2) .foo:nth-child(2) .monkey:nth-child(3)'); | |
var selector = uniqueSelector(node); | |
console.log(selector); | |
var selectedNode = document.querySelector(selector); | |
isSameNode(node, selectedNode); | |
} | |
testByTag(); | |
testById(); | |
testByClass(); | |
testByArbitraryAttribute(); | |
testByNthChild(); | |
testByOneNestedNthChild(); | |
testCombinedNestedNthChild(); | |
testByTwoNestedNthChild(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment