Manipulation HTML DOM of Vue3 List Item - javascript

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>

Related

How to bind array in DOM using Vue.js

I am working on an application where I bind the list of data into DOM using Vue.js. But it is not working I have used v-for, v-list, and v-repeat but don't get it working. Here is my code both for the template and the script.
<div class="weather-info" v-if="weather!=undefined">
<div v-repeat="item in weather">
<div class="location-box">
<div class="location">{{item.day}}
</div>
<!-- <div class="date">{{ todaysDate() }}</div> -->
</div>
<div class="weather-box">
<div class="temp">{{ Math.round(item.temprature) }}°c</div>
<div class="weather">{{Math.round(item.windSpeed)}}</div>
<div class="icon">
<img src="{{iconUrl}}.png"/>
</div>
</div>
</div>
</div>
Here is the code of the Script
export default {
data() {
return {
url_base: "https://localhost:7197/api/weather/",
weather: undefined,
};
},
methods : {
async fetchWeather(e) {
if (e.key == "Enter") {
let response = await axios.get(`${this.url_base}forecast?city=${this.query}`);
this.setResults(response.data);
}
},
setResults(res) {
console.log(res)
if(res.isSuccessful === true){
this.weather = res.response;
}else{
// error message
}
},
},
};
The JSON i received in res is show below.
Please try to use v-for instead of v-repeat, you can replace it as follow:
<div v-for="(item, key) in weather" :key="key">
{{ item }}
...
</div>
Your code should work fine, Just wondering from where you are invoking the fetchWeather method. I just created a working demo below. Please have a look and try to find the root cause of the issue.
Just for a demo purpose I am using mock data but you can replace that with an API call.
new Vue({
el: '#app',
data: {
weather: undefined
},
mounted() {
this.weather = [
{ day: 'Day 1' },
{ day: 'Day 2' },
{ day: 'Day 3' },
{ day: 'Day 4' }
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="weather-info" v-if="weather && weather.length">
<div v-for="(item, index) in weather" :key="index">
{{ item.day }}
</div>
</div>
</div>

How to pass props to child component in vue

I have a parent component where I am doing the API call and getting the response. So what I am trying to do is pass this response as a prop to child component in Vue.
So here is my parent component and the call:
<button class="btn button col-2" #click="addToCart()">
Add to cart
</button>
addToCart: function () {
let amount = this.itemsCount !== "" ? this.itemsCount : 1;
if(this.variationId != null) {
this.warningMessage = false;
cartHelper.addToCart(this.product.id, this.variationId, amount, (response) => {
this.cartItems = response.data.attributes.items;
});
} else {
this.warningMessage = true;
}
},
So I want to pass this "this.cartItems" to the child component which is:
<template>
<div
class="dropdown-menu cart"
aria-labelledby="triggerId"
>
<div class="inner-cart">
<div v-for="item in cart" :key="item.product.id">
<div class="cart-items">
<div>
<strong>{{ item.product.name }}</strong>
<br/> {{ item.quantity }} x $45
</div>
<div>
<a class="remove" #click.prevent="removeProductFromCart(item.product)">Remove</a>
</div>
</div>
</div>
<hr/>
<div class="cart-items-total">
<span>Total: {{cartTotalPrice}}</span>
Clear Cart
</div>
<hr/>
<router-link :to="{name: 'order'}" class="btn button-secondary">Go To Cart</router-link>
</div>
</div>
</template>
<script>
export default {
computed: {
},
methods: {
}
};
</script>
So I am quite new in vue if you can help me with thi, I would be really glad.
Passing props is quite simple. If cartItems is what you wan´t to pass as a prop, you can do this:
<my-child-component :cartItems="cartItems"></my-child-component>
In this case you implemented your child as myChildComponent. You pass cartItems with :cartItems="cartItems" to it. In your child you do this:
props: {
cartItems: Object
}
Now you can use it with this.cartItems in your methods or {{cartItems}} in your themplate.
Vue.component('Child', {
template: `
<div class="">
<p>{{ childitems }}</p>
</div>
`,
props: ['childitems']
})
new Vue({
el: '#demo',
data() {
return {
items: []
}
},
methods: {
getItems() {
//your API call
setTimeout(() => {
this.items = [1, 2]
}, 2000);
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="getItems">get data</button>
<Child v-if="items.length" :childitems="items" />
</div>
You can wait for response, and when you gate this.cartItems then render your child component with a v-if="this.cartItems.length" condition

getBoundingClientRect is not a function on vueJs

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>

How render component in v-for by button from parent

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>

How to keep sortable arrays and components in order in Vue.js

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>

Categories

Resources