(function() { "use strict"; function buildDraggable(Sortable) { function removeNode (node) { node.parentElement.removeChild(node) } function insertNodeAt (fatherNode, node, position) { if (position < fatherNode.children.length) { fatherNode.insertBefore(node, fatherNode.children[position]) } else { fatherNode.appendChild(node) } } function computeVmIndex (vnodes, element) { return vnodes.map(elt => elt.elm).indexOf(element) } function computeIndexes (slots, children) { return (!slots)? [] : Array.prototype.map.call(children, elt => computeVmIndex(slots, elt)) } function emit (evtName, evtData) { this.$emit( evtName.toLowerCase(), evtData) } function delegateAndEmit (evtName) { return (evtData) => { if (this.list!==null) { this['onDrag' + evtName](evtData) } emit.call(this, evtName, evtData) } } const eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End'] const eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone'] const readonlyProperties = ['Move', ...eventsListened, ...eventsToEmit].map(evt => 'on'+evt) var draggingElement = null const props = { options: Object, list: { type: Array, required: false, default: null }, clone: { type: Function, default : (original) => { return original;} }, element: { type: String, default: 'div' }, move: { type: Function, default: null } } const draggableComponent = { props, data() { return { transitionMode: false } }, render (h) { if (this.$slots.default && this.$slots.default.length===1) { const child = this.$slots.default[0] if (child.componentOptions && child.componentOptions.tag==="transition-group") { this.transitionMode = true } } return h(this.element, null, this.$slots.default); }, mounted () { var optionsAdded = {}; eventsListened.forEach( elt => { optionsAdded['on' + elt] = delegateAndEmit.call(this, elt) }); eventsToEmit.forEach( elt => { optionsAdded['on' + elt] = emit.bind(this, elt) }); const options = Object.assign({}, this.options, optionsAdded, { onMove: evt => {return this.onDragMove(evt);} }) this._sortable = new Sortable(this.rootContainer, options) this.computeIndexes() }, beforeDestroy () { this._sortable.destroy() }, computed : { rootContainer () { return this.transitionMode? this.$el.children[0] : this.$el; } }, watch: { options (newOptionValue){ for(var property in newOptionValue) { if (readonlyProperties.indexOf(property)==-1) { this._sortable.option(property, newOptionValue[property] ); } } }, list(){ this.computeIndexes() } }, methods: { getChildrenNodes () { const rawNodes = this.$slots.default return this.transitionMode? rawNodes[0].child.$slots.default : rawNodes }, computeIndexes () { this.$nextTick( () => { this.visibleIndexes = computeIndexes(this.getChildrenNodes(), this.rootContainer.children) }) }, getUnderlyingVm (htmlElt) { const index = computeVmIndex(this.getChildrenNodes(), htmlElt) const element = this.list[index] return {index, element} }, getUnderlyingPotencialDraggableComponent ({__vue__}) { if (!__vue__ || !__vue__.$options || __vue__.$options._componentTag!=="transition-group"){ return __vue__ } return __vue__.$parent }, emitChanges (evt) { this.$nextTick( ()=>{ this.$emit('change', evt) }); }, spliceList () { this.list.splice(...arguments) }, updatePosition (oldIndex, newIndex) { this.list.splice(newIndex, 0, this.list.splice(oldIndex, 1)[0]) }, getRelatedContextFromMoveEvent({to, related}) { const component = this.getUnderlyingPotencialDraggableComponent(to) if (!component) { return {component} } const list = component.list const context = {list, component} if (to !== related && list && component.getUnderlyingVm) { const destination = component.getUnderlyingVm(related) return Object.assign(destination, context) } return context }, getVmIndex (domIndex) { const indexes = this.visibleIndexes const numberIndexes = indexes.length return (domIndex > numberIndexes - 1) ? numberIndexes : indexes[domIndex] }, onDragStart (evt) { this.context = this.getUnderlyingVm(evt.item) evt.item._underlying_vm_ = this.clone(this.context.element) draggingElement = evt.item }, onDragAdd (evt) { const element = evt.item._underlying_vm_ if (element === undefined) { return } removeNode(evt.item) const newIndex = this.getVmIndex(evt.newIndex) this.spliceList(newIndex, 0, element) this.computeIndexes() const added = {element, newIndex} this.emitChanges({added}) }, onDragRemove (evt) { insertNodeAt(this.rootContainer, evt.item, evt.oldIndex) const isCloning = !!evt.clone if (isCloning) { removeNode(evt.clone) return } const oldIndex = this.context.index this.spliceList(oldIndex, 1) const removed = {element: this.context.element, oldIndex} this.emitChanges({removed}) }, onDragUpdate (evt) { removeNode(evt.item) insertNodeAt(evt.from, evt.item, evt.oldIndex) const oldIndex = this.context.index const newIndex = this.getVmIndex(evt.newIndex) this.updatePosition(oldIndex, newIndex) const moved = {element: this.context.element, oldIndex, newIndex} this.emitChanges({moved}) }, computeFutureIndex (relatedContext, evt) { if (!relatedContext.element){ return 0 } const domChildren = [...evt.to.children] const currentDOMIndex = domChildren.indexOf(evt.related) const currentIndex = relatedContext.component.getVmIndex(currentDOMIndex) const draggedInList = domChildren.indexOf(draggingElement) != -1 return draggedInList? currentIndex : currentIndex+1 }, onDragMove (evt) { const onMove = this.move if (!onMove || !this.list) { return true } const relatedContext = this.getRelatedContextFromMoveEvent(evt) const draggedContext = this.context const futureIndex = this.computeFutureIndex(relatedContext, evt) Object.assign(draggedContext, { futureIndex }) Object.assign(evt, {relatedContext, draggedContext}) return onMove(evt) }, onDragEnd (evt) { this.computeIndexes() draggingElement = null } } } return draggableComponent } if (typeof exports == "object") { var Sortable = require("sortablejs") module.exports = buildDraggable(Sortable) } else if (typeof define == "function" && define.amd) { define(['sortablejs'], function(Sortable) {return buildDraggable(Sortable);}); } else if ( window && (window.Vue) && (window.Sortable)) { var draggable = buildDraggable(window.Sortable) Vue.component('draggable', draggable) } })();