This is probably a really naive question that is less about vue-drag-drop and more about vuejs, which I'm new to.
If I have two lists of stuff:
<ul>
<li v-for="thing in thing">
<drag :transfer-data="{ thing }">
<span>{{ thing.title }}</span>
</drag>
</li>
</ul>
<ul>
<li v-for="day in days">
<drop #drop="handleDrop" ref="day"></drop?
</li>
</ul>
In the handleDrop() method I can see the event, which include what was dragged into the list item, but I don't see how I have any context on which item in the array the dragged thing was dragged into. I tried using a ref on the drop element, but that didn't seem to be what I wanted.
How do I know which day the item was dragged into?
I figured out I need to pass the data myself. One way to do this is to wrap the function vue-drag-drop provides.
<ul>
<li v-for="day in days">
<drop #drop="function(data, event) { handleDrop(data, day, event); }" ref="day"></drop>
</li>
</ul>
That seems to work as expected, but I'm wondering if there's another way to do it like using a library attribute (similar to ref).
Maybe I missed something, but wouldn't this in handleDrop() be the component that was dropped onto? That's how it is for other event handlers in Vue.
See here for an example:
<div id="example-2">
<!-- `greet` is the name of a method defined below -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// define methods under the `methods` object
methods: {
greet: function (event) {
// `this` inside methods points to the Vue instance
alert('Hello ' + this.name + '!')
// `event` is the native DOM event
if (event) {
alert(event.target.tagName)
}
}
}
})
// you can invoke methods in JavaScript too
example2.greet() // => 'Hello Vue.js!'
Update
The above is true, but you want to know which day is that drop component. To achieve that, pass it as a prop to the child drop component like so:
Vue.component('drop', {
props: ['day'],
// ...
})
<ul>
<li v-for="day in days">
<drop #drop="handleDrop" ref="day" :day="day"></drop>
</li>
</ul>
The :day="day" is the key. Then, in handleDrop(), this.day will be the day
Related
we are building a chat application in Vuejs, now every chat message is component in our application, now whenever we are changing the value of one chat message, the value of all chat messages changes
What is happening
source code
App Component
const App = new Vue({
el: '#myApp',
data: {
children: [
MyCmp
],
m1: '',
m2: '',
m3: 'Hello world',
m4: 'How are you'
},
methods: {
sendMessage (event) {
if(event.key == "Enter") {
this.m2= this.m3;
this.children.push(MyCmp);
}
},
}
});
component code
let MyCmp = {
props: ['myMessage'],
template: `
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p>{{ myMessage }}</p>
</div>
</li>
`
};
** view where components are generating **
<ol class="chat">
<template v-for="(child, index) in children">
<component :is="child" :key="child.name" v-bind="{myMessage: m3}"></component>
</template>
</ol>
Even though you are creating new components by pushing them into the children array, they are still getting bound to the same data via the line v-bind="{myMessage: m3}". Whenever you change m3, it will be passed down to all the child components and they will all render the same message.
This is an odd way of creating custom components since you could easily do so using the templating syntax or render function provided by Vue.
Solution 1 - Recommended
Change your approach - push message strings instead of card component definitions into children and use MyCmp inside v-for to render the message cards.
So the new template could be refactored as :-
<ol class="chat">
<template v-for="(message, index) in children">
<my-cmp :key="index" :my-message="message"></my-cmp>
</template>
</ol>
And inside App component, you can replace this.children.push(MyCmp) with this.children.push(messageVariable); where messageVariable contains the message that you receive from the input box.
Why is the recommended? This is a standard approach where component lists are rendered based on an array of data. It will be easier to scale and maintain.
Solution 2
You can use the v-once directive to bind the message one-time as static text. The binding won't be updated even if m3 changes on the parent.
Then MyCmp template will become :-
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p v-once>{{ myMessage }}</p>
</div>
</li>
You bind myMessage of all your components instances with one variable m3. So, when m3 is changed myMessage in all instances changes respectively. Use another variable (e.g. msg) for rendering the message and then use myMessage property only for the initialisation of msg, like this:
let MyCmp = {
props: ['myMessage'],
data: function () {
return {
msg: this.myMessage
}
},
template: `
<li class="self">
<div class="avatar"><img src="" draggable="false"/></div>
<div class="msg">
<p>{{ msg }}</p>
</div>
</li>
`
};
So basically I have this;
<ul class="queryView">
<li
v-for="(object, index) in Objects"
>
{{ object.key }}
<input
type="text"
#input="saveValue(value)"
>
</li>
</ul>
...
saveValue(value){
... do somthing with value
},
Since it's in a loop, v-model does not work as they will affect each looped element. i.e If in one input field I put in a word all of the fields will display the same word.
thus I need to get the input value directly into the function.
v-model does work if properly used.
See this JS Fiddle:
https://jsfiddle.net/eywraw8t/167740/
But if you want to use a function to handle the values, it is also fine, but much more verbose.
See this JS Fiddle:
https://jsfiddle.net/eywraw8t/167731/
See the docs: https://v2.vuejs.org/v2/api/#v-on
[…] the method receives the native event as the only argument. If using inline statement, the statement has access to the special $event property: v-on:click="handle('ok', $event)".
The "input" event has a target property, which in your case is your <input> element, on which you can read its current value.
new Vue({
el: '#app',
methods: {
saveValue(event) {
const target = event.target;
const value = target.value;
console.log(value);
},
otherMethod(text1, event) {
console.log(text1);
console.log(event.target.value);
},
},
});
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<ul>
<li v-for="i in 3">
{{i}}<input #input="saveValue" />
</li>
</ul>
<p>
With inline statement:
<input #input="otherMethod('other', $event)" />
</p>
</div>
I had this issue while trying to render html into a vue component.
I am trying to insert component html through x-template. The issue is when I was trying to display the value {{i.value}} like this it was throwing error on console.
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection">{{ i.id }}</li>
</ul>
</div>
</script>
Vue.component('menu', {
template: '#first-template',
data() {
return {
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}],
}
}
});
The error on console was:
But when I was giving value as attribute like:
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection" :id="i.id"></li>
</ul>
</div>
</script>
it works perfect.
Anyone know any fix ?
You should not put script/x-template tages inside of the element that you mount to the main instance to. Vue 2.0 will read all of its content and try to use it as a template for the main instance, and Vue's virtualDOM treats script/x-template's like normal DOM, which screws everthing up,
Simply moving the template out of the main element solved the problem.
Source
This is a suggestion, not a answer.
As #DmitriyPanov mentioned, you'd better bind unique key when using v-for.
Another issue is you'd better to use non built-in/resevered html elements.
so change component id from menu to v-menu or else you like.
Then simulate similar codes below which are working fine.
I doubt the error is caused by some elements of dataCollection doesn't have key=id (probably you didn't post out all elements). You can try {{ 'id' in i ? i.id : 'None' }}.
Vue.component('v-menu', { //
template: '#first-template',
data() {
return {
newDataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}, {'xx':0}],
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}]
}
}
});
new Vue({
el: '#app',
data() {
return {testProperty: {
'test': '1'
}}
},
methods:{
test: function() {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<v-menu></v-menu>
</div>
<script type="text/x-template" id="first-template">
<div>
<div style="float:left;margin-right:100px;">
<p>Old:</p>
<ul>
<li v-for="(i, index) in dataCollection" :key="index">{{ i.id }}</li>
</ul>
</div>
<div>
<p>Adjusted:</p>
<ul>
<li v-for="(i, index) in newDataCollection" :key="index">{{ 'id' in i ? i.id : 'None' }}</li>
</ul>
</div>
</div>
</script>
I think the problem here lies in the placement of the X-Template code (I had the same issue). According to the documentation:
Your x-template needs to be defined outside the DOM element to which Vue is attached.
If you are using some kind of CMS, you might end up doing just that.
What helped me in that case was (based on your example):
Placing the X-template script outside the #app
passing the collection as a prop to the v-menu component:
<v-menu v-bind:data-collection="dataCollection"></v-menu>
list dataCollection as a prop inside the v-menu component:
Vue.component('v-menu', { //
template: '#first-template',
props: [ "dataCollection" ],
...
});
I hope that helps anyone.
In 2.2.0+, when using v-for with a component, a key is now required.
You can read about it here https://v2.vuejs.org/v2/guide/list.html#v-for-with-a-Component
I have vue event attached to elements that are looped.
I'm having challenge trying display CRUD action on an item, instead, all the looped items display their individual CRUD
How can I make it unique to an element? any vue event modifier for this?
Below is my code
<i class="material-icons">list</i>
<div v-if="showButtons">
<ul>
<li>Edit</li>
<li>Delete</li>
<li>Stop</li>
</ul>
</div>
The showIcons method below
showIcons: function () {
this.showButtons = true
}
since you are binding showButtons property to all your looped items, when you mouse over an item theshowButtonsis toggled true and all the items bound to showButtons are displayed.
So you need to use a unique identifier to decide whether the buttons for an item should be displayed or not.
You might be looping using v-for so you can make use of index.
template
<div v-for="(item , index)">
<i class="material-icons">list</i>
<div v-if="currentlyShowing === index">
<ul>
<li>Edit</li>
<li>Delete</li>
<li>Stop</li>
</ul>
</div>
</div>
script
data(){
return{
currentlyShowing: null
}
},
methods:{
showIcons: function (index) {
this.showButtons = true
this.currentlyShowing = index;
}
}
I have a component named controls:
<li class="controls__item" v-if="options[0].save == 'show'">
<button class="btn" :options[0].saveAttr>Save</button>
</li>
I'm having trouble rendering an attribute defined in the options property:
<controls :options='[{ save: "show", saveAttr: "sampleAttr='0' "}]'></controls>
This is what I'm trying to achieve:
<button class="btn" sampleAttr='0'>Save</button>
That's not the correct syntax for binding in Vue.
If the name of the attribute to bind to is never going to change, you should specify the name in the controls component:
<li class="controls__item" v-if="options[0].save == 'show'">
<button class="btn" :sampleAttr="options[0].saveAttr">Save</button>
</li>
And just change the options to pass in a value for saveAttr:
<controls :options='[{ save: "show", saveAttr: "0" }]'></controls>
If the name of the attribute (or the number of attributes) could change, then you should pass an object to the v-bind directive like so:
<li class="controls__item" v-if="options[0].save == 'show'">
<button class="btn" v-bind="options[0].saveAttrs">Save</button>
</li>
And then pass in an object for saveAttrs:
<controls :options='[{save : "show", saveAttrs: { sampleAttr: 0 }]'></controls>
Let's start with your testdata (just a little clean up) let's say you have two buttons since it seems like you want to do that later on. I'm not yet sure what the save : "show" is supposed to do - so I do my best to give a flexible example.
[{
'text': 'Save',
'click': function() { alert('save'); }
,{
'text': 'Delete',
'click': function() { alert('delete'); }
}]
Not lets say you have that testdata in your component called "controls"
<controls :options="[{'text': 'Save','click': function() { alert('save'); },{'text': 'Delete','click': function() { alert('delete'); }}]"> </controls>
As we can see your controls has an property called options. So your code for your component should look like:
<template>
<div class="controls">
<li class="controls__item" v-for="control in options">
<button class="btn" #click="control.click">{{ control.text }}</button>
</li>
</div>
</template>
<script>
export default {
props: ['options']
}
</script>
You need to define the prop you want to bind on the component (options). Options is now bound according to our test date. Since it's an array we can use v-for to loop through it. We then bind the given text as button content and the given click function as on click event.
I hope this helps.