19

I need to generate unique css selector for elements.
Particularly, I have onclick event handler that should remember what target element was clicked and send this info to my server. Is there a way to do it without doing DOM modifications?

P.S. my javascript code supposed to be run on different
3-rd party websites so I can't make any assumptions about html.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
tsds
  • 8,700
  • 12
  • 62
  • 83
  • 1
    Can you provide the html you need to select the element in? – Abe Petrillo Dec 21 '11 at 10:24
  • actually, I haven't got "my" html because my javascript code is inserted in some 3-rd party websites, so the html code has some arbitrary structure – tsds Dec 21 '11 at 10:26
  • possible 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:09
  • @Dan Dascalescu: I'd be careful with marking a question about standard selectors as a duplicate of a question about a well-known non-standard implementation. Most standard selectors are compatible with jQuery, but not the other way around. – BoltClock Apr 21 '16 at 15:59
  • @BoltClock: I did ask in a comment on that question whether the OP was actually looking for a jQuery or a CSS selector. – Dan Dascalescu Apr 22 '16 at 03:31
  • @Dan Dascalescu: Ah, sorry I missed that. The accepted answer makes use of `:eq()`, which suggests that the OP was indeed looking for a jQuery selector. I don't know if it's better to edit the question to remove all ambiguity, or close it. Clearly the OP isn't interested in responding to any further inquiries. But to be fair, it is a popular and useful question, albeit confusing. – BoltClock Apr 22 '16 at 04:29

7 Answers7

17

This function creates a long, but quite practical unique selector, works quickly.

const getCssSelector = (el) => {
  let path = [], parent;
  while (parent = el.parentNode) {
    path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el)+1})`);
    el = parent;
  }
  return `${path.join(' > ')}`.toLowerCase();
};

Example result:

html:nth-child(1) > body:nth-child(2) > div:nth-child(1) > div:nth-child(1) > main:nth-child(3) > div:nth-child(2) > p:nth-child(2)

The following code creates a slightly more beautiful and short selector

const getCssSelectorShort = (el) => {
  let path = [], parent;
  while (parent = el.parentNode) {
    let tag = el.tagName, siblings;
    path.unshift(
      el.id ? `#${el.id}` : (
        siblings = parent.children,
        [].filter.call(siblings, sibling => sibling.tagName === tag).length === 1 ? tag :
        `${tag}:nth-child(${1+[].indexOf.call(siblings, el)})`
      )
    );
    el = parent;
  };
  return `${path.join(' > ')}`.toLowerCase();
};

Example result:

html > body > div > div > main > div:nth-child(2) > p:nth-child(2)
redisko
  • 559
  • 5
  • 4
8

Check this CSS selector generator library @medv/finder

Build Status

  • 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

Yes, you could do this. But with a few caveats. In order to be able to guarantee that selectors are unique, you'd need to use :nth-child() which isn't universally supported. If you're then wanting to put these CSS selectors into CSS files, it won't work in all browsers.

I'd do it with something like this:

function () {
    if (this.id) {
        return sendToServer('#' + this.id);
    }
    var parent = this.parentNode;
    var selector = '>' + this.nodeName + ':nth-child(' + getChildNumber(this) ')';
    while (!parent.id && parent.nodeName.toLowerCase() !== 'body') {
        selector = '>' + this.nodeName + ':nth-child(' + getChildNumber(parent) + ')' + selector;
        parent = parent.parentNode;
    }
    if (parent.nodeName === 'body') {
        selector = 'body' + selector;
    } else {
        selector = '#' + parent.id + selector;
    }
    return sendToServer(selector);
}

Then add that to your click handler for each element you want to model. I'll leave you to implement getChildNumber().

Edit: Just seen your comment about it being 3rd party code... so you could add an event argument, replace all instances of this with event.target and then just attach the function to window's click event if that's easier.

Nathan MacInnes
  • 11,033
  • 4
  • 35
  • 50
  • 1
    Generating unique CSS selectors that ideally are somewhat robust to changes in the page structure is a complex problem. There are 10+ libraries that generate CSS selectors, and the author of one of them has published [this comparison](https://github.com/fczbkk/css-selector-generator-benchmark). – Dan Dascalescu Aug 26 '15 at 04:37
1
"use strict";
function getSelector(_context){
     var index, localName,pathSelector, that = _context, node;
     if(that =='null') throw 'not an  dom reference';
     index =  getIndex(that);

     while(that.tagName){
       pathSelector = that.localName+(pathSelector?'>'+pathSelector :'');
       that = that.parentNode;
     }
    pathSelector = pathSelector+':nth-of-type('+index+')';

    return pathSelector;
}

function getIndex(node){
    var i=1;
    var tagName = node.tagName;

    while(node.previousSibling){
    node = node.previousSibling;
        if(node.nodeType === 1 && (tagName.toLowerCase() == node.tagName.toLowerCase())){
            i++;
        }
    }
    return i;
}
document.addEventListener('DOMContentLoaded', function(){
   document.body.addEventListener('mouseover', function(e){
     var c = getSelector(e.target);
         var  element = document.querySelector(c);
          //console.log(element);
      console.log(c);
        //element.style.color = "red"

   });
});

you can try this one. without using jquery.

Maneesh Singh
  • 555
  • 2
  • 12
  • Using only `nth-of-type` selectors and ignoring ids makes for a selector that's less robust to changes in the page structure. This problem may seem simple, but in actuality it's a little more complex - generating unique CSS selectors that ideally are somewhat robust to changes in the page structure. There are 10+ libraries that generate CSS selectors, and the author of one of them has published [this comparison](https://github.com/fczbkk/css-selector-generator-benchmark). – Dan Dascalescu Aug 26 '15 at 04:36
1

The Firefox developer console uses the following heuristics to find a unique selector for an element. The algorithm returns the first suitable selector found. (Source)

  1. If the element has a unique id attribute, return the id selector #<idname>.

  2. If the tag is html, head, or body, return the type selector <elementname>.

  3. For each class of the element:

    1. If the class is unique to the element, return the class selector .<classname>.

    2. If the tag/class combination is unique, return the selector <elementname>.<classname>.

    3. Compute the element's index in its parent's child list. If the selector <elementname>.<classname>:nth-child(<index>) is unique, return that.

  4. Let index be the element's index in its parent's child list.

    1. If the parent node is the root node, return the selector <elementname>:nth-child(<index>).

    2. Otherwise, recursively find a unique selector for the parent node and return <parentselector> > <elementname>:nth-child(<index>).

augurar
  • 12,081
  • 6
  • 50
  • 65
1

You could probably traverse the DOM tree from the node back to the body element to generate a selector.

Firebug has a feature for this, both using XPath and CSS selectors.

See this answer

Community
  • 1
  • 1
Jani Hartikainen
  • 42,745
  • 10
  • 68
  • 86
  • can you suggest any javascript library that provides such a functionality? – tsds Dec 21 '11 at 10:33
  • 2
    @tsds: there are 10+ libraries that generate CSS selectors, and the author of one of them has published [this comparison](https://github.com/fczbkk/css-selector-generator-benchmark). – Dan Dascalescu Aug 26 '15 at 04:32
1

let say you have a list of links for the sake of simplicity: you can simply pass the index of the triggering element in the collection of all elements

<a href="#">...</a>
<a href="#">...</a>    
<a href="#">...</a>

the js (jQuery 1.7+, I used .on()otherwise use bind()) function can be

var triggers = $('a');
triggers.on('click', function(e) {
   e.preventDefault();
   var index = triggers.index($(this));
   /* ajax call passing index value */
});

so that if you click on third element index value passed will be 2. (0-based index); of course this is valid as long as the code (the DOM) doesn't change. Later you can use that index to create a css rule to that element e.g. using :nth-child

Otherwise if each one of your elements have a different attribute (like an id) you can pass that attribute

example on JsFiddle : http://jsfiddle.net/t7J8T/

Fabrizio Calderan
  • 120,726
  • 26
  • 164
  • 177
  • 2
    This is a simplistic solution for a rather complex problem - generating unique CSS selectors that ideally are somewhat robust to changes in the page structure. There are 10+ libraries that generate CSS selectors, and the author of one of them has published [this comparison](https://github.com/fczbkk/css-selector-generator-benchmark). – Dan Dascalescu Aug 26 '15 at 04:33
  • @Dan Dascalescu: Unfortunately, Selectors itself doesn't provide very much in the way of solving such a problem. Other than id and class selectors, pretty much everything else in Selectors is tightly coupled to document structure by design. The only selector you can construct that would uniquely identify an element regardless of structure is a lone id selector, and even that assumes the document is conformant. (I'm not sure if :root guarantees a unique element in a conforming document, but if it does, that would be just one other selector that does, and not a particularly useful one at that.) – BoltClock Apr 21 '16 at 15:46