8

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!

Selvakumar Arumugam
  • 79,297
  • 15
  • 120
  • 134
owenmelbz
  • 6,180
  • 16
  • 63
  • 113
  • 2
    possible duplicate http://stackoverflow.com/questions/3454526/how-to-calculate-the-xpath-position-of-an-element-using-javascript – Teena Thomas Sep 28 '12 at 16:55
  • 1
    The difference being I don't want to use the index of an element like :eq(3) as if another element is added eq3 will be wrong which is why I want it to match the contents :) – owenmelbz Sep 28 '12 at 18:57
  • 1
    OK, but any kind of selector you use is vulnerable to *some* kinds of changes to the DOM. E.g. if you use `html body div ul li:contains('hello world')` but another
      element is added that contains `
    • hello world war ii
    • `, your path may select the wrong element. So you have to specify what kinds of differences your selector should be sensitive to.
    – LarsH Sep 28 '12 at 19:52
  • 1
    Okay then replace contains with [where text = hello world] is that any easier?? – owenmelbz Sep 28 '12 at 20:32
  • @OwenMelbourne, Please try to understand the comment of LarsH -- your reply to this comment is an evidence that you haven't understood this comment. – Dimitre Novatchev Oct 04 '12 at 04:47
  • Might I ask why you want to do this? Unless you have some very specific requirements I suspect you may be going about things the wrong way. – superluminary Oct 10 '12 at 10:33

4 Answers4

16

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')

jcern
  • 7,798
  • 4
  • 39
  • 47
6

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;
}
sshow
  • 8,820
  • 4
  • 51
  • 82
0

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.

BeniBela
  • 16,412
  • 4
  • 45
  • 52
0

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;
});
Louis Ricci
  • 20,804
  • 5
  • 48
  • 62
  • So what happens when you then come back to the page later and need to use the selector we previously saved into the database to select the element which no longer has the id you just saved to it? – owenmelbz Oct 11 '12 at 09:52
  • @Owen Melbourne - interesting thought, perhaps that caveat should be added to the question, so others can better interpret your 'use case'. I would say generating proper unique "id" for all of the elements you plan on referencing would be a good thing. Maybe you could use a hash of the element's tagName and contents for the id. – Louis Ricci Oct 11 '12 at 11:33