List dragging

by Kingdaro

HTML

<div class='list-items'></div>

CSS

/*
  cosmetic information should be in the CSS styling
  specifically, parts of the app that the script doesn't functionally rely on,
  such as position:; and left/top:;
  */

body {
  font: 16pt sans-serif;
  padding: 50px;
}

.item {
  cursor: pointer;
  transition: 0.2s;
}

.item:hover {
  color: gray;
}

JavaScript

(function() {
  'use strict'
	
  // it's much more straight-forward logic-wise to keep internal data of what we have,
  // then editing the DOM based off of that
  // as opposed to just using the actual DOM to keep our information.
  // for example, adding a feature to the page to add new elements
  // is as easy as adding to this array, then calling reorderItems() again.
  let itemList = []

  function killEvent(event) {
    event.preventDefault()
    event.stopPropagation()
  }
  
  function setItemTop(item, top) {
    item.$element.style.top = (item.top = top) + 'px'
  }
  
  function correctItemElementPosition(item, index) {
  	setItemTop(item, index * 24)
  }
  
  function reorderItems(exception) {
  	// sort each item in the itemList array by the element's position
    itemList.sort((a, b) => Math.sign(a.top - b.top))
    
    // then reset each position of each item's element according to its index
    // make sure to keep the position of the exception item (for dragging, mainly)
    let top = exception.top
    itemList.forEach(correctItemElementPosition)
    setItemTop(exception, top)
  }

  function newItem(text) {
    let $element = document.createElement('div')
    
    // we *could* just parseInt() the element's style top whenever we needed it
    // buuuuut it's better logic-wise to keep an internal position
    let top = itemList.length * 24
    
    // <3 es6
    let item = { $element, top }

    $element.className = 'item'
    $element.innerText = text
    $element.style.position = 'absolute'
    $element.style.top = top + 'px'
    let transition = $element.style.transition

    function drag(event) {
      $element.style.top = (item.top += event.movementY) + 'px'
      reorderItems(item)
    }

    function startDrag(event) {
      document.addEventListener('mousemove', drag)
      
      // when the transition is enabled on a dragged element,
      // it'll feel awkward and unresponsive because of how CSS transition:; works
     ...