Ben Schnell
Ben Schnell

Reputation: 133

Update the displayed number to match its position in the order of elements in pure javascript

I have code that allows me to drag and drop to rearrange items and move them from container to container, but I also want to update the number inside the span tag to match it's position in order.

For example, "Colorado" is in the 0 position, but when I drag it after "Utah" I want to update "Montana" to have "0" next to it, "Utah" to "1", and "Colorado" to "2", but I don't know how to do that.

Here is the JS fiddle: https://jsfiddle.net/benschnell/0wbfoqk9/

const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')

draggables.forEach(draggable => {
  draggable.addEventListener('dragstart', () => {
    draggable.classList.add('dragging')
  })

  draggable.addEventListener('dragend', () => {
    draggable.classList.remove('dragging')
  })
})

containers.forEach(container => {
  container.addEventListener('dragover', dragOver);
})

function dragOver(e) {
   e.preventDefault()
    const afterElement = getDragAfterElement(this, e.clientY)
    const draggable = document.querySelector('.dragging')
    if (afterElement == null) {
      this.appendChild(draggable)
    } else {
      this.insertBefore(draggable, afterElement)
    }
}

function getDragAfterElement(container, y) {
  const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]

  return draggableElements.reduce((closest, child) => {
    const box = child.getBoundingClientRect()
    const offset = y - box.top - box.height / 2
    if (offset < 0 && offset > closest.offset) {
      return { offset: offset, element: child }
    } else {
      return closest
    }
  }, { offset: Number.NEGATIVE_INFINITY }).element
}
<ul class="container">
  <li class="draggable" draggable="true"><span>0</span> Colorado</li>
  <li class="draggable" draggable="true"><span>1</span> Montana</li>
  <li class="draggable" draggable="true"><span>2</span> Utah</li>
  <li class="draggable" draggable="true"><span>3</span> Montana</li>
  <li class="draggable" draggable="true"><span>4</span> Wyoming</li>
</ul>

<ul class="container">
  <li class="draggable" draggable="true"><span>0</span> Maine</li>
  <li class="draggable" draggable="true"><span>1</span> North Carolina</li>
  <li class="draggable" draggable="true"><span>2</span> Florida</li>
  <li class="draggable" draggable="true"><span>3</span> Virginia</li>
  <li class="draggable" draggable="true"><span>4</span> New York</li>
</ul>

Upvotes: 5

Views: 86

Answers (2)

Sascha A.
Sascha A.

Reputation: 4626

I added in the eventlistener for the draggable a call for update the numbers.

  draggable.addEventListener('dragend', (e) => {
    draggable.classList.remove('dragging');
    dragUpdateNr();
  })

This function looks first the UL's and goes with foreach over them. Here I search for all LI's. Over these founded elements I go with a simple for-loop and set the textContent from the first child (this is the SPAN with the number) to the index from the loop.

function dragUpdateNr() {
    let uls = document.querySelectorAll('ul.container');
    
    uls.forEach(ul => {
        let lis = ul.querySelectorAll('li.draggable');
        for (let i=0; i<lis.length; i++) {
            let li = lis[i];
            li.firstChild.textContent = i;
        }
    });
}

Here is your extended JSFifiddle for testing and here the hole code with these 2 extensions.

const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')

draggables.forEach(draggable => {
  draggable.addEventListener('dragstart', () => {
    draggable.classList.add('dragging')
  })

  draggable.addEventListener('dragend', (e) => {
    draggable.classList.remove('dragging');
    dragUpdateNr();
  })
})

containers.forEach(container => {
    container.addEventListener('dragover', dragOver);
})

function dragOver(e) {
     e.preventDefault()
    const afterElement = getDragAfterElement(this, e.clientY)
    const draggable = document.querySelector('.dragging')
    if (afterElement == null) {
      this.appendChild(draggable)
    } else {
      this.insertBefore(draggable, afterElement)
    }
}

function getDragAfterElement(container, y) {
  const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]

  return draggableElements.reduce((closest, child) => {
    const box = child.getBoundingClientRect()
    const offset = y - box.top - box.height / 2
    if (offset < 0 && offset > closest.offset) {
      return { offset: offset, element: child }
    } else {
      return closest
    }
  }, { offset: Number.NEGATIVE_INFINITY }).element
}

function dragUpdateNr() {
    let uls = document.querySelectorAll('ul.container');
    
    uls.forEach(ul => {
        let lis = ul.querySelectorAll('li.draggable');
        for (let i=0; i<lis.length; i++) {
            let li = lis[i];
            li.firstChild.textContent = i;
        }
    });
}
.container {
  max-width: 300px;
}
.container li {
  height: 30px;
  background-color: #fafafa;
  border: 1px solid #888;
  display: flex;
  align-items: center;
}
.dragging {
  opacity: 0.5;
}

.container li span {
  background-color: #000;
  margin-right: 10px;
  color: #fff;
  padding: 0 10px;
  height: 100%;
  display: flex;
  align-items: center;
}
  <ul class="container">
    <li class="draggable" draggable="true"><span>0</span> Colorado</li>
    <li class="draggable" draggable="true"><span>1</span> Montana</li>
    <li class="draggable" draggable="true"><span>2</span> Utah</li>
    <li class="draggable" draggable="true"><span>3</span> Montana</li>
    <li class="draggable" draggable="true"><span>4</span> Wyoming</li>
  </ul>

  <ul class="container">
    <li class="draggable" draggable="true"><span>0</span> Maine</li>
    <li class="draggable" draggable="true"><span>1</span> North Carolina</li>
    <li class="draggable" draggable="true"><span>2</span> Florida</li>
    <li class="draggable" draggable="true"><span>3</span> Virginia</li>
    <li class="draggable" draggable="true"><span>4</span> New York</li>
  </ul> 

Upvotes: 0

Brian Lee
Brian Lee

Reputation: 18197

You can loop the list elements in each container and renumber them according to their index:

const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')

draggables.forEach(draggable => {
  draggable.addEventListener('dragstart', () => {
    draggable.classList.add('dragging')
  })

  draggable.addEventListener('dragend', () => {
    draggable.classList.remove('dragging')
  })
})

containers.forEach(container => {
  container.addEventListener('dragover', dragOver);
})

function dragOver(e) {
  e.preventDefault()
  const afterElement = getDragAfterElement(this, e.clientY)
  const draggable = document.querySelector('.dragging')
  if (afterElement == null) {
    this.appendChild(draggable)
  } else {
    this.insertBefore(draggable, afterElement)
  }
  
  document.querySelectorAll('.container').forEach(c => {
    c.querySelectorAll('li.draggable').forEach((r, i) => {
      r.querySelector('span').innerHTML = i
    })
  })
}

function getDragAfterElement(container, y) {
  const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]

  return draggableElements.reduce((closest, child) => {
    const box = child.getBoundingClientRect()
    const offset = y - box.top - box.height / 2
    if (offset < 0 && offset > closest.offset) {
      return {
        offset: offset,
        element: child
      }
    } else {
      return closest
    }
  }, {
    offset: Number.NEGATIVE_INFINITY
  }).element
}
<ul class="container">
  <li class="draggable" draggable="true"><span>0</span> Colorado</li>
  <li class="draggable" draggable="true"><span>1</span> Montana</li>
  <li class="draggable" draggable="true"><span>2</span> Utah</li>
  <li class="draggable" draggable="true"><span>3</span> Montana</li>
  <li class="draggable" draggable="true"><span>4</span> Wyoming</li>
</ul>

<ul class="container">
  <li class="draggable" draggable="true"><span>0</span> Maine</li>
  <li class="draggable" draggable="true"><span>1</span> North Carolina</li>
  <li class="draggable" draggable="true"><span>2</span> Florida</li>
  <li class="draggable" draggable="true"><span>3</span> Virginia</li>
  <li class="draggable" draggable="true"><span>4</span> New York</li>
</ul>

Upvotes: 1

Related Questions