| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- import Sortable from "sortablejs";
- import { insertNodeAt, removeNode } from "./util/htmlHelper";
- import { console } from "./util/console";
- import {
- getComponentAttributes,
- createSortableOption,
- getValidSortableEntries
- } from "./core/componentBuilderHelper";
- import { computeComponentStructure } from "./core/renderHelper";
- import { events } from "./core/sortableEvents";
- import { h, defineComponent, nextTick } from "vue";
- function emit(evtName, evtData) {
- nextTick(() => this.$emit(evtName.toLowerCase(), evtData));
- }
- function manage(evtName) {
- return (evtData, originalElement) => {
- if (this.realList !== null) {
- return this[`onDrag${evtName}`](evtData, originalElement);
- }
- };
- }
- function manageAndEmit(evtName) {
- const delegateCallBack = manage.call(this, evtName);
- return (evtData, originalElement) => {
- delegateCallBack.call(this, evtData, originalElement);
- emit.call(this, evtName, evtData);
- };
- }
- let draggingElement = null;
- const props = {
- list: {
- type: Array,
- required: false,
- default: null
- },
- modelValue: {
- type: Array,
- required: false,
- default: null
- },
- itemKey: {
- type: [String, Function],
- required: true
- },
- clone: {
- type: Function,
- default: original => {
- return original;
- }
- },
- tag: {
- type: String,
- default: "div"
- },
- move: {
- type: Function,
- default: null
- },
- componentData: {
- type: Object,
- required: false,
- default: null
- }
- };
- const emits = [
- "update:modelValue",
- "change",
- ...[...events.manageAndEmit, ...events.emit].map(evt => evt.toLowerCase())
- ];
- const draggableComponent = defineComponent({
- name: "draggable",
- inheritAttrs: false,
- props,
- emits,
- data() {
- return {
- error: false
- };
- },
- render() {
- try {
- this.error = false;
- const { $slots, $attrs, tag, componentData, realList, getKey } = this;
- const componentStructure = computeComponentStructure({
- $slots,
- tag,
- realList,
- getKey
- });
- this.componentStructure = componentStructure;
- const attributes = getComponentAttributes({ $attrs, componentData });
- return componentStructure.render(h, attributes);
- } catch (err) {
- this.error = true;
- return h("pre", { style: { color: "red" } }, err.stack);
- }
- },
- created() {
- if (this.list !== null && this.modelValue !== null) {
- console.error(
- "modelValue and list props are mutually exclusive! Please set one or another."
- );
- }
- },
- mounted() {
- if (this.error) {
- return;
- }
- const { $attrs, $el, componentStructure } = this;
- componentStructure.updated();
- const sortableOptions = createSortableOption({
- $attrs,
- callBackBuilder: {
- manageAndEmit: event => manageAndEmit.call(this, event),
- emit: event => emit.bind(this, event),
- manage: event => manage.call(this, event)
- }
- });
- const targetDomElement = $el.nodeType === 1 ? $el : $el.parentElement;
- this._sortable = new Sortable(targetDomElement, sortableOptions);
- this.targetDomElement = targetDomElement;
- targetDomElement.__draggable_component__ = this;
- },
- updated() {
- this.componentStructure.updated();
- },
- beforeUnmount() {
- if (this._sortable !== undefined) this._sortable.destroy();
- },
- computed: {
- realList() {
- const { list } = this;
- return list ? list : this.modelValue;
- },
- getKey() {
- const { itemKey } = this;
- if (typeof itemKey === "function") {
- return itemKey;
- }
- return element => element[itemKey];
- }
- },
- watch: {
- $attrs: {
- handler(newOptionValue) {
- const { _sortable } = this;
- if (!_sortable) return;
- getValidSortableEntries(newOptionValue).forEach(([key, value]) => {
- _sortable.option(key, value);
- });
- },
- deep: true
- }
- },
- methods: {
- getUnderlyingVm(domElement) {
- return this.componentStructure.getUnderlyingVm(domElement) || null;
- },
- getUnderlyingPotencialDraggableComponent(htmElement) {
- //TODO check case where you need to see component children
- return htmElement.__draggable_component__;
- },
- emitChanges(evt) {
- nextTick(() => this.$emit("change", evt));
- },
- alterList(onList) {
- if (this.list) {
- onList(this.list);
- return;
- }
- const newList = [...this.modelValue];
- onList(newList);
- this.$emit("update:modelValue", newList);
- },
- spliceList() {
- const spliceList = list => list.splice(...arguments);
- this.alterList(spliceList);
- },
- updatePosition(oldIndex, newIndex) {
- const updatePosition = list =>
- list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);
- this.alterList(updatePosition);
- },
- getRelatedContextFromMoveEvent({ to, related }) {
- const component = this.getUnderlyingPotencialDraggableComponent(to);
- if (!component) {
- return { component };
- }
- const list = component.realList;
- const context = { list, component };
- if (to !== related && list) {
- const destination = component.getUnderlyingVm(related) || {};
- return { ...destination, ...context };
- }
- return context;
- },
- getVmIndexFromDomIndex(domIndex) {
- return this.componentStructure.getVmIndexFromDomIndex(
- domIndex,
- this.targetDomElement
- );
- },
- 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.getVmIndexFromDomIndex(evt.newIndex);
- this.spliceList(newIndex, 0, element);
- const added = { element, newIndex };
- this.emitChanges({ added });
- },
- onDragRemove(evt) {
- insertNodeAt(this.$el, evt.item, evt.oldIndex);
- if (evt.pullMode === "clone") {
- removeNode(evt.clone);
- return;
- }
- const { index: oldIndex, element } = this.context;
- this.spliceList(oldIndex, 1);
- const removed = { 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.getVmIndexFromDomIndex(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].filter(
- el => el.style["display"] !== "none"
- );
- const currentDomIndex = domChildren.indexOf(evt.related);
- const currentIndex = relatedContext.component.getVmIndexFromDomIndex(
- currentDomIndex
- );
- const draggedInList = domChildren.indexOf(draggingElement) !== -1;
- return draggedInList || !evt.willInsertAfter
- ? currentIndex
- : currentIndex + 1;
- },
- onDragMove(evt, originalEvent) {
- const { move, realList } = this;
- if (!move || !realList) {
- return true;
- }
- const relatedContext = this.getRelatedContextFromMoveEvent(evt);
- const futureIndex = this.computeFutureIndex(relatedContext, evt);
- const draggedContext = {
- ...this.context,
- futureIndex
- };
- const sendEvent = {
- ...evt,
- relatedContext,
- draggedContext
- };
- return move(sendEvent, originalEvent);
- },
- onDragEnd() {
- draggingElement = null;
- }
- }
- });
- export default draggableComponent;
|