Skip to content

Instantly share code, notes, and snippets.

@danemacaulay
Last active January 3, 2017 19:44
Show Gist options
  • Save danemacaulay/3c719af04bdfa39cb41339477f39204f to your computer and use it in GitHub Desktop.
Save danemacaulay/3c719af04bdfa39cb41339477f39204f to your computer and use it in GitHub Desktop.
UniqueSelector
'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