Nesting in VueDraggable is not not working - vue.js

here's my problem :
I'm using the VueDraggable library in order to drag and drop elements between a DragBoard.vue and a DropBoard.vue, and a specific type of element should allow to be nested when it is in the DropBoard.
I'm going to take this element as an example :
"Grouped Items"
To do that I've followed this example : https://github.com/SortableJS/vue.draggable.next/blob/master/example/components/nested-example.vue
And this is what I get when I drop "Grouped Items" into the DropBoard.vue :
IMG
As you can see, the DropBoard appears a second time inside Grouped items for whatever reason. I've supposed that the nested-draggable tag also loop what is out of the draggable tag and I've no idea how to resolve that...
📄 dragItems.JSON (used in DragBoard.vue) :
1st object is a common element
2nd object is a nestable element
[
{
"type": "Simple list",
"title": "Simple list",
"id": 1,
"properties": "this is an item property"
},
...
{
"type": "Grouped items",
"title": "Grouped items",
"id": 10,
"properties": "this is an item property",
"tasks": []
},
...
]
🚩 DropBoard.vue template:
<template>
<div class="board">
<div class="head">Mock</div>
<div class="dd-container">
<draggable
:list="tasks"
v-model="dropItems"
item-key="title"
:group="{ name: 'items', put: true }"
#change="log"
>
<template #item="{ element }">
<div
class="item"
:key="element"
>
<div>
{{ element.title }}
</div>
<nested-draggable
v-if="element.tasks"
:tasks="element.tasks"
class="group-container"
/>
<div class="trashico" :key="index">
<i class="fas fa-trash" #click="deleteItem(index)"></i>
</div>
</div>
</template>
</draggable>
</div>
</div>
</template>
🚩 DropBoard.vue script
<script>
import draggable from "vuedraggable";
export default {
name: "nested-draggable",
components: {
draggable,
},
props: {
dropItems: {
type: Array,
required: true,
},
tasks: {
required: true,
type: Array,
},
},
data() {
return {
dropItems: [],
};
},
methods: {
deleteItem(id) {
this.dropItems.splice(id, 1);
},
},
};
</script>

Here is what I found while using the Vue DevTools, it's quit explicit about the problem.
See image: IMG

Related

show/hide elements inside of for loop

I have the following:
<div v-for="num in [1,2,3,4,5]" :key="num ">
<customelement0 :num ="num" />
<span #click="show = !show" > Details: </span>
<customelement1 v-if="show" :num ="num" />
<hr />
</div>
and:
export default {
data() {
return {
show: false
};
},
};
However, in this implementation Whenever show changes it affects all of the customelement1s and will show/hide all of them.
How would one solve this problem, so that whenever a user clicks on the span it only shows/hides one element in the loop?
P.S.: in reality, the length of the loop is much longer, than what's indicated above
The problem here is essentially that your show is a variable for the entire component, and isn't linked to one of your array elements.
Generally, we don't really tend to hard-code an array into the html, but rather in the data, as shown in the other answers.
The other answers show it with num being coded into the object but you could also do something like this. Note that other field is optional and not required. The main advantage of this method is that you don't need to code every number.
<div v-for="(item, i) in items" :key="i">
<!-- Note I do i+1 so that it matches the 1,2,3 in your example -->
<!-- if you're okay with having 0,1,2 you can omit the +1 -->
<customelement0 :num="i+1" />
<span #click="item.show = !item.show" >Details: </span>
<customelement1 v-if="item.show" :num="i+1" />
<hr />
</div>
export default {
data() {
return {
items: [
{show: false, other: "foo"},
{show: false, other: "bar"},
//...
],
};
},
};
You can change number to object and toggle property:
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>
new Vue({
el: "#app",
data: {
todos: [
{ text: "1", done: false },
{ text: "2", done: false },
{ text: "3", done: true },
{ text: "4", done: true },
{ text: "5", done: true },
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})
You can to save the value isShow for each element in the array. So you need to use an array of objects in you data and do something like this:
export default {
data() {
return {
numList: [
{
num: 1,
isShow: true
},
{
num: 2,
isShow: true
}
]
};
},
};
In your template:
<div v-for="item in numList" :key="item.num">
<customelement0 :num="item.num" />
<span #click="item.isShow = !item.isShow" > Details: </span>
<customelement1 v-if="item.isShow" :num="item.num" />
<hr />
</div>

where on earth is vue-draggable item move to?

I use nested draggable and listen events while drag, then find out that if i drag a itemA into itemB, event throws:
event add => newIndex, element.
event removed => oldIndex, element.
It tells me that itemA's index in itemB, but where on earth i konw who is itemB. something I missed? :3
I found it's hard to write a vue-draggable nested-component between 2 pages, i put a simple code to review it in your own computer(not really mine, copy official doc and little change ). code below
<template>
<div class="row">
<div class="col-8">
<h3>Nested draggable</h3>
<nested-draggable :tasks="list" />
</div>
</div>
</template>
<script>
import nestedDraggable from "./infra/nested";
export default {
name: "nested-example",
display: "Nested",
order: 15,
components: {
nestedDraggable
},
data() {
return {
list: [
{
name: "task 1",
tasks: [
{
name: "task 2",
tasks: []
}
]
},
{
name: "task 3",
tasks: [
{
name: "task 4",
tasks: []
}
]
},
{
name: "task 5",
tasks: []
}
]
};
}
};
</script>
<style scoped></style>
and infra/nested
<template>
<draggable class="dragArea" tag="ul" :list="tasks" :group="{ name: 'g1' }" #change="changed">
<li v-for="el in tasks" :key="el.name">
<p>{{ el.name }}</p>
<nested-draggable :tasks="el.tasks" />
</li>
</draggable>
</template>
<script>
import draggable from "#/vuedraggable";
export default {
props: {
tasks: {
required: true,
type: Array
}
},
components: {
draggable
},
name: "nested-draggable",
methods: {
changed (event) {
console.log(event)
}
}
};
</script>
<style scoped>
.dragArea {
min-height: 50px;
outline: 1px dashed;
}
</style>
So far, pass a el.list can calculate its index with save level element, though, drag in a empty list need to be prevented.

How to disable vue draggable placeholder

I am currently doing a website builder, where user can drag and drop to add element.
The drag and drop works well, but what i want is, how can i disable/hide the drop placeholder in the target container ?
As show in the image, whenever I hover on a container, it will show a copy of my dragging element by default, which I don't want.
Here is my code :
<template>
<div style="display : flex;">
<div id="dragArea">
<draggable
class="dragArea list-group"
:list="list1"
:group="{ name: 'item', pull: 'clone', put: false }"
:clone="cloneItem"
#change="log"
>
<div class="list-group-item" v-for="element in list1" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
<div id="dropArea">
<draggable class="dragArea list-group" :list="list2" group="item" #change="log">
<div class="list-group-item" v-for="element in list2" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
</div>
</template>
Script :
<script>
import draggable from "vuedraggable";
let idGlobal = 8;
export default {
name: "custom-clone",
display: "Custom Clone",
order: 3,
components: {
draggable,
},
data() {
return {
hover : false,
list1: [
{ name: "cloned 1", id: 1 },
{ name: "cloned 2", id: 2 },
],
list2: [
]
};
},
methods: {
log: function(evt) {
window.console.log(evt);
},
cloneItem({ name, id }) {
return {
id: idGlobal++,
name: name
};
},
},
};
</script>
On each of your <draggable> components within your <template>, you can set the ghost-class prop to a CSS class that hides the drop placeholder (ie. "ghost", or "dragging element" as you called it) using display: none; or visibility: hidden;.
For example:
In your <template>:
<draggable ghost-class="hidden-ghost">
and in the <style> section of your Vue Single File Component, or in the corresponding stylesheet:
.hidden-ghost {
display: none;
}
Working Fiddle
The ghost-class prop internally sets the SortableJS ghostClass option (see all the options here). The ability to modify these SortableJS options as Vue.Draggable props is available as of Vue.Draggable v2.19.1.

"prop" is undefined though it's being passed correctly

I've been following VueJS official documentation on passing data to child components with props; though I'm not working with a string template. I'm aware about what happens when your prop is camel case; you should write it as kebab case.
Nevertheless, this is not the case since it's all lowercase and won't work.
I'm using nuxt and I've separated my work into files, which are:
<template>
<div class="row">
<input type="text" name="" id="" placeholder="Write your question" v-model="text">
<select v-model="selectedField">
<option v-for="option in options" :key="option.id" :value="option.value">
{{ option.text }}
</option>
</select>
<button class="btn btn-sm btn-primary" #click="$emit('add-field')"
v-bind:class="{ disabled: ($parent.count <= 1 && $parent.count == identifier) }">
>{{identifier}}</button>
<button class="btn btn-sm btn-danger" #click="$emit('delete-field')">-</button>
</div>
Now for its JS file:
export default {
data () {
return {
options: [
{
id: 1,
value: 1,
text: "Radio"
},
{
id: 2,
value: 2,
text: "Rate"
},
{
id: 3,
value: 3,
text: "Text"
}
],
props: ['identifier'],
selectedField: 1,
text: "",
}
},
}
Now, for my parent component:
<template>
<div class="offset-md-3" id="qz">
<form-maker
v-for="item in questions" :key="item._id"
v-on:add-field="addField()"
v-on:delete-field="deleteField(item._id)"
v-bind:identifier="item._id" <<--What I want to set
ref="child"
></form-maker>
<button #click="saveForm" class="btn btn-large btn-success">SAVE</button>
</div>
</template>
Finally:
var vm = null;
export default {
layout: 'admin',
components: {
formMaker
},
data() {
return {
count: 1,
questions: [{
_id: 1//static
}]
}
},
}
What I'm trying to do is, to use the prop for some validations, nevertheless it throws the next error:
Property or method "identifier" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property.
Thank you.
Here is where you go wrong. Props should not be in data(). See the code snippet below
<script>
export default {
props: ['identifier'],
data() {
return {
options: [
{
id: 1,
value: 1,
text: "Radio"
},
{
id: 2,
value: 2,
text: "Rate"
},
{
id: 3,
value: 3,
text: "Text"
}
],
selectedField: 1,
text: "",
}
}
}
</script>

Nested Vue.Draggable list breaks when dragging cross-level

I want to have a draggable, nested list with Vue and used the Vue.Draggable component for it. I'm however stuck with updating nested lists.
The rendering is fine, the dragging is fine when you stay inside the same level. But dragging cross-level doesn't seem to do it (error in VueComponent.onDragStart).
html
<template>
<div class="fluid container">
<div class="col-md-6">
<draggable class="list-group" element="ul" v-model="list" :options="dragOptions" :move="onMove" #start="isDragging=true" #end="isDragging=false">
<transition-group type="transition" :name="'flip-list'">
<li class="list-group-item" v-for="element in list" :key="element.order">
<i :class="element.fixed? 'fa fa-anchor' : 'glyphicon glyphicon-pushpin'" #click=" element.fixed=! element.fixed" aria-hidden="true"></i>
{{element.name}}
<span class="badge">{{element.order}}</span>
<draggable v-if="element.notes" class="list-group" element="ul" :options="dragOptions" :move="onMove">
<transition-group class="list-group" :name="'flip-list'">
<li class="list-group-item" v-for="elementDeep in element.notes" :key="elementDeep.order">
{{elementDeep.name}} <span class="badge">{{ elementDeep.order }}</span>
</li>
</transition-group>
</draggable>
</li>
</transition-group>
</draggable>
</div>
<div class="list-group col-md-6">
<pre>{{listString}}</pre>
</div>
</div>
</template>
Vue js
<script>
import draggable from 'vuedraggable'
var folderOneReady = [
{
"name":"LOREM IPSUM",
"order":1,
"fixed":false
},
{
"name":"MAGNA ALIQUA",
"order":2,
"fixed":false
},
{
"name": "DOLOREM LAUDANTIUM",
"notes": [
{
"name": "Note level deep One",
"order":31,
"fixed":false
},
{
"name": "Note level deep two",
"order":32,
"fixed":false
},
{
"name": "Note level deep deep three",
"order":33,
"fixed":false
}
],
"order":3,
"fixed":false
},
{
"name":"SIT AMET",
"order":4,
"fixed":false
},
{
"name":"NEMO",
"order":5,
"fixed":false
},
{
"name":"ACCUSANTIUM",
"order":6,
"fixed":false
},
{
"name":"ESSE",
"order":7,
"fixed":false
},
{
"name":"DOLORES",
"order":8,
"fixed":false
}
];
export default {
name: 'hello',
components: {
draggable,
},
data () {
return {
list: folderOneReady,
editable:true,
isDragging: false,
delayedDragging:false
}
},
methods:{
onMove ({relatedContext, draggedContext}) {
const relatedElement = relatedContext.element;
const draggedElement = draggedContext.element;
return (!relatedElement || !relatedElement.fixed) && !draggedElement.fixed;
}
},
computed: {
dragOptions () {
return {
animation: 1,
group: 'description',
disabled: !this.editable,
ghostClass: 'ghost'
};
},
listString(){
return JSON.stringify(this.list, null, 2);
}
},
watch: {
isDragging (newValue) {
if (newValue){
this.delayedDragging= true
return
}
this.$nextTick( () =>{
this.delayedDragging =false
})
}
}
}
</script>
Anyone can direct me in the right direction?
It may not be the only problem but you need to set the list prop or use v-model for the nested draggable
Try something like:
<draggable v-if="element.notes" class="list-group" element="ul" :options="dragOptions" :move="onMove" :list="element.notes">