18
var topClick = function() {
  child = this.parentNode.parentNode.parentNode.parentNode;
  child.parentNode.removeChild(child);
  var theFirstChild = document.querySelector(".m-ctt .slt");
  data[child.getAttribute("data-id")].rank = 5;
  insertBlogs();
};

As you see, there is a part of my code like this:

this.parentNode.parentNode.parentNode.parentNode;  

Is there another way to optimize the code (without using jQuery)?

thanksd
  • 44,567
  • 20
  • 126
  • 130
yaochiqkl
  • 469
  • 3
  • 13
  • 1
    Have you tried a loop? – Bergi Feb 01 '16 at 11:45
  • Or you can do $("elem").parents("parentElementSelector") that you are looking for. – HGrover Feb 01 '16 at 11:45
  • 2
    @Bergi — A loop would be less efficient. The OP knows how far up the tree they want to go. The object seems to be "less typing" rather than "Quickly searching for an element that matches a rule". – Quentin Feb 01 '16 at 11:47
  • you can use document.querySelector('parentElement selector') for that – kp singh Feb 01 '16 at 11:48
  • 2
    @kpsingh — That searches in the wrong direction. The question is looking **up** the tree, not down it. – Quentin Feb 01 '16 at 11:53
  • 1
    In supported browsers, there is a [native `.closest()` method](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest) (pure JavaScript) that works similarly to jQuery's `.closest()` method. – Josh Crozier Feb 01 '16 at 14:29
  • 2
    You might also want to think about the way you name your variables. – Stephan Bijzitter Feb 01 '16 at 17:51
  • What is your use case here? Genuinely curious. – Monkpit Feb 01 '16 at 20:54
  • There is a list of blogs.Each blog has a button like "edit" and "delete". When I click such buttons I want to find it's blog element . @Monkpit – yaochiqkl Feb 02 '16 at 07:27

4 Answers4

32

You can use a non recursive helper function, for example:

function nthParent(element, n) {
  while(n-- && element)  
    element = element.parentNode;
  return element;
}
hansmaad
  • 16,551
  • 7
  • 46
  • 89
28

You can use recursive helper function, for example:

function getNthParent(elem, n) {
    return n === 0 ? elem : getNthParent(elem.parentNode, n - 1);
}

var child = getNthParent(someElement, 4);
madox2
  • 39,489
  • 13
  • 88
  • 91
  • 1
    Thank you! Finally I accept your method . But when use a variable initialized with a Function Expression to define this It says `Uncaught ReferenceError: getNthParent is not defined` So I changed a little bit `return n === 0 ? elem : arguments.callee(elem.parentNode, n - 1);` – yaochiqkl Feb 01 '16 at 12:41
  • 3
    @yaochiqkl `arguments.callee` looks little bit cumbersome to me. I would use named function expression instead. `var myFn = function getNthParent(elem, n) { ... }` – madox2 Feb 01 '16 at 12:48
8

An alternative approach


Your goal

According to your comments on the original question, your overall goal is this:

There is a list of blogs.Each blog has a button like "edit" and "delete". When I click such buttons I want to find it's blog element.

I believe the best approach to solve the problem you're facing (as opposed to answering the question you asked - sorry!) is to approach the problem in a different manner.

From your comments, you said you have something like this:

<ul id="blog-list">
  <li class="blog-item">
    <a href="blog1.com">
      Boats galore!
    </a>
    <span class="blog-description">
      This is a blog about some of the best boats from Instagram.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
  <li class="blog-item">
    <a href="blog2.com">
      Goats galore!
    </a>
    <span class="blog-description">
      A blog about the everyday adventures of goats in South Africa.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
  <li class="blog-item">
    <a class="blog-link" href="blog3.com">
      Totes galore!
    </a>
    <span class="blog-description">
      A blog about containers and bags, and the owners who love them.
    </span>
    <span class="blog-button delete-button">
      Delete
    </span>
    <span class="blog-button edit-button">
      Edit
    </span>
  </li>
</ul>

And your goal is to add click event handlers to the button elements for each blog-link item.

So let's just translate that goal from plain English into pseudo-code:

for each `blog-link` `b`
  delete_button.onclick = delete_handler(`b`);
  edit_button.onclick = edit_handler(`b`);

Example script

Working example at: http://jsfiddle.net/vbt6bjwy/10/

<script>
  function deleteClickHandler(event, parent) {
    event.stopPropagation();
    parent.remove();
  }

  function editClickHandler(event, parent) {
    event.stopPropagation();
    var description = parent.getElementsByClassName("blog-description")[0];
    description.innerHTML = prompt("Enter a new description:");
  }

  function addClickHandlers() {
    var bloglistElement = document.getElementById("blog-list");
    var blogitems = bloglistElement.getElementsByClassName("blog-item");

    blogitems.forEach(function(blogitem){
      var deleteButtons = blogitem.getElementsByClassName("delete-button");

      deleteButtons.forEach(function(deleteButton){
        deleteButton.onclick = function(event) {
          return deleteClickHandler(event, blogitem);
        }
      });

      var editButtons = blogitem.getElementsByClassName("edit-button");

      editButtons.forEach(function(editButton){
        editButton.onclick = function(event) {
          return editClickHandler(event, blogitem);
        }
      });
    });
  }

  HTMLCollection.prototype.forEach = Array.prototype.forEach;
  addClickHandlers();
</script>

Explanation

The way you've chosen to implement a solution is valid, but I thought I'd give you a different way to look at the same problem.

In my opinion, the inner tags of the blog entity should not have to have knowledge of the structure or properties of the surrounding HTML for your edit and delete buttons to work.

Your original solution has to work backwards from each button up the chain of parents until it finds what it assumes is the correct parent element, based on a brittle method like hard-coding moving up N times in the chain of parent elements. Wouldn't it be nicer if we could use normal JavaScript element selection to be absolutely sure of what we're selecting? That way, no matter how the HTML structure might change, our JavaScript isn't going to break as long as the classes and IDs remain consistent.

This solution iterates over every blog-item in the #blog-list element:

blogitems.forEach(function(blogitem){ ... });

Within the forEach loop, we grab arrays containing .delete-button and .edit-button elements. On each of those elements, we add the appropriate event handler to the onclick property:

deleteButtons.forEach(function(deleteButton){
  deleteButton.onclick = function(event) {
    return deleteClickHandler(event, blogitem);
  }
});

For each deleteButton element in the array, we assign an anonymous function to the event handler onclick. Creating this anonymous function allows us to create a closure.

This means each deleteButton.onclick function will individually "remember" which blogitem it belongs to.

Check out this SO question/answer about closures for more info: How do JavaScript closures work?

And we throw in the HTMLCollection.prototype.forEach... line to provide forEach functionality to all HTMLCollection objects. Functions like getElementsByClassName return an HTMLCollection object. It acts exactly like an array for the most part, but it doesn't let us use forEach by default. A note about compatibility: you can certainly use a standard for loop, since forEach isn't available in all browsers (mostly old IE browsers are to blame). I prefer the forEach idiom.

End result

The end result is a little bit longer code, since the scope of the problem is a little wider than the question you actually asked. The advantage is that this code is much more flexible and resistant to being broken by changes to the HTML structure.

Community
  • 1
  • 1
Monkpit
  • 2,058
  • 1
  • 23
  • 32
  • 1
    I'm so moved that you taught me enthusuiasticly . And I really learned a lot form it , knowing how to solve it in a better way as well as getting deeper understanding of the closure . I'll read it repeatedly to acquire this method . Thanks a lot! – yaochiqkl Feb 03 '16 at 02:32
  • @yaochiqkl I'm glad you could benefit from my answer. If you're interested in JavaScript and reading more about things like closures, I suggest Crockford's [Javascript: the Good Parts](http://shop.oreilly.com/product/mobile/9780596517748.do). In particular, chapter 4 covers functions, scoping in JS, and closures in JS. The whole book is short enough to read through it quickly, but it's packed with great info. It was published in 2008, but it is still very relevant to modern JS. – Monkpit Feb 03 '16 at 03:48
2
var topClick = function(event){
    console.log(event);
    child = this.parentNode.parentNode.parentNode.parentNode;
    child.parentNode.removeChild(child);
    var theFirstChild = document.querySelector(".m-ctt .slt");
    data[child.getAttribute("data-id")].rank = 5;
    insertBlogs();
};

Surprise! I print the event through console.log. And find a array like this inside the event: enter image description here

And I find the element that I want :

function(event){
console.log(event.path[4]);
child = event.path[4];
}

and it work! How magic! Maybe event agent is another way!
Thank you all the same for giving answers!
Next time before asking questions I'll think over first! :D

yaochiqkl
  • 469
  • 3
  • 13
  • 8
    This is not a standard and may not be supported in every browser. See http://stackoverflow.com/a/30021867/2011448 – Radek Pech Feb 01 '16 at 12:01
  • 3
    This assumes information that wasn't given in the question. (I know it was *your* question, but still...) – nnnnnn Feb 01 '16 at 12:09