How add parent for each slots in vuejs? - vue.js

I'm trying add parent span for each slot on my component, but it isn't working.
render(createElement) {
return createElement('div', this.$slots.default.map(vnode => createElement('span', vnode)));
},
I want get html like below
<div>
<span><img /></span>
<span><img /></span>
<span><img /></span>
</div>
but max what i can get it is
<div>
<span>
<img />
<img />
<img />
</span>
</div>
how fix it?

Try this.
render(h){
return h("div", this.$slots.default.map(v => {
// eliminate returns, comments
if (!v.tag)
return v
return h('span', [v])
}))
Example.

Related

Vue Computed Value Filter (script setup)

I can't find anything relevant online. The relevant answers online dont use the tag.
Does it even work with script setup?
<template>
<div class="bg-white md:container md:mx-auto w-10">
<ul class="flex flex-wrap p-2 justify-center bg-black text-white">
<li class="p-2">
Filter
</li>
<li class="p-2">
Search
</li>
</ul>
<div class="flex flex-wrap justify-center h-52 content-center">
<div class="flex justify-center">
<div class="mb-3 xl:w-96">
<label
for="exampleSearch2"
class="form-label inline-block mb-2 text-gray-700"
>Search</label
>
<input
v-model="searchValue"
type="search"
class="form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
id="exampleSearch2"
placeholder="Type query"
/>
<div class="flex flex-wrap">
<div>Test: {{ searchValue }}</div>
</div>
</div>
</div>
</div>
<div class="grid lg:grid-cols-4 md:grid-cols-3 gap-8">
<dogCard v-for="breed in breedsArray" :key="breed.id" :breed="breed" />
</div>
</div>
</template>
<script setup>
import dogCard from "../components/dogCard.vue";
import { ref, onMounted, computed } from "vue";
import searchBox from "./searchBox.vue";
let URL = "https://api.thedogapi.com/v1/breeds";
const searchValue = ref("");
let breedsData = ref([]);
// Fetching API Data
function getBreedsArray() {
fetch(URL)
.then((response) => response.json())
.then((data) => {
breedsData.value = data;
console.log("Data Fetched", breedsData.value);
})
.catch((error) => {
console.error(error);
});
}
onMounted(() => {
getBreedsArray();
});
const breedsArray = computed({
get() {
return breedsData.value;
},
set(val) {
breedsData.value = breedsData.value.filter((breed) => breed.name.includes() == searchValue);
},
});
let isVisible = ref(false);
</script>
Im trying to filter the dogCard through a Searchbox. I just don't know how to do it.
The Data of 'breedsData' should change on when something is entered in the Searchbox.
Thanks in advance, I'm stuck for so long now, an i dont how to fix this.
In the filter() in the computed setter, you do:
(breed) => breed.name.includes() == searchValue
But you probably want to use the value of the ref instead of the ref itself, and put it into includes():
(breed) => breed.name.includes(searchValue.value)
Note that this would permanently remove filtered breeds, as you change the breedsData.value. A better approach might be to just use the computed getter (I don't think the setter works the way you want it to anyway):
const breedsArray = computed(() => searchValue.value ?
breedsData.value.filter( breed => breed.name.includes(searchValue.value)) :
breedsData.value
)
Since searchValue is reactive, a change to it will trigger an update of the breedsArray.

V-model inside v-for is filling all the inputs

In few words I have multiple Todolists, once I try to post a new Todo inside the Todolist all the inputs of other Todolists gets fill in with the same words. How can I solve this?
Edit: I can post a new todo without problems and it will appear on the relative todolist (see addTodo method). The ONLY problem is that the v-model is for all the todolists on the field, not only for the one I'm writing in, so as soon as I start to write inside an input I got all the inputs filled.
<div class="todolist" v-for="todolist in todolists" :key="todolist.id">
<div class="td-title">
<h5 class="text-center m-0 py-2 text-light">
{{ todolist.title }}
</h5>
</div>
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
// this is the v-model
<input v-model="todo.title" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>
<div>
<div v-for="todo in todolist.todos" :key="todo.id" class="todo">
{{ todo.title }}
</div>
<div>
<i class="fas fa-times" #click="cancel(todolist.id)"></i>
</div>
</div>
</div>
data
data() {
return {
todolists: [],
loading: true,
todo: {
title: "",
todolist_id: "",
user_id: ""
},
};
},
Methods
getTodoLists() {
axios
.get("http://127.0.0.1:8000/api/todolists")
.then((res) => {
this.todolists = res.data;
this.loading = false;
})
.catch((err) => {
console.log(err);
});
},
cancel(id) {
axios
.delete(`http://127.0.0.1:8000/api/todolists/${id}`)
.then(() => {
this.getTodoLists();
})
.catch((err) => {
console.log(err);
});
},
addTodo(todolist) {
axios
.post(`http://127.0.0.1:8000/api/${todolist}/todos`, this.todo)
.then(() => {
this.todo = {}
this.getTodoLists();
})
.catch((err) => {
console.log(err);
});
},
Like down here: I'm writing on the left input and it shows even on the right input.
SOLVED:
With this computed property (with setter and getter) I solved my problem, now just the input in which I'm typing in is filled, and the todo can be post without a problem.
computed: {
getTitle: {
get: function () {
return "";
},
set: function (value) {
this.todo.title = value;
},
},
},
And this is the v-model with the new computed property instead of todo.title
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
<input v-model="getTitle" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>
You have a variable in data called "todo" and then you are reusing that variable name inside your inner v-for:
<div v-for="todo in todolist.todos" :key="todo.id" class="todo">
{{ todo.title }}
</div>
I think todo is referring to the todo from your data, not from the v-for. Thus, all todo items refer to the same variable. Try to rename one of them and see if it solves the problem.
You are binding your input to your todo declared data object.
Instead, you should bind your input to your todolist object in this way:
<div class="todolist" v-for="todolist in todolists" :key="todolist.id">
....
<input v-model="todolist.title" class="px-2" type="text" />
....
</div>
SOLVED:
With this computed property (with setter and getter) I solved my problem, now just the input in which I'm typing in is filled, and the todo can be post without a problem.
computed: {
getTitle: {
get: function () {
return "";
},
set: function (value) {
this.todo.title = value;
},
},
},
And this is the v-model with the new computed property instead of todo.title
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
<input v-model="getTitle" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>

How to unshift to append under spicific div in Vue

I have comments and this comment containe likes, My propblem is when I try to push replay to the comment it is append to the wrong comment, So I used this.$el; to get the target comment bu the propblem is unshift/push just use with list and it shows error push is not a function
My qustion is how I can use unshift/push to append under spicific div not list
async addReplay(comment, e){
const element = this.$el; //this to get the target div
}
here the post replay function
async getaddReplay(id, setReplayPlace){
await axios.get('/account/api/auth/user/')
.then(response => {
this.currentUserImage = response.data.profile_image
})
const replayData = {
content: this.commentReplayContent,
video: this.$route.params.video_id,
parent: id
}
await axios.post(`/video/api/video/comment/${id}/replay/create/`, replayData)
.then(response => {
console.log(this.setReplayPlace)
// here I want append the item to spicific div
this.setReplayPlace.unshift({ content: this.commentReplayContent, author: this.$store.state.user.username, id:response.data.id, author_image:this.currentUserImage, video:this.$route.params.video_id ,likes:0, total_parents:0, check_like: false, publish:'now'})
this.commentReplayContent = ''
})
.catch(error => {
console.log(error)
})
}
Edit
Here there are comments with replies component and I pass the data to repliy component using props
<ul v-for="(comment, index) in comments" :key="index">
<li class="comment-object">
<div class="image-container">
<img class="profile-pic" :src="comment.author_image" v-on:change="currentUserImage" alt="profile picture" id="user_video_comment_profile_image" refs="user_video_comment_profile_image" />
</div>
<div class="comment-text">
<h2 class="username" style="color: #C2C3C4">{{comment.author}} <span class="muted">· {{comment.publish}}</span>
<DeleteComment :comment="comment" v-if="comments || index" :comments="comments" :index="index" />
</h2>
<p class="comment">{{comment.content}} </p>
<RepliesJustAdded #justAddedReplies="addRepliesToParent" :replaiesJustAdded="replaiesJustAdded" :setReplayPlace="setReplayPlace" :allReplies="allReplies" :replaiesData="replaiesData" v-if="replaiesJustAdded || setReplayPlace || allReplies || replaiesData" />
</div>
</li>
</ul>
The Replay component
<ul v-for="replay in replies" :key="replay.id" id="video_comments_replies" >
<li class="comment-object">
<div class="image-container">
<img class="profile-pic" :src="replay.author_image" alt="profile picture" id="user_video_comment_profile_image" refs="user_video_comment_profile_image" />
</div>
<div class="comment-text">
<h2 class="username" style="color: #C2C3C4">{{replay.author}} <span class="muted">· {{replay.publish}}</span></h2>
<p class="comment">{{replay.content}} </p>
<RepliesActionButtons :replay="replay" />
</div>
</li>
</ul>
I didn't find the line where you are trying to call push but I thing I know what's wrong: your are trying to call push but array-like collections don't have such method. You should convert your collection to array before calling push. Try something like that:
// create a `NodeList` object
const divs = document.querySelectorAll('div');
// convert `NodeList` to an array
const divsArr = Array.from(divs);
After that all methods of Array.prototype will be available.

Bind class item in the loop

i want to bind my button only on the element that i added to the cart, it's working well when i'm not in a loop but in a loop anything happen. i'm not sure if it was the right way to add the index like that in order to bind only the item clicked, if i don't put the index every button on the loop are binded and that's not what i want in my case.
:loading="isLoading[index]"
here the vue :
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading[index]"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here the data :
data () {
return {
products : [],
isLoading: false,
}
},
here my add to cart method where i change the state of isLoading :
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
You can change your isLoading to an array of booleans, and your addToCart method to also have an index argument.
Data:
return {
// ...
isLoading: []
}
Methods:
addToCart(product, index) {
// ...
}
And on your button, also include index:
#click="addToCart(product, index)"
By changing isLoading to an empty array, I don't think isLoading[index] = true will be reactive since index on isLoading doesn't exist yet. So you would use Vue.set in your addToCart(product, index) such as:
this.$set(this.isLoading, index, true)
This will ensure that changes being made to isLoading will be reactive.
Hope this works for you.
add on data productsLoading: []
on add to cart click, add loop index to productsLoading.
this.productsLoading.push(index)
after http request done, remove index from productsLoading.
this.productsLoading.splice(this.productoading.indexOf(index), 1)
and check button with :loading=productsLoading.includes(index)
You can create another component only for product card,
for better option as show below
Kindly follow this steps.
place the content of card in another vue component as shown below.
<!-- Product.vue -->
<template>
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</templete>
<script>
export default {
name: "Product",
data() {
return {
isLoading: false
}
},
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
}
}
</script>
Change your component content as shown below.
<template>
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<product :product="product" />
</div>
</div>
</div>
</div>
</templete>
<script>
import Product from 'path to above component'
export default {
components: {
Product
}
}
</script>
so in the above method you can reuse the component in other components as well.
Happy coding :-)

Checking checkbox programmatically doesn't render the change when using nested arrays with objects

сеI have an array with categories. Each category has children.
When a parent is checked/unchecked, its children must be checked/unchecked as well. If all children are checked, the parent must be checked as well.
Vue updates the fields as expected, but doesn't re-render. I cannot understand why.
Here is my code:
<template>
<div class="card">
<div class="card-body">
<ul class="list-tree">
<li v-for="category in categories" :key="category.id" v-show="category.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + category.id"
v-model="category.checked"
#change="checkParent(category)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label"
:for="'category-' + category.id">
{{ category.name }}
</label>
</div>
<ul>
<li v-for="child in category.children" :key="child.id" v-show="child.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + child.id"
v-model="child.checked"
#change="checkChild(child, category)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label" :for="'category-' + child.id">
{{ child.name }}
<small class="counter">({{ child.products_count }})</small>
</label>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
</template>
export default {
data () {
return {
categories: [],
listItemTemplate: { show: true, markedText: null, checked: false }
}
},
methods: {
checkParent (category) {
category.children.forEach(child => {
child.checked = category.checked
})
},
initializeCategories () {
this.categories = []
this.originalCategories.forEach(originalCategory => {
var parent = this.copyObject(originalCategory)
this.categories.push(parent)
parent.children.forEach (child => {
child = this.copyObject(child)
})
})
},
copyObject (category) {
return Object.assign(category, {...this.listItemTemplate})
}
},
computed: {
...mapState({
originalCategories: state => state.categories,
})
},
mounted () {
this.initializeCategories()
}
}
You need to expand your scope, since you are changing it only within checkParent() method, variables that you are making changes to will not have an effect onto components variables.
Use the index instead of value in categories iteration to find correct category, and then apply changes in scope of whole component:
<li v-for="(category, categoryIndex) in categories" :key="category.id" v-show="category.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + category.id"
v-model="category.checked"
#change="checkParent(categoryIndex)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label"
:for="'category-' + category.id">
{{ category.name }}
</label>
</div> <!-- the rest of the code ... -->
And then in component's method:
methods: {
checkParent (categoryIndex) {
let categoryChecked = this.categories[categoryIndex];
this.categories[categoryIndex].children.forEach((child, childIndex) => {
this.categories[categoryIndex].children[childIndex].checked = categoryChecked;
})
},
I fixed it. The problem wasn't related to the checkboxes at all. The problem was related to the way I've created the categories array.
When I initialize the component, I copy the array from vuex and add new properties (like checked) in order to check the children when the parent is checked. I didn't follow the rules for adding new fields, that's why the children wasn't reactive and didn't get checked when the parent was checked.
Thanks a lot for your effort to help me!