I'm building an App using vuedraggable and I'm dragging vue components. So my question is: is there a possibility to get the component from the vuedraggbale-Events instead of only HTML-Elements. Looking at sortable documentation here https://github.com/RubaXa/Sortable#event-object-demo I can't find a way doing this. Do you have any idea?
Thx you
Make use of the change handler to find the data. Here's a minimum working example:
const app = new Vue({
el: '#app',
data: {
items: [
{ name: 'Alan', content: 'a' },
{ name: 'Blake', content: 'b' },
{ name: 'Chris', content: 'c' },
{ name: 'Dora', content: 'd' },
{ name: 'Ellen', content: 'e' }
],
history: []
},
methods: {
afterAdd(evt) {
console.log(evt)
const element = evt.moved.element
const oldIndex = evt.moved.oldIndex
const newIndex = evt.moved.newIndex
this.history.push(`${element.name} is moved from position ${oldIndex} to ${newIndex}`)
}
}
})
.dragArea {
border: solid 1px black;
background-color: grey;
min-height: 10px;
}
.document-item {
background-color: white;
border: solid 1px black;
margin: 8px 8px 8px 8px;
}
<!-- CDNJS :: Vue (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.6.0/Sortable.min.js"></script>
<!-- CDNJS :: Vue.Draggable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.14.1/vuedraggable.min.js"></script></script>
<div id="app">
<div>
<h3>History: </h3>
<div>
<ul>
<li v-for="msg in history">{{msg}}</li>
</ul>
</div>
</div>
<draggable
class="dragArea"
:options="{group:'people'}"
#change="afterAdd"
:list="items">
<div
class="document-item"
v-for="(item, index) in items"
:key="index">
<h3>{{item.name}}</h3>
</div>
</draggable>
</div>
Related
When working with an array of objects, is it possible in a v-for-loop to assign the current object to a variable and destruct its properties at the same time? Something like this:
<div v-for="(person = {name, age}, index) in persons">
Ultimately I'm looking for a way to use e.g. both the whole person object and its properties in a template.
As far as I know, you can't do both.
But, you can destructure, i.e.
<div v-for="({name, age}, index) in persons">
You could then just access the correct element with the index: persons[index].
Example:
new Vue({
el: "#app",
data: {
todos: [{
text: "Learn JavaScript",
done: false
},
{
text: "Learn Vue",
done: false
},
{
text: "Play around in JSFiddle",
done: true
},
{
text: "Build something awesome",
done: true
}
]
},
methods: {
toggle: function(index) {
this.todos[index].done = !this.todos[index].done
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="({text, done}, i) in todos">
<label>
<input type="checkbox"
v-on:change="toggle(i)"
v-bind:checked="done">
<del v-if="done">
{{ text }}
</del>
<span v-else>
{{ text }}
</span>
{{todos[i]}}
</label>
</li>
</ol>
</div>
There is a template
<div class="wrap">
<p v-for="item in items">
{{ item }}
</p>
</div>
There is a data
data: {
items: [1, 2, 3]
}
Problem: after update DOM I trying to actualization data, but update a data causes to re-rendering a component.
Expected result:
Question: how to synchronisation data after update DOM?
Live demo
Below is one simple demo on the implemention of drag/drop by Vue.
Create one watch, it will watch this.dragedItem. if changed, it will re-calculate the styles which apply to the dragItems.
When drag start, get current dragging item (dragedItem=current item).
When drag end, reset dragging item (dragedItem={}),
When drop, remove item from dragItems, then push to dropItems.
In above steps, we just need to change the data, then Vue will auto render.
You can check HTML Drag And Drop API for more details.
For the transition effects, you can check Vue Guide: Enter/Leave Transition and Vue Guide: State Transtion.
app = new Vue({
el: "#app",
data: {
dragItems: ['A', 'B', 'C', 'D'],
dragedItem: {},
dropItems: [],
styles: {},
defaultStyle: {'opacity': '', 'background-color': 'yellow'}
},
watch: {
dragedItem: function () {
this.styles = this.dragItems.reduce((pre, cur) => {
pre[cur] = cur === this.dragedItem.item ? {'opacity': 0.5, 'background-color': 'blue'} : {'opacity': '', 'background-color': 'yellow'}
return pre
}, {})
}
},
methods: {
onDragStart: function (ev, item, index) {
ev.dataTransfer.setData('text/plain',null)
this.dragedItem = {item, index}
},
onDragEnd: function (ev) {
this.dragedItem = {}
},
onDrop: function (ev) {
this.dropItems.push(this.dragedItem.item)
this.dragItems.splice(this.dragedItem.index, 1)
}
}
})
.dragzone {
display:flex;
flex-direction: row;
}
.dragger {
width: 30px;
height: 30px;
text-align: center;
border: 1px solid gray;
}
.dropzone {
width: 200px;
height: 30px;
background: green;
padding: 10px;
display:flex;
flex-direction: row;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div class="dragzone">
<div class="dragger" draggable="true"
v-for="(item, index) in dragItems" :key="index"
:style="styles[item] ? styles[item] : defaultStyle"
#dragstart="onDragStart($event, item, index)"
#dragend="onDragEnd($event)"
>
{{item}}
</div>
</div>
<div class="dropzone" #drop.prevent="onDrop($event)"
#dragover.prevent=""
>
<div class="dragger" style="background-color:yellow" draggable="true" v-for="(item, index) in dropItems" :key="index">
{{item}}
</div>
</div>
</div>
How can i toggle form inside v-for loop,I have a form inside v-for which i want to display (toggle) on click.
But when i click all the form inside the v-for gets toggled.
Secondly is it better approach to keep the form inside the loop,when have large amount of data inside loop or load it as a separate component.
This is what i am trying to do.
new Vue({
el: "#app",
data: {
todos: [{
text: "Learn JavaScript"
},
{
text: "Learn Vue"
},
{
text: "Play around in JSFiddle"
},
{
text: "Build something awesome"
}
],
show: ''
},
methods: {
toggle: function(todo) {
this.show = !this.show
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="(todo,key) in todos">
<p>
{{ key+1 }} - {{ todo.text}} <span #click="toggle(todo)"><b>Contact</b></span>
<div v-if="show">
<hr />
<p>
<label>Message</label>
<input type="text">
</p>
<hr />
</div>
</p>
</li>
</ol>
</div>
There is only 1 reactive variable show. Setting it to true while all form is using v-if="show", will show everything.
You can set show to something that each form uniquely have. For example, its text, and perform a v-if using its text.
demo: https://jsfiddle.net/jacobgoh101/umaszo9c/
change v-if="show" to v-if="show === todo.text"
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="(todo,key) in todos">
<p>
{{ key+1 }} - {{ todo.text}} <span #click="toggle(todo)"><b>Contact</b></span>
<div v-if="show === todo.text">
<hr />
<p>
<label>Message</label>
<input type="text">
</p>
<hr />
</div>
</p>
</li>
</ol>
</div>
change toggle method
new Vue({
el: "#app",
data: {
todos: [{
text: "Learn JavaScript"
},
{
text: "Learn Vue"
},
{
text: "Play around in JSFiddle"
},
{
text: "Build something awesome"
}
],
show: ''
},
methods: {
toggle: function(todo) {
if (this.show === todo.text)
this.show = false
else
this.show = todo.text
}
}
})
property "show" should be a prop of todo,not prop of data
new Vue({
el: "#app",
data: {
todos: [{
text: "Learn JavaScript"
},
{
text: "Learn Vue"
},
{
text: "Play around in JSFiddle"
},
{
text: "Build something awesome"
}
].map(o=>({...o,show:false}))
},
methods: {
toggle: function(todo) {
todo.show = !todo.show
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="(todo,key) in todos">
<p>
{{ key+1 }} - {{ todo.text}} <span #click="toggle(todo)"><b>Contact</b></span>
<div v-if="todo.show">
<hr />
<p>
<label>Message</label>
<input type="text">
</p>
<hr />
</div>
</p>
</li>
</ol>
</div>
Can I access Vue components data properties and methods from external javascript? I am trying to create a hybrid application where a portion of the screen is a Vue component, and I want to call a method inside that component on click of a button which is handled by pure js. Is there a way to achieve this?
Thanks!
Yes. You need to assign your Vue object to a variable (i.e. vue) and then you can access vue.methodName() and vue.propertyName:
// add you new Vue object to a variable
const vue = new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
});
// Add event listener to outside button
const button = document.getElementById('outsideButton');
button.addEventListener('click', function() {
vue.toggle(vue.todos[1]);
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
<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>
<div class="outside">
<button id="outsideButton">
Click outside button
</button>
</div>
Yes, you can add an event listener to the Vue component that listens for the button click's event. See example here on codepen.
JS
new Vue({
el: '#app',
methods: {
clickMethod(event){
if (event.target.id === 'outsideButton') {
alert('button clicked')
}
}
},
created(){
let localThis = this
document.addEventListener('click', this.clickMethod)
}
})
HTML
<div>
<button id="outsideButton">Button</button>
<div id="app">
</div>
</div>
This is warning when i click on go to contact in tab about: "Property or method "switchTo" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in component )."
How do I fix this?
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
Just change your about template to this
<template id="about">
<p>about go to contact</p>
</template>
new Vue({
el: '#app',
data: {
currentPage: 'home',
},
methods: {
switchTo: function(page) {
this.currentPage = page;
}
},
components: {
home: {
template: `#home`,
},
about: {
template: `#about`,
},
contact: {
template: '#contact'
}
}
})
.navigation {
margin: 10px 0;
}
.navigation ul {
margin: 0;
padding: 0;
}
.navigation ul li {
display: inline-block;
margin-right: 20px;
}
input, label, button {
display: block
}
input, textarea {
margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
<div class="navigation">
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
<div class="pages">
<keep-alive>
<component v-bind:is="currentPage">
</component>
</keep-alive>
</div>
</div>
<template id="home">
<p>home</p>
</template>
<template id="about">
<p>about go to contact</p>
</template>
<template id="contact">
<p>contact</p>
</template>
I already solved a problem like this in this question: Calling methods in Vue build
It's not the same problem so it's not a repeated question, but the answer is the same:
In the created hook, add the component to window.componentInstance like this:
methods: {
foo () {
console.log('bar')
}
},
created () {
window.componentInstance = this
}
Then you can call the method anywhere like this:
window.componentInstance.foo()