How to create a query selector out of a selected element?

Viewed 185

In google chrome, especially now with custom elements, it became very cumbersome to select and element by hand nowadays, even though the browser knows the whole path to it already. Or is there a way that leads to a query selected for an element that I'm inspecting?

Situation:

enter image description here

What chrome can tell me:

enter image description here

What chrome is unable to create for me AFAIK:

enter image description here

1 Answers

While building an chrome extension I have found a need to uniquely locate an element when returning to a page. To do this I needed to create a query string for a selected element (custom context menu click)

While searching for a solution I found this unanswered question.

As I could not find an off the shelf solution or API to do the task I wrote the following function. It is untested in the wild, is very rough and ready (using poor node traversing techniques). I posted it in this state lest I forget and this question remains unanswered.

Create Query String For Element

A function to build a query string that will uniquely locate an element from a reference of the element.

const querytStr = createQueryStringForElement(myElement); // return string or undefined
if (querytStr) {
    const element = document.querySelector(queryStr);
    console.log(element === myElement); // expected result true
}

If the function fails to create a query that uniquely locates an element it returns undefined. else it returns the query string.

Example results

"#editor > div.ace_scroller > div.ace_content > div.ace_layer.ace_text-layer > div.ace_line:nth-child(45) > span.ace_punctuation.ace_operator"


"#buttons"                             // A UI container
"#buttons > div.buttons"               // A sub UI container
"#buttons > div.buttons:nth-child(2)"  // A button element by position
"#buttons > div.buttons:nth-child(3)"  // A button element by position

How it works

  • The code assumes that the page is well formed (ids must be unique).

  • The query string will try to start with an id eg "#elementId" but if an element has no id the query will use the tag and class names. eg "div.my-class".

    The tag and class name may not uniquely identify the element. To check if the query is unique, the query string is used to query the DOM from the elements parent.

    If needed the query string will use the elements position to refine the query "div.my-class:nth-child(2)". Unfortunately this makes the resultant query string insensitive to changes in element order.

  • The query string is built up along each parent until it finds an element with an id or there are no more parents.

  • The final step uses the query to see if the query finds the correct element returning the query if successful.

The code

function createQueryStringForElement(element) {
    const getElementSel = element => {
        const tName = element.tagName.toLowerCase();
        var i = 0, str = element.id ? "#" + element.id : sel = tName;
        if (str.includes("#")) { return str}
        str += element.classList.length ? "." + [...element.classList.values()].join(".") : "";
        if (element.parentElement) {
            const res = element.parentElement.querySelector(str);
            if (res !== element) {
                while (i < element.parentElement.children.length) {
                    if (element.parentElement.children[i] === element) {
                        i > 0 && (str += ":nth-child(" + (i + 1) + ")" );
                        break;
                    }
                    i++;
                }
            }
        }
        return str;
    }
    const queryPath = [];
    const original = element;
    do {
        const subQuery = getElementSel(element);
        queryPath.push(subQuery);
        if (subQuery[0] === "#") { break }
        element = element.parentElement;
    } while (element);
    const query = queryPath.reverse().join(" > ");
    try {
        const els = document.querySelector(query);
        if (els === original) { return query }
    } catch(e) { }
}
Related