owenmelbz
owenmelbz

Reputation: 6564

Getting Element Path for selector

Running into a spot of trouble and basically trying to create a variable which can be used as a selector. eg

$('a').click(function(){
   var selector = $(this).dompath();
});

HTML:

html
    body
        div
            div /div
        /div
       ul
         li
         li
       /ul
    div
        ul
           li
           li
           li hello world
        /ul
   /div
   body
html

this would then return something like

path = html body div ul li:contains('hello world')

then i could use this in a selector to select this div so if i did like

$(path).text() would return "hello world"

many thanks!

Upvotes: 8

Views: 15538

Answers (4)

sshow
sshow

Reputation: 9094

Modified version of @jcern`s fantastic code.

Features:

  • If the element has an id: show only #elementId
  • If no id is present: show element.className
  • If no class is present: show element with it's innerHtml appended (if any)
  • Skip the <body> and <html> elements to shorten output
  • Does not rely on jQuery
function dompath(element) {
    var path = '',
    i, innerText, tag, selector, classes;

    for (i = 0; element && element.nodeType == 1; element = element.parentNode, i++) {
        innerText = element.childNodes.length === 0 ? element.innerHTML : '';
        tag = element.tagName.toLowerCase();
        classes = element.className;

        // Skip <html> and <body> tags
        if (tag === "html" || tag === "body")
            continue;

        if (element.id !== '') {
            // If element has an ID, use only the ID of the element
            selector = '#' + element.id;

            // To use this with jQuery, return a path once we have an ID
            // as it's no need to look for more parents afterwards.
            //return selector + ' ' + path;
        } else if (classes.length > 0) {
            // If element has classes, use the element tag with the class names appended
            selector = tag + '.' + classes.replace(/ /g , ".");
        } else {
            // If element has neither, print tag with containing text appended (if any)
            selector = tag + ((innerText.length > 0) ? ":contains('" + innerText + "')" : "");
        }

        path = ' ' + selector + path;
    }
    return path;
}

Upvotes: 6

Louis Ricci
Louis Ricci

Reputation: 21086

The request is kind of silly, since there's a much better way.

Either assign a unique ID to the element to quickly reference it later, or if an ID is already assigned use it.

//
// generate a unique (enough) id for an element if necessary
//
function getUID(id) {
    if(window["uidCounter"]==null)
        window["uidCounter"]=0;
    return id||( (window["uidCounter"]++) + "_" + (new Date()).getTime() );
}
//
// use an #ID selector
//
$('a').click(function(){
   var uid = getUID( $(this).attr('id') );
   $(this).attr('id', uid);
   var selector = "#" + uid;
});

Upvotes: 0

BeniBela
BeniBela

Reputation: 16917

You would need enumerate all parents of the element you want to create a query for, and add a selector for each parent, e.g. the node name of the parent or the name with the contains-test, if that test is needed for that parent. The only way to be sure, if this contains-test is needed, is probably to apply the current query against the current parent in each step and check, if the query only returns the target element. Then add the contains-test if it matches too much...

I wrote a Greasemonkey script doing that. First it collects all elements that are needed to find the target element in another tree ("template") and then converts that to a query. However, it uses the attributes (specifically class/id/name ) instead of the text for matching, and the position, if the attributes are not unique enough, since I think in most cases the text changes more often than the structure.

Upvotes: 0

jcern
jcern

Reputation: 7848

Perhaps something like this:

function dompath( element )
{
    var path = '';
    for ( ; element && element.nodeType == 1; element = element.parentNode )
    {
        var inner = $(element).children().length == 0 ? $(element).text() : '';
        var eleSelector = element.tagName.toLowerCase() + 
           ((inner.length > 0) ? ':contains(\'' + inner + '\')' : '');
        path = ' ' + eleSelector + path;
    }
    return path;
}

This modified a method from another question to go through, and add in the full text contents of the tag via a :contains() operator only if the tag has no children tags.

I had tested with this method:

$(document).ready(function(){
    $('#p').click(function() {
      console.log(dompath(this));
    });
});

Against this:

<html>
    <body>
        <div>
            <div> </div>
        </div>
       <ul>
         <li></li>
         <li></li>
       </ul>
       <div>
         <ul>
           <li id="p">hi</li>
           <li></li>
           <li id="p2">hello world</li>
        </ul>
       </div>
   <body>
<html>

The results of clicking on p then get output as:

html body div ul li:contains('hi')

Upvotes: 16

Related Questions