Drag and Drop List
A list that can be reordered.
by Perry Kaufman
December 31, 2018
HTML
<section class="pk-section">
<h1 class="pk-h1"> Drag and Drop List</h1>
<p class="pk-p b">By Perry Kaufman</p>
<p class="pk-p">Click and drag an item to reorder the list!</p>
<form class="pk-list-form"></form>
<ul class="pk-list"></ul>
</section>
SCSS
/* Fonts */
@import url('https://fonts.googleapis.com/css?family=Roboto');
$fonts: 'Roboto', arial, sans-serif;
/* Colors */
$black: black;
$gray: gray;
$lightgray: lightgray;
$lightteal: rgb(194,227,227);
$teal: rgb(6,152,152);
$white: white;
/* Sizing */
$box-dim: 20px;
$form-input-padding: 2px 5px;
$item-border: 1px;
$item-padding: 5px;
$item-height: 40px;
$item-width: 280px;
$list-padding-lat: 0px;
$list-padding-vert: 5px;
$list-width: 280px;
$slot-padding: 3px;
/* Mixins */
@mixin user-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin user-drag {
user-drag: none;
-webkit-user-drag: none;
}
@mixin transform($x) {
-webkit-transform: $x;
-ms-transform: $x;
transform: $x;
}
/* Shared */
%pk-box {
border-radius: 5px;
height: $box-dim;
width: $box-dim;
}
/* Utility */
.b {
font-weight: 600;
}
.hidden {
height: 0;
opacity: 0;
left: -10000px;
position: absolute;
width: 0;
}
/* General */
* {
box-sizing: inherit;
}
html, body {
box-sizing: border-box;
font-family: $fonts;
font-size: 16px;
margin: 0;
min-width: 300px;
padding: 0;
}
.pk-section {
align-items: center;
display: flex;
flex-flow: column nowrap;
font-size: inherit;
}
.pk-h1 {
font-size: 2em;
}
.pk-p {
margin: 0 0 10px 0;
}
/* List */
.pk-list {
list-style: none;
margin: 0;
padding: $list-padding-vert $list-padding-lat;
position: relative;
width: $list-width;
}
.pk-item-slot {
height: $item-height;
padding: $slot-padding 0;
width: $item-width;
}
.pk-item {
align-items: center;
background: $lightgray;
border: $item-border solid $gray;
border-radius: 5px;
display: grid;
grid-template-areas: "left middle right";
grid-template-columns: 20px 1fr 20px;
grid-template-rows: auto;
height: $item-height - ($slot-padding * 2);
padding: 0 5px;
width: $item-width;
z-index: 1;
.pk-checkbox {
grid-area: left;
justify-self: start;
...
JavaScript
//initalization list
const ITEMS = [
'Apple',
'Banana',
'Kiwi',
'Orange',
'Pineapple',
'Watermelon'
]
//make checkbox element
const PK_ELEMENTS = {
createCheckboxElement() {
const checkbox = document.createElement('label')
checkbox.classList.add('pk-checkbox')
const input = document.createElement('input')
input.dataset.type='select'
input.classList.add('hidden')
input.classList.add('pk-checkbox-input')
input.setAttribute('type', 'checkbox')
checkbox.appendChild(input)
const check = document.createElement('span')
check.classList.add('check')
checkbox.appendChild(check)
return checkbox
}
}
//id generator
function* idGen(start) {
while(true) yield start++
}
/*
* create a list from a root UL/OL element and an optional array
*/
class List {
constructor(element, array) {
this.element = element
this.items = []
this.mouse = {clientX: 0, clientY: 0}
this._moving = null
this._nextKey = idGen(0)
this._listeners = []
this._scrolling = null
if (array) array.forEach((text) => this.addItem(text))
this._init()
}
_init() {
this.element.addEventListener('pk-move', (e) => this._move(e))
}
_cancel(e) {
//temporary solution
if (!e.relatedTarget) this._drop(e)
}
_drag(e) {
//update item position
this._updatePosition(e)
//update slot position
this._updateDropArea(e)
}
_drop(e) {
//reset
const item = this._moving
item.resetPosition()
item.moving = false
this._moving = null
//stop scrolling
this._scrolling = null
//remove listeners
this._listeners.forEach((current) => {
document.removeEventListener(current.type, current.listener)
})
this._listeners = []
}
_move(e) {
//setup
this._moving = this.getItem(e.target)
const item = this._moving
this._updatePosition(e)
this._updateMouse(e)
item.moving = true
//add...