Can I perform "Copy Selector" with javascript without using Chrome Dev tools [duplicate]

I am moving elements using javascript and I need to create a logic for the combinations happening during the drag/drops

I'm trying to get details from the elements, a CSS like selector could be also good, but dunno if it is possible.. (like copy-selector in chrome dev tools)

document.onmouseup = function(e){
    targetDest = e.target;
    //console.log('targetDest: ', targetDest);

    let 
    indexA = Array.from(targetCurr.parentNode.children).indexOf(targetCurr),
    indexB = Array.from(targetDest.parentNode.children).indexOf(targetDest);

    console.log(indexA, indexB);


    if(targetDest != targetCurr){
        if(targetDest == document.documentElement){
            console.log('document');
        }
        else if(targetDest == undefined){
            console.log('undefined');
        }
        else if(!targetDest){
            console.log('!dest');
        }
        else if(targetDest == null){
            console.log('null');
        }
        else if(targetDest == false){
            console.log('false');
        }
        else{
            console.log('else');
            //targetCurr.parentNode.insertBefore(targetDest, targetCurr);

            //console.log('...');
        }
    }else{
        console.log('itself');
    }


}

Solution 1:

Keep in mind that this will not necessarily uniquely identify elements. But, you can construct that type of selector by traversing upwards from the node and prepending the element you're at. You could potentially do something like this

var generateQuerySelector = function(el) {
      if (el.tagName.toLowerCase() == "html")
          return "HTML";
      var str = el.tagName;
      str += (el.id != "") ? "#" + el.id : "";
      if (el.className) {
          var classes = el.className.split(/\s/);
          for (var i = 0; i < classes.length; i++) {
              str += "." + classes[i]
          }
      }
      return generateQuerySelector(el.parentNode) + " > " + str;
}

var qStr = generateQuerySelector(document.querySelector("div.moo"));
alert(qStr);
body
<div class="outer">
  div.outer
  <div class="inner" id="foo">
    div#foo.inner
    <div class="moo man">
      div.moo.man
    </div>
  </div>
</div>

I wouldn't suggest using this for much besides presenting the information to a user. Splitting it up and reusing parts are bound to cause problems.

Solution 2:

My solution using :nth-child:

function getSelector(elm)
{
if (elm.tagName === "BODY") return "BODY";
const names = [];
while (elm.parentElement && elm.tagName !== "BODY") {
    if (elm.id) {
        names.unshift("#" + elm.getAttribute("id")); // getAttribute, because `elm.id` could also return a child element with name "id"
        break; // Because ID should be unique, no more is needed. Remove the break, if you always want a full path.
    } else {
        let c = 1, e = elm;
        for (; e.previousElementSibling; e = e.previousElementSibling, c++) ;
        names.unshift(elm.tagName + ":nth-child(" + c + ")");
    }
    elm = elm.parentElement;
}
return names.join(">");
}

var qStr = getSelector(document.querySelector("div.moo"));
alert(qStr);
body
<div class="outer">
  div.outer
  <div class="inner" id="foo">
    div#foo.inner
    <div class="moo man">
      div.moo.man
    </div>
  </div>
</div>

Please note it won't return the whole path if there's an element with ID in it - every ID should be unique on the page, as valid HTML requires.

I use output of this function in document.querySelector later in the code, because I needed to return focus to the same element after replaceChild of its parent element.

I hope CollinD won't mind I borrowed his markup for the code snippet :-)

Solution 3:

I mixed the 2 solutions proposed to have a result readable by humans and which gives the right element if there are several similar siblings:

function elemToSelector(elem) {
  const {
    tagName,
    id,
    className,
    parentNode
  } = elem;

  if (tagName === 'HTML') return 'HTML';

  let str = tagName;

  str += (id !== '') ? `#${id}` : '';

  if (className) {
    const classes = className.split(/\s/);
    for (let i = 0; i < classes.length; i++) {
      str += `.${classes[i]}`;
    }
  }

  let childIndex = 1;

  for (let e = elem; e.previousElementSibling; e = e.previousElementSibling) {
    childIndex += 1;
  }

  str += `:nth-child(${childIndex})`;

  return `${elemToSelector(parentNode)} > ${str}`;
}

Test with:

// Select an element in Elements tab of your navigator Devtools, or replace $0

document.querySelector(elemToSelector($0)) === $0 &&
document.querySelectorAll(elemToSelector($0)).length === 1

Which might give you something like, it's a bit longer but it's readable and it always works:

HTML > BODY:nth-child(2) > DIV.container:nth-child(2) > DIV.row:nth-child(2) > DIV.col-md-4:nth-child(2) > DIV.sidebar:nth-child(1) > DIV.sidebar-wrapper:nth-child(2) > DIV.my-4:nth-child(1) > H4:nth-child(3)

Edit: I just found the package unique-selector