0

I just got a major bug that was really hard to solve and found out after a lot of work it was because two HTML elements had the same ID attribute.

Is there a command that spots double IDs across the whole DOM?


Update:

After reading all the comments I tested the different approaches in a relatively big website (http://www.powtoon.com html5 studio) and here are the results.

Please comment if you have better ways then these two.

Both answers returned the same results, but looks like the querySelectorAll was indeed faster:

QuerySelectorAll

function getDuplicateIdsUsingQuerySelector(){
  const idsOccurances = Array.from(document.querySelectorAll('[id]'))
    .map(elem => elem.id)
    .reduce((allIDs, id) => {
      allIDs[id] = (allIDs[id] || 0) + 1
      return allIDs
    }, {})
  return Object.entries(idsOccurances)
    .filter(([id, occurances]) => occurances > 1)
    .map(([id]) => id)
}

1.5ms per call on average.

Regex

function getDuplicateIdsUsingRegEx(){
  const idRegex = / id=".*?"/ig
  const ids = document.body.innerHTML.match(idRegex)
  const idsOccurances = ids.reduce((allIDs, id) => {
    allIDs[id] = (allIDs[id] || 0) + 1
    return allIDs
  }, {})

  return Object.entries(idsOccurances)
    .filter(([id, occurrences]) => occurrences > 1)
    .map(([id]) => id.slice(4, -1))
}

5.5ms per call average.

Vitali Zaidman
  • 819
  • 6
  • 22

6 Answers6

6

document.querySelectorAll('[id="dupid"]') would give you multiple results

[edit]

I made this jsfiddle with another way of finding all duplicates if you don't know what IDs to expect.

https://jsfiddle.net/edc4h5k2/

function checkAllIds() {
    var counts = {};
  var duplicates = new Set();
  function addCount(id) {
    if (counts[id]) {
        counts[id] += 1;
      duplicates.add(id);
    } else {
        counts[id] = 1;
    }
  }
  document.querySelectorAll("[id]").forEach(function(elem) {
    addCount(elem.id);
  })
  return Array.from(duplicates);
}
TKoL
  • 10,782
  • 1
  • 26
  • 50
3

Is there a command that spots double IDs across the whole DOM?

Simply, put this in your console

$("[id='idValue']").length

If the value is more than one, then you have a duplicate!

Once, you have spotted the fact that there are duplicates, then you need to check the hierarchy of the individual elements

$("[id='idValue']").each( function(){
  //traverse parents till body to see the dom structure to locate those duplicates
});

If you don't know the id value in advance, then first create the array of duplicate ids

var allIds = {};
var duplicateIds = [];
$( "[id]" ).each( function(){
    var idValue = $(this).attr("id");
    if ( allIds[ idValue ] )
    {
        duplicateIds.push ( idValue ); 
    }
    else
    {
        allIds[ idValue ]  = true;
    }
});

Now duplicateIds is the array containing the duplicate ids. Iterate the same to check its DOM hierarchy so that you can spot it in the entire DOM.

duplicateIds.forEach( function( idValue ){
   var $elementWithDuplicateId = $( "#" + idValue );
   //logic to traverse parents 
});
gurvinder372
  • 61,170
  • 7
  • 61
  • 75
2

If you are using Chrome you could run an audit by opening the console->Audits and check the checkbox for Best practices. If there are any double id's they show up there.

Mr. Greenwoodz
  • 235
  • 1
  • 10
1

This will give you an array of all IDs that occur more than once.

First all elements that have an ID are selected, then their IDs are extracted, then everything is reduced into an object that counts the occurrences. Of that object, the entries are converted into an array, which is filtered to only contain those that occur more than once, then only the ID names are extracted.

const duplicateIDs = Object.entries(Array.from(document.querySelectorAll("[id]"), ({id}) => id)
    .reduce((allIDs, id) => (allIDs[id] = (allIDs[id] || 0) + 1, allIDs), {}))
      .filter(([id, occurrences]) => occurrences > 1)
      .map(([id]) => id);

console.log(duplicateIDs);
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
<div id="e"></div>
<div id="f"></div>
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>

Here’s an alternative that uses Sets and Maps, and a lower number of intermediate arrays between Array methods:

const duplicateIDs = Array.from(Array.from(document.querySelectorAll("[id]"), ({id}) => id)
    .reduce((allIDs, id) => {
      if(allIDs.map.has(id)){
        allIDs.dupes.add(id);
      }
      else{
        allIDs.map.set(id, 1);
      }
      
      return allIDs;
    }, {
      map: new Map(),
      dupes: new Set()
    }).dupes);

console.log(duplicateIDs);
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
<div id="e"></div>
<div id="f"></div>
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
  • I will test if this is faster then the regex answer i found. – Vitali Zaidman Oct 18 '17 at 08:29
  • 1
    @VitalikZaidman Regex tends to always be slower than an approach based on DOM operations. However, this shouldn’t be an issue, since IDs are supposed to be unique anyway and thus both should ideally take about 0ms. Anyway, both approaches have the complexity _O(n)_, so their efficiency is equivalent. – Sebastian Simon Oct 18 '17 at 08:47
  • it is an issue for me because this code will be run as part of automatic tests and it will run again and again so every ms matters for me – Vitali Zaidman Oct 18 '17 at 08:48
0

Ideally you shouldn't have multiple elements with same id. I know your question is about detecting if there are elements with same id. IMHO, whatever you are trying with this probably would be a short term fix. You need to fix the root of the problem i.e. no two elements should have the same id.

0

After reading all the comments I tested the different approaches in a relatively big website (http://www.powtoon.com html5 studio) and here are the results.

Both answers returned the same results, but looks like the querySelectorAll was indeed faster:

QuerySelectorAll

function getDuplicateIdsUsingQuerySelector(){
  const idsOccurances = Array.from(document.querySelectorAll('[id]'))
    .map(elem => elem.id)
    .reduce((allIDs, id) => {
      allIDs[id] = (allIDs[id] || 0) + 1
      return allIDs
    }, {})
  return Object.entries(idsOccurances)
    .filter(([id, occurances]) => occurances > 1)
    .map(([id]) => id)
}

1.5ms per call on average.

Regex

function getDuplicateIdsUsingRegEx(){
  const idRegex = / id=".*?"/ig
  const ids = document.body.innerHTML.match(idRegex)
  const idsOccurances = ids.reduce((allIDs, id) => {
    allIDs[id] = (allIDs[id] || 0) + 1
    return allIDs
  }, {})

  return Object.entries(idsOccurances)
    .filter(([id, occurrences]) => occurrences > 1)
    .map(([id]) => id.slice(4, -1))
}

5.5ms per call average.

Vitali Zaidman
  • 819
  • 6
  • 22