I have a todo app that I am working on which I divided it into two component one for form and the other for the todo list. in todo list I have a Method that delete the todo but it delete two todo instead of one I tried to console log the component that I click on and found that it get logged twice. I tried prevent method on click solution i found it here in stackoverflow but it didn't work.
<template>
<div class="todo" v-for="(todo, index) in todos" :key="index">
<div class="todo-text">{{ todo.text }}</div>
<div class="IconDiv">
<fa #click.prevent="deleteTodo(index)" class="icon" icon="trash-alt" />
<fa #click="completeTodo(index)" class="icon" icon="edit" />
<fa class="icon" icon="check-square" />
</div>
</div>
</template>
<script>
export default {
name: "TodoList",
emits: ["removeTodo", "completEmit"],
props: {
msg: String,
todos: Array,
val: String
},
data() {
return {};
},
methods: {
deleteTodo(index) {
return console.log(this.todos[index].text);
}
}
};
</script>
Related
well i'm new to vue js and developing an application with a search function.
This is my component where the search results will be rendered.
<script>
import RecipeItem from "../recipe/RecipeItem";
import { baseApiUrl } from "#/global";
import axios from "axios";
import PageTitle from "../template/PageTitle";
export default {
name: "Search",
components: { PageTitle, RecipeItem },
data() {
return {
recipes: [],
recipe: {},
search: '',
}
},
methods: {
getRecipes() {
const url = `${baseApiUrl}/search?search=${this.search}`;
axios(url).then((res) => {
this.recipes = res.data;
});
}
},
watch: {
search() {
const route = {
name: 'searchRecipes'
}
if(this.search !== '') {
route.query = {
search: this.search
}
}
},
'$route.query.search': {
immediate: true,
handler(value) {
this.search = value
}
}
},
};
</script>
<template>
<div class="recipes-by-category">
<form class="search">
<router-link :to="{ path: '/search', query: { search: search }}">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
ok so far it does what it should do, searches and prints the result on the screen.
But as you can see I created the search field inside it and I want to take it out of the search result component and insert it in my header component which makes more sense for it to be there.
but I'm not able to render the result in my search result component with my search field in the header component.
but I'm not able to render the result in my search result component with my search field in the header component.
Header component
<template>
<header class="header">
<form class="search">
<input v-model="search" #keyup.enter="getRecipes()" placeholder="Search recipe" />
<router-link :to="{ path: '/search', query: { search: this.search }}">
<button type="submit">
<font-icon class="icon" :icon="['fas', 'search']"></font-icon>
</button>
</router-link>
</form>
<div class="menu">
<ul class="menu-links">
<div class="item-home">
<li><router-link to="/">Home</router-link></li>
</div>
<div class="item-recipe">
<li>
<router-link to="/"
>Recipes
<font-icon class="icon" :icon="['fa', 'chevron-down']"></font-icon>
</router-link>
<Dropdown class="mega-menu" title="Recipes" />
</li>
</div>
<div class="item-login">
<li>
<router-link to="/auth" v-if="hideUserDropdown">Login</router-link>
<Userdropdown class="user" v-if="!hideUserDropdown" />
</li>
</div>
</ul>
</div>
</header>
</template>
Result component
<template>
<div class="recipes-by-category">
<div class="result-search">
<ul>
<li v-for="(recipe, i) in recipes" :key="i">
<RecipeItem :recipe="recipe" />
</li>
</ul>
</div>
</div>
</template>
Keep the state variables in whatever parent component is common to both the header component and the results component. For example, if you have a Layout component something like this:
<!-- this is the layout component -->
<template>
<HeaderWithSearch v-on:newResults="someFuncToUpdateState" />
<ResultsComponent v-bind:results="resultsState" />
</template>
<!-- state and function to update state are in a script here... -->
When the search bar returns results you need to pass that data "up" to the parent component with an $emit call, then the parent component can then pass that state back down to the results component using normal props.
Check out this documentation: https://v2.vuejs.org/v2/guide/components-custom-events.html
Be sure to pay special attention to the .sync part of the documentation and determine if that's something you need to implement as well.
Unless you want to use a more complicated state management library like vuex (which shouldn't be necessary in this case) you can just keep state in a common parent and use $emit to pass up and props to pass down.
I have a web app which shows the blog posts in a grid. But the BlogCards component in the Home.vue just outputs nothing, whereas it should output the blogs in a grid format. All the datas are stored in firebase. If I go to /blogs, I can see the blogs in grid format, but it doesn't work on the Home.vue. It also spits out the Vue Warn: property or method "blogPostsCards" is not defined on the instance but referenced during render.
I took this code from this tutorial at 5:31:05 minute mark.
Any solution to this problem.
Home.vue
<template>
<div class="home">
<BlogPost :post="post" v-for="(post, index) in blogPostsFeed" :key="index" />
<div class="blog-card-wrap">
<div class="container">
<h3>View more recent blogs</h3>
<div class="blog-cards">
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BlogPost from '../components/BlogPost.vue'
import BlogCards from '../components/BlogCards.vue'
export default {
name: "Home",
components: {
BlogPost,
BlogCards,
Arrow
},
computed : {
blogPostsCards() {
return this.$store.getters.blogPostsCards;
},
blogPostsFeed() {
return this.$store.getters.blogPostsFeed;
},
}
};
</script>
BlogCards.vue
<template>
<div class="blog-card">
<img :src="post.blogCoverPhoto" alt="">
<div class="info">
<h4>{{ post.blogTitle }}</h4>
<h6>Posted on: {{ new Date(post.blogDate).toLocaleString('en-us', {dateStyle: "long"})}}</h6>
<router-link class="link" to="#" >
View Post <Arrow class="arrow" />
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "blogCard",
props: ["post"],
computed: {
editPost() {
return this.$store.state.editPost
},
}
}
</script>
And getter function in store/index.js
getters:{
blogPostsFeed(state){
return state.blogPosts.slice(0,2);
},
blogPostsCards(state) {
return state.blogPosts.slice(2,6);
},
},
<BlogCards :post="post" v-for="(post, index) in blogPostsCard" :key="index" />
In your Home.vue >> change blogPostsCard to blogPostsCards because you use blogPostsCards in your computed so it gives you that error.
UPDATE
Was able to make it work, but got one last problem. Updated code is here:
VueJs not working on first click or first event
-----------------------------------------------------------
I've been trying to find out a way for the components inside a loop to not act as one.
I have a loop (3 divs), and inside the loop, I have 2 textboxes. But whenever I enter a value in any of them, the value is populated to everyone.
Can anyone help me separate those components?
I'm trying to make the parent div (1st loop) dynamic. So the children components (2nd loop) should be acting separately with their own grandparent components (textbox).
Here's my code:
<div id="app">
<div v-for="(ctr, c) in 3" :key="c">
<button #click="input_add">1st</button>
<div>
<div v-for="(input, act) in inputs" :key="act.id">
<input type="text" v-model="input.name">
<input type="text" v-model="input.time">
<button #click="input_remove(act)">Delete</button>
<button #click="input_add">Add row</button>
</div>
</div>
{{ inputs }}
</div>
</div>
const app = new Vue({
el: "#app",
data: {
inputs: [],
counter: 0,
},
methods: {
input_add() {
this.inputs.push({
id: this.counter + 1,
day: null,
name: null,
time: null,
})
this.counter += 1
},
input_remove(index) {
this.inputs.splice(index,1)
this.counter -= 1
}
}
});
Result:
as I mentioned in the comment, you should create a component for the iterated item.
parent component:
<div v-for="(item, index) in array" :key="index">
<child :item="item" />
</div>
Now you sent the item as prop. Let's catch it in child.
child components:
<div>
<input type="text" v-model="input.name">
<input type="text" v-model="input.time">
<button #click="input_remove(act)">Delete</button>
<button #click="input_add">Add row</button>
</div>
{{ inputs }}
props: [item], // I am not sure you need it or not, BUT just showing how to do it.
data() {return { // your datas };},
methods: {
// your methods...
},
//and else...
Now each iterated item can control self only. I am hope it make sense now.
then build the buttons an input in child component. After that you can apply the events for just clicked item.
You should use Array of Objects. Here's a codesandbox. This way everytime you add a new object to the array, a new index is created with a new name and time ready to be filled in.
<template>
<div id="app">
<img width="25%" src="./assets/logo.png">
<div v-for="item in basic" :key="item.id">
<button #click="addRow">Add row</button>
<input type="text" v-model="item.name">
<input type="text" v-model="item.time">
{{ item.name }} - {{ item.time }}
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
id: 1,
basic: [{ name: "", time: "" }]
};
},
methods: {
addRow() {
console.log("added");
this.id += 1;
this.basic.push({
name: "",
time: ""
});
}
}
};
</script>
I'm making an app with VueJS and Laravel. I'm getting an error, the push is not a function when I clicked an add to cart button. Everything is working here fine but methods addToCart gives error push is not a function. when I first click add to cart button it gives that error and once I refresh the page I can see a product in cart and again if click adds to cart button this time error is not seen, works perfectly. when cart[] is empty it gives error push is not a function, but when cart[] has at least one element I don't get that error.
Any help would be greatly appreciated.
productlist.vue
<template>
<div class="col-md-7">
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<p class="card-text">{{ product.price }}
</p>
<button class="btn btn-primary" #click="addProductToCart(product)">Add to cart</button>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default{
props:['product'],
data(){
return{
}
},
methods:{
addProductToCart(product){
axios.post('/products/create',{
product : product
}).then((response)=>{
console.log(response)
this.$emit('addedToCart',product)
});
}
}
}
</script>
cart.vue
<template>
<div class="col-md-4">
<li v-for="(item,index) in cart">
{{ item.name }}-{{ item.price }}
<button #click="removeitem(index)">Remove</button>
</li>
</div>
</template>
<script type="text/javascript">
export default{
props:['cart'],
}
</script>
Main.vue
<template>
<div>
<div class="col-md-7" v-for="product in products">
<Productlist :product="product" #addedToCart="addedToCart"></Productlist>
</div>
<Cart :cart="cart" ></Cart>
</div>
</template>
<script type="text/javascript">
import Productlist from './Productlist';
import Cart from './Cart';
export default{
data(){
return{
products:[],
cart: [ ]
}
},
mounted() {
//get all products and show in page
axios.get('/products')
.then((response)=>{
this.products = response.data;
});
// get only those products that are added to cart
axios.get('/list')
.then((response)=>{
this.cart= response.data;
console.log(response)
});
},
methods:{
addedToCart(product){
this.cart.push(product)
}
},
components:{Productlist,Cart}
}
</script>
i don't sure if this will resolve your problem but is unnecessary do:
<button class="btn btn-primary" #click="addProductToCart(product)">Add to cart</button>
because you have product as prop of the component, should be #click="addProductToCart" without problem.
and your method should be so:
addProductToCart() {
axios
.post('/products/create', {
product: this.product,
})
.then(response => {
console.log(response);
this.$emit('addedToCart', this.product);
});
}
One thing more, use kebab-case to call the key string when you emit to the parent component:
this.$emit('addedToCart', this.product);
replace it with:
this.$emit('added-to-cart', this.product);
Then in your parent component you have:
<Productlist :product="product" #addedToCart="addedToCart"></Productlist>
replace it with:
<Productlist :product="product" #added-to-cart="addedToCart"></Productlist>
I guess this last things will resolve your problem according the Vue documentation.
I'm having an issue when using a child component, a list does not update based on a prop passed into it.
If the comments array data changes, the list will not update when it uses a child component <comment></comment>.
TopHeader template:
<template>
<ul v-for="comment in comments">
// If don't use a child component, it updates whenever `comments` array changes:
<li>
<div>
/r/{{comment.data.subreddit}} ·
{{comment.data.score}}
</div>
<div class="comment" v-html="comment.data.body"></div>
<hr>
</li>
</ul>
</template>
TopHeader component:
import Comment from 'components/Comment'
export default {
name: 'top-header',
components: {
Comment
},
data () {
return {
username: '',
comments: []
}
},
methods: {
fetchData: function(username){
var vm = this;
this.$http.get(`https://www.reddit.com/user/${username}/comments.json?jsonp=`)
.then(function(response){
vm.$set(vm, 'comments', response.body.data.children);
});
}
}
}
However, if I use a child component it does not update.
Modified TopHeader template:
<template>
<ul v-for="comment in comments">
// If I instead use a component with prop data, it does not update
<comment :data="comment.data"></comment>
</ul>
</template>
Comment child template:
<template>
<li>
<div>
/r/{{subreddit}} ·
{{score}}
</div>
<div class="comment" v-html="body"></div>
<hr>
</li>
</template>
Comment child component:
export default {
name: 'comment',
props: ['data'],
data() {
return {
body: this.data.body,
subreddit: this.data.subreddit,
score: this.data.score,
}
}
}
Move the v-for statement to the component <comment>. With the v-for on the ul-tag you would repeat the ul tag. See http://codepen.io/tuelsch/pen/YNOqYR again for an example.
<div id="app">
<button v-on:click="fetch">Fetch data</button>
<ul>
<comment v-for="comment in comments" v-bind:comment="comment" />
</ul>
</div>