0

In jQuery to traverse DOM several steps upward instead of

 $(this).parent().parent().parent().parent().parent().click();

I can write short version:

 $(this).parent(5).click();

So, I want to know, if there's a way to shorten code instead of spamming '.parentNode'?

  • 1
    I personally avoid `parent()` except for rare occasions. I strongly prefer [closest()](https://api.jquery.com/closest/) - finds the closest element that matches the selector: `$(this).closest('div.some-class');`.... – random_user_name Jul 29 '17 at 16:16
  • 1
    have you tried using a loop? – Mehrad Jul 29 '17 at 16:16
  • Possible duplicate of [Using parentNode multiple times](https://stackoverflow.com/questions/22151440/using-parentnode-multiple-times) – P.S. Jul 29 '17 at 16:16
  • The same way jQuery does it: write a function that loops `.parentNode` n times. – JJJ Jul 29 '17 at 16:17

2 Answers2

2

Rather trivial:

function parent (element, n = 1) {
  let {parentNode} = element;
  for (let i = 1; parentNode && i < n; i++) {
    ({parentNode} = parentNode);
  }
  return parentNode;
}

const span = document.querySelector('span');
const directParent = parent(span);                  // direct parent node
const greatGreatGreatGrandParent = parent(span, 5); // parent node 5 times
console.log(directParent.getAttribute('data-foo'));                // baz
console.log(greatGreatGreatGrandParent.getAttribute('data-foo')); // bar
<div data-foo="bar">
  <div>
    <div>
       <div>
         <div data-foo="baz">
          <span>Hello, World!</span>
         </div>
      </div>
    </div>
  </div>
</div>

I check for parentNode because it might be null. In that case, I break the loop and return null, because continuing the loop would result in an error:

({parentNode} = null); // TypeError: can't convert null to object

The parentheses are necessary to indicate that the opening { doesn't start a block, but a destructuring assignment:

{parentNode} = parentNode; // SyntaxError: expected expression, got '='

Edit:

I must admit that my solution is rather condensed and uses some new JavaScript features to make it even more succinct.

What does let {parentNode} = element; mean?

Object destructuring lets you read properties from an object in a more succinct way:

let {parentNode} = element;

is equivalent to:

let parentNode = element.parentNode;

As explained above,

({parentNode} = parentNode);

is equivalent to:

parentNode = parentNode.parentNode;

What does parentNode && i < n exactly?

Every for-loop has a condition. If this condition is true, the loop is continued. If the condition is false, it breaks out of the loop.

I could write:

for (let i = 1; i < n; i++) {
  // ...
}

That would run the loop n - 1 iterations. After the last iteration, i is equal to n, so the condition evaluates to false and it stops, which is fine.

parentNode && i < n extends this condition. It not only checks whether i is less than n, but it also checks if parentNode contains a truthy value. If parentNode is null (which might happen is an element has no parent node), the loop will break, because it can't read the property parentNode of null.


I hope you understand the explanations with the following function which is equivalent to the original one:

function parent (element, times) {
  var n; // how many times 'parentNode'?
  if (times) {
    n = times;
  } else {
    n = 1;
  }
  var elementToReturn = element.parentNode;
  for (var i = 1; i < n; i++) {
    if (elementToReturn === null) {
      break;
    } else {
      elementToReturn = elementToReturn.parentNode;
    }
  }
  return elementToReturn;
}
PeterMader
  • 6,987
  • 1
  • 21
  • 31
  • Wow, can you briefly explain the code, please? why are you checking for `parent` in the `for` loop and why `({parentNode} = parentNode)`? I read about the object/array destructuring but I can't understand that. – Marco Luzzara Jul 29 '17 at 16:39
  • The `parent` was a typo. I should be `parentNode`. – PeterMader Jul 29 '17 at 17:14
  • What `parentNode && i < n` does exactly? – dementialover Aug 01 '17 at 06:58
  • And `let {parentNode} = element;` as well. And difference between `parentNode` and `{parentNode}`. Object destructuring is above me for now.. – dementialover Aug 01 '17 at 07:23
  • Okay, so far so good. I've managed to solve issue that was bugging me (my code is nice and short now), thus thanks for that. Do you know, though, how to chain that function? I've tried `this.parent(5).style.display = 'none;'`, and it ignored me. – dementialover Aug 01 '17 at 16:15
  • You have to call it as `parent(this, 5).style.display = ...`. If you want to be able to call it as `this.parent(...)`, you have to assign it to `HTMLElement.prototype` ([more info](https://stackoverflow.com/questions/4670361/how-to-add-my-own-methods-to-htmlelement-object)). But I recommend you don't do that. – PeterMader Aug 01 '17 at 19:27
0

The sorcerer's one-liner... It's the same as @petermader's only shorter. I guess there's less of a need to be super explicit here, as this would probably be just an imported utility function, and it's still more reliable than a for loop.

const getParentElement = ({ parentNode }, n = 1) =>
  Array.from({ length: n - 1 }, () => ({ parentNode } = parentNode)) && parentNode
Andrea
  • 553
  • 9
  • 12