vuedraggable.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. (function() {
  2. "use strict";
  3. function buildDraggable(Sortable) {
  4. function removeNode (node) {
  5. node.parentElement.removeChild(node)
  6. }
  7. function insertNodeAt (fatherNode, node, position) {
  8. if (position < fatherNode.children.length) {
  9. fatherNode.insertBefore(node, fatherNode.children[position])
  10. } else {
  11. fatherNode.appendChild(node)
  12. }
  13. }
  14. function computeVmIndex (vnodes, element) {
  15. return vnodes.map(elt => elt.elm).indexOf(element)
  16. }
  17. function computeIndexes (slots, children) {
  18. return (!slots)? [] : Array.prototype.map.call(children, elt => computeVmIndex(slots, elt))
  19. }
  20. function emit (evtName, evtData) {
  21. this.$emit( evtName.toLowerCase(), evtData)
  22. }
  23. function delegateAndEmit (evtName) {
  24. return (evtData) => {
  25. if (this.list!==null) {
  26. this['onDrag' + evtName](evtData)
  27. }
  28. emit.call(this, evtName, evtData)
  29. }
  30. }
  31. const eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End']
  32. const eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone']
  33. const readonlyProperties = ['Move', ...eventsListened, ...eventsToEmit].map(evt => 'on'+evt)
  34. var draggingElement = null
  35. const props = {
  36. options: Object,
  37. list: {
  38. type: Array,
  39. required: false,
  40. default: null
  41. },
  42. clone: {
  43. type: Function,
  44. default : (original) => { return original;}
  45. },
  46. element: {
  47. type: String,
  48. default: 'div'
  49. },
  50. move: {
  51. type: Function,
  52. default: null
  53. }
  54. }
  55. const draggableComponent = {
  56. props,
  57. data() {
  58. return {
  59. transitionMode: false
  60. }
  61. },
  62. render (h) {
  63. if (this.$slots.default && this.$slots.default.length===1) {
  64. const child = this.$slots.default[0]
  65. if (child.componentOptions && child.componentOptions.tag==="transition-group") {
  66. this.transitionMode = true
  67. }
  68. }
  69. return h(this.element, null, this.$slots.default);
  70. },
  71. mounted () {
  72. var optionsAdded = {};
  73. eventsListened.forEach( elt => {
  74. optionsAdded['on' + elt] = delegateAndEmit.call(this, elt)
  75. });
  76. eventsToEmit.forEach( elt => {
  77. optionsAdded['on' + elt] = emit.bind(this, elt)
  78. });
  79. const options = Object.assign({}, this.options, optionsAdded, { onMove: evt => {return this.onDragMove(evt);} })
  80. this._sortable = new Sortable(this.rootContainer, options)
  81. this.computeIndexes()
  82. },
  83. beforeDestroy () {
  84. this._sortable.destroy()
  85. },
  86. computed : {
  87. rootContainer () {
  88. return this.transitionMode? this.$el.children[0] : this.$el;
  89. }
  90. },
  91. watch: {
  92. options (newOptionValue){
  93. for(var property in newOptionValue) {
  94. if (readonlyProperties.indexOf(property)==-1) {
  95. this._sortable.option(property, newOptionValue[property] );
  96. }
  97. }
  98. },
  99. list(){
  100. this.computeIndexes()
  101. }
  102. },
  103. methods: {
  104. getChildrenNodes () {
  105. const rawNodes = this.$slots.default
  106. return this.transitionMode? rawNodes[0].child.$slots.default : rawNodes
  107. },
  108. computeIndexes () {
  109. this.$nextTick( () => {
  110. this.visibleIndexes = computeIndexes(this.getChildrenNodes(), this.rootContainer.children)
  111. })
  112. },
  113. getUnderlyingVm (htmlElt) {
  114. const index = computeVmIndex(this.getChildrenNodes(), htmlElt)
  115. const element = this.list[index]
  116. return {index, element}
  117. },
  118. getUnderlyingPotencialDraggableComponent ({__vue__}) {
  119. if (!__vue__ || !__vue__.$options || __vue__.$options._componentTag!=="transition-group"){
  120. return __vue__
  121. }
  122. return __vue__.$parent
  123. },
  124. emitChanges (evt) {
  125. this.$nextTick( ()=>{
  126. this.$emit('change', evt)
  127. });
  128. },
  129. spliceList () {
  130. this.list.splice(...arguments)
  131. },
  132. updatePosition (oldIndex, newIndex) {
  133. this.list.splice(newIndex, 0, this.list.splice(oldIndex, 1)[0])
  134. },
  135. getRelatedContextFromMoveEvent({to, related}) {
  136. const component = this.getUnderlyingPotencialDraggableComponent(to)
  137. if (!component) {
  138. return {component}
  139. }
  140. const list = component.list
  141. const context = {list, component}
  142. if (to !== related && list && component.getUnderlyingVm) {
  143. const destination = component.getUnderlyingVm(related)
  144. return Object.assign(destination, context)
  145. }
  146. return context
  147. },
  148. getVmIndex (domIndex) {
  149. const indexes = this.visibleIndexes
  150. const numberIndexes = indexes.length
  151. return (domIndex > numberIndexes - 1) ? numberIndexes : indexes[domIndex]
  152. },
  153. onDragStart (evt) {
  154. this.context = this.getUnderlyingVm(evt.item)
  155. evt.item._underlying_vm_ = this.clone(this.context.element)
  156. draggingElement = evt.item
  157. },
  158. onDragAdd (evt) {
  159. const element = evt.item._underlying_vm_
  160. if (element === undefined) {
  161. return
  162. }
  163. removeNode(evt.item)
  164. const newIndex = this.getVmIndex(evt.newIndex)
  165. this.spliceList(newIndex, 0, element)
  166. this.computeIndexes()
  167. const added = {element, newIndex}
  168. this.emitChanges({added})
  169. },
  170. onDragRemove (evt) {
  171. insertNodeAt(this.rootContainer, evt.item, evt.oldIndex)
  172. const isCloning = !!evt.clone
  173. if (isCloning) {
  174. removeNode(evt.clone)
  175. return
  176. }
  177. const oldIndex = this.context.index
  178. this.spliceList(oldIndex, 1)
  179. const removed = {element: this.context.element, oldIndex}
  180. this.emitChanges({removed})
  181. },
  182. onDragUpdate (evt) {
  183. removeNode(evt.item)
  184. insertNodeAt(evt.from, evt.item, evt.oldIndex)
  185. const oldIndex = this.context.index
  186. const newIndex = this.getVmIndex(evt.newIndex)
  187. this.updatePosition(oldIndex, newIndex)
  188. const moved = {element: this.context.element, oldIndex, newIndex}
  189. this.emitChanges({moved})
  190. },
  191. computeFutureIndex (relatedContext, evt) {
  192. if (!relatedContext.element){
  193. return 0
  194. }
  195. const domChildren = [...evt.to.children]
  196. const currentDOMIndex = domChildren.indexOf(evt.related)
  197. const currentIndex = relatedContext.component.getVmIndex(currentDOMIndex)
  198. const draggedInList = domChildren.indexOf(draggingElement) != -1
  199. return draggedInList? currentIndex : currentIndex+1
  200. },
  201. onDragMove (evt) {
  202. const onMove = this.move
  203. if (!onMove || !this.list) {
  204. return true
  205. }
  206. const relatedContext = this.getRelatedContextFromMoveEvent(evt)
  207. const draggedContext = this.context
  208. const futureIndex = this.computeFutureIndex(relatedContext, evt)
  209. Object.assign(draggedContext, { futureIndex })
  210. Object.assign(evt, {relatedContext, draggedContext})
  211. return onMove(evt)
  212. },
  213. onDragEnd (evt) {
  214. this.computeIndexes()
  215. draggingElement = null
  216. }
  217. }
  218. }
  219. return draggableComponent
  220. }
  221. if (typeof exports == "object") {
  222. var Sortable = require("sortablejs")
  223. module.exports = buildDraggable(Sortable)
  224. } else if (typeof define == "function" && define.amd) {
  225. define(['sortablejs'], function(Sortable) {return buildDraggable(Sortable);});
  226. } else if ( window && (window.Vue) && (window.Sortable)) {
  227. var draggable = buildDraggable(window.Sortable)
  228. Vue.component('draggable', draggable)
  229. }
  230. })();