3

This question is nearly identical, but differs in one crucial aspect and does not help ultimately: the sequence of style properties.

That is, two elements with the same style properties -- but in different orders -- should be considered equivalent.

Equality should also consider descendants, and apply this logic recursively. That is, two elements with the same descendants are equal if the only differences are the order of style properties. (This was implied previously under an outdated understanding of IsEqualNode, which considered children under an old spec but no longer does under the new spec.)

However, the highest-rated answer treats two such elements as different, as illustrated here:

<div style="color: red; font-size: 28px">TEST A</div>
<div style="font-size: 28px; color: red">TEST A</div>

if ($("div")[0].isEqualNode($("div")[1])) {
  alert("Same");
} else {
  alert("Different");
}

How could you recognize two HTML elements as equal if the only difference between them is the order of style properties?

Crashalot
  • 33,605
  • 61
  • 269
  • 439

2 Answers2

4

Node.isEqualNode() does compare children. See the spec for the complete algorithm, if applied to elements:

A node A equals a node B if all of the following conditions are true:

  • A and B's nodeType attribute value is identical.
  • The following are also equal...:
    • [Element:] Its namespace, namespace prefix, local name, and its number of attributes in its attribute list.
  • If A is an element, each attribute in its attribute list has an attribute with the same namespace, local name, and value in B's attribute list.
  • A and B have the same number of children.
  • Each child of A equals the child of B at the identical index.

The problem remains the text comparison of the style attribute values, and the same argument - functionally the same, but textually different - can be applied to the class atribute.

A solution is to produce a clone that has a defined (alphabetical) order for the style properties and class names.

Element.style returns an object both with string keys and numeric keys. The first are a list containing all CSS properties in existence, whether they are really set in the style attribute or not. That is a long list, and most entries are the empty string.

That is where the numeric keys help: the object contains array-like entries listing all the property names that are actually set. Converting them with Array.from() to a real array makes it possible to only get the relevant parts and sort them.

Element.classList is an equally Array-like list.

Both properties are considered read-only, so to write back, use the basic method to write to a named attribute.

$.fn.normalizeTree = function () {
    return this.each(function () {
        $(this).add("*", this).each(function () {
            const sortedClass = Array.from(this.classList)
               .sort()
               .join(' ');
            $(this).attr('class', sortedClass);

            const sortedStyle = Array.from(this.style)
               .sort()
               .map(prop => `${prop}: ${this.style[prop]};`)
               .join('');
            $(this).attr('style', sortedStyle);
        });
    });
}

const rawNodes = $(".comparable");

if (rawNodes[0].isEqualNode(rawNodes[1])) {
  $("#result1").text("equal")
}

const normalNodes = rawNodes.clone().normalizeTree();

if (normalNodes[0].isEqualNode(normalNodes[1])) {
  $("#result2").text("equal")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="comparable">
  <p class="one two"><span style="color:rgb(0, 0, 0);font-family:sans-serif;font-size:15px">A text.</span></p>
</div>
<div class="comparable">
  <p class="two one"><span style="font-size:15px;font-family: sans-serif;color:rgb(0, 0, 0);">A text.</span></p>
</div>

<p>These are <span id="result1">not equal</span> if raw.</p>
<p>These are <span id="result2">not equal</span> if normalized.</p>
xmedeko
  • 7,336
  • 6
  • 55
  • 85
ccprog
  • 20,308
  • 4
  • 27
  • 44
  • @T.J.Crowder sorry about missing that. Please understand that while I criticise your solution, mine started out as a fork of yours (and then differences piled up), and due credit goes to you for pointing me in the right direction. Re the numeric keys, your solution loops through all object props - and that is a long list! - while the numeric keys just list which properties are really set. – ccprog Jun 20 '19 at 16:31
  • No worries at all! It was well-spotted. :-) Ah, thanks. So I really should be doing `for (const key of Array.from(astyle))`. It didn't occur to me that there would be extant own properties for style props that weren't set! What an odd way to define the object... – T.J. Crowder Jun 20 '19 at 17:02
  • On the whole my answer wasn't worth keeping in the end. Glad it helped inspire your answer, though. Thanks for teaching me about the style object's weird setup and also about `isEqualNode`! Also, [FYI](https://stackoverflow.com/a/10679802/157247). :-) – T.J. Crowder Jun 20 '19 at 17:13
1

I was adding this as an edit to your question, but @Doğancan Arabacı and @Shree rejected it and said its better suited as an answer despite it simply fixing what I believe to be an error.

In reference to "How could you recognize two HTML elements as equal if the only difference between them is the order of style properties?", your code would print "Different" even if the style properties were same because the id and the text inside are still different.

Hence, I changed the code to this, in which the only difference is the order of styles. I don't have commenting privileges yet, so I needed to do this instead.

<div class="a" style="color: red; font-size: 28px;">TEST A</div>
<div class="a" style="font-size: 28px; color: red;">TEST A</div>

<script>
var x = document.getElementsByClassName("a");  
if (x[0].isEqualNode(x[1])) alert("Same");
else alert("Different");
</script>

I do not believe the other answers are correct, because there are more differences other than the order of styles in your code, and hence it should print false regardless.

Arvind
  • 155
  • 3
  • 12