Created
August 6, 2013 23:54
-
-
Save enjalot/6170003 to your computer and use it in GitHub Desktop.
drag and drop in derby (by @ishbu)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
helpers = require('../util').helpers | |
module.exports = Draggable = (e, containerEl, handle, dragSelector, handleSelector, component, @moveCallback, startX, startY) -> | |
items = containerEl.querySelectorAll dragSelector | |
if dragSelector == handleSelector | |
el = handle | |
else | |
handles = containerEl.querySelectorAll handleSelector | |
index = nodeIndex handles, handle | |
el = items[index] | |
@el = el | |
@container = cloneEl el | |
@container.classList.add 'dragging' | |
el.style.opacity = 0.5 | |
rect = el.getBoundingClientRect() | |
startX ?= e.clientX | |
startY ?= e.clientY | |
@offsetLeft = rect.left - startX | |
@offsetTop = rect.top - startY | |
@onMove e | |
helpers.appendToBody @container, component | |
return | |
Draggable::finish = (cancel) -> | |
@el.style.opacity = '' | |
unless cancel | |
@moveCallback @el | |
document.body.removeChild @container | |
Draggable::onMove = (e) -> | |
@container.style.left = (e.clientX + window.pageXOffset + @offsetLeft) + 'px' | |
@container.style.top = (e.clientY + window.pageYOffset + @offsetTop) + 'px' | |
nodeIndex = (nodeList, node) -> | |
for child, i in nodeList | |
return i if child == node | |
return -1 | |
cloneEl = (el) -> | |
parent = el.parentNode | |
# Need table if we are dragging something like a TR | |
if parent.tagName == 'TBODY' || parent.tagName == 'THEAD' | |
container = parent.parentNode.cloneNode false | |
container.removeAttribute 'id' | |
parentClone = parent.cloneNode false | |
parentClone.removeAttribute 'id' | |
container.appendChild parentClone | |
else | |
container = parentClone = parent.cloneNode false | |
container.removeAttribute 'id' | |
clone = el.cloneNode true | |
clone.removeAttribute 'id' | |
parentClone.appendChild clone | |
container.style.width = window.getComputedStyle(parent).width | |
container.style.position = 'absolute' | |
clone.style = window.getComputedStyle el | |
return container |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
exports.appendToBody = (el, component, stopPropagation) -> | |
return unless el | |
# Put the element at the top level of the body so that it can't be | |
# cut off if the menu is in a container with overflow:hidden | |
document.body.appendChild el | |
return if el._hasDestroyListener || !component | |
el._hasDestroyListener = true | |
if stopPropagation | |
# Bubbling events may be problematic, since the element is | |
# no longer in the original DOM location | |
component.dom.addListener el, 'click', (e) -> | |
e.stopPropagation() | |
# Make sure that the element is removed if its parent component is destroyed | |
component.on 'destroy', -> | |
el.parentNode.removeChild el if el.parentNode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###* | |
* Enables you to create a set of draggables tied to a set of droppables | |
* When an item is being dragged, droppables will add class .hovered as the mouse moves over them | |
* When an item is dropped into a droppabel target, a 'drop' event is emitted of form | |
* (draggedItemEl, dropTargetEl) | |
* Example: | |
* <ui:droppable | |
* container=".list-content" # The outer container that contains draggable items | |
* draggable="tr.profile" # Selector for draggable elements must be inside container | |
* droppable=".stage-target" # Selector for droppable elements | |
* bind="drop: lists.changeStage"> | |
### | |
exports.name = "droppable" | |
Draggable = require './Draggable' | |
exports.create = (model, dom) -> | |
@_dragSelector = model.get('draggable') || '.draggable-item' | |
@_handleSelector = model.get('handle') || @_dragSelector | |
@_dropSelector = model.get('droppable') || '.droppable-target' | |
@_containerSelector = model.get('container') || '.draggable-container' | |
@_delayDistance = model.get('delay-distance') || 10 | |
@_draggables = document.querySelectorAll @_dragSelector | |
@_droppables = document.querySelectorAll @_dropSelector | |
@_container = document.querySelector @_containerSelector | |
@_handle = null | |
@_draggable = null | |
@_dropTarget = null | |
@_lastDown = null | |
@_startPos = null | |
dom.addListener document, 'mousemove', (e) => | |
if @_startPos && @_handle && !@_draggable | |
startX = @_startPos.clientX | |
startY = @_startPos.clientY | |
distanceX = e.clientX - startX | |
distanceY = e.clientY - startY | |
return if Math.sqrt(distanceX * distanceX + distanceY * distanceY) < @_delayDistance | |
onDrop = (itemEl) => | |
return unless itemEl && @_dropTarget | |
@emit 'drop', itemEl, @_dropTarget | |
model.set 'dragging', true | |
@_draggable = new Draggable e, @_container, @_handle, @_dragSelector, @_handleSelector, this, onDrop, startX, startY | |
return unless @_draggable | |
@_draggable?.onMove e | |
onMove e | |
, true | |
dom.addListener document, 'mouseup', (e) => | |
@_startPos = null | |
@_handle = null | |
return unless @_draggable | |
@_finishDragging(false, e) | |
# If the mouse is in the same spot as when down was fired, we click | |
target = document.elementFromPoint(e.clientX, e.clientY) | |
if @_lastDown == target | |
@_lastDown.click() | |
, true | |
dom.addListener window, 'blur', (e) => | |
@_finishDragging(true) | |
, true | |
dom.addListener document, 'mousedown', exports._down.bind(this), true | |
onMove = (e) => | |
clientX = e.clientX | |
clientY = e.clientY | |
for droppable in @_droppables | |
rect = droppable.getBoundingClientRect() | |
if clientX > rect.left && clientX < rect.right | |
if clientY > rect.top && clientY < rect.bottom | |
@_dropTarget?.classList.remove('hovered') | |
droppable.classList.add('hovered') | |
@_dropTarget = droppable | |
break | |
exports._finishDragging = (cancel, e) -> | |
return unless @_draggable | |
@_draggable.finish cancel | |
@_handle = null | |
@_draggable = null | |
@_dropTarget = null | |
@_lastDown = null | |
@_startPos = null | |
@model.set 'dragging', false | |
# Clean up any hover states | |
hovered = document.querySelectorAll @_dropSelector + '.hovered' | |
for hover in hovered | |
hover.classList.remove('hovered') | |
exports._down = (e) -> | |
return if e.button != 0 # Left click only | |
handle = containedBy @_container, e.target, @_handleSelector | |
return unless handle # Is what I'm clicking on in the container? | |
@_handle = handle | |
@_lastDown = el = e.target | |
clientX = e.clientX | |
clientY = e.clientY | |
@_startPos = {clientX, clientY} | |
# OPTIMIZE: This preventDefault is critical for some unknown performance reason, find out why | |
e.preventDefault?() | |
containedBy = (root, target, selector) -> | |
while target != root | |
return target if target.matches? selector | |
target = target.parentNode | |
return unless target | |
return |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment