-
-
Save archseer/8529c45c5b76e564c53eb4f48524f623 to your computer and use it in GitHub Desktop.
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
<script> | |
export default { | |
name: 'vddl-list', | |
// css: placeholder, dragover | |
props: { | |
list: String, | |
allowedTypes: Array, | |
disableIf: Boolean, | |
horizontal: Boolean, | |
externalSources: Boolean, | |
dragover: Function, | |
inserted: Function, | |
drop: Function, | |
}, | |
data() { | |
return { | |
placeholderIndex: null, | |
}; | |
}, | |
render(createElement) { | |
const placeholder = this.$slots.placeholder ? | |
this.$slots.placeholder[0] | |
: createElement('div', { attrs: { class: 'placeholder' } }); | |
const children = [...(this.$slots.default || [])]; | |
if (this.placeholderIndex !== null) { | |
children.splice(this.placeholderIndex, 0, placeholder); | |
} | |
return createElement( | |
'div', | |
{ | |
attrs: { | |
class: 'vddl-list', | |
}, | |
on: { | |
dragenter: this.handleDragenter, | |
dragover: this.handleDragover, | |
drop: this.handleDrop, | |
dragleave: this.handleDragleave, | |
}, | |
}, | |
children, | |
); | |
}, | |
computed: {}, | |
methods: { | |
handleDragenter(event) { | |
event.preventDefault(); | |
if (!this.isDropAllowed(event)) { return true; } | |
}, | |
handleDragover(event) { | |
event.stopPropagation(); | |
event.preventDefault(); | |
if (!this.isDropAllowed(event)) { return true; } | |
if (event.target !== this.$el) { | |
// Try to find the node direct directly below the list node. | |
let listItemNode = event.target; | |
while (listItemNode.parentNode !== this.$el && listItemNode.parentNode) { | |
listItemNode = listItemNode.parentNode; | |
} | |
if (listItemNode.parentNode === this.$el && listItemNode.className !== 'placeholder') { | |
// this is the first frame of the drag and items are wrongly spaced | |
if (!this.placeholderIndex) { | |
this.placeholderIndex = this.getNodeIndex(listItemNode); | |
// If the mouse pointer is in the upper half of the child element, | |
// we place it before the child element, otherwise below it. | |
} else if (this.isMouseInFirstHalf(event, listItemNode)) { | |
this.placeholderIndex = this.getNodeIndex(listItemNode); | |
} else { | |
this.placeholderIndex = this.getNodeIndex(listItemNode) + 1; | |
} | |
} | |
} else { | |
// TODO: | |
/* | |
// This branch is reached when we are dragging directly over the list element. | |
// Usually we wouldn't need to do anything here, but the IE does not fire it's | |
// events for the child element, only for the list directly. Therefore, we repeat | |
// the positioning algorithm for IE here. | |
if (this.isMouseInFirstHalf(event, this.placeholderNode, true)) { | |
// Check if we should move the placeholder element one spot towards the top. | |
// Note that display none elements will have offsetTop and offsetHeight set to | |
// zero, therefore we need a special check for them. | |
while (this.placeholderNode.previousElementSibling | |
&& (this.isMouseInFirstHalf(event, this.placeholderNode.previousElementSibling, true) | |
|| this.placeholderNode.previousElementSibling.offsetHeight === 0)) { | |
//this.$el.insertBefore(this.placeholderNode, this.placeholderNode.previousElementSibling); | |
} | |
} else { | |
// Check if we should move the placeholder element one spot towards the bottom | |
while (this.placeholderNode.nextElementSibling && | |
!this.isMouseInFirstHalf(event, this.placeholderNode.nextElementSibling, true)) { | |
//this.$el.insertBefore(this.placeholderNode, | |
// this.placeholderNode.nextElementSibling.nextElementSibling); | |
} | |
} | |
*/ | |
} | |
// At this point we invoke the callback, which still can disallow the drop. | |
// We can't do this earlier because we want to pass the index of the placeholder. | |
if (this.dragover && !this.invokeCallback('dragover', event, this.placeholderIndex)) { | |
return this.stopDragover(event); | |
} | |
if (this.$el.className.indexOf('vddl-dragover') < 0) { this.$el.className = this.$el.className.trim() + ' vddl-dragover'; } | |
return false; | |
}, | |
handleDrop(event) { | |
event.stopPropagation(); | |
event.preventDefault(); | |
if (!this.isDropAllowed(event)) { return true; } | |
// The default behavior in Firefox is to interpret the dropped element as URL and | |
// forward to it. We want to prevent that even if our drop is aborted. | |
// Unserialize the data that was serialized in dragstart. According to the HTML5 specs, | |
// the "Text" drag type will be converted to text/plain, but IE does not do that. | |
const data = event.dataTransfer.getData('Text') || event.dataTransfer.getData('text/plain'); | |
let transferredObject; | |
try { | |
transferredObject = JSON.parse(data); | |
} catch (e) { | |
return this.stopDragover(); | |
} | |
// Invoke the callback, which can transform the transferredObject and even abort the drop. | |
const index = this.placeholderIndex; | |
if (this.drop) { | |
transferredObject = this.invokeCallback('drop', event, index, transferredObject); | |
if (!transferredObject) { | |
return this.stopDragover(); | |
} | |
} | |
// Insert the object into the array, unless drop took care of that (returned true). | |
if (transferredObject !== true) { | |
this.list.splice(index, 0, transferredObject); | |
} | |
this.invokeCallback('inserted', event, index, transferredObject); | |
// In Chrome on Windows the dropEffect will always be none... | |
// We have to determine the actual effect manually from the allowed effects | |
if (event.dataTransfer.dropEffect === 'none') { | |
if (event.dataTransfer.effectAllowed === 'copy' || | |
event.dataTransfer.effectAllowed === 'move') { | |
this.vddlDropEffectWorkaround.dropEffect = event.dataTransfer.effectAllowed; | |
} else { | |
this.vddlDropEffectWorkaround.dropEffect = event.ctrlKey ? 'copy' : 'move'; | |
} | |
} else { | |
this.vddlDropEffectWorkaround.dropEffect = event.dataTransfer.dropEffect; | |
} | |
// Clean up | |
this.stopDragover(); | |
return false; | |
}, | |
handleDragleave(event) { | |
this.$el.className = this.$el.className.replace('vddl-dragover', '').trim(); | |
setTimeout(() => { | |
if (this.$el.className.indexOf('vddl-dragover') < 0) { | |
this.placeholderIndex = null; | |
} | |
}, 100); | |
}, | |
// Checks whether the mouse pointer is in the first half of the given target element. | |
isMouseInFirstHalf(event, targetNode, relativeToParent) { | |
const mousePointer = this.horizontal ? (event.offsetX || event.layerX) | |
: (event.offsetY || event.layerY); | |
const targetSize = this.horizontal ? targetNode.offsetWidth : targetNode.offsetHeight; | |
let targetPosition = this.horizontal ? targetNode.offsetLeft : targetNode.offsetTop; | |
targetPosition = relativeToParent ? targetPosition : 0; | |
return mousePointer < targetPosition + targetSize / 2; | |
}, | |
getNodeIndex(node) { | |
return Array.from(this.$el.children) | |
.filter((c) => !c.classList.contains('vddl-dragging')) | |
.indexOf(node); | |
}, | |
/** | |
* Checks various conditions that must be fulfilled for a drop to be allowed | |
*/ | |
isDropAllowed(event) { | |
// Disallow drop from external source unless it's allowed explicitly. | |
if (!this.vddlDragTypeWorkaround.isDragging && !this.externalSources) { return false; } | |
// Check mimetype. Usually we would use a custom drag type instead of Text, but IE doesn't | |
// support that. | |
if (!this.hasTextMimetype(event.dataTransfer.types)) { return false; } | |
// Now check the allowed-types against the type of the incoming element. For drops from | |
// external sources we don't know the type, so it will need to be checked via drop. | |
if (this.allowedTypes && this.vddlDragTypeWorkaround.isDragging) { | |
const allowed = this.allowedTypes; | |
if (Array.isArray(allowed) && allowed.indexOf(this.vddlDragTypeWorkaround.dragType) === -1) { | |
return false; | |
} | |
} | |
// Check whether droping is disabled completely | |
if (this.disableIf) { return false; } | |
return true; | |
}, | |
/** | |
* Small helper function that cleans up if we aborted a drop. | |
*/ | |
stopDragover() { | |
this.placeholderIndex = null; | |
this.$el.className = this.$el.className.replace('vddl-dragover', '').trim(); | |
return true; | |
}, | |
/** | |
* Invokes a callback with some interesting parameters and returns the callbacks return value. | |
*/ | |
invokeCallback(expression, event, index, item) { | |
const fn = this[expression]; | |
if (fn) { | |
fn({ | |
event, | |
index, | |
item: item || undefined, | |
list: this.list, | |
external: !this.vddlDragTypeWorkaround.isDragging, | |
type: this.vddlDragTypeWorkaround.isDragging ? this.vddlDragTypeWorkaround.dragType : undefined, | |
}); | |
} | |
return fn ? true : false; | |
}, | |
/** | |
* Check if the dataTransfer object contains a drag type that we can handle. In old versions | |
* of IE the types collection will not even be there, so we just assume a drop is possible. | |
*/ | |
hasTextMimetype(types) { | |
if (!types) { return true; } | |
for (let i = 0; i < types.length; i += 1) { | |
if (types[i] === 'Text' || types[i] === 'text/plain') { return true; } | |
} | |
return false; | |
}, | |
}, | |
}; | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment