shubham agrawal
shubham agrawal

Reputation: 3541

JQuery Traversing through DOM on click

I am having problem of traversing through each HTML element one by one.There are two buttons #up and #down.On click of #up the id #myID should move to the next element upwards and vice versa for #down.The problem is I am able to move through the siblings but not through the child elements.

For example if I click on #down the id #myID should have moved to p tag which is the child of that div on next click to span which is child of p then on next click to div.But in my code it is directly jumping to div ignoring the children.

JSFIDDLE

Here is the code:

$("#up").click(function() {
  $("#startHere").find("#myID").next().attr('id', 'myID');
  $('#startHere').find("#myID").removeAttr('id');
});
$("#down").click(function() {
  $("#startHere").find("#myID").prev().attr('id', 'myID');
  $('#startHere').find("#myID").next().removeAttr('id');
})
#myID {
  border: 2px solid yellow;
}

#startHere {
  height: 100%;
  width: 50%;
}

div {
  height: 100px;
  width: 100px;
  border: 2px solid;
  margin: 10px;
}

p {
  height: 50px;
  width: 50px;
  border: 2px solid blue;
  margin: 10px;
}

h1 {
  height: 40px;
  width: 40px;
  border: 2px solid red;
  margin: 10px;
}

span {
  display: block;
  height: 25px;
  width: 25px;
  border: 2px solid green;
  margin: 10px;
}

button {
  height: 25px;
  width: 100px;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="up">GO DOWN</button>
<button id="down">GO UP</button>
<div id="startHere">
  <div id="myID">
    <p><span></span></p>
  </div>
  <div><span></span></div>
  <div>
    <h1></h1>
  </div>
  <p></p>
  <h1></h1>
  <p><span></span></p>
</div>

Upvotes: 1

Views: 292

Answers (1)

Raphael Schweikert
Raphael Schweikert

Reputation: 18556

I think you can just find all the elements first, jQuery returns them in DOM order, which is what you want. No need to search for the next/prev element on-the-fly.

var allElements = $("#startHere").find('*');
var currentIndex = allElements.index('#myID');

function move(delta) {
    // Find the new index
    var index = currentIndex + delta;
    // Clamp to 0…lengh of list
    // Here we could also make it wrap instead
    index = Math.max(Math.min(index, allElements.length - 1), 0);
    // Remove the ID from the old element
    allElements.eq(currentIndex).removeAttr('id');
    // Add the ID to the new element
    allElements.eq(index).attr('id', 'myID');
    // Update the index
    currentIndex = index;
}

$("#up").click(function() {
  move(1);
});
$("#down").click(function() {
  move(-1);
})

var allElements = $("#startHere").find('*');
var currentIndex = allElements.index('#myID');

function move(delta) {
    // Find the new index
    var index = currentIndex + delta;
    // Clamp to 0…lengh of list
    // Here we could also make it wrap instead
    index = Math.max(Math.min(index, allElements.length - 1), 0);
    // Remove the ID from the old element
    allElements.eq(currentIndex).removeAttr('id');
    // Add the ID to the new element
    allElements.eq(index).attr('id', 'myID');
    // Update the index
    currentIndex = index;
}

$("#up").click(function() {
  move(-1);
});
$("#down").click(function() {
  move(1);
})
#myID {
  border: 2px solid yellow;
}

#startHere {
  height: 100%;
  width: 50%;
}

div {
  height: 100px;
  width: 100px;
  border: 2px solid;
  margin: 10px;
}

p {
  height: 50px;
  width: 50px;
  border: 2px solid blue;
  margin: 10px;
}

h1 {
  height: 40px;
  width: 40px;
  border: 2px solid red;
  margin: 10px;
}

span {
  display: block;
  height: 25px;
  width: 25px;
  border: 2px solid green;
  margin: 10px;
}

button {
  height: 25px;
  width: 100px;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="down">GO DOWN</button>
<button id="up">GO UP</button>
<div id="startHere">
  <div id="myID">
    <p><span></span></p>
  </div>
  <div><span></span></div>
  <div>
    <h1></h1>
  </div>
  <p></p>
  <h1></h1>
  <p><span></span></p>
</div>

If you do need the elements on-the-fly (because they might have changed), you can still use the same tactic (and simply build up the allElements list in the move function and get the index using allElements.index('#myID')) but it might be more performant to update the list only when you know it changed (after an Ajax request, after modification on event handlers, etc.).

Edit:

The code for searching the next/prev element on-the-fly is a bit more work because it has to recurse when traversing up but makes it possible to have a different set of rules for up vs. down movement.

var boundary = $("#startHere");

function findNext(node, anchor) {
  if(!anchor && node.children(':first-child').length) {
    return node.children(':first-child');
  }
  if(node.next().length) {
    return node.next();
  }
  if(!boundary.find(node.parent()).length) {
    // Out of boundary. Stick to the last node
    return anchor||node;
  }
  return findNext(node.parent(), anchor||node);
}

function findPrev(node, anchor) {
  if(!anchor && node.children(':last-child').length) {
    return node.children(':last-child');
  }
  if(node.prev().length) {
    return node.prev();
  }
  if(!boundary.find(node.parent()).length) {
    // Out of boundary. Stick to the last node
    return anchor||node;
  }
  return findPrev(node.parent(), anchor||node);
}

function move(finder) {
  // Find the current item
  var current = boundary.find('#myID');
  // Find the next item
  var next = finder(current);
  // Remove the ID from the old element
  current.removeAttr('id');
  // Add the ID to the new element
  next.attr('id', 'myID');
}

$("#up").click(function() {
  move(findPrev);
});
$("#down").click(function() {
  move(findNext);
})

var boundary = $("#startHere");

function findNext(node, anchor) {
  if(!anchor && node.children(':first-child').length) {
    return node.children(':first-child');
  }
  if(node.next().length) {
    return node.next();
  }
  if(!boundary.find(node.parent()).length) {
    // Out of boundary. Stick to the last node
    return anchor||node;
  }
  return findNext(node.parent(), anchor||node);
}

function findPrev(node, anchor) {
  if(!anchor && node.children(':last-child').length) {
    return node.children(':last-child');
  }
  if(node.prev().length) {
    return node.prev();
  }
  if(!boundary.find(node.parent()).length) {
    // Out of boundary. Stick to the last node
    return anchor||node;
  }
  return findPrev(node.parent(), anchor||node);
}

function move(finder) {
  // Find the current item
  var current = boundary.find('#myID');
  // Find the next item
  var next = finder(current);
  // Remove the ID from the old element
  current.removeAttr('id');
  // Add the ID to the new element
  next.attr('id', 'myID');
}

$("#up").click(function() {
  move(findPrev);
});
$("#down").click(function() {
  move(findNext);
})
#myID {
  border: 2px solid yellow;
}

#startHere {
  height: 100%;
  width: 50%;
}

div {
  height: 100px;
  width: 100px;
  border: 2px solid;
  margin: 10px;
}

p {
  height: 50px;
  width: 50px;
  border: 2px solid blue;
  margin: 10px;
}

h1 {
  height: 40px;
  width: 40px;
  border: 2px solid red;
  margin: 10px;
}

span {
  display: block;
  height: 25px;
  width: 25px;
  border: 2px solid green;
  margin: 10px;
}

button {
  height: 25px;
  width: 100px;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="down">GO DOWN</button>
<button id="up">GO UP</button>
<div id="startHere">
  <div id="myID">
    <p><span></span></p>
  </div>
  <div><span></span></div>
  <div>
    <h1></h1>
  </div>
  <p></p>
  <h1></h1>
  <p><span></span></p>
</div>

This is really bad UI. To select some nodes in some states, you first have to navigate “UP” and then “DOWN” again. But it seems to do what you ask for.

Upvotes: 4

Related Questions