34

I'm trying to modify a page through JavaScript/CSS (much like Stylish or Greasemonkey do). This is a very complex page (that I didn't build, or can't modify pre-render), which makes constructing the CSS selector hard to do (manually looking at document structure). How can I achieve this?

Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
rcphq
  • 1,795
  • 3
  • 15
  • 13
  • Sorry, but how *can* you identify the element? Or do you mean build full DOM tree? – Shadow The GPT Wizard Jan 03 '11 at 20:39
  • my final intent is to create a css selector for the object. i can look at the pages code and deduct this path by looking at the document (sample selector from gmail.com "div.nH.T4.pp + div.pp + div.nH.pp.ps.TZ"), i'd like a way to help me build the selector. – rcphq Jan 03 '11 at 20:46
  • Hey rcphq did u find any solution for this problem I am also searching for same thing. – C J Jun 07 '12 at 03:39
  • Why not either use it's id if it has one, or just assign it a new id if it does not have one? – Stijn de Witt Nov 21 '12 at 22:37
  • This is a more complex problem than it seems and there are [good libraries that solve it](http://stackoverflow.com/questions/2068272/getting-a-jquery-selector-for-an-element/32218234#32218234). Also, it's a duplicate of [Getting a jQuery selector for an element](http://stackoverflow.com/questions/2068272/getting-a-jquery-selector-for-an-element). – Dan Dascalescu Aug 26 '15 at 05:05

6 Answers6

67
function fullPath(el){
  var names = [];
  while (el.parentNode){
    if (el.id){
      names.unshift('#'+el.id);
      break;
    }else{
      if (el==el.ownerDocument.documentElement) names.unshift(el.tagName);
      else{
        for (var c=1,e=el;e.previousElementSibling;e=e.previousElementSibling,c++);
        names.unshift(el.tagName+":nth-child("+c+")");
      }
      el=el.parentNode;
    }
  }
  return names.join(" > ");
}

console.log(  fullPath( $('input')[0] ) );
// "#search > DIV:nth-child(1) > INPUT:nth-child(1)"

This seems to be what you are asking for, but you may realize that this is not guaranteed to uniquely identify only one element. (For the above example, all the sibling inputs would be matched as well.)

Edit: Changed code to use nth-child instead of CSS classes to properly disambiguate for a single child.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 2
    nice succinct pure js function :) Just a slight note that the :nth-child wont work when applied to the HTML tag... or at least I can't get it to work in Firefox or Chrome... I guess it's probably due to the fact that has no parent. – Pebbl Aug 30 '12 at 00:15
  • no problem, it's not something I would have thought about either :) I've submitted a subtle edit changing "HTML" to "html" in your new revision... due to the .toLowerCase() method. – Pebbl Aug 30 '12 at 07:50
  • 2
    Note that since CSS selectors do not allow selecting DOM text nodes, this code will produce wrong result in such case (el.tagName will give undefined). – Grzegorz Luczywo Jun 26 '14 at 10:51
  • Not bad... might want to package it into a library, or maybe improve [one](https://github.com/fczbkk/css-selector-generator) of the [10+ ones](http://stackoverflow.com/questions/2068272/getting-a-jquery-selector-for-an-element/32218234#32218234) that do the same thing. – Dan Dascalescu Aug 26 '15 at 05:07
  • This will work nicely until they prepend another child, which is the inherent problem with `nth-child()` selectors. I would derive a selector using specificity instead. – thdoan May 31 '17 at 12:01
  • Thanks, for the code. My two cents: if you're using a newer javascript version you will want to use 'let' instead of 'var'. In this case, you will have to add 'let c, e;' *before* the 'for', and *remove* the 'var' within the 'for'. If you don't do this, 'c' will be 'undefined' in the line *after* the 'for'. See why at https://stackoverflow.com/a/11444416/2457251 – Almir Campos Jan 22 '19 at 00:11
23

I found I could actually use this code from chrome devtools source to solve this, without that many modifications.

After adding relevant methods from WebInspector.DOMPresentationUtils to new namespace, and fixing some differences, I simply call it like so:

> UTILS.cssPath(node)

For implementation example see css_path.js

tutuDajuju
  • 10,307
  • 6
  • 65
  • 88
  • 6
    This is by far the best solution I have found. I published it on npm for convenience: https://www.npmjs.com/package/cssman – Macks Jun 27 '15 at 13:37
  • 1
    @Macks: might you be willing to [publish the source on GitHub](https://github.com/maximilianschmitt/iniquest/issues/1)? – Dan Dascalescu Aug 26 '15 at 04:27
  • 3
    Actually the [Chromium code produces a lot of non-unique selectors](https://github.com/fczbkk/css-selector-generator-benchmark/blob/master/README.md#chromiums-dompresentationutils). – Dan Dascalescu Aug 26 '15 at 05:33
  • If by non unique you mean the produced selector should return 1 and only 1 element, it was never explicitly asked for by op. But should be fairly easy to modify code to check for siblings and get index or other distinct attribute – tutuDajuju Aug 26 '15 at 07:36
  • 1
    Why would it produce non-uniques? I thought it did n-th child things? – Tarwin Stroh-Spijer Nov 15 '16 at 01:47
20

Use FireFox with FireBug installed.

  • Right-click any element
  • Select "Inspect Element"
  • Right click the element in the HTML tree
  • Select "Copy XPath" or "Copy CSS Path"

Output for the permalink to this answer (XPath):

/html/body/div[4]/div[2]/div[2]/div[2]/div[3]/table/tbody/tr/td[2]/table/tbody/tr/td/div/a

CSS Path:

html body.question-page div.container div#content div#mainbar div#answers div#answer-4588287.answer table tbody tr td table.fw tbody tr td.vt div.post-menu a


But regarding this comment:

my final intent is to create a css selector for the object ...

If that is your intent, there may be an easier way through JavaScript:

var uniquePrefix = 'isThisUniqueEnough_';
var counterIndex = 0;
function addCssToElement(elem, cssText){
    var domId;
    if(elem.id)domId=elem.id;
    else{
        domId = uniquePrefix + (++counterIndex);
        elem.id = domId;
    }
    document.styleSheets[0].insertRule("#"+domId+"{"+cssText+"}");
}

The last line may need to be implemented differently for different browsers. Did not test.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Thanks, i was using chrome's version of firebug (lite) and couldn't find this option. Worked like a charm. – rcphq Jan 03 '11 at 21:22
6

Check this CSS selector generator library @medv/finder

  • Generates shortest selectors
  • Unique selectors per page
  • Stable and robust selectors
  • 2.9 kB gzip and minify size

Example of generated selector:

.blog > article:nth-child(3) .add-comment
Anton Medvedev
  • 3,393
  • 3
  • 28
  • 40
4

function getCssSelector(el)
{
    names = [];
    do {
        index = 0;
        var cursorElement = el;
        while (cursorElement !== null)
        {
            ++index;
            cursorElement = cursorElement.previousElementSibling;
        };
        names.unshift(el.tagName + ":nth-child(" + index + ")");
        el = el.parentElement;
    } while (el !== null);

    return names.join(" > ");
}
Yassine
  • 41
  • 1
0

you can use for css first-child pseudo classes if the element is a first child in a div table or body..etc

you can use jquery's nth child() function.

http://api.jquery.com/nth-child-selector/

example from jquery.com

<!DOCTYPE html>
<html>
<head>
  <style>

  div { float:left; }
  span { color:blue; }
  </style>
  <script src="http://code.jquery.com/jquery-1.4.4.js"></script>
</head>
<body>
  <div><ul>
    <li>John</li>
    <li>Karl</li>
    <li>Brandon</li>

  </ul></div>
  <div><ul>
    <li>Sam</li>
  </ul></div>

  <div><ul>
    <li>Glen</li>
    <li>Tane</li>
    <li>Ralph</li>

    <li>David</li>
  </ul></div>
<script>$("ul li:nth-child(2)").append("<span> - 2nd!</span>");</script>

</body>
</html>

my 2 cents if I understood the question correctly.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
andrewk
  • 3,721
  • 4
  • 30
  • 37
  • 1
    this would work, but im doing this on a page i didnt build so id need to somehow get what "nth-child" it is. this is my main problem. – rcphq Jan 03 '11 at 20:50