admin管理员组

文章数量:1318156

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?

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?

Share Improve this question asked Jan 9, 2014 at 16:58 TomTom 6,7394 gold badges36 silver badges62 bronze badges 1
  • Try "not:", as shown briefly in this questions answer: stackoverflow./questions/10777684/… – BrianH Commented Jan 9, 2014 at 17:01
Add a ment  | 

4 Answers 4

Reset to default 4

You can create one that can work in all cases. In two ways:

  1. Using nth-child ( which is using index)

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)

  1. Using attributes for a "human readable" way:
    1. Get the attributes of the elements ( only the explicit attributes).
    2. Get the entire path to the element.
    3. Use the multiple attribute selector to unify all the features.

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

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

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

you can use this :

const querySelectorInvers = (elem) => {
      let query = "";
      document.querySelectorAll('*').forEach((el) => {
        if (el !== elem) {
          query += ":not(" + el.tagName + ")";
        }
      });
      return query;
    }

本文标签: javascriptDoes an Inverse querySelector existStack Overflow