Reputation: 6729
I know I can can use querySelector to locate an element in a document
var element = document.querySelector(".myclass")
but does there exist a inverse querySelector such that:
var selector = document.inverseQuerySelector(element);
Assert.AreEqual(element, document.querySelector(selector));
the returned selector of inverseQuerySelector always uniquely identifies the specified element?
Upvotes: 7
Views: 4795
Reputation: 11
you can use this :
const querySelectorInvers = (elem) => {
let query = "";
document.querySelectorAll('*').forEach((el) => {
if (el !== elem) {
query += ":not(" + el.tagName + ")";
}
});
return query;
}
Upvotes: 0
Reputation: 1572
I mixed the 2 solutions proposed to have a result readable by humans and which gives the right element if there are several similar siblings:
function elemToSelector(elem) {
const {
tagName,
id,
className,
parentNode
} = elem;
if (tagName === 'HTML') return 'HTML';
let str = tagName;
str += (id !== '') ? `#${id}` : '';
if (className) {
const classes = className.split(/\s/);
for (let i = 0; i < classes.length; i++) {
str += `.${classes[i]}`;
}
}
let childIndex = 1;
for (let e = elem; e.previousElementSibling; e = e.previousElementSibling) {
childIndex += 1;
}
str += `:nth-child(${childIndex})`;
return `${elemToSelector(parentNode)} > ${str}`;
}
Test with:
// Select an element in Elements tab of your navigator Devtools, or replace $0
document.querySelector(elemToSelector($0)) === $0 &&
document.querySelectorAll(elemToSelector($0)).length === 1
Which might give you something like, it's a bit longer but it's readable and it always works:
HTML > BODY:nth-child(2) > DIV.container:nth-child(2) > DIV.row:nth-child(2) > DIV.col-md-4:nth-child(2) > DIV.sidebar:nth-child(1) > DIV.sidebar-wrapper:nth-child(2) > DIV.my-4:nth-child(1) > H4:nth-child(3)
Edit: I just found the package unique-selector
Upvotes: 0
Reputation: 2016
You can create one that can work in all cases. In two ways:
The solution is the following:
function getMyPathByIndex(element){
if(element == null)
return '';
if(element.parentElement == null)
return 'html'
return getMyPathByIndex(element.parentElement) + '>' + ':nth-child(' + getMyIndex(element) + ')';
}
function getMyIndex(element){
if(element == null)
return -1;
if(element.parentElement == null)
return 0;
let parent = element.parentElement;
for(var index = 0; index < parent.childElementCount; index++)
if(parent.children[index] == element)
return index + 1;
}
For instance, the element:
<a id="y" class="vote-up-off" title="This answer is useful">up vote</a>
You can get this element in this page just by typing in the console:
document.querySelector('a[title="This answer is useful"]');
has it unique querySelector:
html>:nth-child(2)>:nth-child(5)>:nth-child(1)>:nth-child(1)>:nth-child(3)>:nth-child(2)>:nth-child(6)>:nth-child(1)>:nth-child(1)>:nth-child(1)>:nth-child(1)>:nth-child(1)>:nth-child(2)
Using the same elemen before has it unique querySelector:
html>body>div>div>div>div>div>div>table>tbody>tr>td>div>a[id="y"][class="vote-up-off"][title="This answer is useful"]
test if the solution is the correct by:
// e is an element and e must exist inside the page
document.querySelector( getMyPathByIndex(e)) === e &&
document.querySelectorAll( getMyPathByIndex(e)).length === 1
The code solutions is the follow:
function convertAttributesToQuerySelector(element){
var tagName = element.tagName.toLowerCase();
var result = tagName;
Array.prototype.slice.call(element.attributes).forEach( function(item) {
if(element.outerHTML.contains(item.name))
result += '[' + item.name +'="' + item.value + '"]';
});
return result;
//["a[id="y"]", "a[class="vote-up-off"]", "a[title="This answer is useful"]"]
}
function getMyPath(element){
if(element.parentElement.tagName == 'HTML')
return 'html';
return getMyPath(element.parentElement) + '>' + element.parentElement.tagName.toLowerCase() ;
//"html>body>div>div>div>div>div>div>table>tbody>tr>td>div"
}
function myUniqueQuerySelector(element){
var elementPath = getMyPath(element);
var simpleSelector = convertAttributesToQuerySelector(element);
return elementPath + '>' + simpleSelector;
}
You can always test if the solution is the correct by:
// e is an element and e must exist inside the page
document.querySelector( myUniqueQuerySelector(e)) === e &&
document.querySelectorAll( myUniqueQuerySelector(e)).length === 1
Upvotes: 4
Reputation: 18354
No, because there are many selectors (probably infinite) that can select the same element.
For a function to be inversable (even in math), it's mapping has to be 1 to 1, this is not the case.
BTW, Because of that, you could create some that may work only in some cases. For example:
function inverseForElementWithUniqueId(element){
return '#' + element.id;
}
var selector = inverseForElementWithUniqueId(element);
Assert.AreEqual(element, document.querySelector(selector)); //True
(code which indeed may look trivial)
But as said, because of the theory, this would work only in a subset of the cases.
But, it would work only sometimes
Upvotes: 1