Getting a jQuery selector for an element

I see now that a plugin existed (with the same name I thought of too), but here's just some quick JavaScript I wrote. It takes no consideration to the ids or classes of elements – only the structure (and adds :eq(x) where a node name is ambiguous).

jQuery.fn.getPath = function () {
    if (this.length != 1) throw 'Requires one element.';

    var path, node = this;
    while (node.length) {
        var realNode = node[0], name = realNode.name;
        if (!name) break;
        name = name.toLowerCase();

        var parent = node.parent();

        var siblings = parent.children(name);
        if (siblings.length > 1) { 
            name += ':eq(' + siblings.index(realNode) + ')';
        }

        path = name + (path ? '>' + path : '');
        node = parent;
    }

    return path;
};

(License: MIT)


jQuery-GetPath is a good starting point: it'll give you the item's ancestors, like this:

var path = $('#foo').getPath();
// e.g., "html > body > div#bar > ul#abc.def.ghi > li#foo"

TL;DR - this is a more complex problem than it seems and you should use a library.


This problem appears easy at the first glance, but it's trickier than it seems, just as replacing plain URLs with links is non-trivial. Some considerations:

  • Using descendant selectors vs. child selectors can lead to cases where the selector isn't unique.
  • Using :eq() limits the usefulness of the solution, as it will require jQuery
  • Using tag+nth-child selectors can result in unnecessarily long selectors
  • Not taking advantage of ids makes the selector less robust to changes in the page structure.

Further proof that the problem isn't as easy as it seems: there are 10+ libraries that generate CSS selectors, and the author of one of them has published this comparison.


Here's a version of Blixt's answer that works in IE:

jQuery.fn.getPath = function () {
    if (this.length != 1) throw 'Requires one element.';

    var path, node = this;
    while (node.length) {
        var realNode = node[0];
        var name = (

            // IE9 and non-IE
            realNode.localName ||

            // IE <= 8
            realNode.tagName ||
            realNode.nodeName

        );

        // on IE8, nodeName is '#document' at the top level, but we don't need that
        if (!name || name == '#document') break;

        name = name.toLowerCase();
        if (realNode.id) {
            // As soon as an id is found, there's no need to specify more.
            return name + '#' + realNode.id + (path ? '>' + path : '');
        } else if (realNode.className) {
            name += '.' + realNode.className.split(/\s+/).join('.');
        }

        var parent = node.parent(), siblings = parent.children(name);
        if (siblings.length > 1) name += ':eq(' + siblings.index(node) + ')';
        path = name + (path ? '>' + path : '');

        node = parent;
    }

    return path;
};