My page reloads, and the added task wont register - vue.js

I am creating a to do list for my exam. For some reason the page keep reload when I click add task, and the tasks wont register. I am new to Vue.js and Javascript.
I have problems with finding the issue. It is a simple code, not to complex, but the add task part is not working.
Here is my code:
<template >
<section class="todolist">
<h1 class="title">{{ title }}</h1>
<form class="container">
<h3 class="container__title">New Task </h3>
<input class="container__input" type="text" placeholder="Enter task" v-model="task">
<button class="container__button" #click="addPlanningTask">Add Task</button>
<h3 class="container__title-second">To Do List</h3>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<span>{{ task.name }} </span>
<button class="todolist__button" #click="deletePlanningTask">Remove</button>
</li>
</ul>
<h4 class="container__list-title" v-if="task.length === 0">
List is empty!</h4>
</form>
</section>
<div class="guestlist">
<h4 class="guestlist__title">{{ invited }}</h4>
<span>{{ guestList }}</span>
<button class="guestlist__button" #click="toggleGuestList">Guestlist</button>
<div v-if="isGuestListVisible === true">
<ul>
<li v-for="guest in people" :key="guest.id.value">
{{ guest.name.first }} {{ guest.name.last }}</li>
</ul>
</div>
</div>
</template>

export default {
props: {
titleName: {
type: String,
default: 'todolist',
},
},
data() {
return {
title: 'Planning the party!',
task: '',
tasks: [{
name: ''
}],
invited: 'Who is invited?',
guestList: '',
people: [],
name: '',
isGuestListVisible: false
}
},
created() {
this.addGuest();
},
methods: {
async addGuest() {
const url = 'https://randomuser.me/api/?page=2&results=8';
const res = await fetch(url);
const { results } = await res.json();
this.people = results;
},
toggleGuestList() {
this.isGuestListVisible = !this.isGuestListVisible;
},
addPlanningTask() {
if(this.task.length === 0)
return;
this.tasks.push({
name: this.task
});
},
deletePlanningTask(index) {
this.tasks.splice(index, 1);
},
},
};
</script>

You can use #submit.prevent on form if you don't want to reload page:
new Vue({
el: "#demo",
props: {
titleName: {
type: String,
default: 'todolist',
},
},
data() {
return {
title: 'Planning the party!',
task: '',
tasks: [],
invited: 'Who is invited?',
guestList: '',
people: [],
name: '',
isGuestListVisible: false
}
},
created() {
this.addGuest();
},
methods: {
async addGuest() {
const url = 'https://randomuser.me/api/?page=2&results=8';
const res = await fetch(url);
const { results } = await res.json();
this.people = results;
},
toggleGuestList() {
this.isGuestListVisible = !this.isGuestListVisible;
},
addPlanningTask() {
if (this.task.length === 0) return;
this.tasks.push({ name: this.task });
},
deletePlanningTask(index) {
this.tasks.splice(index, 1);
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" >
<section class="todolist">
<h1 class="title">{{ title }}</h1>
<form class="container" #submit.prevent>
<h3 class="container__title">New Task </h3>
<input class="container__input" type="text" placeholder="Enter task" v-model="task">
<button class="container__button" #click="addPlanningTask">Add Task</button>
<h3 class="container__title-second">To Do List</h3>
<ul>
<li v-for="(task, index) in tasks" :key="index">
<span>{{ task.name }} </span>
<button class="todolist__button" #click="deletePlanningTask">Remove</button>
</li>
</ul>
<h4 class="container__list-title" v-if="task.length === 0">List is empty!</h4>
</form>
</section>
<div class="guestlist">
<h4 class="guestlist__title">{{ invited }}</h4>
<span>{{ guestList }}</span>
<button class="guestlist__button" #click="toggleGuestList">Guestlist</button>
<div v-if="isGuestListVisible === true">
<ul>
<li v-for="guest in people" :key="guest.id.value">
{{ guest.name.first }} {{ guest.name.last }}</li>
</ul>
</div>
</div>
</div>

Related

Vue is not update DOM when object property changes in array of objects

I have a const array of objects which set to the data property on created(), I am trying to update the object properties as the user entered the form, the Console print shows the value updated, but the DOM is changing. DOM is updated when the key value is changed too but I do not want to change the key value
Data Array:
const invoices = [
{
number: "BBRFL",
client: "Ellison Daugherty",
amount: 8800,
status: "Paid",
},
{
number: "TYUBK",
client: "Myrna Vinson",
amount: 4097,
status: "Paid",
},
{
number: "IRUBR",
client: "Mcmillan Warner",
amount: 6466,
status: "Draft",
},
];
Here is the app.
const app = new Vue({
el: "#app",
components: {
"create-invoice": CreateInvoice,
"invoice-item": InvoiceItem,
},
data() {
return {
invoices: invoices,
status: null,
};
},
created: function () {
const invoices = localStorage.getItem("invoices");
if (invoices) {
this.invoices = JSON.parse(invoices);
}
},
computed: {
count: function () {
return this.filteredInvoices.length;
},
filteredInvoices: function () {
if (this.status) {
return this.invoices.filter((invoice) => invoice.status == this.status);
}
return this.invoices;
},
},
methods: {
saveInvoice(invoice) {
this.invoices.push(invoice);
},
invoiceUpdate(invoice) {
const index = this.invoices.findIndex((i) => i.number === invoice.number);
// this.invoices.splice(index, 1, invoice);
Vue.set(this.invoices[index], "client", invoice.client);
Vue.set(this.invoices[index], "amount", invoice.amount);
Vue.set(this.invoices[index], "status", invoice.status);
},
invoiceDelete(number) {
const index = this.invoices.findIndex((i) => i.number === number);
this.invoices.splice(index, 1);
},
},
watch: {
invoices: {
deep: true,
handler: function (invoices) {
localStorage.setItem("invoices", JSON.stringify(invoices));
},
},
},
template: `
<div>
<div class="row">
<div class="col-md-4 text-left">
<h1>Invoices</h1>
<p class="text-dark">There are {{count}} Total Invoices</p>
</div>
<div class="col-md-4 text-start" style="margin-top: 10px">
<label for="status">Filter</label>
<select name="filter" id="status" class="form-control" v-model="status">
<option value="Draft">Draft</option>
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
<div class="col-md-4 text-right" style="margin-top: 10px">
<button class="btn btn-primary mt-4" id="createInvoice">
New Invoice
</button>
</div>
</div>
<div class="col-md-12">
<div class="row mt-2 mb-2">
<div
class="invoice col-md-12"
>
<invoice-item
v-for="(n, index) in filteredInvoices"
:key="n.number"
:initial-client="n.client"
:initial-number="n.number"
:initial-amount="n.amount"
:initial-status="n.status"
#update-invoice="invoiceUpdate"
#delete-invoice="invoiceDelete"
></invoice-item>
</div>
<div class="text-center" v-if="filteredInvoices.length === 0"><p>No invoice found</p></div>
</div>
</div>
<create-invoice #saveInvoice="saveInvoice" ></create-invoice>
</div>
</div>
`,
});
I had tried, this.$set, Vue.set, Direct assigning to property, assingment using splice function, but none of working. It only works with the change of value of key in for loop. Which I do not want to update.
Jsfiddle
Any Help? Thanks in advance.

VUE Props still undefined how to fix it?

productPage
<template>
<div id="products" class="products">
<div class="container">
<h1 class="text-center p-5">熱銷商品</h1>
<div class="row">
<div v-for="product in products" class="col-md-3">
<div class="card product-item">
<swiper :pagination="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="image in product.data().images">
<img :src="image" class="card-img-top" alt="..." />
</swiper-slide>
</swiper>
<div class="card-body">
<div
class="d-flex flex-column justify-content-between text-center"
>
<h5 class="card-title">{{ product.data().name }}</h5>
<h5 class="card-priceS">
{{ currency(product.data().price) }}$
</h5>
<p>{{ product.data().description }}</p>
</div>
<add-to-cart
:name="product.data().name"
:price="product.data().price"
:product-id="product.id"
:product-image="getImage(product.data().images)"
>
</add-to-cart>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Produtspage',
components: {
Navbar,
Login,
Swiper,
SwiperSlide,
},
props: {
msg: String,
},
setup() {
return {
modules: [Pagination],
}
},
data() {
return {
products: [],
}
},
mounted() {
this.readData()
},
methods: {
currency,
async readData() {
const querySnapshot = await getDocs(collection(db, 'products'))
querySnapshot.forEach((doc) => {
this.products.push(doc)
console.log(doc.id, ' => ', doc.data())
console.log(doc.data())
})
},
getImage(images) {
console.log(images)
return images
},
},
}
</script>
addtocart component
<template>
<div class="add-to-cart">
<button class="btn btn-success fs-6" #click="addToCart">
<i class="fa-solid fa-cart-plus mx-1"></i>加到購物車
</button>
</div>
</template>
<script>
export default {
name: 'AddToCart',
props: {
name: String,
price: String,
productId: String,
image: String,
},
data() {
return {
item: {
productName: this.name,
productPrice: this.price,
product_id: this.productId,
product_image: this.image,
// productQuantity: 1,
},
}
},
}
</script>
I checked the Vue Devtools, only image no props success, other data does have correct props, the image prop is still undefined, the image data is url and when I console.log(images), the data is shown, but props are still undefined.
I use firestore for the project, did I do anything wrong here?
you are using the wrong name for the binded attribute that dosnt match the prop name :
you are using :product-image in productPage
<add-to-cart
:name="product.data().name"
:price="product.data().price"
:product-id="product.id"
:product-image="getImage(product.data().images)"
>
so you have to match it with productImage instead of product-image as a prop name in the addtocart component
props: {
name: String,
price: String,
productId: String,
productImage: String, // appropriate prop name
},

Pagination. How to make moving between pages by clicking on numerals

Tell me how to make it so that when you click on a button from a cycle with page numbers, this particular page opens. Switching along the arrows works for me, but I cannot understand how to switch between pages. I take data from Api. Total posts 98. It is possible to add your posts. On one page only 10 posts are shown.
My html:
<div id="app">
<div class="smallfon">
<div class="blocktwitter"><img src="src/assets/twitter.png" class="twitter"/></div>
<div class="addTextPost">Add a post</div>
<input type="text" v-model="createTitle" class="created"/>
<input type="text" v-model="createBody" class="created"/>
<div><button #click="addPost()" class="addPost">AddPost</button></div>
<div class="post1">
<div class="yourPosts">Your Posts</div>
<ul>
<li v-for="(post, index) of paginatedData" class="post">
<p><span class="boldText">Title:</span> {{ post.title }}</p>
<p><span class="boldText">Content:</span> {{ post.body }}</p>
<button #click="deleteData(index, post.id)" class="buttonDelete">Delete</button>
<button #click="visiblePostID = post.id" class="buttonChange">Change</button>
<div v-if="visiblePostID === post.id" class="modalWindow">
<div><input v-model="post.title" class="changePost"><input v-model="post.body" class="changePost"></div>
<button type="button" #click="changePost(post.id, post.title, post.body)" class="apply">To apply</button>
</div>
</li>
</ul>
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}">{{ n }} </button>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</div>
My js:
export default {
el: "#app",
data () {
return {
current: null,
page: 0,
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
watch: {
counter: function(newValue, oldValue) {
this.getData()
}
},
created(){
this.getData()
},
computed: {
evenPosts: function(posts){
return Math.ceil(this.posts.length/10);
},
paginatedData() {
const start = this.page * 10;
const end = start + 10;
return this.posts.slice(start, end);
}
},
methods: {
setCurrent: function(id) {
this.current = id;
},
getData() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(response => {
this.posts = response.data
})
},
deleteData(index, id) {
axios.delete('http://jsonplaceholder.typicode.com/posts/' + id)
.then(response => {
console.log('delete')
this.posts.splice(index, 1);
})
.catch(function(error) {
console.log(error)
})
},
addPost() {
axios.post('http://jsonplaceholder.typicode.com/posts/', {
title: this.createTitle,
body: this.createBody
}).then((response) => {
this.posts.unshift(response.data)
})
},
changePost(id, title, body) {
axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
title: title,
body: body
})
},
}
}
Screenshot of application
add click event #click="page=n" in button
<button #click="page=n" class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}">{{ n }} </button>
Codepen : https://codepen.io/anon/pen/bZOROO

Multiple Dynamic Checkboxes with input groups in vue js

I am trying to make a form which contains an input group section, in this group, there are one select box and multiple checkboxes. Checkboxes are populated based on the select box selection. There is also an add and remove button to generate and remove input group. The select box is used with v-model to filtered the checkboxes. But when I generate a new input group and make changes, all checkboxes are changed.
I want them to be isolated. How can I achieve?
Here is my Template.
<template>
<form #submit.prevent="onSubmit">
<div v-for="(variationProduct, index) in variationProducts" :key="variationProduct.id">
<div class="from-group mb-4">
<label class="col-form-label"><b>Categories :</b></label>
<select class="form-control mr-2" ref="categories" v-model="category">
<option value="0">Please select category...</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
<div v-if="hasError">
<validation-errors v-if="errors['categories.'+index]" :errors="errors">
{{ errors['categories.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="form-group mb-4">
<label class="col-form-lablel"><b>Variation Products :</b></label>
<div class="row">
<div v-for="filteredVariationProduct in filteredVariationProducts" :key="filteredVariationProduct.id">
<div class="col-12">
<input :id="filteredVariationProduct.id" :value="filteredVariationProduct.id" type="checkbox" ref="variationProducts">
<label :for="filteredVariationProduct.id">{{ filteredVariationProduct.name }}</label>
</div>
</div>
</div>
<div v-if="hasError">
<validation-errors v-if="errors['variationProducts.'+index]" :errors="errors">
{{ errors['variationProducts.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="float-right">
<button #click.prevent="add" class="btn btn-success">Add</button>
<button #click.prevent="remove(index)" class="btn btn-danger">Remove</button>
</div>
<br>
<br>
<hr>
</div>
<input type="submit" class="btn btn-primary" value="Submit">
</form>
</template>
Here is my JS.
<script>
import ValidationErrors from './ValidationErrors.vue';
export default {
components: {
'validation-errors': ValidationErrors,
},
data () {
return {
variationProducts: [],
categories: [
{ id: 1, name: 'Technology'},
{ id: 2, name: 'Business'},
{ id: 3, name: 'Marketing'},
{ id: 4, name: 'Development'},
{ id: 5, name: 'Engineering'},
],
category: 0,
activeVariationProducts: [],
count: 1,
errors: {},
hasError: false,
}
},
methods: {
add: function() {
this.count++;
this.errors = {};
this.hasError = false;
this.variationProducts.push({ id: this.count });
},
remove: function (index) {
if ( this.variationProducts.length > 0 && index > -1) {
this.variationProducts.splice(index, 1);
} else {
alert('Must have at least one input!')
}
},
onSubmit: function() {
console.log(this.$refs.variationProducts.value);
},
generateVariationProducts: function(num) {
for(let i = 1; i <= num; i++) {
let activeVariationProduct = {
id: i,
name: 'Product '+ i,
category_id: i
};
this.activeVariationProducts.push(activeVariationProduct);
}
},
},
computed : {
filteredVariationProducts: function () {
let categoryId = parseInt(this.category);
if (categoryId !== 0) {
let filteredVariationProducts = this.activeVariationProducts.filter((variationProduct) => {
return variationProduct.category_id === categoryId;
});
return filteredVariationProducts;
} else {
let filteredVariationProducts = this.activeVariationProducts;
return filteredVariationProducts;
}
}
},
created () {
this.variationProducts.push({ id: this.count });
this.generateVariationProducts(10);
},
}
</script>
Here is a sample code. This code Shows how you can use multiple Checkboxes that is generated dynamically and how to make them isolated -
new Vue({
el : "#app",
data : {
Items : ["One", "Two", "Three"],
newCheckbox : "",
SelectedItems : {
'One' : "",
'Two' : "",
'Three' : "",
},
},
methods:{
add:function(){
Vue.set(this.SelectedItems, this.newCheckbox, false);
this.Items.push(this.newCheckbox);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js" type="text/javascript"></script>
<div id="app">
<div>
<label v-for="(item, index) in Items">
<input type="checkbox" v-bind:key="index" v-model="SelectedItems[item]"> {{ item }}
</label>
</div>
<div>
<input type="text" v-model="newCheckbox">
<button #click="add">Add</button>
</div>
<div>
Output : {{ SelectedItems }}
</div>
</div>
You can dynamically add new checkbox and still they are isolated

Is it possible to sync Vuejs components displayed multiple times on the same page?

I have a web page that displays items. For each items there is a button (vuejs component) which allow user to toggle (add/remove) this item to his collection.
Here is the component:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': dAdded, 'btn-primary': !dAdded}">{{ dText }}</button>
</template>
<script>
export default {
props: {
added: Boolean,
text: String,
id: Number,
},
data() {
return {
dAdded: this.added,
dText: this.text,
dId: this.id
}
},
watch: {
added: function(newVal, oldVal) { // watch it
this.dAdded = this.added
},
text: function(newVal, oldVal) { // watch it
this.dText = this.text
},
id: function(newVal, oldVal) { // watch it
this.dId = this.id
}
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.dId
}))
.then(response => {
this.dText = response.data.message
let success = response.data.success
this.dText = response.data.new_text
if (success) {
this.dAdded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.dId);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
}
}
</script>
For each item, the user can also open a modal, loaded by a click on this link:
<a href="#" data-toggle="modal" data-target="#popModal" #click="id = {{$pop->id}}">
<figure>
<img class="card-img-top" src="{{ URL::asset($pop->img_path) }}" alt="Card image cap">
</figure>
</a>
The modal is also a Vuejs component:
<template>
<section id="pop" class="h-100">
<div class="card">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-1 flex-column others d-none d-xl-block">
<div class="row flex-column h-100">
<div v-for="other_pop in pop.other_pops" class="col">
<a :href="route('frontend.pop.collection.detail', {collection: pop.collection.slug, pop: other_pop.slug})">
<img :src="other_pop.img_path" :alt="'{{ other_pop.name }}'" class="img-fluid">
</a>
</div>
<div class="col active order-3">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
</div>
</div>
<div class="col-12 col-lg-6 content text-center">
<div class="row">
<div class="col-12">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
<div class="col-6 text-right">
<toggle-pop :id="pop.id" :added="pop.in_user_collection" :text="pop.in_user_collection ? 'Supprimer' : 'Ajouter'"></toggle-pop>
</div>
<div class="col-6 text-left">
<!-- <btnaddpopwhishlist :pop_id="propid" :added="pop.in_user_whishlist" :text="pop.in_user_whishlist ? 'Supprimer' : 'Ajouter'"></btnaddpopwhishlist> -->
</div>
</div>
</div>
<div class="col-12 col-lg-5 infos">
<div class="header">
<h1 class="h-100">{{ pop.name }}</h1>
</div>
<div class="card yellow">
<div class="card p-0">
<div class="container-fluid">
<div class="row">
<div class="col-3 py-2">
</div>
<div class="col-6 py-2 bg-lightgray">
<h4>Collection:</h4>
<h3>{{ pop.collection ? pop.collection.name : '' }}</h3>
</div>
<div class="col-3 py-2 bg-lightgray text-center">
<a :href="route('frontend.index') + 'collections/' + pop.collection.slug" class="btn-round right white"></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
id: Number
},
data() {
return {
pop: {
collection: {
}
}
}
},
ready: function() {
if (this.propid != -1)
this.fetchData()
},
watch: {
id: function(newVal, oldVal) { // watch it
// console.log('Prop changed: ', newVal, ' | was: ', oldVal)
this.fetchData()
}
},
computed: {
imgSrc: function() {
if (this.pop.img_path)
return 'storage/images/pops/' + this.pop.img_path
else
return ''
}
},
methods: {
fetchData() {
axios.get(route('frontend.api.v1.pops.show', this.id))
.then(response => {
// JSON responses are automatically parsed.
// console.log(response.data.data.collection)
this.pop = response.data.data
})
.catch(e => {
this.errors.push(e)
})
// console.log('fetchData')
}
}
}
</script>
Here is my app.js script :
window.Vue = require('vue');
Vue.component('pop-modal', require('./components/PopModal.vue'));
Vue.component('toggle-pop', require('./components/TogglePop.vue'));
const app = new Vue({
el: '#app',
props: {
id: Number
}
});
I would like to sync the states of the component named toggle-pop, how can I achieve this ? One is rendered by Blade template (laravel) and the other one by pop-modal component. But they are just the same, displayed at different places.
Thanks.
You could pass a state object as a property to the toggle-pop components. They could use this property to store/modify their state. In this way you can have multiple sets of components sharing state.
Your component could become:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': sstate.added, 'btn-primary': !sstate.added}">{{ sstate.text }}</button>
</template>
<script>
export default {
props: {
sstate: {
type: Object,
default: function() {
return { added: false, text: "", id: -1 };
}
}
},
data() {
return {};
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.sstate.id
}))
.then(response => {
this.sstate.text = response.data.message
let success = response.data.success
this.sstate.text = response.data.new_text
if (success) {
this.sstate.ddded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.sstate.id);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
};
</script>
Live demo
https://codesandbox.io/s/vq8r33o1w7
If you are 100% sure that all toggle-pop components should always have the same state, you can choose to not define data as a function. Just declare it as an object.
data: {
dAdded: this.added,
dText: this.text,
dId: this.id
}
In https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, it mentions
a component’s data option must be a function, so that each instance
can maintain an independent copy of the returned data object
If Vue didn’t have this rule, clicking on one button would affect the
data of all other instances
Since you want to sync the data of all toggle-pop component instances, you don't have to follow the data option must be a function rule.