I would like to find the top and bottom edge of a div when i move my cursor over it. The div is generated dynamically. However, it produced error message
getBoundingClientRect is not a function
<template>
<div>
<div v-for="(i, index) in divs" :key="index">
<div :ref="'ref_' + index" #mouseover="divDragOver($event, index)">
This is div {{ index }}
</div>
</div>
</div>
</template>
<script>
export default{
methods: {
divDragOver(e, i){
var divTop = this.$refs["ref_" + i].getBoundingClientRect().top;
console.log(divTop);
}
}
}
</script>
You can use event.target as another user suggested which is, I think, the cleanest way
<template>
<div>
<div v-for="(i, index) in divs" :key="index">
<div ref="myDiv" #mouseover="divDragOver">This is div {{ index }}</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return { divs: [1, 2, 3, 4] };
},
methods: {
divDragOver(event) {
console.log(event.target); // this will be your mouseover div
}
}
};
</script>
For your issue though, based on Vue.js documentation:
When ref is used together with v-for, the ref you get will be an array containing the child components mirroring the data source.
So you don't have to use the index on your ref creation, while you will end up with divs.length arrays which their first element will be your reference.
If you had to use refs:
<template>
<div>
<div v-for="(i, index) in divs" :key="index">
<div ref="myDiv" #mouseover="divDragOver($event, index)">This is div {{ index }}</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return { divs: [1, 2, 3, 4] };
},
methods: {
divDragOver(e, i) {
console.log(this.$refs.myDiv); // this will be an array with all your refs, use i to access
}
}
};
</script>
So you were getting an error because your element's ref was on this.$refs["ref_" + i][0]
Try something like that:
<template>
<div>
<div v-for="index in divs" :key="index">
<div :ref="'ref_' + index" #mouseover="divDragOver($event, index)">
This is div {{ index }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data: () => ({
divs: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
}),
methods: {
divDragOver(e, i) {
let [div] = this.$refs['ref_' + i];
let top = div.getBoundingClientRect().top;
console.log(top);
},
},
};
</script>
Your code doesn't work, because the this.refs return an Array.
You can handle this problem by accessing the target element in the event:
<script>
export default{
methods: {
divDragOver(e){
var divTop = e.target.getBoundingClientRect();
console.log(divTop);
}
}
}
</script>
Related
i use vue 3 and i want to manipulate a single list item by button click
this is my html:
<socialDiv v-for="(follower, i) in followerList" :key="follower.id" :ref="el => { followerDiv[i] = el }">
<div class="text-md text-gray-800">{{ follower.name }}</div>
<div class="text-sm text-gray-500">{{ follower.username }}</div>
<button #click="handleBtnClick" id="fbtn">{{ follower.btn }}</button>
this is my script:
<script setup>
const followerDiv = ref({})
function handleBtnClick() {
console.log(followerDiv.value)
}
</script>
this is my output:
i'm able to access followerDiv.value[0] but i cannot manipulate the item itself as a DOM element.
how can i access the child items as DOM elements?
Update:
To access the list items i have to add "$el" after value.
I can access the values via:
followerDiv.value[i].$el.style.background = "red"
If I understood you correctly, you can pass index to your function :
const { ref } = Vue
const app = Vue.createApp({
setup() {
const followerDiv = ref({})
const followerList = ref([{id: 1, name: 'aa', username: 'aa', btn: 'aa'}, {id: 2, name: 'bb', username: 'bb', btn: 'bb'}])
function handleBtnClick(i) {
followerDiv.value[i].style.color = "red"
}
return {
followerList, followerDiv, handleBtnClick
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(follower, i) in followerList" :key="follower.id" :ref="el => { followerDiv[i] = el }">
<div class="text-md text-gray-800">{{ follower.name }}</div>
<div class="text-sm text-gray-500">{{ follower.username }}</div>
<button #click="handleBtnClick(i)" id="fbtn">{{ follower.btn }}</button>
</div>
</div>
I have a Vue 3 app. In this app, I need to show a list of items and allow a user to choose items in the array. Currently, my component looks like this:
MyComponent.vue
<template>
<div>
<div v-for="(item, index) in getItems()" :key="`item-${itemIndex}`">
<div class="form-check">
<input class="form-check-input" :id="`checkbox-${itemIndex}`" v-model="item.selected" />
<label class="form-check-label" :for="`checkbox-${itemIndex}`">{{ item.name }} (selected: {{ item.selected }})</label>
</div>
</div>
<button class="btn" #click="generateItems">Generate Items</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
data() {
return itemCount: 0
},
methods: {
generateItems() {
this.itemCount = Math.floor(Math.random() * 25) + 1;
},
getItems() {
let items = reactive([]);
for (let i=0; i<this.itemCount; i++) {
items.push({
id: (i+1),
selected: false,
name: `Item #${i+1}`
});
}
return items;
}
}
}
</script>
When I click select/deselect the checkbox, the "selected" text does not get updated. This tells me that I have not bound to the property properly. However, I'm also unsure what I'm doing wrong.
How do I bind a checkbox to the property of an object in an Array in Vue 3?
If you set a breakpoint in getItems(), or output a console.log in there, you will notice every time that you change a checkbox selection, it is getting called. This is because the v-for loop is re-rendered, and it'll call getItems() which will return it a fresh list of items with selected reset to false on everything. The one that was there before is no longer in use by anything.
To fix this, you could only call getItems() from generateItems() for example, and store that array some where - like in data, and change the v-for to iterate that rather than calling the getItems() method.
Steve is right.
Here is a fixed version: Vue SFC Playground.
<template>
<div>
<div v-for="(item, index) in items" :key="`item-${index}`">
<div class="form-check">
<input type="checkbox" class="form-check-input" :id="`checkbox-${index}`" v-model="item.selected" />
<label class="form-check-label" :for="`checkbox-${index}`">{{ item.name }} (selected: {{ item.selected }})</label>
</div>
</div>
<button class="btn" #click="generateItems">Generate Items</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
data() {
return { items: [] }
},
methods: {
generateItems() {
this.itemCount = Math.floor(Math.random() * 25) + 1;
this.getRndItems();
},
getRndItems() {
this.items = reactive([]);
for (let i=0; i<this.itemCount; i++) {
this.items.push({
id: (i+1),
selected: false,
name: `Item #${i+1}`
});
}
}
}
}
</script>
Let's say I have a form, and can add or remove a column by a click.
I use v-for to render the vue components, when I try to use splice() to delete a specific component, it always delete the last component in the array.
I can't figure out what I did wrong here, any hint will be very appreciated.
Here is a part of my code:
the problem is occured at removePerson method.
Parent Component
<template>
<div class="form-container">
<template>
<div v-for="(child, key) in otherPersonArray" :key="key" class="form-container">
<button #click="removePerson(key)" class="close">X</button>
<component :is="child"></component>
</div>
</template>
<button #click="addPerson" >+ Add Person</button>
</div>
</template>
<script>
import otherPerson from './OtherPerson';
export default {
components: {
otherPerson
},
data() {
return {
otherPersonArray: [],
}
},
methods: {
addPerson() {
if (this.otherPersonArray.length <= 10) {
this.otherPersonArray.push(otherPerson);
}
},
removePerson(key) {
this.otherPersonArray.splice(key, 1);
},
},
}
</script>
For example, when I try to delete the component which input value is 1, it delete the component which input value is 2.
otherPerson component
<template>
<div class="form-container">
<div class="person">
<div class="row">
<div class="form-group col-6">
<label for="inventor-last-name">Lastname of Person *</label>
<div class="input-container">
<input v-model="lastName" type="text" class="form-control form-control-lg">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
lastName: '',
}
},
}
</script>
You can use Array.prototype.filter
removePerson(key) {
this.otherPerson = this.otherPersonArray.filter((x, i) => i !== key);
}
there are couple of ways to achieve this but for now can you try these:
removePerson(key) {
this.otherPersonArray = this.otherPersonArray.filter(person => {
return person.key === key;
})
}
OR
const index = this.otherPersonArray.indexOf(ele by key); // get index by key person key
if (index > -1) {
this.otherPersonArray.splice(index, 1);
}
What I am understanding key is index here then you should follow this:
var filtered = this.otherPersonArray.filter(function(value, index){
return index !== key;
});
let me know if still it not working for you?
Here is example :
How i can render v-if component by button(button in parent) click inside v-for loop? and should render only in that item where clicked
<div v-for="item in items">
<button #click >Show child<button>
<div>{{item.name}}</div>
<child v-if="this button clicked" :item="item"><child>
<div>
You have to store info about state of every item (if it was clicked) in your data. Then, when you click on button you should update clicked property for particular item. Finally if item.clicked is set on true you will show your child component (or any other html).
<template>
<div>
<div v-for="item in items" :key="item.id">
<button #click="item.clicked = true" >Show child</button>
{{item.name}}
<div v-if="item.clicked">Item child</div>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: function() {
return {
items: [
{
id: 1,
name: 'test1',
clicked: false
},
{
id: 2,
name: 'test2',
clicked: false
},
{
id: 3,
name: 'test3',
clicked: false
}
]
}
}
}
</script>
Plain and simple you just have to set some flag for latter v-if:
<div id="app">
<div v-for="item in items">
<button #click="$set(item, 'shown', true)">Show child</button>
<div>{{ item.name }}</div>
<div v-if="item.shown">Child component</div>
</div>
</div>
Here, $set() is used because initial item could lack shown field, so setting it directly with item.shown=true won't be reactive.
You can also hide button after click:
<button #click="$set(item, 'shown', true)" v-if="!item.shown">Show child</button>
To toggle visibility you just have to do it like this:
<button #click="$set(item, 'shown', !item.shown)">
{{ item.shown ? 'Hide' : 'Show' }} child
</button>
JSFiddle
You can take advantage of an item... index available in v-for directive (e.g. v-for="(item, i) in items"), to bind it (index of the item) to the function which shows an item by changing it's property:
Update: Initial answer has been deleted after requirements refinement.
Since you prefer to keep from mutation of items, you can wrap them in Map object (as keys) and keep visibility settings separately as Map values. Unfortunately, as far as I know, for the time being Vue.js does not support reactivity for Map objects, that's why I have to trigger rerendering manually by using forceUpdate:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
template: '<p>Visible child</p>'
})
new Vue({
el: "#demo",
template: `
<div>
<div v-for="item in items">
<button #click="toggleChild(item)">Toggle child</button>
<div>{{item.name}}</div>
<child v-if="isVisible(item)" :item="item"></child>
</div>
</div>
`,
data () {
return {
itemsMap: new Map(
[
{ name: 'test1' },
{ name: 'test2' }
].map(item => [item, { visible: false }])
)
};
},
methods: {
toggleChild(item) {
this.itemsMap.set(item, { visible: !this.itemsMap.get(item).visible });
this.$forceUpdate();
},
isVisible(item) {
return this.itemsMap.get(item).visible;
}
},
computed: {
items: function() {
return Array.from(this.itemsMap.keys());
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo"></div>
I'm using Vue.js and Dragula to make a drag and drop tile page. Each tile contains its own set of data, so each tile is a Vue component.
The problem is that once I drag and drop one of the tiles, the DOM elements and the data array in the Vue instance fall out of sync and start to cause problems. Just dragging and dropping doesn't create a problem, but once I drag and drop something and then try to delete it, everything goes wrong.
Here's a fiddle: https://jsfiddle.net/wfjawvnL/13/
Here's my HTML, with the component template:
<body>
<div id="app">
<div id="draggable" class="wrapper">
<template v-for="(index, item) in list">
<block :id="index" :index="index" :item="item" :name="item.name">
</block>
</template>
</div>
<div class="footer">
<pre>{{ $data | json }}</pre>
</div>
</div>
</body>
<template id="block-template">
<div :id="index" :class="[name, 'block']">
<div class="text name">{{ name }}</div>
<div>Index: {{ index }}</div>
<span class="delete" v-on:click="removeItem(item)">✗</span>
</div>
</template>
Here's my Vue instance:
var vm = new Vue({
el: '#app',
data: {
list: [
{name: 'item1'},
{name: 'item2'},
{name: 'item3'},
{name: 'item4'},
{name: 'item5'}
]
},
methods: {
reorder: function (element, sibling) {
var children = element.parentNode.querySelectorAll(".block");
var length = this.list.length;
var ids = [];
for (var i = 0; i < length; i++) {
if (children[i].id) {
ids.push(children[i].id);
}
children[i].id = i;
}
var vm = this;
var newArr = ids.map(function (id) {
return vm.list[id];
});
this.list = newArr;
}
}
});
Here's the component:
Vue.component('block', {
template: '#block-template',
props: ['index', 'name', 'item'],
methods: {
removeItem: function (item) {
vm.list.$remove(item);
}
}
});
And I'm calling Dragula like this:
dragula([document.getElementById('draggable')]).on('drop', function (el, target, source, sibling) {
vm.reorder(el, sibling);
});
This fiddle works fine but doesn't use components: https://jsfiddle.net/z2s83yfL/1/
I had two problems. First, I was binding the array index and using it as an id. The index was being changed, which was also causing the id to change. Second, using v-for on a template tag was causing some issues. I changed the tags to divs and it now works.
Working fiddle: https://jsfiddle.net/wfjawvnL/18/
Vue stuff:
Vue.component('block', {
template: '#block-template',
props: ['name', 'item'],
methods: {
removeItem: function (item) {
vm.list.$remove(item);
}
}
});
var vm = new Vue({
el: '#app',
data: {
list: [
{name: 'item1'},
{name: 'item2'},
{name: 'item3'},
{name: 'item4'},
{name: 'item5'}
]
},
ready: function() {
var self = this;
var from = null;
var drake = dragula([document.querySelector('#draggable')]);
drake.on('drag', function(element, source) {
var index = [].indexOf.call(element.parentNode.children, element);
from = index;
});
drake.on('drop', function(element, target, source, sibling) {
var index = [].indexOf.call(element.parentNode.children, element);
self.list.splice(index, 0, self.list.splice(from, 1)[0]);
});
}
});
HTML stuff:
<body>
<div id="app">
<div id="draggable" class="wrapper">
<div class="wrapper" v-for="item in list">
<block :item="item" :name="item.name">
</block>
</div>
</div>
<pre>{{ $data | json }}</pre>
</div>
</body>
<template id="block-template">
<div :class="[name, 'block']">
<div class="text name" v-on:click="removeItem(item)">{{ name }}</div>
<span class="delete" v-on:click="removeItem(item)">✗</span>
</div>
</template>